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