TYPO3 CMS  TYPO3_8-7
MarkerBasedTemplateService.php
Go to the documentation of this file.
1 <?php
2 namespace TYPO3\CMS\Core\Service;
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  */
19 
25 {
35  public function getSubpart($content, $marker)
36  {
37  $start = strpos($content, $marker);
38  if ($start === false) {
39  return '';
40  }
41  $start += strlen($marker);
42  $stop = strpos($content, $marker, $start);
43  // Q: What shall get returned if no stop marker is given
44  // Everything till the end or nothing?
45  if ($stop === false) {
46  return '';
47  }
48  $content = substr($content, $start, $stop - $start);
49  $matches = [];
50  if (preg_match('/^([^\\<]*\\-\\-\\>)(.*)(\\<\\!\\-\\-[^\\>]*)$/s', $content, $matches) === 1) {
51  return $matches[2];
52  }
53  // Resetting $matches
54  $matches = [];
55  if (preg_match('/(.*)(\\<\\!\\-\\-[^\\>]*)$/s', $content, $matches) === 1) {
56  return $matches[1];
57  }
58  // Resetting $matches
59  $matches = [];
60  if (preg_match('/^([^\\<]*\\-\\-\\>)(.*)$/s', $content, $matches) === 1) {
61  return $matches[2];
62  }
63 
64  return $content;
65  }
66 
78  public function substituteSubpart($content, $marker, $subpartContent, $recursive = true, $keepMarker = false)
79  {
80  $start = strpos($content, $marker);
81  if ($start === false) {
82  return $content;
83  }
84  $startAM = $start + strlen($marker);
85  $stop = strpos($content, $marker, $startAM);
86  if ($stop === false) {
87  return $content;
88  }
89  $stopAM = $stop + strlen($marker);
90  $before = substr($content, 0, $start);
91  $after = substr($content, $stopAM);
92  $between = substr($content, $startAM, $stop - $startAM);
93  if ($recursive) {
94  $after = $this->substituteSubpart($after, $marker, $subpartContent, $recursive, $keepMarker);
95  }
96  if ($keepMarker) {
97  $matches = [];
98  if (preg_match('/^([^\\<]*\\-\\-\\>)(.*)(\\<\\!\\-\\-[^\\>]*)$/s', $between, $matches) === 1) {
99  $before .= $marker . $matches[1];
100  $between = $matches[2];
101  $after = $matches[3] . $marker . $after;
102  } elseif (preg_match('/^(.*)(\\<\\!\\-\\-[^\\>]*)$/s', $between, $matches) === 1) {
103  $before .= $marker;
104  $between = $matches[1];
105  $after = $matches[2] . $marker . $after;
106  } elseif (preg_match('/^([^\\<]*\\-\\-\\>)(.*)$/s', $between, $matches) === 1) {
107  $before .= $marker . $matches[1];
108  $between = $matches[2];
109  $after = $marker . $after;
110  } else {
111  $before .= $marker;
112  $after = $marker . $after;
113  }
114  } else {
115  $matches = [];
116  if (preg_match('/^(.*)\\<\\!\\-\\-[^\\>]*$/s', $before, $matches) === 1) {
117  $before = $matches[1];
118  }
119  if (is_array($subpartContent)) {
120  $matches = [];
121  if (preg_match('/^([^\\<]*\\-\\-\\>)(.*)(\\<\\!\\-\\-[^\\>]*)$/s', $between, $matches) === 1) {
122  $between = $matches[2];
123  } elseif (preg_match('/^(.*)(\\<\\!\\-\\-[^\\>]*)$/s', $between, $matches) === 1) {
124  $between = $matches[1];
125  } elseif (preg_match('/^([^\\<]*\\-\\-\\>)(.*)$/s', $between, $matches) === 1) {
126  $between = $matches[2];
127  }
128  }
129  $matches = [];
130  // resetting $matches
131  if (preg_match('/^[^\\<]*\\-\\-\\>(.*)$/s', $after, $matches) === 1) {
132  $after = $matches[1];
133  }
134  }
135  if (is_array($subpartContent)) {
136  $between = $subpartContent[0] . $between . $subpartContent[1];
137  } else {
138  $between = $subpartContent;
139  }
140 
141  return $before . $between . $after;
142  }
143 
152  public function substituteSubpartArray($content, array $subpartsContent)
153  {
154  foreach ($subpartsContent as $subpartMarker => $subpartContent) {
155  $content = $this->substituteSubpart($content, $subpartMarker, $subpartContent);
156  }
157 
158  return $content;
159  }
160 
172  public function substituteMarker($content, $marker, $markContent)
173  {
174  return str_replace($marker, $markContent, $content);
175  }
176 
196  public function substituteMarkerArray($content, $markContentArray, $wrap = '', $uppercase = false, $deleteUnused = false)
197  {
198  if (is_array($markContentArray)) {
199  $wrapArr = GeneralUtility::trimExplode('|', $wrap);
200  $search = [];
201  $replace = [];
202  foreach ($markContentArray as $marker => $markContent) {
203  if ($uppercase) {
204  // use strtr instead of strtoupper to avoid locale problems with Turkish
205  $marker = strtr($marker, 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ');
206  }
207  if (!empty($wrapArr)) {
208  $marker = $wrapArr[0] . $marker . $wrapArr[1];
209  }
210  $search[] = $marker;
211  $replace[] = $markContent;
212  }
213  $content = str_replace($search, $replace, $content);
214  unset($search, $replace);
215  if ($deleteUnused) {
216  if (empty($wrap)) {
217  $wrapArr = ['###', '###'];
218  }
219  $content = preg_replace('/' . preg_quote($wrapArr[0], '/') . '([A-Z0-9_|\\-]*)' . preg_quote($wrapArr[1], '/') . '/is', '', $content);
220  }
221  }
222 
223  return $content;
224  }
225 
261  public function substituteMarkerAndSubpartArrayRecursive($content, array $markersAndSubparts, $wrap = '', $uppercase = false, $deleteUnused = false)
262  {
263  $wraps = GeneralUtility::trimExplode('|', $wrap);
264  $singleItems = [];
265  $compoundItems = [];
266  // Split markers and subparts into separate arrays
267  foreach ($markersAndSubparts as $markerName => $markerContent) {
268  if (is_array($markerContent)) {
269  $compoundItems[] = $markerName;
270  } else {
271  $singleItems[$markerName] = $markerContent;
272  }
273  }
274  $subTemplates = [];
275  $subpartSubstitutes = [];
276  // Build a cache for the sub template
277  foreach ($compoundItems as $subpartMarker) {
278  if ($uppercase) {
279  // Use strtr instead of strtoupper to avoid locale problems with Turkish
280  $subpartMarker = strtr($subpartMarker, 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ');
281  }
282  if (!empty($wraps)) {
283  $subpartMarker = $wraps[0] . $subpartMarker . $wraps[1];
284  }
285  $subTemplates[$subpartMarker] = $this->getSubpart($content, $subpartMarker);
286  }
287  // Replace the subpart contents recursively
288  foreach ($compoundItems as $subpartMarker) {
289  $completeMarker = $subpartMarker;
290  if ($uppercase) {
291  // use strtr instead of strtoupper to avoid locale problems with Turkish
292  $completeMarker = strtr($completeMarker, 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ');
293  }
294  if (!empty($wraps)) {
295  $completeMarker = $wraps[0] . $completeMarker . $wraps[1];
296  }
297  if (!empty($markersAndSubparts[$subpartMarker])) {
298  foreach ($markersAndSubparts[$subpartMarker] as $partialMarkersAndSubparts) {
299  $subpartSubstitutes[$completeMarker] .= $this->substituteMarkerAndSubpartArrayRecursive(
300  $subTemplates[$completeMarker],
301  $partialMarkersAndSubparts,
302  $wrap,
303  $uppercase,
304  $deleteUnused
305  );
306  }
307  } else {
308  $subpartSubstitutes[$completeMarker] = '';
309  }
310  }
311  // Substitute the single markers and subparts
312  $result = $this->substituteSubpartArray($content, $subpartSubstitutes);
313  $result = $this->substituteMarkerArray($result, $singleItems, $wrap, $uppercase, $deleteUnused);
314 
315  return $result;
316  }
317 
350  public function substituteMarkerArrayCached($content, array $markContentArray = null, array $subpartContentArray = null, array $wrappedSubpartContentArray = null)
351  {
352  $runtimeCache = $this->getRuntimeCache();
353  // If not arrays then set them
354  if (is_null($markContentArray)) {
355  // Plain markers
356  $markContentArray = [];
357  }
358  if (is_null($subpartContentArray)) {
359  // Subparts being directly substituted
360  $subpartContentArray = [];
361  }
362  if (is_null($wrappedSubpartContentArray)) {
363  // Subparts being wrapped
364  $wrappedSubpartContentArray = [];
365  }
366  // Finding keys and check hash:
367  $sPkeys = array_keys($subpartContentArray);
368  $wPkeys = array_keys($wrappedSubpartContentArray);
369  $keysToReplace = array_merge(array_keys($markContentArray), $sPkeys, $wPkeys);
370  if (empty($keysToReplace)) {
371  return $content;
372  }
373  asort($keysToReplace);
374  $storeKey = md5('substituteMarkerArrayCached_storeKey:' . serialize([$content, $keysToReplace]));
375  $fromCache = $runtimeCache->get($storeKey);
376  if ($fromCache) {
377  $storeArr = $fromCache;
378  } else {
379  $cache = $this->getCache();
380  $storeArrDat = $cache->get($storeKey);
381  if (is_array($storeArrDat)) {
382  $storeArr = $storeArrDat;
383  // Setting the data in the first level cache
384  $runtimeCache->set($storeKey, $storeArr);
385  } else {
386  // Finding subparts and substituting them with the subpart as a marker
387  foreach ($sPkeys as $sPK) {
388  $content = $this->substituteSubpart($content, $sPK, $sPK);
389  }
390  // Finding subparts and wrapping them with markers
391  foreach ($wPkeys as $wPK) {
392  $content = $this->substituteSubpart($content, $wPK, [
393  $wPK,
394  $wPK
395  ]);
396  }
397 
398  $storeArr = [];
399  // search all markers in the content
400  $result = preg_match_all('/###([^#](?:[^#]*+|#{1,2}[^#])+)###/', $content, $markersInContent);
401  if ($result !== false && !empty($markersInContent[1])) {
402  $keysToReplaceFlipped = array_flip($keysToReplace);
403  $regexKeys = [];
404  $wrappedKeys = [];
405  // Traverse keys and quote them for reg ex.
406  foreach ($markersInContent[1] as $key) {
407  if (isset($keysToReplaceFlipped['###' . $key . '###'])) {
408  $regexKeys[] = preg_quote($key, '/');
409  $wrappedKeys[] = '###' . $key . '###';
410  }
411  }
412  $regex = '/###(?:' . implode('|', $regexKeys) . ')###/';
413  $storeArr['c'] = preg_split($regex, $content); // contains all content parts around markers
414  $storeArr['k'] = $wrappedKeys; // contains all markers incl. ###
415  // Setting the data inside the second-level cache
416  $runtimeCache->set($storeKey, $storeArr);
417  // Storing the cached data permanently
418  $cache->set($storeKey, $storeArr, ['substMarkArrayCached'], 0);
419  }
420  }
421  }
422  if (!empty($storeArr['k']) && is_array($storeArr['k'])) {
423  // Substitution/Merging:
424  // Merging content types together, resetting
425  $valueArr = array_merge($markContentArray, $subpartContentArray, $wrappedSubpartContentArray);
426  $wSCA_reg = [];
427  $content = '';
428  // Traversing the keyList array and merging the static and dynamic content
429  foreach ($storeArr['k'] as $n => $keyN) {
430  // add content before marker
431  $content .= $storeArr['c'][$n];
432  if (!is_array($valueArr[$keyN])) {
433  // fetch marker replacement from $markContentArray or $subpartContentArray
434  $content .= $valueArr[$keyN];
435  } else {
436  if (!isset($wSCA_reg[$keyN])) {
437  $wSCA_reg[$keyN] = 0;
438  }
439  // fetch marker replacement from $wrappedSubpartContentArray
440  $content .= $valueArr[$keyN][$wSCA_reg[$keyN] % 2];
441  $wSCA_reg[$keyN]++;
442  }
443  }
444  // add remaining content
445  $content .= $storeArr['c'][count($storeArr['k'])];
446  }
447  return $content;
448  }
449 
458  public function substituteMarkerInObject(&$tree, array $markContentArray)
459  {
460  if (is_array($tree)) {
461  foreach ($tree as $key => $value) {
462  $this->substituteMarkerInObject($tree[$key], $markContentArray);
463  }
464  } else {
465  $tree = $this->substituteMarkerArray($tree, $markContentArray);
466  }
467  return $tree;
468  }
469 
483  public function fillInMarkerArray(array $markContentArray, array $row, $fieldList = '', $nl2br = true, $prefix = 'FIELD_', $htmlSpecialCharsValue = false, $respectXhtml = false)
484  {
485  if ($fieldList) {
486  $fArr = GeneralUtility::trimExplode(',', $fieldList, true);
487  foreach ($fArr as $field) {
488  $markContentArray['###' . $prefix . $field . '###'] = $nl2br ? nl2br($row[$field], $respectXhtml) : $row[$field];
489  }
490  } else {
491  if (is_array($row)) {
492  foreach ($row as $field => $value) {
494  if ($htmlSpecialCharsValue) {
495  $value = htmlspecialchars($value);
496  }
497  $markContentArray['###' . $prefix . $field . '###'] = $nl2br ? nl2br($value, $respectXhtml) : $value;
498  }
499  }
500  }
501  }
502  return $markContentArray;
503  }
504 
510  protected function getCache()
511  {
512  return GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_hash');
513  }
514 
520  protected function getRuntimeCache()
521  {
522  return GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_runtime');
523  }
524 }
substituteMarkerArrayCached($content, array $markContentArray=null, array $subpartContentArray=null, array $wrappedSubpartContentArray=null)
substituteMarkerAndSubpartArrayRecursive($content, array $markersAndSubparts, $wrap='', $uppercase=false, $deleteUnused=false)
static trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
static makeInstance($className,... $constructorArguments)
fillInMarkerArray(array $markContentArray, array $row, $fieldList='', $nl2br=true, $prefix='FIELD_', $htmlSpecialCharsValue=false, $respectXhtml=false)
substituteMarkerArray($content, $markContentArray, $wrap='', $uppercase=false, $deleteUnused=false)
substituteSubpart($content, $marker, $subpartContent, $recursive=true, $keepMarker=false)