|
1 <?php |
|
2 /** |
|
3 * debugConsole class |
|
4 * |
|
5 * This class allows opening an external JavaScript |
|
6 * window for debugging purposes. |
|
7 * |
|
8 * @author Andreas Demmer <info@debugconsole.de> |
|
9 * @see <http://www.debugconsole.de> |
|
10 * @version 1.2.1 |
|
11 * @package debugConsole_1.2.1 |
|
12 */ |
|
13 class debugConsole { |
|
14 /** |
|
15 * events which are shown in debug console |
|
16 * |
|
17 * @var array |
|
18 */ |
|
19 protected $filters; |
|
20 |
|
21 /** |
|
22 * all watched variables with their current content |
|
23 * |
|
24 * @var array |
|
25 */ |
|
26 protected $watches; |
|
27 |
|
28 /** |
|
29 * debugConsole configuration values |
|
30 * |
|
31 * @var array |
|
32 */ |
|
33 protected $config; |
|
34 |
|
35 /** |
|
36 * URL where template can be found |
|
37 * |
|
38 * @var string |
|
39 */ |
|
40 protected $template; |
|
41 |
|
42 /** |
|
43 * javascripts to control popup |
|
44 * |
|
45 * @var array |
|
46 */ |
|
47 protected $javascripts; |
|
48 |
|
49 /** |
|
50 * html for popup |
|
51 * |
|
52 * @var array |
|
53 */ |
|
54 protected $html; |
|
55 |
|
56 /** |
|
57 * time of debugrun start in milliseconds |
|
58 * |
|
59 * @var string |
|
60 */ |
|
61 protected $starttime; |
|
62 |
|
63 /** |
|
64 * time of timer start in milliseconds |
|
65 * |
|
66 * @var array |
|
67 */ |
|
68 protected $timers; |
|
69 |
|
70 /** |
|
71 * constructor, opens popup window |
|
72 */ |
|
73 public function __construct () { |
|
74 /* initialize class vars */ |
|
75 $this->starttime = $this->getMicrotime(); |
|
76 $this->watches = array (); |
|
77 $this->config = $GLOBALS['_debugConsoleConfig']; |
|
78 $this->html = $this->config['html']; |
|
79 $this->html['header'] = str_replace("\n\r", NULL, $this->html['header']); |
|
80 $this->html['header'] = str_replace("\n", NULL, $this->html['header']); |
|
81 $this->javascripts = $this->config['javascripts']; |
|
82 |
|
83 /* replace PHP's errorhandler */ |
|
84 $errorhandler = array ( |
|
85 $this, |
|
86 'errorHandlerCallback' |
|
87 ); |
|
88 |
|
89 set_error_handler($errorhandler); |
|
90 |
|
91 /* open popup */ |
|
92 $popupOptions = "', 'debugConsole', 'width=" . $this->config['dimensions']['width'] . ",height=" . $this->config['dimensions']['height'] . ',scrollbars=yes'; |
|
93 |
|
94 $this->sendCommand('openPopup', $popupOptions); |
|
95 $this->sendCommand('write', $this->html['header']); |
|
96 |
|
97 $this->startDebugRun(); |
|
98 } |
|
99 |
|
100 /** |
|
101 * destructor, shows runtime and finishes html document in popup window |
|
102 */ |
|
103 public function __destruct () { |
|
104 $runtime = $this->getMicrotime() - $this->starttime; |
|
105 $runtime = number_format((float)$runtime, 4, '.', NULL); |
|
106 |
|
107 $info = '<p class="runtime">This debug-run took ' . $runtime . ' seconds to complete.</p>'; |
|
108 |
|
109 $this->sendCommand('write', $info); |
|
110 $this->sendCommand('write', '</div>'); |
|
111 $this->sendCommand('scroll', "0','100000"); |
|
112 $this->sendCommand('write', $this->html['footer']); |
|
113 |
|
114 if ($this->config['focus']) { |
|
115 $this->sendCommand('focus'); |
|
116 } |
|
117 } |
|
118 |
|
119 /** |
|
120 * show new debug run header in console |
|
121 */ |
|
122 |
|
123 protected function startDebugRun () { |
|
124 $info = '<h1>new debug-run (' . date('H:i') . ' hours)</h1>'; |
|
125 $this->sendCommand('write', '<div>'); |
|
126 $this->sendCommand('write', $info); |
|
127 } |
|
128 |
|
129 /** |
|
130 * adds a variable to the watchlist |
|
131 * |
|
132 * Watched variables must be in a declare(ticks=n) |
|
133 * block so that every n ticks the watched variables |
|
134 * are checked for changes. If any changes were made, |
|
135 * the new value of the variable is shown in the |
|
136 * debugConsole with additional information where the |
|
137 * changes happened. |
|
138 * |
|
139 * @param string $variableName |
|
140 */ |
|
141 public function watchVariable ($variableName) { |
|
142 if (count($this->watches) === 0) { |
|
143 $watchMethod = array ( |
|
144 $this, |
|
145 'watchesCallback' |
|
146 ); |
|
147 |
|
148 register_tick_function($watchMethod); |
|
149 } |
|
150 |
|
151 if (isset($GLOBALS[$variableName])) { |
|
152 $this->watches[$variableName] = $GLOBALS[$variableName]; |
|
153 } else { |
|
154 $this->watches[$variableName] = NULL; |
|
155 } |
|
156 } |
|
157 |
|
158 /** |
|
159 * tick callback: process watches and show changes |
|
160 */ |
|
161 public function watchesCallback () { |
|
162 if ($this->config['filters']['watches']) { |
|
163 foreach ($this->watches as $variableName => $variableValue) { |
|
164 if ($GLOBALS[$variableName] !== $this->watches[$variableName]) { |
|
165 $info = '<p class="watch"><strong>$' . $variableName; |
|
166 $info .= '</strong> changed from "'; |
|
167 $info .= $this->watches[$variableName]; |
|
168 $info .= '" (' . gettype($this->watches[$variableName]) . ')'; |
|
169 $info .= ' to "' . $GLOBALS[$variableName] . '" ('; |
|
170 $info .= gettype($GLOBALS[$variableName]) . ')'; |
|
171 $info .= $this->getTraceback() . '</p>'; |
|
172 |
|
173 $this->watches[$variableName] = $GLOBALS[$variableName]; |
|
174 $this->sendCommand('write', $info); |
|
175 } |
|
176 } |
|
177 } |
|
178 } |
|
179 |
|
180 /** |
|
181 * sends a javascript command to browser |
|
182 * |
|
183 * @param string $command |
|
184 * @param string $value |
|
185 */ |
|
186 protected function sendCommand ($command, $value = FALSE) { |
|
187 if($command == 'write') $value = '\'+unescape(\''.rawurlencode($value).'\')+\''; |
|
188 $value = str_replace('\\', '\\\\', $value); |
|
189 $value = nl2br($value); |
|
190 |
|
191 if ((bool)$value) { |
|
192 /* write optionally logfile */ |
|
193 $this->writeLogfileEntry($command, $value); |
|
194 |
|
195 $command = $this->javascripts[$command] . "('" . $value . "');"; |
|
196 } else { |
|
197 $command = $this->javascripts[$command] . ';'; |
|
198 } |
|
199 |
|
200 $command = str_replace("\n\r", NULL, $command); |
|
201 $command = str_replace("\n", NULL, $command); |
|
202 |
|
203 if (!$this->config['logfile']['disablePopup']) { |
|
204 echo $this->javascripts['openTag'], "\n"; |
|
205 echo $command, "\n"; |
|
206 echo $this->javascripts['closeTag'], "\n"; |
|
207 } |
|
208 |
|
209 flush(); |
|
210 } |
|
211 |
|
212 /** |
|
213 * writes html output as text entry into logfile |
|
214 * |
|
215 * @param string $command |
|
216 * @param string $value |
|
217 */ |
|
218 protected function writeLogfileEntry ($command, $value) { |
|
219 if ($this->config['logfile']['enable']) { |
|
220 $logfile = $this->config['logfile']['path'] . $this->config['logfile']['filename']; |
|
221 /* log only useful entries, no html header and footer */ |
|
222 if ( |
|
223 $command === 'write' |
|
224 && !strpos($value, '<html>') |
|
225 && !strpos($value, '</html>') |
|
226 ) { |
|
227 /* convert html to text */ |
|
228 $value = html_entity_decode($value); |
|
229 $value = str_replace('>', '> ', $value); |
|
230 $value = strip_tags($value); |
|
231 |
|
232 $fp = fopen($logfile, 'a+'); |
|
233 fputs($fp, $value . "\n\n"); |
|
234 fclose($fp); |
|
235 } elseif (strpos($value, '</html>')) { |
|
236 $fp = fopen($logfile, 'a+'); |
|
237 fputs($fp, "-----------\n"); |
|
238 fclose($fp); |
|
239 } |
|
240 } |
|
241 } |
|
242 |
|
243 /** |
|
244 * shows in console that a checkpoint has been passed, |
|
245 * additional info is the file and line which triggered |
|
246 * the output |
|
247 * |
|
248 * @param string $message |
|
249 */ |
|
250 public function passedCheckpoint ($message = NULL) { |
|
251 if ($this->config['filters']['checkpoints']) { |
|
252 $message = (bool)$message ? $message : 'Checkpoint passed!'; |
|
253 |
|
254 $info = '<p class="checkpoint"><strong>' . $message . '</strong>'; |
|
255 $info .= $this->getTraceback() . '</p>'; |
|
256 |
|
257 $this->sendCommand('write', $info); |
|
258 } |
|
259 } |
|
260 |
|
261 /** |
|
262 * returns microtime as float value |
|
263 * |
|
264 * @return float |
|
265 */ |
|
266 protected function getMicrotime () { |
|
267 list($usec, $sec) = explode(' ', microtime()); |
|
268 return ((float)$usec + (float)$sec); |
|
269 } |
|
270 |
|
271 /** |
|
272 * returns all possible filter events for debugConsole::setFilter() method |
|
273 * |
|
274 * @return array |
|
275 */ |
|
276 public function getFilters () { |
|
277 $filters = array_keys($this->config['filters']); |
|
278 |
|
279 ksort($filters); |
|
280 reset($filters); |
|
281 |
|
282 return $filters; |
|
283 } |
|
284 |
|
285 /** |
|
286 * shows or hides an event-type in debugConsole, |
|
287 * returns previous setting of the given event-type |
|
288 * |
|
289 * @param string $event |
|
290 * @param bool $isShown |
|
291 * @return bool |
|
292 */ |
|
293 public function setFilter ($event, $isShown) { |
|
294 if (array_key_exists($event, $this->config['filters'])) { |
|
295 $oldValue = $this->config['filters'][$event]; |
|
296 $this->config['filters'][$event] = $isShown; |
|
297 } else { |
|
298 throw new Exception ('debugConsole: unknown event "' . $event . '" in debugConsole::filter()'); |
|
299 } |
|
300 |
|
301 return $oldValue; |
|
302 } |
|
303 |
|
304 /** |
|
305 * show debug info for variable in debugConsole, |
|
306 * added by custom text for documentation and hints |
|
307 * |
|
308 * @param mixed $variable |
|
309 * @param string $text |
|
310 */ |
|
311 public function dump ($variable, $text) { |
|
312 if ($this->config['filters']['debug']) { |
|
313 @ob_start(); |
|
314 |
|
315 /* grab current ob content */ |
|
316 $obContents = ob_get_contents(); |
|
317 ob_clean(); |
|
318 |
|
319 /* grap var dump from ob */ |
|
320 var_dump($variable); |
|
321 $variableDebug = ob_get_contents(); |
|
322 ob_end_clean(); |
|
323 |
|
324 /* restore previous ob content */ |
|
325 if ((bool)$obContents) echo $obContents; |
|
326 |
|
327 /* render debug */ |
|
328 $variableDebug = htmlspecialchars($variableDebug); |
|
329 $infos = '<p class="dump">' . $text . '<br />'; |
|
330 |
|
331 if (is_array($variable)) { |
|
332 $variableDebug = str_replace(' ', ' ', $variableDebug); |
|
333 $infos .= '<span class="source">' . $variableDebug . '</span>'; |
|
334 } else { |
|
335 $infos .= '<strong>' . $variableDebug . '</strong>'; |
|
336 } |
|
337 |
|
338 $infos .= $this->getTraceback() . '</p>'; |
|
339 $this->sendCommand('write', $infos); |
|
340 } |
|
341 } |
|
342 |
|
343 /** |
|
344 * callback method for PHP errorhandling |
|
345 * |
|
346 * @todo implement more errorlevels |
|
347 */ |
|
348 public function errorHandlerCallback () { |
|
349 $details = func_get_args(); |
|
350 $details[1] = str_replace("'", '"', $details[1]); |
|
351 $details[1] = str_replace('href="function.', 'target="_blank" href="http://www.php.net/', $details[1]); |
|
352 |
|
353 |
|
354 /* determine error level */ |
|
355 switch ($details[0]) { |
|
356 case 2: |
|
357 if (!$this->config['filters']['php_warnings']) return; |
|
358 $errorlevel = 'warning'; |
|
359 break; |
|
360 case 8: |
|
361 if (!$this->config['filters']['php_notices']) return; |
|
362 $errorlevel = 'notice'; |
|
363 break; |
|
364 case 2048: |
|
365 if (!$this->config['filters']['php_suggestions']) return; |
|
366 $errorlevel = 'suggestion'; |
|
367 break; |
|
368 } |
|
369 |
|
370 $file = $this->cropScriptPath($details[2]); |
|
371 |
|
372 $infos = '<p class="' . $errorlevel . '"><strong>'; |
|
373 $infos .= 'PHP ' . strtoupper($errorlevel) . '</strong>'; |
|
374 $infos .= $details[1] . '<span class="backtrace">'; |
|
375 $infos .= $file . ' on line '; |
|
376 $infos .= $details[3] . '</span></p>'; |
|
377 |
|
378 $this->sendCommand('write', $infos); |
|
379 } |
|
380 |
|
381 /** |
|
382 * start timer clock, returns timer handle |
|
383 * |
|
384 * @return mixed |
|
385 * @param string $comment |
|
386 */ |
|
387 public function startTimer ($comment) { |
|
388 if ($this->config['filters']['timers']) { |
|
389 $timerHandle = md5(microtime()); |
|
390 |
|
391 $this->timers[$timerHandle] = array ( |
|
392 'starttime' => $this->getMicrotime(), |
|
393 'comment' => $comment |
|
394 ); |
|
395 } else { |
|
396 $timerHandle = FALSE; |
|
397 } |
|
398 |
|
399 return $timerHandle; |
|
400 } |
|
401 |
|
402 /** |
|
403 * stop timer clock |
|
404 * |
|
405 * @return bool |
|
406 * @param string $timerHandle |
|
407 */ |
|
408 public function stopTimer ($timerHandle) { |
|
409 if ($this->config['filters']['timers']) { |
|
410 if (array_key_exists($timerHandle, $this->timers)) { |
|
411 $timerExists = TRUE; |
|
412 $timespan = $this->getMicrotime() - $this->timers[$timerHandle]['starttime']; |
|
413 |
|
414 $info = '<p class="timer"><strong>' . $this->timers[$timerHandle]['comment']; |
|
415 $info .= '</strong><br />The timer ran '; |
|
416 $info .= '<strong>' . number_format ($timespan, 4, '.', NULL) . '</strong>'; |
|
417 $info .= ' seconds.' . $this->getTraceback() . '</p>'; |
|
418 |
|
419 $this->sendCommand('write', $info); |
|
420 } else { |
|
421 $timerExists = FALSE; |
|
422 } |
|
423 } else { |
|
424 $timerExists = FALSE; |
|
425 } |
|
426 |
|
427 return $timerExists; |
|
428 } |
|
429 |
|
430 /** |
|
431 * returns a formatted traceback string |
|
432 * |
|
433 * @return string |
|
434 */ |
|
435 public function getTraceback () { |
|
436 $callStack = debug_backtrace(); |
|
437 |
|
438 $debugConsoleFiles = array( |
|
439 'debugConsole.class.php', |
|
440 'debugConsole.functions.php' |
|
441 ); |
|
442 |
|
443 $call = array ( |
|
444 'file' => 'debugConsole.class.php' |
|
445 ); |
|
446 |
|
447 while(in_array(basename($call['file']), $debugConsoleFiles)) { |
|
448 $call = array_shift($callStack); |
|
449 } |
|
450 |
|
451 $call['file'] = $this->cropScriptPath($call['file']); |
|
452 |
|
453 $traceback = '<span class="backtrace">'; |
|
454 $traceback .= $call['file'] . ' on line '; |
|
455 $traceback .= $call['line'] . '</span>'; |
|
456 |
|
457 return $traceback; |
|
458 } |
|
459 |
|
460 /** |
|
461 * crops long script path, shows only the last $maxLength chars |
|
462 * |
|
463 * @param string $path |
|
464 * @param int $maxLength |
|
465 * @return string |
|
466 */ |
|
467 protected function cropScriptPath ($path, $maxLength = 30) { |
|
468 if (strlen($path) > $maxLength) { |
|
469 $startPos = strlen($path) - $maxLength - 2; |
|
470 |
|
471 if ($startPos > 0) { |
|
472 $path = '...' . substr($path, $startPos); |
|
473 } |
|
474 } |
|
475 |
|
476 return $path; |
|
477 } |
|
478 } |
|
479 ?> |