4c324567067106177131d7120bcf5b06d3fbc395
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / TimeTracker / TimeTracker.php
1 <?php
2 namespace TYPO3\CMS\Core\TimeTracker;
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 use TYPO3\CMS\Core\Imaging\Icon;
17 use TYPO3\CMS\Core\Imaging\IconFactory;
18 use TYPO3\CMS\Core\Utility\GeneralUtility;
19
20 /**
21 * Frontend Timetracking functions
22 *
23 * Is used to register how much time is used with operations in TypoScript
24 */
25 class TimeTracker
26 {
27
28 /**
29 * If set to true (see constructor) then then the timetracking is enabled
30 * @var bool
31 */
32 protected $isEnabled = false;
33
34 /**
35 * Is loaded with the millisecond time when this object is created
36 *
37 * @var int
38 */
39 public $starttime = 0;
40
41 /**
42 * Log Rendering flag. If set, ->push() and ->pull() is called from the cObj->cObjGetSingle().
43 * This determines whether or not the TypoScript parsing activity is logged. But it also slows down the rendering
44 *
45 * @var bool
46 */
47 public $LR = 1;
48
49 /**
50 * @var array
51 */
52 public $printConf = array(
53 'showParentKeys' => 1,
54 'contentLength' => 10000,
55 // Determines max length of displayed content before it gets cropped.
56 'contentLength_FILE' => 400,
57 // 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.
58 'flag_tree' => 1,
59 'flag_messages' => 1,
60 'flag_queries' => 0,
61 'flag_content' => 0,
62 'allTime' => 0,
63 'keyLgd' => 40
64 );
65
66 /**
67 * @var array
68 */
69 public $wrapError = [
70 0 => ['', ''],
71 1 => ['<strong>', '</strong>'],
72 2 => ['<strong style="color:#ff6600;">', '</strong>'],
73 3 => ['<strong style="color:#ff0000;">', '</strong>']
74 ];
75
76 /**
77 * @var array
78 */
79 public $wrapIcon = [
80 0 => '',
81 1 => 'actions-document-info',
82 2 => 'status-dialog-warning',
83 3 => 'status-dialog-error'
84 ];
85
86 /**
87 * @var int
88 */
89 public $uniqueCounter = 0;
90
91 /**
92 * @var array
93 */
94 public $tsStack = array(array());
95
96 /**
97 * @var int
98 */
99 public $tsStackLevel = 0;
100
101 /**
102 * @var array
103 */
104 public $tsStackLevelMax = array();
105
106 /**
107 * @var array
108 */
109 public $tsStackLog = array();
110
111 /**
112 * @var int
113 */
114 public $tsStackPointer = 0;
115
116 /**
117 * @var array
118 */
119 public $currentHashPointer = array();
120
121 /**
122 * Log entries that take than this number of milliseconds (own time) will be highlighted during log display. Set 0 to disable highlighting.
123 *
124 * @var int
125 */
126 public $highlightLongerThan = 0;
127
128 /*******************************************
129 *
130 * Logging parsing times in the scripts
131 *
132 *******************************************/
133
134 /**
135 * TimeTracker constructor.
136 *
137 * @param bool $isEnabled
138 */
139 public function __construct($isEnabled = true)
140 {
141 $this->isEnabled = $isEnabled;
142 }
143
144 /**
145 * Sets the starting time
146 *
147 * @return void
148 */
149 public function start()
150 {
151 if (!$this->isEnabled) {
152 return;
153 }
154 $this->starttime = $this->getMilliseconds();
155 }
156
157 /**
158 * Pushes an element to the TypoScript tracking array
159 *
160 * @param string $tslabel Label string for the entry, eg. TypoScript property name
161 * @param string $value Additional value(?)
162 * @return void
163 * @see \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::cObjGetSingle(), pull()
164 */
165 public function push($tslabel, $value = '')
166 {
167 if (!$this->isEnabled) {
168 return;
169 }
170 array_push($this->tsStack[$this->tsStackPointer], $tslabel);
171 array_push($this->currentHashPointer, 'timetracker_' . $this->uniqueCounter++);
172 $this->tsStackLevel++;
173 $this->tsStackLevelMax[] = $this->tsStackLevel;
174 // setTSlog
175 $k = end($this->currentHashPointer);
176 $this->tsStackLog[$k] = array(
177 'level' => $this->tsStackLevel,
178 'tsStack' => $this->tsStack,
179 'value' => $value,
180 'starttime' => microtime(true),
181 'stackPointer' => $this->tsStackPointer
182 );
183 }
184
185 /**
186 * Pulls an element from the TypoScript tracking array
187 *
188 * @param string $content The content string generated within the push/pull part.
189 * @return void
190 * @see \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::cObjGetSingle(), push()
191 */
192 public function pull($content = '')
193 {
194 if (!$this->isEnabled) {
195 return;
196 }
197 $k = end($this->currentHashPointer);
198 $this->tsStackLog[$k]['endtime'] = microtime(true);
199 $this->tsStackLog[$k]['content'] = $content;
200 $this->tsStackLevel--;
201 array_pop($this->tsStack[$this->tsStackPointer]);
202 array_pop($this->currentHashPointer);
203 }
204
205 /**
206 * Logs the TypoScript entry
207 *
208 * @param string $content The message string
209 * @param int $num Message type: 0: information, 1: message, 2: warning, 3: error
210 * @return void
211 * @see \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::CONTENT()
212 */
213 public function setTSlogMessage($content, $num = 0)
214 {
215 if (!$this->isEnabled) {
216 return;
217 }
218 end($this->currentHashPointer);
219 $k = current($this->currentHashPointer);
220 // Enlarge the "details" column by adding a span
221 if (strlen($content) > 30) {
222 $placeholder = '<br /><span style="width: 300px; height: 1px; display: inline-block;"></span>';
223 }
224 $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
225 $this->tsStackLog[$k]['message'][] = $iconFactory->getIcon($this->wrapIcon[$num], Icon::SIZE_SMALL)->render() . $this->wrapError[$num][0] . htmlspecialchars($content) . $this->wrapError[$num][1] . $placeholder;
226 }
227
228 /**
229 * Set TSselectQuery - for messages in TypoScript debugger.
230 *
231 * @param array $data Query array
232 * @param string $msg Message/Label to attach
233 * @return void
234 */
235 public function setTSselectQuery(array $data, $msg = '')
236 {
237 if (!$this->isEnabled) {
238 return;
239 }
240 end($this->currentHashPointer);
241 $k = current($this->currentHashPointer);
242 if ($msg !== '') {
243 $data['msg'] = $msg;
244 }
245 $this->tsStackLog[$k]['selectQuery'][] = $data;
246 }
247
248 /**
249 * Increases the stack pointer
250 *
251 * @return void
252 * @see decStackPointer(), \TYPO3\CMS\Frontend\Page\PageGenerator::renderContent(), \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::cObjGetSingle()
253 */
254 public function incStackPointer()
255 {
256 if (!$this->isEnabled) {
257 return;
258 }
259 $this->tsStackPointer++;
260 $this->tsStack[$this->tsStackPointer] = array();
261 }
262
263 /**
264 * Decreases the stack pointer
265 *
266 * @return void
267 * @see incStackPointer(), \TYPO3\CMS\Frontend\Page\PageGenerator::renderContent(), \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::cObjGetSingle()
268 */
269 public function decStackPointer()
270 {
271 if (!$this->isEnabled) {
272 return;
273 }
274 unset($this->tsStack[$this->tsStackPointer]);
275 $this->tsStackPointer--;
276 }
277
278 /**
279 * Gets a microtime value as milliseconds value.
280 *
281 * @param float $microtime The microtime value - if not set the current time is used
282 * @return int The microtime value as milliseconds value
283 */
284 public function getMilliseconds($microtime = null)
285 {
286 if (!$this->isEnabled) {
287 return 0;
288 }
289 if (!isset($microtime)) {
290 $microtime = microtime(true);
291 }
292 return round($microtime * 1000);
293 }
294
295 /**
296 * Gets the difference between a given microtime value and the starting time as milliseconds.
297 *
298 * @param float $microtime The microtime value - if not set the current time is used
299 * @return int The difference between a given microtime value and starting time as milliseconds
300 */
301 public function getDifferenceToStarttime($microtime = null)
302 {
303 return $this->getMilliseconds($microtime) - $this->starttime;
304 }
305
306 /*******************************************
307 *
308 * Printing the parsing time information (for Admin Panel)
309 *
310 *******************************************/
311 /**
312 * Print TypoScript parsing log
313 *
314 * @return string HTML table with the information about parsing times.
315 */
316 public function printTSlog()
317 {
318 if (!$this->isEnabled) {
319 return '';
320 }
321 // Calculate times and keys for the tsStackLog
322 foreach ($this->tsStackLog as $uniqueId => &$data) {
323 $data['endtime'] = $this->getDifferenceToStarttime($data['endtime']);
324 $data['starttime'] = $this->getDifferenceToStarttime($data['starttime']);
325 $data['deltatime'] = $data['endtime'] - $data['starttime'];
326 if (is_array($data['tsStack'])) {
327 $data['key'] = implode($data['stackPointer'] ? '.' : '/', end($data['tsStack']));
328 }
329 }
330 unset($data);
331 // Create hierarchical array of keys pointing to the stack
332 $arr = array();
333 foreach ($this->tsStackLog as $uniqueId => $data) {
334 $this->createHierarchyArray($arr, $data['level'], $uniqueId);
335 }
336 // Parsing the registeret content and create icon-html for the tree
337 $this->tsStackLog[$arr['0.'][0]]['content'] = $this->fixContent($arr['0.'], $this->tsStackLog[$arr['0.'][0]]['content'], '', 0, $arr['0.'][0]);
338 // Displaying the tree:
339 $outputArr = array();
340 $outputArr[] = $this->fw('TypoScript Key');
341 $outputArr[] = $this->fw('Value');
342 if ($this->printConf['allTime']) {
343 $outputArr[] = $this->fw('Time');
344 $outputArr[] = $this->fw('Own');
345 $outputArr[] = $this->fw('Sub');
346 $outputArr[] = $this->fw('Total');
347 } else {
348 $outputArr[] = $this->fw('Own');
349 }
350 $outputArr[] = $this->fw('Details');
351 $out = '';
352 foreach ($outputArr as $row) {
353 $out .= '
354 <th><strong>' . $row . '</strong></th>';
355 }
356 $out = '<tr class="typo3-adminPanel-itemRow">' . $out . '</tr>';
357 $flag_tree = $this->printConf['flag_tree'];
358 $flag_messages = $this->printConf['flag_messages'];
359 $flag_content = $this->printConf['flag_content'];
360 $flag_queries = $this->printConf['flag_queries'];
361 $keyLgd = $this->printConf['keyLgd'];
362 $c = 0;
363 foreach ($this->tsStackLog as $uniqueId => $data) {
364 if ($this->highlightLongerThan && (int)$data['owntime'] > (int)$this->highlightLongerThan) {
365 $logRowClass = 'typo3-adminPanel-logRow-highlight';
366 } else {
367 $logRowClass = $c % 2 ? 'line-odd' : 'line-even';
368 }
369 $logRowClass .= ' typo3-adminPanel-section-content-title';
370 $item = '';
371 // If first...
372 if (!$c) {
373 $data['icons'] = '';
374 $data['key'] = 'Script Start';
375 $data['value'] = '';
376 }
377 // Key label:
378 $keyLabel = '';
379 if (!$flag_tree && $data['stackPointer']) {
380 $temp = array();
381 foreach ($data['tsStack'] as $k => $v) {
382 $temp[] = GeneralUtility::fixed_lgd_cs(implode($v, $k ? '.' : '/'), -$keyLgd);
383 }
384 array_pop($temp);
385 $temp = array_reverse($temp);
386 array_pop($temp);
387 if (!empty($temp)) {
388 $keyLabel = '<br /><span style="color:#999999;">' . implode($temp, '<br />') . '</span>';
389 }
390 }
391 if ($flag_tree) {
392 $tmp = GeneralUtility::trimExplode('.', $data['key'], true);
393 $theLabel = end($tmp);
394 } else {
395 $theLabel = $data['key'];
396 }
397 $theLabel = GeneralUtility::fixed_lgd_cs($theLabel, -$keyLgd);
398 $theLabel = $data['stackPointer'] ? '<span class="stackPointer">' . $theLabel . '</span>' : $theLabel;
399 $keyLabel = $theLabel . $keyLabel;
400 $item .= '<td class="' . $logRowClass . '">' . ($flag_tree ? $data['icons'] : '') . $this->fw($keyLabel) . '</td>';
401 // Key value:
402 $keyValue = $data['value'];
403 $item .= '<td class="' . $logRowClass . ' typo3-adminPanel-tsLogTime">' . $this->fw(htmlspecialchars($keyValue)) . '</td>';
404 if ($this->printConf['allTime']) {
405 $item .= '<td class="' . $logRowClass . ' typo3-adminPanel-tsLogTime"> ' . $this->fw($data['starttime']) . '</td>';
406 $item .= '<td class="' . $logRowClass . ' typo3-adminPanel-tsLogTime"> ' . $this->fw($data['owntime']) . '</td>';
407 $item .= '<td class="' . $logRowClass . ' typo3-adminPanel-tsLogTime"> ' . $this->fw(($data['subtime'] ? '+' . $data['subtime'] : '')) . '</td>';
408 $item .= '<td class="' . $logRowClass . ' typo3-adminPanel-tsLogTime"> ' . $this->fw(($data['subtime'] ? '=' . $data['deltatime'] : '')) . '</td>';
409 } else {
410 $item .= '<td class="' . $logRowClass . ' typo3-adminPanel-tsLogTime"> ' . $this->fw($data['owntime']) . '</td>';
411 }
412 // Messages:
413 $msgArr = array();
414 $msg = '';
415 if ($flag_messages && is_array($data['message'])) {
416 foreach ($data['message'] as $v) {
417 $msgArr[] = nl2br($v);
418 }
419 }
420 if ($flag_queries && is_array($data['selectQuery'])) {
421 $msgArr[] = \TYPO3\CMS\Core\Utility\DebugUtility::viewArray($data['selectQuery']);
422 }
423 if ($flag_content && (string)$data['content'] !== '') {
424 $maxlen = 120;
425 // Break lines which are too longer than $maxlen chars (can happen if content contains long paths...)
426 if (preg_match_all('/(\\S{' . $maxlen . ',})/', $data['content'], $reg)) {
427 foreach ($reg[1] as $key => $match) {
428 $match = preg_replace('/(.{' . $maxlen . '})/', '$1 ', $match);
429 $data['content'] = str_replace($reg[0][$key], $match, $data['content']);
430 }
431 }
432 $msgArr[] = '<span style="color:#000066;">' . nl2br($data['content']) . '</span>';
433 }
434 if (!empty($msgArr)) {
435 $msg = implode($msgArr, '<hr />');
436 }
437 $item .= '<td valign="top" class="' . $logRowClass . '" style="text-align:left;">' . $this->fw($msg) . '</td>';
438 $out .= '<tr class="typo3-adminPanel-itemRow">' . $item . '</tr>';
439 $c++;
440 }
441 $out = '<table class="typo3-adminPanel-table typo3-adminPanel-tsLog">' . $out . '</table>';
442 return $out;
443 }
444
445 /**
446 * Recursively generates the content to display
447 *
448 * @param array $arr Array which is modified with content. Reference
449 * @param string $content Current content string for the level
450 * @param string $depthData Prefixed icons for new PM icons
451 * @param bool $first Set this for the first call from outside.
452 * @param string $vKey Seems to be the previous tsStackLog key
453 * @return string Returns the $content string generated/modified. Also the $arr array is modified!
454 */
455 protected function fixContent(&$arr, $content, $depthData = '', $first = 0, $vKey = '')
456 {
457 $ac = 0;
458 $c = 0;
459 // First, find number of entries
460 foreach ($arr as $k => $v) {
461 if (\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($k)) {
462 $ac++;
463 }
464 }
465 // Traverse through entries
466 $subtime = 0;
467 foreach ($arr as $k => $v) {
468 if (\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($k)) {
469 $c++;
470 $deeper = is_array($arr[$k . '.']) ? 1 : 0;
471 $LN = $ac == $c ? 'blank' : 'line';
472
473 $BTM = $ac == $c ? 'bottom' : '';
474 $PM = is_array($arr[$k . '.']) ? '<i class="fa fa-' . ($deeper ? 'minus' : 'plus') . '-square-o"></i>' : '<span class="treeline-icon treeline-icon-join' . ($BTM ? 'bottom' : '') . '"></span>';
475
476 $this->tsStackLog[$v]['icons'] = $depthData . ($first ? '' : $PM);
477 if ($this->tsStackLog[$v]['content'] !== '') {
478 $content = str_replace($this->tsStackLog[$v]['content'], $v, $content);
479 }
480 if (is_array($arr[$k . '.'])) {
481 $this->tsStackLog[$v]['content'] = $this->fixContent($arr[$k . '.'], $this->tsStackLog[$v]['content'], $depthData . ($first ? '' : '<span class="treeline-icon treeline-icon-' . $LN . '"></span>'), 0, $v);
482 } else {
483 $this->tsStackLog[$v]['content'] = $this->fixCLen($this->tsStackLog[$v]['content'], $this->tsStackLog[$v]['value']);
484 $this->tsStackLog[$v]['subtime'] = '';
485 $this->tsStackLog[$v]['owntime'] = $this->tsStackLog[$v]['deltatime'];
486 }
487 $subtime += $this->tsStackLog[$v]['deltatime'];
488 }
489 }
490 // Set content with special chars
491 if (isset($this->tsStackLog[$vKey])) {
492 $this->tsStackLog[$vKey]['subtime'] = $subtime;
493 $this->tsStackLog[$vKey]['owntime'] = $this->tsStackLog[$vKey]['deltatime'] - $subtime;
494 }
495 $content = $this->fixCLen($content, $this->tsStackLog[$vKey]['value']);
496 // Traverse array again, this time substitute the unique hash with the red key
497 foreach ($arr as $k => $v) {
498 if (\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($k)) {
499 if ($this->tsStackLog[$v]['content'] !== '') {
500 $content = str_replace($v, '<strong style="color:red;">[' . $this->tsStackLog[$v]['key'] . ']</strong>', $content);
501 }
502 }
503 }
504 // Return the content
505 return $content;
506 }
507
508 /**
509 * Wraps the input content string in green colored span-tags IF the length o fthe input string exceeds $this->printConf['contentLength'] (or $this->printConf['contentLength_FILE'] if $v == "FILE"
510 *
511 * @param string $c The content string
512 * @param string $v Command: If "FILE" then $this->printConf['contentLength_FILE'] is used for content length comparison, otherwise $this->printConf['contentLength']
513 * @return string
514 */
515 protected function fixCLen($c, $v)
516 {
517 $len = $v == 'FILE' ? $this->printConf['contentLength_FILE'] : $this->printConf['contentLength'];
518 if (strlen($c) > $len) {
519 $c = '<span style="color:green;">' . htmlspecialchars(GeneralUtility::fixed_lgd_cs($c, $len)) . '</span>';
520 } else {
521 $c = htmlspecialchars($c);
522 }
523 return $c;
524 }
525
526 /**
527 * Wraps input string in a <span> tag
528 *
529 * @param string $str The string to be wrapped
530 * @return string
531 */
532 protected function fw($str)
533 {
534 return '<span>' . $str . '</span>';
535 }
536
537 /**
538 * Helper function for internal data manipulation
539 *
540 * @param array $arr Array (passed by reference) and modified
541 * @param int $pointer Pointer value
542 * @param string $uniqueId Unique ID string
543 * @return void
544 * @access private
545 * @see printTSlog()
546 */
547 protected function createHierarchyArray(&$arr, $pointer, $uniqueId)
548 {
549 if (!is_array($arr)) {
550 $arr = array();
551 }
552 if ($pointer > 0) {
553 end($arr);
554 $k = key($arr);
555 $this->createHierarchyArray($arr[(int)$k . '.'], $pointer - 1, $uniqueId);
556 } else {
557 $arr[] = $uniqueId;
558 }
559 }
560 }