Total Complexity | 1088 |
Total Lines | 6590 |
Duplicated Lines | 0 % |
Changes | 0 |
Complex classes like ContentObjectRenderer often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use ContentObjectRenderer, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
89 | class ContentObjectRenderer implements LoggerAwareInterface |
||
90 | { |
||
91 | use LoggerAwareTrait; |
||
92 | |||
93 | /** |
||
94 | * @var ContainerInterface|null |
||
95 | */ |
||
96 | protected $container; |
||
97 | |||
98 | /** |
||
99 | * @var array |
||
100 | */ |
||
101 | public $align = [ |
||
102 | 'center', |
||
103 | 'right', |
||
104 | 'left' |
||
105 | ]; |
||
106 | |||
107 | /** |
||
108 | * stdWrap functions in their correct order |
||
109 | * |
||
110 | * @see stdWrap() |
||
111 | * @var string[] |
||
112 | */ |
||
113 | public $stdWrapOrder = [ |
||
114 | 'stdWrapPreProcess' => 'hook', |
||
115 | // this is a placeholder for the first Hook |
||
116 | 'cacheRead' => 'hook', |
||
117 | // this is a placeholder for checking if the content is available in cache |
||
118 | 'setContentToCurrent' => 'boolean', |
||
119 | 'setContentToCurrent.' => 'array', |
||
120 | 'addPageCacheTags' => 'string', |
||
121 | 'addPageCacheTags.' => 'array', |
||
122 | 'setCurrent' => 'string', |
||
123 | 'setCurrent.' => 'array', |
||
124 | 'lang.' => 'array', |
||
125 | 'data' => 'getText', |
||
126 | 'data.' => 'array', |
||
127 | 'field' => 'fieldName', |
||
128 | 'field.' => 'array', |
||
129 | 'current' => 'boolean', |
||
130 | 'current.' => 'array', |
||
131 | 'cObject' => 'cObject', |
||
132 | 'cObject.' => 'array', |
||
133 | 'numRows.' => 'array', |
||
134 | 'preUserFunc' => 'functionName', |
||
135 | 'stdWrapOverride' => 'hook', |
||
136 | // this is a placeholder for the second Hook |
||
137 | 'override' => 'string', |
||
138 | 'override.' => 'array', |
||
139 | 'preIfEmptyListNum' => 'listNum', |
||
140 | 'preIfEmptyListNum.' => 'array', |
||
141 | 'ifNull' => 'string', |
||
142 | 'ifNull.' => 'array', |
||
143 | 'ifEmpty' => 'string', |
||
144 | 'ifEmpty.' => 'array', |
||
145 | 'ifBlank' => 'string', |
||
146 | 'ifBlank.' => 'array', |
||
147 | 'listNum' => 'listNum', |
||
148 | 'listNum.' => 'array', |
||
149 | 'trim' => 'boolean', |
||
150 | 'trim.' => 'array', |
||
151 | 'strPad.' => 'array', |
||
152 | 'stdWrap' => 'stdWrap', |
||
153 | 'stdWrap.' => 'array', |
||
154 | 'stdWrapProcess' => 'hook', |
||
155 | // this is a placeholder for the third Hook |
||
156 | 'required' => 'boolean', |
||
157 | 'required.' => 'array', |
||
158 | 'if.' => 'array', |
||
159 | 'fieldRequired' => 'fieldName', |
||
160 | 'fieldRequired.' => 'array', |
||
161 | 'csConv' => 'string', |
||
162 | 'csConv.' => 'array', |
||
163 | 'parseFunc' => 'objectpath', |
||
164 | 'parseFunc.' => 'array', |
||
165 | 'HTMLparser' => 'boolean', |
||
166 | 'HTMLparser.' => 'array', |
||
167 | 'split.' => 'array', |
||
168 | 'replacement.' => 'array', |
||
169 | 'prioriCalc' => 'boolean', |
||
170 | 'prioriCalc.' => 'array', |
||
171 | 'char' => 'integer', |
||
172 | 'char.' => 'array', |
||
173 | 'intval' => 'boolean', |
||
174 | 'intval.' => 'array', |
||
175 | 'hash' => 'string', |
||
176 | 'hash.' => 'array', |
||
177 | 'round' => 'boolean', |
||
178 | 'round.' => 'array', |
||
179 | 'numberFormat.' => 'array', |
||
180 | 'expandList' => 'boolean', |
||
181 | 'expandList.' => 'array', |
||
182 | 'date' => 'dateconf', |
||
183 | 'date.' => 'array', |
||
184 | 'strtotime' => 'strtotimeconf', |
||
185 | 'strtotime.' => 'array', |
||
186 | 'strftime' => 'strftimeconf', |
||
187 | 'strftime.' => 'array', |
||
188 | 'age' => 'boolean', |
||
189 | 'age.' => 'array', |
||
190 | 'case' => 'case', |
||
191 | 'case.' => 'array', |
||
192 | 'bytes' => 'boolean', |
||
193 | 'bytes.' => 'array', |
||
194 | 'substring' => 'parameters', |
||
195 | 'substring.' => 'array', |
||
196 | 'cropHTML' => 'crop', |
||
197 | 'cropHTML.' => 'array', |
||
198 | 'stripHtml' => 'boolean', |
||
199 | 'stripHtml.' => 'array', |
||
200 | 'crop' => 'crop', |
||
201 | 'crop.' => 'array', |
||
202 | 'rawUrlEncode' => 'boolean', |
||
203 | 'rawUrlEncode.' => 'array', |
||
204 | 'htmlSpecialChars' => 'boolean', |
||
205 | 'htmlSpecialChars.' => 'array', |
||
206 | 'encodeForJavaScriptValue' => 'boolean', |
||
207 | 'encodeForJavaScriptValue.' => 'array', |
||
208 | 'doubleBrTag' => 'string', |
||
209 | 'doubleBrTag.' => 'array', |
||
210 | 'br' => 'boolean', |
||
211 | 'br.' => 'array', |
||
212 | 'brTag' => 'string', |
||
213 | 'brTag.' => 'array', |
||
214 | 'encapsLines.' => 'array', |
||
215 | 'keywords' => 'boolean', |
||
216 | 'keywords.' => 'array', |
||
217 | 'innerWrap' => 'wrap', |
||
218 | 'innerWrap.' => 'array', |
||
219 | 'innerWrap2' => 'wrap', |
||
220 | 'innerWrap2.' => 'array', |
||
221 | 'preCObject' => 'cObject', |
||
222 | 'preCObject.' => 'array', |
||
223 | 'postCObject' => 'cObject', |
||
224 | 'postCObject.' => 'array', |
||
225 | 'wrapAlign' => 'align', |
||
226 | 'wrapAlign.' => 'array', |
||
227 | 'typolink.' => 'array', |
||
228 | 'wrap' => 'wrap', |
||
229 | 'wrap.' => 'array', |
||
230 | 'noTrimWrap' => 'wrap', |
||
231 | 'noTrimWrap.' => 'array', |
||
232 | 'wrap2' => 'wrap', |
||
233 | 'wrap2.' => 'array', |
||
234 | 'dataWrap' => 'dataWrap', |
||
235 | 'dataWrap.' => 'array', |
||
236 | 'prepend' => 'cObject', |
||
237 | 'prepend.' => 'array', |
||
238 | 'append' => 'cObject', |
||
239 | 'append.' => 'array', |
||
240 | 'wrap3' => 'wrap', |
||
241 | 'wrap3.' => 'array', |
||
242 | 'orderedStdWrap' => 'stdWrap', |
||
243 | 'orderedStdWrap.' => 'array', |
||
244 | 'outerWrap' => 'wrap', |
||
245 | 'outerWrap.' => 'array', |
||
246 | 'insertData' => 'boolean', |
||
247 | 'insertData.' => 'array', |
||
248 | 'postUserFunc' => 'functionName', |
||
249 | 'postUserFuncInt' => 'functionName', |
||
250 | 'prefixComment' => 'string', |
||
251 | 'prefixComment.' => 'array', |
||
252 | 'editIcons' => 'string', |
||
253 | 'editIcons.' => 'array', |
||
254 | 'editPanel' => 'boolean', |
||
255 | 'editPanel.' => 'array', |
||
256 | 'cacheStore' => 'hook', |
||
257 | // this is a placeholder for storing the content in cache |
||
258 | 'stdWrapPostProcess' => 'hook', |
||
259 | // this is a placeholder for the last Hook |
||
260 | 'debug' => 'boolean', |
||
261 | 'debug.' => 'array', |
||
262 | 'debugFunc' => 'boolean', |
||
263 | 'debugFunc.' => 'array', |
||
264 | 'debugData' => 'boolean', |
||
265 | 'debugData.' => 'array' |
||
266 | ]; |
||
267 | |||
268 | /** |
||
269 | * Class names for accordant content object names |
||
270 | * |
||
271 | * @var array |
||
272 | */ |
||
273 | protected $contentObjectClassMap = []; |
||
274 | |||
275 | /** |
||
276 | * Loaded with the current data-record. |
||
277 | * |
||
278 | * If the instance of this class is used to render records from the database those records are found in this array. |
||
279 | * The function stdWrap has TypoScript properties that fetch field-data from this array. |
||
280 | * |
||
281 | * @var array |
||
282 | * @see start() |
||
283 | */ |
||
284 | public $data = []; |
||
285 | |||
286 | /** |
||
287 | * @var string |
||
288 | */ |
||
289 | protected $table = ''; |
||
290 | |||
291 | /** |
||
292 | * Used for backup |
||
293 | * |
||
294 | * @var array |
||
295 | */ |
||
296 | public $oldData = []; |
||
297 | |||
298 | /** |
||
299 | * If this is set with an array before stdWrap, it's used instead of $this->data in the data-property in stdWrap |
||
300 | * |
||
301 | * @var string |
||
302 | */ |
||
303 | public $alternativeData = ''; |
||
304 | |||
305 | /** |
||
306 | * Used by the parseFunc function and is loaded with tag-parameters when parsing tags. |
||
307 | * |
||
308 | * @var array |
||
309 | */ |
||
310 | public $parameters = []; |
||
311 | |||
312 | /** |
||
313 | * @var string |
||
314 | */ |
||
315 | public $currentValKey = 'currentValue_kidjls9dksoje'; |
||
316 | |||
317 | /** |
||
318 | * This is set to the [table]:[uid] of the record delivered in the $data-array, if the cObjects CONTENT or RECORD is in operation. |
||
319 | * Note that $GLOBALS['TSFE']->currentRecord is set to an equal value but always indicating the latest record rendered. |
||
320 | * |
||
321 | * @var string |
||
322 | */ |
||
323 | public $currentRecord = ''; |
||
324 | |||
325 | /** |
||
326 | * Set in RecordsContentObject and ContentContentObject to the current number of records selected in a query. |
||
327 | * |
||
328 | * @var int |
||
329 | */ |
||
330 | public $currentRecordTotal = 0; |
||
331 | |||
332 | /** |
||
333 | * Incremented in RecordsContentObject and ContentContentObject before each record rendering. |
||
334 | * |
||
335 | * @var int |
||
336 | */ |
||
337 | public $currentRecordNumber = 0; |
||
338 | |||
339 | /** |
||
340 | * Incremented in RecordsContentObject and ContentContentObject before each record rendering. |
||
341 | * |
||
342 | * @var int |
||
343 | */ |
||
344 | public $parentRecordNumber = 0; |
||
345 | |||
346 | /** |
||
347 | * 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. |
||
348 | * |
||
349 | * @var array |
||
350 | */ |
||
351 | public $parentRecord = []; |
||
352 | |||
353 | /** |
||
354 | * @var string|int |
||
355 | */ |
||
356 | public $checkPid_badDoktypeList = PageRepository::DOKTYPE_RECYCLER; |
||
357 | |||
358 | /** |
||
359 | * This will be set by typoLink() to the url of the most recent link created. |
||
360 | * |
||
361 | * @var string |
||
362 | */ |
||
363 | public $lastTypoLinkUrl = ''; |
||
364 | |||
365 | /** |
||
366 | * DO. link target. |
||
367 | * |
||
368 | * @var string |
||
369 | */ |
||
370 | public $lastTypoLinkTarget = ''; |
||
371 | |||
372 | /** |
||
373 | * @var array |
||
374 | */ |
||
375 | public $lastTypoLinkLD = []; |
||
376 | |||
377 | /** |
||
378 | * array that registers rendered content elements (or any table) to make sure they are not rendered recursively! |
||
379 | * |
||
380 | * @var array |
||
381 | */ |
||
382 | public $recordRegister = []; |
||
383 | |||
384 | /** |
||
385 | * Containing hook objects for stdWrap |
||
386 | * |
||
387 | * @var array |
||
388 | */ |
||
389 | protected $stdWrapHookObjects = []; |
||
390 | |||
391 | /** |
||
392 | * Containing hook objects for getImgResource |
||
393 | * |
||
394 | * @var array |
||
395 | */ |
||
396 | protected $getImgResourceHookObjects; |
||
397 | |||
398 | /** |
||
399 | * @var File|FileReference|Folder|string|null Current file objects (during iterations over files) |
||
400 | */ |
||
401 | protected $currentFile; |
||
402 | |||
403 | /** |
||
404 | * Set to TRUE by doConvertToUserIntObject() if USER object wants to become USER_INT |
||
405 | * @var bool |
||
406 | */ |
||
407 | public $doConvertToUserIntObject = false; |
||
408 | |||
409 | /** |
||
410 | * Indicates current object type. Can hold one of OBJECTTYPE_ constants or FALSE. |
||
411 | * The value is set and reset inside USER() function. Any time outside of |
||
412 | * USER() it is FALSE. |
||
413 | * @var bool |
||
414 | */ |
||
415 | protected $userObjectType = false; |
||
416 | |||
417 | /** |
||
418 | * @var array |
||
419 | */ |
||
420 | protected $stopRendering = []; |
||
421 | |||
422 | /** |
||
423 | * @var int |
||
424 | */ |
||
425 | protected $stdWrapRecursionLevel = 0; |
||
426 | |||
427 | /** |
||
428 | * @var TypoScriptFrontendController|null |
||
429 | */ |
||
430 | protected $typoScriptFrontendController; |
||
431 | |||
432 | /** |
||
433 | * Request pointer, if injected. Use getRequest() instead of reading this property directly. |
||
434 | * |
||
435 | * @var ServerRequestInterface|null |
||
436 | */ |
||
437 | private ?ServerRequestInterface $request = null; |
||
438 | |||
439 | /** |
||
440 | * Indicates that object type is USER. |
||
441 | * |
||
442 | * @see ContentObjectRender::$userObjectType |
||
443 | */ |
||
444 | const OBJECTTYPE_USER_INT = 1; |
||
445 | /** |
||
446 | * Indicates that object type is USER. |
||
447 | * |
||
448 | * @see ContentObjectRender::$userObjectType |
||
449 | */ |
||
450 | const OBJECTTYPE_USER = 2; |
||
451 | |||
452 | /** |
||
453 | * @param TypoScriptFrontendController $typoScriptFrontendController |
||
454 | * @param ContainerInterface $container |
||
455 | */ |
||
456 | public function __construct(TypoScriptFrontendController $typoScriptFrontendController = null, ContainerInterface $container = null) |
||
457 | { |
||
458 | $this->typoScriptFrontendController = $typoScriptFrontendController; |
||
459 | $this->contentObjectClassMap = $GLOBALS['TYPO3_CONF_VARS']['FE']['ContentObjects'] ?? []; |
||
460 | $this->container = $container; |
||
461 | } |
||
462 | |||
463 | public function setRequest(ServerRequestInterface $request): void |
||
464 | { |
||
465 | $this->request = $request; |
||
466 | } |
||
467 | |||
468 | /** |
||
469 | * Prevent several objects from being serialized. |
||
470 | * If currentFile is set, it is either a File or a FileReference object. As the object itself can't be serialized, |
||
471 | * we have store a hash and restore the object in __wakeup() |
||
472 | * |
||
473 | * @return array |
||
474 | */ |
||
475 | public function __sleep() |
||
487 | } |
||
488 | |||
489 | /** |
||
490 | * Restore currentFile from hash. |
||
491 | * If currentFile references a File, the identifier equals file identifier. |
||
492 | * If it references a FileReference the identifier equals the uid of the reference. |
||
493 | */ |
||
494 | public function __wakeup() |
||
495 | { |
||
496 | if (isset($GLOBALS['TSFE'])) { |
||
497 | $this->typoScriptFrontendController = $GLOBALS['TSFE']; |
||
498 | } |
||
499 | if ($this->currentFile !== null && is_string($this->currentFile)) { |
||
500 | [$objectType, $identifier] = explode(':', $this->currentFile, 2); |
||
501 | try { |
||
502 | if ($objectType === 'File') { |
||
503 | $this->currentFile = GeneralUtility::makeInstance(ResourceFactory::class)->retrieveFileOrFolderObject($identifier); |
||
504 | } elseif ($objectType === 'FileReference') { |
||
505 | $this->currentFile = GeneralUtility::makeInstance(ResourceFactory::class)->getFileReferenceObject($identifier); |
||
506 | } |
||
507 | } catch (ResourceDoesNotExistException $e) { |
||
508 | $this->currentFile = null; |
||
509 | } |
||
510 | } |
||
511 | $this->logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(__CLASS__); |
||
512 | $this->container = GeneralUtility::getContainer(); |
||
513 | |||
514 | // We do not derive $this->request from globals here. The request is expected to be injected |
||
515 | // using setRequest() after deserialization or with start(). |
||
516 | // (A fallback to $GLOBALS['TYPO3_REQUEST'] is available in getRequest() for BC) |
||
517 | } |
||
518 | |||
519 | /** |
||
520 | * Allow injecting content object class map. |
||
521 | * |
||
522 | * This method is private API, please use configuration |
||
523 | * $GLOBALS['TYPO3_CONF_VARS']['FE']['ContentObjects'] to add new content objects |
||
524 | * |
||
525 | * @internal |
||
526 | * @param array $contentObjectClassMap |
||
527 | */ |
||
528 | public function setContentObjectClassMap(array $contentObjectClassMap) |
||
529 | { |
||
530 | $this->contentObjectClassMap = $contentObjectClassMap; |
||
531 | } |
||
532 | |||
533 | /** |
||
534 | * Register a single content object name to class name |
||
535 | * |
||
536 | * This method is private API, please use configuration |
||
537 | * $GLOBALS['TYPO3_CONF_VARS']['FE']['ContentObjects'] to add new content objects |
||
538 | * |
||
539 | * @param string $className |
||
540 | * @param string $contentObjectName |
||
541 | * @internal |
||
542 | */ |
||
543 | public function registerContentObjectClass($className, $contentObjectName) |
||
544 | { |
||
545 | $this->contentObjectClassMap[$contentObjectName] = $className; |
||
546 | } |
||
547 | |||
548 | /** |
||
549 | * Class constructor. |
||
550 | * Well, it has to be called manually since it is not a real constructor function. |
||
551 | * 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. |
||
552 | * |
||
553 | * @param array $data The record data that is rendered. |
||
554 | * @param string $table The table that the data record is from. |
||
555 | * @param ServerRequestInterface|null $request |
||
556 | */ |
||
557 | public function start($data, $table = '', ?ServerRequestInterface $request = null) |
||
558 | { |
||
559 | $this->request = $request ?? $this->request; |
||
560 | $this->data = $data; |
||
561 | $this->table = $table; |
||
562 | $this->currentRecord = $table !== '' |
||
563 | ? $table . ':' . ($this->data['uid'] ?? '') |
||
564 | : ''; |
||
565 | $this->parameters = []; |
||
566 | $this->stdWrapHookObjects = []; |
||
567 | foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['stdWrap'] ?? [] as $className) { |
||
568 | $hookObject = GeneralUtility::makeInstance($className); |
||
569 | if (!$hookObject instanceof ContentObjectStdWrapHookInterface) { |
||
570 | throw new \UnexpectedValueException($className . ' must implement interface ' . ContentObjectStdWrapHookInterface::class, 1195043965); |
||
571 | } |
||
572 | $this->stdWrapHookObjects[] = $hookObject; |
||
573 | } |
||
574 | foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['postInit'] ?? [] as $className) { |
||
575 | $postInitializationProcessor = GeneralUtility::makeInstance($className); |
||
576 | if (!$postInitializationProcessor instanceof ContentObjectPostInitHookInterface) { |
||
577 | throw new \UnexpectedValueException($className . ' must implement interface ' . ContentObjectPostInitHookInterface::class, 1274563549); |
||
578 | } |
||
579 | $postInitializationProcessor->postProcessContentObjectInitialization($this); |
||
580 | } |
||
581 | } |
||
582 | |||
583 | /** |
||
584 | * Returns the current table |
||
585 | * |
||
586 | * @return string |
||
587 | */ |
||
588 | public function getCurrentTable() |
||
589 | { |
||
590 | return $this->table; |
||
591 | } |
||
592 | |||
593 | /** |
||
594 | * Gets the 'getImgResource' hook objects. |
||
595 | * The first call initializes the accordant objects. |
||
596 | * |
||
597 | * @return array The 'getImgResource' hook objects (if any) |
||
598 | */ |
||
599 | protected function getGetImgResourceHookObjects() |
||
600 | { |
||
601 | if (!isset($this->getImgResourceHookObjects)) { |
||
602 | $this->getImgResourceHookObjects = []; |
||
603 | foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getImgResource'] ?? [] as $className) { |
||
604 | $hookObject = GeneralUtility::makeInstance($className); |
||
605 | if (!$hookObject instanceof ContentObjectGetImageResourceHookInterface) { |
||
606 | throw new \UnexpectedValueException('$hookObject must implement interface ' . ContentObjectGetImageResourceHookInterface::class, 1218636383); |
||
607 | } |
||
608 | $this->getImgResourceHookObjects[] = $hookObject; |
||
609 | } |
||
610 | } |
||
611 | return $this->getImgResourceHookObjects; |
||
612 | } |
||
613 | |||
614 | /** |
||
615 | * Sets the internal variable parentRecord with information about current record. |
||
616 | * 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. |
||
617 | * |
||
618 | * @param array $data The record array |
||
619 | * @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. |
||
620 | * @internal |
||
621 | */ |
||
622 | public function setParent($data, $currentRecord) |
||
627 | ]; |
||
628 | } |
||
629 | |||
630 | /*********************************************** |
||
631 | * |
||
632 | * CONTENT_OBJ: |
||
633 | * |
||
634 | ***********************************************/ |
||
635 | /** |
||
636 | * Returns the "current" value. |
||
637 | * 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. |
||
638 | * It's like "load accumulator" in the good old C64 days... basically a "register" you can use as you like. |
||
639 | * 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. |
||
640 | * |
||
641 | * @return mixed The "current" value |
||
642 | */ |
||
643 | public function getCurrentVal() |
||
644 | { |
||
645 | return $this->data[$this->currentValKey]; |
||
646 | } |
||
647 | |||
648 | /** |
||
649 | * Sets the "current" value. |
||
650 | * |
||
651 | * @param mixed $value The variable that you want to set as "current |
||
652 | * @see getCurrentVal() |
||
653 | */ |
||
654 | public function setCurrentVal($value) |
||
655 | { |
||
656 | $this->data[$this->currentValKey] = $value; |
||
657 | } |
||
658 | |||
659 | /** |
||
660 | * Rendering of a "numerical array" of cObjects from TypoScript |
||
661 | * Will call ->cObjGetSingle() for each cObject found and accumulate the output. |
||
662 | * |
||
663 | * @param array $setup array with cObjects as values. |
||
664 | * @param string $addKey A prefix for the debugging information |
||
665 | * @return string Rendered output from the cObjects in the array. |
||
666 | * @see cObjGetSingle() |
||
667 | */ |
||
668 | public function cObjGet($setup, $addKey = '') |
||
683 | } |
||
684 | |||
685 | /** |
||
686 | * Renders a content object |
||
687 | * |
||
688 | * @param string $name The content object name, eg. "TEXT" or "USER" or "IMAGE" |
||
689 | * @param array $conf The array with TypoScript properties for the content object |
||
690 | * @param string $TSkey A string label used for the internal debugging tracking. |
||
691 | * @return string cObject output |
||
692 | * @throws \UnexpectedValueException |
||
693 | */ |
||
694 | public function cObjGetSingle($name, $conf, $TSkey = '__') |
||
695 | { |
||
696 | $content = ''; |
||
697 | // Checking that the function is not called eternally. This is done by interrupting at a depth of 100 |
||
698 | $this->getTypoScriptFrontendController()->cObjectDepthCounter--; |
||
699 | if ($this->getTypoScriptFrontendController()->cObjectDepthCounter > 0) { |
||
700 | $timeTracker = $this->getTimeTracker(); |
||
701 | $name = trim($name); |
||
702 | if ($timeTracker->LR) { |
||
703 | $timeTracker->push($TSkey, $name); |
||
704 | } |
||
705 | // Checking if the COBJ is a reference to another object. (eg. name of 'some.object =< styles.something') |
||
706 | if (isset($name[0]) && $name[0] === '<') { |
||
707 | $key = trim(substr($name, 1)); |
||
708 | $cF = GeneralUtility::makeInstance(TypoScriptParser::class); |
||
709 | // $name and $conf is loaded with the referenced values. |
||
710 | $confOverride = is_array($conf) ? $conf : []; |
||
711 | [$name, $conf] = $cF->getVal($key, $this->getTypoScriptFrontendController()->tmpl->setup); |
||
712 | $conf = array_replace_recursive(is_array($conf) ? $conf : [], $confOverride); |
||
713 | // Getting the cObject |
||
714 | $timeTracker->incStackPointer(); |
||
715 | $content .= $this->cObjGetSingle($name, $conf, $key); |
||
716 | $timeTracker->decStackPointer(); |
||
717 | } else { |
||
718 | $contentObject = $this->getContentObject($name); |
||
719 | if ($contentObject) { |
||
720 | $content .= $this->render($contentObject, $conf); |
||
721 | } |
||
722 | } |
||
723 | if ($timeTracker->LR) { |
||
724 | $timeTracker->pull($content); |
||
725 | } |
||
726 | } |
||
727 | // Increasing on exit... |
||
728 | $this->getTypoScriptFrontendController()->cObjectDepthCounter++; |
||
729 | return $content; |
||
730 | } |
||
731 | |||
732 | /** |
||
733 | * Returns a new content object of type $name. |
||
734 | * This content object needs to be registered as content object |
||
735 | * in $this->contentObjectClassMap |
||
736 | * |
||
737 | * @param string $name |
||
738 | * @return AbstractContentObject|null |
||
739 | * @throws ContentRenderingException |
||
740 | */ |
||
741 | public function getContentObject($name) |
||
742 | { |
||
743 | if (!isset($this->contentObjectClassMap[$name])) { |
||
744 | return null; |
||
745 | } |
||
746 | $fullyQualifiedClassName = $this->contentObjectClassMap[$name]; |
||
747 | $contentObject = GeneralUtility::makeInstance($fullyQualifiedClassName, $this); |
||
748 | if (!($contentObject instanceof AbstractContentObject)) { |
||
749 | throw new ContentRenderingException(sprintf('Registered content object class name "%s" must be an instance of AbstractContentObject, but is not!', $fullyQualifiedClassName), 1422564295); |
||
750 | } |
||
751 | $contentObject->setRequest($this->getRequest()); |
||
752 | return $contentObject; |
||
753 | } |
||
754 | |||
755 | /******************************************** |
||
756 | * |
||
757 | * Functions rendering content objects (cObjects) |
||
758 | * |
||
759 | ********************************************/ |
||
760 | /** |
||
761 | * Renders a content object by taking exception and cache handling |
||
762 | * into consideration |
||
763 | * |
||
764 | * @param AbstractContentObject $contentObject Content object instance |
||
765 | * @param array $configuration Array of TypoScript properties |
||
766 | * |
||
767 | * @throws ContentRenderingException |
||
768 | * @throws \Exception |
||
769 | * @return string |
||
770 | */ |
||
771 | public function render(AbstractContentObject $contentObject, $configuration = []) |
||
772 | { |
||
773 | $content = ''; |
||
774 | |||
775 | // Evaluate possible cache and return |
||
776 | $cacheConfiguration = $configuration['cache.'] ?? null; |
||
777 | if ($cacheConfiguration !== null) { |
||
778 | unset($configuration['cache.']); |
||
779 | $cache = $this->getFromCache($cacheConfiguration); |
||
780 | if ($cache !== false) { |
||
781 | return $cache; |
||
782 | } |
||
783 | } |
||
784 | |||
785 | // Render content |
||
786 | try { |
||
787 | $content .= $contentObject->render($configuration); |
||
788 | } catch (ContentRenderingException $exception) { |
||
789 | // Content rendering Exceptions indicate a critical problem which should not be |
||
790 | // caught e.g. when something went wrong with Exception handling itself |
||
791 | throw $exception; |
||
792 | } catch (\Exception $exception) { |
||
793 | $exceptionHandler = $this->createExceptionHandler($configuration); |
||
794 | if ($exceptionHandler === null) { |
||
795 | throw $exception; |
||
796 | } |
||
797 | $content = $exceptionHandler->handle($exception, $contentObject, $configuration); |
||
798 | } |
||
799 | |||
800 | // Store cache |
||
801 | if ($cacheConfiguration !== null && !$this->getTypoScriptFrontendController()->no_cache) { |
||
802 | $key = $this->calculateCacheKey($cacheConfiguration); |
||
803 | if (!empty($key)) { |
||
804 | /** @var \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface $cacheFrontend */ |
||
805 | $cacheFrontend = GeneralUtility::makeInstance(CacheManager::class)->getCache('hash'); |
||
806 | $tags = $this->calculateCacheTags($cacheConfiguration); |
||
807 | $lifetime = $this->calculateCacheLifetime($cacheConfiguration); |
||
808 | $cacheFrontend->set($key, $content, $tags, $lifetime); |
||
809 | } |
||
810 | } |
||
811 | |||
812 | return $content; |
||
813 | } |
||
814 | |||
815 | /** |
||
816 | * Creates the content object exception handler from local content object configuration |
||
817 | * or, from global configuration if not explicitly disabled in local configuration |
||
818 | * |
||
819 | * @param array $configuration |
||
820 | * @return ExceptionHandlerInterface|null |
||
821 | * @throws ContentRenderingException |
||
822 | */ |
||
823 | protected function createExceptionHandler($configuration = []) |
||
824 | { |
||
825 | $exceptionHandler = null; |
||
826 | $exceptionHandlerClassName = $this->determineExceptionHandlerClassName($configuration); |
||
827 | if (!empty($exceptionHandlerClassName)) { |
||
828 | $exceptionHandler = GeneralUtility::makeInstance($exceptionHandlerClassName, $this->mergeExceptionHandlerConfiguration($configuration)); |
||
829 | if (!$exceptionHandler instanceof ExceptionHandlerInterface) { |
||
830 | throw new ContentRenderingException('An exception handler was configured but the class does not exist or does not implement the ExceptionHandlerInterface', 1403653369); |
||
831 | } |
||
832 | } |
||
833 | |||
834 | return $exceptionHandler; |
||
835 | } |
||
836 | |||
837 | /** |
||
838 | * Determine exception handler class name from global and content object configuration |
||
839 | * |
||
840 | * @param array $configuration |
||
841 | * @return string|null |
||
842 | */ |
||
843 | protected function determineExceptionHandlerClassName($configuration) |
||
844 | { |
||
845 | $exceptionHandlerClassName = null; |
||
846 | $tsfe = $this->getTypoScriptFrontendController(); |
||
847 | if (!isset($tsfe->config['config']['contentObjectExceptionHandler'])) { |
||
848 | if (Environment::getContext()->isProduction()) { |
||
849 | $exceptionHandlerClassName = '1'; |
||
850 | } |
||
851 | } else { |
||
852 | $exceptionHandlerClassName = $tsfe->config['config']['contentObjectExceptionHandler']; |
||
853 | } |
||
854 | |||
855 | if (isset($configuration['exceptionHandler'])) { |
||
856 | $exceptionHandlerClassName = $configuration['exceptionHandler']; |
||
857 | } |
||
858 | |||
859 | if ($exceptionHandlerClassName === '1') { |
||
860 | $exceptionHandlerClassName = ProductionExceptionHandler::class; |
||
861 | } |
||
862 | |||
863 | return $exceptionHandlerClassName; |
||
864 | } |
||
865 | |||
866 | /** |
||
867 | * Merges global exception handler configuration with the one from the content object |
||
868 | * and returns the merged exception handler configuration |
||
869 | * |
||
870 | * @param array $configuration |
||
871 | * @return array |
||
872 | */ |
||
873 | protected function mergeExceptionHandlerConfiguration($configuration) |
||
874 | { |
||
875 | $exceptionHandlerConfiguration = []; |
||
876 | $tsfe = $this->getTypoScriptFrontendController(); |
||
877 | if (!empty($tsfe->config['config']['contentObjectExceptionHandler.'])) { |
||
878 | $exceptionHandlerConfiguration = $tsfe->config['config']['contentObjectExceptionHandler.']; |
||
879 | } |
||
880 | if (!empty($configuration['exceptionHandler.'])) { |
||
881 | $exceptionHandlerConfiguration = array_replace_recursive($exceptionHandlerConfiguration, $configuration['exceptionHandler.']); |
||
882 | } |
||
883 | |||
884 | return $exceptionHandlerConfiguration; |
||
885 | } |
||
886 | |||
887 | /** |
||
888 | * Retrieves a type of object called as USER or USER_INT. Object can detect their |
||
889 | * type by using this call. It returns OBJECTTYPE_USER_INT or OBJECTTYPE_USER depending on the |
||
890 | * current object execution. In all other cases it will return FALSE to indicate |
||
891 | * a call out of context. |
||
892 | * |
||
893 | * @return mixed One of OBJECTTYPE_ class constants or FALSE |
||
894 | */ |
||
895 | public function getUserObjectType() |
||
896 | { |
||
897 | return $this->userObjectType; |
||
898 | } |
||
899 | |||
900 | /** |
||
901 | * Sets the user object type |
||
902 | * |
||
903 | * @param mixed $userObjectType |
||
904 | */ |
||
905 | public function setUserObjectType($userObjectType) |
||
906 | { |
||
907 | $this->userObjectType = $userObjectType; |
||
908 | } |
||
909 | |||
910 | /** |
||
911 | * Requests the current USER object to be converted to USER_INT. |
||
912 | */ |
||
913 | public function convertToUserIntObject() |
||
914 | { |
||
915 | if ($this->userObjectType !== self::OBJECTTYPE_USER) { |
||
916 | $this->getTimeTracker()->setTSlogMessage(self::class . '::convertToUserIntObject() is called in the wrong context or for the wrong object type', 2); |
||
917 | } else { |
||
918 | $this->doConvertToUserIntObject = true; |
||
919 | } |
||
920 | } |
||
921 | |||
922 | /************************************ |
||
923 | * |
||
924 | * Various helper functions for content objects: |
||
925 | * |
||
926 | ************************************/ |
||
927 | /** |
||
928 | * Converts a given config in Flexform to a conf-array |
||
929 | * |
||
930 | * @param string|array $flexData Flexform data |
||
931 | * @param array $conf Array to write the data into, by reference |
||
932 | * @param bool $recursive Is set if called recursive. Don't call function with this parameter, it's used inside the function only |
||
933 | */ |
||
934 | public function readFlexformIntoConf($flexData, &$conf, $recursive = false) |
||
935 | { |
||
936 | if ($recursive === false && is_string($flexData)) { |
||
937 | $flexData = GeneralUtility::xml2array($flexData, 'T3'); |
||
938 | } |
||
939 | if (is_array($flexData) && isset($flexData['data']['sDEF']['lDEF'])) { |
||
940 | $flexData = $flexData['data']['sDEF']['lDEF']; |
||
941 | } |
||
942 | if (!is_array($flexData)) { |
||
943 | return; |
||
944 | } |
||
945 | foreach ($flexData as $key => $value) { |
||
946 | if (!is_array($value)) { |
||
947 | continue; |
||
948 | } |
||
949 | if (isset($value['el'])) { |
||
950 | if (is_array($value['el']) && !empty($value['el'])) { |
||
951 | foreach ($value['el'] as $ekey => $element) { |
||
952 | if (isset($element['vDEF'])) { |
||
953 | $conf[$ekey] = $element['vDEF']; |
||
954 | } else { |
||
955 | if (is_array($element)) { |
||
956 | $this->readFlexformIntoConf($element, $conf[$key][key($element)][$ekey], true); |
||
957 | } else { |
||
958 | $this->readFlexformIntoConf($element, $conf[$key][$ekey], true); |
||
959 | } |
||
960 | } |
||
961 | } |
||
962 | } else { |
||
963 | $this->readFlexformIntoConf($value['el'], $conf[$key], true); |
||
964 | } |
||
965 | } |
||
966 | if (isset($value['vDEF'])) { |
||
967 | $conf[$key] = $value['vDEF']; |
||
968 | } |
||
969 | } |
||
970 | } |
||
971 | |||
972 | /** |
||
973 | * Returns all parents of the given PID (Page UID) list |
||
974 | * |
||
975 | * @param string $pidList A list of page Content-Element PIDs (Page UIDs) / stdWrap |
||
976 | * @param array $pidConf stdWrap array for the list |
||
977 | * @return string A list of PIDs |
||
978 | * @internal |
||
979 | */ |
||
980 | public function getSlidePids($pidList, $pidConf) |
||
981 | { |
||
982 | // todo: phpstan states that $pidConf always exists and is not nullable. At the moment, this is a false positive |
||
983 | // as null can be passed into this method via $pidConf. As soon as more strict types are used, this isset |
||
984 | // check must be replaced with a more appropriate check like empty or count. |
||
985 | $pidList = isset($pidConf) ? trim($this->stdWrap($pidList, $pidConf)) : trim($pidList); |
||
986 | if ($pidList === '') { |
||
987 | $pidList = 'this'; |
||
988 | } |
||
989 | $tsfe = $this->getTypoScriptFrontendController(); |
||
990 | $listArr = null; |
||
991 | if (trim($pidList)) { |
||
992 | $listArr = GeneralUtility::intExplode(',', str_replace('this', (string)$tsfe->contentPid, $pidList)); |
||
993 | $listArr = $this->checkPidArray($listArr); |
||
994 | } |
||
995 | $pidList = []; |
||
996 | if (is_array($listArr) && !empty($listArr)) { |
||
997 | foreach ($listArr as $uid) { |
||
998 | $page = $tsfe->sys_page->getPage($uid); |
||
999 | if (!$page['is_siteroot']) { |
||
1000 | $pidList[] = $page['pid']; |
||
1001 | } |
||
1002 | } |
||
1003 | } |
||
1004 | return implode(',', $pidList); |
||
1005 | } |
||
1006 | |||
1007 | /** |
||
1008 | * Wraps the input string in link-tags that opens the image in a new window. |
||
1009 | * |
||
1010 | * @param string $string String to wrap, probably an <img> tag |
||
1011 | * @param string|File|FileReference $imageFile The original image file |
||
1012 | * @param array $conf TypoScript properties for the "imageLinkWrap" function |
||
1013 | * @return string The input string, $string, wrapped as configured. |
||
1014 | * @internal This method should be used within TYPO3 Core only |
||
1015 | */ |
||
1016 | public function imageLinkWrap($string, $imageFile, $conf) |
||
1017 | { |
||
1018 | $string = (string)$string; |
||
1019 | $enable = $this->stdWrapValue('enable', $conf ?? []); |
||
1020 | if (!$enable) { |
||
1021 | return $string; |
||
1022 | } |
||
1023 | $content = (string)$this->typoLink($string, $conf['typolink.']); |
||
1024 | if (isset($conf['file.']) && is_scalar($imageFile)) { |
||
1025 | $imageFile = $this->stdWrap((string)$imageFile, $conf['file.']); |
||
1026 | } |
||
1027 | |||
1028 | if ($imageFile instanceof File) { |
||
1029 | $file = $imageFile; |
||
1030 | } elseif ($imageFile instanceof FileReference) { |
||
1031 | $file = $imageFile->getOriginalFile(); |
||
1032 | } else { |
||
1033 | if (MathUtility::canBeInterpretedAsInteger($imageFile)) { |
||
1034 | $file = GeneralUtility::makeInstance(ResourceFactory::class)->getFileObject((int)$imageFile); |
||
1035 | } else { |
||
1036 | $file = GeneralUtility::makeInstance(ResourceFactory::class)->getFileObjectFromCombinedIdentifier($imageFile); |
||
1037 | } |
||
1038 | } |
||
1039 | |||
1040 | // Create imageFileLink if not created with typolink |
||
1041 | if ($content === $string && $file !== null) { |
||
1042 | $parameterNames = ['width', 'height', 'effects', 'bodyTag', 'title', 'wrap', 'crop']; |
||
1043 | $parameters = []; |
||
1044 | $sample = $this->stdWrapValue('sample', $conf ?? []); |
||
1045 | if ($sample) { |
||
1046 | $parameters['sample'] = 1; |
||
1047 | } |
||
1048 | foreach ($parameterNames as $parameterName) { |
||
1049 | if (isset($conf[$parameterName . '.'])) { |
||
1050 | $conf[$parameterName] = $this->stdWrap($conf[$parameterName], $conf[$parameterName . '.']); |
||
1051 | } |
||
1052 | if (isset($conf[$parameterName]) && $conf[$parameterName]) { |
||
1053 | $parameters[$parameterName] = $conf[$parameterName]; |
||
1054 | } |
||
1055 | } |
||
1056 | $parametersEncoded = base64_encode((string)json_encode($parameters)); |
||
1057 | $hmac = GeneralUtility::hmac(implode('|', [$file->getUid(), $parametersEncoded])); |
||
1058 | $params = '&md5=' . $hmac; |
||
1059 | foreach (str_split($parametersEncoded, 64) as $index => $chunk) { |
||
1060 | $params .= '¶meters' . rawurlencode('[') . $index . rawurlencode(']') . '=' . rawurlencode($chunk); |
||
1061 | } |
||
1062 | $url = $this->getTypoScriptFrontendController()->absRefPrefix . 'index.php?eID=tx_cms_showpic&file=' . $file->getUid() . $params; |
||
1063 | $directImageLink = $this->stdWrapValue('directImageLink', $conf ?? []); |
||
1064 | if ($directImageLink) { |
||
1065 | $imgResourceConf = [ |
||
1066 | 'file' => $imageFile, |
||
1067 | 'file.' => $conf |
||
1068 | ]; |
||
1069 | $url = $this->cObjGetSingle('IMG_RESOURCE', $imgResourceConf); |
||
1070 | if (!$url) { |
||
1071 | // If no imagemagick / gm is available |
||
1072 | $url = $imageFile; |
||
1073 | } |
||
1074 | } |
||
1075 | // Create TARGET-attribute only if the right doctype is used |
||
1076 | $target = ''; |
||
1077 | $xhtmlDocType = $this->getTypoScriptFrontendController()->xhtmlDoctype; |
||
1078 | if ($xhtmlDocType !== 'xhtml_strict' && $xhtmlDocType !== 'xhtml_11') { |
||
1079 | $target = (string)$this->stdWrapValue('target', $conf ?? []); |
||
1080 | if ($target === '') { |
||
1081 | $target = 'thePicture'; |
||
1082 | } |
||
1083 | } |
||
1084 | $a1 = ''; |
||
1085 | $a2 = ''; |
||
1086 | $conf['JSwindow'] = $this->stdWrapValue('JSwindow', $conf ?? []); |
||
1087 | if ($conf['JSwindow']) { |
||
1088 | $altUrl = $this->stdWrapValue('altUrl', $conf['JSwindow.'] ?? []); |
||
1089 | if ($altUrl) { |
||
1090 | $url = $altUrl . ($conf['JSwindow.']['altUrl_noDefaultParams'] ? '' : '?file=' . rawurlencode((string)$imageFile) . $params); |
||
1091 | } |
||
1092 | |||
1093 | $processedFile = $file->process(ProcessedFile::CONTEXT_IMAGECROPSCALEMASK, $conf); |
||
1094 | $JSwindowExpand = $this->stdWrapValue('expand', $conf['JSwindow.'] ?? []); |
||
1095 | $offset = GeneralUtility::intExplode(',', $JSwindowExpand . ','); |
||
1096 | $newWindow = $this->stdWrapValue('newWindow', $conf['JSwindow.'] ?? []); |
||
1097 | $params = [ |
||
1098 | 'width' => ($processedFile->getProperty('width') + $offset[0]), |
||
1099 | 'height' => ($processedFile->getProperty('height') + $offset[1]), |
||
1100 | 'status' => '0', |
||
1101 | 'menubar' => '0' |
||
1102 | ]; |
||
1103 | // params override existing parameters from above, or add more |
||
1104 | $windowParams = (string)$this->stdWrapValue('params', $conf['JSwindow.'] ?? []); |
||
1105 | $windowParams = explode(',', $windowParams); |
||
1106 | foreach ($windowParams as $windowParam) { |
||
1107 | [$paramKey, $paramValue] = explode('=', $windowParam); |
||
1108 | if ($paramValue !== '') { |
||
1109 | $params[$paramKey] = $paramValue; |
||
1110 | } else { |
||
1111 | unset($params[$paramKey]); |
||
1112 | } |
||
1113 | } |
||
1114 | $paramString = ''; |
||
1115 | foreach ($params as $paramKey => $paramValue) { |
||
1116 | $paramString .= htmlspecialchars((string)$paramKey) . '=' . htmlspecialchars((string)$paramValue) . ','; |
||
1117 | } |
||
1118 | |||
1119 | $onClick = 'openPic(' |
||
1120 | . GeneralUtility::quoteJSvalue($this->getTypoScriptFrontendController()->baseUrlWrap($url)) . ',' |
||
1121 | . '\'' . ($newWindow ? md5((string)$url) : 'thePicture') . '\',' |
||
1122 | . GeneralUtility::quoteJSvalue(rtrim($paramString, ',')) . '); return false;'; |
||
1123 | $a1 = '<a href="' . htmlspecialchars((string)$url) . '"' |
||
1124 | . ' onclick="' . htmlspecialchars($onClick) . '"' |
||
1125 | . ($target !== '' ? ' target="' . htmlspecialchars($target) . '"' : '') |
||
1126 | . $this->getTypoScriptFrontendController()->ATagParams . '>'; |
||
1127 | $a2 = '</a>'; |
||
1128 | GeneralUtility::makeInstance(AssetCollector::class)->addInlineJavaScript('openPic', 'function openPic(url, winName, winParams) { var theWindow = window.open(url, winName, winParams); if (theWindow) { theWindow.focus(); } }'); |
||
1129 | } else { |
||
1130 | $conf['linkParams.']['directImageLink'] = (bool)$conf['directImageLink']; |
||
1131 | $conf['linkParams.']['parameter'] = $url; |
||
1132 | $string = $this->typoLink($string, $conf['linkParams.']); |
||
1133 | } |
||
1134 | if (isset($conf['stdWrap.'])) { |
||
1135 | $string = $this->stdWrap($string, $conf['stdWrap.']); |
||
1136 | } |
||
1137 | $content = $a1 . $string . $a2; |
||
1138 | } |
||
1139 | return $content; |
||
1140 | } |
||
1141 | |||
1142 | /** |
||
1143 | * Sets the SYS_LASTCHANGED timestamp if input timestamp is larger than current value. |
||
1144 | * The SYS_LASTCHANGED timestamp can be used by various caching/indexing applications to determine if the page has new content. |
||
1145 | * Therefore you should call this function with the last-changed timestamp of any element you display. |
||
1146 | * |
||
1147 | * @param int $tstamp Unix timestamp (number of seconds since 1970) |
||
1148 | * @see TypoScriptFrontendController::setSysLastChanged() |
||
1149 | */ |
||
1150 | public function lastChanged($tstamp) |
||
1151 | { |
||
1152 | $tstamp = (int)$tstamp; |
||
1153 | $tsfe = $this->getTypoScriptFrontendController(); |
||
1154 | if ($tstamp > (int)$tsfe->register['SYS_LASTCHANGED']) { |
||
1155 | $tsfe->register['SYS_LASTCHANGED'] = $tstamp; |
||
1156 | } |
||
1157 | } |
||
1158 | |||
1159 | /** |
||
1160 | * An abstraction method to add parameters to an A tag. |
||
1161 | * Uses the ATagParams property. |
||
1162 | * |
||
1163 | * @param array $conf TypoScript configuration properties |
||
1164 | * @param bool|int $addGlobal If set, will add the global config.ATagParams to the link |
||
1165 | * @return string String containing the parameters to the A tag (if non empty, with a leading space) |
||
1166 | * @see typolink() |
||
1167 | */ |
||
1168 | public function getATagParams($conf, $addGlobal = 1) |
||
1169 | { |
||
1170 | $aTagParams = ' ' . $this->stdWrapValue('ATagParams', $conf ?? []); |
||
1171 | if ($addGlobal) { |
||
1172 | $globalParams = $this->getTypoScriptFrontendController()->ATagParams ?? ''; |
||
1173 | $aTagParams = ' ' . trim($globalParams . $aTagParams); |
||
1174 | } |
||
1175 | // Extend params |
||
1176 | $_params = [ |
||
1177 | 'conf' => &$conf, |
||
1178 | 'aTagParams' => &$aTagParams |
||
1179 | ]; |
||
1180 | foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getATagParamsPostProc'] ?? [] as $className) { |
||
1181 | $processor = GeneralUtility::makeInstance($className); |
||
1182 | $aTagParams = $processor->process($_params, $this); |
||
1183 | } |
||
1184 | |||
1185 | $aTagParams = trim($aTagParams); |
||
1186 | if (!empty($aTagParams)) { |
||
1187 | $aTagParams = ' ' . $aTagParams; |
||
1188 | } |
||
1189 | |||
1190 | return $aTagParams; |
||
1191 | } |
||
1192 | |||
1193 | /*********************************************** |
||
1194 | * |
||
1195 | * HTML template processing functions |
||
1196 | * |
||
1197 | ***********************************************/ |
||
1198 | |||
1199 | /** |
||
1200 | * Sets the current file object during iterations over files. |
||
1201 | * |
||
1202 | * @param File $fileObject The file object. |
||
1203 | */ |
||
1204 | public function setCurrentFile($fileObject) |
||
1205 | { |
||
1206 | $this->currentFile = $fileObject; |
||
1207 | } |
||
1208 | |||
1209 | /** |
||
1210 | * Gets the current file object during iterations over files. |
||
1211 | * |
||
1212 | * @return File The current file object. |
||
1213 | */ |
||
1214 | public function getCurrentFile() |
||
1215 | { |
||
1216 | return $this->currentFile; |
||
1217 | } |
||
1218 | |||
1219 | /*********************************************** |
||
1220 | * |
||
1221 | * "stdWrap" + sub functions |
||
1222 | * |
||
1223 | ***********************************************/ |
||
1224 | /** |
||
1225 | * The "stdWrap" function. This is the implementation of what is known as "stdWrap properties" in TypoScript. |
||
1226 | * Basically "stdWrap" performs some processing of a value based on properties in the input $conf array(holding the TypoScript "stdWrap properties") |
||
1227 | * 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. |
||
1228 | * |
||
1229 | * If $this->alternativeData is an array it's used instead of the $this->data array in ->getData |
||
1230 | * |
||
1231 | * @param string $content Input value undergoing processing in this function. Possibly substituted by other values fetched from another source. |
||
1232 | * @param array $conf TypoScript "stdWrap properties". |
||
1233 | * @return string The processed input value |
||
1234 | */ |
||
1235 | public function stdWrap($content = '', $conf = []) |
||
1236 | { |
||
1237 | $content = (string)$content; |
||
1238 | // If there is any hook object, activate all of the process and override functions. |
||
1239 | // The hook interface ContentObjectStdWrapHookInterface takes care that all 4 methods exist. |
||
1240 | if ($this->stdWrapHookObjects) { |
||
1241 | $conf['stdWrapPreProcess'] = 1; |
||
1242 | $conf['stdWrapOverride'] = 1; |
||
1243 | $conf['stdWrapProcess'] = 1; |
||
1244 | $conf['stdWrapPostProcess'] = 1; |
||
1245 | } |
||
1246 | |||
1247 | if (!is_array($conf) || !$conf) { |
||
1248 | return $content; |
||
1249 | } |
||
1250 | |||
1251 | // Cache handling |
||
1252 | if (isset($conf['cache.']) && is_array($conf['cache.'])) { |
||
1253 | $conf['cache.']['key'] = $this->stdWrapValue('key', $conf['cache.'] ?? []); |
||
1254 | $conf['cache.']['tags'] = $this->stdWrapValue('tags', $conf['cache.'] ?? []); |
||
1255 | $conf['cache.']['lifetime'] = $this->stdWrapValue('lifetime', $conf['cache.'] ?? []); |
||
1256 | $conf['cacheRead'] = 1; |
||
1257 | $conf['cacheStore'] = 1; |
||
1258 | } |
||
1259 | // The configuration is sorted and filtered by intersection with the defined stdWrapOrder. |
||
1260 | $sortedConf = array_keys(array_intersect_key($this->stdWrapOrder, $conf)); |
||
1261 | // Functions types that should not make use of nested stdWrap function calls to avoid conflicts with internal TypoScript used by these functions |
||
1262 | $stdWrapDisabledFunctionTypes = 'cObject,functionName,stdWrap'; |
||
1263 | // Additional Array to check whether a function has already been executed |
||
1264 | $isExecuted = []; |
||
1265 | // Additional switch to make sure 'required', 'if' and 'fieldRequired' |
||
1266 | // will still stop rendering immediately in case they return FALSE |
||
1267 | $this->stdWrapRecursionLevel++; |
||
1268 | $this->stopRendering[$this->stdWrapRecursionLevel] = false; |
||
1269 | // execute each function in the predefined order |
||
1270 | foreach ($sortedConf as $stdWrapName) { |
||
1271 | // eliminate the second key of a pair 'key'|'key.' to make sure functions get called only once and check if rendering has been stopped |
||
1272 | if ((!isset($isExecuted[$stdWrapName]) || !$isExecuted[$stdWrapName]) && !$this->stopRendering[$this->stdWrapRecursionLevel]) { |
||
1273 | $functionName = rtrim($stdWrapName, '.'); |
||
1274 | $functionProperties = $functionName . '.'; |
||
1275 | $functionType = $this->stdWrapOrder[$functionName] ?? ''; |
||
1276 | // If there is any code on the next level, check if it contains "official" stdWrap functions |
||
1277 | // if yes, execute them first - will make each function stdWrap aware |
||
1278 | // so additional stdWrap calls within the functions can be removed, since the result will be the same |
||
1279 | if (!empty($conf[$functionProperties]) && !GeneralUtility::inList($stdWrapDisabledFunctionTypes, $functionType)) { |
||
1280 | if (array_intersect_key($this->stdWrapOrder, $conf[$functionProperties])) { |
||
1281 | // Check if there's already content available before processing |
||
1282 | // any ifEmpty or ifBlank stdWrap properties |
||
1283 | if (($functionName === 'ifBlank' && $content !== '') || |
||
1284 | ($functionName === 'ifEmpty' && trim($content) !== '')) { |
||
1285 | continue; |
||
1286 | } |
||
1287 | |||
1288 | $conf[$functionName] = $this->stdWrap($conf[$functionName] ?? '', $conf[$functionProperties] ?? []); |
||
1289 | } |
||
1290 | } |
||
1291 | // Check if key is still containing something, since it might have been changed by next level stdWrap before |
||
1292 | if ((isset($conf[$functionName]) || $conf[$functionProperties]) |
||
1293 | && ($functionType !== 'boolean' || $conf[$functionName]) |
||
1294 | ) { |
||
1295 | // Get just that part of $conf that is needed for the particular function |
||
1296 | $singleConf = [ |
||
1297 | $functionName => $conf[$functionName] ?? null, |
||
1298 | $functionProperties => $conf[$functionProperties] ?? null |
||
1299 | ]; |
||
1300 | // Hand over the whole $conf array to the stdWrapHookObjects |
||
1301 | if ($functionType === 'hook') { |
||
1302 | $singleConf = $conf; |
||
1303 | } |
||
1304 | // Add both keys - with and without the dot - to the set of executed functions |
||
1305 | $isExecuted[$functionName] = true; |
||
1306 | $isExecuted[$functionProperties] = true; |
||
1307 | // Call the function with the prefix stdWrap_ to make sure nobody can execute functions just by adding their name to the TS Array |
||
1308 | $functionName = 'stdWrap_' . $functionName; |
||
1309 | $content = $this->{$functionName}($content, $singleConf); |
||
1310 | } elseif ($functionType === 'boolean' && !$conf[$functionName]) { |
||
1311 | $isExecuted[$functionName] = true; |
||
1312 | $isExecuted[$functionProperties] = true; |
||
1313 | } |
||
1314 | } |
||
1315 | } |
||
1316 | unset($this->stopRendering[$this->stdWrapRecursionLevel]); |
||
1317 | $this->stdWrapRecursionLevel--; |
||
1318 | |||
1319 | return $content; |
||
1320 | } |
||
1321 | |||
1322 | /** |
||
1323 | * Gets a configuration value by passing them through stdWrap first and taking a default value if stdWrap doesn't yield a result. |
||
1324 | * |
||
1325 | * @param string $key The config variable key (from TS array). |
||
1326 | * @param array $config The TypoScript array. |
||
1327 | * @param string|int|bool|null $defaultValue Optional default value. |
||
1328 | * @return string|int|bool|null Value of the config variable |
||
1329 | */ |
||
1330 | public function stdWrapValue($key, array $config, $defaultValue = '') |
||
1331 | { |
||
1332 | if (isset($config[$key])) { |
||
1333 | if (!isset($config[$key . '.'])) { |
||
1334 | return $config[$key]; |
||
1335 | } |
||
1336 | } elseif (isset($config[$key . '.'])) { |
||
1337 | $config[$key] = ''; |
||
1338 | } else { |
||
1339 | return $defaultValue; |
||
1340 | } |
||
1341 | $stdWrapped = $this->stdWrap($config[$key], $config[$key . '.']); |
||
1342 | // The string "0" should be returned. |
||
1343 | return $stdWrapped !== '' ? $stdWrapped : $defaultValue; |
||
1344 | } |
||
1345 | |||
1346 | /** |
||
1347 | * stdWrap pre process hook |
||
1348 | * can be used by extensions authors to modify the behaviour of stdWrap functions to their needs |
||
1349 | * this hook will execute functions before any other stdWrap function can modify anything |
||
1350 | * |
||
1351 | * @param string $content Input value undergoing processing in these functions. |
||
1352 | * @param array $conf All stdWrap properties, not just the ones for a particular function. |
||
1353 | * @return string The processed input value |
||
1354 | */ |
||
1355 | public function stdWrap_stdWrapPreProcess($content = '', $conf = []) |
||
1356 | { |
||
1357 | foreach ($this->stdWrapHookObjects as $hookObject) { |
||
1358 | /** @var ContentObjectStdWrapHookInterface $hookObject */ |
||
1359 | $content = $hookObject->stdWrapPreProcess($content, $conf, $this); |
||
1360 | } |
||
1361 | return $content; |
||
1362 | } |
||
1363 | |||
1364 | /** |
||
1365 | * Check if content was cached before (depending on the given cache key) |
||
1366 | * |
||
1367 | * @param string $content Input value undergoing processing in these functions. |
||
1368 | * @param array $conf All stdWrap properties, not just the ones for a particular function. |
||
1369 | * @return string The processed input value |
||
1370 | */ |
||
1371 | public function stdWrap_cacheRead($content = '', $conf = []) |
||
1372 | { |
||
1373 | if (!isset($conf['cache.'])) { |
||
1374 | return $content; |
||
1375 | } |
||
1376 | $result = $this->getFromCache($conf['cache.']); |
||
1377 | return $result === false ? $content : $result; |
||
1378 | } |
||
1379 | |||
1380 | /** |
||
1381 | * Add tags to page cache (comma-separated list) |
||
1382 | * |
||
1383 | * @param string $content Input value undergoing processing in these functions. |
||
1384 | * @param array $conf All stdWrap properties, not just the ones for a particular function. |
||
1385 | * @return string The processed input value |
||
1386 | */ |
||
1387 | public function stdWrap_addPageCacheTags($content = '', $conf = []) |
||
1388 | { |
||
1389 | $tags = (string)$this->stdWrapValue('addPageCacheTags', $conf ?? []); |
||
1390 | if (!empty($tags)) { |
||
1391 | $cacheTags = GeneralUtility::trimExplode(',', $tags, true); |
||
1392 | $this->getTypoScriptFrontendController()->addCacheTags($cacheTags); |
||
1393 | } |
||
1394 | return $content; |
||
1395 | } |
||
1396 | |||
1397 | /** |
||
1398 | * setContentToCurrent |
||
1399 | * actually it just does the contrary: Sets the value of 'current' based on current content |
||
1400 | * |
||
1401 | * @param string $content Input value undergoing processing in this function. |
||
1402 | * @return string The processed input value |
||
1403 | */ |
||
1404 | public function stdWrap_setContentToCurrent($content = '') |
||
1405 | { |
||
1406 | $this->data[$this->currentValKey] = $content; |
||
1407 | return $content; |
||
1408 | } |
||
1409 | |||
1410 | /** |
||
1411 | * setCurrent |
||
1412 | * Sets the value of 'current' based on the outcome of stdWrap operations |
||
1413 | * |
||
1414 | * @param string $content Input value undergoing processing in this function. |
||
1415 | * @param array $conf stdWrap properties for setCurrent. |
||
1416 | * @return string The processed input value |
||
1417 | */ |
||
1418 | public function stdWrap_setCurrent($content = '', $conf = []) |
||
1419 | { |
||
1420 | $this->data[$this->currentValKey] = $conf['setCurrent'] ?? null; |
||
1421 | return $content; |
||
1422 | } |
||
1423 | |||
1424 | /** |
||
1425 | * lang |
||
1426 | * Translates content based on the language currently used by the FE |
||
1427 | * |
||
1428 | * @param string $content Input value undergoing processing in this function. |
||
1429 | * @param array $conf stdWrap properties for lang. |
||
1430 | * @return string The processed input value |
||
1431 | */ |
||
1432 | public function stdWrap_lang($content = '', $conf = []) |
||
1433 | { |
||
1434 | $currentLanguageCode = $this->getTypoScriptFrontendController()->getLanguage()->getTypo3Language(); |
||
1435 | if ($currentLanguageCode && isset($conf['lang.'][$currentLanguageCode])) { |
||
1436 | $content = $conf['lang.'][$currentLanguageCode]; |
||
1437 | } |
||
1438 | return $content; |
||
1439 | } |
||
1440 | |||
1441 | /** |
||
1442 | * data |
||
1443 | * Gets content from different sources based on getText functions, makes use of alternativeData, when set |
||
1444 | * |
||
1445 | * @param string $content Input value undergoing processing in this function. |
||
1446 | * @param array $conf stdWrap properties for data. |
||
1447 | * @return string The processed input value |
||
1448 | */ |
||
1449 | public function stdWrap_data($content = '', $conf = []) |
||
1450 | { |
||
1451 | $content = $this->getData($conf['data'], is_array($this->alternativeData) ? $this->alternativeData : $this->data); |
||
1452 | // This must be unset directly after |
||
1453 | $this->alternativeData = ''; |
||
1454 | return $content; |
||
1455 | } |
||
1456 | |||
1457 | /** |
||
1458 | * field |
||
1459 | * Gets content from a DB field |
||
1460 | * |
||
1461 | * @param string $content Input value undergoing processing in this function. |
||
1462 | * @param array $conf stdWrap properties for field. |
||
1463 | * @return string The processed input value |
||
1464 | */ |
||
1465 | public function stdWrap_field($content = '', $conf = []) |
||
1466 | { |
||
1467 | return $this->getFieldVal($conf['field']); |
||
1468 | } |
||
1469 | |||
1470 | /** |
||
1471 | * current |
||
1472 | * Gets content that has been previously set as 'current' |
||
1473 | * Can be set via setContentToCurrent or setCurrent or will be set automatically i.e. inside the split function |
||
1474 | * |
||
1475 | * @param string $content Input value undergoing processing in this function. |
||
1476 | * @param array $conf stdWrap properties for current. |
||
1477 | * @return string The processed input value |
||
1478 | */ |
||
1479 | public function stdWrap_current($content = '', $conf = []) |
||
1480 | { |
||
1481 | return $this->data[$this->currentValKey]; |
||
1482 | } |
||
1483 | |||
1484 | /** |
||
1485 | * cObject |
||
1486 | * Will replace the content with the value of an official TypoScript cObject |
||
1487 | * like TEXT, COA, HMENU |
||
1488 | * |
||
1489 | * @param string $content Input value undergoing processing in this function. |
||
1490 | * @param array $conf stdWrap properties for cObject. |
||
1491 | * @return string The processed input value |
||
1492 | */ |
||
1493 | public function stdWrap_cObject($content = '', $conf = []) |
||
1494 | { |
||
1495 | return $this->cObjGetSingle($conf['cObject'] ?? '', $conf['cObject.'] ?? [], '/stdWrap/.cObject'); |
||
1496 | } |
||
1497 | |||
1498 | /** |
||
1499 | * numRows |
||
1500 | * Counts the number of returned records of a DB operation |
||
1501 | * makes use of select internally |
||
1502 | * |
||
1503 | * @param string $content Input value undergoing processing in this function. |
||
1504 | * @param array $conf stdWrap properties for numRows. |
||
1505 | * @return string The processed input value |
||
1506 | */ |
||
1507 | public function stdWrap_numRows($content = '', $conf = []) |
||
1508 | { |
||
1509 | return $this->numRows($conf['numRows.']); |
||
1510 | } |
||
1511 | |||
1512 | /** |
||
1513 | * preUserFunc |
||
1514 | * Will execute a user public function before the content will be modified by any other stdWrap function |
||
1515 | * |
||
1516 | * @param string $content Input value undergoing processing in this function. |
||
1517 | * @param array $conf stdWrap properties for preUserFunc. |
||
1518 | * @return string The processed input value |
||
1519 | */ |
||
1520 | public function stdWrap_preUserFunc($content = '', $conf = []) |
||
1521 | { |
||
1522 | return $this->callUserFunction($conf['preUserFunc'], $conf['preUserFunc.'] ?? [], $content); |
||
1523 | } |
||
1524 | |||
1525 | /** |
||
1526 | * stdWrap override hook |
||
1527 | * can be used by extensions authors to modify the behaviour of stdWrap functions to their needs |
||
1528 | * this hook will execute functions on existing content but still before the content gets modified or replaced |
||
1529 | * |
||
1530 | * @param string $content Input value undergoing processing in these functions. |
||
1531 | * @param array $conf All stdWrap properties, not just the ones for a particular function. |
||
1532 | * @return string The processed input value |
||
1533 | */ |
||
1534 | public function stdWrap_stdWrapOverride($content = '', $conf = []) |
||
1535 | { |
||
1536 | foreach ($this->stdWrapHookObjects as $hookObject) { |
||
1537 | /** @var ContentObjectStdWrapHookInterface $hookObject */ |
||
1538 | $content = $hookObject->stdWrapOverride($content, $conf, $this); |
||
1539 | } |
||
1540 | return $content; |
||
1541 | } |
||
1542 | |||
1543 | /** |
||
1544 | * override |
||
1545 | * Will override the current value of content with its own value' |
||
1546 | * |
||
1547 | * @param string $content Input value undergoing processing in this function. |
||
1548 | * @param array $conf stdWrap properties for override. |
||
1549 | * @return string The processed input value |
||
1550 | */ |
||
1551 | public function stdWrap_override($content = '', $conf = []) |
||
1552 | { |
||
1553 | if (trim($conf['override'] ?? false)) { |
||
1554 | $content = $conf['override']; |
||
1555 | } |
||
1556 | return $content; |
||
1557 | } |
||
1558 | |||
1559 | /** |
||
1560 | * preIfEmptyListNum |
||
1561 | * Gets a value off a CSV list before the following ifEmpty check |
||
1562 | * 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 |
||
1563 | * |
||
1564 | * @param string $content Input value undergoing processing in this function. |
||
1565 | * @param array $conf stdWrap properties for preIfEmptyListNum. |
||
1566 | * @return string The processed input value |
||
1567 | */ |
||
1568 | public function stdWrap_preIfEmptyListNum($content = '', $conf = []) |
||
1569 | { |
||
1570 | return $this->listNum($content, $conf['preIfEmptyListNum'] ?? null, $conf['preIfEmptyListNum.']['splitChar'] ?? null); |
||
1571 | } |
||
1572 | |||
1573 | /** |
||
1574 | * ifNull |
||
1575 | * Will set content to a replacement value in case the value of content is NULL |
||
1576 | * |
||
1577 | * @param string|null $content Input value undergoing processing in this function. |
||
1578 | * @param array $conf stdWrap properties for ifNull. |
||
1579 | * @return string The processed input value |
||
1580 | */ |
||
1581 | public function stdWrap_ifNull($content = '', $conf = []) |
||
1582 | { |
||
1583 | return $content ?? $conf['ifNull']; |
||
1584 | } |
||
1585 | |||
1586 | /** |
||
1587 | * ifEmpty |
||
1588 | * Will set content to a replacement value in case the trimmed value of content returns FALSE |
||
1589 | * 0 (zero) will be replaced as well |
||
1590 | * |
||
1591 | * @param string $content Input value undergoing processing in this function. |
||
1592 | * @param array $conf stdWrap properties for ifEmpty. |
||
1593 | * @return string The processed input value |
||
1594 | */ |
||
1595 | public function stdWrap_ifEmpty($content = '', $conf = []) |
||
1596 | { |
||
1597 | if (!trim($content)) { |
||
1598 | $content = $conf['ifEmpty']; |
||
1599 | } |
||
1600 | return $content; |
||
1601 | } |
||
1602 | |||
1603 | /** |
||
1604 | * ifBlank |
||
1605 | * Will set content to a replacement value in case the trimmed value of content has no length |
||
1606 | * 0 (zero) will not be replaced |
||
1607 | * |
||
1608 | * @param string $content Input value undergoing processing in this function. |
||
1609 | * @param array $conf stdWrap properties for ifBlank. |
||
1610 | * @return string The processed input value |
||
1611 | */ |
||
1612 | public function stdWrap_ifBlank($content = '', $conf = []) |
||
1613 | { |
||
1614 | if (trim($content) === '') { |
||
1615 | $content = $conf['ifBlank']; |
||
1616 | } |
||
1617 | return $content; |
||
1618 | } |
||
1619 | |||
1620 | /** |
||
1621 | * listNum |
||
1622 | * Gets a value off a CSV list after ifEmpty check |
||
1623 | * Might return an empty value in case the CSV does not contain a value at the position given by listNum |
||
1624 | * Use preIfEmptyListNum to avoid that behaviour |
||
1625 | * |
||
1626 | * @param string $content Input value undergoing processing in this function. |
||
1627 | * @param array $conf stdWrap properties for listNum. |
||
1628 | * @return string The processed input value |
||
1629 | */ |
||
1630 | public function stdWrap_listNum($content = '', $conf = []) |
||
1631 | { |
||
1632 | return $this->listNum($content, $conf['listNum'] ?? null, $conf['listNum.']['splitChar'] ?? null); |
||
1633 | } |
||
1634 | |||
1635 | /** |
||
1636 | * trim |
||
1637 | * Cuts off any whitespace at the beginning and the end of the content |
||
1638 | * |
||
1639 | * @param string $content Input value undergoing processing in this function. |
||
1640 | * @return string The processed input value |
||
1641 | */ |
||
1642 | public function stdWrap_trim($content = '') |
||
1643 | { |
||
1644 | return trim($content); |
||
1645 | } |
||
1646 | |||
1647 | /** |
||
1648 | * strPad |
||
1649 | * Will return a string padded left/right/on both sides, based on configuration given as stdWrap properties |
||
1650 | * |
||
1651 | * @param string $content Input value undergoing processing in this function. |
||
1652 | * @param array $conf stdWrap properties for strPad. |
||
1653 | * @return string The processed input value |
||
1654 | */ |
||
1655 | public function stdWrap_strPad($content = '', $conf = []) |
||
1656 | { |
||
1657 | // Must specify a length in conf for this to make sense |
||
1658 | $length = (int)$this->stdWrapValue('length', $conf['strPad.'] ?? [], 0); |
||
1659 | // Padding with space is PHP-default |
||
1660 | $padWith = (string)$this->stdWrapValue('padWith', $conf['strPad.'] ?? [], ' '); |
||
1661 | // Padding on the right side is PHP-default |
||
1662 | $padType = STR_PAD_RIGHT; |
||
1663 | |||
1664 | if (!empty($conf['strPad.']['type'])) { |
||
1665 | $type = (string)$this->stdWrapValue('type', $conf['strPad.'] ?? []); |
||
1666 | if (strtolower($type) === 'left') { |
||
1667 | $padType = STR_PAD_LEFT; |
||
1668 | } elseif (strtolower($type) === 'both') { |
||
1669 | $padType = STR_PAD_BOTH; |
||
1670 | } |
||
1671 | } |
||
1672 | return str_pad($content, $length, $padWith, $padType); |
||
1673 | } |
||
1674 | |||
1675 | /** |
||
1676 | * stdWrap |
||
1677 | * A recursive call of the stdWrap function set |
||
1678 | * This enables the user to execute stdWrap functions in another than the predefined order |
||
1679 | * It modifies the content, not the property |
||
1680 | * while the new feature of chained stdWrap functions modifies the property and not the content |
||
1681 | * |
||
1682 | * @param string $content Input value undergoing processing in this function. |
||
1683 | * @param array $conf stdWrap properties for stdWrap. |
||
1684 | * @return string The processed input value |
||
1685 | */ |
||
1686 | public function stdWrap_stdWrap($content = '', $conf = []) |
||
1687 | { |
||
1688 | return $this->stdWrap($content, $conf['stdWrap.']); |
||
1689 | } |
||
1690 | |||
1691 | /** |
||
1692 | * stdWrap process hook |
||
1693 | * can be used by extensions authors to modify the behaviour of stdWrap functions to their needs |
||
1694 | * this hook executes functions directly after the recursive stdWrap function call but still before the content gets modified |
||
1695 | * |
||
1696 | * @param string $content Input value undergoing processing in these functions. |
||
1697 | * @param array $conf All stdWrap properties, not just the ones for a particular function. |
||
1698 | * @return string The processed input value |
||
1699 | */ |
||
1700 | public function stdWrap_stdWrapProcess($content = '', $conf = []) |
||
1701 | { |
||
1702 | foreach ($this->stdWrapHookObjects as $hookObject) { |
||
1703 | /** @var ContentObjectStdWrapHookInterface $hookObject */ |
||
1704 | $content = $hookObject->stdWrapProcess($content, $conf, $this); |
||
1705 | } |
||
1706 | return $content; |
||
1707 | } |
||
1708 | |||
1709 | /** |
||
1710 | * required |
||
1711 | * Will immediately stop rendering and return an empty value |
||
1712 | * when there is no content at this point |
||
1713 | * |
||
1714 | * @param string $content Input value undergoing processing in this function. |
||
1715 | * @return string The processed input value |
||
1716 | */ |
||
1717 | public function stdWrap_required($content = '') |
||
1718 | { |
||
1719 | if ((string)$content === '') { |
||
1720 | $content = ''; |
||
1721 | $this->stopRendering[$this->stdWrapRecursionLevel] = true; |
||
1722 | } |
||
1723 | return $content; |
||
1724 | } |
||
1725 | |||
1726 | /** |
||
1727 | * if |
||
1728 | * Will immediately stop rendering and return an empty value |
||
1729 | * when the result of the checks returns FALSE |
||
1730 | * |
||
1731 | * @param string $content Input value undergoing processing in this function. |
||
1732 | * @param array $conf stdWrap properties for if. |
||
1733 | * @return string The processed input value |
||
1734 | */ |
||
1735 | public function stdWrap_if($content = '', $conf = []) |
||
1736 | { |
||
1737 | if (empty($conf['if.']) || $this->checkIf($conf['if.'])) { |
||
1738 | return $content; |
||
1739 | } |
||
1740 | $this->stopRendering[$this->stdWrapRecursionLevel] = true; |
||
1741 | return ''; |
||
1742 | } |
||
1743 | |||
1744 | /** |
||
1745 | * fieldRequired |
||
1746 | * Will immediately stop rendering and return an empty value |
||
1747 | * when there is no content in the field given by fieldRequired |
||
1748 | * |
||
1749 | * @param string $content Input value undergoing processing in this function. |
||
1750 | * @param array $conf stdWrap properties for fieldRequired. |
||
1751 | * @return string The processed input value |
||
1752 | */ |
||
1753 | public function stdWrap_fieldRequired($content = '', $conf = []) |
||
1754 | { |
||
1755 | if (!trim($this->data[$conf['fieldRequired'] ?? null] ?? '')) { |
||
1756 | $content = ''; |
||
1757 | $this->stopRendering[$this->stdWrapRecursionLevel] = true; |
||
1758 | } |
||
1759 | return $content; |
||
1760 | } |
||
1761 | |||
1762 | /** |
||
1763 | * stdWrap csConv: Converts the input to UTF-8 |
||
1764 | * |
||
1765 | * The character set of the input must be specified. Returns the input if |
||
1766 | * matters go wrong, for example if an invalid character set is given. |
||
1767 | * |
||
1768 | * @param string $content The string to convert. |
||
1769 | * @param array $conf stdWrap properties for csConv. |
||
1770 | * @return string The processed input. |
||
1771 | */ |
||
1772 | public function stdWrap_csConv($content = '', $conf = []) |
||
1773 | { |
||
1774 | if (!empty($conf['csConv'])) { |
||
1775 | $output = mb_convert_encoding($content, 'utf-8', trim(strtolower($conf['csConv']))); |
||
1776 | return $output !== false && $output !== '' ? $output : $content; |
||
1777 | } |
||
1778 | return $content; |
||
1779 | } |
||
1780 | |||
1781 | /** |
||
1782 | * parseFunc |
||
1783 | * Will parse the content based on functions given as stdWrap properties |
||
1784 | * Heavily used together with RTE based content |
||
1785 | * |
||
1786 | * @param string $content Input value undergoing processing in this function. |
||
1787 | * @param array $conf stdWrap properties for parseFunc. |
||
1788 | * @return string The processed input value |
||
1789 | */ |
||
1790 | public function stdWrap_parseFunc($content = '', $conf = []) |
||
1791 | { |
||
1792 | return $this->parseFunc($content, $conf['parseFunc.'], $conf['parseFunc']); |
||
1793 | } |
||
1794 | |||
1795 | /** |
||
1796 | * HTMLparser |
||
1797 | * Will parse HTML content based on functions given as stdWrap properties |
||
1798 | * Heavily used together with RTE based content |
||
1799 | * |
||
1800 | * @param string $content Input value undergoing processing in this function. |
||
1801 | * @param array $conf stdWrap properties for HTMLparser. |
||
1802 | * @return string The processed input value |
||
1803 | */ |
||
1804 | public function stdWrap_HTMLparser($content = '', $conf = []) |
||
1805 | { |
||
1806 | if (isset($conf['HTMLparser.']) && is_array($conf['HTMLparser.'])) { |
||
1807 | $content = $this->HTMLparser_TSbridge($content, $conf['HTMLparser.']); |
||
1808 | } |
||
1809 | return $content; |
||
1810 | } |
||
1811 | |||
1812 | /** |
||
1813 | * split |
||
1814 | * Will split the content by a given token and treat the results separately |
||
1815 | * Automatically fills 'current' with a single result |
||
1816 | * |
||
1817 | * @param string $content Input value undergoing processing in this function. |
||
1818 | * @param array $conf stdWrap properties for split. |
||
1819 | * @return string The processed input value |
||
1820 | */ |
||
1821 | public function stdWrap_split($content = '', $conf = []) |
||
1822 | { |
||
1823 | return $this->splitObj($content, $conf['split.']); |
||
1824 | } |
||
1825 | |||
1826 | /** |
||
1827 | * replacement |
||
1828 | * Will execute replacements on the content (optionally with preg-regex) |
||
1829 | * |
||
1830 | * @param string $content Input value undergoing processing in this function. |
||
1831 | * @param array $conf stdWrap properties for replacement. |
||
1832 | * @return string The processed input value |
||
1833 | */ |
||
1834 | public function stdWrap_replacement($content = '', $conf = []) |
||
1835 | { |
||
1836 | return $this->replacement($content, $conf['replacement.']); |
||
1837 | } |
||
1838 | |||
1839 | /** |
||
1840 | * prioriCalc |
||
1841 | * Will use the content as a mathematical term and calculate the result |
||
1842 | * Can be set to 1 to just get a calculated value or 'intval' to get the integer of the result |
||
1843 | * |
||
1844 | * @param string $content Input value undergoing processing in this function. |
||
1845 | * @param array $conf stdWrap properties for prioriCalc. |
||
1846 | * @return string The processed input value |
||
1847 | */ |
||
1848 | public function stdWrap_prioriCalc($content = '', $conf = []) |
||
1849 | { |
||
1850 | $content = MathUtility::calculateWithParentheses($content); |
||
1851 | if (!empty($conf['prioriCalc']) && $conf['prioriCalc'] === 'intval') { |
||
1852 | $content = (int)$content; |
||
1853 | } |
||
1854 | return $content; |
||
1855 | } |
||
1856 | |||
1857 | /** |
||
1858 | * char |
||
1859 | * Returns a one-character string containing the character specified by ascii code. |
||
1860 | * |
||
1861 | * Reliable results only for character codes in the integer range 0 - 127. |
||
1862 | * |
||
1863 | * @see https://php.net/manual/en/function.chr.php |
||
1864 | * @param string $content Input value undergoing processing in this function. |
||
1865 | * @param array $conf stdWrap properties for char. |
||
1866 | * @return string The processed input value |
||
1867 | */ |
||
1868 | public function stdWrap_char($content = '', $conf = []) |
||
1869 | { |
||
1870 | return chr((int)$conf['char']); |
||
1871 | } |
||
1872 | |||
1873 | /** |
||
1874 | * intval |
||
1875 | * Will return an integer value of the current content |
||
1876 | * |
||
1877 | * @param string $content Input value undergoing processing in this function. |
||
1878 | * @return string The processed input value |
||
1879 | */ |
||
1880 | public function stdWrap_intval($content = '') |
||
1881 | { |
||
1882 | return (int)$content; |
||
1883 | } |
||
1884 | |||
1885 | /** |
||
1886 | * Will return a hashed value of the current content |
||
1887 | * |
||
1888 | * @param string $content Input value undergoing processing in this function. |
||
1889 | * @param array $conf stdWrap properties for hash. |
||
1890 | * @return string The processed input value |
||
1891 | * @link https://php.net/manual/de/function.hash-algos.php for a list of supported hash algorithms |
||
1892 | */ |
||
1893 | public function stdWrap_hash($content = '', array $conf = []) |
||
1894 | { |
||
1895 | $algorithm = (string)$this->stdWrapValue('hash', $conf ?? []); |
||
1896 | if (function_exists('hash') && in_array($algorithm, hash_algos())) { |
||
1897 | return hash($algorithm, $content); |
||
1898 | } |
||
1899 | // Non-existing hashing algorithm |
||
1900 | return ''; |
||
1901 | } |
||
1902 | |||
1903 | /** |
||
1904 | * stdWrap_round will return a rounded number with ceil(), floor() or round(), defaults to round() |
||
1905 | * Only the english number format is supported . (dot) as decimal point |
||
1906 | * |
||
1907 | * @param string $content Input value undergoing processing in this function. |
||
1908 | * @param array $conf stdWrap properties for round. |
||
1909 | * @return string The processed input value |
||
1910 | */ |
||
1911 | public function stdWrap_round($content = '', $conf = []) |
||
1912 | { |
||
1913 | return $this->round($content, $conf['round.']); |
||
1914 | } |
||
1915 | |||
1916 | /** |
||
1917 | * numberFormat |
||
1918 | * Will return a formatted number based on configuration given as stdWrap properties |
||
1919 | * |
||
1920 | * @param string $content Input value undergoing processing in this function. |
||
1921 | * @param array $conf stdWrap properties for numberFormat. |
||
1922 | * @return string The processed input value |
||
1923 | */ |
||
1924 | public function stdWrap_numberFormat($content = '', $conf = []) |
||
1925 | { |
||
1926 | return $this->numberFormat((float)$content, $conf['numberFormat.'] ?? []); |
||
1927 | } |
||
1928 | |||
1929 | /** |
||
1930 | * expandList |
||
1931 | * Will return a formatted number based on configuration given as stdWrap properties |
||
1932 | * |
||
1933 | * @param string $content Input value undergoing processing in this function. |
||
1934 | * @return string The processed input value |
||
1935 | */ |
||
1936 | public function stdWrap_expandList($content = '') |
||
1937 | { |
||
1938 | return GeneralUtility::expandList($content); |
||
1939 | } |
||
1940 | |||
1941 | /** |
||
1942 | * date |
||
1943 | * Will return a formatted date based on configuration given according to PHP date/gmdate properties |
||
1944 | * Will return gmdate when the property GMT returns TRUE |
||
1945 | * |
||
1946 | * @param string $content Input value undergoing processing in this function. |
||
1947 | * @param array $conf stdWrap properties for date. |
||
1948 | * @return string The processed input value |
||
1949 | */ |
||
1950 | public function stdWrap_date($content = '', $conf = []) |
||
1951 | { |
||
1952 | // Check for zero length string to mimic default case of date/gmdate. |
||
1953 | $content = (string)$content === '' ? $GLOBALS['EXEC_TIME'] : (int)$content; |
||
1954 | $content = !empty($conf['date.']['GMT']) ? gmdate($conf['date'] ?? null, $content) : date($conf['date'] ?? null, $content); |
||
1955 | return $content; |
||
1956 | } |
||
1957 | |||
1958 | /** |
||
1959 | * strftime |
||
1960 | * Will return a formatted date based on configuration given according to PHP strftime/gmstrftime properties |
||
1961 | * Will return gmstrftime when the property GMT returns TRUE |
||
1962 | * |
||
1963 | * @param string $content Input value undergoing processing in this function. |
||
1964 | * @param array $conf stdWrap properties for strftime. |
||
1965 | * @return string The processed input value |
||
1966 | */ |
||
1967 | public function stdWrap_strftime($content = '', $conf = []) |
||
1968 | { |
||
1969 | // Check for zero length string to mimic default case of strtime/gmstrftime |
||
1970 | $content = (string)$content === '' ? $GLOBALS['EXEC_TIME'] : (int)$content; |
||
1971 | $content = (isset($conf['strftime.']['GMT']) && $conf['strftime.']['GMT']) |
||
1972 | ? gmstrftime($conf['strftime'] ?? null, $content) |
||
1973 | : strftime($conf['strftime'] ?? null, $content); |
||
1974 | if (!empty($conf['strftime.']['charset'])) { |
||
1975 | $output = mb_convert_encoding((string)$content, 'utf-8', trim(strtolower($conf['strftime.']['charset']))); |
||
1976 | return $output ?: $content; |
||
1977 | } |
||
1978 | return $content; |
||
1979 | } |
||
1980 | |||
1981 | /** |
||
1982 | * strtotime |
||
1983 | * Will return a timestamp based on configuration given according to PHP strtotime |
||
1984 | * |
||
1985 | * @param string $content Input value undergoing processing in this function. |
||
1986 | * @param array $conf stdWrap properties for strtotime. |
||
1987 | * @return string The processed input value |
||
1988 | */ |
||
1989 | public function stdWrap_strtotime($content = '', $conf = []) |
||
1990 | { |
||
1991 | if ($conf['strtotime'] !== '1') { |
||
1992 | $content .= ' ' . $conf['strtotime']; |
||
1993 | } |
||
1994 | return strtotime($content, $GLOBALS['EXEC_TIME']); |
||
1995 | } |
||
1996 | |||
1997 | /** |
||
1998 | * age |
||
1999 | * Will return the age of a given timestamp based on configuration given by stdWrap properties |
||
2000 | * |
||
2001 | * @param string $content Input value undergoing processing in this function. |
||
2002 | * @param array $conf stdWrap properties for age. |
||
2003 | * @return string The processed input value |
||
2004 | */ |
||
2005 | public function stdWrap_age($content = '', $conf = []) |
||
2006 | { |
||
2007 | return $this->calcAge((int)($GLOBALS['EXEC_TIME'] ?? 0) - (int)$content, $conf['age'] ?? null); |
||
2008 | } |
||
2009 | |||
2010 | /** |
||
2011 | * case |
||
2012 | * Will transform the content to be upper or lower case only |
||
2013 | * Leaves HTML tags untouched |
||
2014 | * |
||
2015 | * @param string $content Input value undergoing processing in this function. |
||
2016 | * @param array $conf stdWrap properties for case. |
||
2017 | * @return string The processed input value |
||
2018 | */ |
||
2019 | public function stdWrap_case($content = '', $conf = []) |
||
2020 | { |
||
2021 | return $this->HTMLcaseshift($content, $conf['case']); |
||
2022 | } |
||
2023 | |||
2024 | /** |
||
2025 | * bytes |
||
2026 | * Will return the size of a given number in Bytes * |
||
2027 | * |
||
2028 | * @param string $content Input value undergoing processing in this function. |
||
2029 | * @param array $conf stdWrap properties for bytes. |
||
2030 | * @return string The processed input value |
||
2031 | */ |
||
2032 | public function stdWrap_bytes($content = '', $conf = []) |
||
2033 | { |
||
2034 | return GeneralUtility::formatSize((int)$content, $conf['bytes.']['labels'], $conf['bytes.']['base']); |
||
2035 | } |
||
2036 | |||
2037 | /** |
||
2038 | * substring |
||
2039 | * Will return a substring based on position information given by stdWrap properties |
||
2040 | * |
||
2041 | * @param string $content Input value undergoing processing in this function. |
||
2042 | * @param array $conf stdWrap properties for substring. |
||
2043 | * @return string The processed input value |
||
2044 | */ |
||
2045 | public function stdWrap_substring($content = '', $conf = []) |
||
2046 | { |
||
2047 | return $this->substring($content, $conf['substring']); |
||
2048 | } |
||
2049 | |||
2050 | /** |
||
2051 | * cropHTML |
||
2052 | * Crops content to a given size while leaving HTML tags untouched |
||
2053 | * |
||
2054 | * @param string $content Input value undergoing processing in this function. |
||
2055 | * @param array $conf stdWrap properties for cropHTML. |
||
2056 | * @return string The processed input value |
||
2057 | */ |
||
2058 | public function stdWrap_cropHTML($content = '', $conf = []) |
||
2059 | { |
||
2060 | return $this->cropHTML($content, $conf['cropHTML'] ?? ''); |
||
2061 | } |
||
2062 | |||
2063 | /** |
||
2064 | * stripHtml |
||
2065 | * Completely removes HTML tags from content |
||
2066 | * |
||
2067 | * @param string $content Input value undergoing processing in this function. |
||
2068 | * @return string The processed input value |
||
2069 | */ |
||
2070 | public function stdWrap_stripHtml($content = '') |
||
2071 | { |
||
2072 | return strip_tags($content); |
||
2073 | } |
||
2074 | |||
2075 | /** |
||
2076 | * crop |
||
2077 | * Crops content to a given size without caring about HTML tags |
||
2078 | * |
||
2079 | * @param string $content Input value undergoing processing in this function. |
||
2080 | * @param array $conf stdWrap properties for crop. |
||
2081 | * @return string The processed input value |
||
2082 | */ |
||
2083 | public function stdWrap_crop($content = '', $conf = []) |
||
2084 | { |
||
2085 | return $this->crop($content, $conf['crop']); |
||
2086 | } |
||
2087 | |||
2088 | /** |
||
2089 | * rawUrlEncode |
||
2090 | * Encodes content to be used within URLs |
||
2091 | * |
||
2092 | * @param string $content Input value undergoing processing in this function. |
||
2093 | * @return string The processed input value |
||
2094 | */ |
||
2095 | public function stdWrap_rawUrlEncode($content = '') |
||
2096 | { |
||
2097 | return rawurlencode($content); |
||
2098 | } |
||
2099 | |||
2100 | /** |
||
2101 | * htmlSpecialChars |
||
2102 | * Transforms HTML tags to readable text by replacing special characters with their HTML entity |
||
2103 | * When preserveEntities returns TRUE, existing entities will be left untouched |
||
2104 | * |
||
2105 | * @param string $content Input value undergoing processing in this function. |
||
2106 | * @param array $conf stdWrap properties for htmlSpecialChars. |
||
2107 | * @return string The processed input value |
||
2108 | */ |
||
2109 | public function stdWrap_htmlSpecialChars($content = '', $conf = []) |
||
2110 | { |
||
2111 | if (!empty($conf['htmlSpecialChars.']['preserveEntities'])) { |
||
2112 | $content = htmlspecialchars($content, ENT_COMPAT, 'UTF-8', false); |
||
2113 | } else { |
||
2114 | $content = htmlspecialchars($content); |
||
2115 | } |
||
2116 | return $content; |
||
2117 | } |
||
2118 | |||
2119 | /** |
||
2120 | * encodeForJavaScriptValue |
||
2121 | * Escapes content to be used inside JavaScript strings. Single quotes are added around the value. |
||
2122 | * |
||
2123 | * @param string $content Input value undergoing processing in this function |
||
2124 | * @return string The processed input value |
||
2125 | */ |
||
2126 | public function stdWrap_encodeForJavaScriptValue($content = '') |
||
2127 | { |
||
2128 | return GeneralUtility::quoteJSvalue($content); |
||
2129 | } |
||
2130 | |||
2131 | /** |
||
2132 | * doubleBrTag |
||
2133 | * Searches for double line breaks and replaces them with the given value |
||
2134 | * |
||
2135 | * @param string $content Input value undergoing processing in this function. |
||
2136 | * @param array $conf stdWrap properties for doubleBrTag. |
||
2137 | * @return string The processed input value |
||
2138 | */ |
||
2139 | public function stdWrap_doubleBrTag($content = '', $conf = []) |
||
2140 | { |
||
2141 | return preg_replace('/\R{1,2}[\t\x20]*\R{1,2}/', $conf['doubleBrTag'] ?? null, $content); |
||
2142 | } |
||
2143 | |||
2144 | /** |
||
2145 | * br |
||
2146 | * Searches for single line breaks and replaces them with a <br />/<br> tag |
||
2147 | * according to the doctype |
||
2148 | * |
||
2149 | * @param string $content Input value undergoing processing in this function. |
||
2150 | * @return string The processed input value |
||
2151 | */ |
||
2152 | public function stdWrap_br($content = '') |
||
2153 | { |
||
2154 | return nl2br($content, !empty($this->getTypoScriptFrontendController()->xhtmlDoctype)); |
||
2155 | } |
||
2156 | |||
2157 | /** |
||
2158 | * brTag |
||
2159 | * Searches for single line feeds and replaces them with the given value |
||
2160 | * |
||
2161 | * @param string $content Input value undergoing processing in this function. |
||
2162 | * @param array $conf stdWrap properties for brTag. |
||
2163 | * @return string The processed input value |
||
2164 | */ |
||
2165 | public function stdWrap_brTag($content = '', $conf = []) |
||
2166 | { |
||
2167 | return str_replace(LF, $conf['brTag'] ?? null, $content); |
||
2168 | } |
||
2169 | |||
2170 | /** |
||
2171 | * encapsLines |
||
2172 | * Modifies text blocks by searching for lines which are not surrounded by HTML tags yet |
||
2173 | * and wrapping them with values given by stdWrap properties |
||
2174 | * |
||
2175 | * @param string $content Input value undergoing processing in this function. |
||
2176 | * @param array $conf stdWrap properties for erncapsLines. |
||
2177 | * @return string The processed input value |
||
2178 | */ |
||
2179 | public function stdWrap_encapsLines($content = '', $conf = []) |
||
2180 | { |
||
2181 | return $this->encaps_lineSplit($content, $conf['encapsLines.']); |
||
2182 | } |
||
2183 | |||
2184 | /** |
||
2185 | * keywords |
||
2186 | * Transforms content into a CSV list to be used i.e. as keywords within a meta tag |
||
2187 | * |
||
2188 | * @param string $content Input value undergoing processing in this function. |
||
2189 | * @return string The processed input value |
||
2190 | */ |
||
2191 | public function stdWrap_keywords($content = '') |
||
2192 | { |
||
2193 | return $this->keywords($content); |
||
2194 | } |
||
2195 | |||
2196 | /** |
||
2197 | * innerWrap |
||
2198 | * First of a set of different wraps which will be applied in a certain order before or after other functions that modify the content |
||
2199 | * See wrap |
||
2200 | * |
||
2201 | * @param string $content Input value undergoing processing in this function. |
||
2202 | * @param array $conf stdWrap properties for innerWrap. |
||
2203 | * @return string The processed input value |
||
2204 | */ |
||
2205 | public function stdWrap_innerWrap($content = '', $conf = []) |
||
2206 | { |
||
2207 | return $this->wrap($content, $conf['innerWrap'] ?? null); |
||
2208 | } |
||
2209 | |||
2210 | /** |
||
2211 | * innerWrap2 |
||
2212 | * Second of a set of different wraps which will be applied in a certain order before or after other functions that modify the content |
||
2213 | * See wrap |
||
2214 | * |
||
2215 | * @param string $content Input value undergoing processing in this function. |
||
2216 | * @param array $conf stdWrap properties for innerWrap2. |
||
2217 | * @return string The processed input value |
||
2218 | */ |
||
2219 | public function stdWrap_innerWrap2($content = '', $conf = []) |
||
2220 | { |
||
2221 | return $this->wrap($content, $conf['innerWrap2'] ?? null); |
||
2222 | } |
||
2223 | |||
2224 | /** |
||
2225 | * preCObject |
||
2226 | * A content object that is prepended to the current content but between the innerWraps and the rest of the wraps |
||
2227 | * |
||
2228 | * @param string $content Input value undergoing processing in this function. |
||
2229 | * @param array $conf stdWrap properties for preCObject. |
||
2230 | * @return string The processed input value |
||
2231 | */ |
||
2232 | public function stdWrap_preCObject($content = '', $conf = []) |
||
2233 | { |
||
2234 | return $this->cObjGetSingle($conf['preCObject'], $conf['preCObject.'], '/stdWrap/.preCObject') . $content; |
||
2235 | } |
||
2236 | |||
2237 | /** |
||
2238 | * postCObject |
||
2239 | * A content object that is appended to the current content but between the innerWraps and the rest of the wraps |
||
2240 | * |
||
2241 | * @param string $content Input value undergoing processing in this function. |
||
2242 | * @param array $conf stdWrap properties for postCObject. |
||
2243 | * @return string The processed input value |
||
2244 | */ |
||
2245 | public function stdWrap_postCObject($content = '', $conf = []) |
||
2246 | { |
||
2247 | return $content . $this->cObjGetSingle($conf['postCObject'], $conf['postCObject.'], '/stdWrap/.postCObject'); |
||
2248 | } |
||
2249 | |||
2250 | /** |
||
2251 | * wrapAlign |
||
2252 | * Wraps content with a div container having the style attribute text-align set to the given value |
||
2253 | * See wrap |
||
2254 | * |
||
2255 | * @param string $content Input value undergoing processing in this function. |
||
2256 | * @param array $conf stdWrap properties for wrapAlign. |
||
2257 | * @return string The processed input value |
||
2258 | */ |
||
2259 | public function stdWrap_wrapAlign($content = '', $conf = []) |
||
2260 | { |
||
2261 | $wrapAlign = trim($conf['wrapAlign'] ?? ''); |
||
2262 | if ($wrapAlign) { |
||
2263 | $content = $this->wrap($content, '<div style="text-align:' . htmlspecialchars($wrapAlign) . ';">|</div>'); |
||
2264 | } |
||
2265 | return $content; |
||
2266 | } |
||
2267 | |||
2268 | /** |
||
2269 | * typolink |
||
2270 | * Wraps the content with a link tag |
||
2271 | * URLs and other attributes are created automatically by the values given in the stdWrap properties |
||
2272 | * See wrap |
||
2273 | * |
||
2274 | * @param string $content Input value undergoing processing in this function. |
||
2275 | * @param array $conf stdWrap properties for typolink. |
||
2276 | * @return string The processed input value |
||
2277 | */ |
||
2278 | public function stdWrap_typolink($content = '', $conf = []) |
||
2279 | { |
||
2280 | return $this->typoLink($content, $conf['typolink.']); |
||
2281 | } |
||
2282 | |||
2283 | /** |
||
2284 | * wrap |
||
2285 | * This is the "mother" of all wraps |
||
2286 | * Third of a set of different wraps which will be applied in a certain order before or after other functions that modify the content |
||
2287 | * Basically it will put additional content before and after the current content using a split character as a placeholder for the current content |
||
2288 | * The default split character is | but it can be replaced with other characters by the property splitChar |
||
2289 | * Any other wrap that does not have own splitChar settings will be using the default split char though |
||
2290 | * |
||
2291 | * @param string $content Input value undergoing processing in this function. |
||
2292 | * @param array $conf stdWrap properties for wrap. |
||
2293 | * @return string The processed input value |
||
2294 | */ |
||
2295 | public function stdWrap_wrap($content = '', $conf = []) |
||
2296 | { |
||
2297 | return $this->wrap( |
||
2298 | $content, |
||
2299 | $conf['wrap'] ?? null, |
||
2300 | $conf['wrap.']['splitChar'] ?? '|' |
||
2301 | ); |
||
2302 | } |
||
2303 | |||
2304 | /** |
||
2305 | * noTrimWrap |
||
2306 | * Fourth of a set of different wraps which will be applied in a certain order before or after other functions that modify the content |
||
2307 | * The major difference to any other wrap is, that this one can make use of whitespace without trimming * |
||
2308 | * |
||
2309 | * @param string $content Input value undergoing processing in this function. |
||
2310 | * @param array $conf stdWrap properties for noTrimWrap. |
||
2311 | * @return string The processed input value |
||
2312 | */ |
||
2313 | public function stdWrap_noTrimWrap($content = '', $conf = []) |
||
2314 | { |
||
2315 | $splitChar = isset($conf['noTrimWrap.']['splitChar.']) |
||
2316 | ? $this->stdWrap($conf['noTrimWrap.']['splitChar'] ?? '', $conf['noTrimWrap.']['splitChar.']) |
||
2317 | : $conf['noTrimWrap.']['splitChar'] ?? ''; |
||
2318 | if ($splitChar === null || $splitChar === '') { |
||
2319 | $splitChar = '|'; |
||
2320 | } |
||
2321 | $content = $this->noTrimWrap( |
||
2322 | $content, |
||
2323 | $conf['noTrimWrap'], |
||
2324 | $splitChar |
||
2325 | ); |
||
2326 | return $content; |
||
2327 | } |
||
2328 | |||
2329 | /** |
||
2330 | * wrap2 |
||
2331 | * Fifth of a set of different wraps which will be applied in a certain order before or after other functions that modify the content |
||
2332 | * The default split character is | but it can be replaced with other characters by the property splitChar |
||
2333 | * |
||
2334 | * @param string $content Input value undergoing processing in this function. |
||
2335 | * @param array $conf stdWrap properties for wrap2. |
||
2336 | * @return string The processed input value |
||
2337 | */ |
||
2338 | public function stdWrap_wrap2($content = '', $conf = []) |
||
2339 | { |
||
2340 | return $this->wrap( |
||
2341 | $content, |
||
2342 | $conf['wrap2'] ?? null, |
||
2343 | $conf['wrap2.']['splitChar'] ?? '|' |
||
2344 | ); |
||
2345 | } |
||
2346 | |||
2347 | /** |
||
2348 | * dataWrap |
||
2349 | * Sixth of a set of different wraps which will be applied in a certain order before or after other functions that modify the content |
||
2350 | * Can fetch additional content the same way data does (i.e. {field:whatever}) and apply it to the wrap before that is applied to the content |
||
2351 | * |
||
2352 | * @param string $content Input value undergoing processing in this function. |
||
2353 | * @param array $conf stdWrap properties for dataWrap. |
||
2354 | * @return string The processed input value |
||
2355 | */ |
||
2356 | public function stdWrap_dataWrap($content = '', $conf = []) |
||
2357 | { |
||
2358 | return $this->dataWrap($content, $conf['dataWrap']); |
||
2359 | } |
||
2360 | |||
2361 | /** |
||
2362 | * prepend |
||
2363 | * A content object that will be prepended to the current content after most of the wraps have already been applied |
||
2364 | * |
||
2365 | * @param string $content Input value undergoing processing in this function. |
||
2366 | * @param array $conf stdWrap properties for prepend. |
||
2367 | * @return string The processed input value |
||
2368 | */ |
||
2369 | public function stdWrap_prepend($content = '', $conf = []) |
||
2370 | { |
||
2371 | return $this->cObjGetSingle($conf['prepend'], $conf['prepend.'], '/stdWrap/.prepend') . $content; |
||
2372 | } |
||
2373 | |||
2374 | /** |
||
2375 | * append |
||
2376 | * A content object that will be appended to the current content after most of the wraps have already been applied |
||
2377 | * |
||
2378 | * @param string $content Input value undergoing processing in this function. |
||
2379 | * @param array $conf stdWrap properties for append. |
||
2380 | * @return string The processed input value |
||
2381 | */ |
||
2382 | public function stdWrap_append($content = '', $conf = []) |
||
2383 | { |
||
2384 | return $content . $this->cObjGetSingle($conf['append'], $conf['append.'], '/stdWrap/.append'); |
||
2385 | } |
||
2386 | |||
2387 | /** |
||
2388 | * wrap3 |
||
2389 | * Seventh of a set of different wraps which will be applied in a certain order before or after other functions that modify the content |
||
2390 | * The default split character is | but it can be replaced with other characters by the property splitChar |
||
2391 | * |
||
2392 | * @param string $content Input value undergoing processing in this function. |
||
2393 | * @param array $conf stdWrap properties for wrap3. |
||
2394 | * @return string The processed input value |
||
2395 | */ |
||
2396 | public function stdWrap_wrap3($content = '', $conf = []) |
||
2397 | { |
||
2398 | return $this->wrap( |
||
2399 | $content, |
||
2400 | $conf['wrap3'] ?? null, |
||
2401 | $conf['wrap3.']['splitChar'] ?? '|' |
||
2402 | ); |
||
2403 | } |
||
2404 | |||
2405 | /** |
||
2406 | * orderedStdWrap |
||
2407 | * Calls stdWrap for each entry in the provided array |
||
2408 | * |
||
2409 | * @param string $content Input value undergoing processing in this function. |
||
2410 | * @param array $conf stdWrap properties for orderedStdWrap. |
||
2411 | * @return string The processed input value |
||
2412 | */ |
||
2413 | public function stdWrap_orderedStdWrap($content = '', $conf = []) |
||
2414 | { |
||
2415 | $sortedKeysArray = ArrayUtility::filterAndSortByNumericKeys($conf['orderedStdWrap.'], true); |
||
2416 | foreach ($sortedKeysArray as $key) { |
||
2417 | $content = $this->stdWrap($content, $conf['orderedStdWrap.'][$key . '.'] ?? null); |
||
2418 | } |
||
2419 | return $content; |
||
2420 | } |
||
2421 | |||
2422 | /** |
||
2423 | * outerWrap |
||
2424 | * Eighth of a set of different wraps which will be applied in a certain order before or after other functions that modify the content |
||
2425 | * |
||
2426 | * @param string $content Input value undergoing processing in this function. |
||
2427 | * @param array $conf stdWrap properties for outerWrap. |
||
2428 | * @return string The processed input value |
||
2429 | */ |
||
2430 | public function stdWrap_outerWrap($content = '', $conf = []) |
||
2431 | { |
||
2432 | return $this->wrap($content, $conf['outerWrap'] ?? null); |
||
2433 | } |
||
2434 | |||
2435 | /** |
||
2436 | * insertData |
||
2437 | * Can fetch additional content the same way data does and replaces any occurrence of {field:whatever} with this content |
||
2438 | * |
||
2439 | * @param string $content Input value undergoing processing in this function. |
||
2440 | * @return string The processed input value |
||
2441 | */ |
||
2442 | public function stdWrap_insertData($content = '') |
||
2443 | { |
||
2444 | return $this->insertData($content); |
||
2445 | } |
||
2446 | |||
2447 | /** |
||
2448 | * postUserFunc |
||
2449 | * Will execute a user function after the content has been modified by any other stdWrap function |
||
2450 | * |
||
2451 | * @param string $content Input value undergoing processing in this function. |
||
2452 | * @param array $conf stdWrap properties for postUserFunc. |
||
2453 | * @return string The processed input value |
||
2454 | */ |
||
2455 | public function stdWrap_postUserFunc($content = '', $conf = []) |
||
2456 | { |
||
2457 | return $this->callUserFunction($conf['postUserFunc'], $conf['postUserFunc.'] ?? [], $content); |
||
2458 | } |
||
2459 | |||
2460 | /** |
||
2461 | * postUserFuncInt |
||
2462 | * Will execute a user function after the content has been created and each time it is fetched from Cache |
||
2463 | * The result of this function itself will not be cached |
||
2464 | * |
||
2465 | * @param string $content Input value undergoing processing in this function. |
||
2466 | * @param array $conf stdWrap properties for postUserFuncInt. |
||
2467 | * @return string The processed input value |
||
2468 | */ |
||
2469 | public function stdWrap_postUserFuncInt($content = '', $conf = []) |
||
2470 | { |
||
2471 | $substKey = 'INT_SCRIPT.' . $this->getTypoScriptFrontendController()->uniqueHash(); |
||
2472 | $this->getTypoScriptFrontendController()->config['INTincScript'][$substKey] = [ |
||
2473 | 'content' => $content, |
||
2474 | 'postUserFunc' => $conf['postUserFuncInt'], |
||
2475 | 'conf' => $conf['postUserFuncInt.'], |
||
2476 | 'type' => 'POSTUSERFUNC', |
||
2477 | 'cObj' => serialize($this) |
||
2478 | ]; |
||
2479 | $content = '<!--' . $substKey . '-->'; |
||
2480 | return $content; |
||
2481 | } |
||
2482 | |||
2483 | /** |
||
2484 | * prefixComment |
||
2485 | * Will add HTML comments to the content to make it easier to identify certain content elements within the HTML output later on |
||
2486 | * |
||
2487 | * @param string $content Input value undergoing processing in this function. |
||
2488 | * @param array $conf stdWrap properties for prefixComment. |
||
2489 | * @return string The processed input value |
||
2490 | */ |
||
2491 | public function stdWrap_prefixComment($content = '', $conf = []) |
||
2492 | { |
||
2493 | if ( |
||
2494 | (!isset($this->getTypoScriptFrontendController()->config['config']['disablePrefixComment']) || !$this->getTypoScriptFrontendController()->config['config']['disablePrefixComment']) |
||
2495 | && !empty($conf['prefixComment']) |
||
2496 | ) { |
||
2497 | $content = $this->prefixComment($conf['prefixComment'], [], $content); |
||
2498 | } |
||
2499 | return $content; |
||
2500 | } |
||
2501 | |||
2502 | /** |
||
2503 | * editIcons |
||
2504 | * Will render icons for frontend editing as long as there is a BE user logged in |
||
2505 | * |
||
2506 | * @param string $content Input value undergoing processing in this function. |
||
2507 | * @param array $conf stdWrap properties for editIcons. |
||
2508 | * @return string The processed input value |
||
2509 | */ |
||
2510 | public function stdWrap_editIcons($content = '', $conf = []) |
||
2511 | { |
||
2512 | if ($this->getTypoScriptFrontendController()->isBackendUserLoggedIn() && $conf['editIcons']) { |
||
2513 | if (!isset($conf['editIcons.']) || !is_array($conf['editIcons.'])) { |
||
2514 | $conf['editIcons.'] = []; |
||
2515 | } |
||
2516 | $content = $this->editIcons($content, $conf['editIcons'], $conf['editIcons.']); |
||
2517 | } |
||
2518 | return $content; |
||
2519 | } |
||
2520 | |||
2521 | /** |
||
2522 | * editPanel |
||
2523 | * Will render the edit panel for frontend editing as long as there is a BE user logged in |
||
2524 | * |
||
2525 | * @param string $content Input value undergoing processing in this function. |
||
2526 | * @param array $conf stdWrap properties for editPanel. |
||
2527 | * @return string The processed input value |
||
2528 | */ |
||
2529 | public function stdWrap_editPanel($content = '', $conf = []) |
||
2530 | { |
||
2531 | if ($this->getTypoScriptFrontendController()->isBackendUserLoggedIn()) { |
||
2532 | $content = $this->editPanel($content, $conf['editPanel.']); |
||
2533 | } |
||
2534 | return $content; |
||
2535 | } |
||
2536 | |||
2537 | /** |
||
2538 | * Store content into cache |
||
2539 | * |
||
2540 | * @param string $content Input value undergoing processing in these functions. |
||
2541 | * @param array $conf All stdWrap properties, not just the ones for a particular function. |
||
2542 | * @return string The processed input value |
||
2543 | */ |
||
2544 | public function stdWrap_cacheStore($content = '', $conf = []) |
||
2545 | { |
||
2546 | if (!isset($conf['cache.'])) { |
||
2547 | return $content; |
||
2548 | } |
||
2549 | $key = $this->calculateCacheKey($conf['cache.']); |
||
2550 | if (empty($key)) { |
||
2551 | return $content; |
||
2552 | } |
||
2553 | /** @var \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface $cacheFrontend */ |
||
2554 | $cacheFrontend = GeneralUtility::makeInstance(CacheManager::class)->getCache('hash'); |
||
2555 | $tags = $this->calculateCacheTags($conf['cache.']); |
||
2556 | $lifetime = $this->calculateCacheLifetime($conf['cache.']); |
||
2557 | foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['stdWrap_cacheStore'] ?? [] as $_funcRef) { |
||
2558 | $params = [ |
||
2559 | 'key' => $key, |
||
2560 | 'content' => $content, |
||
2561 | 'lifetime' => $lifetime, |
||
2562 | 'tags' => $tags |
||
2563 | ]; |
||
2564 | $ref = $this; // introduced for phpstan to not lose type information when passing $this into callUserFunction |
||
2565 | GeneralUtility::callUserFunction($_funcRef, $params, $ref); |
||
2566 | } |
||
2567 | $cacheFrontend->set($key, $content, $tags, $lifetime); |
||
2568 | return $content; |
||
2569 | } |
||
2570 | |||
2571 | /** |
||
2572 | * stdWrap post process hook |
||
2573 | * can be used by extensions authors to modify the behaviour of stdWrap functions to their needs |
||
2574 | * this hook executes functions at after the content has been modified by the rest of the stdWrap functions but still before debugging |
||
2575 | * |
||
2576 | * @param string $content Input value undergoing processing in these functions. |
||
2577 | * @param array $conf All stdWrap properties, not just the ones for a particular function. |
||
2578 | * @return string The processed input value |
||
2579 | */ |
||
2580 | public function stdWrap_stdWrapPostProcess($content = '', $conf = []) |
||
2581 | { |
||
2582 | foreach ($this->stdWrapHookObjects as $hookObject) { |
||
2583 | /** @var ContentObjectStdWrapHookInterface $hookObject */ |
||
2584 | $content = $hookObject->stdWrapPostProcess($content, $conf, $this); |
||
2585 | } |
||
2586 | return $content; |
||
2587 | } |
||
2588 | |||
2589 | /** |
||
2590 | * debug |
||
2591 | * Will output the content as readable HTML code |
||
2592 | * |
||
2593 | * @param string $content Input value undergoing processing in this function. |
||
2594 | * @return string The processed input value |
||
2595 | */ |
||
2596 | public function stdWrap_debug($content = '') |
||
2597 | { |
||
2598 | return '<pre>' . htmlspecialchars($content) . '</pre>'; |
||
2599 | } |
||
2600 | |||
2601 | /** |
||
2602 | * debugFunc |
||
2603 | * Will output the content in a debug table |
||
2604 | * |
||
2605 | * @param string $content Input value undergoing processing in this function. |
||
2606 | * @param array $conf stdWrap properties for debugFunc. |
||
2607 | * @return string The processed input value |
||
2608 | */ |
||
2609 | public function stdWrap_debugFunc($content = '', $conf = []) |
||
2610 | { |
||
2611 | debug((int)$conf['debugFunc'] === 2 ? [$content] : $content); |
||
2612 | return $content; |
||
2613 | } |
||
2614 | |||
2615 | /** |
||
2616 | * debugData |
||
2617 | * Will output the data used by the current record in a debug table |
||
2618 | * |
||
2619 | * @param string $content Input value undergoing processing in this function. |
||
2620 | * @return string The processed input value |
||
2621 | */ |
||
2622 | public function stdWrap_debugData($content = '') |
||
2623 | { |
||
2624 | debug($this->data, '$cObj->data:'); |
||
2625 | if (is_array($this->alternativeData)) { |
||
2626 | debug($this->alternativeData, '$this->alternativeData'); |
||
2627 | } |
||
2628 | return $content; |
||
2629 | } |
||
2630 | |||
2631 | /** |
||
2632 | * Returns number of rows selected by the query made by the properties set. |
||
2633 | * Implements the stdWrap "numRows" property |
||
2634 | * |
||
2635 | * @param array $conf TypoScript properties for the property (see link to "numRows") |
||
2636 | * @return int The number of rows found by the select |
||
2637 | * @internal |
||
2638 | * @see stdWrap() |
||
2639 | */ |
||
2640 | public function numRows($conf) |
||
2641 | { |
||
2642 | $conf['select.']['selectFields'] = 'count(*)'; |
||
2643 | $statement = $this->exec_getQuery($conf['table'], $conf['select.']); |
||
2644 | |||
2645 | return (int)$statement->fetchOne(); |
||
2646 | } |
||
2647 | |||
2648 | /** |
||
2649 | * Exploding a string by the $char value (if integer its an ASCII value) and returning index $listNum |
||
2650 | * |
||
2651 | * @param string $content String to explode |
||
2652 | * @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()) |
||
2653 | * @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. |
||
2654 | * @return string |
||
2655 | */ |
||
2656 | public function listNum($content, $listNum, $char) |
||
2657 | { |
||
2658 | $char = $char ?: ','; |
||
2659 | if (MathUtility::canBeInterpretedAsInteger($char)) { |
||
2660 | $char = chr((int)$char); |
||
2661 | } |
||
2662 | $temp = explode($char, $content); |
||
2663 | if (empty($temp)) { |
||
2664 | return ''; |
||
2665 | } |
||
2666 | $last = '' . (count($temp) - 1); |
||
2667 | // Take a random item if requested |
||
2668 | if ($listNum === 'rand') { |
||
2669 | $listNum = (string)random_int(0, count($temp) - 1); |
||
2670 | } |
||
2671 | $index = $this->calc(str_ireplace('last', $last, $listNum)); |
||
2672 | return $temp[$index]; |
||
2673 | } |
||
2674 | |||
2675 | /** |
||
2676 | * Compares values together based on the settings in the input TypoScript array and returns the comparison result. |
||
2677 | * Implements the "if" function in TYPO3 TypoScript |
||
2678 | * |
||
2679 | * @param array $conf TypoScript properties defining what to compare |
||
2680 | * @return bool |
||
2681 | * @see stdWrap() |
||
2682 | * @see _parseFunc() |
||
2683 | */ |
||
2684 | public function checkIf($conf) |
||
2685 | { |
||
2686 | if (!is_array($conf)) { |
||
2687 | return true; |
||
2688 | } |
||
2689 | if (isset($conf['directReturn'])) { |
||
2690 | return (bool)$conf['directReturn']; |
||
2691 | } |
||
2692 | $flag = true; |
||
2693 | if (isset($conf['isNull.'])) { |
||
2694 | $isNull = $this->stdWrap('', $conf['isNull.']); |
||
2695 | if ($isNull !== null) { |
||
2696 | $flag = false; |
||
2697 | } |
||
2698 | } |
||
2699 | if (isset($conf['isTrue']) || isset($conf['isTrue.'])) { |
||
2700 | $isTrue = trim((string)$this->stdWrapValue('isTrue', $conf ?? [])); |
||
2701 | if (!$isTrue) { |
||
2702 | $flag = false; |
||
2703 | } |
||
2704 | } |
||
2705 | if (isset($conf['isFalse']) || isset($conf['isFalse.'])) { |
||
2706 | $isFalse = trim((string)$this->stdWrapValue('isFalse', $conf ?? [])); |
||
2707 | if ($isFalse) { |
||
2708 | $flag = false; |
||
2709 | } |
||
2710 | } |
||
2711 | if (isset($conf['isPositive']) || isset($conf['isPositive.'])) { |
||
2712 | $number = $this->calc((string)$this->stdWrapValue('isPositive', $conf ?? [])); |
||
2713 | if ($number < 1) { |
||
2714 | $flag = false; |
||
2715 | } |
||
2716 | } |
||
2717 | if ($flag) { |
||
2718 | $value = trim((string)$this->stdWrapValue('value', $conf ?? [])); |
||
2719 | if (isset($conf['isGreaterThan']) || isset($conf['isGreaterThan.'])) { |
||
2720 | $number = trim((string)$this->stdWrapValue('isGreaterThan', $conf ?? [])); |
||
2721 | if ($number <= $value) { |
||
2722 | $flag = false; |
||
2723 | } |
||
2724 | } |
||
2725 | if (isset($conf['isLessThan']) || isset($conf['isLessThan.'])) { |
||
2726 | $number = trim((string)$this->stdWrapValue('isLessThan', $conf ?? [])); |
||
2727 | if ($number >= $value) { |
||
2728 | $flag = false; |
||
2729 | } |
||
2730 | } |
||
2731 | if (isset($conf['equals']) || isset($conf['equals.'])) { |
||
2732 | $number = trim((string)$this->stdWrapValue('equals', $conf ?? [])); |
||
2733 | if ($number != $value) { |
||
2734 | $flag = false; |
||
2735 | } |
||
2736 | } |
||
2737 | if (isset($conf['isInList']) || isset($conf['isInList.'])) { |
||
2738 | $number = trim((string)$this->stdWrapValue('isInList', $conf ?? [])); |
||
2739 | if (!GeneralUtility::inList($value, $number)) { |
||
2740 | $flag = false; |
||
2741 | } |
||
2742 | } |
||
2743 | if (isset($conf['bitAnd']) || isset($conf['bitAnd.'])) { |
||
2744 | $number = (int)trim((string)$this->stdWrapValue('bitAnd', $conf ?? [])); |
||
2745 | if ((new BitSet($number))->get($value) === false) { |
||
2746 | $flag = false; |
||
2747 | } |
||
2748 | } |
||
2749 | } |
||
2750 | if ($conf['negate'] ?? false) { |
||
2751 | $flag = !$flag; |
||
2752 | } |
||
2753 | return $flag; |
||
2754 | } |
||
2755 | |||
2756 | /** |
||
2757 | * Passes the input value, $theValue, to an instance of "\TYPO3\CMS\Core\Html\HtmlParser" |
||
2758 | * together with the TypoScript options which are first converted from a TS style array |
||
2759 | * to a set of arrays with options for the \TYPO3\CMS\Core\Html\HtmlParser class. |
||
2760 | * |
||
2761 | * @param string $theValue The value to parse by the class \TYPO3\CMS\Core\Html\HtmlParser |
||
2762 | * @param array $conf TypoScript properties for the parser. See link. |
||
2763 | * @return string Return value. |
||
2764 | * @see stdWrap() |
||
2765 | * @see \TYPO3\CMS\Core\Html\HtmlParser::HTMLparserConfig() |
||
2766 | * @see \TYPO3\CMS\Core\Html\HtmlParser::HTMLcleaner() |
||
2767 | */ |
||
2768 | public function HTMLparser_TSbridge($theValue, $conf) |
||
2769 | { |
||
2770 | $htmlParser = GeneralUtility::makeInstance(HtmlParser::class); |
||
2771 | $htmlParserCfg = $htmlParser->HTMLparserConfig($conf); |
||
2772 | return $htmlParser->HTMLcleaner($theValue, $htmlParserCfg[0], $htmlParserCfg[1], $htmlParserCfg[2], $htmlParserCfg[3]); |
||
2773 | } |
||
2774 | |||
2775 | /** |
||
2776 | * Wrapping input value in a regular "wrap" but parses the wrapping value first for "insertData" codes. |
||
2777 | * |
||
2778 | * @param string $content Input string being wrapped |
||
2779 | * @param string $wrap The wrap string, eg. "<strong></strong>" or more likely here '<a href="index.php?id={TSFE:id}"> | </a>' which will wrap the input string in a <a> tag linking to the current page. |
||
2780 | * @return string Output string wrapped in the wrapping value. |
||
2781 | * @see insertData() |
||
2782 | * @see stdWrap() |
||
2783 | */ |
||
2784 | public function dataWrap($content, $wrap) |
||
2785 | { |
||
2786 | return $this->wrap($content, $this->insertData($wrap)); |
||
2787 | } |
||
2788 | |||
2789 | /** |
||
2790 | * Implements the "insertData" property of stdWrap meaning that if strings matching {...} is found in the input string they |
||
2791 | * will be substituted with the return value from getData (datatype) which is passed the content of the curly braces. |
||
2792 | * If the content inside the curly braces starts with a hash sign {#...} it is a field name that must be quoted by Doctrine |
||
2793 | * DBAL and is skipped here for later processing. |
||
2794 | * |
||
2795 | * Example: If input string is "This is the page title: {page:title}" then the part, '{page:title}', will be substituted with |
||
2796 | * the current pages title field value. |
||
2797 | * |
||
2798 | * @param string $str Input value |
||
2799 | * @return string Processed input value |
||
2800 | * @see getData() |
||
2801 | * @see stdWrap() |
||
2802 | * @see dataWrap() |
||
2803 | */ |
||
2804 | public function insertData($str) |
||
2805 | { |
||
2806 | $inside = 0; |
||
2807 | $newVal = ''; |
||
2808 | $pointer = 0; |
||
2809 | $totalLen = strlen($str); |
||
2810 | do { |
||
2811 | if (!$inside) { |
||
2812 | $len = strcspn(substr($str, $pointer), '{'); |
||
2813 | $newVal .= substr($str, $pointer, $len); |
||
2814 | $inside = true; |
||
2815 | if (substr($str, $pointer + $len + 1, 1) === '#') { |
||
2816 | $len2 = strcspn(substr($str, $pointer + $len), '}'); |
||
2817 | $newVal .= substr($str, $pointer + $len, $len2); |
||
2818 | $len += $len2; |
||
2819 | $inside = false; |
||
2820 | } |
||
2821 | } else { |
||
2822 | $len = strcspn(substr($str, $pointer), '}') + 1; |
||
2823 | $newVal .= $this->getData(substr($str, $pointer + 1, $len - 2), $this->data); |
||
2824 | $inside = false; |
||
2825 | } |
||
2826 | $pointer += $len; |
||
2827 | } while ($pointer < $totalLen); |
||
2828 | return $newVal; |
||
2829 | } |
||
2830 | |||
2831 | /** |
||
2832 | * Returns a HTML comment with the second part of input string (divided by "|") where first part is an integer telling how many trailing tabs to put before the comment on a new line. |
||
2833 | * Notice; this function (used by stdWrap) can be disabled by a "config.disablePrefixComment" setting in TypoScript. |
||
2834 | * |
||
2835 | * @param string $str Input value |
||
2836 | * @param array $conf TypoScript Configuration (not used at this point.) |
||
2837 | * @param string $content The content to wrap the comment around. |
||
2838 | * @return string Processed input value |
||
2839 | * @see stdWrap() |
||
2840 | */ |
||
2841 | public function prefixComment($str, $conf, $content) |
||
2842 | { |
||
2843 | if (empty($str)) { |
||
2844 | return $content; |
||
2845 | } |
||
2846 | $parts = explode('|', $str); |
||
2847 | $indent = (int)$parts[0]; |
||
2848 | $comment = htmlspecialchars($this->insertData($parts[1])); |
||
2849 | $output = LF |
||
2850 | . str_pad('', $indent, "\t") . '<!-- ' . $comment . ' [begin] -->' . LF |
||
2851 | . str_pad('', $indent + 1, "\t") . $content . LF |
||
2852 | . str_pad('', $indent, "\t") . '<!-- ' . $comment . ' [end] -->' . LF |
||
2853 | . str_pad('', $indent + 1, "\t"); |
||
2854 | return $output; |
||
2855 | } |
||
2856 | |||
2857 | /** |
||
2858 | * Implements the stdWrap property "substring" which is basically a TypoScript implementation of the PHP function, substr() |
||
2859 | * |
||
2860 | * @param string $content The string to perform the operation on |
||
2861 | * @param string $options The parameters to substring, given as a comma list of integers where the first and second number is passed as arg 1 and 2 to substr(). |
||
2862 | * @return string The processed input value. |
||
2863 | * @internal |
||
2864 | * @see stdWrap() |
||
2865 | */ |
||
2866 | public function substring($content, $options) |
||
2867 | { |
||
2868 | $options = GeneralUtility::intExplode(',', $options . ','); |
||
2869 | if ($options[1]) { |
||
2870 | return mb_substr($content, $options[0], $options[1], 'utf-8'); |
||
2871 | } |
||
2872 | return mb_substr($content, $options[0], null, 'utf-8'); |
||
2873 | } |
||
2874 | |||
2875 | /** |
||
2876 | * Implements the stdWrap property "crop" which is a modified "substr" function allowing to limit a string length to a certain number of chars (from either start or end of string) and having a pre/postfix applied if the string really was cropped. |
||
2877 | * |
||
2878 | * @param string $content The string to perform the operation on |
||
2879 | * @param string $options The parameters splitted by "|": First parameter is the max number of chars of the string. Negative value means cropping from end of string. Second parameter is the pre/postfix string to apply if cropping occurs. Third parameter is a boolean value. If set then crop will be applied at nearest space. |
||
2880 | * @return string The processed input value. |
||
2881 | * @internal |
||
2882 | * @see stdWrap() |
||
2883 | */ |
||
2884 | public function crop($content, $options) |
||
2885 | { |
||
2886 | $options = explode('|', $options); |
||
2887 | $chars = (int)$options[0]; |
||
2888 | $afterstring = trim($options[1] ?? ''); |
||
2889 | $crop2space = trim($options[2] ?? ''); |
||
2890 | if ($chars) { |
||
2891 | if (mb_strlen($content, 'utf-8') > abs($chars)) { |
||
2892 | $truncatePosition = false; |
||
2893 | if ($chars < 0) { |
||
2894 | $content = mb_substr($content, $chars, null, 'utf-8'); |
||
2895 | if ($crop2space) { |
||
2896 | $truncatePosition = strpos($content, ' '); |
||
2897 | } |
||
2898 | $content = $truncatePosition ? $afterstring . substr($content, $truncatePosition) : $afterstring . $content; |
||
2899 | } else { |
||
2900 | $content = mb_substr($content, 0, $chars, 'utf-8'); |
||
2901 | if ($crop2space) { |
||
2902 | $truncatePosition = strrpos($content, ' '); |
||
2903 | } |
||
2904 | $content = $truncatePosition ? substr($content, 0, $truncatePosition) . $afterstring : $content . $afterstring; |
||
2905 | } |
||
2906 | } |
||
2907 | } |
||
2908 | return $content; |
||
2909 | } |
||
2910 | |||
2911 | /** |
||
2912 | * Implements the stdWrap property "cropHTML" which is a modified "substr" function allowing to limit a string length |
||
2913 | * to a certain number of chars (from either start or end of string) and having a pre/postfix applied if the string |
||
2914 | * really was cropped. |
||
2915 | * |
||
2916 | * Compared to stdWrap.crop it respects HTML tags and entities. |
||
2917 | * |
||
2918 | * @param string $content The string to perform the operation on |
||
2919 | * @param string $options The parameters splitted by "|": First parameter is the max number of chars of the string. Negative value means cropping from end of string. Second parameter is the pre/postfix string to apply if cropping occurs. Third parameter is a boolean value. If set then crop will be applied at nearest space. |
||
2920 | * @return string The processed input value. |
||
2921 | * @internal |
||
2922 | * @see stdWrap() |
||
2923 | */ |
||
2924 | public function cropHTML($content, $options) |
||
2925 | { |
||
2926 | $options = explode('|', $options); |
||
2927 | $chars = (int)$options[0]; |
||
2928 | $absChars = abs($chars); |
||
2929 | $replacementForEllipsis = trim($options[1] ?? ''); |
||
2930 | $crop2space = trim($options[2] ?? '') === '1'; |
||
2931 | // Split $content into an array(even items in the array are outside the tags, odd numbers are tag-blocks). |
||
2932 | $tags = 'a|abbr|address|area|article|aside|audio|b|bdi|bdo|blockquote|body|br|button|caption|cite|code|col|colgroup|data|datalist|dd|del|dfn|div|dl|dt|em|embed|fieldset|figcaption|figure|font|footer|form|h1|h2|h3|h4|h5|h6|header|hr|i|iframe|img|input|ins|kbd|keygen|label|legend|li|link|main|map|mark|meter|nav|object|ol|optgroup|option|output|p|param|pre|progress|q|rb|rp|rt|rtc|ruby|s|samp|section|select|small|source|span|strong|sub|sup|table|tbody|td|textarea|tfoot|th|thead|time|tr|track|u|ul|ut|var|video|wbr'; |
||
2933 | $tagsRegEx = ' |
||
2934 | ( |
||
2935 | (?: |
||
2936 | <!--.*?--> # a comment |
||
2937 | | |
||
2938 | <canvas[^>]*>.*?</canvas> # a canvas tag |
||
2939 | | |
||
2940 | <script[^>]*>.*?</script> # a script tag |
||
2941 | | |
||
2942 | <noscript[^>]*>.*?</noscript> # a noscript tag |
||
2943 | | |
||
2944 | <template[^>]*>.*?</template> # a template tag |
||
2945 | ) |
||
2946 | | |
||
2947 | </?(?:' . $tags . ')+ # opening tag (\'<tag\') or closing tag (\'</tag\') |
||
2948 | (?: |
||
2949 | (?: |
||
2950 | (?: |
||
2951 | \\s+\\w[\\w-]* # EITHER spaces, followed by attribute names |
||
2952 | (?: |
||
2953 | \\s*=?\\s* # equals |
||
2954 | (?> |
||
2955 | ".*?" # attribute values in double-quotes |
||
2956 | | |
||
2957 | \'.*?\' # attribute values in single-quotes |
||
2958 | | |
||
2959 | [^\'">\\s]+ # plain attribute values |
||
2960 | ) |
||
2961 | )? |
||
2962 | ) |
||
2963 | | # OR a single dash (for TYPO3 link tag) |
||
2964 | (?: |
||
2965 | \\s+- |
||
2966 | ) |
||
2967 | )+\\s* |
||
2968 | | # OR only spaces |
||
2969 | \\s* |
||
2970 | ) |
||
2971 | /?> # closing the tag with \'>\' or \'/>\' |
||
2972 | )'; |
||
2973 | $splittedContent = preg_split('%' . $tagsRegEx . '%xs', $content, -1, PREG_SPLIT_DELIM_CAPTURE); |
||
2974 | if ($splittedContent === false) { |
||
2975 | $this->logger->debug('Unable to split "{content}" into tags.', ['content' => $content]); |
||
2976 | $splittedContent = []; |
||
2977 | } |
||
2978 | // Reverse array if we are cropping from right. |
||
2979 | if ($chars < 0) { |
||
2980 | $splittedContent = array_reverse($splittedContent); |
||
2981 | } |
||
2982 | // Crop the text (chars of tag-blocks are not counted). |
||
2983 | $strLen = 0; |
||
2984 | // This is the offset of the content item which was cropped. |
||
2985 | $croppedOffset = null; |
||
2986 | $countSplittedContent = count($splittedContent); |
||
2987 | for ($offset = 0; $offset < $countSplittedContent; $offset++) { |
||
2988 | if ($offset % 2 === 0) { |
||
2989 | $tempContent = $splittedContent[$offset]; |
||
2990 | $thisStrLen = mb_strlen(html_entity_decode($tempContent, ENT_COMPAT, 'UTF-8'), 'utf-8'); |
||
2991 | if ($strLen + $thisStrLen > $absChars) { |
||
2992 | $croppedOffset = $offset; |
||
2993 | $cropPosition = $absChars - $strLen; |
||
2994 | // The snippet "&[^&\s;]{2,8};" in the RegEx below represents entities. |
||
2995 | $patternMatchEntityAsSingleChar = '(&[^&\\s;]{2,8};|.)'; |
||
2996 | $cropRegEx = $chars < 0 ? '#' . $patternMatchEntityAsSingleChar . '{0,' . ($cropPosition + 1) . '}$#uis' : '#^' . $patternMatchEntityAsSingleChar . '{0,' . ($cropPosition + 1) . '}#uis'; |
||
2997 | if (preg_match($cropRegEx, $tempContent, $croppedMatch)) { |
||
2998 | $tempContentPlusOneCharacter = $croppedMatch[0]; |
||
2999 | } else { |
||
3000 | $tempContentPlusOneCharacter = false; |
||
3001 | } |
||
3002 | $cropRegEx = $chars < 0 ? '#' . $patternMatchEntityAsSingleChar . '{0,' . $cropPosition . '}$#uis' : '#^' . $patternMatchEntityAsSingleChar . '{0,' . $cropPosition . '}#uis'; |
||
3003 | if (preg_match($cropRegEx, $tempContent, $croppedMatch)) { |
||
3004 | $tempContent = $croppedMatch[0]; |
||
3005 | if ($crop2space && $tempContentPlusOneCharacter !== false) { |
||
3006 | $cropRegEx = $chars < 0 ? '#(?<=\\s)' . $patternMatchEntityAsSingleChar . '{0,' . $cropPosition . '}$#uis' : '#^' . $patternMatchEntityAsSingleChar . '{0,' . $cropPosition . '}(?=\\s)#uis'; |
||
3007 | if (preg_match($cropRegEx, $tempContentPlusOneCharacter, $croppedMatch)) { |
||
3008 | $tempContent = $croppedMatch[0]; |
||
3009 | } |
||
3010 | } |
||
3011 | } |
||
3012 | $splittedContent[$offset] = $tempContent; |
||
3013 | break; |
||
3014 | } |
||
3015 | $strLen += $thisStrLen; |
||
3016 | } |
||
3017 | } |
||
3018 | // Close cropped tags. |
||
3019 | $closingTags = []; |
||
3020 | if ($croppedOffset !== null) { |
||
3021 | $openingTagRegEx = '#^<(\\w+)(?:\\s|>)#'; |
||
3022 | $closingTagRegEx = '#^</(\\w+)(?:\\s|>)#'; |
||
3023 | for ($offset = $croppedOffset - 1; $offset >= 0; $offset = $offset - 2) { |
||
3024 | if (substr($splittedContent[$offset], -2) === '/>') { |
||
3025 | // Ignore empty element tags (e.g. <br />). |
||
3026 | continue; |
||
3027 | } |
||
3028 | preg_match($chars < 0 ? $closingTagRegEx : $openingTagRegEx, $splittedContent[$offset], $matches); |
||
3029 | $tagName = $matches[1] ?? null; |
||
3030 | if ($tagName !== null) { |
||
3031 | // Seek for the closing (or opening) tag. |
||
3032 | $countSplittedContent = count($splittedContent); |
||
3033 | for ($seekingOffset = $offset + 2; $seekingOffset < $countSplittedContent; $seekingOffset = $seekingOffset + 2) { |
||
3034 | preg_match($chars < 0 ? $openingTagRegEx : $closingTagRegEx, $splittedContent[$seekingOffset], $matches); |
||
3035 | $seekingTagName = $matches[1] ?? null; |
||
3036 | if ($tagName === $seekingTagName) { |
||
3037 | // We found a matching tag. |
||
3038 | // Add closing tag only if it occurs after the cropped content item. |
||
3039 | if ($seekingOffset > $croppedOffset) { |
||
3040 | $closingTags[] = $splittedContent[$seekingOffset]; |
||
3041 | } |
||
3042 | break; |
||
3043 | } |
||
3044 | } |
||
3045 | } |
||
3046 | } |
||
3047 | // Drop the cropped items of the content array. The $closingTags will be added later on again. |
||
3048 | array_splice($splittedContent, $croppedOffset + 1); |
||
3049 | } |
||
3050 | $splittedContent = array_merge($splittedContent, [ |
||
3051 | $croppedOffset !== null ? $replacementForEllipsis : '' |
||
3052 | ], $closingTags); |
||
3053 | // Reverse array once again if we are cropping from the end. |
||
3054 | if ($chars < 0) { |
||
3055 | $splittedContent = array_reverse($splittedContent); |
||
3056 | } |
||
3057 | return implode('', $splittedContent); |
||
3058 | } |
||
3059 | |||
3060 | /** |
||
3061 | * Performs basic mathematical evaluation of the input string. Does NOT take parenthesis and operator precedence into account! (for that, see \TYPO3\CMS\Core\Utility\MathUtility::calculateWithPriorityToAdditionAndSubtraction()) |
||
3062 | * |
||
3063 | * @param string $val The string to evaluate. Example: "3+4*10/5" will generate "35". Only integer numbers can be used. |
||
3064 | * @return int The result (might be a float if you did a division of the numbers). |
||
3065 | * @see \TYPO3\CMS\Core\Utility\MathUtility::calculateWithPriorityToAdditionAndSubtraction() |
||
3066 | */ |
||
3067 | public function calc($val) |
||
3068 | { |
||
3069 | $parts = GeneralUtility::splitCalc($val, '+-*/'); |
||
3070 | $value = 0; |
||
3071 | foreach ($parts as $part) { |
||
3072 | $theVal = $part[1]; |
||
3073 | $sign = $part[0]; |
||
3074 | if ((string)(int)$theVal === (string)$theVal) { |
||
3075 | $theVal = (int)$theVal; |
||
3076 | } else { |
||
3077 | $theVal = 0; |
||
3078 | } |
||
3079 | if ($sign === '-') { |
||
3080 | $value -= $theVal; |
||
3081 | } |
||
3082 | if ($sign === '+') { |
||
3083 | $value += $theVal; |
||
3084 | } |
||
3085 | if ($sign === '/') { |
||
3086 | if ((int)$theVal) { |
||
3087 | $value /= (int)$theVal; |
||
3088 | } |
||
3089 | } |
||
3090 | if ($sign === '*') { |
||
3091 | $value *= $theVal; |
||
3092 | } |
||
3093 | } |
||
3094 | return $value; |
||
3095 | } |
||
3096 | |||
3097 | /** |
||
3098 | * Implements the "split" property of stdWrap; Splits a string based on a token (given in TypoScript properties), sets the "current" value to each part and then renders a content object pointer to by a number. |
||
3099 | * In classic TypoScript (like 'content (default)'/'styles.content (default)') this is used to render tables, splitting rows and cells by tokens and putting them together again wrapped in <td> tags etc. |
||
3100 | * Implements the "optionSplit" processing of the TypoScript options for each splitted value to parse. |
||
3101 | * |
||
3102 | * @param string $value The string value to explode by $conf[token] and process each part |
||
3103 | * @param array $conf TypoScript properties for "split |
||
3104 | * @return string Compiled result |
||
3105 | * @internal |
||
3106 | * @see stdWrap() |
||
3107 | * @see \TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject::processItemStates() |
||
3108 | */ |
||
3109 | public function splitObj($value, $conf) |
||
3110 | { |
||
3111 | $conf['token'] = isset($conf['token.']) ? $this->stdWrap($conf['token'], $conf['token.']) : $conf['token']; |
||
3112 | if ($conf['token'] === '') { |
||
3113 | return $value; |
||
3114 | } |
||
3115 | $valArr = explode($conf['token'], $value); |
||
3116 | |||
3117 | // return value directly by returnKey. No further processing |
||
3118 | if (!empty($valArr) && (MathUtility::canBeInterpretedAsInteger($conf['returnKey'] ?? null) || ($conf['returnKey.'] ?? false))) { |
||
3119 | $key = (int)$this->stdWrapValue('returnKey', $conf ?? []); |
||
3120 | return $valArr[$key] ?? ''; |
||
3121 | } |
||
3122 | |||
3123 | // return the amount of elements. No further processing |
||
3124 | if (!empty($valArr) && (($conf['returnCount'] ?? false) || ($conf['returnCount.'] ?? false))) { |
||
3125 | $returnCount = (bool)$this->stdWrapValue('returnCount', $conf ?? []); |
||
3126 | return $returnCount ? count($valArr) : 0; |
||
3127 | } |
||
3128 | |||
3129 | // calculate splitCount |
||
3130 | $splitCount = count($valArr); |
||
3131 | $max = (int)$this->stdWrapValue('max', $conf ?? []); |
||
3132 | if ($max && $splitCount > $max) { |
||
3133 | $splitCount = $max; |
||
3134 | } |
||
3135 | $min = (int)$this->stdWrapValue('min', $conf ?? []); |
||
3136 | if ($min && $splitCount < $min) { |
||
3137 | $splitCount = $min; |
||
3138 | } |
||
3139 | $wrap = (string)$this->stdWrapValue('wrap', $conf ?? []); |
||
3140 | $cObjNumSplitConf = isset($conf['cObjNum.']) ? (string)$this->stdWrap($conf['cObjNum'], $conf['cObjNum.']) : (string)$conf['cObjNum']; |
||
3141 | $splitArr = []; |
||
3142 | if ($wrap !== '' || $cObjNumSplitConf !== '') { |
||
3143 | $splitArr['wrap'] = $wrap; |
||
3144 | $splitArr['cObjNum'] = $cObjNumSplitConf; |
||
3145 | $splitArr = GeneralUtility::makeInstance(TypoScriptService::class) |
||
3146 | ->explodeConfigurationForOptionSplit($splitArr, $splitCount); |
||
3147 | } |
||
3148 | $content = ''; |
||
3149 | for ($a = 0; $a < $splitCount; $a++) { |
||
3150 | $this->getTypoScriptFrontendController()->register['SPLIT_COUNT'] = $a; |
||
3151 | $value = '' . $valArr[$a]; |
||
3152 | $this->data[$this->currentValKey] = $value; |
||
3153 | if ($splitArr[$a]['cObjNum']) { |
||
3154 | $objName = (int)$splitArr[$a]['cObjNum']; |
||
3155 | $value = isset($conf[$objName . '.']) |
||
3156 | ? $this->stdWrap($this->cObjGet($conf[$objName . '.'], $objName . '.'), $conf[$objName . '.']) |
||
3157 | : $this->cObjGet($conf[$objName . '.'], $objName . '.'); |
||
3158 | } |
||
3159 | $wrap = (string)$this->stdWrapValue('wrap', $splitArr[$a] ?? []); |
||
3160 | if ($wrap) { |
||
3161 | $value = $this->wrap($value, $wrap); |
||
3162 | } |
||
3163 | $content .= $value; |
||
3164 | } |
||
3165 | return $content; |
||
3166 | } |
||
3167 | |||
3168 | /** |
||
3169 | * Processes ordered replacements on content data. |
||
3170 | * |
||
3171 | * @param string $content The content to be processed |
||
3172 | * @param array $configuration The TypoScript configuration for stdWrap.replacement |
||
3173 | * @return string The processed content data |
||
3174 | */ |
||
3175 | protected function replacement($content, array $configuration) |
||
3176 | { |
||
3177 | // Sorts actions in configuration by numeric index |
||
3178 | ksort($configuration, SORT_NUMERIC); |
||
3179 | foreach ($configuration as $index => $action) { |
||
3180 | // Checks whether we have a valid action and a numeric key ending with a dot ("10.") |
||
3181 | if (is_array($action) && substr($index, -1) === '.' && MathUtility::canBeInterpretedAsInteger(substr($index, 0, -1))) { |
||
3182 | $content = $this->replacementSingle($content, $action); |
||
3183 | } |
||
3184 | } |
||
3185 | return $content; |
||
3186 | } |
||
3187 | |||
3188 | /** |
||
3189 | * Processes a single search/replace on content data. |
||
3190 | * |
||
3191 | * @param string $content The content to be processed |
||
3192 | * @param array $configuration The TypoScript of the search/replace action to be processed |
||
3193 | * @return string The processed content data |
||
3194 | */ |
||
3195 | protected function replacementSingle($content, array $configuration) |
||
3196 | { |
||
3197 | if ((isset($configuration['search']) || isset($configuration['search.'])) && (isset($configuration['replace']) || isset($configuration['replace.']))) { |
||
3198 | // Gets the strings |
||
3199 | $search = (string)$this->stdWrapValue('search', $configuration ?? []); |
||
3200 | $replace = (string)$this->stdWrapValue('replace', $configuration, null); |
||
3201 | |||
3202 | // Determines whether regular expression shall be used |
||
3203 | $useRegularExpression = (bool)$this->stdWrapValue('useRegExp', $configuration, false); |
||
3204 | |||
3205 | // Determines whether replace-pattern uses option-split |
||
3206 | $useOptionSplitReplace = (bool)$this->stdWrapValue('useOptionSplitReplace', $configuration, false); |
||
3207 | |||
3208 | // Performs a replacement by preg_replace() |
||
3209 | if ($useRegularExpression) { |
||
3210 | // Get separator-character which precedes the string and separates search-string from the modifiers |
||
3211 | $separator = $search[0]; |
||
3212 | $startModifiers = strrpos($search, $separator); |
||
3213 | if ($separator !== false && $startModifiers > 0) { |
||
3214 | $modifiers = substr($search, $startModifiers + 1); |
||
3215 | // remove "e" (eval-modifier), which would otherwise allow to run arbitrary PHP-code |
||
3216 | $modifiers = str_replace('e', '', $modifiers); |
||
3217 | $search = substr($search, 0, $startModifiers + 1) . $modifiers; |
||
3218 | } |
||
3219 | if ($useOptionSplitReplace) { |
||
3220 | // init for replacement |
||
3221 | $splitCount = preg_match_all($search, $content, $matches); |
||
3222 | $typoScriptService = GeneralUtility::makeInstance(TypoScriptService::class); |
||
3223 | $replaceArray = $typoScriptService->explodeConfigurationForOptionSplit([$replace], $splitCount); |
||
3224 | $replaceCount = 0; |
||
3225 | |||
3226 | $replaceCallback = function ($match) use ($replaceArray, $search, &$replaceCount) { |
||
3227 | $replaceCount++; |
||
3228 | return preg_replace($search, $replaceArray[$replaceCount - 1][0], $match[0]); |
||
3229 | }; |
||
3230 | $content = preg_replace_callback($search, $replaceCallback, $content); |
||
3231 | } else { |
||
3232 | $content = preg_replace($search, $replace, $content); |
||
3233 | } |
||
3234 | } elseif ($useOptionSplitReplace) { |
||
3235 | // turn search-string into a preg-pattern |
||
3236 | $searchPreg = '#' . preg_quote($search, '#') . '#'; |
||
3237 | |||
3238 | // init for replacement |
||
3239 | $splitCount = preg_match_all($searchPreg, $content, $matches); |
||
3240 | $typoScriptService = GeneralUtility::makeInstance(TypoScriptService::class); |
||
3241 | $replaceArray = $typoScriptService->explodeConfigurationForOptionSplit([$replace], $splitCount); |
||
3242 | $replaceCount = 0; |
||
3243 | |||
3244 | $replaceCallback = function () use ($replaceArray, &$replaceCount) { |
||
3245 | $replaceCount++; |
||
3246 | return $replaceArray[$replaceCount - 1][0]; |
||
3247 | }; |
||
3248 | $content = preg_replace_callback($searchPreg, $replaceCallback, $content); |
||
3249 | } else { |
||
3250 | $content = str_replace($search, $replace, $content); |
||
3251 | } |
||
3252 | } |
||
3253 | return $content; |
||
3254 | } |
||
3255 | |||
3256 | /** |
||
3257 | * Implements the "round" property of stdWrap |
||
3258 | * This is a Wrapper function for PHP's rounding functions (round,ceil,floor), defaults to round() |
||
3259 | * |
||
3260 | * @param string $content Value to process |
||
3261 | * @param array $conf TypoScript configuration for round |
||
3262 | * @return string The formatted number |
||
3263 | */ |
||
3264 | protected function round($content, array $conf = []) |
||
3265 | { |
||
3266 | $decimals = (int)$this->stdWrapValue('decimals', $conf, 0); |
||
3267 | $type = $this->stdWrapValue('roundType', $conf ?? []); |
||
3268 | $floatVal = (float)$content; |
||
3269 | switch ($type) { |
||
3270 | case 'ceil': |
||
3271 | $content = ceil($floatVal); |
||
3272 | break; |
||
3273 | case 'floor': |
||
3274 | $content = floor($floatVal); |
||
3275 | break; |
||
3276 | case 'round': |
||
3277 | |||
3278 | default: |
||
3279 | $content = round($floatVal, $decimals); |
||
3280 | } |
||
3281 | return $content; |
||
3282 | } |
||
3283 | |||
3284 | /** |
||
3285 | * Implements the stdWrap property "numberFormat" |
||
3286 | * This is a Wrapper function for php's number_format() |
||
3287 | * |
||
3288 | * @param float $content Value to process |
||
3289 | * @param array $conf TypoScript Configuration for numberFormat |
||
3290 | * @return string The formatted number |
||
3291 | */ |
||
3292 | public function numberFormat($content, $conf) |
||
3293 | { |
||
3294 | $decimals = (int)$this->stdWrapValue('decimals', $conf, 0); |
||
3295 | $dec_point = (string)$this->stdWrapValue('dec_point', $conf, '.'); |
||
3296 | $thousands_sep = (string)$this->stdWrapValue('thousands_sep', $conf, ','); |
||
3297 | return number_format((float)$content, $decimals, $dec_point, $thousands_sep); |
||
3298 | } |
||
3299 | |||
3300 | /** |
||
3301 | * Implements the stdWrap property, "parseFunc". |
||
3302 | * This is a function with a lot of interesting uses. In classic TypoScript this is used to process text |
||
3303 | * from the bodytext field; This included highlighting of search words, changing http:// and mailto: prefixed strings into etc. |
||
3304 | * It is still a very important function for processing of bodytext which is normally stored in the database |
||
3305 | * in a format which is not fully ready to be outputted. |
||
3306 | * This situation has not become better by having a RTE around... |
||
3307 | * |
||
3308 | * This function is actually just splitting the input content according to the configuration of "external blocks". |
||
3309 | * This means that before the input string is actually "parsed" it will be splitted into the parts configured to BE parsed |
||
3310 | * (while other parts/blocks should NOT be parsed). |
||
3311 | * Therefore the actual processing of the parseFunc properties goes on in ->_parseFunc() |
||
3312 | * |
||
3313 | * @param string $theValue The value to process. |
||
3314 | * @param array $conf TypoScript configuration for parseFunc |
||
3315 | * @param string $ref Reference to get configuration from. Eg. "< lib.parseFunc" which means that the configuration of the object path "lib.parseFunc" will be retrieved and MERGED with what is in $conf! |
||
3316 | * @return string The processed value |
||
3317 | * @see _parseFunc() |
||
3318 | */ |
||
3319 | public function parseFunc($theValue, $conf, $ref = '') |
||
3320 | { |
||
3321 | // Fetch / merge reference, if any |
||
3322 | if ($ref) { |
||
3323 | $temp_conf = [ |
||
3324 | 'parseFunc' => $ref, |
||
3325 | 'parseFunc.' => $conf |
||
3326 | ]; |
||
3327 | $temp_conf = $this->mergeTSRef($temp_conf, 'parseFunc'); |
||
3328 | $conf = $temp_conf['parseFunc.']; |
||
3329 | } |
||
3330 | // Process: |
||
3331 | if ((string)($conf['externalBlocks'] ?? '') === '') { |
||
3332 | return $this->_parseFunc($theValue, $conf); |
||
3333 | } |
||
3334 | $tags = strtolower(implode(',', GeneralUtility::trimExplode(',', $conf['externalBlocks']))); |
||
3335 | $htmlParser = GeneralUtility::makeInstance(HtmlParser::class); |
||
3336 | $parts = $htmlParser->splitIntoBlock($tags, $theValue); |
||
3337 | foreach ($parts as $k => $v) { |
||
3338 | if ($k % 2) { |
||
3339 | // font: |
||
3340 | $tagName = strtolower($htmlParser->getFirstTagName($v)); |
||
3341 | $cfg = $conf['externalBlocks.'][$tagName . '.']; |
||
3342 | if ($cfg['stripNLprev'] || $cfg['stripNL']) { |
||
3343 | $parts[$k - 1] = preg_replace('/' . CR . '?' . LF . '[ ]*$/', '', $parts[$k - 1]); |
||
3344 | } |
||
3345 | if ($cfg['stripNLnext'] || $cfg['stripNL']) { |
||
3346 | $parts[$k + 1] = preg_replace('/^[ ]*' . CR . '?' . LF . '/', '', $parts[$k + 1]); |
||
3347 | } |
||
3348 | } |
||
3349 | } |
||
3350 | foreach ($parts as $k => $v) { |
||
3351 | if ($k % 2) { |
||
3352 | $tag = $htmlParser->getFirstTag($v); |
||
3353 | $tagName = strtolower($htmlParser->getFirstTagName($v)); |
||
3354 | $cfg = $conf['externalBlocks.'][$tagName . '.']; |
||
3355 | if ($cfg['callRecursive']) { |
||
3356 | $parts[$k] = $this->parseFunc($htmlParser->removeFirstAndLastTag($v), $conf); |
||
3357 | if (!$cfg['callRecursive.']['dontWrapSelf']) { |
||
3358 | if ($cfg['callRecursive.']['alternativeWrap']) { |
||
3359 | $parts[$k] = $this->wrap($parts[$k], $cfg['callRecursive.']['alternativeWrap']); |
||
3360 | } else { |
||
3361 | if (is_array($cfg['callRecursive.']['tagStdWrap.'])) { |
||
3362 | $tag = $this->stdWrap($tag, $cfg['callRecursive.']['tagStdWrap.']); |
||
3363 | } |
||
3364 | $parts[$k] = $tag . $parts[$k] . '</' . $tagName . '>'; |
||
3365 | } |
||
3366 | } |
||
3367 | } elseif ($cfg['HTMLtableCells']) { |
||
3368 | $rowParts = $htmlParser->splitIntoBlock('tr', $parts[$k]); |
||
3369 | foreach ($rowParts as $kk => $vv) { |
||
3370 | if ($kk % 2) { |
||
3371 | $colParts = $htmlParser->splitIntoBlock('td,th', $vv); |
||
3372 | $cc = 0; |
||
3373 | foreach ($colParts as $kkk => $vvv) { |
||
3374 | if ($kkk % 2) { |
||
3375 | $cc++; |
||
3376 | $tag = $htmlParser->getFirstTag($vvv); |
||
3377 | $tagName = strtolower($htmlParser->getFirstTagName($vvv)); |
||
3378 | $colParts[$kkk] = $htmlParser->removeFirstAndLastTag($vvv); |
||
3379 | if ($cfg['HTMLtableCells.'][$cc . '.']['callRecursive'] || !isset($cfg['HTMLtableCells.'][$cc . '.']['callRecursive']) && $cfg['HTMLtableCells.']['default.']['callRecursive']) { |
||
3380 | if ($cfg['HTMLtableCells.']['addChr10BetweenParagraphs']) { |
||
3381 | $colParts[$kkk] = str_replace('</p><p>', '</p>' . LF . '<p>', $colParts[$kkk]); |
||
3382 | } |
||
3383 | $colParts[$kkk] = $this->parseFunc($colParts[$kkk], $conf); |
||
3384 | } |
||
3385 | $tagStdWrap = is_array($cfg['HTMLtableCells.'][$cc . '.']['tagStdWrap.']) |
||
3386 | ? $cfg['HTMLtableCells.'][$cc . '.']['tagStdWrap.'] |
||
3387 | : $cfg['HTMLtableCells.']['default.']['tagStdWrap.']; |
||
3388 | if (is_array($tagStdWrap)) { |
||
3389 | $tag = $this->stdWrap($tag, $tagStdWrap); |
||
3390 | } |
||
3391 | $stdWrap = is_array($cfg['HTMLtableCells.'][$cc . '.']['stdWrap.']) |
||
3392 | ? $cfg['HTMLtableCells.'][$cc . '.']['stdWrap.'] |
||
3393 | : $cfg['HTMLtableCells.']['default.']['stdWrap.']; |
||
3394 | if (is_array($stdWrap)) { |
||
3395 | $colParts[$kkk] = $this->stdWrap($colParts[$kkk], $stdWrap); |
||
3396 | } |
||
3397 | $colParts[$kkk] = $tag . $colParts[$kkk] . '</' . $tagName . '>'; |
||
3398 | } |
||
3399 | } |
||
3400 | $rowParts[$kk] = implode('', $colParts); |
||
3401 | } |
||
3402 | } |
||
3403 | $parts[$k] = implode('', $rowParts); |
||
3404 | } |
||
3405 | if (is_array($cfg['stdWrap.'])) { |
||
3406 | $parts[$k] = $this->stdWrap($parts[$k], $cfg['stdWrap.']); |
||
3407 | } |
||
3408 | } else { |
||
3409 | $parts[$k] = $this->_parseFunc($parts[$k], $conf); |
||
3410 | } |
||
3411 | } |
||
3412 | return implode('', $parts); |
||
3413 | } |
||
3414 | |||
3415 | /** |
||
3416 | * Helper function for parseFunc() |
||
3417 | * |
||
3418 | * @param string $theValue The value to process. |
||
3419 | * @param array $conf TypoScript configuration for parseFunc |
||
3420 | * @return string The processed value |
||
3421 | * @internal |
||
3422 | * @see parseFunc() |
||
3423 | */ |
||
3424 | public function _parseFunc($theValue, $conf) |
||
3425 | { |
||
3426 | if (!empty($conf['if.']) && !$this->checkIf($conf['if.'])) { |
||
3427 | return $theValue; |
||
3428 | } |
||
3429 | // Indicates that the data is from within a tag. |
||
3430 | $inside = false; |
||
3431 | // Pointer to the total string position |
||
3432 | $pointer = 0; |
||
3433 | // Loaded with the current typo-tag if any. |
||
3434 | $currentTag = null; |
||
3435 | $stripNL = 0; |
||
3436 | $contentAccum = []; |
||
3437 | $contentAccumP = 0; |
||
3438 | $allowTags = strtolower(str_replace(' ', '', $conf['allowTags'] ?? '')); |
||
3439 | $denyTags = strtolower(str_replace(' ', '', $conf['denyTags'] ?? '')); |
||
3440 | $totalLen = strlen($theValue); |
||
3441 | do { |
||
3442 | if (!$inside) { |
||
3443 | if ($currentTag === null) { |
||
3444 | // These operations should only be performed on code outside the typotags... |
||
3445 | // data: this checks that we enter tags ONLY if the first char in the tag is alphanumeric OR '/' |
||
3446 | $len_p = 0; |
||
3447 | $c = 100; |
||
3448 | do { |
||
3449 | $len = strcspn(substr($theValue, $pointer + $len_p), '<'); |
||
3450 | $len_p += $len + 1; |
||
3451 | $endChar = ord(strtolower(substr($theValue, $pointer + $len_p, 1))); |
||
3452 | $c--; |
||
3453 | } while ($c > 0 && $endChar && ($endChar < 97 || $endChar > 122) && $endChar != 47); |
||
3454 | $len = $len_p - 1; |
||
3455 | } else { |
||
3456 | $len = $this->getContentLengthOfCurrentTag($theValue, $pointer, (string)$currentTag[0]); |
||
3457 | } |
||
3458 | // $data is the content until the next <tag-start or end is detected. |
||
3459 | // In case of a currentTag set, this would mean all data between the start- and end-tags |
||
3460 | $data = substr($theValue, $pointer, $len); |
||
3461 | if ($data !== false) { |
||
3462 | if ($stripNL) { |
||
3463 | // If the previous tag was set to strip NewLines in the beginning of the next data-chunk. |
||
3464 | $data = preg_replace('/^[ ]*' . CR . '?' . LF . '/', '', $data); |
||
3465 | if ($data === null) { |
||
3466 | $this->logger->debug('Stripping new lines failed for "{data}"', ['data' => $data]); |
||
3467 | $data = ''; |
||
3468 | } |
||
3469 | } |
||
3470 | // These operations should only be performed on code outside the tags... |
||
3471 | if (!is_array($currentTag)) { |
||
3472 | // Constants |
||
3473 | $tsfe = $this->getTypoScriptFrontendController(); |
||
3474 | $tmpConstants = $tsfe->tmpl->setup['constants.'] ?? null; |
||
3475 | if (!empty($conf['constants']) && is_array($tmpConstants)) { |
||
3476 | foreach ($tmpConstants as $key => $val) { |
||
3477 | if (is_string($val)) { |
||
3478 | $data = str_replace('###' . $key . '###', $val, $data); |
||
3479 | } |
||
3480 | } |
||
3481 | } |
||
3482 | // Short |
||
3483 | if (isset($conf['short.']) && is_array($conf['short.'])) { |
||
3484 | $shortWords = $conf['short.']; |
||
3485 | krsort($shortWords); |
||
3486 | foreach ($shortWords as $key => $val) { |
||
3487 | if (is_string($val)) { |
||
3488 | $data = str_replace($key, $val, $data); |
||
3489 | } |
||
3490 | } |
||
3491 | } |
||
3492 | // stdWrap |
||
3493 | if (isset($conf['plainTextStdWrap.']) && is_array($conf['plainTextStdWrap.'])) { |
||
3494 | $data = $this->stdWrap($data, $conf['plainTextStdWrap.']); |
||
3495 | } |
||
3496 | // userFunc |
||
3497 | if ($conf['userFunc'] ?? false) { |
||
3498 | $data = $this->callUserFunction($conf['userFunc'], $conf['userFunc.'] ?? [], $data); |
||
3499 | } |
||
3500 | // Makelinks: (Before search-words as we need the links to be generated when searchwords go on...!) |
||
3501 | if ($conf['makelinks'] ?? false) { |
||
3502 | $data = $this->http_makelinks($data, $conf['makelinks.']['http.']); |
||
3503 | $data = $this->mailto_makelinks($data, $conf['makelinks.']['mailto.'] ?? []); |
||
3504 | } |
||
3505 | // Search Words: |
||
3506 | if (($tsfe->no_cache ?? false) && $conf['sword'] && is_array($tsfe->sWordList) && $tsfe->sWordRegEx) { |
||
3507 | $newstring = ''; |
||
3508 | do { |
||
3509 | $pregSplitMode = 'i'; |
||
3510 | if (isset($tsfe->config['config']['sword_noMixedCase']) && !empty($tsfe->config['config']['sword_noMixedCase'])) { |
||
3511 | $pregSplitMode = ''; |
||
3512 | } |
||
3513 | $pieces = preg_split('/' . $tsfe->sWordRegEx . '/' . $pregSplitMode, $data, 2); |
||
3514 | $newstring .= $pieces[0]; |
||
3515 | $match_len = strlen($data) - (strlen($pieces[0]) + strlen($pieces[1])); |
||
3516 | $inTag = false; |
||
3517 | if (strpos($pieces[0], '<') !== false || strpos($pieces[0], '>') !== false) { |
||
3518 | // Returns TRUE, if a '<' is closer to the string-end than '>'. |
||
3519 | // This is the case if we're INSIDE a tag (that could have been |
||
3520 | // made by makelinks...) and we must secure, that the inside of a tag is |
||
3521 | // not marked up. |
||
3522 | $inTag = strrpos($pieces[0], '<') > strrpos($pieces[0], '>'); |
||
3523 | } |
||
3524 | // The searchword: |
||
3525 | $match = substr($data, strlen($pieces[0]), $match_len); |
||
3526 | if (trim($match) && strlen($match) > 1 && !$inTag) { |
||
3527 | $match = $this->wrap($match, $conf['sword']); |
||
3528 | } |
||
3529 | // Concatenate the Search Word again. |
||
3530 | $newstring .= $match; |
||
3531 | $data = $pieces[1]; |
||
3532 | } while ($pieces[1]); |
||
3533 | $data = $newstring; |
||
3534 | } |
||
3535 | } |
||
3536 | // Search for tags to process in current data and |
||
3537 | // call this method recursively if found |
||
3538 | if (strpos($data, '<') !== false && isset($conf['tags.']) && is_array($conf['tags.'])) { |
||
3539 | foreach ($conf['tags.'] as $tag => $tagConfig) { |
||
3540 | // only match tag `a` in `<a href"...">` but not in `<abbr>` |
||
3541 | if (preg_match('#<' . $tag . '[\s/>]#', $data)) { |
||
3542 | $data = $this->_parseFunc($data, $conf); |
||
3543 | break; |
||
3544 | } |
||
3545 | } |
||
3546 | } |
||
3547 | $contentAccum[$contentAccumP] = isset($contentAccum[$contentAccumP]) |
||
3548 | ? $contentAccum[$contentAccumP] . $data |
||
3549 | : $data; |
||
3550 | } |
||
3551 | $inside = true; |
||
3552 | } else { |
||
3553 | // tags |
||
3554 | $len = strcspn(substr($theValue, $pointer), '>') + 1; |
||
3555 | $data = substr($theValue, $pointer, $len); |
||
3556 | if (StringUtility::endsWith($data, '/>') && strpos($data, '<link ') !== 0) { |
||
3557 | $tagContent = substr($data, 1, -2); |
||
3558 | } else { |
||
3559 | $tagContent = substr($data, 1, -1); |
||
3560 | } |
||
3561 | $tag = explode(' ', trim($tagContent), 2); |
||
3562 | $tag[0] = strtolower($tag[0]); |
||
3563 | // end tag like </li> |
||
3564 | if ($tag[0][0] === '/') { |
||
3565 | $tag[0] = substr($tag[0], 1); |
||
3566 | $tag['out'] = 1; |
||
3567 | } |
||
3568 | if ($conf['tags.'][$tag[0]] ?? false) { |
||
3569 | $treated = false; |
||
3570 | $stripNL = false; |
||
3571 | // in-tag |
||
3572 | if (!$currentTag && (!isset($tag['out']) || !$tag['out'])) { |
||
3573 | // $currentTag (array!) is the tag we are currently processing |
||
3574 | $currentTag = $tag; |
||
3575 | $contentAccumP++; |
||
3576 | $treated = true; |
||
3577 | // in-out-tag: img and other empty tags |
||
3578 | if (preg_match('/^(area|base|br|col|hr|img|input|meta|param)$/i', (string)$tag[0])) { |
||
3579 | $tag['out'] = 1; |
||
3580 | } |
||
3581 | } |
||
3582 | // out-tag |
||
3583 | if ($currentTag[0] === $tag[0] && isset($tag['out']) && $tag['out']) { |
||
3584 | $theName = $conf['tags.'][$tag[0]]; |
||
3585 | $theConf = $conf['tags.'][$tag[0] . '.']; |
||
3586 | // This flag indicates, that NL- (13-10-chars) should be stripped first and last. |
||
3587 | $stripNL = (bool)($theConf['stripNL'] ?? false); |
||
3588 | // This flag indicates, that this TypoTag section should NOT be included in the nonTypoTag content. |
||
3589 | $breakOut = (bool)($theConf['breakoutTypoTagContent'] ?? false); |
||
3590 | $this->parameters = []; |
||
3591 | if (isset($currentTag[1])) { |
||
3592 | // decode HTML entities in attributes, since they're processed |
||
3593 | $params = GeneralUtility::get_tag_attributes((string)$currentTag[1], true); |
||
3594 | if (is_array($params)) { |
||
3595 | foreach ($params as $option => $val) { |
||
3596 | // contains non-encoded values |
||
3597 | $this->parameters[strtolower($option)] = $val; |
||
3598 | } |
||
3599 | } |
||
3600 | $this->parameters['allParams'] = trim((string)$currentTag[1]); |
||
3601 | } |
||
3602 | // Removes NL in the beginning and end of the tag-content AND at the end of the currentTagBuffer. |
||
3603 | // $stripNL depends on the configuration of the current tag |
||
3604 | if ($stripNL) { |
||
3605 | $contentAccum[$contentAccumP - 1] = preg_replace('/' . CR . '?' . LF . '[ ]*$/', '', $contentAccum[$contentAccumP - 1]); |
||
3606 | $contentAccum[$contentAccumP] = preg_replace('/^[ ]*' . CR . '?' . LF . '/', '', $contentAccum[$contentAccumP]); |
||
3607 | $contentAccum[$contentAccumP] = preg_replace('/' . CR . '?' . LF . '[ ]*$/', '', $contentAccum[$contentAccumP]); |
||
3608 | } |
||
3609 | $this->data[$this->currentValKey] = $contentAccum[$contentAccumP]; |
||
3610 | $newInput = $this->cObjGetSingle($theName, $theConf, '/parseFunc/.tags.' . $tag[0]); |
||
3611 | // fetch the content object |
||
3612 | $contentAccum[$contentAccumP] = $newInput; |
||
3613 | $contentAccumP++; |
||
3614 | // If the TypoTag section |
||
3615 | if (!$breakOut) { |
||
3616 | if (!isset($contentAccum[$contentAccumP - 2])) { |
||
3617 | $contentAccum[$contentAccumP - 2] = ''; |
||
3618 | } |
||
3619 | $contentAccum[$contentAccumP - 2] .= ($contentAccum[$contentAccumP - 1] ?? '') . ($contentAccum[$contentAccumP] ?? ''); |
||
3620 | unset($contentAccum[$contentAccumP]); |
||
3621 | unset($contentAccum[$contentAccumP - 1]); |
||
3622 | $contentAccumP -= 2; |
||
3623 | } |
||
3624 | $currentTag = null; |
||
3625 | $treated = true; |
||
3626 | } |
||
3627 | // other tags |
||
3628 | if (!$treated) { |
||
3629 | $contentAccum[$contentAccumP] .= $data; |
||
3630 | } |
||
3631 | } else { |
||
3632 | // If a tag was not a typo tag, then it is just added to the content |
||
3633 | $stripNL = false; |
||
3634 | if (GeneralUtility::inList($allowTags, (string)$tag[0]) || |
||
3635 | ($denyTags !== '*' && !GeneralUtility::inList($denyTags, (string)$tag[0]))) { |
||
3636 | $contentAccum[$contentAccumP] = isset($contentAccum[$contentAccumP]) |
||
3637 | ? $contentAccum[$contentAccumP] . $data |
||
3638 | : $data; |
||
3639 | } else { |
||
3640 | $contentAccum[$contentAccumP] = isset($contentAccum[$contentAccumP]) |
||
3641 | ? $contentAccum[$contentAccumP] . htmlspecialchars($data) |
||
3642 | : htmlspecialchars($data); |
||
3643 | } |
||
3644 | } |
||
3645 | $inside = false; |
||
3646 | } |
||
3647 | $pointer += $len; |
||
3648 | } while ($pointer < $totalLen); |
||
3649 | // Parsing nonTypoTag content (all even keys): |
||
3650 | reset($contentAccum); |
||
3651 | $contentAccumCount = count($contentAccum); |
||
3652 | for ($a = 0; $a < $contentAccumCount; $a++) { |
||
3653 | if ($a % 2 != 1) { |
||
3654 | // stdWrap |
||
3655 | if (isset($conf['nonTypoTagStdWrap.']) && is_array($conf['nonTypoTagStdWrap.'])) { |
||
3656 | $contentAccum[$a] = $this->stdWrap((string)($contentAccum[$a] ?? ''), $conf['nonTypoTagStdWrap.']); |
||
3657 | } |
||
3658 | // userFunc |
||
3659 | if (!empty($conf['nonTypoTagUserFunc'])) { |
||
3660 | $contentAccum[$a] = $this->callUserFunction($conf['nonTypoTagUserFunc'], $conf['nonTypoTagUserFunc.'] ?? [], (string)($contentAccum[$a] ?? '')); |
||
3661 | } |
||
3662 | } |
||
3663 | } |
||
3664 | return implode('', $contentAccum); |
||
3665 | } |
||
3666 | |||
3667 | /** |
||
3668 | * Lets you split the content by LF and process each line independently. Used to format content made with the RTE. |
||
3669 | * |
||
3670 | * @param string $theValue The input value |
||
3671 | * @param array $conf TypoScript options |
||
3672 | * @return string The processed input value being returned; Splitted lines imploded by LF again. |
||
3673 | * @internal |
||
3674 | */ |
||
3675 | public function encaps_lineSplit($theValue, $conf) |
||
3676 | { |
||
3677 | if ((string)$theValue === '') { |
||
3678 | return ''; |
||
3679 | } |
||
3680 | $lParts = explode(LF, $theValue); |
||
3681 | |||
3682 | // When the last element is an empty linebreak we need to remove it, otherwise we will have a duplicate empty line. |
||
3683 | $lastPartIndex = count($lParts) - 1; |
||
3684 | if ($lParts[$lastPartIndex] === '' && trim($lParts[$lastPartIndex - 1], CR) === '') { |
||
3685 | array_pop($lParts); |
||
3686 | } |
||
3687 | |||
3688 | $encapTags = GeneralUtility::trimExplode(',', strtolower($conf['encapsTagList']), true); |
||
3689 | $nonWrappedTag = $conf['nonWrappedTag']; |
||
3690 | $defaultAlign = trim((string)$this->stdWrapValue('defaultAlign', $conf ?? [])); |
||
3691 | |||
3692 | $str_content = ''; |
||
3693 | foreach ($lParts as $k => $l) { |
||
3694 | $sameBeginEnd = 0; |
||
3695 | $emptyTag = false; |
||
3696 | $l = trim($l); |
||
3697 | $attrib = []; |
||
3698 | $nonWrapped = false; |
||
3699 | $tagName = ''; |
||
3700 | if (isset($l[0]) && $l[0] === '<' && substr($l, -1) === '>') { |
||
3701 | $fwParts = explode('>', substr($l, 1), 2); |
||
3702 | [$tagName] = explode(' ', $fwParts[0], 2); |
||
3703 | if (!$fwParts[1]) { |
||
3704 | if (substr($tagName, -1) === '/') { |
||
3705 | $tagName = substr($tagName, 0, -1); |
||
3706 | } |
||
3707 | if (substr($fwParts[0], -1) === '/') { |
||
3708 | $sameBeginEnd = 1; |
||
3709 | $emptyTag = true; |
||
3710 | // decode HTML entities, they're encoded later again |
||
3711 | $attrib = GeneralUtility::get_tag_attributes('<' . substr($fwParts[0], 0, -1) . '>', true); |
||
3712 | } |
||
3713 | } else { |
||
3714 | $backParts = GeneralUtility::revExplode('<', substr($fwParts[1], 0, -1), 2); |
||
3715 | // decode HTML entities, they're encoded later again |
||
3716 | $attrib = GeneralUtility::get_tag_attributes('<' . $fwParts[0] . '>', true); |
||
3717 | $str_content = $backParts[0]; |
||
3718 | $sameBeginEnd = substr(strtolower($backParts[1]), 1, strlen($tagName)) === strtolower($tagName); |
||
3719 | } |
||
3720 | } |
||
3721 | if ($sameBeginEnd && in_array(strtolower($tagName), $encapTags)) { |
||
3722 | $uTagName = strtoupper($tagName); |
||
3723 | $uTagName = strtoupper($conf['remapTag.'][$uTagName] ?? $uTagName); |
||
3724 | } else { |
||
3725 | $uTagName = strtoupper($nonWrappedTag); |
||
3726 | // The line will be wrapped: $uTagName should not be an empty tag |
||
3727 | $emptyTag = false; |
||
3728 | $str_content = $lParts[$k]; |
||
3729 | $nonWrapped = true; |
||
3730 | $attrib = []; |
||
3731 | } |
||
3732 | // Wrapping all inner-content: |
||
3733 | if (is_array($conf['innerStdWrap_all.'])) { |
||
3734 | $str_content = $this->stdWrap($str_content, $conf['innerStdWrap_all.']); |
||
3735 | } |
||
3736 | if ($uTagName) { |
||
3737 | // Setting common attributes |
||
3738 | if (isset($conf['addAttributes.'][$uTagName . '.']) && is_array($conf['addAttributes.'][$uTagName . '.'])) { |
||
3739 | foreach ($conf['addAttributes.'][$uTagName . '.'] as $kk => $vv) { |
||
3740 | if (!is_array($vv)) { |
||
3741 | if ((string)$conf['addAttributes.'][$uTagName . '.'][$kk . '.']['setOnly'] === 'blank') { |
||
3742 | if ((string)($attrib[$kk] ?? '') === '') { |
||
3743 | $attrib[$kk] = $vv; |
||
3744 | } |
||
3745 | } elseif ((string)$conf['addAttributes.'][$uTagName . '.'][$kk . '.']['setOnly'] === 'exists') { |
||
3746 | if (!isset($attrib[$kk])) { |
||
3747 | $attrib[$kk] = $vv; |
||
3748 | } |
||
3749 | } else { |
||
3750 | $attrib[$kk] = $vv; |
||
3751 | } |
||
3752 | } |
||
3753 | } |
||
3754 | } |
||
3755 | // Wrapping all inner-content: |
||
3756 | if (isset($conf['encapsLinesStdWrap.'][$uTagName . '.']) && is_array($conf['encapsLinesStdWrap.'][$uTagName . '.'])) { |
||
3757 | $str_content = $this->stdWrap($str_content, $conf['encapsLinesStdWrap.'][$uTagName . '.']); |
||
3758 | } |
||
3759 | // Default align |
||
3760 | if ((!isset($attrib['align']) || !$attrib['align']) && $defaultAlign) { |
||
3761 | $attrib['align'] = $defaultAlign; |
||
3762 | } |
||
3763 | // implode (insecure) attributes, that's why `htmlspecialchars` is used here |
||
3764 | $params = GeneralUtility::implodeAttributes($attrib, true); |
||
3765 | if (!isset($conf['removeWrapping']) || !$conf['removeWrapping'] || ($emptyTag && $conf['removeWrapping.']['keepSingleTag'])) { |
||
3766 | $selfClosingTagList = ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr']; |
||
3767 | if ($emptyTag && in_array(strtolower($uTagName), $selfClosingTagList, true)) { |
||
3768 | $str_content = '<' . strtolower($uTagName) . (trim($params) ? ' ' . trim($params) : '') . ' />'; |
||
3769 | } else { |
||
3770 | $str_content = '<' . strtolower($uTagName) . (trim($params) ? ' ' . trim($params) : '') . '>' . $str_content . '</' . strtolower($uTagName) . '>'; |
||
3771 | } |
||
3772 | } |
||
3773 | } |
||
3774 | if ($nonWrapped && isset($conf['wrapNonWrappedLines']) && $conf['wrapNonWrappedLines']) { |
||
3775 | $str_content = $this->wrap($str_content, $conf['wrapNonWrappedLines']); |
||
3776 | } |
||
3777 | $lParts[$k] = $str_content; |
||
3778 | } |
||
3779 | return implode(LF, $lParts); |
||
3780 | } |
||
3781 | |||
3782 | /** |
||
3783 | * Finds URLS in text and makes it to a real link. |
||
3784 | * Will find all strings prefixed with "http://" and "https://" in the $data string and make them into a link, |
||
3785 | * linking to the URL we should have found. |
||
3786 | * |
||
3787 | * @param string $data The string in which to search for "http:// |
||
3788 | * @param array $conf Configuration for makeLinks, see link |
||
3789 | * @return string The processed input string, being returned. |
||
3790 | * @see _parseFunc() |
||
3791 | */ |
||
3792 | public function http_makelinks($data, $conf) |
||
3793 | { |
||
3794 | $parts = []; |
||
3795 | $aTagParams = $this->getATagParams($conf); |
||
3796 | foreach (['http://', 'https://'] as $scheme) { |
||
3797 | $textpieces = explode($scheme, $data); |
||
3798 | $pieces = count($textpieces); |
||
3799 | $textstr = $textpieces[0]; |
||
3800 | for ($i = 1; $i < $pieces; $i++) { |
||
3801 | $len = strcspn($textpieces[$i], chr(32) . "\t" . CRLF); |
||
3802 | if (trim(substr($textstr, -1)) === '' && $len) { |
||
3803 | $lastChar = substr($textpieces[$i], $len - 1, 1); |
||
3804 | if (!preg_match('/[A-Za-z0-9\\/#_-]/', $lastChar)) { |
||
3805 | $len--; |
||
3806 | } |
||
3807 | // Included '\/' 3/12 |
||
3808 | $parts[0] = substr($textpieces[$i], 0, $len); |
||
3809 | $parts[1] = substr($textpieces[$i], $len); |
||
3810 | $keep = $conf['keep']; |
||
3811 | $linkParts = parse_url($scheme . $parts[0]); |
||
3812 | $linktxt = ''; |
||
3813 | if (strpos($keep, 'scheme') !== false) { |
||
3814 | $linktxt = $scheme; |
||
3815 | } |
||
3816 | $linktxt .= $linkParts['host']; |
||
3817 | if (strpos($keep, 'path') !== false) { |
||
3818 | $linktxt .= $linkParts['path']; |
||
3819 | // Added $linkParts['query'] 3/12 |
||
3820 | if (strpos($keep, 'query') !== false && $linkParts['query']) { |
||
3821 | $linktxt .= '?' . $linkParts['query']; |
||
3822 | } elseif ($linkParts['path'] === '/') { |
||
3823 | $linktxt = substr($linktxt, 0, -1); |
||
3824 | } |
||
3825 | } |
||
3826 | $target = (string)$this->stdWrapValue('extTarget', $conf, $this->getTypoScriptFrontendController()->extTarget); |
||
3827 | |||
3828 | // check for jump URLs or similar |
||
3829 | $linkUrl = $this->processUrl(UrlProcessorInterface::CONTEXT_COMMON, $scheme . $parts[0], $conf) ?? ''; |
||
3830 | |||
3831 | $res = '<a href="' . htmlspecialchars($linkUrl) . '"' |
||
3832 | . ($target !== '' ? ' target="' . htmlspecialchars($target) . '"' : '') |
||
3833 | . $aTagParams . '>'; |
||
3834 | |||
3835 | $wrap = (string)$this->stdWrapValue('wrap', $conf ?? []); |
||
3836 | if ((string)$conf['ATagBeforeWrap'] !== '') { |
||
3837 | $res = $res . $this->wrap($linktxt, $wrap) . '</a>'; |
||
3838 | } else { |
||
3839 | $res = $this->wrap($res . $linktxt . '</a>', $wrap); |
||
3840 | } |
||
3841 | $textstr .= $res . $parts[1]; |
||
3842 | } else { |
||
3843 | $textstr .= $scheme . $textpieces[$i]; |
||
3844 | } |
||
3845 | } |
||
3846 | $data = $textstr; |
||
3847 | } |
||
3848 | return $textstr; |
||
3849 | } |
||
3850 | |||
3851 | /** |
||
3852 | * Will find all strings prefixed with "mailto:" in the $data string and make them into a link, |
||
3853 | * linking to the email address they point to. |
||
3854 | * |
||
3855 | * @param string $data The string in which to search for "mailto: |
||
3856 | * @param array $conf Configuration for makeLinks, see link |
||
3857 | * @return string The processed input string, being returned. |
||
3858 | * @see _parseFunc() |
||
3859 | */ |
||
3860 | public function mailto_makelinks($data, $conf) |
||
3861 | { |
||
3862 | $conf = (array)$conf; |
||
3863 | $parts = []; |
||
3864 | // http-split |
||
3865 | $aTagParams = $this->getATagParams($conf); |
||
3866 | $textpieces = explode('mailto:', $data); |
||
3867 | $pieces = count($textpieces); |
||
3868 | $textstr = $textpieces[0]; |
||
3869 | $tsfe = $this->getTypoScriptFrontendController(); |
||
3870 | for ($i = 1; $i < $pieces; $i++) { |
||
3871 | $len = strcspn($textpieces[$i], chr(32) . "\t" . CRLF); |
||
3872 | if (trim(substr($textstr, -1)) === '' && $len) { |
||
3873 | $lastChar = substr($textpieces[$i], $len - 1, 1); |
||
3874 | if (!preg_match('/[A-Za-z0-9]/', $lastChar)) { |
||
3875 | $len--; |
||
3876 | } |
||
3877 | $parts[0] = substr($textpieces[$i], 0, $len); |
||
3878 | $parts[1] = substr($textpieces[$i], $len); |
||
3879 | $linktxt = (string)preg_replace('/\\?.*/', '', $parts[0]); |
||
3880 | [$mailToUrl, $linktxt] = $this->getMailTo($parts[0], $linktxt); |
||
3881 | $mailToUrl = $tsfe->spamProtectEmailAddresses === 'ascii' ? $mailToUrl : htmlspecialchars($mailToUrl); |
||
3882 | $res = '<a href="' . $mailToUrl . '"' . $aTagParams . '>'; |
||
3883 | $wrap = (string)$this->stdWrapValue('wrap', $conf); |
||
3884 | if ((string)$conf['ATagBeforeWrap'] !== '') { |
||
3885 | $res = $res . $this->wrap($linktxt, $wrap) . '</a>'; |
||
3886 | } else { |
||
3887 | $res = $this->wrap($res . $linktxt . '</a>', $wrap); |
||
3888 | } |
||
3889 | $textstr .= $res . $parts[1]; |
||
3890 | } else { |
||
3891 | $textstr .= 'mailto:' . $textpieces[$i]; |
||
3892 | } |
||
3893 | } |
||
3894 | return $textstr; |
||
3895 | } |
||
3896 | |||
3897 | /** |
||
3898 | * Creates and returns a TypoScript "imgResource". |
||
3899 | * The value ($file) can either be a file reference (TypoScript resource) or the string "GIFBUILDER". |
||
3900 | * In the first case a current image is returned, possibly scaled down or otherwise processed. |
||
3901 | * In the latter case a GIFBUILDER image is returned; This means an image is made by TYPO3 from layers of elements as GIFBUILDER defines. |
||
3902 | * In the function IMG_RESOURCE() this function is called like $this->getImgResource($conf['file'], $conf['file.']); |
||
3903 | * |
||
3904 | * Structure of the returned info array: |
||
3905 | * 0 => width |
||
3906 | * 1 => height |
||
3907 | * 2 => file extension |
||
3908 | * 3 => file name |
||
3909 | * origFile => original file name |
||
3910 | * origFile_mtime => original file mtime |
||
3911 | * -- only available if processed via FAL: -- |
||
3912 | * originalFile => original file object |
||
3913 | * processedFile => processed file object |
||
3914 | * fileCacheHash => checksum of processed file |
||
3915 | * |
||
3916 | * @param string|File|FileReference $file A "imgResource" TypoScript data type. Either a TypoScript file resource, a file or a file reference object or the string GIFBUILDER. See description above. |
||
3917 | * @param array $fileArray TypoScript properties for the imgResource type |
||
3918 | * @return array|null Returns info-array |
||
3919 | * @see cImage() |
||
3920 | * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder |
||
3921 | */ |
||
3922 | public function getImgResource($file, $fileArray) |
||
3923 | { |
||
3924 | $importedFile = null; |
||
3925 | if (empty($file) && empty($fileArray)) { |
||
3926 | return null; |
||
3927 | } |
||
3928 | if (!is_array($fileArray)) { |
||
3929 | $fileArray = (array)$fileArray; |
||
3930 | } |
||
3931 | $imageResource = null; |
||
3932 | if ($file === 'GIFBUILDER') { |
||
3933 | $gifCreator = GeneralUtility::makeInstance(GifBuilder::class); |
||
3934 | $theImage = ''; |
||
3935 | if ($GLOBALS['TYPO3_CONF_VARS']['GFX']['gdlib']) { |
||
3936 | $gifCreator->start($fileArray, $this->data); |
||
3937 | $theImage = $gifCreator->gifBuild(); |
||
3938 | } |
||
3939 | $imageResource = $gifCreator->getImageDimensions($theImage); |
||
3940 | $imageResource['origFile'] = $theImage; |
||
3941 | } else { |
||
3942 | if ($file instanceof File) { |
||
3943 | $fileObject = $file; |
||
3944 | } elseif ($file instanceof FileReference) { |
||
3945 | $fileObject = $file->getOriginalFile(); |
||
3946 | } else { |
||
3947 | try { |
||
3948 | if (isset($fileArray['import.']) && $fileArray['import.']) { |
||
3949 | $importedFile = trim($this->stdWrap('', $fileArray['import.'])); |
||
3950 | if (!empty($importedFile)) { |
||
3951 | $file = $importedFile; |
||
3952 | } |
||
3953 | } |
||
3954 | |||
3955 | if (MathUtility::canBeInterpretedAsInteger($file)) { |
||
3956 | $treatIdAsReference = $this->stdWrapValue('treatIdAsReference', $fileArray ?? []); |
||
3957 | if (!empty($treatIdAsReference)) { |
||
3958 | $file = $this->getResourceFactory()->getFileReferenceObject($file); |
||
3959 | $fileObject = $file->getOriginalFile(); |
||
3960 | } else { |
||
3961 | $fileObject = $this->getResourceFactory()->getFileObject($file); |
||
3962 | } |
||
3963 | } elseif (preg_match('/^(0|[1-9][0-9]*):/', $file)) { // combined identifier |
||
3964 | $fileObject = $this->getResourceFactory()->retrieveFileOrFolderObject($file); |
||
3965 | } else { |
||
3966 | if (isset($importedFile) && !empty($importedFile) && !empty($fileArray['import'])) { |
||
3967 | $file = $fileArray['import'] . $file; |
||
3968 | } |
||
3969 | $fileObject = $this->getResourceFactory()->retrieveFileOrFolderObject($file); |
||
3970 | } |
||
3971 | } catch (Exception $exception) { |
||
3972 | $this->logger->warning('The image "{file}" could not be found and won\'t be included in frontend output', [ |
||
3973 | 'file' => $file, |
||
3974 | 'exception' => $exception, |
||
3975 | ]); |
||
3976 | return null; |
||
3977 | } |
||
3978 | } |
||
3979 | if ($fileObject instanceof File) { |
||
3980 | $processingConfiguration = []; |
||
3981 | $processingConfiguration['width'] = $this->stdWrapValue('width', $fileArray ?? []); |
||
3982 | $processingConfiguration['height'] = $this->stdWrapValue('height', $fileArray ?? []); |
||
3983 | $processingConfiguration['fileExtension'] = $this->stdWrapValue('ext', $fileArray ?? []); |
||
3984 | $processingConfiguration['maxWidth'] = (int)$this->stdWrapValue('maxW', $fileArray ?? []); |
||
3985 | $processingConfiguration['maxHeight'] = (int)$this->stdWrapValue('maxH', $fileArray ?? []); |
||
3986 | $processingConfiguration['minWidth'] = (int)$this->stdWrapValue('minW', $fileArray ?? []); |
||
3987 | $processingConfiguration['minHeight'] = (int)$this->stdWrapValue('minH', $fileArray ?? []); |
||
3988 | $processingConfiguration['noScale'] = $this->stdWrapValue('noScale', $fileArray ?? []); |
||
3989 | $processingConfiguration['additionalParameters'] = $this->stdWrapValue('params', $fileArray ?? []); |
||
3990 | $processingConfiguration['frame'] = (int)$this->stdWrapValue('frame', $fileArray ?? []); |
||
3991 | if ($file instanceof FileReference) { |
||
3992 | $processingConfiguration['crop'] = $this->getCropAreaFromFileReference($file, $fileArray); |
||
3993 | } else { |
||
3994 | $processingConfiguration['crop'] = $this->getCropAreaFromFromTypoScriptSettings($fileObject, $fileArray); |
||
3995 | } |
||
3996 | |||
3997 | // Possibility to cancel/force profile extraction |
||
3998 | // see $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_stripColorProfileCommand'] |
||
3999 | if (isset($fileArray['stripProfile'])) { |
||
4000 | $processingConfiguration['stripProfile'] = $fileArray['stripProfile']; |
||
4001 | } |
||
4002 | // Check if we can handle this type of file for editing |
||
4003 | if ($fileObject->isImage()) { |
||
4004 | $maskArray = $fileArray['m.']; |
||
4005 | // Must render mask images and include in hash-calculating |
||
4006 | // - otherwise we cannot be sure the filename is unique for the setup! |
||
4007 | if (is_array($maskArray)) { |
||
4008 | $mask = $this->getImgResource($maskArray['mask'], $maskArray['mask.']); |
||
4009 | $bgImg = $this->getImgResource($maskArray['bgImg'], $maskArray['bgImg.']); |
||
4010 | $bottomImg = $this->getImgResource($maskArray['bottomImg'], $maskArray['bottomImg.']); |
||
4011 | $bottomImg_mask = $this->getImgResource($maskArray['bottomImg_mask'], $maskArray['bottomImg_mask.']); |
||
4012 | |||
4013 | $processingConfiguration['maskImages']['maskImage'] = $mask['processedFile']; |
||
4014 | $processingConfiguration['maskImages']['backgroundImage'] = $bgImg['processedFile']; |
||
4015 | $processingConfiguration['maskImages']['maskBottomImage'] = $bottomImg['processedFile']; |
||
4016 | $processingConfiguration['maskImages']['maskBottomImageMask'] = $bottomImg_mask['processedFile']; |
||
4017 | } |
||
4018 | $processedFileObject = $fileObject->process(ProcessedFile::CONTEXT_IMAGECROPSCALEMASK, $processingConfiguration); |
||
4019 | if ($processedFileObject->isProcessed()) { |
||
4020 | $imageResource = [ |
||
4021 | 0 => (int)$processedFileObject->getProperty('width'), |
||
4022 | 1 => (int)$processedFileObject->getProperty('height'), |
||
4023 | 2 => $processedFileObject->getExtension(), |
||
4024 | 3 => $processedFileObject->getPublicUrl(), |
||
4025 | 'origFile' => $fileObject->getPublicUrl(), |
||
4026 | 'origFile_mtime' => $fileObject->getModificationTime(), |
||
4027 | // This is needed by \TYPO3\CMS\Frontend\Imaging\GifBuilder, |
||
4028 | // in order for the setup-array to create a unique filename hash. |
||
4029 | 'originalFile' => $fileObject, |
||
4030 | 'processedFile' => $processedFileObject |
||
4031 | ]; |
||
4032 | } |
||
4033 | } |
||
4034 | } |
||
4035 | } |
||
4036 | // If image was processed by GIFBUILDER: |
||
4037 | // ($imageResource indicates that it was processed the regular way) |
||
4038 | if (!isset($imageResource)) { |
||
4039 | try { |
||
4040 | $theImage = GeneralUtility::makeInstance(FilePathSanitizer::class)->sanitize((string)$file); |
||
4041 | $info = GeneralUtility::makeInstance(GifBuilder::class)->imageMagickConvert($theImage, 'WEB'); |
||
4042 | $info['origFile'] = $theImage; |
||
4043 | // This is needed by \TYPO3\CMS\Frontend\Imaging\GifBuilder, ln 100ff in order for the setup-array to create a unique filename hash. |
||
4044 | $info['origFile_mtime'] = @filemtime($theImage); |
||
4045 | $imageResource = $info; |
||
4046 | } catch (Exception $e) { |
||
4047 | // do nothing in case the file path is invalid |
||
4048 | } |
||
4049 | } |
||
4050 | // Hook 'getImgResource': Post-processing of image resources |
||
4051 | if (isset($imageResource)) { |
||
4052 | /** @var ContentObjectGetImageResourceHookInterface $hookObject */ |
||
4053 | foreach ($this->getGetImgResourceHookObjects() as $hookObject) { |
||
4054 | $imageResource = $hookObject->getImgResourcePostProcess($file, (array)$fileArray, $imageResource, $this); |
||
4055 | } |
||
4056 | } |
||
4057 | return $imageResource; |
||
4058 | } |
||
4059 | |||
4060 | /** |
||
4061 | * Returns an ImageManipulation\Area object for the given cropVariant (or 'default') |
||
4062 | * or null if the crop settings or crop area is empty. |
||
4063 | * |
||
4064 | * The cropArea from file reference is used, if not set in TypoScript. |
||
4065 | * |
||
4066 | * Example TypoScript settings: |
||
4067 | * file.crop = |
||
4068 | * OR |
||
4069 | * file.crop = 50,50,100,100 |
||
4070 | * OR |
||
4071 | * file.crop.data = file:current:crop |
||
4072 | * |
||
4073 | * @param FileReference $fileReference |
||
4074 | * @param array $fileArray TypoScript properties for the imgResource type |
||
4075 | * @return Area|null |
||
4076 | */ |
||
4077 | protected function getCropAreaFromFileReference(FileReference $fileReference, array $fileArray) |
||
4078 | { |
||
4079 | // Use cropping area from file reference if nothing is configured in TypoScript. |
||
4080 | if (!isset($fileArray['crop']) && !isset($fileArray['crop.'])) { |
||
4081 | // Set crop variant from TypoScript settings. If not set, use default. |
||
4082 | $cropVariant = $fileArray['cropVariant'] ?? 'default'; |
||
4083 | $fileCropArea = $this->createCropAreaFromJsonString((string)$fileReference->getProperty('crop'), $cropVariant); |
||
4084 | return $fileCropArea->isEmpty() ? null : $fileCropArea->makeAbsoluteBasedOnFile($fileReference); |
||
4085 | } |
||
4086 | |||
4087 | return $this->getCropAreaFromFromTypoScriptSettings($fileReference, $fileArray); |
||
4088 | } |
||
4089 | |||
4090 | /** |
||
4091 | * Returns an ImageManipulation\Area object for the given cropVariant (or 'default') |
||
4092 | * or null if the crop settings or crop area is empty. |
||
4093 | * |
||
4094 | * @param FileInterface $file |
||
4095 | * @param array $fileArray |
||
4096 | * @return Area|null |
||
4097 | */ |
||
4098 | protected function getCropAreaFromFromTypoScriptSettings(FileInterface $file, array $fileArray) |
||
4099 | { |
||
4100 | /** @var Area $cropArea */ |
||
4101 | $cropArea = null; |
||
4102 | // Resolve TypoScript configured cropping. |
||
4103 | $cropSettings = isset($fileArray['crop.']) |
||
4104 | ? $this->stdWrap($fileArray['crop'], $fileArray['crop.']) |
||
4105 | : ($fileArray['crop'] ?? null); |
||
4106 | |||
4107 | if (is_string($cropSettings)) { |
||
4108 | // Set crop variant from TypoScript settings. If not set, use default. |
||
4109 | $cropVariant = $fileArray['cropVariant'] ?? 'default'; |
||
4110 | // Get cropArea from CropVariantCollection, if cropSettings is a valid json. |
||
4111 | // CropVariantCollection::create does json_decode. |
||
4112 | $jsonCropArea = $this->createCropAreaFromJsonString($cropSettings, $cropVariant); |
||
4113 | $cropArea = $jsonCropArea->isEmpty() ? null : $jsonCropArea->makeAbsoluteBasedOnFile($file); |
||
4114 | |||
4115 | // Cropping is configured in TypoScript in the following way: file.crop = 50,50,100,100 |
||
4116 | if ($jsonCropArea->isEmpty() && preg_match('/^[0-9]+,[0-9]+,[0-9]+,[0-9]+$/', $cropSettings)) { |
||
4117 | $cropSettings = explode(',', $cropSettings); |
||
4118 | if (count($cropSettings) === 4) { |
||
4119 | $stringCropArea = GeneralUtility::makeInstance( |
||
4120 | Area::class, |
||
4121 | ...$cropSettings |
||
4122 | ); |
||
4123 | $cropArea = $stringCropArea->isEmpty() ? null : $stringCropArea; |
||
4124 | } |
||
4125 | } |
||
4126 | } |
||
4127 | |||
4128 | return $cropArea; |
||
4129 | } |
||
4130 | |||
4131 | /** |
||
4132 | * Takes a JSON string and creates CropVariantCollection and fetches the corresponding |
||
4133 | * CropArea for that. |
||
4134 | * |
||
4135 | * @param string $cropSettings |
||
4136 | * @param string $cropVariant |
||
4137 | * @return Area |
||
4138 | */ |
||
4139 | protected function createCropAreaFromJsonString(string $cropSettings, string $cropVariant): Area |
||
4140 | { |
||
4141 | return CropVariantCollection::create($cropSettings)->getCropArea($cropVariant); |
||
4142 | } |
||
4143 | |||
4144 | /*********************************************** |
||
4145 | * |
||
4146 | * Data retrieval etc. |
||
4147 | * |
||
4148 | ***********************************************/ |
||
4149 | /** |
||
4150 | * Returns the value for the field from $this->data. If "//" is found in the $field value that token will split the field values apart and the first field having a non-blank value will be returned. |
||
4151 | * |
||
4152 | * @param string $field The fieldname, eg. "title" or "navtitle // title" (in the latter case the value of $this->data[navtitle] is returned if not blank, otherwise $this->data[title] will be) |
||
4153 | * @return string|null |
||
4154 | */ |
||
4155 | public function getFieldVal($field) |
||
4156 | { |
||
4157 | if (strpos($field, '//') === false) { |
||
4158 | return $this->data[trim($field)] ?? null; |
||
4159 | } |
||
4160 | $sections = GeneralUtility::trimExplode('//', $field, true); |
||
4161 | foreach ($sections as $k) { |
||
4162 | if ((string)($this->data[$k] ?? '') !== '') { |
||
4163 | return $this->data[$k]; |
||
4164 | } |
||
4165 | } |
||
4166 | |||
4167 | return ''; |
||
4168 | } |
||
4169 | |||
4170 | /** |
||
4171 | * Implements the TypoScript data type "getText". This takes a string with parameters and based on those a value from somewhere in the system is returned. |
||
4172 | * |
||
4173 | * @param string $string The parameter string, eg. "field : title" or "field : navtitle // field : title" (in the latter case and example of how the value is FIRST splitted by "//" is shown) |
||
4174 | * @param array|null $fieldArray Alternative field array; If you set this to an array this variable will be used to look up values for the "field" key. Otherwise the current page record in $GLOBALS['TSFE']->page is used. |
||
4175 | * @return string The value fetched |
||
4176 | * @see getFieldVal() |
||
4177 | */ |
||
4178 | public function getData($string, $fieldArray = null) |
||
4179 | { |
||
4180 | $tsfe = $this->getTypoScriptFrontendController(); |
||
4181 | if (!is_array($fieldArray)) { |
||
4182 | $fieldArray = $tsfe->page; |
||
4183 | } |
||
4184 | $retVal = ''; |
||
4185 | $sections = explode('//', $string); |
||
4186 | foreach ($sections as $secKey => $secVal) { |
||
4187 | if ($retVal) { |
||
4188 | break; |
||
4189 | } |
||
4190 | $parts = explode(':', $secVal, 2); |
||
4191 | $type = strtolower(trim($parts[0])); |
||
4192 | $typesWithOutParameters = ['level', 'date', 'current', 'pagelayout']; |
||
4193 | $key = trim($parts[1] ?? ''); |
||
4194 | if (($key != '') || in_array($type, $typesWithOutParameters)) { |
||
4195 | switch ($type) { |
||
4196 | case 'gp': |
||
4197 | // Merge GET and POST and get $key out of the merged array |
||
4198 | $getPostArray = GeneralUtility::_GET(); |
||
4199 | ArrayUtility::mergeRecursiveWithOverrule($getPostArray, GeneralUtility::_POST()); |
||
4200 | $retVal = $this->getGlobal($key, $getPostArray); |
||
4201 | break; |
||
4202 | case 'tsfe': |
||
4203 | $retVal = $this->getGlobal('TSFE|' . $key); |
||
4204 | break; |
||
4205 | case 'getenv': |
||
4206 | $retVal = getenv($key); |
||
4207 | break; |
||
4208 | case 'getindpenv': |
||
4209 | $retVal = $this->getEnvironmentVariable($key); |
||
4210 | break; |
||
4211 | case 'field': |
||
4212 | $retVal = $this->getGlobal($key, $fieldArray); |
||
4213 | break; |
||
4214 | case 'file': |
||
4215 | $retVal = $this->getFileDataKey($key); |
||
4216 | break; |
||
4217 | case 'parameters': |
||
4218 | $retVal = $this->parameters[$key] ?? null; |
||
4219 | break; |
||
4220 | case 'register': |
||
4221 | $retVal = $tsfe->register[$key] ?? null; |
||
4222 | break; |
||
4223 | case 'global': |
||
4224 | $retVal = $this->getGlobal($key); |
||
4225 | break; |
||
4226 | case 'level': |
||
4227 | $retVal = count($tsfe->tmpl->rootLine) - 1; |
||
4228 | break; |
||
4229 | case 'leveltitle': |
||
4230 | $keyParts = GeneralUtility::trimExplode(',', $key); |
||
4231 | $pointer = (int)($keyParts[0] ?? 0); |
||
4232 | $slide = (string)($keyParts[1] ?? ''); |
||
4233 | |||
4234 | $numericKey = $this->getKey($pointer, $tsfe->tmpl->rootLine); |
||
4235 | $retVal = $this->rootLineValue($numericKey, 'title', strtolower($slide) === 'slide'); |
||
4236 | break; |
||
4237 | case 'levelmedia': |
||
4238 | $keyParts = GeneralUtility::trimExplode(',', $key); |
||
4239 | $pointer = (int)($keyParts[0] ?? 0); |
||
4240 | $slide = (string)($keyParts[1] ?? ''); |
||
4241 | |||
4242 | $numericKey = $this->getKey($pointer, $tsfe->tmpl->rootLine); |
||
4243 | $retVal = $this->rootLineValue($numericKey, 'media', strtolower($slide) === 'slide'); |
||
4244 | break; |
||
4245 | case 'leveluid': |
||
4246 | $numericKey = $this->getKey((int)$key, $tsfe->tmpl->rootLine); |
||
4247 | $retVal = $this->rootLineValue($numericKey, 'uid'); |
||
4248 | break; |
||
4249 | case 'levelfield': |
||
4250 | $keyParts = GeneralUtility::trimExplode(',', $key); |
||
4251 | $pointer = (int)($keyParts[0] ?? 0); |
||
4252 | $field = (string)($keyParts[1] ?? ''); |
||
4253 | $slide = (string)($keyParts[2] ?? ''); |
||
4254 | |||
4255 | $numericKey = $this->getKey($pointer, $tsfe->tmpl->rootLine); |
||
4256 | $retVal = $this->rootLineValue($numericKey, $field, strtolower($slide) === 'slide'); |
||
4257 | break; |
||
4258 | case 'fullrootline': |
||
4259 | $keyParts = GeneralUtility::trimExplode(',', $key); |
||
4260 | $pointer = (int)($keyParts[0] ?? 0); |
||
4261 | $field = (string)($keyParts[1] ?? ''); |
||
4262 | $slide = (string)($keyParts[2] ?? ''); |
||
4263 | |||
4264 | $fullKey = (int)($pointer - count($tsfe->tmpl->rootLine) + count($tsfe->rootLine)); |
||
4265 | if ($fullKey >= 0) { |
||
4266 | $retVal = $this->rootLineValue($fullKey, $field, stristr($slide, 'slide') !== false, $tsfe->rootLine); |
||
4267 | } |
||
4268 | break; |
||
4269 | case 'date': |
||
4270 | if (!$key) { |
||
4271 | $key = 'd/m Y'; |
||
4272 | } |
||
4273 | $retVal = date($key, $GLOBALS['EXEC_TIME']); |
||
4274 | break; |
||
4275 | case 'page': |
||
4276 | $retVal = $tsfe->page[$key]; |
||
4277 | break; |
||
4278 | case 'pagelayout': |
||
4279 | $retVal = GeneralUtility::makeInstance(PageLayoutResolver::class) |
||
4280 | ->getLayoutForPage($tsfe->page, $tsfe->rootLine); |
||
4281 | break; |
||
4282 | case 'current': |
||
4283 | $retVal = $this->data[$this->currentValKey] ?? null; |
||
4284 | break; |
||
4285 | case 'db': |
||
4286 | $selectParts = GeneralUtility::trimExplode(':', $key); |
||
4287 | $db_rec = $tsfe->sys_page->getRawRecord($selectParts[0], $selectParts[1]); |
||
4288 | if (is_array($db_rec) && $selectParts[2]) { |
||
4289 | $retVal = $db_rec[$selectParts[2]]; |
||
4290 | } |
||
4291 | break; |
||
4292 | case 'lll': |
||
4293 | $retVal = $tsfe->sL('LLL:' . $key); |
||
4294 | break; |
||
4295 | case 'path': |
||
4296 | try { |
||
4297 | $retVal = GeneralUtility::makeInstance(FilePathSanitizer::class)->sanitize($key); |
||
4298 | } catch (Exception $e) { |
||
4299 | // do nothing in case the file path is invalid |
||
4300 | $retVal = null; |
||
4301 | } |
||
4302 | break; |
||
4303 | case 'cobj': |
||
4304 | switch ($key) { |
||
4305 | case 'parentRecordNumber': |
||
4306 | $retVal = $this->parentRecordNumber; |
||
4307 | break; |
||
4308 | } |
||
4309 | break; |
||
4310 | case 'debug': |
||
4311 | switch ($key) { |
||
4312 | case 'rootLine': |
||
4313 | $retVal = DebugUtility::viewArray($tsfe->tmpl->rootLine); |
||
4314 | break; |
||
4315 | case 'fullRootLine': |
||
4316 | $retVal = DebugUtility::viewArray($tsfe->rootLine); |
||
4317 | break; |
||
4318 | case 'data': |
||
4319 | $retVal = DebugUtility::viewArray($this->data); |
||
4320 | break; |
||
4321 | case 'register': |
||
4322 | $retVal = DebugUtility::viewArray($tsfe->register); |
||
4323 | break; |
||
4324 | case 'page': |
||
4325 | $retVal = DebugUtility::viewArray($tsfe->page); |
||
4326 | break; |
||
4327 | } |
||
4328 | break; |
||
4329 | case 'flexform': |
||
4330 | $keyParts = GeneralUtility::trimExplode(':', $key, true); |
||
4331 | if (count($keyParts) === 2 && isset($this->data[$keyParts[0]])) { |
||
4332 | $flexFormContent = $this->data[$keyParts[0]]; |
||
4333 | if (!empty($flexFormContent)) { |
||
4334 | $flexFormService = GeneralUtility::makeInstance(FlexFormService::class); |
||
4335 | $flexFormKey = str_replace('.', '|', $keyParts[1]); |
||
4336 | $settings = $flexFormService->convertFlexFormContentToArray($flexFormContent); |
||
4337 | $retVal = $this->getGlobal($flexFormKey, $settings); |
||
4338 | } |
||
4339 | } |
||
4340 | break; |
||
4341 | case 'session': |
||
4342 | $keyParts = GeneralUtility::trimExplode('|', $key, true); |
||
4343 | $sessionKey = array_shift($keyParts); |
||
4344 | $retVal = $this->getTypoScriptFrontendController()->fe_user->getSessionData($sessionKey); |
||
4345 | foreach ($keyParts as $keyPart) { |
||
4346 | if (is_object($retVal)) { |
||
4347 | $retVal = $retVal->{$keyPart}; |
||
4348 | } elseif (is_array($retVal)) { |
||
4349 | $retVal = $retVal[$keyPart]; |
||
4350 | } else { |
||
4351 | $retVal = ''; |
||
4352 | break; |
||
4353 | } |
||
4354 | } |
||
4355 | if (!is_scalar($retVal)) { |
||
4356 | $retVal = ''; |
||
4357 | } |
||
4358 | break; |
||
4359 | case 'context': |
||
4360 | $context = GeneralUtility::makeInstance(Context::class); |
||
4361 | [$aspectName, $propertyName] = GeneralUtility::trimExplode(':', $key, true, 2); |
||
4362 | $retVal = $context->getPropertyFromAspect($aspectName, $propertyName, ''); |
||
4363 | if (is_array($retVal)) { |
||
4364 | $retVal = implode(',', $retVal); |
||
4365 | } |
||
4366 | if (!is_scalar($retVal)) { |
||
4367 | $retVal = ''; |
||
4368 | } |
||
4369 | break; |
||
4370 | case 'site': |
||
4371 | $site = $this->getTypoScriptFrontendController()->getSite(); |
||
4372 | if ($key === 'identifier') { |
||
4373 | $retVal = $site->getIdentifier(); |
||
4374 | } elseif ($key === 'base') { |
||
4375 | $retVal = $site->getBase(); |
||
4376 | } else { |
||
4377 | try { |
||
4378 | $retVal = ArrayUtility::getValueByPath($site->getConfiguration(), $key, '.'); |
||
4379 | } catch (MissingArrayPathException $exception) { |
||
4380 | $this->logger->warning('getData() with "{key}" failed', ['key' => $key, 'exception' => $exception]); |
||
4381 | } |
||
4382 | } |
||
4383 | break; |
||
4384 | case 'sitelanguage': |
||
4385 | $siteLanguage = $this->getTypoScriptFrontendController()->getLanguage(); |
||
4386 | $config = $siteLanguage->toArray(); |
||
4387 | if (isset($config[$key])) { |
||
4388 | $retVal = $config[$key]; |
||
4389 | } |
||
4390 | break; |
||
4391 | } |
||
4392 | } |
||
4393 | |||
4394 | foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getData'] ?? [] as $className) { |
||
4395 | $hookObject = GeneralUtility::makeInstance($className); |
||
4396 | if (!$hookObject instanceof ContentObjectGetDataHookInterface) { |
||
4397 | throw new \UnexpectedValueException('$hookObject must implement interface ' . ContentObjectGetDataHookInterface::class, 1195044480); |
||
4398 | } |
||
4399 | $ref = $this; // introduced for phpstan to not lose type information when passing $this into callUserFunction |
||
4400 | $retVal = $hookObject->getDataExtension($string, $fieldArray, $secVal, $retVal, $ref); |
||
4401 | } |
||
4402 | } |
||
4403 | return $retVal; |
||
4404 | } |
||
4405 | |||
4406 | /** |
||
4407 | * Gets file information. This is a helper function for the getData() method above, which resolves e.g. |
||
4408 | * page.10.data = file:current:title |
||
4409 | * or |
||
4410 | * page.10.data = file:17:title |
||
4411 | * |
||
4412 | * @param string $key A colon-separated key, e.g. 17:name or current:sha1, with the first part being a sys_file uid or the keyword "current" and the second part being the key of information to get from file (e.g. "title", "size", "description", etc.) |
||
4413 | * @return string|int The value as retrieved from the file object. |
||
4414 | */ |
||
4415 | protected function getFileDataKey($key) |
||
4416 | { |
||
4417 | [$fileUidOrCurrentKeyword, $requestedFileInformationKey] = GeneralUtility::trimExplode(':', $key, false, 3); |
||
4418 | try { |
||
4419 | if ($fileUidOrCurrentKeyword === 'current') { |
||
4420 | $fileObject = $this->getCurrentFile(); |
||
4421 | } elseif (MathUtility::canBeInterpretedAsInteger($fileUidOrCurrentKeyword)) { |
||
4422 | /** @var ResourceFactory $fileFactory */ |
||
4423 | $fileFactory = GeneralUtility::makeInstance(ResourceFactory::class); |
||
4424 | $fileObject = $fileFactory->getFileObject($fileUidOrCurrentKeyword); |
||
4425 | } else { |
||
4426 | $fileObject = null; |
||
4427 | } |
||
4428 | } catch (Exception $exception) { |
||
4429 | $this->logger->warning('The file "{uid}" could not be found and won\'t be included in frontend output', ['uid' => $fileUidOrCurrentKeyword, 'exception' => $exception]); |
||
4430 | $fileObject = null; |
||
4431 | } |
||
4432 | |||
4433 | if ($fileObject instanceof FileInterface) { |
||
4434 | // All properties of the \TYPO3\CMS\Core\Resource\FileInterface are available here: |
||
4435 | switch ($requestedFileInformationKey) { |
||
4436 | case 'name': |
||
4437 | return $fileObject->getName(); |
||
4438 | case 'uid': |
||
4439 | if (method_exists($fileObject, 'getUid')) { |
||
4440 | return $fileObject->getUid(); |
||
4441 | } |
||
4442 | return 0; |
||
4443 | case 'originalUid': |
||
4444 | if ($fileObject instanceof FileReference) { |
||
4445 | return $fileObject->getOriginalFile()->getUid(); |
||
4446 | } |
||
4447 | return null; |
||
4448 | case 'size': |
||
4449 | return $fileObject->getSize(); |
||
4450 | case 'sha1': |
||
4451 | return $fileObject->getSha1(); |
||
4452 | case 'extension': |
||
4453 | return $fileObject->getExtension(); |
||
4454 | case 'mimetype': |
||
4455 | return $fileObject->getMimeType(); |
||
4456 | case 'contents': |
||
4457 | return $fileObject->getContents(); |
||
4458 | case 'publicUrl': |
||
4459 | return $fileObject->getPublicUrl(); |
||
4460 | default: |
||
4461 | // Generic alternative here |
||
4462 | return $fileObject->getProperty($requestedFileInformationKey); |
||
4463 | } |
||
4464 | } else { |
||
4465 | // @todo fail silently as is common in tslib_content |
||
4466 | return 'Error: no file object'; |
||
4467 | } |
||
4468 | } |
||
4469 | |||
4470 | /** |
||
4471 | * Returns a value from the current rootline (site) from $GLOBALS['TSFE']->tmpl->rootLine; |
||
4472 | * |
||
4473 | * @param int $key Which level in the root line |
||
4474 | * @param string $field The field in the rootline record to return (a field from the pages table) |
||
4475 | * @param bool $slideBack If set, then we will traverse through the rootline from outer level towards the root level until the value found is TRUE |
||
4476 | * @param mixed $altRootLine If you supply an array for this it will be used as an alternative root line array |
||
4477 | * @return string The value from the field of the rootline. |
||
4478 | * @internal |
||
4479 | * @see getData() |
||
4480 | */ |
||
4481 | public function rootLineValue($key, $field, $slideBack = false, $altRootLine = '') |
||
4482 | { |
||
4483 | $rootLine = is_array($altRootLine) ? $altRootLine : $this->getTypoScriptFrontendController()->tmpl->rootLine; |
||
4484 | if (!$slideBack) { |
||
4485 | return $rootLine[$key][$field]; |
||
4486 | } |
||
4487 | for ($a = $key; $a >= 0; $a--) { |
||
4488 | $val = $rootLine[$a][$field]; |
||
4489 | if ($val) { |
||
4490 | return $val; |
||
4491 | } |
||
4492 | } |
||
4493 | |||
4494 | return ''; |
||
4495 | } |
||
4496 | |||
4497 | /** |
||
4498 | * Return global variable where the input string $var defines array keys separated by "|" |
||
4499 | * Example: $var = "HTTP_SERVER_VARS | something" will return the value $GLOBALS['HTTP_SERVER_VARS']['something'] value |
||
4500 | * |
||
4501 | * @param string $keyString Global var key, eg. "HTTP_GET_VAR" or "HTTP_GET_VARS|id" to get the GET parameter "id" back. |
||
4502 | * @param array $source Alternative array than $GLOBAL to get variables from. |
||
4503 | * @return mixed Whatever value. If none, then blank string. |
||
4504 | * @see getData() |
||
4505 | */ |
||
4506 | public function getGlobal($keyString, $source = null) |
||
4507 | { |
||
4508 | $keys = explode('|', $keyString); |
||
4509 | $numberOfLevels = count($keys); |
||
4510 | $rootKey = trim($keys[0]); |
||
4511 | $value = isset($source) ? ($source[$rootKey] ?? '') : ($GLOBALS[$rootKey] ?? ''); |
||
4512 | for ($i = 1; $i < $numberOfLevels && isset($value); $i++) { |
||
4513 | $currentKey = trim($keys[$i]); |
||
4514 | if (is_object($value)) { |
||
4515 | $value = $value->{$currentKey}; |
||
4516 | } elseif (is_array($value)) { |
||
4517 | $value = $value[$currentKey] ?? ''; |
||
4518 | } else { |
||
4519 | $value = ''; |
||
4520 | break; |
||
4521 | } |
||
4522 | } |
||
4523 | if (!is_scalar($value)) { |
||
4524 | $value = ''; |
||
4525 | } |
||
4526 | return $value; |
||
4527 | } |
||
4528 | |||
4529 | /** |
||
4530 | * Processing of key values pointing to entries in $arr; Here negative values are converted to positive keys pointer to an entry in the array but from behind (based on the negative value). |
||
4531 | * Example: entrylevel = -1 means that entryLevel ends up pointing at the outermost-level, -2 means the level before the outermost... |
||
4532 | * |
||
4533 | * @param int $key The integer to transform |
||
4534 | * @param array $arr array in which the key should be found. |
||
4535 | * @return int The processed integer key value. |
||
4536 | * @internal |
||
4537 | * @see getData() |
||
4538 | */ |
||
4539 | public function getKey($key, $arr) |
||
4540 | { |
||
4541 | $key = (int)$key; |
||
4542 | if (is_array($arr)) { |
||
4543 | if ($key < 0) { |
||
4544 | $key = count($arr) + $key; |
||
4545 | } |
||
4546 | if ($key < 0) { |
||
4547 | $key = 0; |
||
4548 | } |
||
4549 | } |
||
4550 | return $key; |
||
4551 | } |
||
4552 | |||
4553 | /*********************************************** |
||
4554 | * |
||
4555 | * Link functions (typolink) |
||
4556 | * |
||
4557 | ***********************************************/ |
||
4558 | /** |
||
4559 | * called from the typoLink() function |
||
4560 | * |
||
4561 | * does the magic to split the full "typolink" string like "15,13 _blank myclass &more=1" |
||
4562 | * into separate parts |
||
4563 | * |
||
4564 | * @param string $linkText The string (text) to link |
||
4565 | * @param string $mixedLinkParameter destination data like "15,13 _blank myclass &more=1" used to create the link |
||
4566 | * @param array $configuration TypoScript configuration |
||
4567 | * @return array|string |
||
4568 | * @see typoLink() |
||
4569 | * |
||
4570 | * @todo the functionality of the "file:" syntax + the hook should be marked as deprecated, an upgrade wizard should handle existing links |
||
4571 | */ |
||
4572 | protected function resolveMixedLinkParameter($linkText, $mixedLinkParameter, &$configuration = []) |
||
4573 | { |
||
4574 | // Link parameter value = first part |
||
4575 | $linkParameterParts = GeneralUtility::makeInstance(TypoLinkCodecService::class)->decode($mixedLinkParameter); |
||
4576 | |||
4577 | // Check for link-handler keyword |
||
4578 | $linkHandlerExploded = explode(':', $linkParameterParts['url'], 2); |
||
4579 | $linkHandlerKeyword = (string)($linkHandlerExploded[0] ?? ''); |
||
4580 | |||
4581 | if (in_array(strtolower((string)preg_replace('#\s|[[:cntrl:]]#', '', $linkHandlerKeyword)), ['javascript', 'data'], true)) { |
||
4582 | // Disallow insecure scheme's like javascript: or data: |
||
4583 | return $linkText; |
||
4584 | } |
||
4585 | |||
4586 | // additional parameters that need to be set |
||
4587 | if ($linkParameterParts['additionalParams'] !== '') { |
||
4588 | $forceParams = $linkParameterParts['additionalParams']; |
||
4589 | // params value |
||
4590 | $configuration['additionalParams'] .= $forceParams[0] === '&' ? $forceParams : '&' . $forceParams; |
||
4591 | } |
||
4592 | |||
4593 | return [ |
||
4594 | 'href' => $linkParameterParts['url'], |
||
4595 | 'target' => $linkParameterParts['target'], |
||
4596 | 'class' => $linkParameterParts['class'], |
||
4597 | 'title' => $linkParameterParts['title'] |
||
4598 | ]; |
||
4599 | } |
||
4600 | |||
4601 | /** |
||
4602 | * Implements the "typolink" property of stdWrap (and others) |
||
4603 | * Basically the input string, $linktext, is (typically) wrapped in a <a>-tag linking to some page, email address, file or URL based on a parameter defined by the configuration array $conf. |
||
4604 | * This function is best used from internal functions as is. There are some API functions defined after this function which is more suited for general usage in external applications. |
||
4605 | * Generally the concept "typolink" should be used in your own applications as an API for making links to pages with parameters and more. The reason for this is that you will then automatically make links compatible with all the centralized functions for URL simulation and manipulation of parameters into hashes and more. |
||
4606 | * For many more details on the parameters and how they are interpreted, please see the link to TSref below. |
||
4607 | * |
||
4608 | * the FAL API is handled with the namespace/prefix "file:..." |
||
4609 | * |
||
4610 | * @param string $linkText The string (text) to link |
||
4611 | * @param array $conf TypoScript configuration (see link below) |
||
4612 | * @return string A link-wrapped string. |
||
4613 | * @see stdWrap() |
||
4614 | * @see \TYPO3\CMS\Frontend\Plugin\AbstractPlugin::pi_linkTP() |
||
4615 | */ |
||
4616 | public function typoLink($linkText, $conf) |
||
4617 | { |
||
4618 | $linkText = (string)$linkText; |
||
4619 | $tsfe = $this->getTypoScriptFrontendController(); |
||
4620 | |||
4621 | $linkParameter = trim((string)$this->stdWrapValue('parameter', $conf ?? [])); |
||
4622 | $this->lastTypoLinkUrl = ''; |
||
4623 | $this->lastTypoLinkTarget = ''; |
||
4624 | |||
4625 | $resolvedLinkParameters = $this->resolveMixedLinkParameter($linkText, $linkParameter, $conf); |
||
4626 | // check if the link handler hook has resolved the link completely already |
||
4627 | if (!is_array($resolvedLinkParameters)) { |
||
4628 | return $resolvedLinkParameters; |
||
4629 | } |
||
4630 | $linkParameter = $resolvedLinkParameters['href']; |
||
4631 | $target = $resolvedLinkParameters['target']; |
||
4632 | $title = $resolvedLinkParameters['title']; |
||
4633 | |||
4634 | if (!$linkParameter) { |
||
4635 | return $this->resolveAnchorLink($linkText, $conf ?? []); |
||
4636 | } |
||
4637 | |||
4638 | // Detecting kind of link and resolve all necessary parameters |
||
4639 | $linkService = GeneralUtility::makeInstance(LinkService::class); |
||
4640 | try { |
||
4641 | $linkDetails = $linkService->resolve($linkParameter); |
||
4642 | } catch (UnknownLinkHandlerException | InvalidPathException $exception) { |
||
4643 | $this->logger->warning('The link could not be generated', ['exception' => $exception]); |
||
4644 | return $linkText; |
||
4645 | } |
||
4646 | |||
4647 | $linkDetails['typoLinkParameter'] = $linkParameter; |
||
4648 | if (isset($linkDetails['type']) && isset($GLOBALS['TYPO3_CONF_VARS']['FE']['typolinkBuilder'][$linkDetails['type']])) { |
||
4649 | /** @var AbstractTypolinkBuilder $linkBuilder */ |
||
4650 | $linkBuilder = GeneralUtility::makeInstance( |
||
4651 | $GLOBALS['TYPO3_CONF_VARS']['FE']['typolinkBuilder'][$linkDetails['type']], |
||
4652 | $this, |
||
4653 | $tsfe |
||
4654 | ); |
||
4655 | try { |
||
4656 | [$this->lastTypoLinkUrl, $linkText, $target] = $linkBuilder->build($linkDetails, $linkText, $target, $conf); |
||
4657 | $this->lastTypoLinkTarget = htmlspecialchars($target); |
||
4658 | $this->lastTypoLinkLD['target'] = htmlspecialchars($target); |
||
4659 | $this->lastTypoLinkLD['totalUrl'] = $this->lastTypoLinkUrl; |
||
4660 | } catch (UnableToLinkException $e) { |
||
4661 | $this->logger->debug('Unable to link "{text}"', [ |
||
4662 | 'text' => $e->getLinkText(), |
||
4663 | 'exception' => $e, |
||
4664 | ]); |
||
4665 | |||
4666 | // Only return the link text directly |
||
4667 | return $e->getLinkText(); |
||
4668 | } |
||
4669 | } elseif (isset($linkDetails['url'])) { |
||
4670 | $this->lastTypoLinkUrl = $linkDetails['url']; |
||
4671 | $this->lastTypoLinkTarget = htmlspecialchars($target); |
||
4672 | $this->lastTypoLinkLD['target'] = htmlspecialchars($target); |
||
4673 | $this->lastTypoLinkLD['totalUrl'] = $this->lastTypoLinkUrl; |
||
4674 | } else { |
||
4675 | return $linkText; |
||
4676 | } |
||
4677 | |||
4678 | // We need to backup the URL because ATagParams might call typolink again and change the last URL. |
||
4679 | $url = $this->lastTypoLinkUrl; |
||
4680 | $finalTagParts = [ |
||
4681 | 'aTagParams' => $this->getATagParams($conf), |
||
4682 | 'url' => $url, |
||
4683 | 'TYPE' => $linkDetails['type'] |
||
4684 | ]; |
||
4685 | |||
4686 | // Ensure "href" is not in the list of aTagParams to avoid double tags, usually happens within buggy parseFunc settings |
||
4687 | if (!empty($finalTagParts['aTagParams'])) { |
||
4688 | $aTagParams = GeneralUtility::get_tag_attributes($finalTagParts['aTagParams'], true); |
||
4689 | if (isset($aTagParams['href'])) { |
||
4690 | unset($aTagParams['href']); |
||
4691 | $finalTagParts['aTagParams'] = GeneralUtility::implodeAttributes($aTagParams, true); |
||
4692 | } |
||
4693 | } |
||
4694 | |||
4695 | // Building the final <a href=".."> tag |
||
4696 | $tagAttributes = []; |
||
4697 | |||
4698 | // Title attribute |
||
4699 | if (empty($title)) { |
||
4700 | $title = $conf['title'] ?? ''; |
||
4701 | if (isset($conf['title.']) && is_array($conf['title.'])) { |
||
4702 | $title = $this->stdWrap($title, $conf['title.']); |
||
4703 | } |
||
4704 | } |
||
4705 | |||
4706 | // Check, if the target is coded as a JS open window link: |
||
4707 | $JSwindowParts = []; |
||
4708 | $JSwindowParams = ''; |
||
4709 | if ($target && preg_match('/^([0-9]+)x([0-9]+)(:(.*)|.*)$/', $target, $JSwindowParts)) { |
||
4710 | // Take all pre-configured and inserted parameters and compile parameter list, including width+height: |
||
4711 | $JSwindow_tempParamsArr = GeneralUtility::trimExplode(',', strtolower(($conf['JSwindow_params'] ?? '') . ',' . ($JSwindowParts[4] ?? '')), true); |
||
4712 | $JSwindow_paramsArr = []; |
||
4713 | $target = $conf['target'] ?? 'FEopenLink'; |
||
4714 | foreach ($JSwindow_tempParamsArr as $JSv) { |
||
4715 | [$JSp, $JSv] = explode('=', $JSv, 2); |
||
4716 | // If the target is set as JS param, this is extracted |
||
4717 | if ($JSp === 'target') { |
||
4718 | $target = $JSv; |
||
4719 | } else { |
||
4720 | $JSwindow_paramsArr[$JSp] = $JSp . '=' . $JSv; |
||
4721 | } |
||
4722 | } |
||
4723 | // Add width/height: |
||
4724 | $JSwindow_paramsArr['width'] = 'width=' . $JSwindowParts[1]; |
||
4725 | $JSwindow_paramsArr['height'] = 'height=' . $JSwindowParts[2]; |
||
4726 | // Imploding into string: |
||
4727 | $JSwindowParams = implode(',', $JSwindow_paramsArr); |
||
4728 | } |
||
4729 | |||
4730 | if (!$JSwindowParams && $linkDetails['type'] === LinkService::TYPE_EMAIL && $tsfe->spamProtectEmailAddresses === 'ascii') { |
||
4731 | $tagAttributes['href'] = $finalTagParts['url']; |
||
4732 | } else { |
||
4733 | $tagAttributes['href'] = htmlspecialchars($finalTagParts['url']); |
||
4734 | } |
||
4735 | if (!empty($title)) { |
||
4736 | $tagAttributes['title'] = htmlspecialchars($title); |
||
4737 | } |
||
4738 | |||
4739 | // Target attribute |
||
4740 | if (!empty($target)) { |
||
4741 | $tagAttributes['target'] = htmlspecialchars($target); |
||
4742 | } |
||
4743 | if ($JSwindowParams && in_array($tsfe->xhtmlDoctype, ['xhtml_strict', 'xhtml_11'], true)) { |
||
4744 | // Create TARGET-attribute only if the right doctype is used |
||
4745 | unset($tagAttributes['target']); |
||
4746 | } |
||
4747 | |||
4748 | if ($JSwindowParams) { |
||
4749 | $onClick = 'vHWin=window.open(' . GeneralUtility::quoteJSvalue($tsfe->baseUrlWrap($finalTagParts['url'])) |
||
4750 | . ',' . GeneralUtility::quoteJSvalue($target) . ',' |
||
4751 | . GeneralUtility::quoteJSvalue($JSwindowParams) |
||
4752 | . ');vHWin.focus();return false;'; |
||
4753 | $tagAttributes['onclick'] = htmlspecialchars($onClick); |
||
4754 | } |
||
4755 | |||
4756 | if (!empty($resolvedLinkParameters['class'])) { |
||
4757 | $tagAttributes['class'] = htmlspecialchars($resolvedLinkParameters['class']); |
||
4758 | } |
||
4759 | |||
4760 | // Prevent trouble with double and missing spaces between attributes and merge params before implode |
||
4761 | // (skip decoding HTML entities, since `$tagAttributes` are expected to be encoded already) |
||
4762 | $finalTagAttributes = array_merge($tagAttributes, GeneralUtility::get_tag_attributes($finalTagParts['aTagParams'])); |
||
4763 | $finalTagAttributes = $this->addSecurityRelValues($finalTagAttributes, $target, $tagAttributes['href']); |
||
4764 | $finalAnchorTag = '<a ' . GeneralUtility::implodeAttributes($finalTagAttributes) . '>'; |
||
4765 | |||
4766 | // kept for backwards-compatibility in hooks |
||
4767 | $finalTagParts['targetParams'] = !empty($tagAttributes['target']) ? ' target="' . $tagAttributes['target'] . '"' : ''; |
||
4768 | $this->lastTypoLinkTarget = $target; |
||
4769 | |||
4770 | // Call user function: |
||
4771 | if ($conf['userFunc'] ?? false) { |
||
4772 | $finalTagParts['TAG'] = $finalAnchorTag; |
||
4773 | $finalAnchorTag = $this->callUserFunction($conf['userFunc'], $conf['userFunc.'] ?? [], $finalTagParts); |
||
4774 | } |
||
4775 | |||
4776 | // Hook: Call post processing function for link rendering: |
||
4777 | $_params = [ |
||
4778 | 'conf' => &$conf, |
||
4779 | 'linktxt' => &$linkText, |
||
4780 | 'finalTag' => &$finalAnchorTag, |
||
4781 | 'finalTagParts' => &$finalTagParts, |
||
4782 | 'linkDetails' => &$linkDetails, |
||
4783 | 'tagAttributes' => &$finalTagAttributes |
||
4784 | ]; |
||
4785 | foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['typoLink_PostProc'] ?? [] as $_funcRef) { |
||
4786 | $ref = $this; // introduced for phpstan to not lose type information when passing $this into callUserFunction |
||
4787 | GeneralUtility::callUserFunction($_funcRef, $_params, $ref); |
||
4788 | } |
||
4789 | |||
4790 | // If flag "returnLastTypoLinkUrl" set, then just return the latest URL made: |
||
4791 | if ($conf['returnLast'] ?? false) { |
||
4792 | switch ($conf['returnLast']) { |
||
4793 | case 'url': |
||
4794 | return $this->lastTypoLinkUrl; |
||
4795 | case 'target': |
||
4796 | return $this->lastTypoLinkTarget; |
||
4797 | } |
||
4798 | } |
||
4799 | |||
4800 | $wrap = (string)$this->stdWrapValue('wrap', $conf ?? []); |
||
4801 | |||
4802 | if ($conf['ATagBeforeWrap'] ?? false) { |
||
4803 | return $finalAnchorTag . $this->wrap($linkText, $wrap) . '</a>'; |
||
4804 | } |
||
4805 | return $this->wrap($finalAnchorTag . $linkText . '</a>', $wrap); |
||
4806 | } |
||
4807 | |||
4808 | protected function addSecurityRelValues(array $tagAttributes, ?string $target, string $url): array |
||
4809 | { |
||
4810 | $relAttribute = 'noreferrer'; |
||
4811 | if (in_array($target, ['', null, '_self', '_parent', '_top'], true) || $this->isInternalUrl($url)) { |
||
4812 | return $tagAttributes; |
||
4813 | } |
||
4814 | |||
4815 | if (!isset($tagAttributes['rel'])) { |
||
4816 | $tagAttributes['rel'] = $relAttribute; |
||
4817 | return $tagAttributes; |
||
4818 | } |
||
4819 | |||
4820 | $tagAttributes['rel'] = implode(' ', array_unique(array_merge( |
||
4821 | GeneralUtility::trimExplode(' ', $relAttribute), |
||
4822 | GeneralUtility::trimExplode(' ', $tagAttributes['rel']) |
||
4823 | ))); |
||
4824 | |||
4825 | return $tagAttributes; |
||
4826 | } |
||
4827 | |||
4828 | /** |
||
4829 | * Checks whether the given url is an internal url. |
||
4830 | * |
||
4831 | * It will check the host part only, against all configured sites |
||
4832 | * whether the given host is any. If so, the url is considered internal |
||
4833 | * |
||
4834 | * @param string $url The url to check. |
||
4835 | * @return bool |
||
4836 | * @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException |
||
4837 | */ |
||
4838 | protected function isInternalUrl(string $url): bool |
||
4839 | { |
||
4840 | $cache = GeneralUtility::makeInstance(CacheManager::class)->getCache('runtime'); |
||
4841 | $parsedUrl = parse_url($url); |
||
4842 | $foundDomains = 0; |
||
4843 | if (!isset($parsedUrl['host'])) { |
||
4844 | return true; |
||
4845 | } |
||
4846 | |||
4847 | $cacheIdentifier = sha1('isInternalDomain' . $parsedUrl['host']); |
||
4848 | |||
4849 | if ($cache->has($cacheIdentifier) === false) { |
||
4850 | foreach (GeneralUtility::makeInstance(SiteFinder::class)->getAllSites() as $site) { |
||
4851 | if ($site->getBase()->getHost() === $parsedUrl['host']) { |
||
4852 | ++$foundDomains; |
||
4853 | break; |
||
4854 | } |
||
4855 | |||
4856 | if ($site->getBase()->getHost() === '' && GeneralUtility::isOnCurrentHost($url)) { |
||
4857 | ++$foundDomains; |
||
4858 | break; |
||
4859 | } |
||
4860 | } |
||
4861 | |||
4862 | $cache->set($cacheIdentifier, $foundDomains > 0); |
||
4863 | } |
||
4864 | |||
4865 | return (bool)$cache->get($cacheIdentifier); |
||
4866 | } |
||
4867 | |||
4868 | /** |
||
4869 | * Based on the input "TypoLink" TypoScript configuration this will return the generated URL |
||
4870 | * |
||
4871 | * @param array $conf TypoScript properties for "typolink |
||
4872 | * @return string The URL of the link-tag that typolink() would by itself return |
||
4873 | * @see typoLink() |
||
4874 | */ |
||
4875 | public function typoLink_URL($conf) |
||
4876 | { |
||
4877 | $this->typoLink('|', $conf); |
||
4878 | return $this->lastTypoLinkUrl; |
||
4879 | } |
||
4880 | |||
4881 | /** |
||
4882 | * Returns a linked string made from typoLink parameters. |
||
4883 | * |
||
4884 | * This function takes $label as a string, wraps it in a link-tag based on the $params string, which should contain data like that you would normally pass to the popular <LINK>-tag in the TSFE. |
||
4885 | * Optionally you can supply $urlParameters which is an array with key/value pairs that are rawurlencoded and appended to the resulting url. |
||
4886 | * |
||
4887 | * @param string $label Text string being wrapped by the link. |
||
4888 | * @param string $params Link parameter; eg. "123" for page id, "[email protected]" for email address, "http://...." for URL, "fileadmin/example.txt" for file. |
||
4889 | * @param array|string $urlParameters As an array key/value pairs represent URL parameters to set. Values NOT URL-encoded yet, keys should be URL-encoded if needed. As a string the parameter is expected to be URL-encoded already. |
||
4890 | * @param string $target Specific target set, if any. (Default is using the current) |
||
4891 | * @return string The wrapped $label-text string |
||
4892 | * @see getTypoLink_URL() |
||
4893 | */ |
||
4894 | public function getTypoLink($label, $params, $urlParameters = [], $target = '') |
||
4895 | { |
||
4896 | $conf = []; |
||
4897 | $conf['parameter'] = $params; |
||
4898 | if ($target) { |
||
4899 | $conf['target'] = $target; |
||
4900 | $conf['extTarget'] = $target; |
||
4901 | $conf['fileTarget'] = $target; |
||
4902 | } |
||
4903 | if (is_array($urlParameters)) { |
||
4904 | if (!empty($urlParameters)) { |
||
4905 | $conf['additionalParams'] .= HttpUtility::buildQueryString($urlParameters, '&'); |
||
4906 | } |
||
4907 | } else { |
||
4908 | $conf['additionalParams'] .= $urlParameters; |
||
4909 | } |
||
4910 | $out = $this->typoLink($label, $conf); |
||
4911 | return $out; |
||
4912 | } |
||
4913 | |||
4914 | /** |
||
4915 | * Returns the canonical URL to the current "location", which include the current page ID and type |
||
4916 | * and optionally the query string |
||
4917 | * |
||
4918 | * @param bool $addQueryString Whether additional GET arguments in the query string should be included or not |
||
4919 | * @return string |
||
4920 | */ |
||
4921 | public function getUrlToCurrentLocation($addQueryString = true) |
||
4922 | { |
||
4923 | $conf = []; |
||
4924 | $conf['parameter'] = $this->getTypoScriptFrontendController()->id . ',' . $this->getTypoScriptFrontendController()->type; |
||
4925 | if ($addQueryString) { |
||
4926 | $conf['addQueryString'] = '1'; |
||
4927 | $linkVars = implode(',', array_keys(GeneralUtility::explodeUrl2Array($this->getTypoScriptFrontendController()->linkVars))); |
||
4928 | $conf['addQueryString.'] = [ |
||
4929 | 'exclude' => 'id,type,cHash' . ($linkVars ? ',' . $linkVars : '') |
||
4930 | ]; |
||
4931 | } |
||
4932 | |||
4933 | return $this->typoLink_URL($conf); |
||
4934 | } |
||
4935 | |||
4936 | /** |
||
4937 | * Returns the URL of a "typolink" create from the input parameter string, url-parameters and target |
||
4938 | * |
||
4939 | * @param string $params Link parameter; eg. "123" for page id, "[email protected]" for email address, "http://...." for URL, "fileadmin/example.txt" for file. |
||
4940 | * @param array|string $urlParameters As an array key/value pairs represent URL parameters to set. Values NOT URL-encoded yet, keys should be URL-encoded if needed. As a string the parameter is expected to be URL-encoded already. |
||
4941 | * @param string $target Specific target set, if any. (Default is using the current) |
||
4942 | * @return string The URL |
||
4943 | * @see getTypoLink() |
||
4944 | */ |
||
4945 | public function getTypoLink_URL($params, $urlParameters = [], $target = '') |
||
4946 | { |
||
4947 | $this->getTypoLink('', $params, $urlParameters, $target); |
||
4948 | return $this->lastTypoLinkUrl; |
||
4949 | } |
||
4950 | |||
4951 | /** |
||
4952 | * Loops over all configured URL modifier hooks (if available) and returns the generated URL or NULL if no URL was generated. |
||
4953 | * |
||
4954 | * @param string $context The context in which the method is called (e.g. typoLink). |
||
4955 | * @param string $url The URL that should be processed. |
||
4956 | * @param array $typolinkConfiguration The current link configuration array. |
||
4957 | * @return string|null Returns NULL if URL was not processed or the processed URL as a string. |
||
4958 | * @throws \RuntimeException if a hook was registered but did not fulfill the correct parameters. |
||
4959 | */ |
||
4960 | protected function processUrl($context, $url, $typolinkConfiguration = []) |
||
4961 | { |
||
4962 | $urlProcessors = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['urlProcessing']['urlProcessors'] ?? []; |
||
4963 | if (empty($urlProcessors)) { |
||
4964 | return $url; |
||
4965 | } |
||
4966 | |||
4967 | foreach ($urlProcessors as $identifier => $configuration) { |
||
4968 | if (empty($configuration) || !is_array($configuration)) { |
||
4969 | throw new \RuntimeException('Missing configuration for URI processor "' . $identifier . '".', 1442050529); |
||
4970 | } |
||
4971 | if (!is_string($configuration['processor']) || empty($configuration['processor']) || !class_exists($configuration['processor']) || !is_subclass_of($configuration['processor'], UrlProcessorInterface::class)) { |
||
4972 | throw new \RuntimeException('The URI processor "' . $identifier . '" defines an invalid provider. Ensure the class exists and implements the "' . UrlProcessorInterface::class . '".', 1442050579); |
||
4973 | } |
||
4974 | } |
||
4975 | |||
4976 | $orderedProcessors = GeneralUtility::makeInstance(DependencyOrderingService::class)->orderByDependencies($urlProcessors); |
||
4977 | $keepProcessing = true; |
||
4978 | |||
4979 | foreach ($orderedProcessors as $configuration) { |
||
4980 | /** @var UrlProcessorInterface $urlProcessor */ |
||
4981 | $urlProcessor = GeneralUtility::makeInstance($configuration['processor']); |
||
4982 | $url = $urlProcessor->process($context, $url, $typolinkConfiguration, $this, $keepProcessing); |
||
4983 | if (!$keepProcessing) { |
||
4984 | break; |
||
4985 | } |
||
4986 | } |
||
4987 | |||
4988 | return $url; |
||
4989 | } |
||
4990 | |||
4991 | /** |
||
4992 | * Creates a href attibute for given $mailAddress. |
||
4993 | * The function uses spamProtectEmailAddresses for encoding the mailto statement. |
||
4994 | * If spamProtectEmailAddresses is disabled, it'll just return a string like "mailto:[email protected]". |
||
4995 | * |
||
4996 | * @param string $mailAddress Email address |
||
4997 | * @param string $linktxt Link text, default will be the email address. |
||
4998 | * @return array A numerical array with two elements: 1) $mailToUrl, string ready to be inserted into the href attribute of the <a> tag, b) $linktxt: The string between starting and ending <a> tag. |
||
4999 | */ |
||
5000 | public function getMailTo($mailAddress, $linktxt) |
||
5001 | { |
||
5002 | $mailAddress = (string)$mailAddress; |
||
5003 | if ((string)$linktxt === '') { |
||
5004 | $linktxt = htmlspecialchars($mailAddress); |
||
5005 | } |
||
5006 | |||
5007 | $originalMailToUrl = 'mailto:' . $mailAddress; |
||
5008 | $mailToUrl = $this->processUrl(UrlProcessorInterface::CONTEXT_MAIL, $originalMailToUrl); |
||
5009 | |||
5010 | // no processing happened, therefore, the default processing kicks in |
||
5011 | if ($mailToUrl === $originalMailToUrl) { |
||
5012 | $tsfe = $this->getTypoScriptFrontendController(); |
||
5013 | if ($tsfe->spamProtectEmailAddresses) { |
||
5014 | $mailToUrl = $this->encryptEmail($mailToUrl, $tsfe->spamProtectEmailAddresses); |
||
5015 | if ($tsfe->spamProtectEmailAddresses !== 'ascii') { |
||
5016 | $encodedForJsAndHref = rawurlencode(GeneralUtility::quoteJSvalue($mailToUrl)); |
||
5017 | $mailToUrl = 'javascript:linkTo_UnCryptMailto(' . $encodedForJsAndHref . ');'; |
||
5018 | } |
||
5019 | $atLabel = trim($tsfe->config['config']['spamProtectEmailAddresses_atSubst']) ?: '(at)'; |
||
5020 | $spamProtectedMailAddress = str_replace('@', $atLabel, htmlspecialchars($mailAddress)); |
||
5021 | if ($tsfe->config['config']['spamProtectEmailAddresses_lastDotSubst']) { |
||
5022 | $lastDotLabel = trim($tsfe->config['config']['spamProtectEmailAddresses_lastDotSubst']); |
||
5023 | $lastDotLabel = $lastDotLabel ?: '(dot)'; |
||
5024 | $spamProtectedMailAddress = preg_replace('/\\.([^\\.]+)$/', $lastDotLabel . '$1', $spamProtectedMailAddress); |
||
5025 | if ($spamProtectedMailAddress === null) { |
||
5026 | $this->logger->debug('Error replacing the last dot in email address "{email}"', ['email' => $spamProtectedMailAddress]); |
||
5027 | $spamProtectedMailAddress = ''; |
||
5028 | } |
||
5029 | } |
||
5030 | $linktxt = str_ireplace($mailAddress, $spamProtectedMailAddress, $linktxt); |
||
5031 | } |
||
5032 | } |
||
5033 | |||
5034 | return [$mailToUrl, $linktxt]; |
||
5035 | } |
||
5036 | |||
5037 | /** |
||
5038 | * Encryption of email addresses for <A>-tags See the spam protection setup in TS 'config.' |
||
5039 | * |
||
5040 | * @param string $string Input string to en/decode: "mailto:[email protected] |
||
5041 | * @param mixed $type - either "ascii" or a number between -10 and 10, taken from config.spamProtectEmailAddresses |
||
5042 | * @return string encoded version of $string |
||
5043 | */ |
||
5044 | protected function encryptEmail(string $string, $type): string |
||
5045 | { |
||
5046 | $out = ''; |
||
5047 | // obfuscates using the decimal HTML entity references for each character |
||
5048 | if ($type === 'ascii') { |
||
5049 | foreach (preg_split('//u', $string, -1, PREG_SPLIT_NO_EMPTY) as $char) { |
||
5050 | $out .= '&#' . mb_ord($char) . ';'; |
||
5051 | } |
||
5052 | } else { |
||
5053 | // like str_rot13() but with a variable offset and a wider character range |
||
5054 | $len = strlen($string); |
||
5055 | $offset = (int)$type; |
||
5056 | for ($i = 0; $i < $len; $i++) { |
||
5057 | $charValue = ord($string[$i]); |
||
5058 | // 0-9 . , - + / : |
||
5059 | if ($charValue >= 43 && $charValue <= 58) { |
||
5060 | $out .= $this->encryptCharcode($charValue, 43, 58, $offset); |
||
5061 | } elseif ($charValue >= 64 && $charValue <= 90) { |
||
5062 | // A-Z @ |
||
5063 | $out .= $this->encryptCharcode($charValue, 64, 90, $offset); |
||
5064 | } elseif ($charValue >= 97 && $charValue <= 122) { |
||
5065 | // a-z |
||
5066 | $out .= $this->encryptCharcode($charValue, 97, 122, $offset); |
||
5067 | } else { |
||
5068 | $out .= $string[$i]; |
||
5069 | } |
||
5070 | } |
||
5071 | } |
||
5072 | return $out; |
||
5073 | } |
||
5074 | |||
5075 | /** |
||
5076 | * Encryption (or decryption) of a single character. |
||
5077 | * Within the given range the character is shifted with the supplied offset. |
||
5078 | * |
||
5079 | * @param int $n Ordinal of input character |
||
5080 | * @param int $start Start of range |
||
5081 | * @param int $end End of range |
||
5082 | * @param int $offset Offset |
||
5083 | * @return string encoded/decoded version of character |
||
5084 | */ |
||
5085 | protected function encryptCharcode($n, $start, $end, $offset) |
||
5094 | } |
||
5095 | |||
5096 | /** |
||
5097 | * Gets the query arguments and assembles them for URLs. |
||
5098 | * Arguments may be removed or set, depending on configuration. |
||
5099 | * |
||
5100 | * @param array $conf Configuration |
||
5101 | * @return string The URL query part (starting with a &) |
||
5102 | */ |
||
5103 | public function getQueryArguments($conf) |
||
5104 | { |
||
5105 | $currentQueryArray = GeneralUtility::_GET(); |
||
5106 | if ($conf['exclude'] ?? false) { |
||
5107 | $excludeString = str_replace(',', '&', $conf['exclude']); |
||
5108 | $excludedQueryParts = []; |
||
5109 | parse_str($excludeString, $excludedQueryParts); |
||
5110 | $newQueryArray = ArrayUtility::arrayDiffKeyRecursive($currentQueryArray, $excludedQueryParts); |
||
5111 | } else { |
||
5112 | $newQueryArray = $currentQueryArray; |
||
5113 | } |
||
5114 | return HttpUtility::buildQueryString($newQueryArray, '&'); |
||
5115 | } |
||
5116 | |||
5117 | /*********************************************** |
||
5118 | * |
||
5119 | * Miscellaneous functions, stand alone |
||
5120 | * |
||
5121 | ***********************************************/ |
||
5122 | /** |
||
5123 | * Wrapping a string. |
||
5124 | * Implements the TypoScript "wrap" property. |
||
5125 | * Example: $content = "HELLO WORLD" and $wrap = "<strong> | </strong>", result: "<strong>HELLO WORLD</strong>" |
||
5126 | * |
||
5127 | * @param string $content The content to wrap |
||
5128 | * @param string $wrap The wrap value, eg. "<strong> | </strong> |
||
5129 | * @param string $char The char used to split the wrapping value, default is "| |
||
5130 | * @return string Wrapped input string |
||
5131 | * @see noTrimWrap() |
||
5132 | */ |
||
5133 | public function wrap($content, $wrap, $char = '|') |
||
5134 | { |
||
5135 | if ($wrap) { |
||
5136 | $wrapArr = explode($char, $wrap); |
||
5137 | $content = trim($wrapArr[0] ?? '') . $content . trim($wrapArr[1] ?? ''); |
||
5138 | } |
||
5139 | return $content; |
||
5140 | } |
||
5141 | |||
5142 | /** |
||
5143 | * Wrapping a string, preserving whitespace in wrap value. |
||
5144 | * Notice that the wrap value uses part 1/2 to wrap (and not 0/1 which wrap() does) |
||
5145 | * |
||
5146 | * @param string $content The content to wrap, eg. "HELLO WORLD |
||
5147 | * @param string $wrap The wrap value, eg. " | <strong> | </strong> |
||
5148 | * @param string $char The char used to split the wrapping value, default is "|" |
||
5149 | * @return string Wrapped input string, eg. " <strong> HELLO WORD </strong> |
||
5150 | * @see wrap() |
||
5151 | */ |
||
5152 | public function noTrimWrap($content, $wrap, $char = '|') |
||
5153 | { |
||
5154 | if ($wrap) { |
||
5155 | // expects to be wrapped with (at least) 3 characters (before, middle, after) |
||
5156 | // anything else is not taken into account |
||
5157 | $wrapArr = explode($char, $wrap, 4); |
||
5158 | $content = $wrapArr[1] . $content . $wrapArr[2]; |
||
5159 | } |
||
5160 | return $content; |
||
5161 | } |
||
5162 | |||
5163 | /** |
||
5164 | * Calling a user function/class-method |
||
5165 | * Notice: For classes the instantiated object will have the internal variable, $cObj, set to be a *reference* to $this (the parent/calling object). |
||
5166 | * |
||
5167 | * @param string $funcName The functionname, eg "user_myfunction" or "user_myclass->main". Notice that there are rules for the names of functions/classes you can instantiate. If a function cannot be called for some reason it will be seen in the TypoScript log in the AdminPanel. |
||
5168 | * @param array $conf The TypoScript configuration to pass the function |
||
5169 | * @param mixed $content The content payload to pass the function |
||
5170 | * @return mixed The return content from the function call. Should probably be a string. |
||
5171 | * @see stdWrap() |
||
5172 | * @see typoLink() |
||
5173 | * @see _parseFunc() |
||
5174 | */ |
||
5175 | public function callUserFunction($funcName, $conf, $content) |
||
5176 | { |
||
5177 | // Split parts |
||
5178 | $parts = explode('->', $funcName); |
||
5179 | if (count($parts) === 2) { |
||
5180 | // Check whether PHP class is available |
||
5181 | if (class_exists($parts[0])) { |
||
5182 | if ($this->container && $this->container->has($parts[0])) { |
||
5183 | $classObj = $this->container->get($parts[0]); |
||
5184 | } else { |
||
5185 | $classObj = GeneralUtility::makeInstance($parts[0]); |
||
5186 | } |
||
5187 | $methodName = (string)$parts[1]; |
||
5188 | $callable = [$classObj, $methodName]; |
||
5189 | if (is_object($classObj) && method_exists($classObj, $parts[1]) && is_callable($callable)) { |
||
5190 | $classObj->cObj = $this; |
||
5191 | $content = $callable($content, $conf, $this->getRequest()); |
||
5192 | } else { |
||
5193 | $this->getTimeTracker()->setTSlogMessage('Method "' . $parts[1] . '" did not exist in class "' . $parts[0] . '"', 3); |
||
5194 | } |
||
5195 | } else { |
||
5196 | $this->getTimeTracker()->setTSlogMessage('Class "' . $parts[0] . '" did not exist', 3); |
||
5197 | } |
||
5198 | } elseif (function_exists($funcName)) { |
||
5199 | $content = $funcName($content, $conf); |
||
5200 | } else { |
||
5201 | $this->getTimeTracker()->setTSlogMessage('Function "' . $funcName . '" did not exist', 3); |
||
5202 | } |
||
5203 | return $content; |
||
5204 | } |
||
5205 | |||
5206 | /** |
||
5207 | * Cleans up a string of keywords. Keywords at splitted by "," (comma) ";" (semi colon) and linebreak |
||
5208 | * |
||
5209 | * @param string $content String of keywords |
||
5210 | * @return string Cleaned up string, keywords will be separated by a comma only. |
||
5211 | */ |
||
5212 | public function keywords($content) |
||
5213 | { |
||
5214 | $listArr = preg_split('/[,;' . LF . ']/', $content); |
||
5215 | if ($listArr === false) { |
||
5216 | return ''; |
||
5217 | } |
||
5218 | foreach ($listArr as $k => $v) { |
||
5219 | $listArr[$k] = trim($v); |
||
5220 | } |
||
5221 | return implode(',', $listArr); |
||
5222 | } |
||
5223 | |||
5224 | /** |
||
5225 | * Changing character case of a string, converting typically used western charset characters as well. |
||
5226 | * |
||
5227 | * @param string $theValue The string to change case for. |
||
5228 | * @param string $case The direction; either "upper" or "lower |
||
5229 | * @return string |
||
5230 | * @see HTMLcaseshift() |
||
5231 | */ |
||
5232 | public function caseshift($theValue, $case) |
||
5233 | { |
||
5234 | switch (strtolower($case)) { |
||
5235 | case 'upper': |
||
5236 | $theValue = mb_strtoupper($theValue, 'utf-8'); |
||
5237 | break; |
||
5238 | case 'lower': |
||
5239 | $theValue = mb_strtolower($theValue, 'utf-8'); |
||
5240 | break; |
||
5241 | case 'capitalize': |
||
5242 | $theValue = mb_convert_case($theValue, MB_CASE_TITLE, 'utf-8'); |
||
5243 | break; |
||
5244 | case 'ucfirst': |
||
5245 | $firstChar = mb_substr($theValue, 0, 1, 'utf-8'); |
||
5246 | $firstChar = mb_strtoupper($firstChar, 'utf-8'); |
||
5247 | $remainder = mb_substr($theValue, 1, null, 'utf-8'); |
||
5248 | $theValue = $firstChar . $remainder; |
||
5249 | break; |
||
5250 | case 'lcfirst': |
||
5251 | $firstChar = mb_substr($theValue, 0, 1, 'utf-8'); |
||
5252 | $firstChar = mb_strtolower($firstChar, 'utf-8'); |
||
5253 | $remainder = mb_substr($theValue, 1, null, 'utf-8'); |
||
5254 | $theValue = $firstChar . $remainder; |
||
5255 | break; |
||
5256 | case 'uppercamelcase': |
||
5257 | $theValue = GeneralUtility::underscoredToUpperCamelCase($theValue); |
||
5258 | break; |
||
5259 | case 'lowercamelcase': |
||
5260 | $theValue = GeneralUtility::underscoredToLowerCamelCase($theValue); |
||
5261 | break; |
||
5262 | } |
||
5263 | return $theValue; |
||
5264 | } |
||
5265 | |||
5266 | /** |
||
5267 | * Shifts the case of characters outside of HTML tags in the input string |
||
5268 | * |
||
5269 | * @param string $theValue The string to change case for. |
||
5270 | * @param string $case The direction; either "upper" or "lower |
||
5271 | * @return string |
||
5272 | * @see caseshift() |
||
5273 | */ |
||
5274 | public function HTMLcaseshift($theValue, $case) |
||
5275 | { |
||
5276 | $inside = 0; |
||
5277 | $newVal = ''; |
||
5278 | $pointer = 0; |
||
5279 | $totalLen = strlen($theValue); |
||
5280 | do { |
||
5281 | if (!$inside) { |
||
5282 | $len = strcspn(substr($theValue, $pointer), '<'); |
||
5283 | $newVal .= $this->caseshift(substr($theValue, $pointer, $len), $case); |
||
5284 | $inside = 1; |
||
5285 | } else { |
||
5286 | $len = strcspn(substr($theValue, $pointer), '>') + 1; |
||
5287 | $newVal .= substr($theValue, $pointer, $len); |
||
5288 | $inside = 0; |
||
5289 | } |
||
5290 | $pointer += $len; |
||
5291 | } while ($pointer < $totalLen); |
||
5292 | return $newVal; |
||
5293 | } |
||
5294 | |||
5295 | /** |
||
5296 | * Returns the 'age' of the tstamp $seconds |
||
5297 | * |
||
5298 | * @param int $seconds Seconds to return age for. Example: "70" => "1 min", "3601" => "1 hrs |
||
5299 | * @param string $labels The labels of the individual units. Defaults to : ' min| hrs| days| yrs' |
||
5300 | * @return string The formatted string |
||
5301 | */ |
||
5302 | public function calcAge($seconds, $labels) |
||
5303 | { |
||
5304 | if (MathUtility::canBeInterpretedAsInteger($labels)) { |
||
5305 | $labels = ' min| hrs| days| yrs| min| hour| day| year'; |
||
5306 | } else { |
||
5307 | $labels = str_replace('"', '', $labels); |
||
5308 | } |
||
5309 | $labelArr = explode('|', $labels); |
||
5310 | if (count($labelArr) === 4) { |
||
5311 | $labelArr = array_merge($labelArr, $labelArr); |
||
5312 | } |
||
5313 | $absSeconds = abs($seconds); |
||
5314 | $sign = $seconds > 0 ? 1 : -1; |
||
5315 | if ($absSeconds < 3600) { |
||
5316 | $val = round($absSeconds / 60); |
||
5317 | $seconds = $sign * $val . ($val == 1 ? $labelArr[4] : $labelArr[0]); |
||
5318 | } elseif ($absSeconds < 24 * 3600) { |
||
5319 | $val = round($absSeconds / 3600); |
||
5320 | $seconds = $sign * $val . ($val == 1 ? $labelArr[5] : $labelArr[1]); |
||
5321 | } elseif ($absSeconds < 365 * 24 * 3600) { |
||
5322 | $val = round($absSeconds / (24 * 3600)); |
||
5323 | $seconds = $sign * $val . ($val == 1 ? $labelArr[6] : $labelArr[2]); |
||
5324 | } else { |
||
5325 | $val = round($absSeconds / (365 * 24 * 3600)); |
||
5326 | $seconds = $sign * $val . ($val == 1 ? ($labelArr[7] ?? null) : ($labelArr[3] ?? null)); |
||
5327 | } |
||
5328 | return $seconds; |
||
5329 | } |
||
5330 | |||
5331 | /** |
||
5332 | * Resolves a TypoScript reference value to the full set of properties BUT overridden with any local properties set. |
||
5333 | * So the reference is resolved but overlaid with local TypoScript properties of the reference value. |
||
5334 | * |
||
5335 | * @param array $confArr The TypoScript array |
||
5336 | * @param string $prop The property name: If this value is a reference (eg. " < plugins.tx_something") then the reference will be retrieved and inserted at that position (into the properties only, not the value...) AND overlaid with the old properties if any. |
||
5337 | * @return array The modified TypoScript array |
||
5338 | */ |
||
5339 | public function mergeTSRef($confArr, $prop) |
||
5340 | { |
||
5341 | if ($confArr[$prop][0] === '<') { |
||
5342 | $key = trim(substr($confArr[$prop], 1)); |
||
5343 | $cF = GeneralUtility::makeInstance(TypoScriptParser::class); |
||
5344 | // $name and $conf is loaded with the referenced values. |
||
5345 | $old_conf = $confArr[$prop . '.']; |
||
5346 | $conf = $cF->getVal($key, $this->getTypoScriptFrontendController()->tmpl->setup)[1] ?? []; |
||
5347 | if (is_array($old_conf) && !empty($old_conf)) { |
||
5348 | $conf = is_array($conf) ? array_replace_recursive($conf, $old_conf) : $old_conf; |
||
5349 | } |
||
5350 | $confArr[$prop . '.'] = $conf; |
||
5351 | } |
||
5352 | return $confArr; |
||
5353 | } |
||
5354 | |||
5355 | /*********************************************** |
||
5356 | * |
||
5357 | * Database functions, making of queries |
||
5358 | * |
||
5359 | ***********************************************/ |
||
5360 | /** |
||
5361 | * Generates a list of Page-uid's from $id. List does not include $id itself |
||
5362 | * (unless the id specified is negative in which case it does!) |
||
5363 | * The only pages WHICH PREVENTS DECENDING in a branch are |
||
5364 | * - deleted pages, |
||
5365 | * - pages in a recycler (doktype = 255) or of the Backend User Section (doktpe = 6) type |
||
5366 | * - pages that has the extendToSubpages set, WHERE start/endtime, hidden |
||
5367 | * and fe_users would hide the records. |
||
5368 | * Apart from that, pages with enable-fields excluding them, will also be |
||
5369 | * removed. HOWEVER $dontCheckEnableFields set will allow |
||
5370 | * enableFields-excluded pages to be included anyway - including |
||
5371 | * extendToSubpages sections! |
||
5372 | * Mount Pages are also descended but notice that these ID numbers are not |
||
5373 | * useful for links unless the correct MPvar is set. |
||
5374 | * |
||
5375 | * @param int $id The id of the start page from which point in the page tree to descend. IF NEGATIVE the id itself is included in the end of the list (only if $begin is 0) AND the output does NOT contain a last comma. Recommended since it will resolve the input ID for mount pages correctly and also check if the start ID actually exists! |
||
5376 | * @param int $depth The number of levels to descend. If you want to descend infinitely, just set this to 100 or so. Should be at least "1" since zero will just make the function return (no descend...) |
||
5377 | * @param int $begin Is an optional integer that determines at which level in the tree to start collecting uid's. Zero means 'start right away', 1 = 'next level and out' |
||
5378 | * @param bool $dontCheckEnableFields See function description |
||
5379 | * @param string $addSelectFields Additional fields to select. Syntax: ",[fieldname],[fieldname],... |
||
5380 | * @param string $moreWhereClauses Additional where clauses. Syntax: " AND [fieldname]=[value] AND ... |
||
5381 | * @param array $prevId_array array of IDs from previous recursions. In order to prevent infinite loops with mount pages. |
||
5382 | * @param int $recursionLevel Internal: Zero for the first recursion, incremented for each recursive call. |
||
5383 | * @return string Returns the list of ids as a comma separated string |
||
5384 | * @see TypoScriptFrontendController::checkEnableFields() |
||
5385 | * @see TypoScriptFrontendController::checkPagerecordForIncludeSection() |
||
5386 | */ |
||
5387 | public function getTreeList($id, $depth, $begin = 0, $dontCheckEnableFields = false, $addSelectFields = '', $moreWhereClauses = '', array $prevId_array = [], $recursionLevel = 0) |
||
5388 | { |
||
5389 | $id = (int)$id; |
||
5390 | if (!$id) { |
||
5391 | return ''; |
||
5392 | } |
||
5393 | |||
5394 | // Init vars: |
||
5395 | $allFields = 'uid,hidden,starttime,endtime,fe_group,extendToSubpages,doktype,php_tree_stop,mount_pid,mount_pid_ol,t3ver_state,l10n_parent' . $addSelectFields; |
||
5396 | $depth = (int)$depth; |
||
5397 | $begin = (int)$begin; |
||
5398 | $theList = []; |
||
5399 | $addId = 0; |
||
5400 | $requestHash = ''; |
||
5401 | |||
5402 | // First level, check id (second level, this is done BEFORE the recursive call) |
||
5403 | $tsfe = $this->getTypoScriptFrontendController(); |
||
5404 | if (!$recursionLevel) { |
||
5405 | // Check tree list cache |
||
5406 | // First, create the hash for this request - not sure yet whether we need all these parameters though |
||
5407 | $parameters = [ |
||
5408 | $id, |
||
5409 | $depth, |
||
5410 | $begin, |
||
5411 | $dontCheckEnableFields, |
||
5412 | $addSelectFields, |
||
5413 | $moreWhereClauses, |
||
5414 | $prevId_array, |
||
5415 | GeneralUtility::makeInstance(Context::class)->getPropertyFromAspect('frontend.user', 'groupIds', [0, -1]) |
||
5416 | ]; |
||
5417 | $requestHash = md5(serialize($parameters)); |
||
5418 | $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) |
||
5419 | ->getQueryBuilderForTable('cache_treelist'); |
||
5420 | $cacheEntry = $queryBuilder->select('treelist') |
||
5421 | ->from('cache_treelist') |
||
5422 | ->where( |
||
5423 | $queryBuilder->expr()->eq( |
||
5424 | 'md5hash', |
||
5425 | $queryBuilder->createNamedParameter($requestHash, \PDO::PARAM_STR) |
||
5426 | ), |
||
5427 | $queryBuilder->expr()->orX( |
||
5428 | $queryBuilder->expr()->gt( |
||
5429 | 'expires', |
||
5430 | $queryBuilder->createNamedParameter($GLOBALS['EXEC_TIME'], \PDO::PARAM_INT) |
||
5431 | ), |
||
5432 | $queryBuilder->expr()->eq('expires', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)) |
||
5433 | ) |
||
5434 | ) |
||
5435 | ->setMaxResults(1) |
||
5436 | ->execute() |
||
5437 | ->fetch(); |
||
5438 | |||
5439 | if (is_array($cacheEntry)) { |
||
5440 | // Cache hit |
||
5441 | return $cacheEntry['treelist']; |
||
5442 | } |
||
5443 | // If Id less than zero it means we should add the real id to list: |
||
5444 | if ($id < 0) { |
||
5445 | $addId = $id = abs($id); |
||
5446 | } |
||
5447 | // Check start page: |
||
5448 | if ($tsfe->sys_page->getRawRecord('pages', $id, 'uid')) { |
||
5449 | // Find mount point if any: |
||
5450 | $mount_info = $tsfe->sys_page->getMountPointInfo($id); |
||
5451 | if (is_array($mount_info)) { |
||
5452 | $id = $mount_info['mount_pid']; |
||
5453 | // In Overlay mode, use the mounted page uid as added ID!: |
||
5454 | if ($addId && $mount_info['overlay']) { |
||
5455 | $addId = $id; |
||
5456 | } |
||
5457 | } |
||
5458 | } else { |
||
5459 | // Return blank if the start page was NOT found at all! |
||
5460 | return ''; |
||
5461 | } |
||
5462 | } |
||
5463 | // Add this ID to the array of IDs |
||
5464 | if ($begin <= 0) { |
||
5465 | $prevId_array[] = $id; |
||
5466 | } |
||
5467 | // Select sublevel: |
||
5468 | if ($depth > 0) { |
||
5469 | $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages'); |
||
5470 | $queryBuilder->getRestrictions() |
||
5471 | ->removeAll() |
||
5472 | ->add(GeneralUtility::makeInstance(DeletedRestriction::class)); |
||
5473 | $queryBuilder->select(...GeneralUtility::trimExplode(',', $allFields, true)) |
||
5474 | ->from('pages') |
||
5475 | ->where( |
||
5476 | $queryBuilder->expr()->eq( |
||
5477 | 'pid', |
||
5478 | $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT) |
||
5479 | ), |
||
5480 | // tree is only built by language=0 pages |
||
5481 | $queryBuilder->expr()->eq('sys_language_uid', 0) |
||
5482 | ) |
||
5483 | ->orderBy('sorting'); |
||
5484 | |||
5485 | if (!empty($moreWhereClauses)) { |
||
5486 | $queryBuilder->andWhere(QueryHelper::stripLogicalOperatorPrefix($moreWhereClauses)); |
||
5487 | } |
||
5488 | |||
5489 | $result = $queryBuilder->execute(); |
||
5490 | while ($row = $result->fetch()) { |
||
5491 | /** @var VersionState $versionState */ |
||
5492 | $versionState = VersionState::cast($row['t3ver_state']); |
||
5493 | $tsfe->sys_page->versionOL('pages', $row); |
||
5494 | if ($row === false |
||
5495 | || (int)$row['doktype'] === PageRepository::DOKTYPE_RECYCLER |
||
5496 | || (int)$row['doktype'] === PageRepository::DOKTYPE_BE_USER_SECTION |
||
5497 | || $versionState->indicatesPlaceholder() |
||
5498 | ) { |
||
5499 | // falsy row means Overlay prevents access to this page. |
||
5500 | // Doing this after the overlay to make sure changes |
||
5501 | // in the overlay are respected. |
||
5502 | // However, we do not process pages below of and |
||
5503 | // including of type recycler and BE user section |
||
5504 | continue; |
||
5505 | } |
||
5506 | // Find mount point if any: |
||
5507 | $next_id = $row['uid']; |
||
5508 | $mount_info = $tsfe->sys_page->getMountPointInfo($next_id, $row); |
||
5509 | // Overlay mode: |
||
5510 | if (is_array($mount_info) && $mount_info['overlay']) { |
||
5511 | $next_id = $mount_info['mount_pid']; |
||
5512 | $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) |
||
5513 | ->getQueryBuilderForTable('pages'); |
||
5514 | $queryBuilder->getRestrictions() |
||
5515 | ->removeAll() |
||
5516 | ->add(GeneralUtility::makeInstance(DeletedRestriction::class)); |
||
5517 | $queryBuilder->select(...GeneralUtility::trimExplode(',', $allFields, true)) |
||
5518 | ->from('pages') |
||
5519 | ->where( |
||
5520 | $queryBuilder->expr()->eq( |
||
5521 | 'uid', |
||
5522 | $queryBuilder->createNamedParameter($next_id, \PDO::PARAM_INT) |
||
5523 | ) |
||
5524 | ) |
||
5525 | ->orderBy('sorting') |
||
5526 | ->setMaxResults(1); |
||
5527 | |||
5528 | if (!empty($moreWhereClauses)) { |
||
5529 | $queryBuilder->andWhere(QueryHelper::stripLogicalOperatorPrefix($moreWhereClauses)); |
||
5530 | } |
||
5531 | |||
5532 | $row = $queryBuilder->execute()->fetch(); |
||
5533 | $tsfe->sys_page->versionOL('pages', $row); |
||
5534 | if ((int)$row['doktype'] === PageRepository::DOKTYPE_RECYCLER |
||
5535 | || (int)$row['doktype'] === PageRepository::DOKTYPE_BE_USER_SECTION |
||
5536 | || $versionState->indicatesPlaceholder() |
||
5537 | ) { |
||
5538 | // Doing this after the overlay to make sure |
||
5539 | // changes in the overlay are respected. |
||
5540 | // see above |
||
5541 | continue; |
||
5542 | } |
||
5543 | } |
||
5544 | // Add record: |
||
5545 | if ($dontCheckEnableFields || $tsfe->checkPagerecordForIncludeSection($row)) { |
||
5546 | // Add ID to list: |
||
5547 | if ($begin <= 0) { |
||
5548 | if ($dontCheckEnableFields || $tsfe->checkEnableFields($row)) { |
||
5549 | $theList[] = $next_id; |
||
5550 | } |
||
5551 | } |
||
5552 | // Next level: |
||
5553 | if ($depth > 1 && !$row['php_tree_stop']) { |
||
5554 | // Normal mode: |
||
5555 | if (is_array($mount_info) && !$mount_info['overlay']) { |
||
5556 | $next_id = $mount_info['mount_pid']; |
||
5557 | } |
||
5558 | // Call recursively, if the id is not in prevID_array: |
||
5559 | if (!in_array($next_id, $prevId_array)) { |
||
5560 | $theList = array_merge( |
||
5561 | GeneralUtility::intExplode( |
||
5562 | ',', |
||
5563 | $this->getTreeList( |
||
5564 | $next_id, |
||
5565 | $depth - 1, |
||
5566 | $begin - 1, |
||
5567 | $dontCheckEnableFields, |
||
5568 | $addSelectFields, |
||
5569 | $moreWhereClauses, |
||
5570 | $prevId_array, |
||
5571 | $recursionLevel + 1 |
||
5572 | ), |
||
5573 | true |
||
5574 | ), |
||
5575 | $theList |
||
5576 | ); |
||
5577 | } |
||
5578 | } |
||
5579 | } |
||
5580 | } |
||
5581 | } |
||
5582 | // If first run, check if the ID should be returned: |
||
5583 | if (!$recursionLevel) { |
||
5584 | if ($addId) { |
||
5585 | if ($begin > 0) { |
||
5586 | $theList[] = 0; |
||
5587 | } else { |
||
5588 | $theList[] = $addId; |
||
5589 | } |
||
5590 | } |
||
5591 | |||
5592 | $cacheEntry = [ |
||
5593 | 'md5hash' => $requestHash, |
||
5594 | 'pid' => $id, |
||
5595 | 'treelist' => implode(',', $theList), |
||
5596 | 'tstamp' => $GLOBALS['EXEC_TIME'], |
||
5597 | ]; |
||
5598 | |||
5599 | // Only add to cache if not logged into TYPO3 Backend |
||
5600 | if (!$this->getFrontendBackendUser() instanceof AbstractUserAuthentication) { |
||
5601 | $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('cache_treelist'); |
||
5602 | try { |
||
5603 | $connection->transactional(function ($connection) use ($cacheEntry) { |
||
5604 | $connection->insert('cache_treelist', $cacheEntry); |
||
5605 | }); |
||
5606 | } catch (\Throwable $e) { |
||
5607 | } |
||
5608 | } |
||
5609 | } |
||
5610 | |||
5611 | return implode(',', $theList); |
||
5612 | } |
||
5613 | |||
5614 | /** |
||
5615 | * Generates a search where clause based on the input search words (AND operation - all search words must be found in record.) |
||
5616 | * Example: The $sw is "content management, system" (from an input form) and the $searchFieldList is "bodytext,header" then the output will be ' AND (bodytext LIKE "%content%" OR header LIKE "%content%") AND (bodytext LIKE "%management%" OR header LIKE "%management%") AND (bodytext LIKE "%system%" OR header LIKE "%system%")' |
||
5617 | * |
||
5618 | * @param string $searchWords The search words. These will be separated by space and comma. |
||
5619 | * @param string $searchFieldList The fields to search in |
||
5620 | * @param string $searchTable The table name you search in (recommended for DBAL compliance. Will be prepended field names as well) |
||
5621 | * @return string The WHERE clause. |
||
5622 | */ |
||
5623 | public function searchWhere($searchWords, $searchFieldList, $searchTable) |
||
5624 | { |
||
5625 | if (!$searchWords) { |
||
5626 | return ''; |
||
5627 | } |
||
5628 | |||
5629 | $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) |
||
5630 | ->getQueryBuilderForTable($searchTable); |
||
5631 | |||
5632 | $prefixTableName = $searchTable ? $searchTable . '.' : ''; |
||
5633 | |||
5634 | $where = $queryBuilder->expr()->andX(); |
||
5635 | $searchFields = explode(',', $searchFieldList); |
||
5636 | $searchWords = preg_split('/[ ,]/', $searchWords); |
||
5637 | foreach ($searchWords as $searchWord) { |
||
5638 | $searchWord = trim($searchWord); |
||
5639 | if (strlen($searchWord) < 3) { |
||
5640 | continue; |
||
5641 | } |
||
5642 | $searchWordConstraint = $queryBuilder->expr()->orX(); |
||
5643 | $searchWord = $queryBuilder->escapeLikeWildcards($searchWord); |
||
5644 | foreach ($searchFields as $field) { |
||
5645 | $searchWordConstraint->add( |
||
5646 | $queryBuilder->expr()->like($prefixTableName . $field, $queryBuilder->quote('%' . $searchWord . '%')) |
||
5647 | ); |
||
5648 | } |
||
5649 | |||
5650 | if ($searchWordConstraint->count()) { |
||
5651 | $where->add($searchWordConstraint); |
||
5652 | } |
||
5653 | } |
||
5654 | |||
5655 | if ((string)$where === '') { |
||
5656 | return ''; |
||
5657 | } |
||
5658 | |||
5659 | return ' AND (' . (string)$where . ')'; |
||
5660 | } |
||
5661 | |||
5662 | /** |
||
5663 | * Executes a SELECT query for records from $table and with conditions based on the configuration in the $conf array |
||
5664 | * This function is preferred over ->getQuery() if you just need to create and then execute a query. |
||
5665 | * |
||
5666 | * @param string $table The table name |
||
5667 | * @param array $conf The TypoScript configuration properties |
||
5668 | * @return Statement |
||
5669 | * @see getQuery() |
||
5670 | */ |
||
5671 | public function exec_getQuery($table, $conf) |
||
5672 | { |
||
5673 | $statement = $this->getQuery($table, $conf); |
||
5674 | $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table); |
||
5675 | |||
5676 | return $connection->executeQuery($statement); |
||
5677 | } |
||
5678 | |||
5679 | /** |
||
5680 | * Executes a SELECT query for records from $table and with conditions based on the configuration in the $conf array |
||
5681 | * and overlays with translation and version if available |
||
5682 | * |
||
5683 | * @param string $tableName the name of the TCA database table |
||
5684 | * @param array $queryConfiguration The TypoScript configuration properties, see .select in TypoScript reference |
||
5685 | * @return array The records |
||
5686 | * @throws \UnexpectedValueException |
||
5687 | */ |
||
5688 | public function getRecords($tableName, array $queryConfiguration) |
||
5689 | { |
||
5690 | $records = []; |
||
5691 | |||
5692 | $statement = $this->exec_getQuery($tableName, $queryConfiguration); |
||
5693 | |||
5694 | $tsfe = $this->getTypoScriptFrontendController(); |
||
5695 | while ($row = $statement->fetch()) { |
||
5696 | // Versioning preview: |
||
5697 | $tsfe->sys_page->versionOL($tableName, $row, true); |
||
5698 | |||
5699 | // Language overlay: |
||
5700 | if (is_array($row)) { |
||
5701 | $row = $tsfe->sys_page->getLanguageOverlay($tableName, $row); |
||
5702 | } |
||
5703 | |||
5704 | // Might be unset in the language overlay |
||
5705 | if (is_array($row)) { |
||
5706 | $records[] = $row; |
||
5707 | } |
||
5708 | } |
||
5709 | |||
5710 | return $records; |
||
5711 | } |
||
5712 | |||
5713 | /** |
||
5714 | * Creates and returns a SELECT query for records from $table and with conditions based on the configuration in the $conf array |
||
5715 | * Implements the "select" function in TypoScript |
||
5716 | * |
||
5717 | * @param string $table See ->exec_getQuery() |
||
5718 | * @param array $conf See ->exec_getQuery() |
||
5719 | * @param bool $returnQueryArray If set, the function will return the query not as a string but array with the various parts. RECOMMENDED! |
||
5720 | * @return mixed A SELECT query if $returnQueryArray is FALSE, otherwise the SELECT query in an array as parts. |
||
5721 | * @throws \RuntimeException |
||
5722 | * @throws \InvalidArgumentException |
||
5723 | * @internal |
||
5724 | * @see numRows() |
||
5725 | */ |
||
5726 | public function getQuery($table, $conf, $returnQueryArray = false) |
||
5727 | { |
||
5728 | // Resolve stdWrap in these properties first |
||
5729 | $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table); |
||
5730 | $properties = [ |
||
5731 | 'pidInList', |
||
5732 | 'uidInList', |
||
5733 | 'languageField', |
||
5734 | 'selectFields', |
||
5735 | 'max', |
||
5736 | 'begin', |
||
5737 | 'groupBy', |
||
5738 | 'orderBy', |
||
5739 | 'join', |
||
5740 | 'leftjoin', |
||
5741 | 'rightjoin', |
||
5742 | 'recursive', |
||
5743 | 'where' |
||
5744 | ]; |
||
5745 | foreach ($properties as $property) { |
||
5746 | $conf[$property] = trim( |
||
5747 | isset($conf[$property . '.']) |
||
5748 | ? $this->stdWrap($conf[$property] ?? '', $conf[$property . '.'] ?? []) |
||
5749 | : ($conf[$property] ?? null) |
||
5750 | ); |
||
5751 | if ($conf[$property] === '') { |
||
5752 | unset($conf[$property]); |
||
5753 | } elseif (in_array($property, ['languageField', 'selectFields', 'join', 'leftjoin', 'rightjoin', 'where'], true)) { |
||
5754 | $conf[$property] = QueryHelper::quoteDatabaseIdentifiers($connection, $conf[$property]); |
||
5755 | } |
||
5756 | if (isset($conf[$property . '.'])) { |
||
5757 | // stdWrapping already done, so remove the sub-array |
||
5758 | unset($conf[$property . '.']); |
||
5759 | } |
||
5760 | } |
||
5761 | // Handle PDO-style named parameter markers first |
||
5762 | $queryMarkers = $this->getQueryMarkers($table, $conf); |
||
5763 | // Replace the markers in the non-stdWrap properties |
||
5764 | foreach ($queryMarkers as $marker => $markerValue) { |
||
5765 | $properties = [ |
||
5766 | 'uidInList', |
||
5767 | 'selectFields', |
||
5768 | 'where', |
||
5769 | 'max', |
||
5770 | 'begin', |
||
5771 | 'groupBy', |
||
5772 | 'orderBy', |
||
5773 | 'join', |
||
5774 | 'leftjoin', |
||
5775 | 'rightjoin' |
||
5776 | ]; |
||
5777 | foreach ($properties as $property) { |
||
5778 | if ($conf[$property]) { |
||
5779 | $conf[$property] = str_replace('###' . $marker . '###', $markerValue, $conf[$property]); |
||
5780 | } |
||
5781 | } |
||
5782 | } |
||
5783 | |||
5784 | // Construct WHERE clause: |
||
5785 | // Handle recursive function for the pidInList |
||
5786 | if (isset($conf['recursive'])) { |
||
5787 | $conf['recursive'] = (int)$conf['recursive']; |
||
5788 | if ($conf['recursive'] > 0) { |
||
5789 | $pidList = GeneralUtility::trimExplode(',', $conf['pidInList'], true); |
||
5790 | array_walk($pidList, function (&$storagePid) { |
||
5791 | if ($storagePid === 'this') { |
||
5792 | $storagePid = $this->getTypoScriptFrontendController()->id; |
||
5793 | } |
||
5794 | if ($storagePid > 0) { |
||
5795 | $storagePid = -$storagePid; |
||
5796 | } |
||
5797 | }); |
||
5798 | $expandedPidList = []; |
||
5799 | foreach ($pidList as $value) { |
||
5800 | // Implementation of getTreeList allows to pass the id negative to include |
||
5801 | // it into the result otherwise only childpages are returned |
||
5802 | $expandedPidList = array_merge( |
||
5803 | GeneralUtility::intExplode(',', $this->getTreeList((int)$value, (int)($conf['recursive'] ?? 0))), |
||
5804 | $expandedPidList |
||
5805 | ); |
||
5806 | } |
||
5807 | $conf['pidInList'] = implode(',', $expandedPidList); |
||
5808 | } |
||
5809 | } |
||
5810 | if ((string)($conf['pidInList'] ?? '') === '') { |
||
5811 | $conf['pidInList'] = 'this'; |
||
5812 | } |
||
5813 | |||
5814 | $queryParts = $this->getQueryConstraints($table, $conf); |
||
5815 | |||
5816 | $queryBuilder = $connection->createQueryBuilder(); |
||
5817 | // @todo Check against getQueryConstraints, can probably use FrontendRestrictions |
||
5818 | // @todo here and remove enableFields there. |
||
5819 | $queryBuilder->getRestrictions()->removeAll(); |
||
5820 | $queryBuilder->select('*')->from($table); |
||
5821 | |||
5822 | if ($queryParts['where'] ?? false) { |
||
5823 | $queryBuilder->where($queryParts['where']); |
||
5824 | } |
||
5825 | |||
5826 | if ($queryParts['groupBy'] ?? false) { |
||
5827 | $queryBuilder->groupBy(...$queryParts['groupBy']); |
||
5828 | } |
||
5829 | |||
5830 | if (is_array($queryParts['orderBy'] ?? false)) { |
||
5831 | foreach ($queryParts['orderBy'] as $orderBy) { |
||
5832 | $queryBuilder->addOrderBy(...$orderBy); |
||
5833 | } |
||
5834 | } |
||
5835 | |||
5836 | // Fields: |
||
5837 | if ($conf['selectFields'] ?? false) { |
||
5838 | $queryBuilder->selectLiteral($this->sanitizeSelectPart($conf['selectFields'], $table)); |
||
5839 | } |
||
5840 | |||
5841 | // Setting LIMIT: |
||
5842 | $error = false; |
||
5843 | if (($conf['max'] ?? false) || ($conf['begin'] ?? false)) { |
||
5844 | // Finding the total number of records, if used: |
||
5845 | if (strpos(strtolower(($conf['begin'] ?? '') . $conf['max']), 'total') !== false) { |
||
5846 | $countQueryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table); |
||
5847 | $countQueryBuilder->getRestrictions()->removeAll(); |
||
5848 | $countQueryBuilder->count('*') |
||
5849 | ->from($table) |
||
5850 | ->where($queryParts['where']); |
||
5851 | |||
5852 | if ($queryParts['groupBy']) { |
||
5853 | $countQueryBuilder->groupBy(...$queryParts['groupBy']); |
||
5854 | } |
||
5855 | |||
5856 | try { |
||
5857 | $count = $countQueryBuilder->execute()->fetchOne(); |
||
5858 | $conf['max'] = str_ireplace('total', $count, $conf['max']); |
||
5859 | $conf['begin'] = str_ireplace('total', $count, $conf['begin']); |
||
5860 | } catch (DBALException $e) { |
||
5861 | $this->getTimeTracker()->setTSlogMessage($e->getPrevious()->getMessage()); |
||
5862 | $error = true; |
||
5863 | } |
||
5864 | } |
||
5865 | |||
5866 | if (!$error) { |
||
5867 | $conf['begin'] = MathUtility::forceIntegerInRange((int)ceil($this->calc($conf['begin'] ?? '')), 0); |
||
5868 | $conf['max'] = MathUtility::forceIntegerInRange((int)ceil($this->calc($conf['max'] ?? '')), 0); |
||
5869 | if ($conf['begin'] > 0) { |
||
5870 | $queryBuilder->setFirstResult($conf['begin']); |
||
5871 | } |
||
5872 | $queryBuilder->setMaxResults($conf['max'] ?: 100000); |
||
5873 | } |
||
5874 | } |
||
5875 | |||
5876 | if (!$error) { |
||
5877 | // Setting up tablejoins: |
||
5878 | if ($conf['join'] ?? false) { |
||
5879 | $joinParts = QueryHelper::parseJoin($conf['join']); |
||
5880 | $queryBuilder->join( |
||
5881 | $table, |
||
5882 | $joinParts['tableName'], |
||
5883 | $joinParts['tableAlias'], |
||
5884 | $joinParts['joinCondition'] |
||
5885 | ); |
||
5886 | } elseif ($conf['leftjoin'] ?? false) { |
||
5887 | $joinParts = QueryHelper::parseJoin($conf['leftjoin']); |
||
5888 | $queryBuilder->leftJoin( |
||
5889 | $table, |
||
5890 | $joinParts['tableName'], |
||
5891 | $joinParts['tableAlias'], |
||
5892 | $joinParts['joinCondition'] |
||
5893 | ); |
||
5894 | } elseif ($conf['rightjoin'] ?? false) { |
||
5895 | $joinParts = QueryHelper::parseJoin($conf['rightjoin']); |
||
5896 | $queryBuilder->rightJoin( |
||
5897 | $table, |
||
5898 | $joinParts['tableName'], |
||
5899 | $joinParts['tableAlias'], |
||
5900 | $joinParts['joinCondition'] |
||
5901 | ); |
||
5902 | } |
||
5903 | |||
5904 | // Convert the QueryBuilder object into a SQL statement. |
||
5905 | $query = $queryBuilder->getSQL(); |
||
5906 | |||
5907 | // Replace the markers in the queryParts to handle stdWrap enabled properties |
||
5908 | foreach ($queryMarkers as $marker => $markerValue) { |
||
5909 | // @todo Ugly hack that needs to be cleaned up, with the current architecture |
||
5910 | // @todo for exec_Query / getQuery it's the best we can do. |
||
5911 | $query = str_replace('###' . $marker . '###', $markerValue, $query); |
||
5912 | foreach ($queryParts as $queryPartKey => &$queryPartValue) { |
||
5913 | $queryPartValue = str_replace('###' . $marker . '###', $markerValue, $queryPartValue); |
||
5914 | } |
||
5915 | unset($queryPartValue); |
||
5916 | } |
||
5917 | |||
5918 | return $returnQueryArray ? $this->getQueryArray($queryBuilder) : $query; |
||
5919 | } |
||
5920 | |||
5921 | return ''; |
||
5922 | } |
||
5923 | |||
5924 | /** |
||
5925 | * Helper to transform a QueryBuilder object into a queryParts array that can be used |
||
5926 | * with exec_SELECT_queryArray |
||
5927 | * |
||
5928 | * @param \TYPO3\CMS\Core\Database\Query\QueryBuilder $queryBuilder |
||
5929 | * @return array |
||
5930 | * @throws \RuntimeException |
||
5931 | */ |
||
5932 | protected function getQueryArray(QueryBuilder $queryBuilder) |
||
5933 | { |
||
5934 | $fromClauses = []; |
||
5935 | $knownAliases = []; |
||
5936 | $queryParts = []; |
||
5937 | |||
5938 | // Loop through all FROM clauses |
||
5939 | foreach ($queryBuilder->getQueryPart('from') as $from) { |
||
5940 | if ($from['alias'] === null) { |
||
5941 | $tableSql = $from['table']; |
||
5942 | $tableReference = $from['table']; |
||
5943 | } else { |
||
5944 | $tableSql = $from['table'] . ' ' . $from['alias']; |
||
5945 | $tableReference = $from['alias']; |
||
5946 | } |
||
5947 | |||
5948 | $knownAliases[$tableReference] = true; |
||
5949 | |||
5950 | $fromClauses[$tableReference] = $tableSql . $this->getQueryArrayJoinHelper( |
||
5951 | $tableReference, |
||
5952 | $queryBuilder->getQueryPart('join'), |
||
5953 | $knownAliases |
||
5954 | ); |
||
5955 | } |
||
5956 | |||
5957 | $queryParts['SELECT'] = implode(', ', $queryBuilder->getQueryPart('select')); |
||
5958 | $queryParts['FROM'] = implode(', ', $fromClauses); |
||
5959 | $queryParts['WHERE'] = (string)$queryBuilder->getQueryPart('where') ?: ''; |
||
5960 | $queryParts['GROUPBY'] = implode(', ', $queryBuilder->getQueryPart('groupBy')); |
||
5961 | $queryParts['ORDERBY'] = implode(', ', $queryBuilder->getQueryPart('orderBy')); |
||
5962 | if ($queryBuilder->getFirstResult() > 0) { |
||
5963 | $queryParts['LIMIT'] = $queryBuilder->getFirstResult() . ',' . $queryBuilder->getMaxResults(); |
||
5964 | } elseif ($queryBuilder->getMaxResults() > 0) { |
||
5965 | $queryParts['LIMIT'] = $queryBuilder->getMaxResults(); |
||
5966 | } |
||
5967 | |||
5968 | return $queryParts; |
||
5969 | } |
||
5970 | |||
5971 | /** |
||
5972 | * Helper to transform the QueryBuilder join part into a SQL fragment. |
||
5973 | * |
||
5974 | * @param string $fromAlias |
||
5975 | * @param array $joinParts |
||
5976 | * @param array $knownAliases |
||
5977 | * @return string |
||
5978 | * @throws \RuntimeException |
||
5979 | */ |
||
5980 | protected function getQueryArrayJoinHelper(string $fromAlias, array $joinParts, array &$knownAliases): string |
||
5981 | { |
||
5982 | $sql = ''; |
||
5983 | |||
5984 | if (isset($joinParts['join'][$fromAlias])) { |
||
5985 | foreach ($joinParts['join'][$fromAlias] as $join) { |
||
5986 | if (array_key_exists($join['joinAlias'], $knownAliases)) { |
||
5987 | throw new \RuntimeException( |
||
5988 | 'Non unique join alias: "' . $join['joinAlias'] . '" found.', |
||
5989 | 1472748872 |
||
5990 | ); |
||
5991 | } |
||
5992 | $sql .= ' ' . strtoupper($join['joinType']) |
||
5993 | . ' JOIN ' . $join['joinTable'] . ' ' . $join['joinAlias'] |
||
5994 | . ' ON ' . ((string)$join['joinCondition']); |
||
5995 | $knownAliases[$join['joinAlias']] = true; |
||
5996 | } |
||
5997 | |||
5998 | foreach ($joinParts['join'][$fromAlias] as $join) { |
||
5999 | $sql .= $this->getQueryArrayJoinHelper($join['joinAlias'], $joinParts, $knownAliases); |
||
6000 | } |
||
6001 | } |
||
6002 | |||
6003 | return $sql; |
||
6004 | } |
||
6005 | /** |
||
6006 | * Helper function for getQuery(), creating the WHERE clause of the SELECT query |
||
6007 | * |
||
6008 | * @param string $table The table name |
||
6009 | * @param array $conf The TypoScript configuration properties |
||
6010 | * @return array Associative array containing the prepared data for WHERE, ORDER BY and GROUP BY fragments |
||
6011 | * @throws \InvalidArgumentException |
||
6012 | * @see getQuery() |
||
6013 | */ |
||
6014 | protected function getQueryConstraints(string $table, array $conf): array |
||
6015 | { |
||
6016 | // Init: |
||
6017 | $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table); |
||
6018 | $expressionBuilder = $queryBuilder->expr(); |
||
6019 | $tsfe = $this->getTypoScriptFrontendController(); |
||
6020 | $constraints = []; |
||
6021 | $pid_uid_flag = 0; |
||
6022 | $enableFieldsIgnore = []; |
||
6023 | $queryParts = [ |
||
6024 | 'where' => null, |
||
6025 | 'groupBy' => null, |
||
6026 | 'orderBy' => null, |
||
6027 | ]; |
||
6028 | |||
6029 | $isInWorkspace = GeneralUtility::makeInstance(Context::class)->getPropertyFromAspect('workspace', 'isOffline'); |
||
6030 | $considerMovePointers = ( |
||
6031 | $isInWorkspace && $table !== 'pages' |
||
6032 | && !empty($GLOBALS['TCA'][$table]['ctrl']['versioningWS']) |
||
6033 | ); |
||
6034 | |||
6035 | if (trim($conf['uidInList'] ?? '')) { |
||
6036 | $listArr = GeneralUtility::intExplode(',', str_replace('this', (string)$tsfe->contentPid, $conf['uidInList'])); |
||
6037 | |||
6038 | // If moved records shall be considered, select via t3ver_oid |
||
6039 | if ($considerMovePointers) { |
||
6040 | $constraints[] = (string)$expressionBuilder->orX( |
||
6041 | $expressionBuilder->in($table . '.uid', $listArr), |
||
6042 | $expressionBuilder->andX( |
||
6043 | $expressionBuilder->eq( |
||
6044 | $table . '.t3ver_state', |
||
6045 | (int)(string)VersionState::cast(VersionState::MOVE_POINTER) |
||
6046 | ), |
||
6047 | $expressionBuilder->in($table . '.t3ver_oid', $listArr) |
||
6048 | ) |
||
6049 | ); |
||
6050 | } else { |
||
6051 | $constraints[] = (string)$expressionBuilder->in($table . '.uid', $listArr); |
||
6052 | } |
||
6053 | $pid_uid_flag++; |
||
6054 | } |
||
6055 | |||
6056 | // Static_* tables are allowed to be fetched from root page |
||
6057 | if (strpos($table, 'static_') === 0) { |
||
6058 | $pid_uid_flag++; |
||
6059 | } |
||
6060 | |||
6061 | if (trim($conf['pidInList'])) { |
||
6062 | $listArr = GeneralUtility::intExplode(',', str_replace('this', (string)$tsfe->contentPid, $conf['pidInList'])); |
||
6063 | // Removes all pages which are not visible for the user! |
||
6064 | $listArr = $this->checkPidArray($listArr); |
||
6065 | if (GeneralUtility::inList($conf['pidInList'], 'root')) { |
||
6066 | $listArr[] = 0; |
||
6067 | } |
||
6068 | if (GeneralUtility::inList($conf['pidInList'], '-1')) { |
||
6069 | $listArr[] = -1; |
||
6070 | $enableFieldsIgnore['pid'] = true; |
||
6071 | } |
||
6072 | if (!empty($listArr)) { |
||
6073 | $constraints[] = $expressionBuilder->in($table . '.pid', array_map('intval', $listArr)); |
||
6074 | $pid_uid_flag++; |
||
6075 | } else { |
||
6076 | // If not uid and not pid then uid is set to 0 - which results in nothing!! |
||
6077 | $pid_uid_flag = 0; |
||
6078 | } |
||
6079 | } |
||
6080 | |||
6081 | // If not uid and not pid then uid is set to 0 - which results in nothing!! |
||
6082 | if (!$pid_uid_flag) { |
||
6083 | $constraints[] = $expressionBuilder->eq($table . '.uid', 0); |
||
6084 | } |
||
6085 | |||
6086 | $where = trim((string)$this->stdWrapValue('where', $conf ?? [])); |
||
6087 | if ($where) { |
||
6088 | $constraints[] = QueryHelper::stripLogicalOperatorPrefix($where); |
||
6089 | } |
||
6090 | |||
6091 | // Check if the default language should be fetched (= doing overlays), or if only the records of a language should be fetched |
||
6092 | // but only do this for TCA tables that have languages enabled |
||
6093 | $languageConstraint = $this->getLanguageRestriction($expressionBuilder, $table, $conf, GeneralUtility::makeInstance(Context::class)); |
||
6094 | if ($languageConstraint !== null) { |
||
6095 | $constraints[] = $languageConstraint; |
||
6096 | } |
||
6097 | |||
6098 | // Enablefields |
||
6099 | if ($table === 'pages') { |
||
6100 | $constraints[] = QueryHelper::stripLogicalOperatorPrefix($tsfe->sys_page->where_hid_del); |
||
6101 | $constraints[] = QueryHelper::stripLogicalOperatorPrefix($tsfe->sys_page->where_groupAccess); |
||
6102 | } else { |
||
6103 | $constraints[] = QueryHelper::stripLogicalOperatorPrefix($tsfe->sys_page->enableFields($table, -1, $enableFieldsIgnore)); |
||
6104 | } |
||
6105 | |||
6106 | // MAKE WHERE: |
||
6107 | if (count($constraints) !== 0) { |
||
6108 | $queryParts['where'] = $expressionBuilder->andX(...$constraints); |
||
6109 | } |
||
6110 | // GROUP BY |
||
6111 | $groupBy = trim((string)$this->stdWrapValue('groupBy', $conf ?? [])); |
||
6112 | if ($groupBy) { |
||
6113 | $queryParts['groupBy'] = QueryHelper::parseGroupBy($groupBy); |
||
6114 | } |
||
6115 | |||
6116 | // ORDER BY |
||
6117 | $orderByString = trim((string)$this->stdWrapValue('orderBy', $conf ?? [])); |
||
6118 | if ($orderByString) { |
||
6119 | $queryParts['orderBy'] = QueryHelper::parseOrderBy($orderByString); |
||
6120 | } |
||
6121 | |||
6122 | // Return result: |
||
6123 | return $queryParts; |
||
6124 | } |
||
6125 | |||
6126 | /** |
||
6127 | * Adds parts to the WHERE clause that are related to language. |
||
6128 | * This only works on TCA tables which have the [ctrl][languageField] field set or if they |
||
6129 | * have select.languageField = my_language_field set explicitly. |
||
6130 | * |
||
6131 | * It is also possible to disable the language restriction for a query by using select.languageField = 0, |
||
6132 | * if select.languageField is not explicitly set, the TCA default values are taken. |
||
6133 | * |
||
6134 | * If the table is "localizeable" (= any of the criteria above is met), then the DB query is restricted: |
||
6135 | * |
||
6136 | * If the current language aspect has overlays enabled, then the only records with language "0" or "-1" are |
||
6137 | * fetched (the overlays are taken care of later-on). |
||
6138 | * if the current language has overlays but also records without localization-parent (free mode) available, |
||
6139 | * then these are fetched as well. This can explicitly set via select.includeRecordsWithoutDefaultTranslation = 1 |
||
6140 | * which overrules the overlayType within the language aspect. |
||
6141 | * |
||
6142 | * If the language aspect has NO overlays enabled, it behaves as in "free mode" (= only fetch the records |
||
6143 | * for the current language. |
||
6144 | * |
||
6145 | * @param ExpressionBuilder $expressionBuilder |
||
6146 | * @param string $table |
||
6147 | * @param array $conf |
||
6148 | * @param Context $context |
||
6149 | * @return string|\TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression|null |
||
6150 | * @throws \TYPO3\CMS\Core\Context\Exception\AspectNotFoundException |
||
6151 | */ |
||
6152 | protected function getLanguageRestriction(ExpressionBuilder $expressionBuilder, string $table, array $conf, Context $context) |
||
6153 | { |
||
6154 | $languageField = ''; |
||
6155 | $localizationParentField = $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'] ?? null; |
||
6156 | // Check if the table is translatable, and set the language field by default from the TCA information |
||
6157 | if (!empty($conf['languageField']) || !isset($conf['languageField'])) { |
||
6158 | if (isset($conf['languageField']) && !empty($GLOBALS['TCA'][$table]['columns'][$conf['languageField']])) { |
||
6159 | $languageField = $conf['languageField']; |
||
6160 | } elseif (!empty($GLOBALS['TCA'][$table]['ctrl']['languageField']) && !empty($localizationParentField)) { |
||
6161 | $languageField = $table . '.' . $GLOBALS['TCA'][$table]['ctrl']['languageField']; |
||
6162 | } |
||
6163 | } |
||
6164 | |||
6165 | // No language restriction enabled explicitly or available via TCA |
||
6166 | if (empty($languageField)) { |
||
6167 | return null; |
||
6168 | } |
||
6169 | |||
6170 | /** @var LanguageAspect $languageAspect */ |
||
6171 | $languageAspect = $context->getAspect('language'); |
||
6172 | if ($languageAspect->doOverlays() && !empty($localizationParentField)) { |
||
6173 | // Sys language content is set to zero/-1 - and it is expected that whatever routine processes the output will |
||
6174 | // OVERLAY the records with localized versions! |
||
6175 | $languageQuery = $expressionBuilder->in($languageField, [0, -1]); |
||
6176 | // Use this option to include records that don't have a default language counterpart ("free mode") |
||
6177 | // (originalpointerfield is 0 and the language field contains the requested language) |
||
6178 | if (isset($conf['includeRecordsWithoutDefaultTranslation']) || !empty($conf['includeRecordsWithoutDefaultTranslation.'])) { |
||
6179 | $includeRecordsWithoutDefaultTranslation = isset($conf['includeRecordsWithoutDefaultTranslation.']) |
||
6180 | ? $this->stdWrap($conf['includeRecordsWithoutDefaultTranslation'], $conf['includeRecordsWithoutDefaultTranslation.']) |
||
6181 | : $conf['includeRecordsWithoutDefaultTranslation']; |
||
6182 | $includeRecordsWithoutDefaultTranslation = trim($includeRecordsWithoutDefaultTranslation) !== ''; |
||
6183 | } else { |
||
6184 | // Option was not explicitly set, check what's in for the language overlay type. |
||
6185 | $includeRecordsWithoutDefaultTranslation = $languageAspect->getOverlayType() === $languageAspect::OVERLAYS_ON_WITH_FLOATING; |
||
6186 | } |
||
6187 | if ($includeRecordsWithoutDefaultTranslation) { |
||
6188 | $languageQuery = $expressionBuilder->orX( |
||
6189 | $languageQuery, |
||
6190 | $expressionBuilder->andX( |
||
6191 | $expressionBuilder->eq($table . '.' . $localizationParentField, 0), |
||
6192 | $expressionBuilder->eq($languageField, $languageAspect->getContentId()) |
||
6193 | ) |
||
6194 | ); |
||
6195 | } |
||
6196 | return $languageQuery; |
||
6197 | } |
||
6198 | // No overlays = only fetch records given for the requested language and "all languages" |
||
6199 | return $expressionBuilder->in($languageField, [$languageAspect->getContentId(), -1]); |
||
6200 | } |
||
6201 | |||
6202 | /** |
||
6203 | * Helper function for getQuery, sanitizing the select part |
||
6204 | * |
||
6205 | * This functions checks if the necessary fields are part of the select |
||
6206 | * and adds them if necessary. |
||
6207 | * |
||
6208 | * @param string $selectPart Select part |
||
6209 | * @param string $table Table to select from |
||
6210 | * @return string Sanitized select part |
||
6211 | * @internal |
||
6212 | * @see getQuery |
||
6213 | */ |
||
6214 | protected function sanitizeSelectPart($selectPart, $table) |
||
6215 | { |
||
6216 | $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table); |
||
6217 | |||
6218 | // Pattern matching parts |
||
6219 | $matchStart = '/(^\\s*|,\\s*|' . $table . '\\.)'; |
||
6220 | $matchEnd = '(\\s*,|\\s*$)/'; |
||
6221 | $necessaryFields = ['uid', 'pid']; |
||
6222 | $wsFields = ['t3ver_state']; |
||
6223 | if (isset($GLOBALS['TCA'][$table]) && !preg_match($matchStart . '\\*' . $matchEnd, $selectPart) && !preg_match('/(count|max|min|avg|sum)\\([^\\)]+\\)|distinct/i', $selectPart)) { |
||
6224 | foreach ($necessaryFields as $field) { |
||
6225 | $match = $matchStart . $field . $matchEnd; |
||
6226 | if (!preg_match($match, $selectPart)) { |
||
6227 | $selectPart .= ', ' . $connection->quoteIdentifier($table . '.' . $field) . ' AS ' . $connection->quoteIdentifier($field); |
||
6228 | } |
||
6229 | } |
||
6230 | if ($GLOBALS['TCA'][$table]['ctrl']['versioningWS']) { |
||
6231 | foreach ($wsFields as $field) { |
||
6232 | $match = $matchStart . $field . $matchEnd; |
||
6233 | if (!preg_match($match, $selectPart)) { |
||
6234 | $selectPart .= ', ' . $connection->quoteIdentifier($table . '.' . $field) . ' AS ' . $connection->quoteIdentifier($field); |
||
6235 | } |
||
6236 | } |
||
6237 | } |
||
6238 | } |
||
6239 | return $selectPart; |
||
6240 | } |
||
6241 | |||
6242 | /** |
||
6243 | * Removes Page UID numbers from the input array which are not available due to enableFields() or the list of bad doktype numbers ($this->checkPid_badDoktypeList) |
||
6244 | * |
||
6245 | * @param int[] $pageIds Array of Page UID numbers for select and for which pages with enablefields and bad doktypes should be removed. |
||
6246 | * @return array Returns the array of remaining page UID numbers |
||
6247 | * @internal |
||
6248 | */ |
||
6249 | public function checkPidArray($pageIds) |
||
6250 | { |
||
6251 | if (!is_array($pageIds) || empty($pageIds)) { |
||
6252 | return []; |
||
6253 | } |
||
6254 | $restrictionContainer = GeneralUtility::makeInstance(FrontendRestrictionContainer::class); |
||
6255 | $restrictionContainer->add(GeneralUtility::makeInstance( |
||
6256 | DocumentTypeExclusionRestriction::class, |
||
6257 | GeneralUtility::intExplode(',', (string)$this->checkPid_badDoktypeList, true) |
||
6258 | )); |
||
6259 | return $this->getTypoScriptFrontendController()->sys_page->filterAccessiblePageIds($pageIds, $restrictionContainer); |
||
6260 | } |
||
6261 | |||
6262 | /** |
||
6263 | * Builds list of marker values for handling PDO-like parameter markers in select parts. |
||
6264 | * Marker values support stdWrap functionality thus allowing a way to use stdWrap functionality in various properties of 'select' AND prevents SQL-injection problems by quoting and escaping of numeric values, strings, NULL values and comma separated lists. |
||
6265 | * |
||
6266 | * @param string $table Table to select records from |
||
6267 | * @param array $conf Select part of CONTENT definition |
||
6268 | * @return array List of values to replace markers with |
||
6269 | * @internal |
||
6270 | * @see getQuery() |
||
6271 | */ |
||
6272 | public function getQueryMarkers($table, $conf) |
||
6273 | { |
||
6274 | if (!isset($conf['markers.']) || !is_array($conf['markers.'])) { |
||
6275 | return []; |
||
6276 | } |
||
6277 | // Parse markers and prepare their values |
||
6278 | $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table); |
||
6279 | $markerValues = []; |
||
6280 | foreach ($conf['markers.'] as $dottedMarker => $dummy) { |
||
6281 | $marker = rtrim($dottedMarker, '.'); |
||
6282 | if ($dottedMarker != $marker . '.') { |
||
6283 | continue; |
||
6284 | } |
||
6285 | // Parse definition |
||
6286 | // todo else value is always null |
||
6287 | $tempValue = isset($conf['markers.'][$dottedMarker]) |
||
6288 | ? $this->stdWrap($conf['markers.'][$dottedMarker]['value'], $conf['markers.'][$dottedMarker]) |
||
6289 | : $conf['markers.'][$dottedMarker]['value']; |
||
6290 | // Quote/escape if needed |
||
6291 | if (is_numeric($tempValue)) { |
||
6292 | if ((int)$tempValue == $tempValue) { |
||
6293 | // Handle integer |
||
6294 | $markerValues[$marker] = (int)$tempValue; |
||
6295 | } else { |
||
6296 | // Handle float |
||
6297 | $markerValues[$marker] = (float)$tempValue; |
||
6298 | } |
||
6299 | } elseif ($tempValue === null) { |
||
6300 | // It represents NULL |
||
6301 | $markerValues[$marker] = 'NULL'; |
||
6302 | } elseif (!empty($conf['markers.'][$dottedMarker]['commaSeparatedList'])) { |
||
6303 | // See if it is really a comma separated list of values |
||
6304 | $explodeValues = GeneralUtility::trimExplode(',', $tempValue); |
||
6305 | if (count($explodeValues) > 1) { |
||
6306 | // Handle each element of list separately |
||
6307 | $tempArray = []; |
||
6308 | foreach ($explodeValues as $listValue) { |
||
6309 | if (is_numeric($listValue)) { |
||
6310 | if ((int)$listValue == $listValue) { |
||
6311 | $tempArray[] = (int)$listValue; |
||
6312 | } else { |
||
6313 | $tempArray[] = (float)$listValue; |
||
6314 | } |
||
6315 | } else { |
||
6316 | // If quoted, remove quotes before |
||
6317 | // escaping. |
||
6318 | if (preg_match('/^\'([^\']*)\'$/', $listValue, $matches)) { |
||
6319 | $listValue = $matches[1]; |
||
6320 | } elseif (preg_match('/^\\"([^\\"]*)\\"$/', $listValue, $matches)) { |
||
6321 | $listValue = $matches[1]; |
||
6322 | } |
||
6323 | $tempArray[] = $connection->quote($listValue); |
||
6324 | } |
||
6325 | } |
||
6326 | $markerValues[$marker] = implode(',', $tempArray); |
||
6327 | } else { |
||
6328 | // Handle remaining values as string |
||
6329 | $markerValues[$marker] = $connection->quote($tempValue); |
||
6330 | } |
||
6331 | } else { |
||
6332 | // Handle remaining values as string |
||
6333 | $markerValues[$marker] = $connection->quote($tempValue); |
||
6334 | } |
||
6335 | } |
||
6336 | return $markerValues; |
||
6337 | } |
||
6338 | |||
6339 | /*********************************************** |
||
6340 | * |
||
6341 | * Frontend editing functions |
||
6342 | * |
||
6343 | ***********************************************/ |
||
6344 | /** |
||
6345 | * Generates the "edit panels" which can be shown for a page or records on a page when the Admin Panel is enabled for a backend users surfing the frontend. |
||
6346 | * With the "edit panel" the user will see buttons with links to editing, moving, hiding, deleting the element |
||
6347 | * This function is used for the cObject EDITPANEL and the stdWrap property ".editPanel" |
||
6348 | * |
||
6349 | * @param string $content A content string containing the content related to the edit panel. For cObject "EDITPANEL" this is empty but not so for the stdWrap property. The edit panel is appended to this string and returned. |
||
6350 | * @param array $conf TypoScript configuration properties for the editPanel |
||
6351 | * @param string $currentRecord The "table:uid" of the record being shown. If empty string then $this->currentRecord is used. For new records (set by $conf['newRecordFromTable']) it's auto-generated to "[tablename]:NEW |
||
6352 | * @param array $dataArray Alternative data array to use. Default is $this->data |
||
6353 | * @return string The input content string with the editPanel appended. This function returns only an edit panel appended to the content string if a backend user is logged in (and has the correct permissions). Otherwise the content string is directly returned. |
||
6354 | */ |
||
6355 | public function editPanel($content, $conf, $currentRecord = '', $dataArray = []) |
||
6356 | { |
||
6357 | if (!$this->getTypoScriptFrontendController()->isBackendUserLoggedIn()) { |
||
6358 | return $content; |
||
6359 | } |
||
6360 | if (!$this->getTypoScriptFrontendController()->displayEditIcons) { |
||
6361 | return $content; |
||
6362 | } |
||
6363 | |||
6364 | if (!$currentRecord) { |
||
6365 | $currentRecord = $this->currentRecord; |
||
6366 | } |
||
6367 | if (empty($dataArray)) { |
||
6368 | $dataArray = $this->data; |
||
6369 | } |
||
6370 | |||
6371 | if ($conf['newRecordFromTable']) { |
||
6372 | $currentRecord = $conf['newRecordFromTable'] . ':NEW'; |
||
6373 | $conf['allow'] = 'new'; |
||
6374 | $checkEditAccessInternals = false; |
||
6375 | } else { |
||
6376 | $checkEditAccessInternals = true; |
||
6377 | } |
||
6378 | [$table, $uid] = explode(':', $currentRecord); |
||
6379 | // Page ID for new records, 0 if not specified |
||
6380 | $newRecordPid = (int)$conf['newRecordInPid']; |
||
6381 | $newUid = null; |
||
6382 | if (!$conf['onlyCurrentPid'] || $dataArray['pid'] == $this->getTypoScriptFrontendController()->id) { |
||
6383 | if ($table === 'pages') { |
||
6384 | $newUid = $uid; |
||
6385 | } else { |
||
6386 | if ($conf['newRecordFromTable']) { |
||
6387 | $newUid = $this->getTypoScriptFrontendController()->id; |
||
6388 | if ($newRecordPid) { |
||
6389 | $newUid = $newRecordPid; |
||
6390 | } |
||
6391 | } else { |
||
6392 | $newUid = -1 * $uid; |
||
6393 | } |
||
6394 | } |
||
6395 | } |
||
6396 | if ($table && $this->getFrontendBackendUser()->allowedToEdit($table, $dataArray, $conf, $checkEditAccessInternals) && $this->getFrontendBackendUser()->allowedToEditLanguage($table, $dataArray)) { |
||
6397 | $editClass = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/classes/class.frontendedit.php']['edit']; |
||
6398 | if ($editClass) { |
||
6399 | $edit = GeneralUtility::makeInstance($editClass); |
||
6400 | $allowedActions = $this->getFrontendBackendUser()->getAllowedEditActions($table, $conf, $dataArray['pid']); |
||
6401 | $content = $edit->editPanel($content, $conf, $currentRecord, $dataArray, $table, $allowedActions, $newUid, []); |
||
6402 | } |
||
6403 | } |
||
6404 | return $content; |
||
6405 | } |
||
6406 | |||
6407 | /** |
||
6408 | * Adds an edit icon to the content string. The edit icon links to FormEngine with proper parameters for editing the table/fields of the context. |
||
6409 | * This implements TYPO3 context sensitive editing facilities. Only backend users will have access (if properly configured as well). |
||
6410 | * |
||
6411 | * @param string $content The content to which the edit icons should be appended |
||
6412 | * @param string $params The parameters defining which table and fields to edit. Syntax is [tablename]:[fieldname],[fieldname],[fieldname],... OR [fieldname],[fieldname],[fieldname],... (basically "[tablename]:" is optional, default table is the one of the "current record" used in the function). The fieldlist is sent as "&columnsOnly=" parameter to FormEngine |
||
6413 | * @param array $conf TypoScript properties for configuring the edit icons. |
||
6414 | * @param string $currentRecord The "table:uid" of the record being shown. If empty string then $this->currentRecord is used. For new records (set by $conf['newRecordFromTable']) it's auto-generated to "[tablename]:NEW |
||
6415 | * @param array $dataArray Alternative data array to use. Default is $this->data |
||
6416 | * @param string $addUrlParamStr Additional URL parameters for the link pointing to FormEngine |
||
6417 | * @return string The input content string, possibly with edit icons added (not necessarily in the end but just after the last string of normal content. |
||
6418 | */ |
||
6419 | public function editIcons($content, $params, array $conf = [], $currentRecord = '', $dataArray = [], $addUrlParamStr = '') |
||
6420 | { |
||
6421 | if (!$this->getTypoScriptFrontendController()->isBackendUserLoggedIn()) { |
||
6422 | return $content; |
||
6423 | } |
||
6424 | if (!$this->getTypoScriptFrontendController()->displayFieldEditIcons) { |
||
6425 | return $content; |
||
6426 | } |
||
6427 | if (!$currentRecord) { |
||
6428 | $currentRecord = $this->currentRecord; |
||
6429 | } |
||
6430 | if (empty($dataArray)) { |
||
6431 | $dataArray = $this->data; |
||
6432 | } |
||
6433 | // Check incoming params: |
||
6434 | [$currentRecordTable, $currentRecordUID] = explode(':', $currentRecord); |
||
6435 | [$fieldList, $table] = array_reverse(GeneralUtility::trimExplode(':', $params, true)); |
||
6436 | // Reverse the array because table is optional |
||
6437 | if (!$table) { |
||
6438 | $table = $currentRecordTable; |
||
6439 | } elseif ($table != $currentRecordTable) { |
||
6440 | // If the table is set as the first parameter, and does not match the table of the current record, then just return. |
||
6441 | return $content; |
||
6442 | } |
||
6443 | |||
6444 | $editUid = $dataArray['_LOCALIZED_UID'] ?: $currentRecordUID; |
||
6445 | // Edit icons imply that the editing action is generally allowed, assuming page and content element permissions permit it. |
||
6446 | if (!array_key_exists('allow', $conf)) { |
||
6447 | $conf['allow'] = 'edit'; |
||
6448 | } |
||
6449 | if ($table && $this->getFrontendBackendUser()->allowedToEdit($table, $dataArray, $conf, true) && $fieldList && $this->getFrontendBackendUser()->allowedToEditLanguage($table, $dataArray)) { |
||
6450 | $editClass = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/classes/class.frontendedit.php']['edit']; |
||
6451 | if ($editClass) { |
||
6452 | $edit = GeneralUtility::makeInstance($editClass); |
||
6453 | $content = $edit->editIcons($content, $params, $conf, $currentRecord, $dataArray, $addUrlParamStr, $table, $editUid, $fieldList); |
||
6454 | } |
||
6455 | } |
||
6456 | return $content; |
||
6457 | } |
||
6458 | |||
6459 | /** |
||
6460 | * Returns TRUE if the input table/row would be hidden in the frontend (according nto the current time and simulate user group) |
||
6461 | * |
||
6462 | * @param string $table The table name |
||
6463 | * @param array $row The data record |
||
6464 | * @return bool |
||
6465 | * @internal |
||
6466 | * @see editPanelPreviewBorder() |
||
6467 | */ |
||
6468 | public function isDisabled($table, $row) |
||
6469 | { |
||
6470 | $tsfe = $this->getTypoScriptFrontendController(); |
||
6471 | $enablecolumns = $GLOBALS['TCA'][$table]['ctrl']['enablecolumns']; |
||
6472 | return $enablecolumns['disabled'] && $row[$enablecolumns['disabled']] |
||
6473 | || $enablecolumns['fe_group'] && $tsfe->simUserGroup && (int)$row[$enablecolumns['fe_group']] === (int)$tsfe->simUserGroup |
||
6474 | || $enablecolumns['starttime'] && $row[$enablecolumns['starttime']] > $GLOBALS['EXEC_TIME'] |
||
6475 | || $enablecolumns['endtime'] && $row[$enablecolumns['endtime']] && $row[$enablecolumns['endtime']] < $GLOBALS['EXEC_TIME']; |
||
6476 | } |
||
6477 | |||
6478 | /** |
||
6479 | * Get instance of FAL resource factory |
||
6480 | * |
||
6481 | * @return ResourceFactory |
||
6482 | */ |
||
6483 | protected function getResourceFactory() |
||
6484 | { |
||
6485 | return GeneralUtility::makeInstance(ResourceFactory::class); |
||
6486 | } |
||
6487 | |||
6488 | /** |
||
6489 | * Wrapper function for GeneralUtility::getIndpEnv() |
||
6490 | * |
||
6491 | * @see GeneralUtility::getIndpEnv |
||
6492 | * @param string $key Name of the "environment variable"/"server variable" you wish to get. |
||
6493 | * @return string |
||
6494 | */ |
||
6495 | protected function getEnvironmentVariable($key) |
||
6496 | { |
||
6497 | if ($key === 'REQUEST_URI') { |
||
6498 | return $this->getRequest()->getAttribute('normalizedParams')->getRequestUri(); |
||
6499 | } |
||
6500 | return GeneralUtility::getIndpEnv($key); |
||
6501 | } |
||
6502 | |||
6503 | /** |
||
6504 | * Fetches content from cache |
||
6505 | * |
||
6506 | * @param array $configuration Array |
||
6507 | * @return string|bool FALSE on cache miss |
||
6508 | * @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException |
||
6509 | */ |
||
6510 | protected function getFromCache(array $configuration) |
||
6511 | { |
||
6512 | $content = false; |
||
6513 | |||
6514 | if ($this->getTypoScriptFrontendController()->no_cache) { |
||
6515 | return $content; |
||
6516 | } |
||
6517 | $cacheKey = $this->calculateCacheKey($configuration); |
||
6518 | if (!empty($cacheKey)) { |
||
6519 | /** @var \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface $cacheFrontend */ |
||
6520 | $cacheFrontend = GeneralUtility::makeInstance(CacheManager::class) |
||
6521 | ->getCache('hash'); |
||
6522 | $content = $cacheFrontend->get($cacheKey); |
||
6523 | } |
||
6524 | return $content; |
||
6525 | } |
||
6526 | |||
6527 | /** |
||
6528 | * Calculates the lifetime of a cache entry based on the given configuration |
||
6529 | * |
||
6530 | * @param array $configuration |
||
6531 | * @return int|null |
||
6532 | */ |
||
6533 | protected function calculateCacheLifetime(array $configuration) |
||
6534 | { |
||
6535 | $configuration['lifetime'] = $configuration['lifetime'] ?? ''; |
||
6536 | $lifetimeConfiguration = (string)$this->stdWrapValue('lifetime', $configuration); |
||
6537 | |||
6538 | $lifetime = null; // default lifetime |
||
6539 | if (strtolower($lifetimeConfiguration) === 'unlimited') { |
||
6540 | $lifetime = 0; // unlimited |
||
6541 | } elseif ($lifetimeConfiguration > 0) { |
||
6542 | $lifetime = (int)$lifetimeConfiguration; // lifetime in seconds |
||
6543 | } |
||
6544 | return $lifetime; |
||
6545 | } |
||
6546 | |||
6547 | /** |
||
6548 | * Calculates the tags for a cache entry bases on the given configuration |
||
6549 | * |
||
6550 | * @param array $configuration |
||
6551 | * @return array |
||
6552 | */ |
||
6553 | protected function calculateCacheTags(array $configuration) |
||
6554 | { |
||
6555 | $configuration['tags'] = $configuration['tags'] ?? ''; |
||
6556 | $tags = (string)$this->stdWrapValue('tags', $configuration); |
||
6557 | return empty($tags) ? [] : GeneralUtility::trimExplode(',', $tags); |
||
6558 | } |
||
6559 | |||
6560 | /** |
||
6561 | * Applies stdWrap to the cache key |
||
6562 | * |
||
6563 | * @param array $configuration |
||
6564 | * @return string |
||
6565 | */ |
||
6566 | protected function calculateCacheKey(array $configuration) |
||
6567 | { |
||
6568 | $configuration['key'] = $configuration['key'] ?? ''; |
||
6569 | return $this->stdWrapValue('key', $configuration); |
||
6570 | } |
||
6571 | |||
6572 | /** |
||
6573 | * Returns the current BE user. |
||
6574 | * |
||
6575 | * @return \TYPO3\CMS\Backend\FrontendBackendUserAuthentication |
||
6576 | */ |
||
6577 | protected function getFrontendBackendUser() |
||
6578 | { |
||
6579 | return $GLOBALS['BE_USER']; |
||
6580 | } |
||
6581 | |||
6582 | /** |
||
6583 | * @return TimeTracker |
||
6584 | */ |
||
6585 | protected function getTimeTracker() |
||
6586 | { |
||
6587 | return GeneralUtility::makeInstance(TimeTracker::class); |
||
6588 | } |
||
6589 | |||
6590 | /** |
||
6591 | * @return \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController |
||
6592 | */ |
||
6593 | protected function getTypoScriptFrontendController() |
||
6594 | { |
||
6595 | return $this->typoScriptFrontendController ?: $GLOBALS['TSFE'] ?? null; |
||
6596 | } |
||
6597 | |||
6598 | /** |
||
6599 | * Support anchors without href value |
||
6600 | * Changes ContentObjectRenderer::typolink to render a tag without href, |
||
6601 | * if id or name attribute is present. |
||
6602 | * |
||
6603 | * @param string $linkText |
||
6604 | * @param array $conf Typolink configuration decoded as array |
||
6605 | * @return string Full a-Tag or just the linktext if id or name are not set. |
||
6606 | */ |
||
6607 | protected function resolveAnchorLink(string $linkText, array $conf): string |
||
6617 | } |
||
6618 | |||
6619 | /** |
||
6620 | * Get content length of the current tag that could also contain nested tag contents |
||
6621 | * |
||
6622 | * @param string $theValue |
||
6623 | * @param int $pointer |
||
6624 | * @param string $currentTag |
||
6625 | * @return int |
||
6626 | */ |
||
6627 | protected function getContentLengthOfCurrentTag(string $theValue, int $pointer, string $currentTag): int |
||
6628 | { |
||
6629 | $tempContent = strtolower(substr($theValue, $pointer)); |
||
6630 | $startTag = '<' . $currentTag; |
||
6631 | $endTag = '</' . $currentTag . '>'; |
||
6632 | $offsetCount = 0; |
||
6633 | |||
6634 | // Take care for nested tags |
||
6635 | do { |
||
6636 | $nextMatchingEndTagPosition = strpos($tempContent, $endTag); |
||
6637 | // only match tag `a` in `<a href"...">` but not in `<abbr>` |
||
6638 | $nextSameTypeTagPosition = preg_match( |
||
6639 | '#' . $startTag . '[\s/>]#', |
||
6640 | $tempContent, |
||
6641 | $nextSameStartTagMatches, |
||
6642 | PREG_OFFSET_CAPTURE |
||
6643 | ) ? $nextSameStartTagMatches[0][1] : false; |
||
6644 | |||
6645 | // filter out nested tag contents to help getting the correct closing tag |
||
6646 | if ($nextMatchingEndTagPosition !== false && $nextSameTypeTagPosition !== false && $nextSameTypeTagPosition < $nextMatchingEndTagPosition) { |
||
6647 | $lastOpeningTagStartPosition = (int)strrpos(substr($tempContent, 0, $nextMatchingEndTagPosition), $startTag); |
||
6648 | $closingTagEndPosition = $nextMatchingEndTagPosition + strlen($endTag); |
||
6649 | $offsetCount += $closingTagEndPosition - $lastOpeningTagStartPosition; |
||
6650 | |||
6651 | // replace content from latest tag start to latest tag end |
||
6652 | $tempContent = substr($tempContent, 0, $lastOpeningTagStartPosition) . substr($tempContent, $closingTagEndPosition); |
||
6653 | } |
||
6654 | } while ( |
||
6655 | ($nextMatchingEndTagPosition !== false && $nextSameTypeTagPosition !== false) && |
||
6656 | $nextSameTypeTagPosition < $nextMatchingEndTagPosition |
||
6657 | ); |
||
6658 | |||
6659 | // if no closing tag is found we use length of the whole content |
||
6660 | $endingOffset = strlen($tempContent); |
||
6661 | if ($nextMatchingEndTagPosition !== false) { |
||
6662 | $endingOffset = $nextMatchingEndTagPosition + $offsetCount; |
||
6663 | } |
||
6664 | |||
6665 | return $endingOffset; |
||
6666 | } |
||
6667 | |||
6668 | public function getRequest(): ServerRequestInterface |
||
6679 | } |
||
6680 | } |
||
6681 |