統計
| ブランチ: | リビジョン:

pictcode_admin / lib / Cake / Utility / Debugger.php @ 5ad38a95

履歴 | 表示 | アノテート | ダウンロード (25.229 KB)

1
<?php
2
/**
3
 * Framework debugging and PHP error-handling class
4
 *
5
 * Provides enhanced logging, stack traces, and rendering debug views
6
 *
7
 * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
8
 * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
9
 *
10
 * Licensed under The MIT License
11
 * For full copyright and license information, please see the LICENSE.txt
12
 * Redistributions of files must retain the above copyright notice.
13
 *
14
 * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
15
 * @link          http://cakephp.org CakePHP(tm) Project
16
 * @package       Cake.Utility
17
 * @since         CakePHP(tm) v 1.2.4560
18
 * @license       http://www.opensource.org/licenses/mit-license.php MIT License
19
 */
20

    
21
App::uses('CakeLog', 'Log');
22
App::uses('CakeText', 'Utility');
23

    
24
/**
25
 * Provide custom logging and error handling.
26
 *
27
 * Debugger overrides PHP's default error handling to provide stack traces and enhanced logging
28
 *
29
 * @package       Cake.Utility
30
 * @link          http://book.cakephp.org/2.0/en/development/debugging.html#debugger-class
31
 */
