1: <?php
2:
3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25:
26: 27: 28: 29:
30: if (!class_exists('Object')) {
31: uses('object');
32: }
33: if (!class_exists('CakeLog')) {
34: uses('cake_log');
35: }
36: 37: 38: 39: 40: 41: 42: 43: 44:
45: class Debugger extends Object {
46: 47: 48: 49: 50: 51:
52: var $errors = array();
53: 54: 55: 56: 57: 58:
59: var $helpPath = null;
60: 61: 62: 63: 64: 65:
66: var $_outputFormat = 'js';
67: 68: 69: 70: 71: 72:
73: var $__data = array();
74: 75: 76: 77:
78: function __construct() {
79: $docRef = ini_get('docref_root');
80: if (empty($docRef)) {
81: ini_set('docref_root', 'http://php.net/');
82: }
83: if (!defined('E_RECOVERABLE_ERROR')) {
84: define('E_RECOVERABLE_ERROR', 4096);
85: }
86: }
87: 88: 89: 90: 91: 92: 93:
94: function &getInstance($class = null) {
95: static $instance = array();
96: if (!empty($class)) {
97: if (!$instance || strtolower($class) != strtolower(get_class($instance[0]))) {
98: $instance[0] = & new $class();
99: if (Configure::read() > 0) {
100: Configure::version();
101: $instance[0]->helpPath = Configure::read('Cake.Debugger.HelpPath');
102: }
103: }
104: }
105:
106: if (!$instance) {
107: $instance[0] =& new Debugger();
108: if (Configure::read() > 0) {
109: Configure::version();
110: $instance[0]->helpPath = Configure::read('Cake.Debugger.HelpPath');
111: }
112: }
113: return $instance[0];
114: }
115: 116: 117: 118: 119: 120: 121: 122: 123: 124:
125: function dump($var) {
126: $_this = Debugger::getInstance();
127: pr($_this->exportVar($var));
128: }
129: 130: 131: 132: 133: 134: 135: 136: 137: 138:
139: function log($var, $level = LOG_DEBUG) {
140: $_this = Debugger::getInstance();
141: $source = $_this->trace(array('start' => 1)) . "\n";
142: CakeLog::write($level, "\n" . $source . $_this->exportVar($var));
143: }
144:
145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155:
156: function handleError($code, $description, $file = null, $line = null, $context = null) {
157: if (error_reporting() == 0 || $code === 2048 || $code === 8192) {
158: return;
159: }
160:
161: $_this = Debugger::getInstance();
162:
163: if (empty($file)) {
164: $file = '[internal]';
165: }
166: if (empty($line)) {
167: $line = '??';
168: }
169: $file = $_this->trimPath($file);
170:
171: $info = compact('code', 'description', 'file', 'line');
172: if (!in_array($info, $_this->errors)) {
173: $_this->errors[] = $info;
174: } else {
175: return;
176: }
177:
178: $level = LOG_DEBUG;
179: switch ($code) {
180: case E_PARSE:
181: case E_ERROR:
182: case E_CORE_ERROR:
183: case E_COMPILE_ERROR:
184: case E_USER_ERROR:
185: $error = 'Fatal Error';
186: $level = LOG_ERROR;
187: break;
188: case E_WARNING:
189: case E_USER_WARNING:
190: case E_COMPILE_WARNING:
191: case E_RECOVERABLE_ERROR:
192: $error = 'Warning';
193: $level = LOG_WARNING;
194: break;
195: case E_NOTICE:
196: case E_USER_NOTICE:
197: $error = 'Notice';
198: $level = LOG_NOTICE;
199: break;
200: default:
201: return false;
202: break;
203: }
204:
205: $helpCode = null;
206: if (!empty($_this->helpPath) && preg_match('/.*\[([0-9]+)\]$/', $description, $codes)) {
207: if (isset($codes[1])) {
208: $helpCode = $codes[1];
209: $description = trim(preg_replace('/\[[0-9]+\]$/', '', $description));
210: }
211: }
212:
213: echo $_this->_output($level, $error, $code, $helpCode, $description, $file, $line, $context);
214:
215: if (Configure::read('log')) {
216: CakeLog::write($level, "{$error} ({$code}): {$description} in [{$file}, line {$line}]");
217: }
218:
219: if ($error == 'Fatal Error') {
220: exit();
221: }
222: return true;
223: }
224: 225: 226: 227: 228: 229: 230: 231: 232:
233: function trace($options = array()) {
234: $options = array_merge(array(
235: 'depth' => 999,
236: 'format' => '',
237: 'args' => false,
238: 'start' => 0,
239: 'scope' => null,
240: 'exclude' => null
241: ),
242: $options
243: );
244:
245: $backtrace = debug_backtrace();
246: $back = array();
247: $count = count($backtrace);
248:
249: for ($i = $options['start']; $i < $count && $i < $options['depth']; $i++) {
250: $trace = array_merge(
251: array(
252: 'file' => '[internal]',
253: 'line' => '??'
254: ),
255: $backtrace[$i]
256: );
257:
258: if (isset($backtrace[$i + 1])) {
259: $next = array_merge(
260: array(
261: 'line' => '??',
262: 'file' => '[internal]',
263: 'class' => null,
264: 'function' => '[main]'
265: ),
266: $backtrace[$i + 1]
267: );
268: $function = $next['function'];
269:
270: if (!empty($next['class'])) {
271: $function = $next['class'] . '::' . $function . '(';
272: if ($options['args'] && isset($next['args'])) {
273: $args = array();
274: foreach ($next['args'] as $arg) {
275: $args[] = Debugger::exportVar($arg);
276: }
277: $function .= implode(', ', $args);
278: }
279: $function .= ')';
280: }
281: } else {
282: $function = '[main]';
283: }
284: if (in_array($function, array('call_user_func_array', 'trigger_error'))) {
285: continue;
286: }
287: if ($options['format'] == 'points' && $trace['file'] != '[internal]') {
288: $back[] = array('file' => $trace['file'], 'line' => $trace['line']);
289: } elseif (empty($options['format'])) {
290: $back[] = $function . ' - ' . Debugger::trimPath($trace['file']) . ', line ' . $trace['line'];
291: } else {
292: $back[] = $trace;
293: }
294: }
295:
296: if ($options['format'] == 'array' || $options['format'] == 'points') {
297: return $back;
298: }
299: return implode("\n", $back);
300: }
301: 302: 303: 304: 305: 306: 307: 308: 309:
310: function trimPath($path) {
311: if (!defined('CAKE_CORE_INCLUDE_PATH') || !defined('APP')) {
312: return $path;
313: }
314:
315: if (strpos($path, APP) === 0) {
316: return str_replace(APP, 'APP' . DS, $path);
317: } elseif (strpos($path, CAKE_CORE_INCLUDE_PATH) === 0) {
318: return str_replace(CAKE_CORE_INCLUDE_PATH, 'CORE', $path);
319: } elseif (strpos($path, ROOT) === 0) {
320: return str_replace(ROOT, 'ROOT', $path);
321: }
322: $corePaths = Configure::corePaths('cake');
323: foreach ($corePaths as $corePath) {
324: if (strpos($path, $corePath) === 0) {
325: return str_replace($corePath, 'CORE' .DS . 'cake' .DS, $path);
326: }
327: }
328: return $path;
329: }
330: 331: 332: 333: 334: 335: 336: 337: 338: 339: 340:
341: function excerpt($file, $line, $context = 2) {
342: $data = $lines = array();
343: if (!file_exists($file)) {
344: return array();
345: }
346: $data = @explode("\n", file_get_contents($file));
347:
348: if (empty($data) || !isset($data[$line])) {
349: return;
350: }
351: for ($i = $line - ($context + 1); $i < $line + $context; $i++) {
352: if (!isset($data[$i])) {
353: continue;
354: }
355: $string = str_replace(array("\r\n", "\n"), "", highlight_string($data[$i], true));
356: if ($i == $line) {
357: $lines[] = '<span class="code-highlight">' . $string . '</span>';
358: } else {
359: $lines[] = $string;
360: }
361: }
362: return $lines;
363: }
364: 365: 366: 367: 368: 369: 370: 371: 372:
373: function exportVar($var, $recursion = 0) {
374: $_this = Debugger::getInstance();
375: switch (strtolower(gettype($var))) {
376: case 'boolean':
377: return ($var) ? 'true' : 'false';
378: break;
379: case 'integer':
380: case 'double':
381: return $var;
382: break;
383: case 'string':
384: if (trim($var) == "") {
385: return '""';
386: }
387: return '"' . h($var) . '"';
388: break;
389: case 'object':
390: return get_class($var) . "\n" . $_this->__object($var);
391: case 'array':
392: $out = "array(";
393: $vars = array();
394: foreach ($var as $key => $val) {
395: if ($recursion >= 0) {
396: if (is_numeric($key)) {
397: $vars[] = "\n\t" . $_this->exportVar($val, $recursion - 1);
398: } else {
399: $vars[] = "\n\t" .$_this->exportVar($key, $recursion - 1)
400: . ' => ' . $_this->exportVar($val, $recursion - 1);
401: }
402: }
403: }
404: $n = null;
405: if (!empty($vars)) {
406: $n = "\n";
407: }
408: return $out . implode(",", $vars) . "{$n})";
409: break;
410: case 'resource':
411: return strtolower(gettype($var));
412: break;
413: case 'null':
414: return 'null';
415: break;
416: }
417: }
418: 419: 420: 421: 422: 423: 424: 425:
426: function __object($var) {
427: $out = array();
428:
429: if (is_object($var)) {
430: $className = get_class($var);
431: $objectVars = get_object_vars($var);
432:
433: foreach ($objectVars as $key => $value) {
434: if (is_object($value)) {
435: $value = get_class($value) . ' object';
436: } elseif (is_array($value)) {
437: $value = 'array';
438: } elseif ($value === null) {
439: $value = 'NULL';
440: } elseif (in_array(gettype($value), array('boolean', 'integer', 'double', 'string', 'array', 'resource'))) {
441: $value = Debugger::exportVar($value);
442: }
443: $out[] = "$className::$$key = " . $value;
444: }
445: }
446: return implode("\n", $out);
447: }
448: 449: 450: 451: 452: 453:
454: function output($format = 'js') {
455: $_this = Debugger::getInstance();
456: $data = null;
457:
458: if ($format === true && !empty($_this->__data)) {
459: $data = $_this->__data;
460: $_this->__data = array();
461: $format = false;
462: }
463: $_this->_outputFormat = $format;
464:
465: return $data;
466: }
467: 468: 469: 470: 471: 472:
473: function _output($level, $error, $code, $helpCode, $description, $file, $line, $kontext) {
474: $files = $this->trace(array('start' => 2, 'format' => 'points'));
475: $listing = $this->excerpt($files[0]['file'], $files[0]['line'] - 1, 1);
476: $trace = $this->trace(array('start' => 2, 'depth' => '20'));
477: $context = array();
478:
479: foreach ((array)$kontext as $var => $value) {
480: $context[] = "\${$var}\t=\t" . $this->exportVar($value, 1);
481: }
482:
483: switch ($this->_outputFormat) {
484: default:
485: case 'js':
486: $link = "document.getElementById(\"CakeStackTrace" . count($this->errors) . "\").style.display = (document.getElementById(\"CakeStackTrace" . count($this->errors) . "\").style.display == \"none\" ? \"\" : \"none\")";
487: $out = "<a href='javascript:void(0);' onclick='{$link}'><b>{$error}</b> ({$code})</a>: {$description} [<b>{$file}</b>, line <b>{$line}</b>]";
488: if (Configure::read() > 0) {
489: debug($out, false, false);
490: echo '<div id="CakeStackTrace' . count($this->errors) . '" class="cake-stack-trace" style="display: none;">';
491: $link = "document.getElementById(\"CakeErrorCode" . count($this->errors) . "\").style.display = (document.getElementById(\"CakeErrorCode" . count($this->errors) . "\").style.display == \"none\" ? \"\" : \"none\")";
492: echo "<a href='javascript:void(0);' onclick='{$link}'>Code</a>";
493:
494: if (!empty($context)) {
495: $link = "document.getElementById(\"CakeErrorContext" . count($this->errors) . "\").style.display = (document.getElementById(\"CakeErrorContext" . count($this->errors) . "\").style.display == \"none\" ? \"\" : \"none\")";
496: echo " | <a href='javascript:void(0);' onclick='{$link}'>Context</a>";
497:
498: if (!empty($helpCode)) {
499: echo " | <a href='{$this->helpPath}{$helpCode}' target='_blank'>Help</a>";
500: }
501:
502: echo "<pre id=\"CakeErrorContext" . count($this->errors) . "\" class=\"cake-context\" style=\"display: none;\">";
503: echo implode("\n", $context);
504: echo "</pre>";
505: }
506:
507: if (!empty($listing)) {
508: echo "<div id=\"CakeErrorCode" . count($this->errors) . "\" class=\"cake-code-dump\" style=\"display: none;\">";
509: pr(implode("\n", $listing) . "\n", false);
510: echo '</div>';
511: }
512: pr($trace, false);
513: echo '</div>';
514: }
515: break;
516: case 'html':
517: echo "<pre class=\"cake-debug\"><b>{$error}</b> ({$code}) : {$description} [<b>{$file}</b>, line <b>{$line}]</b></pre>";
518: if (!empty($context)) {
519: echo "Context:\n" .implode("\n", $context) . "\n";
520: }
521: echo "<pre class=\"cake-debug context\"><b>Context</b> <p>" . implode("\n", $context) . "</p></pre>";
522: echo "<pre class=\"cake-debug trace\"><b>Trace</b> <p>" . $trace. "</p></pre>";
523: break;
524: case 'text':
525: case 'txt':
526: echo "{$error}: {$code} :: {$description} on line {$line} of {$file}\n";
527: if (!empty($context)) {
528: echo "Context:\n" .implode("\n", $context) . "\n";
529: }
530: echo "Trace:\n" . $trace;
531: break;
532: case 'log':
533: $this->log(compact('error', 'code', 'description', 'line', 'file', 'context', 'trace'));
534: break;
535: case false:
536: $this->__data[] = compact('error', 'code', 'description', 'line', 'file', 'context', 'trace');
537: break;
538: }
539: }
540: 541: 542: 543: 544: 545:
546: function checkSessionKey() {
547: if (Configure::read('Security.salt') == 'DYhG93b0qyJfIxfs2guVoUubWwvniR2G0FgaC9mi') {
548: trigger_error(__('Please change the value of \'Security.salt\' in app/config/core.php to a salt value specific to your application', true), E_USER_NOTICE);
549: }
550: }
551: 552: 553: 554: 555: 556: 557: 558: 559:
560: function invoke(&$debugger) {
561: set_error_handler(array(&$debugger, 'handleError'));
562: }
563: }
564:
565: if (!defined('DISABLE_DEFAULT_ERROR_HANDLING')) {
566: Debugger::invoke(Debugger::getInstance());
567: }
568: ?>