1 <?php
2 3 4 5 6
7
8
9 if ( defined( 'KINT_DIR' ) ) return;
10
11
12 define( 'KINT_DIR', dirname( __FILE__ ) . '/' );
13 define( 'KINT_PHP53', version_compare( PHP_VERSION, '5.3.0' ) >= 0 );
14
15 require KINT_DIR . 'config.default.php';
16 require KINT_DIR . 'inc/kintVariableData.class.php';
17 require KINT_DIR . 'inc/kintParser.class.php';
18 require KINT_DIR . 'inc/kintObject.class.php';
19 require KINT_DIR . 'decorators/rich.php';
20 require KINT_DIR . 'decorators/plain.php';
21
22 if ( is_readable( KINT_DIR . 'config.php' ) ) {
23 require KINT_DIR . 'config.php';
24 }
25
26
27 if ( !empty( $GLOBALS['_kint_settings'] ) ) {
28 Kint::enabled( $GLOBALS['_kint_settings']['enabled'] );
29
30 foreach ( $GLOBALS['_kint_settings'] as $key => $val ) {
31 property_exists( 'Kint', $key ) and Kint::$$key = $val;
32 }
33
34 unset( $GLOBALS['_kint_settings'], $key, $val );
35 }
36
37 class Kint
38 {
39
40 private static $_enabledMode;
41
42 public static $returnOutput;
43 public static $fileLinkFormat;
44 public static $displayCalledFrom;
45 public static $charEncodings;
46 public static $maxStrLength;
47 public static $appRootDirs;
48 public static $maxLevels;
49 public static $theme;
50 public static $expandedByDefault;
51
52 public static $cliDetection;
53 public static $cliColors;
54
55 const MODE_RICH = 'r';
56 const MODE_WHITESPACE = 'w';
57 const MODE_CLI = 'c';
58 const MODE_PLAIN = 'p';
59
60
61 public static $aliases = array(
62 'methods' => array(
63 array( 'kint', 'dump' ),
64 array( 'kint', 'trace' ),
65 ),
66 'functions' => array(
67 'd',
68 'dd',
69 'ddd',
70 's',
71 'sd',
72 )
73 );
74
75 private static $_firstRun = true;
76
77 78 79 80 81 82 83 84 85 86 87 88 89 90
91 public static function enabled( $forceMode = null )
92 {
93
94 if ( isset( $forceMode ) ) {
95 $before = self::$_enabledMode;
96 self::$_enabledMode = $forceMode;
97
98 return $before;
99 }
100
101
102 return self::$_enabledMode;
103 }
104
105 106 107 108 109 110 111
112 public static function trace( $trace = null )
113 {
114 if ( !self::enabled() ) return '';
115
116 return self::dump( isset( $trace ) ? $trace : debug_backtrace( true ) );
117 }
118
119
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
155 public static function dump( $data = null )
156 {
157 if ( !self::enabled() ) return '';
158
159 list( $names, $modifiers, $callee, $previousCaller, $miniTrace ) = self::_getCalleeInfo(
160 defined( 'DEBUG_BACKTRACE_IGNORE_ARGS' )
161 ? debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS )
162 : debug_backtrace()
163 );
164 $modeOldValue = self::enabled();
165 $firstRunOldValue = self::$_firstRun;
166
167
168 if ( strpos( $modifiers, '-' ) !== false ) {
169 self::$_firstRun = true;
170 while ( ob_get_level() ) {
171 ob_end_clean();
172 }
173 }
174 if ( strpos( $modifiers, '!' ) !== false ) {
175 $expandedByDefaultOldValue = self::$expandedByDefault;
176 self::$expandedByDefault = true;
177 }
178 if ( strpos( $modifiers, '+' ) !== false ) {
179 $maxLevelsOldValue = self::$maxLevels;
180 self::$maxLevels = false;
181 }
182 if ( strpos( $modifiers, '@' ) !== false ) {
183 $returnOldValue = self::$returnOutput;
184 self::$returnOutput = true;
185 self::$_firstRun = true;
186 }
187 if ( strpos( $modifiers, '~' ) !== false ) {
188 self::enabled( self::MODE_WHITESPACE );
189 }
190
191
192 $mode = self::enabled();
193 if ( $mode === true ) {
194 $mode = PHP_SAPI === 'cli'
195 ? self::MODE_CLI
196 : self::MODE_RICH;
197 }
198 self::enabled( $mode );
199
200 $decorator = self::enabled() === self::MODE_RICH
201 ? 'Kint_Decorators_Rich'
202 : 'Kint_Decorators_Plain';
203
204 $output = '';
205 if ( self::$_firstRun ) {
206 $output .= call_user_func( array( $decorator, 'init' ) );
207 }
208
209
210 $trace = false;
211 if ( $names === array( null ) && func_num_args() === 1 && $data === 1 ) {
212 $trace = KINT_PHP53 ? debug_backtrace( true ) : debug_backtrace();
213 } elseif ( func_num_args() === 1 && is_array( $data ) ) {
214 $trace = $data;
215 }
216 $trace and $trace = self::_parseTrace( $trace );
217
218
219 $output .= call_user_func( array( $decorator, 'wrapStart' ) );
220 if ( $trace ) {
221 $output .= call_user_func( array( $decorator, 'decorateTrace' ), $trace );
222 } else {
223 $data = func_num_args() === 0
224 ? array( "[[no arguments passed]]" )
225 : func_get_args();
226
227 foreach ( $data as $k => $argument ) {
228 kintParser::reset();
229
230
231
232 $output .= call_user_func(
233 array( $decorator, 'decorate' ),
234 kintParser::factory( $argument, isset( $names[ $k ] ) ? $names[ $k ] : '' )
235 );
236 }
237 }
238
239 $output .= call_user_func( array( $decorator, 'wrapEnd' ), $callee, $miniTrace, $previousCaller );
240
241 self::enabled( $modeOldValue );
242
243 self::$_firstRun = false;
244 if ( strpos( $modifiers, '~' ) !== false ) {
245 self::$_firstRun = $firstRunOldValue;
246 } else {
247 self::enabled( $modeOldValue );
248 }
249 if ( strpos( $modifiers, '!' ) !== false ) {
250 self::$expandedByDefault = $expandedByDefaultOldValue;
251 }
252 if ( strpos( $modifiers, '+' ) !== false ) {
253 self::$maxLevels = $maxLevelsOldValue;
254 }
255 if ( strpos( $modifiers, '@' ) !== false ) {
256 self::$returnOutput = $returnOldValue;
257 self::$_firstRun = $firstRunOldValue;
258 return $output;
259 }
260
261 if ( self::$returnOutput ) return $output;
262
263 echo $output;
264 return '';
265 }
266
267
268 269 270 271 272 273 274 275
276 public static function shortenPath( $file )
277 {
278 $file = str_replace( '\\', '/', $file );
279 $shortenedName = $file;
280 $replaced = false;
281 if ( is_array( self::$appRootDirs ) ) foreach ( self::$appRootDirs as $path => $replaceString ) {
282 if ( empty( $path ) ) continue;
283
284 $path = str_replace( '\\', '/', $path );
285
286 if ( strpos( $file, $path ) === 0 ) {
287 $shortenedName = $replaceString . substr( $file, strlen( $path ) );
288 $replaced = true;
289 break;
290 }
291 }
292
293
294 if ( !$replaced ) {
295 $pathParts = explode( '/', str_replace( '\\', '/', KINT_DIR ) );
296 $fileParts = explode( '/', $file );
297 $i = 0;
298 foreach ( $fileParts as $i => $filePart ) {
299 if ( !isset( $pathParts[ $i ] ) || $pathParts[ $i ] !== $filePart ) break;
300 }
301
302 $shortenedName = ( $i ? '.../' : '' ) . implode( '/', array_slice( $fileParts, $i ) );
303 }
304
305 return $shortenedName;
306 }
307
308 public static function getIdeLink( $file, $line )
309 {
310 return str_replace( array( '%f', '%l' ), array( $file, $line ), self::$fileLinkFormat );
311 }
312
313 314 315 316 317 318 319 320 321
322 private static function _showSource( $file, $lineNumber, $padding = 7 )
323 {
324 if ( !$file OR !is_readable( $file ) ) {
325
326 return false;
327 }
328
329
330 $file = fopen( $file, 'r' );
331 $line = 0;
332
333
334 $range = array(
335 'start' => $lineNumber - $padding,
336 'end' => $lineNumber + $padding
337 );
338
339
340 $format = '% ' . strlen( $range['end'] ) . 'd';
341
342 $source = '';
343 while ( ( $row = fgets( $file ) ) !== false ) {
344
345 if ( ++$line > $range['end'] ) {
346 break;
347 }
348
349 if ( $line >= $range['start'] ) {
350
351 $row = htmlspecialchars( $row, ENT_NOQUOTES, 'UTF-8' );
352
353
354 $row = '<span>' . sprintf( $format, $line ) . '</span> ' . $row;
355
356 if ( $line === $lineNumber ) {
357
358 $row = '<div class="kint-highlight">' . $row . '</div>';
359 } else {
360 $row = '<div>' . $row . '</div>';
361 }
362
363
364 $source .= $row;
365 }
366 }
367
368
369 fclose( $file );
370
371 return $source;
372 }
373
374
375 376 377 378 379 380 381 382
383 private static function _getCalleeInfo( $trace )
384 {
385 $previousCaller = array();
386 $miniTrace = array();
387 $prevStep = array();
388
389
390 while ( $step = array_pop( $trace ) ) {
391
392 if ( self::_stepIsInternal( $step ) ) {
393 $previousCaller = $prevStep;
394 break;
395 } elseif ( isset( $step['file'], $step['line'] ) ) {
396 unset( $step['object'], $step['args'] );
397 array_unshift( $miniTrace, $step );
398 }
399
400 $prevStep = $step;
401 }
402 $callee = $step;
403
404 if ( !isset( $callee['file'] ) || !is_readable( $callee['file'] ) ) return false;
405
406
407
408 $file = fopen( $callee['file'], 'r' );
409 $line = 0;
410 $source = '';
411 while ( ( $row = fgets( $file ) ) !== false ) {
412 if ( ++$line > $callee['line'] ) break;
413 $source .= $row;
414 }
415 fclose( $file );
416 $source = self::_removeAllButCode( $source );
417
418
419 if ( empty( $callee['class'] ) ) {
420 $codePattern = $callee['function'];
421 } else {
422 if ( $callee['type'] === '::' ) {
423 $codePattern = $callee['class'] . "\x07*" . $callee['type'] . "\x07*" . $callee['function'];;
424 } else {
425 $codePattern = ".*\x07*" . $callee['type'] . "\x07*" . $callee['function'];;
426 }
427 }
428
429
430
431
432 preg_match_all( "
433 [
434 # beginning of statement
435 [\x07{(]
436
437 # search for modifiers (group 1)
438 ([-+!@~]*)?
439
440 # spaces
441 \x07*
442
443 # check if output is assigned to a variable (group 2) todo: does not detect concat
444 (
445 \\$[a-z0-9_]+ # variable
446 \x07*\\.?=\x07* # assignment
447 )?
448
449 # possibly a namespace symbol
450 \\\\?
451
452 # spaces again
453 \x07*
454
455 # main call to Kint
456 {$codePattern}
457
458 # spaces everywhere
459 \x07*
460
461 # find the character where kint's opening bracket resides (group 3)
462 (\\()
463
464 ]ix",
465 $source,
466 $matches,
467 PREG_OFFSET_CAPTURE
468 );
469
470 $modifiers = end( $matches[1] );
471 $assignment = end( $matches[2] );
472 $bracket = end( $matches[3] );
473
474 $modifiers = $modifiers[0];
475 if ( $assignment[1] !== -1 ) {
476 $modifiers .= '@';
477 }
478
479 $paramsString = preg_replace( "[\x07+]", ' ', substr( $source, $bracket[1] + 1 ) );
480
481
482
483
484
485 $c = strlen( $paramsString );
486 $inString = $escaped = $openedBracket = $closingBracket = false;
487 $i = 0;
488 $inBrackets = 0;
489 $openedBrackets = array();
490
491 while ( $i < $c ) {
492 $letter = $paramsString[ $i ];
493
494 if ( !$inString ) {
495 if ( $letter === '\'' || $letter === '"' ) {
496 $inString = $letter;
497 } elseif ( $letter === '(' || $letter === '[' ) {
498 $inBrackets++;
499 $openedBrackets[] = $openedBracket = $letter;
500 $closingBracket = $openedBracket === '(' ? ')' : ']';
501 } elseif ( $inBrackets && $letter === $closingBracket ) {
502 $inBrackets--;
503 array_pop( $openedBrackets );
504 $openedBracket = end( $openedBrackets );
505 $closingBracket = $openedBracket === '(' ? ')' : ']';
506 } elseif ( !$inBrackets && $letter === ')' ) {
507 $paramsString = substr( $paramsString, 0, $i );
508 break;
509 }
510 } elseif ( $letter === $inString && !$escaped ) {
511 $inString = false;
512 }
513
514
515
516 if ( $inBrackets > 0 ) {
517 if ( $inBrackets > 1 || $letter !== $openedBracket ) {
518 $paramsString[ $i ] = "\x07";
519 }
520 }
521 if ( $inString ) {
522 if ( $letter !== $inString || $escaped ) {
523 $paramsString[ $i ] = "\x07";
524 }
525 }
526
527 $escaped = !$escaped && ( $letter === '\\' );
528 $i++;
529 }
530
531
532 $arguments = explode( ',', preg_replace( "[\x07+]", '...', $paramsString ) );
533
534
535 $parameters = array();
536 $blacklist = array( 'null', 'true', 'false', 'array(...)', 'array()', '"..."', '[...]', 'b"..."', );
537 foreach ( $arguments as $argument ) {
538 $argument = trim( $argument );
539
540 if ( is_numeric( $argument )
541 || in_array( str_replace( "'", '"', strtolower( $argument ) ), $blacklist, true )
542 ) {
543 $parameters[] = null;
544 } else {
545 $parameters[] = $argument;
546 }
547 }
548
549 return array( $parameters, $modifiers, $callee, $previousCaller, $miniTrace );
550 }
551
552 553 554 555 556 557 558
559 private static function _removeAllButCode( $source )
560 {
561 $commentTokens = array(
562 T_COMMENT => true, T_INLINE_HTML => true, T_DOC_COMMENT => true
563 );
564 $whiteSpaceTokens = array(
565 T_WHITESPACE => true, T_CLOSE_TAG => true,
566 T_OPEN_TAG => true, T_OPEN_TAG_WITH_ECHO => true,
567 );
568
569 $cleanedSource = '';
570 foreach ( token_get_all( $source ) as $token ) {
571 if ( is_array( $token ) ) {
572 if ( isset( $commentTokens[ $token[0] ] ) ) continue;
573
574 if ( isset( $whiteSpaceTokens[ $token[0] ] ) ) {
575 $token = "\x07";
576 } else {
577 $token = $token[1];
578 }
579 } elseif ( $token === ';' ) {
580 $token = "\x07";
581 }
582
583 $cleanedSource .= $token;
584 }
585 return $cleanedSource;
586 }
587
588 589 590 591 592 593 594
595 private static function _stepIsInternal( $step )
596 {
597 if ( isset( $step['class'] ) ) {
598 foreach ( self::$aliases['methods'] as $alias ) {
599 if ( $alias[0] === strtolower( $step['class'] ) && $alias[1] === strtolower( $step['function'] ) ) {
600 return true;
601 }
602 }
603 return false;
604 } else {
605 return in_array( strtolower( $step['function'] ), self::$aliases['functions'], true );
606 }
607 }
608
609 private static function _parseTrace( array $data )
610 {
611 $trace = array();
612 $traceFields = array( 'file', 'line', 'args', 'class' );
613 $fileFound = false;
614
615
616 while ( $step = array_pop( $data ) ) {
617 if ( !is_array( $step ) || !isset( $step['function'] ) ) return false;
618 if ( !$fileFound && isset( $step['file'] ) && file_exists( $step['file'] ) ) {
619 $fileFound = true;
620 }
621
622 $valid = false;
623 foreach ( $traceFields as $element ) {
624 if ( isset( $step[ $element ] ) ) {
625 $valid = true;
626 break;
627 }
628 }
629 if ( !$valid ) return false;
630
631 if ( self::_stepIsInternal( $step ) ) {
632 $step = array(
633 'file' => $step['file'],
634 'line' => $step['line'],
635 'function' => '',
636 );
637 array_unshift( $trace, $step );
638 break;
639 }
640 if ( $step['function'] !== 'spl_autoload_call' ) {
641 array_unshift( $trace, $step );
642 }
643 }
644 if ( !$fileFound ) return false;
645
646 $output = array();
647 foreach ( $trace as $step ) {
648 if ( isset( $step['file'] ) ) {
649 $file = $step['file'];
650
651 if ( isset( $step['line'] ) ) {
652 $line = $step['line'];
653
654 if ( self::enabled() === self::MODE_RICH ) {
655 $source = self::_showSource( $file, $line );
656 }
657 }
658 }
659
660 $function = $step['function'];
661
662 if ( in_array( $function, array( 'include', 'include_once', 'require', 'require_once' ) ) ) {
663 if ( empty( $step['args'] ) ) {
664
665 $args = array();
666 } else {
667
668 $args = array( 'file' => self::shortenPath( $step['args'][0] ) );
669 }
670 } elseif ( isset( $step['args'] ) ) {
671 if ( empty( $step['class'] ) && !function_exists( $function ) ) {
672
673 $params = null;
674 } else {
675 try {
676 if ( isset( $step['class'] ) ) {
677 if ( method_exists( $step['class'], $function ) ) {
678 $reflection = new ReflectionMethod( $step['class'], $function );
679 } else if ( isset( $step['type'] ) && $step['type'] == '::' ) {
680 $reflection = new ReflectionMethod( $step['class'], '__callStatic' );
681 } else {
682 $reflection = new ReflectionMethod( $step['class'], '__call' );
683 }
684 } else {
685 $reflection = new ReflectionFunction( $function );
686 }
687
688
689 $params = $reflection->getParameters();
690 } catch ( Exception $e ) {
691 $params = null;
692 }
693 }
694
695 $args = array();
696 foreach ( $step['args'] as $i => $arg ) {
697 if ( isset( $params[ $i ] ) ) {
698
699 $args[ $params[ $i ]->name ] = $arg;
700 } else {
701
702 $args[ '#' . ( $i + 1 ) ] = $arg;
703 }
704 }
705 }
706
707 if ( isset( $step['class'] ) ) {
708
709 $function = $step['class'] . $step['type'] . $function;
710 }
711
712
713 $output[] = array(
714 'function' => $function,
715 'args' => isset( $args ) ? $args : null,
716 'file' => isset( $file ) ? $file : null,
717 'line' => isset( $line ) ? $line : null,
718 'source' => isset( $source ) ? $source : null,
719 'object' => isset( $step['object'] ) ? $step['object'] : null,
720 );
721
722 unset( $function, $args, $file, $line, $source );
723 }
724
725 return $output;
726 }
727 }
728
729
730 if ( !function_exists( 'd' ) ) {
731 732 733 734 735
736 function d()
737 {
738 if ( !Kint::enabled() ) return '';
739 $_ = func_get_args();
740 return call_user_func_array( array( 'Kint', 'dump' ), $_ );
741 }
742 }
743
744 if ( !function_exists( 'dd' ) ) {
745 746 747 748 749 750 751
752 function dd()
753 {
754 if ( !Kint::enabled() ) return '';
755
756 echo "<pre>Kint: dd() is being deprecated, please use ddd() instead</pre>\n";
757 $_ = func_get_args();
758 call_user_func_array( array( 'Kint', 'dump' ), $_ );
759 die;
760 }
761 }
762
763 if ( !function_exists( 'ddd' ) ) {
764 765 766 767 768 769
770 function ddd()
771 {
772 if ( !Kint::enabled() ) return '';
773 $_ = func_get_args();
774 call_user_func_array( array( 'Kint', 'dump' ), $_ );
775 die;
776 }
777 }
778
779 if ( !function_exists( 's' ) ) {
780 781 782 783 784 785 786 787 788 789 790 791 792
793 function s()
794 {
795 $enabled = Kint::enabled();
796 if ( !$enabled ) return '';
797
798 if ( $enabled === Kint::MODE_WHITESPACE ) {
799 $restoreMode = Kint::MODE_WHITESPACE;
800 } else {
801 $restoreMode = Kint::enabled(
802 PHP_SAPI === 'cli' ? Kint::MODE_WHITESPACE : Kint::MODE_PLAIN
803 );
804 }
805
806 $params = func_get_args();
807 $dump = call_user_func_array( array( 'Kint', 'dump' ), $params );
808 Kint::enabled( $restoreMode );
809 return $dump;
810 }
811 }
812
813 if ( !function_exists( 'sd' ) ) {
814 815 816 817 818 819 820
821 function sd()
822 {
823 $enabled = Kint::enabled();
824 if ( !$enabled ) return '';
825
826 if ( $enabled !== Kint::MODE_WHITESPACE ) {
827 Kint::enabled(
828 PHP_SAPI === 'cli' ? Kint::MODE_WHITESPACE : Kint::MODE_PLAIN
829 );
830 }
831
832 $params = func_get_args();
833 call_user_func_array( array( 'Kint', 'dump' ), $params );
834 die;
835 }
836 }
837