32
class Debugger {
33

    
34
/**
35
 * A list of errors generated by the application.
36
 *
37
 * @var array
38
 */
39
        public $errors = array();
40

    
41
/**
42
 * The current output format.
43
 *
44
 * @var string
45
 */
46
        protected $_outputFormat = 'js';
47

    
48
/**
49
 * Templates used when generating trace or error strings. Can be global or indexed by the format
50
 * value used in $_outputFormat.
51
 *
52
 * @var string
53
 */
54
        protected $_templates = array(
55
                'log' => array(
56
                        'trace' => '{:reference} - {:path}, line {:line}',
57
                        'error' => "{:error} ({:code}): {:description} in [{:file}, line {:line}]"
58
                ),
59
                'js' => array(
60
                        'error' => '',
61
                        'info' => '',
62
                        'trace' => '<pre class="stack-trace">{:trace}</pre>',
63
                        'code' => '',
64
                        'context' => '',
65
                        'links' => array(),
66
                        'escapeContext' => true,
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
                        'escapeContext' => true,
72
                ),
73
                'txt' => array(
74
                        'error' => "{:error}: {:code} :: {:description} on line {:line} of {:path}\n{:info}",
75
                        'code' => '',
76
                        'info' => ''
77
                ),
78
                'base' => array(
79
                        'traceLine' => '{:reference} - {:path}, line {:line}',
80
                        'trace' => "Trace:\n{:trace}\n",
81
                        'context' => "Context:\n{:context}\n",
82
                )
83
        );
84

    
85
/**
86
 * Holds current output data when outputFormat is false.
87
 *
88
 * @var string
89
 */
90
        protected $_data = array();
91

    
92
/**
93
 * Constructor.
94
 */
95
        public function __construct() {
96
                $docRef = ini_get('docref_root');
97

    
98
                if (empty($docRef) && function_exists('ini_set')) {
99
                        ini_set('docref_root', 'http://php.net/');
100
                }
101
                if (!defined('E_RECOVERABLE_ERROR')) {
102
                        define('E_RECOVERABLE_ERROR', 4096);
103
                }
104

    
105
                $e = '<pre class="cake-error">';
106
                $e .= '<a href="javascript:void(0);" onclick="document.getElementById(\'{:id}-trace\')';
107
                $e .= '.style.display = (document.getElementById(\'{:id}-trace\').style.display == ';
108
                $e .= '\'none\' ? \'\' : \'none\');"><b>{:error}</b> ({:code})</a>: {:description} ';
109
                $e .= '[<b>{:path}</b>, line <b>{:line}</b>]';
110

    
111
                $e .= '<div id="{:id}-trace" class="cake-stack-trace" style="display: none;">';
112
                $e .= '{:links}{:info}</div>';
113
                $e .= '</pre>';
114
                $this->_templates['js']['error'] = $e;
115

    
116
                $t = '<div id="{:id}-trace" class="cake-stack-trace" style="display: none;">';
117
                $t .= '{:context}{:code}{:trace}</div>';
118
                $this->_templates['js']['info'] = $t;
119

    
120
                $links = array();
121
                $link = '<a href="javascript:void(0);" onclick="document.getElementById(\'{:id}-code\')';
122
                $link .= '.style.display = (document.getElementById(\'{:id}-code\').style.display == ';
123
                $link .= '\'none\' ? \'\' : \'none\')">Code</a>';
124
                $links['code'] = $link;
125

    
126
                $link = '<a href="javascript:void(0);" onclick="document.getElementById(\'{:id}-context\')';
127
                $link .= '.style.display = (document.getElementById(\'{:id}-context\').style.display == ';
128
                $link .= '\'none\' ? \'\' : \'none\')">Context</a>';
129
                $links['context'] = $link;
130

    
131
                $this->_templates['js']['links'] = $links;
132

    
133
                $this->_templates['js']['context'] = '<pre id="{:id}-context" class="cake-context" ';
134
                $this->_templates['js']['context'] .= 'style="display: none;">{:context}</pre>';
135

    
136
                $this->_templates['js']['code'] = '<pre id="{:id}-code" class="cake-code-dump" ';
137
                $this->_templates['js']['code'] .= 'style="display: none;">{:code}</pre>';
138

    
139
                $e = '<pre class="cake-error"><b>{:error}</b> ({:code}) : {:description} ';
140
                $e .= '[<b>{:path}</b>, line <b>{:line}]</b></pre>';
141
                $this->_templates['html']['error'] = $e;
142

    
143
                $this->_templates['html']['context'] = '<pre class="cake-context"><b>Context</b> ';
144
                $this->_templates['html']['context'] .= '<p>{:context}</p></pre>';
145
        }
146

    
147
/**
148
 * Returns a reference to the Debugger singleton object instance.
149
 *
150
 * @param string $class Debugger class name.
151
 * @return object
152
 */
153
        public static function getInstance($class = null) {
154
                static $instance = array();
155
                if (!empty($class)) {
156
                        if (!$instance || strtolower($class) != strtolower(get_class($instance[0]))) {
157
                                $instance[0] = new $class();
158
                        }
159
                }
160
                if (!$instance) {
161
                        $instance[0] = new Debugger();
162
                }
163
                return $instance[0];
164
        }
165

    
166
/**
167
 * Recursively formats and outputs the contents of the supplied variable.
168
 *
169
 * @param mixed $var the variable to dump
170
 * @param int $depth The depth to output to. Defaults to 3.
171
 * @return void
172
 * @see Debugger::exportVar()
173
 * @link http://book.cakephp.org/2.0/en/development/debugging.html#Debugger::dump
174
 */
175
        public static function dump($var, $depth = 3) {
176
                pr(static::exportVar($var, $depth));
177
        }
178

    
179
/**
180
 * Creates an entry in the log file. The log entry will contain a stack trace from where it was called.
181
 * as well as export the variable using exportVar. By default the log is written to the debug log.
182
 *
183
 * @param mixed $var Variable or content to log
184
 * @param int $level type of log to use. Defaults to LOG_DEBUG
185
 * @param int $depth The depth to output to. Defaults to 3.
186
 * @return void
187
 * @link http://book.cakephp.org/2.0/en/development/debugging.html#Debugger::log
188
 */
189
        public static function log($var, $level = LOG_DEBUG, $depth = 3) {
190
                $source = static::trace(array('start' => 1)) . "\n";
191
                CakeLog::write($level, "\n" . $source . static::exportVar($var, $depth));
192
        }
193

    
194
/**
195
 * Overrides PHP's default error handling.
196
 *
197
 * @param int $code Code of error
198
 * @param string $description Error description
199
 * @param string $file File on which error occurred
200
 * @param int $line Line that triggered the error
201
 * @param array $context Context
202
 * @return bool|null True if error was handled, otherwise null.
203
 * @deprecated 3.0.0 Will be removed in 3.0. This function is superseded by Debugger::outputError().
204
 */
205
        public static function showError($code, $description, $file = null, $line = null, $context = null) {
206
                $self = Debugger::getInstance();
207

    
208
                if (empty($file)) {
209
                        $file = '[internal]';
210
                }
211
                if (empty($line)) {
212
                        $line = '??';
213
                }
214

    
215
                $info = compact('code', 'description', 'file', 'line');
216
                if (!in_array($info, $self->errors)) {
217
                        $self->errors[] = $info;
218
                } else {
219
                        return null;
220
                }
221

    
222
                switch ($code) {
223
                        case E_PARSE:
224
                        case E_ERROR:
225
                        case E_CORE_ERROR:
226
                        case E_COMPILE_ERROR:
227
                        case E_USER_ERROR:
228
                                $error = 'Fatal Error';
229
                                $level = LOG_ERR;
230
                                break;
231
                        case E_WARNING:
232
                        case E_USER_WARNING:
233
                        case E_COMPILE_WARNING:
234
                        case E_RECOVERABLE_ERROR:
235
                                $error = 'Warning';
236
                                $level = LOG_WARNING;
237
                                break;
238
                        case E_NOTICE:
239
                        case E_USER_NOTICE:
240
                                $error = 'Notice';
241
                                $level = LOG_NOTICE;
242
                                break;
243
                        case E_DEPRECATED:
244
                        case E_USER_DEPRECATED:
245
                                $error = 'Deprecated';
246
                                $level = LOG_NOTICE;
247
                                break;
248
                        default:
249
                                return null;
250
                }
251

    
252
                $data = compact(
253
                        'level', 'error', 'code', 'description', 'file', 'path', 'line', 'context'
254
                );
255
                echo $self->outputError($data);
256

    
257
                if ($error === 'Fatal Error') {
258
                        exit();
259
                }
260
                return true;
261
        }
262

    
263
/**
264
 * Outputs a stack trace based on the supplied options.
265
 *
266
 * ### Options
267
 *
268
 * - `depth` - The number of stack frames to return. Defaults to 999
269
 * - `format` - The format you want the return. Defaults to the currently selected format. If
270
 *    format is 'array' or 'points' the return will be an array.
271
 * - `args` - Should arguments for functions be shown?  If true, the arguments for each method call
272
 *   will be displayed.
273
 * - `start` - The stack frame to start generating a trace from. Defaults to 0
274
 *
275
 * @param array $options Format for outputting stack trace
276
 * @return mixed Formatted stack trace
277
 * @link http://book.cakephp.org/2.0/en/development/debugging.html#Debugger::trace
278
 */
279
        public static function trace($options = array()) {
280
                $self = Debugger::getInstance();
281
                $defaults = array(
282
                        'depth'                => 999,
283
                        'format'        => $self->_outputFormat,
284
                        'args'                => false,
285
                        'start'                => 0,
286
                        'scope'                => null,
287
                        'exclude'        => array('call_user_func_array', 'trigger_error')
288
                );
289
                $options = Hash::merge($defaults, $options);
290

    
291
                $backtrace = debug_backtrace();
292
                $count = count($backtrace);
293
                $back = array();
294

    
295
                $_trace = array(
296
                        'line' => '??',
297
                        'file' => '[internal]',
298
                        'class' => null,
299
                        'function' => '[main]'
300
                );
301

    
302
                for ($i = $options['start']; $i < $count && $i < $options['depth']; $i++) {
303
                        $trace = array_merge(array('file' => '[internal]', 'line' => '??'), $backtrace[$i]);
304
                        $signature = $reference = '[main]';
305

    
306
                        if (isset($backtrace[$i + 1])) {
307
                                $next = array_merge($_trace, $backtrace[$i + 1]);
308
                                $signature = $reference = $next['function'];
309

    
310
                                if (!empty($next['class'])) {
311
                                        $signature = $next['class'] . '::' . $next['function'];
312
                                        $reference = $signature . '(';
313
                                        if ($options['args'] && isset($next['args'])) {
314
                                                $args = array();
315
                                                foreach ($next['args'] as $arg) {
316
                                                        $args[] = Debugger::exportVar($arg);
317
                                                }
318
                                                $reference .= implode(', ', $args);
319
                                        }
320
                                        $reference .= ')';
321
                                }
322
                        }
323
                        if (in_array($signature, $options['exclude'])) {
324
                                continue;
325
                        }
326
                        if ($options['format'] === 'points' && $trace['file'] !== '[internal]') {
327
                                $back[] = array('file' => $trace['file'], 'line' => $trace['line']);
328
                        } elseif ($options['format'] === 'array') {
329
                                $back[] = $trace;
330
                        } else {
331
                                if (isset($self->_templates[$options['format']]['traceLine'])) {
332
                                        $tpl = $self->_templates[$options['format']]['traceLine'];
333
                                } else {
334
                                        $tpl = $self->_templates['base']['traceLine'];
335
                                }
336
                                $trace['path'] = static::trimPath($trace['file']);
337
                                $trace['reference'] = $reference;
338
                                unset($trace['object'], $trace['args']);
339
                                $back[] = CakeText::insert($tpl, $trace, array('before' => '{:', 'after' => '}'));
340
                        }
341
                }
342

    
343
                if ($options['format'] === 'array' || $options['format'] === 'points') {
344
                        return $back;
345
                }
346
                return implode("\n", $back);
347
        }
348

    
349
/**
350
 * Shortens file paths by replacing the application base path with 'APP', and the CakePHP core
351
 * path with 'CORE'.
352
 *
353
 * @param string $path Path to shorten
354
 * @return string Normalized path
355
 */
356
        public static function trimPath($path) {
357
                if (!defined('CAKE_CORE_INCLUDE_PATH') || !defined('APP')) {
358
                        return $path;
359
                }
360

    
361
                if (strpos($path, APP) === 0) {
362
                        return str_replace(APP, 'APP' . DS, $path);
363
                } elseif (strpos($path, CAKE_CORE_INCLUDE_PATH) === 0) {
364
                        return str_replace(CAKE_CORE_INCLUDE_PATH, 'CORE', $path);
365
                } elseif (strpos($path, ROOT) === 0) {
366
                        return str_replace(ROOT, 'ROOT', $path);
367
                }
368

    
369
                return $path;
370
        }
371

    
372
/**
373
 * Grabs an excerpt from a file and highlights a given line of code.
374
 *
375
 * Usage:
376
 *
377
 * `Debugger::excerpt('/path/to/file', 100, 4);`
378
 *
379
 * The above would return an array of 8 items. The 4th item would be the provided line,
380
 * and would be wrapped in `<span class="code-highlight"></span>`. All of the lines
381
 * are processed with highlight_string() as well, so they have basic PHP syntax highlighting
382
 * applied.
383
 *
384
 * @param string $file Absolute path to a PHP file
385
 * @param int $line Line number to highlight
386
 * @param int $context Number of lines of context to extract above and below $line
387
 * @return array Set of lines highlighted
388
 * @see http://php.net/highlight_string
389
 * @link http://book.cakephp.org/2.0/en/development/debugging.html#Debugger::excerpt
390
 */
391
        public static function excerpt($file, $line, $context = 2) {
392
                $lines = array();
393
                if (!file_exists($file)) {
394
                        return array();
395
                }
396
                $data = file_get_contents($file);
397
                if (empty($data)) {
398
                        return $lines;
399
                }
400
                if (strpos($data, "\n") !== false) {
401
                        $data = explode("\n", $data);
402
                }
403
                if (!isset($data[$line])) {
404
                        return $lines;
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"), "", static::_highlight($data[$i]));
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
 * Wraps the highlight_string function in case the server API does not
422
 * implement the function as it is the case of the HipHop interpreter
423
 *
424
 * @param string $str the string to convert
425
 * @return string
426
 */
427
        protected static function _highlight($str) {
428
                if (function_exists('hphp_log') || function_exists('hphp_gettid')) {
429
                        return htmlentities($str);
430
                }
431
                $added = false;
432
                if (strpos($str, '<?php') === false) {
433
                        $added = true;
434
                        $str = "<?php \n" . $str;
435
                }
436
                $highlight = highlight_string($str, true);
437
                if ($added) {
438
                        $highlight = str_replace(
439
                                '&lt;?php&nbsp;<br />',
440
                                '',
441
                                $highlight
442
                        );
443
                }
444
                return $highlight;
445
        }
446

    
447
/**
448
 * Converts a variable to a string for debug output.
449
 *
450
 * *Note:* The following keys will have their contents
451
 * replaced with `*****`:
452
 *
453
 *  - password
454
 *  - login
455
 *  - host
456
 *  - database
457
 *  - port
458
 *  - prefix
459
 *  - schema
460
 *
461
 * This is done to protect database credentials, which could be accidentally
462
 * shown in an error message if CakePHP is deployed in development mode.
463
 *
464
 * @param string $var Variable to convert
465
 * @param int $depth The depth to output to. Defaults to 3.
466
 * @return string Variable as a formatted string
467
 * @link http://book.cakephp.org/2.0/en/development/debugging.html#Debugger::exportVar
468
 */
469
        public static function exportVar($var, $depth = 3) {
470
                return static::_export($var, $depth, 0);
471
        }
472

    
473
/**
474
 * Protected export function used to keep track of indentation and recursion.
475
 *
476
 * @param mixed $var The variable to dump.
477
 * @param int $depth The remaining depth.
478
 * @param int $indent The current indentation level.
479
 * @return string The dumped variable.
480
 */
481
        protected static function _export($var, $depth, $indent) {
482
                switch (static::getType($var)) {
483
                        case 'boolean':
484
                                return ($var) ? 'true' : 'false';
485
                        case 'integer':
486
                                return '(int) ' . $var;
487
                        case 'float':
488
                                return '(float) ' . $var;
489
                        case 'string':
490
                                if (trim($var) === '') {
491
                                        return "''";
492
                                }
493
                                return "'" . $var . "'";
494
                        case 'array':
495
                                return static::_array($var, $depth - 1, $indent + 1);
496
                        case 'resource':
497
                                return strtolower(gettype($var));
498
                        case 'null':
499
                                return 'null';
500
                        case 'unknown':
501
                                return 'unknown';
502
                        default:
503
                                return static::_object($var, $depth - 1, $indent + 1);
504
                }
505
        }
506

    
507
/**
508
 * Export an array type object. Filters out keys used in datasource configuration.
509
 *
510
 * The following keys are replaced with ***'s
511
 *
512
 * - password
513
 * - login
514
 * - host
515
 * - database
516
 * - port
517
 * - prefix
518
 * - schema
519
 *
520
 * @param array $var The array to export.
521
 * @param int $depth The current depth, used for recursion tracking.
522
 * @param int $indent The current indentation level.
523
 * @return string Exported array.
524
 */
525
        protected static function _array(array $var, $depth, $indent) {
526
                $secrets = array(
527
                        'password' => '*****',
528
                        'login' => '*****',
529
                        'host' => '*****',
530
                        'database' => '*****',
531
                        'port' => '*****',
532
                        'prefix' => '*****',
533
                        'schema' => '*****'
534
                );
535
                $replace = array_intersect_key($secrets, $var);
536
                $var = $replace + $var;
537

    
538
                $out = "array(";
539
                $break = $end = null;
540
                if (!empty($var)) {
541
                        $break = "\n" . str_repeat("\t", $indent);
542
                        $end = "\n" . str_repeat("\t", $indent - 1);
543
                }
544
                $vars = array();
545

    
546
                if ($depth >= 0) {
547
                        foreach ($var as $key => $val) {
548
                                // Sniff for globals as !== explodes in < 5.4
549
                                if ($key === 'GLOBALS' && is_array($val) && isset($val['GLOBALS'])) {
550
                                        $val = '[recursion]';
551
                                } elseif ($val !== $var) {
552
                                        $val = static::_export($val, $depth, $indent);
553
                                }
554
                                $vars[] = $break . static::exportVar($key) .
555
                                        ' => ' .
556
                                        $val;
557
                        }
558
                } else {
559
                        $vars[] = $break . '[maximum depth reached]';
560
                }
561
                return $out . implode(',', $vars) . $end . ')';
562
        }
563

    
564
/**
565
 * Handles object to string conversion.
566
 *
567
 * @param string $var Object to convert
568
 * @param int $depth The current depth, used for tracking recursion.
569
 * @param int $indent The current indentation level.
570
 * @return string
571
 * @see Debugger::exportVar()
572
 */
573
        protected static function _object($var, $depth, $indent) {
574
                $out = '';
575
                $props = array();
576

    
577
                $className = get_class($var);
578
                $out .= 'object(' . $className . ') {';
579

    
580
                if ($depth > 0) {
581
                        $end = "\n" . str_repeat("\t", $indent - 1);
582
                        $break = "\n" . str_repeat("\t", $indent);
583
                        $objectVars = get_object_vars($var);
584
                        foreach ($objectVars as $key => $value) {
585
                                $value = static::_export($value, $depth - 1, $indent);
586
                                $props[] = "$key => " . $value;
587
                        }
588

    
589
                        if (version_compare(PHP_VERSION, '5.3.0') >= 0) {
590
                                $ref = new ReflectionObject($var);
591

    
592
                                $filters = array(
593
                                        ReflectionProperty::IS_PROTECTED => 'protected',
594
                                        ReflectionProperty::IS_PRIVATE => 'private',
595
                                );
596
                                foreach ($filters as $filter => $visibility) {
597
                                        $reflectionProperties = $ref->getProperties($filter);
598
                                        foreach ($reflectionProperties as $reflectionProperty) {
599
                                                $reflectionProperty->setAccessible(true);
600
                                                $property = $reflectionProperty->getValue($var);
601

    
602
                                                $value = static::_export($property, $depth - 1, $indent);
603
                                                $key = $reflectionProperty->name;
604
                                                $props[] = sprintf('[%s] %s => %s', $visibility, $key, $value);
605
                                        }
606
                                }
607
                        }
608

    
609
                        $out .= $break . implode($break, $props) . $end;
610
                }
611
                $out .= '}';
612
                return $out;
613
        }
614

    
615
/**
616
 * Get/Set the output format for Debugger error rendering.
617
 *
618
 * @param string $format The format you want errors to be output as.
619
 *   Leave null to get the current format.
620
 * @return mixed Returns null when setting. Returns the current format when getting.
621
 * @throws CakeException when choosing a format that doesn't exist.
622
 */
623
        public static function outputAs($format = null) {
624
                $self = Debugger::getInstance();
625
                if ($format === null) {
626
                        return $self->_outputFormat;
627
                }
628
                if ($format !== false && !isset($self->_templates[$format])) {
629
                        throw new CakeException(__d('cake_dev', 'Invalid Debugger output format.'));
630
                }
631
                $self->_outputFormat = $format;
632
        }
633

    
634
/**
635
 * Add an output format or update a format in Debugger.
636
 *
637
 * `Debugger::addFormat('custom', $data);`
638
 *
639
 * Where $data is an array of strings that use CakeText::insert() variable
640
 * replacement. The template vars should be in a `{:id}` style.
641
 * An error formatter can have the following keys:
642
 *
643
 * - 'error' - Used for the container for the error message. Gets the following template
644
 *   variables: `id`, `error`, `code`, `description`, `path`, `line`, `links`, `info`
645
 * - 'info' - A combination of `code`, `context` and `trace`. Will be set with
646
 *   the contents of the other template keys.
647
 * - 'trace' - The container for a stack trace. Gets the following template
648
 *   variables: `trace`
649
 * - 'context' - The container element for the context variables.
650
 *   Gets the following templates: `id`, `context`
651
 * - 'links' - An array of HTML links that are used for creating links to other resources.
652
 *   Typically this is used to create javascript links to open other sections.
653
 *   Link keys, are: `code`, `context`, `help`. See the js output format for an
654
 *   example.
655
 * - 'traceLine' - Used for creating lines in the stacktrace. Gets the following
656
 *   template variables: `reference`, `path`, `line`
657
 *
658
 * Alternatively if you want to use a custom callback to do all the formatting, you can use
659
 * the callback key, and provide a callable:
660
 *
661
 * `Debugger::addFormat('custom', array('callback' => array($foo, 'outputError'));`
662
 *
663
 * The callback can expect two parameters. The first is an array of all
664
 * the error data. The second contains the formatted strings generated using
665
 * the other template strings. Keys like `info`, `links`, `code`, `context` and `trace`
666
 * will be present depending on the other templates in the format type.
667
 *
668
 * @param string $format Format to use, including 'js' for JavaScript-enhanced HTML, 'html' for
669
 *    straight HTML output, or 'txt' for unformatted text.
670
 * @param array $strings Template strings, or a callback to be used for the output format.
671
 * @return The resulting format string set.
672
 */
673
        public static function addFormat($format, array $strings) {
674
                $self = Debugger::getInstance();
675
                if (isset($self->_templates[$format])) {
676
                        if (isset($strings['links'])) {
677
                                $self->_templates[$format]['links'] = array_merge(
678
                                        $self->_templates[$format]['links'],
679
                                        $strings['links']
680
                                );
681
                                unset($strings['links']);
682
                        }
683
                        $self->_templates[$format] = array_merge($self->_templates[$format], $strings);
684
                } else {
685
                        $self->_templates[$format] = $strings;
686
                }
687
                return $self->_templates[$format];
688
        }
689

    
690
/**
691
 * Switches output format, updates format strings.
692
 * Can be used to switch the active output format:
693
 *
694
 * @param string $format Format to use, including 'js' for JavaScript-enhanced HTML, 'html' for
695
 *    straight HTML output, or 'txt' for unformatted text.
696
 * @param array $strings Template strings to be used for the output format.
697
 * @return string
698
 * @deprecated 3.0.0 Use Debugger::outputAs() and Debugger::addFormat(). Will be removed
699
 *   in 3.0
700
 */
701
        public static function output($format = null, $strings = array()) {
702
                $self = Debugger::getInstance();
703
                $data = null;
704

    
705
                if ($format === null) {
706
                        return Debugger::outputAs();
707
                }
708

    
709
                if (!empty($strings)) {
710
                        return Debugger::addFormat($format, $strings);
711
                }
712

    
713
                if ($format === true && !empty($self->_data)) {
714
                        $data = $self->_data;
715
                        $self->_data = array();
716
                        $format = false;
717
                }
718
                Debugger::outputAs($format);
719
                return $data;
720
        }
721

    
722
/**
723
 * Takes a processed array of data from an error and displays it in the chosen format.
724
 *
725
 * @param string $data Data to output.
726
 * @return void
727
 */
728
        public function outputError($data) {
729
                $defaults = array(
730
                        'level' => 0,
731
                        'error' => 0,
732
                        'code' => 0,
733
                        'description' => '',
734
                        'file' => '',
735
                        'line' => 0,
736
                        'context' => array(),
737
                        'start' => 2,
738
                );
739
                $data += $defaults;
740

    
741
                $files = $this->trace(array('start' => $data['start'], 'format' => 'points'));
742
                $code = '';
743
                $file = null;
744
                if (isset($files[0]['file'])) {
745
                        $file = $files[0];
746
                } elseif (isset($files[1]['file'])) {
747
                        $file = $files[1];
748
                }
749
                if ($file) {
750
                        $code = $this->excerpt($file['file'], $file['line'] - 1, 1);
751
                }
752
                $trace = $this->trace(array('start' => $data['start'], 'depth' => '20'));
753
                $insertOpts = array('before' => '{:', 'after' => '}');
754
                $context = array();
755
                $links = array();
756
                $info = '';
757

    
758
                foreach ((array)$data['context'] as $var => $value) {
759
                        $context[] = "\${$var} = " . $this->exportVar($value, 3);
760
                }
761

    
762
                switch ($this->_outputFormat) {
763
                        case false:
764
                                $this->_data[] = compact('context', 'trace') + $data;
765
                                return;
766
                        case 'log':
767
                                $this->log(compact('context', 'trace') + $data);
768
                                return;
769
                }
770

    
771
                $data['trace'] = $trace;
772
                $data['id'] = 'cakeErr' . uniqid();
773
                $tpl = array_merge($this->_templates['base'], $this->_templates[$this->_outputFormat]);
774

    
775
                if (isset($tpl['links'])) {
776
                        foreach ($tpl['links'] as $key => $val) {
777
                                $links[$key] = CakeText::insert($val, $data, $insertOpts);
778
                        }
779
                }
780

    
781
                if (!empty($tpl['escapeContext'])) {
782
                        $context = h($context);
783
                }
784

    
785
                $infoData = compact('code', 'context', 'trace');
786
                foreach ($infoData as $key => $value) {
787
                        if (empty($value) || !isset($tpl[$key])) {
788
                                continue;
789
                        }
790
                        if (is_array($value)) {
791
                                $value = implode("\n", $value);
792
                        }
793
                        $info .= CakeText::insert($tpl[$key], array($key => $value) + $data, $insertOpts);
794
                }
795
                $links = implode(' ', $links);
796

    
797
                if (isset($tpl['callback']) && is_callable($tpl['callback'])) {
798
                        return call_user_func($tpl['callback'], $data, compact('links', 'info'));
799
                }
800
                echo CakeText::insert($tpl['error'], compact('links', 'info') + $data, $insertOpts);
801
        }
802

    
803
/**
804
 * Get the type of the given variable. Will return the class name
805
 * for objects.
806
 *
807
 * @param mixed $var The variable to get the type of
808
 * @return string The type of variable.
809
 */
810
        public static function getType($var) {
811
                if (is_object($var)) {
812
                        return get_class($var);
813
                }
814
                if ($var === null) {
815
                        return 'null';
816
                }
817
                if (is_string($var)) {
818
                        return 'string';
819
                }
820
                if (is_array($var)) {
821
                        return 'array';
822
                }
823
                if (is_int($var)) {
824
                        return 'integer';
825
                }
826
                if (is_bool($var)) {
827
                        return 'boolean';
828
                }
829
                if (is_float($var)) {
830
                        return 'float';
831
                }
832
                if (is_resource($var)) {
833
                        return 'resource';
834
                }
835
                return 'unknown';
836
        }
837

    
838
/**
839
 * Verifies that the application's salt and cipher seed value has been changed from the default value.
840
 *
841
 * @return void
842
 */
843
        public static function checkSecurityKeys() {
844
                if (Configure::read('Security.salt') === 'DYhG93b0qyJfIxfs2guVoUubWwvniR2G0FgaC9mi') {
845
                        trigger_error(__d('cake_dev', 'Please change the value of %s in %s to a salt value specific to your application.', '\'Security.salt\'', 'APP/Config/core.php'), E_USER_NOTICE);
846
                }
847

    
848
                if (Configure::read('Security.cipherSeed') === '76859309657453542496749683645') {
849
                        trigger_error(__d('cake_dev', 'Please change the value of %s in %s to a numeric (digits only) seed value specific to your application.', '\'Security.cipherSeed\'', 'APP/Config/core.php'), E_USER_NOTICE);
850
                }
851
        }
852

    
853
}