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