[TASK] Use strict comparison for strings
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / TypoScript / ExtendedTemplateService.php
1 <?php
2 namespace TYPO3\CMS\Core\TypoScript;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Backend\Template\DocumentTemplate;
18 use TYPO3\CMS\Backend\Utility\BackendUtility;
19 use TYPO3\CMS\Core\Database\ConnectionPool;
20 use TYPO3\CMS\Core\Database\Query\QueryBuilder;
21 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
22 use TYPO3\CMS\Core\Exception;
23 use TYPO3\CMS\Core\Imaging\Icon;
24 use TYPO3\CMS\Core\Imaging\IconFactory;
25 use TYPO3\CMS\Core\Utility\GeneralUtility;
26 use TYPO3\CMS\Core\Utility\MathUtility;
27 use TYPO3\CMS\Frontend\Configuration\TypoScript\ConditionMatching\ConditionMatcher;
28 use TYPO3\CMS\Lang\LanguageService;
29
30 /**
31 * TSParser extension class to TemplateService
32 * Contains functions for the TS module in TYPO3 backend
33 */
34 class ExtendedTemplateService extends TemplateService
35 {
36 /**
37 * This string is used to indicate the point in a template from where the editable constants are listed.
38 * Any vars before this point (if it exists though) is regarded as default values.
39 *
40 * @var string
41 */
42 public $edit_divider = '###MOD_TS:EDITABLE_CONSTANTS###';
43
44 /**
45 * Disabled in backend context
46 *
47 * @var bool
48 */
49 public $tt_track = false;
50
51 /**
52 * @var array
53 */
54 public $categories = [
55 'basic' => [],
56 // Constants of superior importance for the template-layout. This is dimensions, imagefiles and enabling of various features. The most basic constants, which you would almost always want to configure.
57 'menu' => [],
58 // Menu setup. This includes fontfiles, sizes, background images. Depending on the menutype.
59 'content' => [],
60 // All constants related to the display of pagecontent elements
61 'page' => [],
62 // General configuration like metatags, link targets
63 'advanced' => [],
64 // Advanced functions, which are used very seldomly.
65 'all' => []
66 ];
67
68 /**
69 * Translated categories
70 *
71 * @var array
72 */
73 protected $categoryLabels = [];
74
75 /**
76 * This will be filled with the available categories of the current template.
77 *
78 * @var array
79 */
80 public $subCategories = [
81 // Standard categories:
82 'enable' => ['Enable features', 'a'],
83 'dims' => ['Dimensions, widths, heights, pixels', 'b'],
84 'file' => ['Files', 'c'],
85 'typo' => ['Typography', 'd'],
86 'color' => ['Colors', 'e'],
87 'links' => ['Links and targets', 'f'],
88 'language' => ['Language specific constants', 'g'],
89 // subcategories based on the default content elements
90 'cheader' => ['Content: \'Header\'', 'ma'],
91 'cheader_g' => ['Content: \'Header\', Graphical', 'ma'],
92 'ctext' => ['Content: \'Text\'', 'mb'],
93 'cimage' => ['Content: \'Image\'', 'md'],
94 'ctextmedia' => ['Content: \'Textmedia\'', 'ml'],
95 'cbullets' => ['Content: \'Bullet list\'', 'me'],
96 'ctable' => ['Content: \'Table\'', 'mf'],
97 'cuploads' => ['Content: \'Filelinks\'', 'mg'],
98 'cmultimedia' => ['Content: \'Multimedia\'', 'mh'],
99 'cmedia' => ['Content: \'Media\'', 'mr'],
100 'cmailform' => ['Content: \'Form\'', 'mi'],
101 'csearch' => ['Content: \'Search\'', 'mj'],
102 'clogin' => ['Content: \'Login\'', 'mk'],
103 'cmenu' => ['Content: \'Menu/Sitemap\'', 'mm'],
104 'cshortcut' => ['Content: \'Insert records\'', 'mn'],
105 'clist' => ['Content: \'List of records\'', 'mo'],
106 'chtml' => ['Content: \'HTML\'', 'mq']
107 ];
108
109 /**
110 * Tsconstanteditor
111 *
112 * @var int
113 */
114 public $ext_inBrace = 0;
115
116 /**
117 * Tsbrowser
118 *
119 * @var array
120 */
121 public $tsbrowser_searchKeys = [];
122
123 /**
124 * @var array
125 */
126 public $tsbrowser_depthKeys = [];
127
128 /**
129 * @var string
130 */
131 public $constantMode = '';
132
133 /**
134 * @var string
135 */
136 public $regexMode = '';
137
138 /**
139 * @var string
140 */
141 public $fixedLgd = '';
142
143 /**
144 * @var int
145 */
146 public $ext_lineNumberOffset = 0;
147
148 /**
149 * @var int
150 */
151 public $ext_expandAllNotes = 0;
152
153 /**
154 * @var int
155 */
156 public $ext_noPMicons = 0;
157
158 /**
159 * @var array
160 */
161 public $ext_listOfTemplatesArr = [];
162
163 /**
164 * @var string
165 */
166 public $ext_lineNumberOffset_mode = '';
167
168 /**
169 * Don't change
170 *
171 * @var int
172 */
173 public $ext_dontCheckIssetValues = 0;
174
175 /**
176 * @var int
177 */
178 public $ext_printAll = 0;
179
180 /**
181 * @var string
182 */
183 public $ext_CEformName = 'forms[0]';
184
185 /**
186 * @var bool
187 */
188 public $doNotSortCategoriesBeforeMakingForm = false;
189
190 /**
191 * Ts analyzer
192 *
193 * @var array
194 */
195 public $templateTitles = [];
196
197 /**
198 * @var array|NULL
199 */
200 protected $lnToScript = null;
201
202 /**
203 * @var array
204 */
205 public $clearList_const_temp;
206
207 /**
208 * @var array
209 */
210 public $clearList_setup_temp;
211
212 /**
213 * @var string
214 */
215 protected $Cmarker = '';
216
217 /**
218 * @var string
219 */
220 public $bType = '';
221
222 /**
223 * @var bool
224 */
225 public $linkObjects = false;
226
227 /**
228 * @var array
229 */
230 public $helpConfig = [];
231
232 /**
233 * @var bool
234 */
235 public $changed = false;
236
237 /**
238 * @var int[]
239 */
240 protected $objReg = [];
241
242 /**
243 * @var array
244 */
245 public $raw = [];
246
247 /**
248 * @var int
249 */
250 public $rawP = 0;
251
252 /**
253 * @var string
254 */
255 public $lastComment = '';
256
257 /**
258 * @var array
259 */
260 protected $inlineJavaScript = [];
261
262 /**
263 * Gets the inline JavaScript.
264 *
265 * @return array
266 */
267 public function getInlineJavaScript()
268 {
269 return $this->inlineJavaScript;
270 }
271
272 /**
273 * Substitute constant
274 *
275 * @param string $all
276 * @return string
277 */
278 public function substituteConstants($all)
279 {
280 $this->Cmarker = substr(md5(uniqid('', true)), 0, 6);
281 return preg_replace_callback('/\\{\\$(.[^}]+)\\}/', [$this, 'substituteConstantsCallBack'], $all);
282 }
283
284 /**
285 * Call back method for preg_replace_callback in substituteConstants
286 *
287 * @param array $matches Regular expression matches
288 * @return string Replacement
289 * @see substituteConstants()
290 */
291 public function substituteConstantsCallBack($matches)
292 {
293 switch ($this->constantMode) {
294 case 'const':
295 $ret_val = isset($this->flatSetup[$matches[1]]) && !is_array($this->flatSetup[$matches[1]]) ? '##' . $this->Cmarker . '_B##' . $matches[0] . '##' . $this->Cmarker . '_E##' : $matches[0];
296 break;
297 case 'subst':
298 $ret_val = isset($this->flatSetup[$matches[1]]) && !is_array($this->flatSetup[$matches[1]]) ? '##' . $this->Cmarker . '_B##' . $this->flatSetup[$matches[1]] . '##' . $this->Cmarker . '_E##' : $matches[0];
299 break;
300 case 'untouched':
301 $ret_val = $matches[0];
302 break;
303 default:
304 $ret_val = isset($this->flatSetup[$matches[1]]) && !is_array($this->flatSetup[$matches[1]]) ? $this->flatSetup[$matches[1]] : $matches[0];
305 }
306 return $ret_val;
307 }
308
309 /**
310 * Subsitute markers
311 *
312 * @param string $all
313 * @return string
314 */
315 public function substituteCMarkers($all)
316 {
317 switch ($this->constantMode) {
318 case 'const':
319 case 'subst':
320 $all = str_replace(
321 ['##' . $this->Cmarker . '_B##', '##' . $this->Cmarker . '_E##'],
322 ['<strong style="color: green;">', '</strong>'],
323 $all
324 );
325 break;
326 default:
327 }
328 return $all;
329 }
330
331 /**
332 * Parse constants with respect to the constant-editor in this module.
333 * In particular comments in the code are registered and the edit_divider is taken into account.
334 *
335 * @return array
336 */
337 public function generateConfig_constants()
338 {
339 // These vars are also set lateron...
340 $this->setup['sitetitle'] = $this->sitetitle;
341 // Parse constants
342 $constants = GeneralUtility::makeInstance(Parser\TypoScriptParser::class);
343 // Register comments!
344 $constants->regComments = 1;
345 $constants->setup = $this->mergeConstantsFromPageTSconfig([]);
346 /** @var ConditionMatcher $matchObj */
347 $matchObj = GeneralUtility::makeInstance(ConditionMatcher::class);
348 // Matches ALL conditions in TypoScript
349 $matchObj->setSimulateMatchResult(true);
350 $c = 0;
351 $cc = count($this->constants);
352 $defaultConstants = [];
353 foreach ($this->constants as $str) {
354 $c++;
355 if ($c == $cc) {
356 if (strstr($str, $this->edit_divider)) {
357 $parts = explode($this->edit_divider, $str, 2);
358 $str = $parts[1];
359 $constants->parse($parts[0], $matchObj);
360 }
361 $this->flatSetup = [];
362 $this->flattenSetup($constants->setup, '');
363 $defaultConstants = $this->flatSetup;
364 }
365 $constants->parse($str, $matchObj);
366 }
367 $this->flatSetup = [];
368 $this->flattenSetup($constants->setup, '');
369 $this->setup['constants'] = $constants->setup;
370 return $this->ext_compareFlatSetups($defaultConstants);
371 }
372
373 /**
374 * @param array $theSetup
375 * @param string $theKey
376 * @return array
377 */
378 public function ext_getSetup($theSetup, $theKey)
379 {
380 $parts = explode('.', $theKey, 2);
381 if ((string)$parts[0] !== '' && is_array($theSetup[$parts[0] . '.'])) {
382 if (trim($parts[1]) !== '') {
383 return $this->ext_getSetup($theSetup[$parts[0] . '.'], trim($parts[1]));
384 } else {
385 return [$theSetup[$parts[0] . '.'], $theSetup[$parts[0]]];
386 }
387 } else {
388 if (trim($theKey) !== '') {
389 return [[], $theSetup[$theKey]];
390 } else {
391 return [$theSetup, ''];
392 }
393 }
394 }
395
396 /**
397 * Get object tree
398 *
399 * @param array $arr
400 * @param string $depth_in
401 * @param string $depthData
402 * @param string $parentType (unused)
403 * @param string $parentValue (unused)
404 * @param string $alphaSort sorts the array keys / tree by alphabet when set to 1
405 * @return array
406 */
407 public function ext_getObjTree($arr, $depth_in, $depthData, $parentType = '', $parentValue = '', $alphaSort = '0')
408 {
409 $HTML = '';
410 if ($alphaSort == '1') {
411 ksort($arr);
412 }
413 $keyArr_num = [];
414 $keyArr_alpha = [];
415 foreach ($arr as $key => $value) {
416 // Don't do anything with comments / linenumber registrations...
417 if (substr($key, -2) !== '..') {
418 $key = preg_replace('/\\.$/', '', $key);
419 if (substr($key, -1) !== '.') {
420 if (MathUtility::canBeInterpretedAsInteger($key)) {
421 $keyArr_num[$key] = $arr[$key];
422 } else {
423 $keyArr_alpha[$key] = $arr[$key];
424 }
425 }
426 }
427 }
428 ksort($keyArr_num);
429 $keyArr = $keyArr_num + $keyArr_alpha;
430 if ($depth_in) {
431 $depth_in = $depth_in . '.';
432 }
433 foreach ($keyArr as $key => $value) {
434 $depth = $depth_in . $key;
435 // This excludes all constants starting with '_' from being shown.
436 if ($this->bType !== 'const' || $depth[0] !== '_') {
437 $goto = substr(md5($depth), 0, 6);
438 $deeper = is_array($arr[$key . '.']) && ($this->tsbrowser_depthKeys[$depth] || $this->ext_expandAllNotes) ? 1 : 0;
439 $PM = is_array($arr[$key . '.']) && !$this->ext_noPMicons ? ($deeper ? 'minus' : 'plus') : 'join';
440 $HTML .= $depthData . '<li>';
441 if ($PM !== 'join') {
442 $urlParameters = [
443 'id' => $GLOBALS['SOBE']->id,
444 'tsbr[' . $depth . ']' => $deeper ? 0 : 1
445 ];
446 if (GeneralUtility::_GP('breakPointLN')) {
447 $urlParameters['breakPointLN'] = GeneralUtility::_GP('breakPointLN');
448 }
449 $aHref = BackendUtility::getModuleUrl('web_ts', $urlParameters) . '#' . $goto;
450 $HTML .= '<a class="list-tree-control' . ($PM === 'minus' ? ' list-tree-control-open' : ' list-tree-control-closed') . '" name="' . $goto . '" href="' . htmlspecialchars($aHref) . '"><i class="fa"></i></a>';
451 }
452 $label = $key;
453 // Read only...
454 if (($depth === 'types' || $depth === 'resources' || $depth === 'sitetitle') && $this->bType === 'setup') {
455 $label = '<span style="color: #666666;">' . $label . '</span>';
456 } else {
457 if ($this->linkObjects) {
458 $urlParameters = [
459 'id' => $GLOBALS['SOBE']->id,
460 'sObj' => $depth
461 ];
462 if (GeneralUtility::_GP('breakPointLN')) {
463 $urlParameters['breakPointLN'] = GeneralUtility::_GP('breakPointLN');
464 }
465 $aHref = BackendUtility::getModuleUrl('web_ts', $urlParameters);
466 if ($this->bType !== 'const') {
467 $ln = is_array($arr[$key . '.ln..']) ? 'Defined in: ' . $this->lineNumberToScript($arr[$key . '.ln..']) : 'N/A';
468 } else {
469 $ln = '';
470 }
471 if ($this->tsbrowser_searchKeys[$depth] & 4) {
472 $label = '<strong class="text-danger">' . $label . '</strong>';
473 }
474 // The key has matched the search string
475 $label = '<a href="' . htmlspecialchars($aHref) . '" title="' . htmlspecialchars($ln) . '">' . $label . '</a>';
476 }
477 }
478 $HTML .= '
479 <span class="list-tree-group">
480 <span class="list-tree-label">[' . $label . ']</span>';
481 if (isset($arr[$key])) {
482 $theValue = $arr[$key];
483 if ($this->fixedLgd) {
484 $imgBlocks = ceil(1 + strlen($depthData) / 77);
485 $lgdChars = 68 - ceil(strlen(('[' . $key . ']')) * 0.8) - $imgBlocks * 3;
486 $theValue = $this->ext_fixed_lgd($theValue, $lgdChars);
487 }
488 // The value has matched the search string
489 if ($this->tsbrowser_searchKeys[$depth] & 2) {
490 $HTML .= ' = <span class="list-tree-value text-danger">' . htmlspecialchars($theValue) . '</span>';
491 } else {
492 $HTML .= ' = <span class="list-tree-value">' . htmlspecialchars($theValue) . '</span>';
493 }
494 if ($this->ext_regComments && isset($arr[$key . '..'])) {
495 $comment = $arr[$key . '..'];
496 // Skip INCLUDE_TYPOSCRIPT comments, they are almost useless
497 if (!preg_match('/### <INCLUDE_TYPOSCRIPT:.*/', $comment)) {
498 // Remove linebreaks, replace with ' '
499 $comment = preg_replace('/[\\r\\n]/', ' ', $comment);
500 // Remove # and * if more than twice in a row
501 $comment = preg_replace('/[#\\*]{2,}/', '', $comment);
502 // Replace leading # (just if it exists) and add it again. Result: Every comment should be prefixed by a '#'.
503 $comment = preg_replace('/^[#\\*\\s]+/', '# ', $comment);
504 // Masking HTML Tags: Replace < with &lt; and > with &gt;
505 $comment = htmlspecialchars($comment);
506 $HTML .= ' <i class="text-muted">' . trim($comment) . '</i>';
507 }
508 }
509 }
510 $HTML .= '</span>';
511 if ($deeper) {
512 $HTML .= $this->ext_getObjTree($arr[$key . '.'], $depth, $depthData, '', $arr[$key], $alphaSort);
513 }
514 }
515 }
516 if ($HTML !== '') {
517 $HTML = '<ul class="list-tree text-monospace">' . $HTML . '</ul>';
518 }
519
520 return $HTML;
521 }
522
523 /**
524 * Find the originating template name for an array of line numbers (TypoScript setup only!)
525 * Given an array of linenumbers the method will try to find the corresponding template where this line originated
526 * The linenumber indicates the *last* lineNumber that is part of the template
527 *
528 * lineNumbers are in sync with the calculated lineNumbers '.ln..' in TypoScriptParser
529 *
530 * @param array $lnArr Array with linenumbers (might have some extra symbols, for example for unsetting) to be processed
531 * @return array The same array where each entry has been prepended by the template title if available
532 */
533 public function lineNumberToScript(array $lnArr)
534 {
535 // On the first call, construct the lnToScript array.
536 if (!is_array($this->lnToScript)) {
537 $this->lnToScript = [];
538
539 // aggregatedTotalLineCount
540 $c = 0;
541 foreach ($this->hierarchyInfo as $templateNumber => $info) {
542 // hierarchyInfo has the number of lines in configLines, but unfortunatly this value
543 // was calculated *before* processing of any INCLUDE instructions
544 // for some yet unknown reason we have to add an extra +2 offset
545 $linecountAfterIncludeProcessing = substr_count($this->config[$templateNumber], LF) + 2;
546 $c += $linecountAfterIncludeProcessing;
547 $this->lnToScript[$c] = $info['title'];
548 }
549 }
550
551 foreach ($lnArr as $k => $ln) {
552 foreach ($this->lnToScript as $endLn => $title) {
553 if ($endLn >= (int)$ln) {
554 $lnArr[$k] = '"' . $title . '", ' . $ln;
555 break;
556 }
557 }
558 }
559
560 return implode('; ', $lnArr);
561 }
562
563 /**
564 * @param array $arr
565 * @param string $depth_in
566 * @param string $searchString
567 * @param array $keyArray
568 * @return array
569 * @throws Exception
570 */
571 public function ext_getSearchKeys($arr, $depth_in, $searchString, $keyArray)
572 {
573 $keyArr = [];
574 foreach ($arr as $key => $value) {
575 $key = preg_replace('/\\.$/', '', $key);
576 if (substr($key, -1) !== '.') {
577 $keyArr[$key] = 1;
578 }
579 }
580 if ($depth_in) {
581 $depth_in = $depth_in . '.';
582 }
583 $searchPattern = '';
584 if ($this->regexMode) {
585 $searchPattern = '/' . addcslashes($searchString, '/') . '/';
586 $matchResult = @preg_match($searchPattern, '');
587 if ($matchResult === false) {
588 throw new Exception(sprintf('Error evaluating regular expression "%s".', $searchPattern), 1446559458);
589 }
590 }
591 foreach ($keyArr as $key => $value) {
592 $depth = $depth_in . $key;
593 $deeper = is_array($arr[$key . '.']);
594 if ($this->regexMode) {
595 // The value has matched
596 if (preg_match($searchPattern, $arr[$key])) {
597 $this->tsbrowser_searchKeys[$depth] += 2;
598 }
599 // The key has matched
600 if (preg_match($searchPattern, $key)) {
601 $this->tsbrowser_searchKeys[$depth] += 4;
602 }
603 // Just open this subtree if the parent key has matched the search
604 if (preg_match($searchPattern, $depth_in)) {
605 $this->tsbrowser_searchKeys[$depth] = 1;
606 }
607 } else {
608 // The value has matched
609 if (stristr($arr[$key], $searchString)) {
610 $this->tsbrowser_searchKeys[$depth] += 2;
611 }
612 // The key has matches
613 if (stristr($key, $searchString)) {
614 $this->tsbrowser_searchKeys[$depth] += 4;
615 }
616 // Just open this subtree if the parent key has matched the search
617 if (stristr($depth_in, $searchString)) {
618 $this->tsbrowser_searchKeys[$depth] = 1;
619 }
620 }
621 if ($deeper) {
622 $cS = count($this->tsbrowser_searchKeys);
623 $keyArray = $this->ext_getSearchKeys($arr[$key . '.'], $depth, $searchString, $keyArray);
624 if ($cS != count($this->tsbrowser_searchKeys)) {
625 $keyArray[$depth] = 1;
626 }
627 }
628 }
629 return $keyArray;
630 }
631
632 /**
633 * @param int $pid
634 * @return int
635 */
636 public function ext_getRootlineNumber($pid)
637 {
638 if ($pid) {
639 foreach ($this->getRootLine() as $key => $val) {
640 if ((int)$val['uid'] === (int)$pid) {
641 return (int)$key;
642 }
643 }
644 }
645 return -1;
646 }
647
648 /**
649 * @param array $arr
650 * @param string $depthData
651 * @param array $keyArray
652 * @param int $first
653 * @return array
654 */
655 public function ext_getTemplateHierarchyArr($arr, $depthData, $keyArray, $first = 0)
656 {
657 $keyArr = [];
658 foreach ($arr as $key => $value) {
659 $key = preg_replace('/\\.$/', '', $key);
660 if (substr($key, -1) !== '.') {
661 $keyArr[$key] = 1;
662 }
663 }
664 $a = 0;
665 $c = count($keyArr);
666 /** @var IconFactory $iconFactory */
667 $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
668 foreach ($keyArr as $key => $value) {
669 $HTML = '';
670 $a++;
671 $deeper = is_array($arr[$key . '.']);
672 $row = $arr[$key];
673 $LN = $a == $c ? 'blank' : 'line';
674 $BTM = $a == $c ? 'top' : '';
675 $HTML .= $depthData;
676 $alttext = '[' . $row['templateID'] . ']';
677 $alttext .= $row['pid'] ? ' - ' . BackendUtility::getRecordPath($row['pid'], $GLOBALS['SOBE']->perms_clause, 20) : '';
678 $icon = substr($row['templateID'], 0, 3) === 'sys'
679 ? '<span title="' . htmlspecialchars($alttext) . '">' . $iconFactory->getIconForRecord('sys_template', $row, Icon::SIZE_SMALL)->render() . '</span>'
680 : '<span title="' . htmlspecialchars($alttext) . '">' . $iconFactory->getIcon('mimetypes-x-content-template-static', Icon::SIZE_SMALL)->render() . '</span>';
681 if (in_array($row['templateID'], $this->clearList_const) || in_array($row['templateID'], $this->clearList_setup)) {
682 $urlParameters = [
683 'id' => $GLOBALS['SOBE']->id,
684 'template' => $row['templateID']
685 ];
686 $aHref = BackendUtility::getModuleUrl('web_ts', $urlParameters);
687 $A_B = '<a href="' . htmlspecialchars($aHref) . '">';
688 $A_E = '</a>';
689 if (GeneralUtility::_GP('template') == $row['templateID']) {
690 $A_B = '<strong>' . $A_B;
691 $A_E .= '</strong>';
692 }
693 } else {
694 $A_B = '';
695 $A_E = '';
696 }
697 $HTML .= ($first ? '' : '<span class="treeline-icon treeline-icon-join' . $BTM . '"></span>') . $icon . ' ' . $A_B
698 . htmlspecialchars(GeneralUtility::fixed_lgd_cs($row['title'], $GLOBALS['BE_USER']->uc['titleLen']))
699 . $A_E . '&nbsp;&nbsp;';
700 $RL = $this->ext_getRootlineNumber($row['pid']);
701 $statusCheckedIcon = $iconFactory->getIcon('status-status-checked', Icon::SIZE_SMALL)->render();
702 $keyArray[] = '<tr>
703 <td nowrap="nowrap">' . $HTML . '</td>
704 <td align="center">' . ($row['root'] ? $statusCheckedIcon : '') . '</td>
705 <td align="center">' . ($row['clConf'] ? $statusCheckedIcon : '') . '</td>
706 <td align="center">' . ($row['clConst'] ? $statusCheckedIcon : '') . '</td>
707 <td align="center">' . ($row['pid'] ?: '') . '</td>
708 <td align="center">' . ($RL >= 0 ? $RL : '') . '</td>
709 <td>' . ($row['next'] ? $row['next'] : '') . '</td>
710 </tr>';
711 if ($deeper) {
712 $keyArray = $this->ext_getTemplateHierarchyArr($arr[$key . '.'], $depthData . ($first ? '' : '<span class="treeline-icon treeline-icon-' . $LN . '"></span>'), $keyArray);
713 }
714 }
715 return $keyArray;
716 }
717
718 /**
719 * Processes the flat array from TemplateService->hierarchyInfo
720 * and turns it into a hierachical array to show dependencies (used by TemplateAnalyzer)
721 *
722 * @param array $depthDataArr (empty array on external call)
723 * @param int &$pointer Element number (1! to count()) of $this->hierarchyInfo that should be processed.
724 * @return array Processed hierachyInfo.
725 */
726 public function ext_process_hierarchyInfo(array $depthDataArr, &$pointer)
727 {
728 $parent = $this->hierarchyInfo[$pointer - 1]['templateParent'];
729 while ($pointer > 0 && $this->hierarchyInfo[$pointer - 1]['templateParent'] == $parent) {
730 $pointer--;
731 $row = $this->hierarchyInfo[$pointer];
732 $depthDataArr[$row['templateID']] = $row;
733 unset($this->clearList_setup_temp[$row['templateID']]);
734 unset($this->clearList_const_temp[$row['templateID']]);
735 $this->templateTitles[$row['templateID']] = $row['title'];
736 if ($row['templateID'] == $this->hierarchyInfo[$pointer - 1]['templateParent']) {
737 $depthDataArr[$row['templateID'] . '.'] = $this->ext_process_hierarchyInfo([], $pointer);
738 }
739 }
740 return $depthDataArr;
741 }
742
743 /**
744 * Get formatted HTML output for TypoScript either with Syntaxhiglighting or in plain mode
745 *
746 * @param array $config Array with simple strings of typoscript code.
747 * @param bool $lineNumbers Prepend linNumbers to each line.
748 * @param bool $comments Enable including comments in output.
749 * @param bool $crop Enable cropping of long lines.
750 * @param bool $syntaxHL Enrich output with syntaxhighlighting.
751 * @param int $syntaxHLBlockmode
752 * @return string
753 */
754 public function ext_outputTS(
755 array $config, $lineNumbers = false, $comments = false, $crop = false, $syntaxHL = false, $syntaxHLBlockmode = 0
756 ) {
757 $all = '';
758 foreach ($config as $str) {
759 $all .= '[GLOBAL]' . LF . $str;
760 }
761 if ($syntaxHL) {
762 $tsparser = GeneralUtility::makeInstance(Parser\TypoScriptParser::class);
763 $tsparser->lineNumberOffset = $this->ext_lineNumberOffset + 1;
764 $tsparser->parentObject = $this;
765 return $tsparser->doSyntaxHighlight($all, $lineNumbers ? [$this->ext_lineNumberOffset + 1] : '', $syntaxHLBlockmode);
766 } else {
767 return $this->ext_formatTS($all, $lineNumbers, $comments, $crop);
768 }
769 }
770
771 /**
772 * Returns a new string of max. $chars length
773 * If the string is longer, it will be truncated and prepended with '...'
774 * $chars must be an integer of at least 4
775 *
776 * @param string $string
777 * @param int $chars
778 * @return string
779 */
780 public function ext_fixed_lgd($string, $chars)
781 {
782 if ($chars >= 4) {
783 if (strlen($string) > $chars) {
784 if (strlen($string) > 24 && substr($string, 0, 12) === '##' . $this->Cmarker . '_B##') {
785 return '##' . $this->Cmarker . '_B##' . GeneralUtility::fixed_lgd_cs(substr($string, 12, -12), ($chars - 3))
786 . '##' . $this->Cmarker . '_E##';
787 } else {
788 return GeneralUtility::fixed_lgd_cs($string, $chars - 3);
789 }
790 }
791 }
792 return $string;
793 }
794
795 /**
796 * @param int $lineNumber Line Number
797 * @param array $str
798 * @return string
799 */
800 public function ext_lnBreakPointWrap($lineNumber, $str)
801 {
802 return '<a href="#" id="line-' . $lineNumber . '" onClick="return brPoint(' . $lineNumber . ','
803 . ($this->ext_lineNumberOffset_mode === 'setup' ? 1 : 0) . ');">' . $str . '</a>';
804 }
805
806 /**
807 * @param string $input
808 * @param bool $ln
809 * @param bool $comments
810 * @param bool $crop
811 * @return string
812 */
813 public function ext_formatTS($input, $ln, $comments = true, $crop = false)
814 {
815 $cArr = explode(LF, $input);
816 $n = ceil(log10(count($cArr) + $this->ext_lineNumberOffset));
817 $lineNum = '';
818 foreach ($cArr as $k => $v) {
819 $lln = $k + $this->ext_lineNumberOffset + 1;
820 if ($ln) {
821 $lineNum = $this->ext_lnBreakPointWrap($lln, str_replace(' ', '&nbsp;', sprintf(('% ' . $n . 'd'), $lln))) . ': ';
822 }
823 $v = htmlspecialchars($v);
824 if ($crop) {
825 $v = $this->ext_fixed_lgd($v, $ln ? 71 : 77);
826 }
827 $cArr[$k] = $lineNum . str_replace(' ', '&nbsp;', $v);
828 $firstChar = substr(trim($v), 0, 1);
829 if ($firstChar === '[') {
830 $cArr[$k] = '<strong class="text-success">' . $cArr[$k] . '</strong>';
831 } elseif ($firstChar === '/' || $firstChar === '#') {
832 if ($comments) {
833 $cArr[$k] = '<span class="text-muted">' . $cArr[$k] . '</span>';
834 } else {
835 unset($cArr[$k]);
836 }
837 }
838 }
839 $output = implode($cArr, '<br />') . '<br />';
840 return $output;
841 }
842
843 /**
844 * Get a single sys_template record attached to a single page.
845 * If multiple template records are on this page, the first (order by sorting)
846 * record will be returned, unless a specific template uid is specified via $templateUid
847 *
848 * @param int $pid The pid to select sys_template records from
849 * @param int $templateUid Optional template uid
850 * @return array|null Returns the template record or null if none was found
851 */
852 public function ext_getFirstTemplate($pid, $templateUid = 0)
853 {
854 if (empty($pid)) {
855 return null;
856 }
857
858 // Query is taken from the runThroughTemplates($theRootLine) function in the parent class.
859 $queryBuilder = $this->getTemplateQueryBuilder($pid)
860 ->setMaxResults(1);
861 if ($templateUid) {
862 $queryBuilder->andWhere(
863 $queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($templateUid, \PDO::PARAM_INT))
864 );
865 }
866 $row = $queryBuilder->execute()->fetch();
867 BackendUtility::workspaceOL('sys_template', $row);
868
869 return $row;
870 }
871
872 /**
873 * Get an array of all template records on a page.
874 *
875 * @param int $pid Pid to fetch sys_template records for
876 * @return array[] Array of template records
877 */
878 public function ext_getAllTemplates($pid): array
879 {
880 if (empty($pid)) {
881 return [];
882 }
883 $result = $this->getTemplateQueryBuilder($pid)->execute();
884 $outRes = [];
885 while ($row = $result->fetch()) {
886 BackendUtility::workspaceOL('sys_template', $row);
887 if (is_array($row)) {
888 $outRes[] = $row;
889 }
890 }
891 return $outRes;
892 }
893
894 /**
895 * Internal helper method to prepare the query builder for
896 * getting sys_template records from a given pid
897 *
898 * @param int $pid The pid to select sys_template records from
899 * @return QueryBuilder Returns a QueryBuilder
900 */
901 protected function getTemplateQueryBuilder(int $pid): QueryBuilder
902 {
903 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
904 ->getQueryBuilderForTable('sys_template');
905 $queryBuilder->getRestrictions()
906 ->removeAll()
907 ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
908 $queryBuilder->select('*')
909 ->from('sys_template')
910 ->where(
911 $queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pid, \PDO::PARAM_INT))
912 );
913 if (!empty($GLOBALS['TCA']['sys_template']['ctrl']['sortby'])) {
914 $queryBuilder->orderBy($GLOBALS['TCA']['sys_template']['ctrl']['sortby']);
915 }
916
917 return $queryBuilder;
918 }
919
920 /**
921 * This function compares the flattened constants (default and all).
922 * Returns an array with the constants from the whole template which may be edited by the module.
923 *
924 * @param array $default
925 * @return array
926 */
927 public function ext_compareFlatSetups($default)
928 {
929 $editableComments = [];
930 $counter = 0;
931 foreach ($this->flatSetup as $const => $value) {
932 if (substr($const, -2) === '..' || !isset($this->flatSetup[$const . '..'])) {
933 continue;
934 }
935 $counter++;
936 $comment = trim($this->flatSetup[$const . '..']);
937 $c_arr = explode(LF, $comment);
938 foreach ($c_arr as $k => $v) {
939 $line = trim(preg_replace('/^[#\\/]*/', '', $v));
940 if (!$line) {
941 continue;
942 }
943 $parts = explode(';', $line);
944 foreach ($parts as $par) {
945 if (strstr($par, '=')) {
946 $keyValPair = explode('=', $par, 2);
947 switch (trim(strtolower($keyValPair[0]))) {
948 case 'type':
949 // Type:
950 $editableComments[$const]['type'] = trim($keyValPair[1]);
951 break;
952 case 'cat':
953 // List of categories.
954 $catSplit = explode('/', strtolower($keyValPair[1]));
955 $catSplit[0] = trim($catSplit[0]);
956 if (isset($this->categoryLabels[$catSplit[0]])) {
957 $catSplit[0] = $this->categoryLabels[$catSplit[0]];
958 }
959 $editableComments[$const]['cat'] = $catSplit[0];
960 // This is the subcategory. Must be a key in $this->subCategories[].
961 // catSplit[2] represents the search-order within the subcat.
962 $catSplit[1] = trim($catSplit[1]);
963 if ($catSplit[1] && isset($this->subCategories[$catSplit[1]])) {
964 $editableComments[$const]['subcat_name'] = $catSplit[1];
965 $editableComments[$const]['subcat'] = $this->subCategories[$catSplit[1]][1]
966 . '/' . $catSplit[1] . '/' . trim($catSplit[2]) . 'z';
967 } elseif (isset($catSplit[2])) {
968 $editableComments[$const]['subcat'] = 'x' . '/' . trim($catSplit[2]) . 'z';
969 } else {
970 $editableComments[$const]['subcat'] = 'x' . '/' . $counter . 'z';
971 }
972 break;
973 case 'label':
974 // Label
975 $editableComments[$const]['label'] = trim($keyValPair[1]);
976 break;
977 case 'customcategory':
978 // Custom category label
979 $customCategory = explode('=', $keyValPair[1], 2);
980 if (trim($customCategory[0])) {
981 $categoryKey = strtolower($customCategory[0]);
982 $this->categoryLabels[$categoryKey] = $this->getLanguageService()->sL($customCategory[1]);
983 }
984 break;
985 case 'customsubcategory':
986 // Custom subCategory label
987 $customSubcategory = explode('=', $keyValPair[1], 2);
988 if (trim($customSubcategory[0])) {
989 $subCategoryKey = strtolower($customSubcategory[0]);
990 $this->subCategories[$subCategoryKey][0] = $this->getLanguageService()->sL($customSubcategory[1]);
991 }
992 break;
993 }
994 }
995 }
996 }
997 if (isset($editableComments[$const])) {
998 $editableComments[$const]['name'] = $const;
999 $editableComments[$const]['value'] = trim($value);
1000 if (isset($default[$const])) {
1001 $editableComments[$const]['default_value'] = trim($default[$const]);
1002 }
1003 }
1004 }
1005 return $editableComments;
1006 }
1007
1008 /**
1009 * @param array $editConstArray
1010 * @return void
1011 */
1012 public function ext_categorizeEditableConstants($editConstArray)
1013 {
1014 // Runs through the available constants and fills the $this->categories array with pointers and priority-info
1015 foreach ($editConstArray as $constName => $constData) {
1016 if (!$constData['type']) {
1017 $constData['type'] = 'string';
1018 }
1019 $cats = explode(',', $constData['cat']);
1020 // if = only one category, while allows for many. We have agreed on only one category is the most basic way...
1021 foreach ($cats as $theCat) {
1022 $theCat = trim($theCat);
1023 if ($theCat) {
1024 $this->categories[$theCat][$constName] = $constData['subcat'];
1025 }
1026 }
1027 }
1028 }
1029
1030 /**
1031 * @return array
1032 */
1033 public function ext_getCategoryLabelArray()
1034 {
1035 // Returns array used for labels in the menu.
1036 $retArr = [];
1037 foreach ($this->categories as $k => $v) {
1038 if (!empty($v)) {
1039 $retArr[$k] = strtoupper($k) . ' (' . count($v) . ')';
1040 }
1041 }
1042 return $retArr;
1043 }
1044
1045 /**
1046 * @param string $type
1047 * @return array
1048 */
1049 public function ext_getTypeData($type)
1050 {
1051 $retArr = [];
1052 $type = trim($type);
1053 if (!$type) {
1054 $retArr['type'] = 'string';
1055 } else {
1056 $m = strcspn($type, ' [');
1057 $retArr['type'] = strtolower(substr($type, 0, $m));
1058 $types = ['int' => 1, 'options' => 1, 'file' => 1, 'boolean' => 1, 'offset' => 1, 'user' => 1];
1059 if (isset($types[$retArr['type']])) {
1060 $p = trim(substr($type, $m));
1061 $reg = [];
1062 preg_match('/\\[(.*)\\]/', $p, $reg);
1063 $p = trim($reg[1]);
1064 if ($p) {
1065 $retArr['paramstr'] = $p;
1066 switch ($retArr['type']) {
1067 case 'int':
1068 if ($retArr['paramstr'][0] === '-') {
1069 $retArr['params'] = GeneralUtility::intExplode('-', substr($retArr['paramstr'], 1));
1070 $retArr['params'][0] = (int)('-' . $retArr['params'][0]);
1071 } else {
1072 $retArr['params'] = GeneralUtility::intExplode('-', $retArr['paramstr']);
1073 }
1074 $retArr['min'] = $retArr['params'][0];
1075 $retArr['max'] = $retArr['params'][1];
1076 $retArr['paramstr'] = $retArr['params'][0] . ' - ' . $retArr['params'][1];
1077 break;
1078 case 'options':
1079 $retArr['params'] = explode(',', $retArr['paramstr']);
1080 break;
1081 }
1082 }
1083 }
1084 }
1085 return $retArr;
1086 }
1087
1088 /**
1089 * @param string $category
1090 * @return void
1091 */
1092 public function ext_getTSCE_config($category)
1093 {
1094 $catConf = $this->setup['constants']['TSConstantEditor.'][$category . '.'];
1095 $out = [];
1096 if (is_array($catConf)) {
1097 foreach ($catConf as $key => $val) {
1098 switch ($key) {
1099 case 'description':
1100 case 'bulletlist':
1101 case 'header':
1102 $out[$key] = $val;
1103 break;
1104 default:
1105 if (MathUtility::canBeInterpretedAsInteger($key)) {
1106 $constRefs = explode(',', $val);
1107 foreach ($constRefs as $const) {
1108 $const = trim($const);
1109 if ($const) {
1110 $out['constants'][$const] .= '<span class="label label-danger">' . $key . '</span>';
1111 }
1112 }
1113 }
1114 }
1115 }
1116 }
1117 $this->helpConfig = $out;
1118 }
1119
1120 /**
1121 * @param array $params
1122 * @return array
1123 */
1124 public function ext_fNandV($params)
1125 {
1126 $fN = 'data[' . $params['name'] . ']';
1127 $idName = str_replace('.', '-', $params['name']);
1128 $fV = $params['value'];
1129 // Values entered from the constantsedit cannot be constants! 230502; removed \{ and set {
1130 if (preg_match('/^{[\\$][a-zA-Z0-9\\.]*}$/', trim($fV), $reg)) {
1131 $fV = '';
1132 }
1133 $fV = htmlspecialchars($fV);
1134 return [$fN, $fV, $params, $idName];
1135 }
1136
1137 /**
1138 * This functions returns the HTML-code that creates the editor-layout of the module.
1139 *
1140 * @param array $theConstants
1141 * @param string $category
1142 * @return string
1143 */
1144 public function ext_printFields($theConstants, $category)
1145 {
1146 reset($theConstants);
1147 $output = '';
1148 $subcat = '';
1149 if (is_array($this->categories[$category])) {
1150 $help = $this->helpConfig;
1151 if (!$this->doNotSortCategoriesBeforeMakingForm) {
1152 asort($this->categories[$category]);
1153 }
1154 /** @var IconFactory $iconFactory */
1155 $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
1156 foreach ($this->categories[$category] as $name => $type) {
1157 $params = $theConstants[$name];
1158 if (is_array($params)) {
1159 if ($subcat != $params['subcat_name']) {
1160 $subcat = $params['subcat_name'];
1161 $subcat_name = $params['subcat_name'] ? $this->subCategories[$params['subcat_name']][0] : 'Others';
1162 $output .= '<h3>' . $subcat_name . '</h3>';
1163 }
1164 $label = $this->getLanguageService()->sL($params['label']);
1165 $label_parts = explode(':', $label, 2);
1166 if (count($label_parts) === 2) {
1167 $head = trim($label_parts[0]);
1168 $body = trim($label_parts[1]);
1169 } else {
1170 $head = trim($label_parts[0]);
1171 $body = '';
1172 }
1173 $typeDat = $this->ext_getTypeData($params['type']);
1174 $p_field = '';
1175 $raname = substr(md5($params['name']), 0, 10);
1176 $aname = '\'' . $raname . '\'';
1177 list($fN, $fV, $params, $idName) = $this->ext_fNandV($params);
1178 $idName = htmlspecialchars($idName);
1179 $hint = '';
1180 switch ($typeDat['type']) {
1181 case 'int':
1182 case 'int+':
1183 $additionalAttributes = '';
1184 if ($typeDat['paramstr']) {
1185 $hint = ' Range: ' . $typeDat['paramstr'];
1186 } elseif ($typeDat['type'] === 'int+') {
1187 $hint = ' Range: 0 - ';
1188 $typeDat['min'] = 0;
1189 } else {
1190 $hint = ' (Integer)';
1191 }
1192
1193 if (isset($typeDat['min'])) {
1194 $additionalAttributes .= ' min="' . (int)$typeDat['min'] . '" ';
1195 }
1196 if (isset($typeDat['max'])) {
1197 $additionalAttributes .= ' max="' . (int)$typeDat['max'] . '" ';
1198 }
1199
1200 $p_field =
1201 '<input class="form-control" id="' . $idName . '" type="number"'
1202 . ' name="' . $fN . '" value="' . $fV . '"' . ' onChange="uFormUrl(' . $aname . ')"' . $additionalAttributes . ' />';
1203 break;
1204 case 'color':
1205 $p_field = '
1206 <input class="form-control formengine-colorpickerelement t3js-color-picker" type="text" id="input-' . $idName . '" rel="' . $idName .
1207 '" name="' . $fN . '" value="' . $fV . '"' . $this->getDocumentTemplate()->formWidth(7) . ' onChange="uFormUrl(' . $aname . ')" />';
1208
1209 if (empty($this->inlineJavaScript[$typeDat['type']])) {
1210 $this->inlineJavaScript[$typeDat['type']] = 'require([\'TYPO3/CMS/Backend/ColorPicker\'], function(ColorPicker){ColorPicker.initialize()});';
1211 }
1212 break;
1213 case 'wrap':
1214 $wArr = explode('|', $fV);
1215 $p_field = '<input class="form-control" type="text" id="' . $idName . '" name="' . $fN . '" value="' . $wArr[0] . '"' . $this->getDocumentTemplate()->formWidth(29) . ' onChange="uFormUrl(' . $aname . ')" />';
1216 $p_field .= ' | ';
1217 $p_field .= '<input class="form-control" type="text" name="W' . $fN . '" value="' . $wArr[1] . '"' . $this->getDocumentTemplate()->formWidth(15) . ' onChange="uFormUrl(' . $aname . ')" />';
1218 break;
1219 case 'offset':
1220 $wArr = explode(',', $fV);
1221 $labels = GeneralUtility::trimExplode(',', $typeDat['paramstr']);
1222 $p_field = ($labels[0] ? $labels[0] : 'x') . ':<input type="text" name="' . $fN . '" value="' . $wArr[0] . '"' . $this->getDocumentTemplate()->formWidth(4) . ' onChange="uFormUrl(' . $aname . ')" />';
1223 $p_field .= ' , ';
1224 $p_field .= ($labels[1] ? $labels[1] : 'y') . ':<input type="text" name="W' . $fN . '" value="' . $wArr[1] . '"' . $this->getDocumentTemplate()->formWidth(4) . ' onChange="uFormUrl(' . $aname . ')" />';
1225 $labelsCount = count($labels);
1226 for ($aa = 2; $aa < $labelsCount; $aa++) {
1227 if ($labels[$aa]) {
1228 $p_field .= ' , ' . $labels[$aa] . ':<input type="text" name="W' . $aa . $fN . '" value="' . $wArr[$aa] . '"' . $this->getDocumentTemplate()->formWidth(4) . ' onChange="uFormUrl(' . $aname . ')" />';
1229 } else {
1230 $p_field .= '<input type="hidden" name="W' . $aa . $fN . '" value="' . $wArr[$aa] . '" />';
1231 }
1232 }
1233 break;
1234 case 'options':
1235 if (is_array($typeDat['params'])) {
1236 $p_field = '';
1237 foreach ($typeDat['params'] as $val) {
1238 $vParts = explode('=', $val, 2);
1239 $label = $vParts[0];
1240 $val = isset($vParts[1]) ? $vParts[1] : $vParts[0];
1241 // option tag:
1242 $sel = '';
1243 if ($val === $params['value']) {
1244 $sel = ' selected';
1245 }
1246 $p_field .= '<option value="' . htmlspecialchars($val) . '"' . $sel . '>' . $this->getLanguageService()->sL($label) . '</option>';
1247 }
1248 $p_field = '<select class="form-control" id="' . $idName . '" name="' . $fN . '" onChange="uFormUrl(' . $aname . ')">' . $p_field . '</select>';
1249 }
1250 break;
1251 case 'boolean':
1252 $sel = $fV ? 'checked' : '';
1253 $p_field =
1254 '<label class="btn btn-default btn-checkbox">'
1255 . '<input type="hidden" name="' . $fN . '" value="0" />'
1256 . '<input id="' . $idName . '" type="checkbox" name="' . $fN . '" value="' . ($typeDat['paramstr'] ? $typeDat['paramstr'] : 1) . '" ' . $sel . ' onClick="uFormUrl(' . $aname . ')" />'
1257 . '<span class="t3-icon fa"></span>'
1258 . '</label>';
1259 break;
1260 case 'comment':
1261 $sel = $fV ? 'checked' : '';
1262 $p_field =
1263 '<label class="btn btn-default btn-checkbox">'
1264 . '<input type="hidden" name="' . $fN . '" value="#" />'
1265 . '<input id="' . $idName . '" type="checkbox" name="' . $fN . '" value="" ' . $sel . ' onClick="uFormUrl(' . $aname . ')" />'
1266 . '<span class="t3-icon fa"></span>'
1267 . '</label>';
1268 break;
1269 case 'file':
1270 // extensionlist
1271 $extList = $typeDat['paramstr'];
1272 if ($extList === 'IMAGE_EXT') {
1273 $extList = $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'];
1274 }
1275 $p_field = '<option value="">(' . $extList . ')</option>';
1276 if (trim($params['value'])) {
1277 $val = $params['value'];
1278 $p_field .= '<option value=""></option>';
1279 $p_field .= '<option value="' . htmlspecialchars($val) . '" selected>' . $val . '</option>';
1280 }
1281 $p_field = '<select class="form-select" id="' . $idName . '" name="' . $fN . '" onChange="uFormUrl(' . $aname . ')">' . $p_field . '</select>';
1282 break;
1283 case 'user':
1284 $userFunction = $typeDat['paramstr'];
1285 $userFunctionParams = ['fieldName' => $fN, 'fieldValue' => $fV];
1286 $p_field = GeneralUtility::callUserFunction($userFunction, $userFunctionParams, $this);
1287 break;
1288 default:
1289 $p_field = '<input class="form-control" id="' . $idName . '" type="text" name="' . $fN . '" value="' . $fV . '"'
1290 . ' onChange="uFormUrl(' . $aname . ')" />';
1291 }
1292 // Define default names and IDs
1293 $userTyposcriptID = 'userTS-' . $idName;
1294 $defaultTyposcriptID = 'defaultTS-' . $idName;
1295 $checkboxName = 'check[' . $params['name'] . ']';
1296 $checkboxID = 'check-' . $idName;
1297 $userTyposcriptStyle = '';
1298 $deleteIconHTML = '';
1299 $constantCheckbox = '';
1300 $constantDefaultRow = '';
1301 if (!$this->ext_dontCheckIssetValues) {
1302 // Set the default styling options
1303 if (isset($this->objReg[$params['name']])) {
1304 $checkboxValue = 'checked';
1305 $defaultTyposcriptStyle = 'style="display:none;"';
1306 } else {
1307 $checkboxValue = '';
1308 $userTyposcriptStyle = 'style="display:none;"';
1309 $defaultTyposcriptStyle = '';
1310 }
1311 $deleteTitle = htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.deleteTitle'));
1312 $deleteIcon = $iconFactory->getIcon('actions-edit-undo', Icon::SIZE_SMALL)->render();
1313 $deleteIconHTML =
1314 '<button type="button" class="btn btn-default t3js-toggle" data-toggle="undo" rel="' . $idName . '">'
1315 . '<span title="' . $deleteTitle . '" alt="' . $deleteTitle . '">'
1316 . $deleteIcon
1317 . '</span>'
1318 . '</button>';
1319 $editTitle = htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.editTitle'));
1320 $editIcon = $iconFactory->getIcon('actions-open', Icon::SIZE_SMALL)->render();
1321 $editIconHTML =
1322 '<button type="button" class="btn btn-default t3js-toggle" data-toggle="edit" rel="' . $idName . '">'
1323 . '<span title="' . $editTitle . '" alt="' . $editTitle . '">'
1324 . $editIcon
1325 . '</span>'
1326 . '</button>';
1327 $constantCheckbox = '<input type="hidden" name="' . $checkboxName . '" id="' . $checkboxID . '" value="' . $checkboxValue . '"/>';
1328 // If there's no default value for the field, use a static label.
1329 if (!$params['default_value']) {
1330 $params['default_value'] = '[Empty]';
1331 }
1332 $constantDefaultRow =
1333 '<div class="input-group defaultTS" id="' . $defaultTyposcriptID . '" ' . $defaultTyposcriptStyle . '>'
1334 . '<span class="input-group-btn">' . $editIconHTML . '</span>'
1335 . '<input class="form-control" type="text" placeholder="' . htmlspecialchars($params['default_value']) . '" readonly>'
1336 . '</div>';
1337 }
1338 $constantEditRow =
1339 '<div class="input-group userTS" id="' . $userTyposcriptID . '" ' . $userTyposcriptStyle . '>'
1340 . '<span class="input-group-btn">' . $deleteIconHTML . '</span>'
1341 . $p_field
1342 . '</div>';
1343 $constantLabel = '<label class="t3js-formengine-label"><span>' . htmlspecialchars($head) . '</span></label>';
1344 $constantName = '<span class="help-block">[' . $params['name'] . ']</span>';
1345 $constantDescription = $body ? '<p class="help-block">' . htmlspecialchars($body) . '</p>' : '';
1346 $constantData = '';
1347 if ($hint !== '') {
1348 $constantData .= '<span class="help-block">' . $hint . '</span>';
1349 }
1350 $constantData .=
1351 $constantCheckbox
1352 . $constantEditRow
1353 . $constantDefaultRow;
1354
1355 $output .=
1356 '<fieldset class="form-section">'
1357 . '<a name="' . $raname . '"></a>' . $help['constants'][$params['name']]
1358 . '<div class="form-group">'
1359 . $constantLabel . $constantName . $constantDescription . $constantData
1360 . '</div>'
1361 . '</fieldset>';
1362 } else {
1363 debug('Error. Constant did not exist. Should not happen.');
1364 }
1365 }
1366 }
1367 return '<div class="tstemplate-constanteditor">' . $output . '</div>';
1368 }
1369
1370 /***************************
1371 *
1372 * Processing input values
1373 *
1374 ***************************/
1375 /**
1376 * @param string $constants
1377 * @return void
1378 */
1379 public function ext_regObjectPositions($constants)
1380 {
1381 // This runs through the lines of the constants-field of the active template and registers the constants-names
1382 // and line positions in an array, $this->objReg
1383 $this->raw = explode(LF, $constants);
1384 $this->rawP = 0;
1385 // Resetting the objReg if the divider is found!!
1386 $this->objReg = [];
1387 $this->ext_regObjects('');
1388 }
1389
1390 /**
1391 * @param string $pre
1392 * @return void
1393 */
1394 public function ext_regObjects($pre)
1395 {
1396 // Works with regObjectPositions. "expands" the names of the TypoScript objects
1397 while (isset($this->raw[$this->rawP])) {
1398 $line = ltrim($this->raw[$this->rawP]);
1399 if (strstr($line, $this->edit_divider)) {
1400 // Resetting the objReg if the divider is found!!
1401 $this->objReg = [];
1402 }
1403 $this->rawP++;
1404 if ($line) {
1405 if ($line[0] === '[') {
1406 } elseif (strcspn($line, '}#/') != 0) {
1407 $varL = strcspn($line, ' {=<');
1408 $var = substr($line, 0, $varL);
1409 $line = ltrim(substr($line, $varL));
1410 switch ($line[0]) {
1411 case '=':
1412 $this->objReg[$pre . $var] = $this->rawP - 1;
1413 break;
1414 case '{':
1415 $this->ext_inBrace++;
1416 $this->ext_regObjects($pre . $var . '.');
1417 break;
1418 }
1419 $this->lastComment = '';
1420 } elseif ($line[0] === '}') {
1421 $this->lastComment = '';
1422 $this->ext_inBrace--;
1423 if ($this->ext_inBrace < 0) {
1424 $this->ext_inBrace = 0;
1425 } else {
1426 break;
1427 }
1428 }
1429 }
1430 }
1431 }
1432
1433 /**
1434 * @param string $key
1435 * @param string $var
1436 * @return void
1437 */
1438 public function ext_putValueInConf($key, $var)
1439 {
1440 // Puts the value $var to the TypoScript value $key in the current lines of the templates.
1441 // If the $key is not found in the template constants field, a new line is inserted in the bottom.
1442 $theValue = ' ' . trim($var);
1443 if (isset($this->objReg[$key])) {
1444 $lineNum = $this->objReg[$key];
1445 $parts = explode('=', $this->raw[$lineNum], 2);
1446 if (count($parts) === 2) {
1447 $parts[1] = $theValue;
1448 }
1449 $this->raw[$lineNum] = implode($parts, '=');
1450 } else {
1451 $this->raw[] = $key . ' =' . $theValue;
1452 }
1453 $this->changed = true;
1454 }
1455
1456 /**
1457 * @param string $key
1458 * @return void
1459 */
1460 public function ext_removeValueInConf($key)
1461 {
1462 // Removes the value in the configuration
1463 if (isset($this->objReg[$key])) {
1464 $lineNum = $this->objReg[$key];
1465 unset($this->raw[$lineNum]);
1466 }
1467 $this->changed = true;
1468 }
1469
1470 /**
1471 * @param array $arr
1472 * @param array $settings
1473 * @return array
1474 */
1475 public function ext_depthKeys($arr, $settings)
1476 {
1477 $tsbrArray = [];
1478 foreach ($arr as $theK => $theV) {
1479 $theKeyParts = explode('.', $theK);
1480 $depth = '';
1481 $c = count($theKeyParts);
1482 $a = 0;
1483 foreach ($theKeyParts as $p) {
1484 $a++;
1485 $depth .= ($depth ? '.' : '') . $p;
1486 $tsbrArray[$depth] = $c == $a ? $theV : 1;
1487 }
1488 }
1489 // Modify settings
1490 foreach ($tsbrArray as $theK => $theV) {
1491 if ($theV) {
1492 $settings[$theK] = 1;
1493 } else {
1494 unset($settings[$theK]);
1495 }
1496 }
1497 return $settings;
1498 }
1499
1500 /**
1501 * Proces input
1502 *
1503 * @param array $http_post_vars
1504 * @param array $http_post_files (not used anymore)
1505 * @param array $theConstants
1506 * @param array $tplRow Not used
1507 * @return void
1508 */
1509 public function ext_procesInput($http_post_vars, $http_post_files, $theConstants, $tplRow)
1510 {
1511 $data = $http_post_vars['data'];
1512 $check = $http_post_vars['check'];
1513 $Wdata = $http_post_vars['Wdata'];
1514 $W2data = $http_post_vars['W2data'];
1515 $W3data = $http_post_vars['W3data'];
1516 $W4data = $http_post_vars['W4data'];
1517 $W5data = $http_post_vars['W5data'];
1518 if (is_array($data)) {
1519 foreach ($data as $key => $var) {
1520 if (isset($theConstants[$key])) {
1521 // If checkbox is set, update the value
1522 if ($this->ext_dontCheckIssetValues || isset($check[$key])) {
1523 // Exploding with linebreak, just to make sure that no multiline input is given!
1524 list($var) = explode(LF, $var);
1525 $typeDat = $this->ext_getTypeData($theConstants[$key]['type']);
1526 switch ($typeDat['type']) {
1527 case 'int':
1528 if ($typeDat['paramstr']) {
1529 $var = MathUtility::forceIntegerInRange($var, $typeDat['params'][0], $typeDat['params'][1]);
1530 } else {
1531 $var = (int)$var;
1532 }
1533 break;
1534 case 'int+':
1535 $var = max(0, (int)$var);
1536 break;
1537 case 'color':
1538 $col = [];
1539 if ($var) {
1540 $var = preg_replace('/[^A-Fa-f0-9]*/', '', $var);
1541 $useFulHex = strlen($var) > 3;
1542 $col[] = hexdec($var[0]);
1543 $col[] = hexdec($var[1]);
1544 $col[] = hexdec($var[2]);
1545 if ($useFulHex) {
1546 $col[] = hexdec($var[3]);
1547 $col[] = hexdec($var[4]);
1548 $col[] = hexdec($var[5]);
1549 }
1550 $var = substr(('0' . dechex($col[0])), -1) . substr(('0' . dechex($col[1])), -1) . substr(('0' . dechex($col[2])), -1);
1551 if ($useFulHex) {
1552 $var .= substr(('0' . dechex($col[3])), -1) . substr(('0' . dechex($col[4])), -1) . substr(('0' . dechex($col[5])), -1);
1553 }
1554 $var = '#' . strtoupper($var);
1555 }
1556 break;
1557 case 'comment':
1558 if ($var) {
1559 $var = '#';
1560 } else {
1561 $var = '';
1562 }
1563 break;
1564 case 'wrap':
1565 if (isset($Wdata[$key])) {
1566 $var .= '|' . $Wdata[$key];
1567 }
1568 break;
1569 case 'offset':
1570 if (isset($Wdata[$key])) {
1571 $var = (int)$var . ',' . (int)$Wdata[$key];
1572 if (isset($W2data[$key])) {
1573 $var .= ',' . (int)$W2data[$key];
1574 if (isset($W3data[$key])) {
1575 $var .= ',' . (int)$W3data[$key];
1576 if (isset($W4data[$key])) {
1577 $var .= ',' . (int)$W4data[$key];
1578 if (isset($W5data[$key])) {
1579 $var .= ',' . (int)$W5data[$key];
1580 }
1581 }
1582 }
1583 }
1584 }
1585 break;
1586 case 'boolean':
1587 if ($var) {
1588 $var = $typeDat['paramstr'] ? $typeDat['paramstr'] : 1;
1589 }
1590 break;
1591 }
1592 if ($this->ext_printAll || (string)$theConstants[$key]['value'] !== (string)$var) {
1593 // Put value in, if changed.
1594 $this->ext_putValueInConf($key, $var);
1595 }
1596 // Remove the entry because it has been "used"
1597 unset($check[$key]);
1598 } else {
1599 $this->ext_removeValueInConf($key);
1600 }
1601 }
1602 }
1603 }
1604 // Remaining keys in $check indicates fields that are just clicked "on" to be edited.
1605 // Therefore we get the default value and puts that in the template as a start...
1606 if (!$this->ext_dontCheckIssetValues && is_array($check)) {
1607 foreach ($check as $key => $var) {
1608 if (isset($theConstants[$key])) {
1609 $dValue = $theConstants[$key]['default_value'];
1610 $this->ext_putValueInConf($key, $dValue);
1611 }
1612 }
1613 }
1614 }
1615
1616 /**
1617 * @param int $id
1618 * @param string $perms_clause
1619 * @return array
1620 */
1621 public function ext_prevPageWithTemplate($id, $perms_clause)
1622 {
1623 $rootLine = BackendUtility::BEgetRootLine($id, $perms_clause ? ' AND ' . $perms_clause : '');
1624 foreach ($rootLine as $p) {
1625 if ($this->ext_getFirstTemplate($p['uid'])) {
1626 return $p;
1627 }
1628 }
1629 return [];
1630 }
1631
1632 /**
1633 * Is set by runThroughTemplates(), previously set via TemplateAnalyzerModuleFunctionController from the outside
1634 *
1635 * @return array
1636 */
1637 protected function getRootLine()
1638 {
1639 return is_array($this->absoluteRootLine) ? $this->absoluteRootLine : [];
1640 }
1641
1642 /**
1643 * @return LanguageService
1644 */
1645 protected function getLanguageService()
1646 {
1647 return $GLOBALS['LANG'];
1648 }
1649
1650 /**
1651 * @return DocumentTemplate
1652 */
1653 protected function getDocumentTemplate()
1654 {
1655 return $GLOBALS['TBE_TEMPLATE'];
1656 }
1657 }