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