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