‪TYPO3CMS  ‪main
TypoScriptWaterfall.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
8  * It is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License, either version 2
10  * of the License, or any later version.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
19 
20 use Psr\Http\Message\ServerRequestInterface;
31 
38 {
39  protected array ‪$printConf = [
40  'showParentKeys' => true,
41  'contentLength' => 10000,
42  // Determines max length of displayed content before it gets cropped.
43  'contentLength_FILE' => 400,
44  // Determines max length of displayed content FROM FILE cObjects before it gets cropped. Reason is that most FILE cObjects are huge and often used as template-code.
45  'flag_tree' => true,
46  'flag_messages' => true,
47  'flag_content' => false,
48  'allTime' => false,
49  'keyLgd' => 40,
50  ];
51 
56  protected int ‪$highlightLongerThan = 0;
57 
58  public function ‪__construct(
59  private readonly ‪ConfigurationService $configurationService,
60  private readonly ‪TimeTracker $timeTracker,
61  ) {}
62 
63  public function ‪getIdentifier(): string
64  {
65  return 'typoscript-waterfall';
66  }
67 
68  public function ‪getLabel(): string
69  {
70  return $this->‪getLanguageService()->sL(
71  'LLL:EXT:adminpanel/Resources/Private/Language/locallang_tsdebug.xlf:sub.waterfall.label'
72  );
73  }
74 
75  public function ‪enrich(ServerRequestInterface $request): ServerRequestInterface
76  {
77  if ($this->‪getConfigurationOption('forceTemplateParsing')) {
78  $cacheInstruction = $request->getAttribute('frontend.cache.instruction', new ‪CacheInstruction());
79  $cacheInstruction->disableCache('EXT:adminpanel: "Force TS rendering" disables cache.');
80  $request = $request->withAttribute('frontend.cache.instruction', $cacheInstruction);
81  }
82  $this->timeTracker->LR = $this->‪getConfigurationOption('LR');
83  return $request;
84  }
85 
89  public function ‪getContent(‪ModuleData $data): string
90  {
91  $view = GeneralUtility::makeInstance(StandaloneView::class);
92  $templateNameAndPath = 'EXT:adminpanel/Resources/Private/Templates/Modules/TsDebug/TypoScript.html';
93  $view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName($templateNameAndPath));
94  $view->setPartialRootPaths(['EXT:adminpanel/Resources/Private/Partials']);
95 
96  $view->assignMultiple(
97  [
98  'tree' => (int)$this->‪getConfigurationOption('tree'),
99  'display' => [
100  'times' => (int)$this->‪getConfigurationOption('displayTimes'),
101  'messages' => (int)$this->‪getConfigurationOption('displayMessages'),
102  'content' => (int)$this->‪getConfigurationOption('displayContent'),
103  ],
104  'trackContentRendering' => (int)$this->‪getConfigurationOption('LR'),
105  'forceTemplateParsing' => (int)$this->‪getConfigurationOption('forceTemplateParsing'),
106  'typoScriptLog' => $this->‪renderTypoScriptLog(),
107  'languageKey' => $this->‪getBackendUser()->user['lang'] ?? null,
108  ]
109  );
110 
111  return $view->render();
112  }
113 
114  public function ‪getSettings(): string
115  {
116  $view = GeneralUtility::makeInstance(StandaloneView::class);
117  $templateNameAndPath = 'EXT:adminpanel/Resources/Private/Templates/Modules/TsDebug/TypoScriptSettings.html';
118  $view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName($templateNameAndPath));
119  $view->setPartialRootPaths(['EXT:adminpanel/Resources/Private/Partials']);
120 
121  $view->assignMultiple(
122  [
123  'tree' => (int)$this->‪getConfigurationOption('tree'),
124  'display' => [
125  'times' => (int)$this->‪getConfigurationOption('displayTimes'),
126  'messages' => (int)$this->‪getConfigurationOption('displayMessages'),
127  'content' => (int)$this->‪getConfigurationOption('displayContent'),
128  ],
129  'trackContentRendering' => (int)$this->‪getConfigurationOption('LR'),
130  'forceTemplateParsing' => (int)$this->‪getConfigurationOption('forceTemplateParsing'),
131  'languageKey' => $this->‪getBackendUser()->user['lang'] ?? null,
132  ]
133  );
134 
135  return $view->render();
136  }
137 
138  protected function ‪getConfigurationOption(string $option): bool
139  {
140  return (bool)$this->configurationService->getConfigurationOption('tsdebug', $option);
141  }
142 
146  protected function ‪renderTypoScriptLog(): string
147  {
148  $this->printConf['flag_tree'] = $this->‪getConfigurationOption('tree');
149  $this->printConf['allTime'] = $this->‪getConfigurationOption(
150  'displayTimes'
151  );
152  $this->printConf['flag_messages'] = $this->‪getConfigurationOption(
153  'displayMessages'
154  );
155  $this->printConf['flag_content'] = $this->‪getConfigurationOption(
156  'displayContent'
157  );
158  return $this->‪printTSlog();
159  }
160 
166  protected function ‪printTSlog(): string
167  {
168  $timeTracker = $this->timeTracker;
169  if (!$timeTracker->isEnabled()) {
170  return '';
171  }
172  $tsStackLog = $timeTracker->getTypoScriptLogStack();
173  // Calculate times and keys for the tsStackLog
174  foreach ($tsStackLog as &$data) {
175  $data['endtime'] = $timeTracker->getDifferenceToStarttime($data['endtime'] ?? 0);
176  $data['starttime'] = $timeTracker->getDifferenceToStarttime($data['starttime'] ?? 0);
177  $data['deltatime'] = $data['endtime'] - $data['starttime'];
178  if (isset($data['tsStack']) && is_array($data['tsStack'])) {
179  $data['key'] = implode($data['stackPointer'] ? '.' : '/', end($data['tsStack']));
180  }
181  }
182  unset($data);
183  // Create hierarchical array of keys pointing to the stack
184  $arr = [];
185  foreach ($tsStackLog as $uniqueId => $data) {
186  $this->‪createHierarchyArray($arr, $data['level'] ?? 0, $uniqueId);
187  }
188  // Parsing the registered content and create icon-html for the tree
189  $tsStackLog[$arr['0.'][0]]['content'] = $this->‪fixContent($tsStackLog, $arr['0.'], $tsStackLog[$arr['0.'][0]]['content'] ?? '', '', $arr['0.'][0]);
190  // Displaying the tree:
191  $outputArr = [];
192  $outputArr[] = $this->‪fw('TypoScript Key');
193  $outputArr[] = $this->‪fw('Value');
194  if ($this->printConf['allTime']) {
195  $outputArr[] = $this->‪fw('Time');
196  $outputArr[] = $this->‪fw('Own');
197  $outputArr[] = $this->‪fw('Sub');
198  $outputArr[] = $this->‪fw('Total');
199  } else {
200  $outputArr[] = $this->‪fw('Own');
201  }
202  $outputArr[] = $this->‪fw('Details');
203  $out = '';
204  foreach ($outputArr as $row) {
205  $out .= '<th>' . $row . '</th>';
206  }
207  $out = '<thead><tr>' . $out . '</tr></thead>';
208  $flag_tree = $this->printConf['flag_tree'];
209  $flag_messages = $this->printConf['flag_messages'];
210  $flag_content = $this->printConf['flag_content'];
211  $keyLgd = (int)$this->printConf['keyLgd'];
212  $c = 0;
213  foreach ($tsStackLog as $data) {
214  $logRowClass = '';
215  if ($this->highlightLongerThan && (int)$data['owntime'] > ‪$this->highlightLongerThan) {
216  $logRowClass = 'typo3-adminPanel-logRow-highlight';
217  }
218  $item = '';
219  // If first...
220  if (!$c) {
221  $data['icons'] = '';
222  $data['key'] = 'Script Start';
223  $data['value'] = '';
224  }
225  // Key label:
226  $keyLabel = '';
227  $stackPointer = $data['stackPointer'] ?? false;
228  if (!$flag_tree && $stackPointer) {
229  $temp = [];
230  foreach ($data['tsStack'] as $k => $v) {
231  $temp[] = ‪GeneralUtility::fixed_lgd_cs(implode($k ? '.' : '/', $v), -$keyLgd);
232  }
233  array_pop($temp);
234  $temp = array_reverse($temp);
235  array_pop($temp);
236  if (!empty($temp)) {
237  $keyLabel = '<br /><span style="color:#999999;">' . implode('<br />', $temp) . '</span>';
238  }
239  }
240  if ($flag_tree) {
241  $tmp = ‪GeneralUtility::trimExplode('.', $data['key'], true);
242  $theLabel = end($tmp);
243  } else {
244  $theLabel = $data['key'];
245  }
246  $theLabel = ‪GeneralUtility::fixed_lgd_cs($theLabel, -$keyLgd);
247  $theLabel = $stackPointer ? '<span class="stackPointer">' . $theLabel . '</span>' : $theLabel;
248  $keyLabel = $theLabel . $keyLabel;
249  $item .= '<th scope="row" class="typo3-adminPanel-table-cell-key ' . $logRowClass . '">' . ($flag_tree ? $data['icons'] : '') . $this->‪fw($keyLabel) . '</th>';
250  // Key value:
251  $keyValue = $data['value'];
252  $item .= '<td class="' . $logRowClass . ' typo3-adminPanel-tsLogTime">' . $this->‪fw(htmlspecialchars($keyValue)) . '</td>';
253  $ownTime = (string)($data['owntime'] ?? '');
254  if ($this->printConf['allTime']) {
255  $item .= '<td class="' . $logRowClass . ' typo3-adminPanel-tsLogTime"> ' . $this->‪fw((string)$data['starttime']) . '</td>';
256  $item .= '<td class="' . $logRowClass . ' typo3-adminPanel-tsLogTime"> ' . $this->‪fw($ownTime) . '</td>';
257  $item .= '<td class="' . $logRowClass . ' typo3-adminPanel-tsLogTime"> ' . $this->‪fw(($data['subtime'] ? '+' . $data['subtime'] : '')) . '</td>';
258  $item .= '<td class="' . $logRowClass . ' typo3-adminPanel-tsLogTime"> ' . $this->‪fw(($data['subtime'] ? '=' . $data['deltatime'] : '')) . '</td>';
259  } else {
260  $item .= '<td class="' . $logRowClass . ' typo3-adminPanel-tsLogTime"> ' . $this->‪fw($ownTime) . '</td>';
261  }
262  // Messages:
263  $msgArr = [];
264  $msg = '';
265  if ($flag_messages && is_array($data['message'] ?? null)) {
266  foreach ($data['message'] as $v) {
267  $msgArr[] = nl2br($v);
268  }
269  }
270  if ($flag_content && (string)$data['content'] !== '') {
271  $maxlen = 120;
272  // Break lines which are longer than $maxlen chars (can happen if content contains long paths...)
273  if (preg_match_all('/(\\S{' . $maxlen . ',})/', $data['content'], $reg)) {
274  foreach ($reg[1] as $key => $match) {
275  $match = preg_replace('/(.{' . $maxlen . '})/', '$1 ', $match);
276  $data['content'] = str_replace($reg[0][$key], $match, $data['content']);
277  }
278  }
279  $msgArr[] = nl2br($data['content']);
280  }
281  if (!empty($msgArr)) {
282  $msg = implode('<br>', $msgArr);
283  }
284  $item .= '<td class="typo3-adminPanel-table-cell-content">' . $this->‪fw($msg) . '</td>';
285  $out .= '<tr>' . $item . '</tr>';
286  $c++;
287  }
288  return '<div class="typo3-adminPanel-table-overflow"><table class="typo3-adminPanel-table typo3-adminPanel-table-debug">' . $out . '</table></div>';
289  }
290 
300  protected function ‪fixContent(array &$tsStackLog, array &$arr, string $content, string $depthData = '', string $vKey = ''): string
301  {
302  $entriesCount = 0;
303  $c = 0;
304  // First, find number of entries
305  foreach ($arr as $k => $v) {
306  //do not count subentries (the one ending with dot, eg. '9.'
308  $entriesCount++;
309  }
310  }
311  // Traverse through entries
312  $subtime = 0;
313  foreach ($arr as $k => $v) {
315  $c++;
316  $hasChildren = isset($arr[$k . '.']);
317  $lastEntry = $entriesCount === $c;
318 
319  $PM = '<span class="treeline-icon treeline-icon-join' . ($lastEntry ? 'bottom' : '') . '"></span>';
320 
321  $tsStackLog[$v]['icons'] = $depthData . $PM;
322  if (($tsStackLog[$v]['content'] ?? '') !== '') {
323  $content = str_replace($tsStackLog[$v]['content'], $v, $content);
324  }
325  if ($hasChildren) {
326  $lineClass = $lastEntry ? 'treeline-icon-clear' : 'treeline-icon-line';
327  $tsStackLog[$v]['content'] = $this->‪fixContent(
328  $tsStackLog,
329  $arr[$k . '.'],
330  ($tsStackLog[$v]['content'] ?? ''),
331  $depthData . '<span class="treeline-icon ' . $lineClass . '"></span>',
332  $v
333  );
334  } else {
335  $tsStackLog[$v]['content'] = $this->‪fixCLen(($tsStackLog[$v]['content'] ?? ''), $tsStackLog[$v]['value']);
336  $tsStackLog[$v]['subtime'] = '';
337  $tsStackLog[$v]['owntime'] = $tsStackLog[$v]['deltatime'];
338  }
339  $subtime += $tsStackLog[$v]['deltatime'];
340  }
341  }
342  // Set content with special chars
343  if (isset($tsStackLog[$vKey])) {
344  $tsStackLog[$vKey]['subtime'] = $subtime;
345  $tsStackLog[$vKey]['owntime'] = $tsStackLog[$vKey]['deltatime'] - $subtime;
346  }
347  $content = $this->‪fixCLen($content, $tsStackLog[$vKey]['value']);
348  // Traverse array again, this time substitute the unique hash with the red key
349  foreach ($arr as $k => $v) {
351  if ($tsStackLog[$v]['content'] !== '') {
352  $content = str_replace($v, '<strong style="color:red;">[' . $tsStackLog[$v]['key'] . ']</strong>', $content);
353  }
354  }
355  }
356  // Return the content
357  return $content;
358  }
359 
366  protected function ‪fixCLen(string $c, string $v): string
367  {
368  $len = (int)($v === 'FILE' ? $this->printConf['contentLength_FILE'] : $this->printConf['contentLength']);
369  if (strlen($c) > $len) {
370  $c = '<span style="color:green;">' . htmlspecialchars(‪GeneralUtility::fixed_lgd_cs($c, $len)) . '</span>';
371  } else {
372  $c = htmlspecialchars($c);
373  }
374  return $c;
375  }
376 
382  protected function ‪fw(string $str): string
383  {
384  return '<span>' . $str . '</span>';
385  }
386 
395  protected function ‪createHierarchyArray(array &$arr, int $pointer, string $uniqueId): void
396  {
397  if ($pointer > 0) {
398  end($arr);
399  $k = key($arr);
400  if (!is_array($arr[(int)$k . '.'] ?? null)) {
401  $arr[(int)$k . '.'] = [];
402  }
403  $this->‪createHierarchyArray($arr[(int)$k . '.'], $pointer - 1, $uniqueId);
404  } else {
405  $arr[] = $uniqueId;
406  }
407  }
408 }
‪TYPO3\CMS\Adminpanel\Modules\TsDebug\TypoScriptWaterfall\fixContent
‪string fixContent(array &$tsStackLog, array &$arr, string $content, string $depthData='', string $vKey='')
Definition: TypoScriptWaterfall.php:300
‪TYPO3\CMS\Adminpanel\Modules\TsDebug
Definition: TypoScriptWaterfall.php:18
‪TYPO3\CMS\Adminpanel\Modules\TsDebug\TypoScriptWaterfall\getConfigurationOption
‪getConfigurationOption(string $option)
Definition: TypoScriptWaterfall.php:138
‪TYPO3\CMS\Adminpanel\Modules\TsDebug\TypoScriptWaterfall
Definition: TypoScriptWaterfall.php:38
‪TYPO3\CMS\Core\Utility\GeneralUtility\fixed_lgd_cs
‪static string fixed_lgd_cs(string $string, int $chars, string $appendString='...')
Definition: GeneralUtility.php:92
‪TYPO3\CMS\Adminpanel\Modules\TsDebug\TypoScriptWaterfall\getContent
‪getContent(ModuleData $data)
Definition: TypoScriptWaterfall.php:89
‪TYPO3\CMS\Adminpanel\Modules\TsDebug\TypoScriptWaterfall\getIdentifier
‪getIdentifier()
Definition: TypoScriptWaterfall.php:63
‪TYPO3\CMS\Adminpanel\Modules\TsDebug\TypoScriptWaterfall\$printConf
‪array $printConf
Definition: TypoScriptWaterfall.php:39
‪TYPO3\CMS\Adminpanel\ModuleApi\ModuleSettingsProviderInterface
Definition: ModuleSettingsProviderInterface.php:34
‪TYPO3\CMS\Adminpanel\ModuleApi\AbstractSubModule\getBackendUser
‪getBackendUser()
Definition: AbstractSubModule.php:35
‪TYPO3\CMS\Core\Utility\MathUtility\canBeInterpretedAsInteger
‪static bool canBeInterpretedAsInteger(mixed $var)
Definition: MathUtility.php:69
‪TYPO3\CMS\Adminpanel\Service\ConfigurationService
Definition: ConfigurationService.php:34
‪TYPO3\CMS\Adminpanel\Modules\TsDebug\TypoScriptWaterfall\__construct
‪__construct(private readonly ConfigurationService $configurationService, private readonly TimeTracker $timeTracker,)
Definition: TypoScriptWaterfall.php:58
‪TYPO3\CMS\Adminpanel\Modules\TsDebug\TypoScriptWaterfall\createHierarchyArray
‪createHierarchyArray(array &$arr, int $pointer, string $uniqueId)
Definition: TypoScriptWaterfall.php:395
‪TYPO3\CMS\Fluid\View\StandaloneView
Definition: StandaloneView.php:30
‪TYPO3\CMS\Adminpanel\Modules\TsDebug\TypoScriptWaterfall\fixCLen
‪fixCLen(string $c, string $v)
Definition: TypoScriptWaterfall.php:366
‪TYPO3\CMS\Adminpanel\Modules\TsDebug\TypoScriptWaterfall\renderTypoScriptLog
‪renderTypoScriptLog()
Definition: TypoScriptWaterfall.php:146
‪TYPO3\CMS\Adminpanel\Modules\TsDebug\TypoScriptWaterfall\getLabel
‪getLabel()
Definition: TypoScriptWaterfall.php:68
‪TYPO3\CMS\Adminpanel\ModuleApi\AbstractSubModule\getLanguageService
‪getLanguageService()
Definition: AbstractSubModule.php:30
‪TYPO3\CMS\Frontend\Cache\CacheInstruction
Definition: CacheInstruction.php:29
‪TYPO3\CMS\Core\Utility\MathUtility
Definition: MathUtility.php:24
‪TYPO3\CMS\Adminpanel\ModuleApi\ModuleData
Definition: ModuleData.php:26
‪TYPO3\CMS\Adminpanel\Modules\TsDebug\TypoScriptWaterfall\$highlightLongerThan
‪int $highlightLongerThan
Definition: TypoScriptWaterfall.php:56
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Adminpanel\Modules\TsDebug\TypoScriptWaterfall\fw
‪fw(string $str)
Definition: TypoScriptWaterfall.php:382
‪TYPO3\CMS\Core\TimeTracker\TimeTracker
Definition: TimeTracker.php:34
‪TYPO3\CMS\Adminpanel\Modules\TsDebug\TypoScriptWaterfall\enrich
‪enrich(ServerRequestInterface $request)
Definition: TypoScriptWaterfall.php:75
‪TYPO3\CMS\Adminpanel\Modules\TsDebug\TypoScriptWaterfall\printTSlog
‪string printTSlog()
Definition: TypoScriptWaterfall.php:166
‪TYPO3\CMS\Adminpanel\ModuleApi\AbstractSubModule
Definition: AbstractSubModule.php:29
‪TYPO3\CMS\Adminpanel\Modules\TsDebug\TypoScriptWaterfall\getSettings
‪getSettings()
Definition: TypoScriptWaterfall.php:114
‪TYPO3\CMS\Core\Utility\GeneralUtility\trimExplode
‪static list< string > trimExplode(string $delim, string $string, bool $removeEmptyValues=false, int $limit=0)
Definition: GeneralUtility.php:822
‪TYPO3\CMS\Adminpanel\ModuleApi\RequestEnricherInterface
Definition: RequestEnricherInterface.php:37