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