1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20:
21:
22: App::uses('CakeLog', 'Log');
23: App::uses('String', 'Utility');
24:
25: 26: 27: 28: 29: 30: 31: 32:
33: class Debugger {
34:
35: 36: 37: 38: 39:
40: public $errors = array();
41:
42: 43: 44: 45: 46:
47: protected $_outputFormat = 'js';
48:
49: 50: 51: 52: 53: 54:
55: protected $_templates = array(
56: 'log' => array(
57: 'trace' => '{:reference} - {:path}, line {:line}',
58: 'error' => "{:error} ({:code}): {:description} in [{:file}, line {:line}]"
59: ),
60: 'js' => array(
61: 'error' => '',
62: 'info' => '',
63: 'trace' => '<pre class="stack-trace">{:trace}</pre>',
64: 'code' => '',
65: 'context' => '',
66: 'links' => array()
67: ),
68: 'html' => array(
69: 'trace' => '<pre class="cake-error trace"><b>Trace</b> <p>{:trace}</p></pre>',
70: 'context' => '<pre class="cake-error context"><b>Context</b> <p>{:context}</p></pre>'
71: ),
72: 'txt' => array(
73: 'error' => "{:error}: {:code} :: {:description} on line {:line} of {:path}\n{:info}",
74: 'code' => '',
75: 'info' => ''
76: ),
77: 'base' => array(
78: 'traceLine' => '{:reference} - {:path}, line {:line}',
79: 'trace' => "Trace:\n{:trace}\n",
80: 'context' => "Context:\n{:context}\n",
81: ),
82: 'log' => array(),
83: );
84:
85: 86: 87: 88: 89:
90: protected $_data = array();
91:
92: 93: 94: 95:
96: public function __construct() {
97: $docRef = ini_get('docref_root');
98:
99: if (empty($docRef) && function_exists('ini_set')) {
100: ini_set('docref_root', 'http://php.net/');
101: }
102: if (!defined('E_RECOVERABLE_ERROR')) {
103: define('E_RECOVERABLE_ERROR', 4096);
104: }
105:
106: $e = '<pre class="cake-error">';
107: $e .= '<a href="javascript:void(0);" onclick="document.getElementById(\'{:id}-trace\')';
108: $e .= '.style.display = (document.getElementById(\'{:id}-trace\').style.display == ';
109: $e .= '\'none\' ? \'\' : \'none\');"><b>{:error}</b> ({:code})</a>: {:description} ';
110: $e .= '[<b>{:path}</b>, line <b>{:line}</b>]';
111:
112: $e .= '<div id="{:id}-trace" class="cake-stack-trace" style="display: none;">';
113: $e .= '{:links}{:info}</div>';
114: $e .= '</pre>';
115: $this->_templates['js']['error'] = $e;
116:
117: $t = '<div id="{:id}-trace" class="cake-stack-trace" style="display: none;">';
118: $t .= '{:context}{:code}{:trace}</div>';
119: $this->_templates['js']['info'] = $t;
120:
121: $links = array();
122: $link = '<a href="javascript:void(0);" onclick="document.getElementById(\'{:id}-code\')';
123: $link .= '.style.display = (document.getElementById(\'{:id}-code\').style.display == ';
124: $link .= '\'none\' ? \'\' : \'none\')">Code</a>';
125: $links['code'] = $link;
126:
127: $link = '<a href="javascript:void(0);" onclick="document.getElementById(\'{:id}-context\')';
128: $link .= '.style.display = (document.getElementById(\'{:id}-context\').style.display == ';
129: $link .= '\'none\' ? \'\' : \'none\')">Context</a>';
130: $links['context'] = $link;
131:
132: $this->_templates['js']['links'] = $links;
133:
134: $this->_templates['js']['context'] = '<pre id="{:id}-context" class="cake-context" ';
135: $this->_templates['js']['context'] .= 'style="display: none;">{:context}</pre>';
136:
137: $this->_templates['js']['code'] = '<pre id="{:id}-code" class="cake-code-dump" ';
138: $this->_templates['js']['code'] .= 'style="display: none;">{:code}</pre>';
139:
140: $e = '<pre class="cake-error"><b>{:error}</b> ({:code}) : {:description} ';
141: $e .= '[<b>{:path}</b>, line <b>{:line}]</b></pre>';
142: $this->_templates['html']['error'] = $e;
143:
144: $this->_templates['html']['context'] = '<pre class="cake-context"><b>Context</b> ';
145: $this->_templates['html']['context'] .= '<p>{:context}</p></pre>';
146: }
147:
148: 149: 150: 151: 152: 153:
154: public static function &getInstance($class = null) {
155: static $instance = array();
156: if (!empty($class)) {
157: if (!$instance || strtolower($class) != strtolower(get_class($instance[0]))) {
158: $instance[0] = new $class();
159: }
160: }
161: if (!$instance) {
162: $instance[0] = new Debugger();
163: }
164: return $instance[0];
165: }
166:
167: 168: 169: 170: 171: 172: 173: 174: 175:
176: public static function dump($var) {
177: pr(self::exportVar($var));
178: }
179:
180: 181: 182: 183: 184: 185: 186: 187: 188:
189: public static function log($var, $level = LOG_DEBUG) {
190: $source = self::trace(array('start' => 1)) . "\n";
191: CakeLog::write($level, "\n" . $source . self::exportVar($var));
192: }
193:
194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204:
205: public static function showError($code, $description, $file = null, $line = null, $context = null) {
206: $_this = Debugger::getInstance();
207:
208: if (empty($file)) {
209: $file = '[internal]';
210: }
211: if (empty($line)) {
212: $line = '??';
213: }
214: $path = self::trimPath($file);
215:
216: $info = compact('code', 'description', 'file', 'line');
217: if (!in_array($info, $_this->errors)) {
218: $_this->errors[] = $info;
219: } else {
220: return;
221: }
222:
223: switch ($code) {
224: case E_PARSE:
225: case E_ERROR:
226: case E_CORE_ERROR:
227: case E_COMPILE_ERROR:
228: case E_USER_ERROR:
229: $error = 'Fatal Error';
230: $level = LOG_ERROR;
231: break;
232: case E_WARNING:
233: case E_USER_WARNING:
234: case E_COMPILE_WARNING:
235: case E_RECOVERABLE_ERROR:
236: $error = 'Warning';
237: $level = LOG_WARNING;
238: break;
239: case E_NOTICE:
240: case E_USER_NOTICE:
241: $error = 'Notice';
242: $level = LOG_NOTICE;
243: break;
244: case E_DEPRECATED:
245: case E_USER_DEPRECATED:
246: $error = 'Deprecated';
247: $level = LOG_NOTICE;
248: break;
249: default:
250: return;
251: break;
252: }
253:
254: $data = compact(
255: 'level', 'error', 'code', 'description', 'file', 'path', 'line', 'context'
256: );
257: echo $_this->outputError($data);
258:
259: if ($error == 'Fatal Error') {
260: exit();
261: }
262: return true;
263: }
264:
265: 266: 267: 268: 269: 270: 271: 272: 273: 274: 275: 276: 277: 278: 279: 280:
281: public static function trace($options = array()) {
282: $_this = Debugger::getInstance();
283: $defaults = array(
284: 'depth' => 999,
285: 'format' => $_this->_outputFormat,
286: 'args' => false,
287: 'start' => 0,
288: 'scope' => null,
289: 'exclude' => array('call_user_func_array', 'trigger_error')
290: );
291: $options = Set::merge($defaults, $options);
292:
293: $backtrace = debug_backtrace();
294: $count = count($backtrace);
295: $back = array();
296:
297: $_trace = array(
298: 'line' => '??',
299: 'file' => '[internal]',
300: 'class' => null,
301: 'function' => '[main]'
302: );
303:
304: for ($i = $options['start']; $i < $count && $i < $options['depth']; $i++) {
305: $trace = array_merge(array('file' => '[internal]', 'line' => '??'), $backtrace[$i]);
306: $signature = $reference = '[main]';
307:
308: if (isset($backtrace[$i + 1])) {
309: $next = array_merge($_trace, $backtrace[$i + 1]);
310: $signature = $reference = $next['function'];
311:
312: if (!empty($next['class'])) {
313: $signature = $next['class'] . '::' . $next['function'];
314: $reference = $signature . '(';
315: if ($options['args'] && isset($next['args'])) {
316: $args = array();
317: foreach ($next['args'] as $arg) {
318: $args[] = Debugger::exportVar($arg);
319: }
320: $reference .= join(', ', $args);
321: }
322: $reference .= ')';
323: }
324: }
325: if (in_array($signature, $options['exclude'])) {
326: continue;
327: }
328: if ($options['format'] == 'points' && $trace['file'] != '[internal]') {
329: $back[] = array('file' => $trace['file'], 'line' => $trace['line']);
330: } elseif ($options['format'] == 'array') {
331: $back[] = $trace;
332: } else {
333: if (isset($_this->_templates[$options['format']]['traceLine'])) {
334: $tpl = $_this->_templates[$options['format']]['traceLine'];
335: } else {
336: $tpl = $_this->_templates['base']['traceLine'];
337: }
338: $trace['path'] = self::trimPath($trace['file']);
339: $trace['reference'] = $reference;
340: unset($trace['object'], $trace['args']);
341: $back[] = String::insert($tpl, $trace, array('before' => '{:', 'after' => '}'));
342: }
343: }
344:
345: if ($options['format'] == 'array' || $options['format'] == 'points') {
346: return $back;
347: }
348: return implode("\n", $back);
349: }
350:
351: 352: 353: 354: 355: 356: 357:
358: public static function trimPath($path) {
359: if (!defined('CAKE_CORE_INCLUDE_PATH') || !defined('APP')) {
360: return $path;
361: }
362:
363: if (strpos($path, APP) === 0) {
364: return str_replace(APP, 'APP' . DS, $path);
365: } elseif (strpos($path, CAKE_CORE_INCLUDE_PATH) === 0) {
366: return str_replace(CAKE_CORE_INCLUDE_PATH, 'CORE', $path);
367: } elseif (strpos($path, ROOT) === 0) {
368: return str_replace(ROOT, 'ROOT', $path);
369: }
370:
371: if (strpos($path, CAKE) === 0) {
372: return str_replace($corePath, 'CORE' . DS, $path);
373: }
374: return $path;
375: }
376:
377: 378: 379: 380: 381: 382: 383: 384: 385: 386: 387: 388: 389: 390: 391: 392: 393: 394: 395:
396: public static function excerpt($file, $line, $context = 2) {
397: $lines = array();
398: if (!file_exists($file)) {
399: return array();
400: }
401: $data = @explode("\n", file_get_contents($file));
402:
403: if (empty($data) || !isset($data[$line])) {
404: return;
405: }
406: for ($i = $line - ($context + 1); $i < $line + $context; $i++) {
407: if (!isset($data[$i])) {
408: continue;
409: }
410: $string = str_replace(array("\r\n", "\n"), "", highlight_string($data[$i], true));
411: if ($i == $line) {
412: $lines[] = '<span class="code-highlight">' . $string . '</span>';
413: } else {
414: $lines[] = $string;
415: }
416: }
417: return $lines;
418: }
419:
420: 421: 422: 423: 424: 425: 426: 427: 428: 429: 430: 431: 432: 433: 434: 435: 436: 437: 438: 439: 440: 441:
442: public static function exportVar($var, $recursion = 0) {
443: switch (strtolower(gettype($var))) {
444: case 'boolean':
445: return ($var) ? 'true' : 'false';
446: break;
447: case 'integer':
448: case 'double':
449: return $var;
450: break;
451: case 'string':
452: if (trim($var) == "") {
453: return '""';
454: }
455: return '"' . h($var) . '"';
456: break;
457: case 'object':
458: return get_class($var) . "\n" . self::_object($var);
459: case 'array':
460: $var = array_merge($var, array_intersect_key(array(
461: 'password' => '*****',
462: 'login' => '*****',
463: 'host' => '*****',
464: 'database' => '*****',
465: 'port' => '*****',
466: 'prefix' => '*****',
467: 'schema' => '*****'
468: ), $var));
469:
470: $out = "array(";
471: $vars = array();
472: foreach ($var as $key => $val) {
473: if ($recursion >= 0) {
474: if (is_numeric($key)) {
475: $vars[] = "\n\t" . self::exportVar($val, $recursion - 1);
476: } else {
477: $vars[] = "\n\t" . self::exportVar($key, $recursion - 1)
478: . ' => ' . self::exportVar($val, $recursion - 1);
479: }
480: }
481: }
482: $n = null;
483: if (!empty($vars)) {
484: $n = "\n";
485: }
486: return $out . implode(",", $vars) . "{$n})";
487: break;
488: case 'resource':
489: return strtolower(gettype($var));
490: break;
491: case 'null':
492: return 'null';
493: break;
494: }
495: }
496:
497: 498: 499: 500: 501: 502: 503:
504: protected static function _object($var) {
505: $out = array();
506:
507: if (is_object($var)) {
508: $className = get_class($var);
509: $objectVars = get_object_vars($var);
510:
511: foreach ($objectVars as $key => $value) {
512: if (is_object($value)) {
513: $value = get_class($value) . ' object';
514: } elseif (is_array($value)) {
515: $value = 'array';
516: } elseif ($value === null) {
517: $value = 'NULL';
518: } elseif (in_array(gettype($value), array('boolean', 'integer', 'double', 'string', 'array', 'resource'))) {
519: $value = Debugger::exportVar($value);
520: }
521: $out[] = "$className::$$key = " . $value;
522: }
523: }
524: return implode("\n", $out);
525: }
526:
527: 528: 529: 530: 531: 532: 533: 534:
535: public static function outputAs($format = null) {
536: $self = Debugger::getInstance();
537: if ($format === null) {
538: return $self->_outputFormat;
539: }
540: if ($format !== false && !isset($self->_templates[$format])) {
541: throw new CakeException(__d('cake_dev', 'Invalid Debugger output format.'));
542: }
543: $self->_outputFormat = $format;
544: }
545:
546: 547: 548: 549: 550: 551: 552: 553: 554: 555: 556: 557: 558: 559: 560: 561: 562: 563: 564: 565: 566: 567: 568: 569: 570: 571: 572: 573: 574: 575: 576: 577: 578: 579: 580: 581: 582: 583: 584:
585: public static function addFormat($format, array $strings) {
586: $self = Debugger::getInstance();
587: if (isset($self->_templates[$format])) {
588: if (isset($strings['links'])) {
589: $self->_templates[$format]['links'] = array_merge(
590: $self->_templates[$format]['links'],
591: $strings['links']
592: );
593: unset($strings['links']);
594: }
595: $self->_templates[$format] = array_merge($self->_templates[$format], $strings);
596: } else {
597: $self->_templates[$format] = $strings;
598: }
599: return $self->_templates[$format];
600: }
601:
602: 603: 604: 605: 606: 607: 608: 609: 610: 611: 612:
613: public function output($format = null, $strings = array()) {
614: $_this = Debugger::getInstance();
615: $data = null;
616:
617: if (is_null($format)) {
618: return Debugger::outputAs();
619: }
620:
621: if (!empty($strings)) {
622: return Debugger::addFormat($format, $strings);
623: }
624:
625: if ($format === true && !empty($_this->_data)) {
626: $data = $_this->_data;
627: $_this->_data = array();
628: $format = false;
629: }
630: Debugger::outputAs($format);
631: return $data;
632: }
633:
634: 635: 636: 637: 638: 639:
640: public function outputError($data) {
641: $defaults = array(
642: 'level' => 0,
643: 'error' => 0,
644: 'code' => 0,
645: 'description' => '',
646: 'file' => '',
647: 'line' => 0,
648: 'context' => array(),
649: 'start' => 2,
650: );
651: $data += $defaults;
652:
653: $files = $this->trace(array('start' => $data['start'], 'format' => 'points'));
654: $code = '';
655: if (isset($files[0]['file'])) {
656: $code = $this->excerpt($files[0]['file'], $files[0]['line'] - 1, 1);
657: }
658: $trace = $this->trace(array('start' => $data['start'], 'depth' => '20'));
659: $insertOpts = array('before' => '{:', 'after' => '}');
660: $context = array();
661: $links = array();
662: $info = '';
663:
664: foreach ((array)$data['context'] as $var => $value) {
665: $context[] = "\${$var}\t=\t" . $this->exportVar($value, 1);
666: }
667:
668: switch ($this->_outputFormat) {
669: case false:
670: $this->_data[] = compact('context', 'trace') + $data;
671: return;
672: case 'log':
673: $this->log(compact('context', 'trace') + $data);
674: return;
675: }
676:
677: $data['trace'] = $trace;
678: $data['id'] = 'cakeErr' . uniqid();
679: $tpl = array_merge($this->_templates['base'], $this->_templates[$this->_outputFormat]);
680: $insert = array('context' => join("\n", $context)) + $data;
681:
682: $detect = array('context');
683:
684: if (isset($tpl['links'])) {
685: foreach ($tpl['links'] as $key => $val) {
686: if (in_array($key, $detect) && empty($insert[$key])) {
687: continue;
688: }
689: $links[$key] = String::insert($val, $insert, $insertOpts);
690: }
691: }
692:
693: foreach (array('code', 'context', 'trace') as $key) {
694: if (empty($$key) || !isset($tpl[$key])) {
695: continue;
696: }
697: if (is_array($$key)) {
698: $$key = join("\n", $$key);
699: }
700: $info .= String::insert($tpl[$key], compact($key) + $insert, $insertOpts);
701: }
702: $links = join(' ', $links);
703: unset($data['context']);
704: if (isset($tpl['callback']) && is_callable($tpl['callback'])) {
705: return call_user_func($tpl['callback'], $data, compact('links', 'info'));
706: }
707: echo String::insert($tpl['error'], compact('links', 'info') + $data, $insertOpts);
708: }
709:
710: 711: 712: 713: 714:
715: public static function checkSecurityKeys() {
716: if (Configure::read('Security.salt') == 'DYhG93b0qyJfIxfs2guVoUubWwvniR2G0FgaC9mi') {
717: trigger_error(__d('cake_dev', 'Please change the value of \'Security.salt\' in app/Config/core.php to a salt value specific to your application'), E_USER_NOTICE);
718: }
719:
720: if (Configure::read('Security.cipherSeed') === '76859309657453542496749683645') {
721: trigger_error(__d('cake_dev', 'Please change the value of \'Security.cipherSeed\' in app/Config/core.php to a numeric (digits only) seed value specific to your application'), E_USER_NOTICE);
722: }
723: }
724: }
725: