Version 1
[yaffs-website] / web / modules / contrib / devel / kint / kint / inc / kintParser.class.php
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          * main and usually single method a custom parser must implement
42          *
43          * @param mixed $variable
44          *
45          * @return mixed [!!!] false is returned if the variable is not of current type
46          */
47         abstract protected function _parse( & $variable );
48
49
50         /**
51          * the only public entry point to return a parsed representation of a variable
52          *
53          * @static
54          *
55          * @param      $variable
56          * @param null $name
57          *
58          * @throws Exception
59          * @return \kintParser
60          */
61         public final static function factory( & $variable, $name = null )
62         {
63                 isset( self::$_customDataTypes ) or self::_init();
64
65                 # save internal data to revert after dumping to properly handle recursions etc
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                 # first parse the variable based on its type
77                 $varType = gettype( $variable );
78                 $varType === 'unknown type' and $varType = 'unknown'; # PHP 5.4 inconsistency
79                 $methodName = '_parse_' . $varType;
80
81                 # objects can be presented in a different way altogether, INSTEAD, not ALONGSIDE the generic parser
82                 if ( $varType === 'object' ) {
83                         foreach ( self::$_objectParsers as $parserClass ) {
84                                 $className = 'Kint_Objects_' . $parserClass;
85
86                                 /** @var $object KintObject */
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                 # base type parser returning false means "stop processing further": e.g. recursion
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                         # if an alternative returns something that can be represented in an alternative way, don't :)
122                         self::$_skipAlternatives = true;
123
124                         # now check whether the variable can be represented in a different way
125                         foreach ( self::$_customDataTypes as $parserClass ) {
126                                 $className = 'Kint_Parsers_' . $parserClass;
127
128                                 /** @var $parser kintParser */
129                                 $parser       = new $className;
130                                 $parser->name = $name; # the parser may overwrite the name value, so set it first
131
132                                 if ( $parser->_parse( $variable ) !== false ) {
133                                         $varData->_alternatives[] = $parser;
134                                 }
135                         }
136
137
138                         # if alternatives exist, push extendedValue to their front and display it as one of alternatives
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; // todo add tabular "tolerance"
182                         }
183
184                         if ( isset( $keys ) && !$closeEnough ) {
185                                 # let's just see if the first two rows have same keys, that's faster and has the
186                                 # positive side effect of easily spotting missing keys in later rows
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                         // todo we could make the symbols hover-title show the code for the invisible symbol
253                         # when possible force invisible characters to have some sort of display (experimental)
254                         $value = preg_replace( '/[\x00-\x08\x0B\x0C\x0E-\x1F\x80-\x9F]/u', '?', $value );
255                 }
256
257                 # this call converts all non-ASCII characters into html chars of format
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                 # naturally, $GLOBALS variable is an intertwined recursion nightmare, use black magic
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 ] ) ) { # recursion; todo mayhaps show from where
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; # this must be AFTER _isArrayTabular
315                         $firstRow                   = true;
316                         $extendedValue              = '<table class="kint-report"><thead>';
317
318                         foreach ( $variable as $rowIndex => & $row ) {
319                                 # display strings in their full length
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>&nbsp;</th>';
338                                 }
339
340                                 # we iterate the known full set of keys from all rows in case some appeared at later rows,
341                                 # as we only check the first two to assume
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*"; // recursion occurred on a higher level, thus $this is 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                 # ArrayObject (and maybe ArrayIterator, did not try yet) unsurprisingly consist of mainly dark magic.
431                 # What bothers me most, var_dump sees no problem with it, and ArrayObject also uses a custom,
432                 # undocumented serialize function, so you can see the properties in internal functions, but
433                 # can never iterate some of them if the flags are not STD_PROP_LIST. Fun stuff.
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; // todo store reflectorObject here for alternatives cache
440                 $reflector               = new ReflectionObject( $variable );
441
442                 # add link to definition of userland objects
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                 # copy the object as an array as it provides more info than Reflection (depends)
455                 foreach ( $castedArray as $key => $value ) {
456                         /* casting object to array:
457                          * integer properties are inaccessible;
458                          * private variables have the class name prepended to the variable name;
459                          * protected variables have a '*' prepended to the variable name.
460                          * These prepended values have null bytes on either side.
461                          * http://www.php.net/manual/en/language.types.array.php#language.types.array.casting
462                          */
463                         if ( $key{0} === "\x00" ) {
464
465                                 $access = $key{1} === "*" ? "protected" : "private";
466
467                                 // Remove the access level from the variable name
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                                         // Only exists on PHP >= 5.2.4
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                                 // encode and truncate
583                                 $variableData->value         = '"'
584                                         . self::escape( self::_substr( $strippedString, 0, Kint::$maxStrLength, $encoding ), $encoding )
585                                         . '&hellip;"';
586                                 $variableData->extendedValue = self::escape( $variable, $encoding );
587
588                                 return;
589                         } elseif ( $variable !== $strippedString ) { // omit no data from display
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 }