1 <?php
2
3 abstract class kintParser extends kintVariableData
4 {
5 private static $_level = 0;
6 private static $_customDataTypes;
7 private static $_objectParsers;
8 private static $_objects;
9 private static $_marker;
10
11 private static $_skipAlternatives = false;
12
13 private static $_placeFullStringInValue = false;
14
15
16 private static function _init()
17 {
18 $fh = opendir( KINT_DIR . 'parsers/custom/' );
19 while ( $fileName = readdir( $fh ) ) {
20 if ( substr( $fileName, -4 ) !== '.php' ) continue;
21
22 require KINT_DIR . 'parsers/custom/' . $fileName;
23 self::$_customDataTypes[] = substr( $fileName, 0, -4 );
24 }
25 $fh = opendir( KINT_DIR . 'parsers/objects/' );
26 while ( $fileName = readdir( $fh ) ) {
27 if ( substr( $fileName, -4 ) !== '.php' ) continue;
28
29 require KINT_DIR . 'parsers/objects/' . $fileName;
30 self::$_objectParsers[] = substr( $fileName, 0, -4 );
31 }
32 }
33
34 public static function reset()
35 {
36 self::$_level = 0;
37 self::$_objects = self::$_marker = null;
38 }
39
40 41 42 43 44 45 46
47 abstract protected function _parse( & $variable );
48
49
50 51 52 53 54 55 56 57 58 59 60
61 public final static function factory( & $variable, $name = null )
62 {
63 isset( self::$_customDataTypes ) or self::_init();
64
65
66 $revert = array(
67 'level' => self::$_level,
68 'objects' => self::$_objects,
69 );
70
71 self::$_level++;
72
73 $varData = new kintVariableData;
74 $varData->name = $name;
75
76
77 $varType = gettype( $variable );
78 $varType === 'unknown type' and $varType = 'unknown';
79 $methodName = '_parse_' . $varType;
80
81
82 if ( $varType === 'object' ) {
83 foreach ( self::$_objectParsers as $parserClass ) {
84 $className = 'Kint_Objects_' . $parserClass;
85
86
87 $object = new $className;
88 if ( ( $alternativeTabs = $object->parse( $variable ) ) !== false ) {
89 self::$_skipAlternatives = true;
90 $alternativeDisplay = new kintVariableData;
91 $alternativeDisplay->type = $object->name;
92 $alternativeDisplay->value = $object->value;
93 $alternativeDisplay->name = $name;
94
95 foreach ( $alternativeTabs as $name => $values ) {
96 $alternative = kintParser::factory( $values );
97 $alternative->type = $name;
98 if ( Kint::enabled() === Kint::MODE_RICH ) {
99 empty( $alternative->value ) and $alternative->value = $alternative->extendedValue;
100 $alternativeDisplay->_alternatives[] = $alternative;
101 } else {
102 $alternativeDisplay->extendedValue[] = $alternative;
103 }
104 }
105
106 self::$_skipAlternatives = false;
107 self::$_level = $revert['level'];
108 self::$_objects = $revert['objects'];
109 return $alternativeDisplay;
110 }
111 }
112 }
113
114
115 if ( self::$methodName( $variable, $varData ) === false ) {
116 self::$_level--;
117 return $varData;
118 }
119
120 if ( Kint::enabled() === Kint::MODE_RICH && !self::$_skipAlternatives ) {
121
122 self::$_skipAlternatives = true;
123
124
125 foreach ( self::$_customDataTypes as $parserClass ) {
126 $className = 'Kint_Parsers_' . $parserClass;
127
128
129 $parser = new $className;
130 $parser->name = $name;
131
132 if ( $parser->_parse( $variable ) !== false ) {
133 $varData->_alternatives[] = $parser;
134 }
135 }
136
137
138
139 if ( !empty( $varData->_alternatives ) && isset( $varData->extendedValue ) ) {
140 $_ = new kintVariableData;
141
142 $_->value = $varData->extendedValue;
143 $_->type = 'contents';
144 $_->size = null;
145
146 array_unshift( $varData->_alternatives, $_ );
147 $varData->extendedValue = null;
148 }
149
150 self::$_skipAlternatives = false;
151 }
152
153 self::$_level = $revert['level'];
154 self::$_objects = $revert['objects'];
155
156 if ( strlen( $varData->name ) > 80 ) {
157 $varData->name =
158 self::_substr( $varData->name, 0, 37 )
159 . '...'
160 . self::_substr( $varData->name, -38, null );
161 }
162 return $varData;
163 }
164
165 private static function _checkDepth()
166 {
167 return Kint::$maxLevels != 0 && self::$_level >= Kint::$maxLevels;
168 }
169
170 private static function _isArrayTabular( array $variable )
171 {
172 if ( Kint::enabled() !== Kint::MODE_RICH ) return false;
173
174 $arrayKeys = array();
175 $keys = null;
176 $closeEnough = false;
177 foreach ( $variable as $row ) {
178 if ( !is_array( $row ) || empty( $row ) ) return false;
179
180 foreach ( $row as $col ) {
181 if ( !empty( $col ) && !is_scalar( $col ) ) return false;
182 }
183
184 if ( isset( $keys ) && !$closeEnough ) {
185
186
187 if ( $keys !== array_keys( $row ) ) return false;
188
189 $closeEnough = true;
190 } else {
191 $keys = array_keys( $row );
192 }
193
194 $arrayKeys = array_unique( array_merge( $arrayKeys, $keys ) );
195 }
196
197 return $arrayKeys;
198 }
199
200 private static function _decorateCell( kintVariableData $kintVar )
201 {
202 if ( $kintVar->extendedValue !== null || !empty( $kintVar->_alternatives ) ) {
203 return '<td>' . Kint_Decorators_Rich::decorate( $kintVar ) . '</td>';
204 }
205
206 $output = '<td';
207
208 if ( $kintVar->value !== null ) {
209 $output .= ' title="' . $kintVar->type;
210
211 if ( $kintVar->size !== null ) {
212 $output .= " (" . $kintVar->size . ")";
213 }
214
215 $output .= '">' . $kintVar->value;
216 } else {
217 $output .= '>';
218
219 if ( $kintVar->type !== 'NULL' ) {
220 $output .= '<u>' . $kintVar->type;
221
222 if ( $kintVar->size !== null ) {
223 $output .= "(" . $kintVar->size . ")";
224 }
225
226 $output .= '</u>';
227 } else {
228 $output .= '<u>NULL</u>';
229 }
230 }
231
232
233 return $output . '</td>';
234 }
235
236
237 public static function escape( $value, $encoding = null )
238 {
239 if ( empty( $value ) ) return $value;
240
241 if ( Kint::enabled() === Kint::MODE_CLI ) {
242 $value = str_replace( "\x1b", "\\x1b", $value );
243 }
244
245 if ( Kint::enabled() === Kint::MODE_CLI || Kint::enabled() === Kint::MODE_WHITESPACE ) return $value;
246
247 $encoding or $encoding = self::_detectEncoding( $value );
248 $value = htmlspecialchars( $value, ENT_NOQUOTES, $encoding === 'ASCII' ? 'UTF-8' : $encoding );
249
250
251 if ( $encoding === 'UTF-8' ) {
252
253
254 $value = preg_replace( '/[\x00-\x08\x0B\x0C\x0E-\x1F\x80-\x9F]/u', '?', $value );
255 }
256
257
258 if ( function_exists( 'mb_encode_numericentity' ) ) {
259 $value = mb_encode_numericentity(
260 $value,
261 array( 0x80, 0xffff, 0, 0xffff, ),
262 $encoding
263 );
264 }
265
266 return $value;
267 }
268
269
270 private static $_dealingWithGlobals = false;
271
272 private static function _parse_array( &$variable, kintVariableData $variableData )
273 {
274 isset( self::$_marker ) or self::$_marker = "\x00" . uniqid();
275
276
277 $globalsDetector = false;
278 if ( array_key_exists( 'GLOBALS', $variable ) && is_array( $variable['GLOBALS'] ) ) {
279 $globalsDetector = "\x01" . uniqid();
280
281 $variable['GLOBALS'][ $globalsDetector ] = true;
282 if ( isset( $variable[ $globalsDetector ] ) ) {
283 unset( $variable[ $globalsDetector ] );
284 self::$_dealingWithGlobals = true;
285 } else {
286 unset( $variable['GLOBALS'][ $globalsDetector ] );
287 $globalsDetector = false;
288 }
289 }
290
291 $variableData->type = 'array';
292 $variableData->size = count( $variable );
293
294 if ( $variableData->size === 0 ) {
295 return;
296 }
297 if ( isset( $variable[ self::$_marker ] ) ) {
298 if ( self::$_dealingWithGlobals ) {
299 $variableData->value = '*RECURSION*';
300 } else {
301 unset( $variable[ self::$_marker ] );
302 $variableData->value = self::$_marker;
303 }
304 return false;
305 }
306 if ( self::_checkDepth() ) {
307 $variableData->extendedValue = "*DEPTH TOO GREAT*";
308 return false;
309 }
310
311 $isSequential = self::_isSequential( $variable );
312
313 if ( $variableData->size > 1 && ( $arrayKeys = self::_isArrayTabular( $variable ) ) !== false ) {
314 $variable[ self::$_marker ] = true;
315 $firstRow = true;
316 $extendedValue = '<table class="kint-report"><thead>';
317
318 foreach ( $variable as $rowIndex => & $row ) {
319
320 self::$_placeFullStringInValue = true;
321
322 if ( $rowIndex === self::$_marker ) continue;
323
324 if ( isset( $row[ self::$_marker ] ) ) {
325 $variableData->value = "*RECURSION*";
326 return false;
327 }
328
329
330 $extendedValue .= '<tr>';
331 if ( $isSequential ) {
332 $output = '<td>' . '#' . ( $rowIndex + 1 ) . '</td>';
333 } else {
334 $output = self::_decorateCell( kintParser::factory( $rowIndex ) );
335 }
336 if ( $firstRow ) {
337 $extendedValue .= '<th> </th>';
338 }
339
340
341
342 foreach ( $arrayKeys as $key ) {
343 if ( $firstRow ) {
344 $extendedValue .= '<th>' . self::escape( $key ) . '</th>';
345 }
346
347 if ( !array_key_exists( $key, $row ) ) {
348 $output .= '<td class="kint-empty"></td>';
349 continue;
350 }
351
352 $var = kintParser::factory( $row[ $key ] );
353
354 if ( $var->value === self::$_marker ) {
355 $variableData->value = '*RECURSION*';
356 return false;
357 } elseif ( $var->value === '*RECURSION*' ) {
358 $output .= '<td class="kint-empty"><u>*RECURSION*</u></td>';
359 } else {
360 $output .= self::_decorateCell( $var );
361 }
362 unset( $var );
363 }
364
365 if ( $firstRow ) {
366 $extendedValue .= '</tr></thead><tr>';
367 $firstRow = false;
368 }
369
370 $extendedValue .= $output . '</tr>';
371 }
372 self::$_placeFullStringInValue = false;
373
374 $variableData->extendedValue = $extendedValue . '</table>';
375
376 } else {
377 $variable[ self::$_marker ] = true;
378 $extendedValue = array();
379
380 foreach ( $variable as $key => & $val ) {
381 if ( $key === self::$_marker ) continue;
382
383 $output = kintParser::factory( $val );
384 if ( $output->value === self::$_marker ) {
385 $variableData->value = "*RECURSION*";
386 return false;
387 }
388 if ( !$isSequential ) {
389 $output->operator = '=>';
390 }
391 $output->name = $isSequential ? null : "'" . $key . "'";
392 $extendedValue[] = $output;
393 }
394 $variableData->extendedValue = $extendedValue;
395 }
396
397 if ( $globalsDetector ) {
398 self::$_dealingWithGlobals = false;
399 }
400
401 unset( $variable[ self::$_marker ] );
402 }
403
404
405 private static function _parse_object( &$variable, kintVariableData $variableData )
406 {
407 if ( function_exists( 'spl_object_hash' ) ) {
408 $hash = spl_object_hash( $variable );
409 } else {
410 ob_start();
411 var_dump( $variable );
412 preg_match( '[#(\d+)]', ob_get_clean(), $match );
413 $hash = $match[1];
414 }
415
416 $castedArray = (array) $variable;
417 $variableData->type = get_class( $variable );
418 $variableData->size = count( $castedArray );
419
420 if ( isset( self::$_objects[ $hash ] ) ) {
421 $variableData->value = '*RECURSION*';
422 return false;
423 }
424 if ( self::_checkDepth() ) {
425 $variableData->extendedValue = "*DEPTH TOO GREAT*";
426 return false;
427 }
428
429
430
431
432
433
434 if ( $variableData->type === 'ArrayObject' || is_subclass_of( $variable, 'ArrayObject' ) ) {
435 $arrayObjectFlags = $variable->getFlags();
436 $variable->setFlags( ArrayObject::STD_PROP_LIST );
437 }
438
439 self::$_objects[ $hash ] = true;
440 $reflector = new ReflectionObject( $variable );
441
442
443 if ( Kint::enabled() === Kint::MODE_RICH && Kint::$fileLinkFormat && $reflector->isUserDefined() ) {
444 $url = Kint::getIdeLink( $reflector->getFileName(), $reflector->getStartLine() );
445
446 $class = ( strpos( $url, 'http://' ) === 0 ) ? 'class="kint-ide-link" ' : '';
447 $variableData->type = "<a {$class}href=\"{$url}\">{$variableData->type}</a>";
448 }
449 $variableData->size = 0;
450
451 $extendedValue = array();
452 $encountered = array();
453
454
455 foreach ( $castedArray as $key => $value ) {
456 457 458 459 460 461 462
463 if ( $key{0} === "\x00" ) {
464
465 $access = $key{1} === "*" ? "protected" : "private";
466
467
468 $key = substr( $key, strrpos( $key, "\x00" ) + 1 );
469 } else {
470 $access = "public";
471 }
472
473 $encountered[ $key ] = true;
474
475 $output = kintParser::factory( $value, self::escape( $key ) );
476 $output->access = $access;
477 $output->operator = '->';
478 $extendedValue[] = $output;
479 $variableData->size++;
480 }
481
482 foreach ( $reflector->getProperties() as $property ) {
483 $name = $property->name;
484 if ( $property->isStatic() || isset( $encountered[ $name ] ) ) continue;
485
486 if ( $property->isProtected() ) {
487 $property->setAccessible( true );
488 $access = "protected";
489 } elseif ( $property->isPrivate() ) {
490 $property->setAccessible( true );
491 $access = "private";
492 } else {
493 $access = "public";
494 }
495
496 $value = $property->getValue( $variable );
497
498 $output = kintParser::factory( $value, self::escape( $name ) );
499 $output->access = $access;
500 $output->operator = '->';
501 $extendedValue[] = $output;
502 $variableData->size++;
503 }
504
505 if ( isset( $arrayObjectFlags ) ) {
506 $variable->setFlags( $arrayObjectFlags );
507 }
508
509 if ( $variableData->size ) {
510 $variableData->extendedValue = $extendedValue;
511 }
512 }
513
514
515 private static function _parse_boolean( &$variable, kintVariableData $variableData )
516 {
517 $variableData->type = 'bool';
518 $variableData->value = $variable ? 'TRUE' : 'FALSE';
519 }
520
521 private static function _parse_double( &$variable, kintVariableData $variableData )
522 {
523 $variableData->type = 'float';
524 $variableData->value = $variable;
525 }
526
527 private static function _parse_integer( &$variable, kintVariableData $variableData )
528 {
529 $variableData->type = 'integer';
530 $variableData->value = $variable;
531 }
532
533 private static function _parse_null( &$variable, kintVariableData $variableData )
534 {
535 $variableData->type = 'NULL';
536 }
537
538 private static function _parse_resource( &$variable, kintVariableData $variableData )
539 {
540 $resourceType = get_resource_type( $variable );
541 $variableData->type = "resource ({$resourceType})";
542
543 if ( $resourceType === 'stream' && $meta = stream_get_meta_data( $variable ) ) {
544
545 if ( isset( $meta['uri'] ) ) {
546 $file = $meta['uri'];
547
548 if ( function_exists( 'stream_is_local' ) ) {
549
550 if ( stream_is_local( $file ) ) {
551 $file = Kint::shortenPath( $file );
552 }
553 }
554
555 $variableData->value = $file;
556 }
557 }
558 }
559
560 private static function _parse_string( &$variable, kintVariableData $variableData )
561 {
562 $variableData->type = 'string';
563
564 $encoding = self::_detectEncoding( $variable );
565 if ( $encoding !== 'ASCII' ) {
566 $variableData->type .= ' ' . $encoding;
567 }
568
569
570 $variableData->size = self::_strlen( $variable, $encoding );
571 if ( Kint::enabled() !== Kint::MODE_RICH ) {
572 $variableData->value = '"' . self::escape( $variable, $encoding ) . '"';
573 return;
574 }
575
576
577 if ( !self::$_placeFullStringInValue ) {
578
579 $strippedString = preg_replace( '[\s+]', ' ', $variable );
580 if ( Kint::$maxStrLength && $variableData->size > Kint::$maxStrLength ) {
581
582
583 $variableData->value = '"'
584 . self::escape( self::_substr( $strippedString, 0, Kint::$maxStrLength, $encoding ), $encoding )
585 . '…"';
586 $variableData->extendedValue = self::escape( $variable, $encoding );
587
588 return;
589 } elseif ( $variable !== $strippedString ) {
590
591 $variableData->value = '"' . self::escape( $variable, $encoding ) . '"';
592 $variableData->extendedValue = self::escape( $variable, $encoding );
593
594 return;
595 }
596 }
597
598 $variableData->value = '"' . self::escape( $variable, $encoding ) . '"';
599 }
600
601 private static function _parse_unknown( &$variable, kintVariableData $variableData )
602 {
603 $type = gettype( $variable );
604 $variableData->type = "UNKNOWN" . ( !empty( $type ) ? " ({$type})" : '' );
605 $variableData->value = var_export( $variable, true );
606 }
607
608 }