PageRenderTime 58ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/ref.php

https://github.com/firejdl/php-ref
PHP | 2178 lines | 1285 code | 443 blank | 450 comment | 206 complexity | b2587fa2f2b1df4da959d1af127fce48 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /**
  3. * Shortcut to ref, HTML mode
  4. *
  5. * @version 1.0
  6. * @param mixed $args
  7. */
  8. function r(){
  9. // arguments passed to this function
  10. $args = func_get_args();
  11. // options (operators) gathered by the expression parser;
  12. // this variable gets passed as reference to getInputExpressions(), which will store the operators in it
  13. $options = array();
  14. // doh
  15. $output = '';
  16. $ref = new ref('html');
  17. // names of the arguments that were passed to this function
  18. $expressions = ref::getInputExpressions($options);
  19. // something went wrong while trying to parse the source expressions?
  20. // if so, silently ignore this part and leave out the expression info
  21. if(func_num_args() !== count($expressions))
  22. $expressions = array_fill(0, func_num_args(), null);
  23. foreach($args as $index => $arg)
  24. $output .= $ref->query($arg, $expressions[$index]);
  25. // return the results if this function was called with the error suppression operator
  26. if(in_array('@', $options, true))
  27. return $output;
  28. // IE goes funky if there's no doctype
  29. if(!headers_sent() && !ob_get_length())
  30. print '<!DOCTYPE HTML><html><head><title>REF</title><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /></head><body>';
  31. print $output;
  32. // stop the script if this function was called with the bitwise not operator
  33. if(in_array('~', $options, true)){
  34. print '</body></html>';
  35. exit(0);
  36. }
  37. }
  38. /**
  39. * Shortcut to ref, plain text mode
  40. *
  41. * @version 1.0
  42. * @param mixed $args
  43. */
  44. function rt(){
  45. $args = func_get_args();
  46. $options = array();
  47. $output = '';
  48. $expressions = ref::getInputExpressions($options);
  49. $ref = new ref('text');
  50. if(func_num_args() !== count($expressions))
  51. $expressions = array_fill(0, func_num_args(), null);
  52. foreach($args as $index => $arg)
  53. $output .= $ref->query($arg, $expressions[$index]);
  54. if(in_array('@', $options, true))
  55. return $output;
  56. if(!headers_sent())
  57. header('Content-Type: text/plain; charset=utf-8');
  58. print $output;
  59. if(in_array('~', $options, true))
  60. exit(0);
  61. }
  62. /**
  63. * REF is a nicer alternative to PHP's print_r() / var_dump().
  64. *
  65. * @version 1.0
  66. * @author digitalnature - http://digitalnature.eu
  67. */
  68. class ref{
  69. const
  70. MARKER_KEY = '_phpRefArrayMarker_';
  71. protected static
  72. /**
  73. * CPU time used for processing
  74. *
  75. * @var array
  76. */
  77. $time = 0,
  78. /**
  79. * Configuration (+ default values)
  80. *
  81. * @var array
  82. */
  83. $config = array(
  84. // initial expand depth (for HTML mode only)
  85. 'expandDepth' => 1,
  86. // shortcut functions used to access the query method below;
  87. // if they are namespaced, the namespace must be present as well (methods are not supported)
  88. 'shortcutFunc' => array('r', 'rt'),
  89. // callbacks for custom/external formatters (as associative array: format => callback)
  90. 'formatter' => array(),
  91. // when this option is set to TRUE, additional information is
  92. // returned (note that this seriously affects performance):
  93. // - string matches (date, file, functions, classes, json, serialized data, regex etc.)
  94. // - extra information for some resource types
  95. // - contents of iterator objects
  96. 'extendedInfo' => true,
  97. // stylesheet path (for HTML only);
  98. // 'false' means no styles
  99. 'stylePath' => '{:dir}/ref.css',
  100. // javascript path (for HTML only);
  101. // 'false' means no js
  102. 'scriptPath' => '{:dir}/ref.js',
  103. );
  104. protected
  105. /**
  106. * Tracks current nesting level
  107. *
  108. * @var int
  109. */
  110. $level = 0,
  111. /**
  112. * Max. expand depth of this instance
  113. *
  114. * @var int
  115. */
  116. $expDepth = 1,
  117. /**
  118. * Output format of this instance
  119. *
  120. * @var string
  121. */
  122. $format = null,
  123. /**
  124. * Some environment variables
  125. * used to determine feature support
  126. *
  127. * @var string
  128. */
  129. $env = array(),
  130. /**
  131. * Used to cache output to speed up processing.
  132. * Cached objects will not be processed again in the same query
  133. *
  134. * @var array
  135. */
  136. $cache = array();
  137. /**
  138. * Constructor
  139. *
  140. * @param string $format Output format, defaults to 'html'
  141. * @param int|null $expDepth Maximum expand depth (relevant to the HTML format)
  142. */
  143. public function __construct($format = 'html', $expDepth = null){
  144. $this->format = $format;
  145. $this->expDepth = ($expDepth !== null) ? $expDepth : static::$config['expandDepth'];
  146. $this->env = array(
  147. // php 5.4+ ?
  148. 'is54' => version_compare(PHP_VERSION, '5.4') >= 0,
  149. // is the 'mbstring' extension active?
  150. 'mbStr' => function_exists('mb_detect_encoding'),
  151. // @see: https://bugs.php.net/bug.php?id=52469
  152. 'supportsDate' => (strncasecmp(PHP_OS, 'WIN', 3) !== 0) || (version_compare(PHP_VERSION, '5.3.10') >= 0),
  153. );
  154. }
  155. /**
  156. * Enforce proper use of this class
  157. *
  158. * @param string $name
  159. */
  160. public function __get($name){
  161. throw new \Exception('No such property');
  162. }
  163. /**
  164. * Enforce proper use of this class
  165. *
  166. * @param string $name
  167. * @param mixed $value
  168. */
  169. public function __set($name, $value){
  170. throw new \Exception('Not allowed');
  171. }
  172. /**
  173. * Used to dispatch output to a custom formatter
  174. *
  175. * @param string $name
  176. * @param array $args
  177. * @return string
  178. */
  179. public function __call($name, array $args = array()){
  180. if(isset(static::$config['formatters'][$name]))
  181. return call_user_func_array(static::$config['formatters'][$name], $args);
  182. throw new \Exception('Method not defined');
  183. }
  184. /**
  185. * Generate structured information about a variable/value/expression (subject)
  186. *
  187. * @param mixed $subject
  188. * @param string $expression
  189. * @return string
  190. */
  191. public function query($subject, $expression = null){
  192. $startTime = microtime(true);
  193. $output = $this->{"to{$this->format}"}('root', $this->evaluate($subject), $this->evaluateExp($expression));
  194. $this->cache = array();
  195. static::$time += microtime(true) - $startTime;
  196. return $output;
  197. }
  198. /**
  199. * Executes a function the given number of times and returns the elapsed time.
  200. *
  201. * Keep in mind that the returned time includes function call overhead (including
  202. * microtime calls) x iteration count. This is why this is better suited for
  203. * determining which of two or more functions is the fastest, rather than
  204. * finding out how fast is a single function.
  205. *
  206. * @param int $iterations Number of times the function will be executed
  207. * @param callable $function Function to execute
  208. * @param mixed &$output If given, last return value will be available in this variable
  209. * @return double Elapsed time
  210. */
  211. public static function timeFunc($iterations, $function, &$output = null){
  212. $time = 0;
  213. for($i = 0; $i < $iterations; $i++){
  214. $start = microtime(true);
  215. $output = call_user_func($function);
  216. $time += microtime(true) - $start;
  217. }
  218. return round($time, 4);
  219. }
  220. /**
  221. * Timery utility
  222. *
  223. * First call of this function will start the timer.
  224. * The second call will stop the timer and return the elapsed time
  225. * since the timer started.
  226. *
  227. * Multiple timers can be controlled simultaneously by specifying a timer ID.
  228. *
  229. * @since 1.0
  230. * @param int $id Timer ID, optional
  231. * @param int $precision Precision of the result, optional
  232. * @return void|double Elapsed time, or void if the timer was just started
  233. */
  234. public static function timer($id = 1, $precision = 4){
  235. static
  236. $timers = array();
  237. // check if this timer was started, and display the elapsed time if so
  238. if(isset($timers[$id])){
  239. $elapsed = round(microtime(true) - $timers[$id], $precision);
  240. unset($timers[$id]);
  241. return $elapsed;
  242. }
  243. // ID doesn't exist, start new timer
  244. $timers[$id] = microtime(true);
  245. }
  246. /**
  247. * Parses a DocBlock comment into a data structure.
  248. *
  249. * @link http://pear.php.net/manual/en/standards.sample.php
  250. * @param string $comment DocBlock comment (must start with /**)
  251. * @param string|null $key Field to return (optional)
  252. * @return array|string|null Array containing all fields, array/string with the contents of
  253. * the requested field, or null if the comment is empty/invalid
  254. */
  255. public static function parseComment($comment, $key = null){
  256. $description = '';
  257. $tags = array();
  258. $tag = null;
  259. $pointer = '';
  260. $padding = 0;
  261. $comment = preg_split('/\r\n|\r|\n/', '* ' . trim($comment, "/* \t\n\r\0\x0B"));
  262. // analyze each line
  263. foreach($comment as $line){
  264. // drop any wrapping spaces
  265. $line = trim($line);
  266. // drop "* "
  267. if($line !== '')
  268. $line = substr($line, 2);
  269. if(strpos($line, '@') !== 0){
  270. // preserve formatting of tag descriptions,
  271. // because they may span across multiple lines
  272. if($tag !== null){
  273. $trimmed = trim($line);
  274. if($padding !== 0)
  275. $trimmed = static::strPad($trimmed, static::strLen($line) - $padding, ' ', STR_PAD_LEFT);
  276. else
  277. $padding = static::strLen($line) - static::strLen($trimmed);
  278. $pointer .= "\n{$trimmed}";
  279. continue;
  280. }
  281. // tag definitions have not started yet; assume this is part of the description text
  282. $description .= "\n{$line}";
  283. continue;
  284. }
  285. $padding = 0;
  286. $parts = explode(' ', $line, 2);
  287. // invalid tag? (should we include it as an empty array?)
  288. if(!isset($parts[1]))
  289. continue;
  290. $tag = substr($parts[0], 1);
  291. $line = ltrim($parts[1]);
  292. // tags that have a single component (eg. link, license, author, throws...);
  293. // note that @throws may have 2 components, however most people use it like "@throws ExceptionClass if whatever...",
  294. // which, if broken into two values, leads to an inconsistent description sentence
  295. if(!in_array($tag, array('global', 'param', 'return', 'var'))){
  296. $tags[$tag][] = $line;
  297. end($tags[$tag]);
  298. $pointer = &$tags[$tag][key($tags[$tag])];
  299. continue;
  300. }
  301. // tags with 2 or 3 components (var, param, return);
  302. $parts = explode(' ', $line, 2);
  303. $parts[1] = isset($parts[1]) ? ltrim($parts[1]) : null;
  304. $lastIdx = 1;
  305. // expecting 3 components on the 'param' tag: type varName varDescription
  306. if($tag === 'param'){
  307. $lastIdx = 2;
  308. if(in_array($parts[1][0], array('&', '$'), true)){
  309. $line = ltrim(array_pop($parts));
  310. $parts = array_merge($parts, explode(' ', $line, 2));
  311. $parts[2] = isset($parts[2]) ? ltrim($parts[2]) : null;
  312. }else{
  313. $parts[2] = $parts[1];
  314. $parts[1] = null;
  315. }
  316. }
  317. $tags[$tag][] = $parts;
  318. end($tags[$tag]);
  319. $pointer = &$tags[$tag][key($tags[$tag])][$lastIdx];
  320. }
  321. // split title from the description texts at the nearest 2x new-line combination
  322. // (note: loose check because 0 isn't valid as well)
  323. if(strpos($description, "\n\n")){
  324. list($title, $description) = explode("\n\n", $description, 2);
  325. // if we don't have 2 new lines, try to extract first sentence
  326. }else{
  327. // in order for a sentence to be considered valid,
  328. // the next one must start with an uppercase letter
  329. $sentences = preg_split('/(?<=[.?!])\s+(?=[A-Z])/', $description, 2, PREG_SPLIT_NO_EMPTY);
  330. // failed to detect a second sentence? then assume there's only title and no description text
  331. $title = isset($sentences[0]) ? $sentences[0] : $description;
  332. $description = isset($sentences[1]) ? $sentences[1] : '';
  333. }
  334. $title = ltrim($title);
  335. $description = ltrim($description);
  336. $data = compact('title', 'description', 'tags');
  337. if(!array_filter($data))
  338. return null;
  339. if($key !== null)
  340. return isset($data[$key]) ? $data[$key] : null;
  341. return $data;
  342. }
  343. /**
  344. * Split a regex into its components
  345. *
  346. * Based on "Regex Colorizer" by Steven Levithan (this is a translation from javascript)
  347. *
  348. * @link https://github.com/slevithan/regex-colorizer
  349. * @link https://github.com/symfony/Finder/blob/master/Expression/Regex.php#L64-74
  350. * @param string $pattern
  351. * @return array
  352. */
  353. public static function splitRegex($pattern){
  354. // detection attempt code from the Symfony Finder component
  355. $maybeValid = false;
  356. if(preg_match('/^(.{3,}?)([imsxuADU]*)$/', $pattern, $m)) {
  357. $start = substr($m[1], 0, 1);
  358. $end = substr($m[1], -1);
  359. if(($start === $end && !preg_match('/[*?[:alnum:] \\\\]/', $start)) || ($start === '{' && $end === '}'))
  360. $maybeValid = true;
  361. }
  362. if(!$maybeValid)
  363. throw new \Exception('Pattern does not appear to be a valid PHP regex');
  364. $output = array();
  365. $capturingGroupCount = 0;
  366. $groupStyleDepth = 0;
  367. $openGroups = array();
  368. $lastIsQuant = false;
  369. $lastType = 1; // 1 = none; 2 = alternator
  370. $lastStyle = null;
  371. preg_match_all('/\[\^?]?(?:[^\\\\\]]+|\\\\[\S\s]?)*]?|\\\\(?:0(?:[0-3][0-7]{0,2}|[4-7][0-7]?)?|[1-9][0-9]*|x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4}|c[A-Za-z]|[\S\s]?)|\((?:\?[:=!]?)?|(?:[?*+]|\{[0-9]+(?:,[0-9]*)?\})\??|[^.?*+^${[()|\\\\]+|./', $pattern, $matches);
  372. $matches = $matches[0];
  373. $getTokenCharCode = function($token){
  374. if(strlen($token) > 1 && $token[0] === '\\'){
  375. $t1 = substr($token, 1);
  376. if(preg_match('/^c[A-Za-z]$/', $t1))
  377. return strpos("ABCDEFGHIJKLMNOPQRSTUVWXYZ", strtoupper($t1[1])) + 1;
  378. if(preg_match('/^(?:x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4})$/', $t1))
  379. return intval(substr($t1, 1), 16);
  380. if(preg_match('/^(?:[0-3][0-7]{0,2}|[4-7][0-7]?)$/', $t1))
  381. return intval($t1, 8);
  382. $len = strlen($t1);
  383. if($len === 1 && strpos('cuxDdSsWw', $t1) !== false)
  384. return null;
  385. if($len === 1){
  386. switch ($t1) {
  387. case 'b': return 8;
  388. case 'f': return 12;
  389. case 'n': return 10;
  390. case 'r': return 13;
  391. case 't': return 9;
  392. case 'v': return 11;
  393. default: return $t1[0];
  394. }
  395. }
  396. }
  397. return ($token !== '\\') ? $token[0] : null;
  398. };
  399. foreach($matches as $m){
  400. if($m[0] === '['){
  401. $lastCC = null;
  402. $cLastRangeable = false;
  403. $cLastType = 0; // 0 = none; 1 = range hyphen; 2 = short class
  404. preg_match('/^(\[\^?)(]?(?:[^\\\\\]]+|\\\\[\S\s]?)*)(]?)$/', $m, $parts);
  405. array_shift($parts);
  406. list($opening, $content, $closing) = $parts;
  407. if(!$closing)
  408. throw new \Exception('Unclosed character class');
  409. preg_match_all('/[^\\\\-]+|-|\\\\(?:[0-3][0-7]{0,2}|[4-7][0-7]?|x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4}|c[A-Za-z]|[\S\s]?)/', $content, $ccTokens);
  410. $ccTokens = $ccTokens[0];
  411. $ccTokenCount = count($ccTokens);
  412. $output[] = array('chr' => $opening);
  413. foreach($ccTokens as $i => $cm) {
  414. if($cm[0] === '\\'){
  415. if(preg_match('/^\\\\[cux]$/', $cm))
  416. throw new \Exception('Incomplete regex token');
  417. if(preg_match('/^\\\\[dsw]$/i', $cm)) {
  418. $output[] = array('chr-meta' => $cm);
  419. $cLastRangeable = ($cLastType !== 1);
  420. $cLastType = 2;
  421. }elseif($cm === '\\'){
  422. throw new \Exception('Incomplete regex token');
  423. }else{
  424. $output[] = array('chr-meta' => $cm);
  425. $cLastRangeable = $cLastType !== 1;
  426. $lastCC = $getTokenCharCode($cm);
  427. }
  428. }elseif($cm === '-'){
  429. if($cLastRangeable){
  430. $nextToken = ($i + 1 < $ccTokenCount) ? $ccTokens[$i + 1] : false;
  431. if($nextToken){
  432. $nextTokenCharCode = $getTokenCharCode($nextToken[0]);
  433. if((!is_null($nextTokenCharCode) && $lastCC > $nextTokenCharCode) || $cLastType === 2 || preg_match('/^\\\\[dsw]$/i', $nextToken[0]))
  434. throw new \Exception('Reversed or invalid range');
  435. $output[] = array('chr-range' => '-');
  436. $cLastRangeable = false;
  437. $cLastType = 1;
  438. }else{
  439. $output[] = $closing ? array('chr' => '-') : array('chr-range' => '-');
  440. }
  441. }else{
  442. $output[] = array('chr' => '-');
  443. $cLastRangeable = ($cLastType !== 1);
  444. }
  445. }else{
  446. $output[] = array('chr' => $cm);
  447. $cLastRangeable = strlen($cm) > 1 || ($cLastType !== 1);
  448. $lastCC = $cm[strlen($cm) - 1];
  449. }
  450. }
  451. $output[] = array('chr' => $closing);
  452. $lastIsQuant = true;
  453. }elseif($m[0] === '('){
  454. if(strlen($m) === 2)
  455. throw new \Exception('Invalid or unsupported group type');
  456. if(strlen($m) === 1)
  457. $capturingGroupCount++;
  458. $groupStyleDepth = ($groupStyleDepth !== 5) ? $groupStyleDepth + 1 : 1;
  459. $openGroups[] = $m; // opening
  460. $lastIsQuant = false;
  461. $output[] = array("g{$groupStyleDepth}" => $m);
  462. }elseif($m[0] === ')'){
  463. if(!count($openGroups))
  464. throw new \Exception('No matching opening parenthesis');
  465. $output[] = array('g' . $groupStyleDepth => ')');
  466. $prevGroup = $openGroups[count($openGroups) - 1];
  467. $prevGroup = isset($prevGroup[2]) ? $prevGroup[2] : '';
  468. $lastIsQuant = !preg_match('/^[=!]/', $prevGroup);
  469. $lastStyle = "g{$groupStyleDepth}";
  470. $lastType = 0;
  471. $groupStyleDepth = ($groupStyleDepth !== 1) ? $groupStyleDepth - 1 : 5;
  472. array_pop($openGroups);
  473. continue;
  474. }elseif($m[0] === '\\'){
  475. if(isset($m[1]) && preg_match('/^[1-9]/', $m[1])){
  476. $nonBackrefDigits = '';
  477. $num = substr(+$m, 1);
  478. while($num > $capturingGroupCount){
  479. preg_match('/[0-9]$/', $num, $digits);
  480. $nonBackrefDigits = $digits[0] . $nonBackrefDigits;
  481. $num = floor($num / 10);
  482. }
  483. if($num > 0){
  484. $output[] = array('meta' => "\\{$num}", 'text' => $nonBackrefDigits);
  485. }else{
  486. preg_match('/^\\\\([0-3][0-7]{0,2}|[4-7][0-7]?|[89])([0-9]*)/', $m, $pts);
  487. $output[] = array('meta' => '\\' . $pts[1], 'text' => $pts[2]);
  488. }
  489. $lastIsQuant = true;
  490. }elseif(isset($m[1]) && preg_match('/^[0bBcdDfnrsStuvwWx]/', $m[1])){
  491. if(preg_match('/^\\\\[cux]$/', $m))
  492. throw new \Exception('Incomplete regex token');
  493. $output[] = array('meta' => $m);
  494. $lastIsQuant = (strpos('bB', $m[1]) === false);
  495. }elseif($m === '\\'){
  496. throw new \Exception('Incomplete regex token');
  497. }else{
  498. $output[] = array('text' => $m);
  499. $lastIsQuant = true;
  500. }
  501. }elseif(preg_match('/^(?:[?*+]|\{[0-9]+(?:,[0-9]*)?\})\??$/', $m)){
  502. if(!$lastIsQuant)
  503. throw new \Exception('Quantifiers must be preceded by a token that can be repeated');
  504. preg_match('/^\{([0-9]+)(?:,([0-9]*))?/', $m, $interval);
  505. if($interval && (+$interval[1] > 65535 || (isset($interval[2]) && (+$interval[2] > 65535))))
  506. throw new \Exception('Interval quantifier cannot use value over 65,535');
  507. if($interval && isset($interval[2]) && (+$interval[1] > +$interval[2]))
  508. throw new \Exception('Interval quantifier range is reversed');
  509. $output[] = array($lastStyle ? $lastStyle : 'meta' => $m);
  510. $lastIsQuant = false;
  511. }elseif($m === '|'){
  512. if($lastType === 1 || ($lastType === 2 && !count($openGroups)))
  513. throw new \Exception('Empty alternative effectively truncates the regex here');
  514. $output[] = count($openGroups) ? array("g{$groupStyleDepth}" => '|') : array('meta' => '|');
  515. $lastIsQuant = false;
  516. $lastType = 2;
  517. $lastStyle = '';
  518. continue;
  519. }elseif($m === '^' || $m === '$'){
  520. $output[] = array('meta' => $m);
  521. $lastIsQuant = false;
  522. }elseif($m === '.'){
  523. $output[] = array('meta' => '.');
  524. $lastIsQuant = true;
  525. }else{
  526. $output[] = array('text' => $m);
  527. $lastIsQuant = true;
  528. }
  529. $lastType = 0;
  530. $lastStyle = '';
  531. }
  532. if($openGroups)
  533. throw new \Exception('Unclosed grouping');
  534. return $output;
  535. }
  536. /**
  537. * Set or get configuration options
  538. *
  539. * @param string $key
  540. * @param mixed|null $value
  541. * @return mixed
  542. */
  543. public static function config($key, $value = null){
  544. if(!array_key_exists($key, static::$config))
  545. throw new \Exception(sprintf('Unrecognized option: "%s". Valid options are: %s', $key, implode(', ', array_keys(static::$config))));
  546. if($value === null)
  547. return static::$config[$key];
  548. if(is_array(static::$config[$key]))
  549. return static::$config[$key] = (array)$value;
  550. return static::$config[$key] = $value;
  551. }
  552. /**
  553. * Get styles and javascript (only generated for the 1st call)
  554. *
  555. * @return string
  556. */
  557. public static function getAssets(){
  558. // tracks style/jscript inclusion state (html only)
  559. static $didAssets = false;
  560. // first call? include styles and javascript
  561. if($didAssets)
  562. return '';
  563. ob_start();
  564. if(static::$config['stylePath'] !== false){
  565. ?>
  566. <style scoped>
  567. <?php readfile(str_replace('{:dir}', __DIR__, static::$config['stylePath'])); ?>
  568. </style>
  569. <?php
  570. }
  571. if(static::$config['scriptPath'] !== false){
  572. ?>
  573. <script>
  574. <?php readfile(str_replace('{:dir}', __DIR__, static::$config['scriptPath'])); ?>
  575. </script>
  576. <?php
  577. }
  578. // normalize space and remove comments
  579. $output = preg_replace('/\s+/', ' ', trim(ob_get_clean()));
  580. $output = preg_replace('!/\*.*?\*/!s', '', $output);
  581. $output = preg_replace('/\n\s*\n/', "\n", $output);
  582. $didAssets = true;
  583. return $output;
  584. }
  585. /**
  586. * Total CPU time used by the class
  587. *
  588. * @param int precision
  589. * @return double
  590. */
  591. public static function getTime($precision = 4){
  592. return round(static::$time, $precision);
  593. }
  594. /**
  595. * Determines the input expression(s) passed to the shortcut function
  596. *
  597. * @param array &$options Optional, options to gather (from operators)
  598. * @return array Array of string expressions
  599. */
  600. public static function getInputExpressions(array &$options = null){
  601. // used to determine the position of the current call,
  602. // if more queries calls were made on the same line
  603. static $lineInst = array();
  604. // pull only basic info with php 5.3.6+ to save some memory
  605. $trace = defined('DEBUG_BACKTRACE_IGNORE_ARGS') ? debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS) : debug_backtrace();
  606. while($callee = array_pop($trace)){
  607. // extract only the information we neeed
  608. $callee = array_intersect_key($callee, array_fill_keys(array('file', 'function', 'line'), false));
  609. extract($callee);
  610. // skip, if the called function doesn't match the shortcut function name
  611. if(!$function || !preg_grep("/{$function}/i" , static::$config['shortcutFunc']))
  612. continue;
  613. if(!$line || !$file)
  614. return array();
  615. $code = file($file);
  616. $code = $code[$line - 1]; // multiline expressions not supported!
  617. $instIndx = 0;
  618. $tokens = token_get_all("<?php {$code}");
  619. // locate the caller position in the line, and isolate argument tokens
  620. foreach($tokens as $i => $token){
  621. // match token with our shortcut function name
  622. if(is_string($token) || ($token[0] !== T_STRING) || (strcasecmp($token[1], $function) !== 0))
  623. continue;
  624. // is this some method that happens to have the same name as the shortcut function?
  625. if(isset($tokens[$i - 1]) && is_array($tokens[$i - 1]) && in_array($tokens[$i - 1][0], array(T_DOUBLE_COLON, T_OBJECT_OPERATOR), true))
  626. continue;
  627. // find argument definition start, just after '('
  628. if(isset($tokens[$i + 1]) && ($tokens[$i + 1][0] === '(')){
  629. $instIndx++;
  630. if(!isset($lineInst[$line]))
  631. $lineInst[$line] = 0;
  632. if($instIndx <= $lineInst[$line])
  633. continue;
  634. $lineInst[$line]++;
  635. // gather options
  636. if($options !== null){
  637. $j = $i - 1;
  638. while(isset($tokens[$j]) && is_string($tokens[$j]) && in_array($tokens[$j], array('@', '+', '-', '!', '~')))
  639. $options[] = $tokens[$j--];
  640. }
  641. $lvl = $index = $curlies = 0;
  642. $expressions = array();
  643. // get the expressions
  644. foreach(array_slice($tokens, $i + 2) as $token){
  645. if(is_array($token)){
  646. if($token[0] !== T_COMMENT)
  647. $expressions[$index][] = ($token[0] !== T_WHITESPACE) ? $token[1] : ' ';
  648. continue;
  649. }
  650. if($token === '{')
  651. $curlies++;
  652. if($token === '}')
  653. $curlies--;
  654. if($token === '(')
  655. $lvl++;
  656. if($token === ')')
  657. $lvl--;
  658. // assume next argument if a comma was encountered,
  659. // and we're not insde a curly bracket or inner parentheses
  660. if(($curlies < 1) && ($lvl === 0) && ($token === ',')){
  661. $index++;
  662. continue;
  663. }
  664. // negative parentheses count means we reached the end of argument definitions
  665. if($lvl < 0){
  666. foreach($expressions as &$expression)
  667. $expression = trim(implode('', $expression));
  668. return $expressions;
  669. }
  670. $expressions[$index][] = $token;
  671. }
  672. break;
  673. }
  674. }
  675. }
  676. }
  677. /**
  678. * Generates the output in plain text format
  679. *
  680. * @param string $element
  681. * @param string|null $arg1
  682. * @param string|null $arg2
  683. * @param string|array|null $meta
  684. * @return string
  685. */
  686. protected function toText($element, $arg1 = null, $arg2 = null, $meta = null){
  687. switch($element){
  688. case 'sep':
  689. return $arg1;
  690. case 'text':
  691. if($arg2 === null)
  692. $arg2 = $arg1;
  693. if($arg1 === 'specialString'){
  694. $arg2 = strtr($arg2, array(
  695. "\r" => '\r', // carriage return
  696. "\t" => '\t', // horizontal tab
  697. "\n" => '\n', // linefeed (new line)
  698. "\v" => '\v', // vertical tab
  699. "\e" => '\e', // escape
  700. "\f" => '\f', // form feed
  701. "\0" => '\0',
  702. ));
  703. }
  704. $formatMap = array(
  705. 'string' => '%3$s "%2$s"',
  706. 'integer' => 'int(%2$s)',
  707. 'double' => 'double(%2$s)',
  708. 'true' => 'bool(%2$s)',
  709. 'false' => 'bool(%2$s)',
  710. 'key' => '[%2$s]',
  711. );
  712. if(!is_string($meta))
  713. $meta = '';
  714. return isset($formatMap[$arg1]) ? sprintf($formatMap[$arg1], $arg1, $arg2, $meta) : $arg2;
  715. case 'match':
  716. return ($arg1 !== 'regex') ? "\n~~ {$arg1}: {$arg2}" : '';
  717. case 'contain':
  718. return ($arg1 !== 'regex') ? $arg2 : '';
  719. case 'link':
  720. return $arg1;
  721. case 'group':
  722. $prefix = ($arg2 !== null) ? $arg2 : '';
  723. if($arg1){
  724. $arg1 = $arg1 . "\n";
  725. $prefix .= " \xe2\x96\xbc";
  726. }
  727. return "({$prefix}{$arg1})";
  728. case 'section':
  729. $output = '';
  730. if($arg2 !== null)
  731. $output .= "\n\n " . $arg2 . "\n " . str_repeat('-', static::strLen($arg2));
  732. $lengths = array();
  733. // determine maximum column width
  734. foreach($arg1 as $item)
  735. foreach($item as $colIdx => $c)
  736. if(!isset($lengths[$colIdx]) || $lengths[$colIdx] < static::strLen($c))
  737. $lengths[$colIdx] = static::strLen($c);
  738. foreach($arg1 as $item){
  739. $lastColIdx = count($item) - 1;
  740. $padLen = 0;
  741. $output .= "\n ";
  742. foreach($item as $colIdx => $c){
  743. // skip empty columns
  744. if($lengths[$colIdx] < 1)
  745. continue;
  746. if($colIdx < $lastColIdx){
  747. $output .= static::strPad($c, $lengths[$colIdx]) . ' ';
  748. $padLen += $lengths[$colIdx] + 1;
  749. continue;
  750. }
  751. // indent the entire block
  752. $output .= str_replace("\n", "\n" . str_repeat(' ', $padLen + 2), $c);
  753. }
  754. }
  755. return $output;
  756. case 'bubbles':
  757. return $arg1 ? '[' . implode('', $arg1) . '] ' : '';
  758. case 'root':
  759. return sprintf("\n%s\n%s\n%s\n", $arg2, str_repeat('=', static::strLen($arg2)), $arg1);
  760. default:
  761. return '';
  762. }
  763. }
  764. /**
  765. * Generates the output in HTML5 format
  766. *
  767. * @param string $element
  768. * @param string|null $arg1
  769. * @param string|null $arg2
  770. * @param string|array|null $meta
  771. * @return string
  772. */
  773. protected function toHtml($element, $arg1 = null, $arg2 = null, $meta = null){
  774. // stores tooltip content for all entries;
  775. // to avoid having duplicate tooltip data in the HTML, we generate them once,
  776. // and use references (the Q index) to pull data when required;
  777. // this improves performance significantly
  778. static $tips = array();
  779. switch($element){
  780. case 'sep':
  781. return '<i>' . static::escape($arg1) . '</i>';
  782. case 'text':
  783. $tip = '';
  784. $arg2 = ($arg2 !== null) ? static::escape($arg2) : static::escape($arg1);
  785. if($arg1 === 'specialString'){
  786. $arg2 = strtr($arg2, array(
  787. "\r" => '<ins>\r</ins>', // carriage return
  788. "\t" => '<ins>\t</ins>', // horizontal tab
  789. "\n" => '<ins>\n</ins>', // linefeed (new line)
  790. "\v" => '<ins>\v</ins>', // vertical tab
  791. "\e" => '<ins>\e</ins>', // escape
  792. "\f" => '<ins>\f</ins>', // form feed
  793. "\0" => '<ins>\0</ins>',
  794. ));
  795. }
  796. // generate tooltip reference (probably the slowest part of the code ;)
  797. if($meta !== null){
  798. $tipIdx = array_search($meta, $tips, true);
  799. if($tipIdx === false)
  800. $tipIdx = array_push($tips, $meta) - 1;
  801. $tip = ' data-tip="' . $tipIdx . '"';
  802. }
  803. return ($arg1 !== 'name') ? "<b data-{$arg1}{$tip}>{$arg2}</b>" : "<b{$tip}>{$arg2}</b>";
  804. case 'match':
  805. return "<br><s>{$arg1}</s>{$arg2}";
  806. case 'contain':
  807. return "<b data-{$arg1}>{$arg2}</b>";
  808. case 'link':
  809. return "<a href=\"{$arg2}\" target=\"_blank\">{$arg1}</a>";
  810. case 'group':
  811. if($arg1){
  812. $exp = ($this->expDepth < 0) || (($this->expDepth > 0) && ($this->level <= $this->expDepth)) ? ' data-exp' : '';
  813. $arg1 = "<kbd{$exp}></kbd><div>{$arg1}</div>";
  814. }
  815. $prefix = ($arg2 !== null) ? '<ins>' . static::escape($arg2) . '</ins>' : '';
  816. return "<i>(</i>{$prefix}{$arg1}<i>)</i>";
  817. case 'section':
  818. $title = ($arg2 !== null) ? "<h4>{$arg2}</h4>" : '';
  819. $output = '';
  820. foreach($arg1 as $row)
  821. $output .= '<dl><dd>' . implode('</dd><dd>', $row) . '</dd></dl>';
  822. return "{$title}<div>{$output}</div>";
  823. case 'bubbles':
  824. return '<b data-mod>' . implode('', $arg1) . '</b>';
  825. case 'root':
  826. static $counter = 0;
  827. if($arg2 !== null)
  828. $arg2 = "<kbd>{$arg2}</kbd>";
  829. $assets = static::getAssets();
  830. $counter++;
  831. // process tooltips
  832. $tipHtml = '';
  833. foreach($tips as $idx => $meta){
  834. $tip = '';
  835. if(!is_array($meta))
  836. $meta = array('title' => $meta);
  837. $meta += array(
  838. 'title' => '',
  839. 'left' => '',
  840. 'description' => '',
  841. 'tags' => array(),
  842. 'sub' => array(),
  843. );
  844. $meta = static::escape($meta);
  845. if($meta['title'])
  846. $tip = "<i>{$meta['title']}</i>";
  847. if($meta['description'])
  848. $tip .= "<i>{$meta['description']}</i>";
  849. $tip = "<i>{$tip}</i>";
  850. if($meta['left'])
  851. $tip = "<b>{$meta['left']}</b>{$tip}";
  852. $tags = '';
  853. foreach($meta['tags'] as $tag => $values){
  854. foreach($values as $value){
  855. if($tag === 'param'){
  856. $value[0] = "{$value[0]} {$value[1]}";
  857. unset($value[1]);
  858. }
  859. $value = is_array($value) ? implode('</b><b>', $value) : $value;
  860. $tags .= "<i><b>@{$tag}</b><b>{$value}</b></i>";
  861. }
  862. }
  863. if($tags)
  864. $tip .= "<u>{$tags}</u>";
  865. $sub = '';
  866. foreach($meta['sub'] as $line)
  867. $sub .= '<i><b>' . implode('</b> <b>', $line) . '</b></i>';
  868. if($sub !== '')
  869. $tip .= "<u>{$sub}</u>";
  870. $tipHtml .= "<q>{$tip}</q>";
  871. }
  872. // empty tip array
  873. $tips = array();
  874. return "<!-- ref#{$counter} --><div>{$assets}<div class=\"ref\">{$arg2}<div>{$arg1}</div>{$tipHtml}</div></div><!-- /ref#{$counter} -->";
  875. default:
  876. return '';
  877. }
  878. }
  879. /**
  880. * Get all parent classes of a class
  881. *
  882. * @param Reflector $class Reflection object
  883. * @return array Array of ReflectionClass objects (starts with the ancestor, ends with the given class)
  884. */
  885. protected static function getParentClasses(\Reflector $class){
  886. $parents = array($class);
  887. while(($class = $class->getParentClass()) !== false)
  888. $parents[] = $class;
  889. return array_reverse($parents);
  890. }
  891. /**
  892. * Generate class / function info
  893. *
  894. * @param Reflector $reflector Class name or reflection object
  895. * @param string $single Skip parent classes
  896. * @param Reflector|null $context Object context (for methods)
  897. * @return string
  898. */
  899. protected function fromReflector(\Reflector $reflector, $single = '', \Reflector $context = null){
  900. // @todo: test this
  901. $hash = var_export(func_get_args(), true);
  902. // check if we already have this in the cache
  903. if(isset($this->cache[__FUNCTION__][$hash]))
  904. return $this->cache[__FUNCTION__][$hash];
  905. $f = "to{$this->format}";
  906. $items = array($reflector);
  907. if(($single === '') && ($reflector instanceof \ReflectionClass))
  908. $items = static::getParentClasses($reflector);
  909. foreach($items as &$item){
  910. $name = ($single !== '') ? $single : $item->getName();
  911. $comments = $item->isInternal() ? array() : static::parseComment($item->getDocComment());
  912. $meta = array('sub' => array());
  913. if($item->isInternal()){
  914. $extension = $item->getExtension();
  915. $meta['title'] = ($extension instanceof \ReflectionExtension) ? sprintf('Internal - part of %s (%s)', $extension->getName(), $extension->getVersion()) : 'Internal';
  916. }else{
  917. $comments = static::parseComment($item->getDocComment());
  918. if($comments)
  919. $meta += $comments;
  920. $meta['sub'][] = array('Defined in', basename($item->getFileName()) . ':' . $item->getStartLine());
  921. }
  922. if(($item instanceof \ReflectionFunction) || ($item instanceof \ReflectionMethod)){
  923. if(($context !== null) && ($context->getShortName() !== $item->getDeclaringClass()->getShortName()))
  924. $meta['sub'][] = array('Inherited from', $item->getDeclaringClass()->getShortName());
  925. if($item instanceof \ReflectionMethod){
  926. try{
  927. $proto = $item->getPrototype();
  928. $meta['sub'][] = array('Prototype defined by', $proto->class);
  929. }catch(\Exception $e){}
  930. }
  931. $item = $this->linkify($this->{$f}('text', 'name', $name, $meta), $item);
  932. continue;
  933. }
  934. $bubbles = array();
  935. // @todo: maybe - list interface methods
  936. if(!($item->isInterface() || ($this->env['is54'] && $item->isTrait()))){
  937. if($item->isAbstract())
  938. $bubbles[] = $this->{$f}('text', 'mod-abstract', 'A', 'Abstract');
  939. if($item->isFinal())
  940. $bubbles[] = $this->{$f}('text', 'mod-final', 'F', 'Final');
  941. // php 5.4+ only
  942. if($this->env['is54'] && $item->isCloneable())
  943. $bubbles[] = $this->{$f}('text', 'mod-cloneable', 'C', 'Cloneable');
  944. if($item->isIterateable())
  945. $bubbles[] = $this->{$f}('text', 'mod-iterateable', 'X', 'Iterateable');
  946. }
  947. if($item->isInterface() && $single !== '')
  948. $bubbles[] = $this->{$f}('text', 'mod-interface', 'I', 'Interface');
  949. $bubbles = $bubbles ? $this->{$f}('bubbles', $bubbles) : '';
  950. $name = $this->{$f}('text', 'name', $name, $meta);
  951. if($item->isInterface() && $single === '')
  952. $name .= sprintf(' (%d)', count($item->getMethods()));
  953. $item = $bubbles . $this->linkify($name, $item);
  954. }
  955. // store in cache and return
  956. return $this->cache[__FUNCTION__][$hash] = count($items) > 1 ? implode($this->{$f}('sep', ' :: '), $items) : $items[0];
  957. }
  958. /**
  959. * Generates an URL that points to the documentation page relevant for the requested context
  960. *
  961. * For internal functions and classes, the URI will point to the local PHP manual
  962. * if installed and configured, otherwise to php.net/manual (the english one)
  963. *
  964. * @param string $node Text to linkify
  965. * @param Reflector $reflector Reflector object (used to determine the URL scheme for internal stuff)
  966. * @param string|null $constant Constant name, if this is a request to linkify a constant
  967. * @return string Updated text
  968. */
  969. protected function linkify($node, \Reflector $reflector, $constant = null){
  970. static $docRefRoot = null, $docRefExt = null;
  971. // most people don't have this set
  972. if(!$docRefRoot)
  973. $docRefRoot = ($docRefRoot = rtrim(ini_get('docref_root'), '/')) ? $docRefRoot : 'http://php.net/manual/en';
  974. if(!$docRefExt)
  975. $docRefExt = ($docRefExt = ini_get('docref_ext')) ? $docRefExt : '.php';
  976. $phpNetSchemes = array(
  977. 'class' => $docRefRoot . '/class.%s' . $docRefExt,
  978. 'function' => $docRefRoot . '/function.%s' . $docRefExt,
  979. 'method' => $docRefRoot . '/%2$s.%1$s' . $docRefExt,
  980. 'property' => $docRefRoot . '/class.%2$s' . $docRefExt . '#%2$s.props.%1$s',
  981. 'constant' => $docRefRoot . '/class.%2$s' . $docRefExt . '#%2$s.constants.%1$s',
  982. );
  983. $url = '';
  984. $args = array();
  985. // determine scheme
  986. if($constant !== null){
  987. $type = 'constant';
  988. $args[] = $constant;
  989. }else{
  990. $type = explode('\\', get_class($reflector));
  991. $type = strtolower(ltrim(end($type), 'Reflection'));
  992. if($type === 'object')
  993. $type = 'class';
  994. }
  995. // properties don't have the internal flag;
  996. // also note that many internal classes use some kind of magic as properties (eg. DateTime);
  997. // these will only get linkifed if the declared class is internal one, and not an extension :(
  998. $parent = ($type !== 'property') ? $reflector : $reflector->getDeclaringClass();
  999. // internal function/method/class/property/constant
  1000. if($parent->isInternal()){
  1001. $args[] = $reflector->name;
  1002. if(in_array($type, array('method', 'property'), true))
  1003. $args[] = $reflector->getDeclaringClass()->getName();
  1004. $args = array_map(function($text){
  1005. return str_replace('_', '-', ltrim(strtolower($text), '\\_'));
  1006. }, $args);
  1007. // check for some special cases that have no links
  1008. $valid = (($type === 'method') || (strcasecmp($parent->name, 'stdClass') !== 0))
  1009. && (($type !== 'method') || (($reflector->name === '__construct') || strpos($reflector->name, '__') !== 0));
  1010. if($valid)
  1011. $url = vsprintf($phpNetSchemes[$type], $args);
  1012. // custom
  1013. }else{
  1014. switch(true){
  1015. // WordPress function;
  1016. // like pretty much everything else in WordPress, API links are inconsistent as well;
  1017. // so we're using queryposts.com as doc source for API
  1018. case ($type === 'function') && class_exists('WP') && defined('ABSPATH') && defined('WPINC'):
  1019. if(strpos($reflector->getFileName(), realpath(ABSPATH . WPINC)) === 0){
  1020. $url = sprintf('http://queryposts.com/function/%s', urlencode(strtolower($reflector->getName())));
  1021. break;
  1022. }
  1023. // @todo: handle more apps
  1024. }
  1025. }
  1026. if($url !== '')
  1027. return $this->{"to{$this->format}"}('link', $node, $url);
  1028. return $node;
  1029. }
  1030. /**
  1031. * Evaluates the given variable
  1032. *
  1033. * @param mixed $subject Variable to query
  1034. * @param bool $specialStr Should this be interpreted as a special string?
  1035. * @return mixed Result (both HTML and text modes generate strings)
  1036. */
  1037. protected function evaluate(&$subject, $specialStr = false){
  1038. // internal shortcut for to(Format) methods
  1039. $f = "to{$this->format}";
  1040. switch($type = gettype($subject)){
  1041. // null value
  1042. case 'NULL':
  1043. return $this->{$f}('text', 'null');
  1044. // integer/double/float
  1045. case 'integer':
  1046. case 'double':
  1047. return $this->{$f}('text', $type, $subject, $type);
  1048. // boolean
  1049. case 'boolean':
  1050. $text = $subject ? 'true' : 'false';
  1051. return $this->{$f}('text', $text, $text, $type);
  1052. // arrays
  1053. case 'array':
  1054. // empty array?
  1055. if(empty($subject))
  1056. return $this->{$f}('text', 'array') . $this->{$f}('group');
  1057. if(isset($subject[static::MARKER_KEY])){
  1058. unset($subject[static::MARKER_KEY]);
  1059. return $this->{$f}('text', 'array') . $this->{$f}('group', null, 'recursion');
  1060. }
  1061. $count = count($subject);
  1062. $subject[static::MARKER_KEY] = true;
  1063. // use splFixedArray() on PHP 5.4+ to save up some memory, because the subject array
  1064. // might contain a huge amount of entries.
  1065. // (note: lower versions of PHP 5.3 throw a heap corruption error after 10K entries)
  1066. // A more efficient way is to build the items as we go as strings,
  1067. // by concatenating the info foreach entry, but then we loose the flexibility that the
  1068. // entity/group/section methods provide us (exporting data in different formats
  1069. // and indenting in text mode would become harder)
  1070. $section = $this->env['is54'] ? new \SplFixedArray($count) : array();
  1071. $idx = 0;
  1072. $this->level++;
  1073. foreach($subject as $key => &$value){
  1074. // ignore our temporary marker
  1075. if($key === static::MARKER_KEY)
  1076. continue;
  1077. // first recursion level detection;
  1078. // this is optional (used to print consistent recursion info)
  1079. if(is_array($value)){
  1080. // save current value in a temporary variable
  1081. $buffer = $value;
  1082. // assign new value
  1083. $value = ($value !== 1) ? 1 : 2;
  1084. // if they're still equal, then we have a reference
  1085. if($value === $subject){
  1086. $value = $buffer;
  1087. $value[static::MARKER_KEY] = true;
  1088. $output = $this->evaluate($value);
  1089. $this->level--;
  1090. return $output;
  1091. }
  1092. // restoring original value
  1093. $value = $buffer;
  1094. }
  1095. $keyInfo = gettype($key);
  1096. if($keyInfo === 'string'){
  1097. $encoding = $this->env['mbStr'] ? mb_detect_encoding($key) : '';
  1098. $keyLen = $encoding && ($encoding !== 'ASCII') ? static::strLen($key) . '; ' . $encoding : static::strLen($key);
  1099. $keyInfo = "{$keyInfo}({$keyLen})";
  1100. }
  1101. $section[$idx++] = array(
  1102. $this->{$f}('text', 'key', $key, "Key: {$keyInfo}"),
  1103. $this->{$f}('sep', '=>'),
  1104. $this->evaluate($value, $specialStr),
  1105. );
  1106. }
  1107. unset($subject[static::MARKER_KEY]);
  1108. $output = $this->{$f}('text', 'array') . $this->{$f}('group', $this->{$f}('section', $section), $count);
  1109. $this->level--;
  1110. return $output;
  1111. // resource
  1112. case 'resource':
  1113. $meta = array();
  1114. $resType = get_resource_type($subject);
  1115. // @see: http://php.net/manual/en/resource.php
  1116. // need to add more...
  1117. switch($resType){
  1118. // curl extension resource
  1119. case 'curl':
  1120. $meta = curl_getinfo($subject);
  1121. break;
  1122. case 'FTP Buffer':
  1123. $meta = array(
  1124. 'time_out' => ftp_get_option($subject, FTP_TIMEOUT_SEC),
  1125. 'auto_seek' => ftp_get_option($subject, FTP_AUTOSEEK),
  1126. );
  1127. break;
  1128. // gd image extension resource
  1129. case 'gd':
  1130. if(!static::$config['extendedInfo'])
  1131. break;
  1132. $meta = array(
  1133. 'size' => sprintf('%d x %d', imagesx($subject), imagesy($subject)),
  1134. 'true_color' => imageistruecolor($subject),
  1135. );
  1136. break;
  1137. case 'ldap link':
  1138. if(!static::$config['extendedInfo'])
  1139. break;
  1140. $constants = get_defined_constants();
  1141. array_walk($constants, function($value, $key) use(&$constants){
  1142. if(strpos($key, 'LDAP_OPT_') !== 0)
  1143. unset($constants[$key]);
  1144. });
  1145. // this seems to fail on my setup :(
  1146. unset($constants['LDAP_OPT_NETWORK_TIMEOUT']);
  1147. foreach(array_slice($constants, 3) as $key => $value)
  1148. if(ldap_get_option($subject, (int)$value, $ret))
  1149. $meta[strtolower(substr($key, 9))] = $ret;
  1150. break;
  1151. // mysql connection (mysql extension is deprecated from php 5.4/5.5)
  1152. case 'mysql link':
  1153. case 'mysql link persistent':
  1154. if(!static::$config['extendedInfo'])
  1155. break;
  1156. $dbs = array();
  1157. $query = @mysql_list_dbs($subject);
  1158. while($row = @mysql_fetch_array($query))
  1159. $dbs[] = $row['Database'];
  1160. $meta = array(
  1161. 'host' => ltrim(@mysql_get_host_info ($subject), 'MySQL host info: '),
  1162. 'server_version' => @mysql_get_server_info($subject),
  1163. 'protocol_version' => @mysql_get_proto_info($subject),
  1164. 'databases' => $dbs,
  1165. );
  1166. break;
  1167. // mysql result
  1168. case 'mysql result':
  1169. if(!static::$config['extendedInfo'])
  1170. break;
  1171. while($row = @mysql_fetch_object($subject))
  1172. $meta[] = (array)$row;
  1173. break;
  1174. // stream resource (fopen, fsockopen, popen, opendir etc)
  1175. case 'stream':
  1176. $meta = stream_get_meta_data($subject);
  1177. break;
  1178. }
  1179. $section = array();
  1180. $this->level++;
  1181. foreach($meta as $key => $value){
  1182. $section[] = array(
  1183. $this->{$f}('text', 'resourceInfo', ucwords(str_replace('_', ' ', $key))),
  1184. $this->{$f}('sep', ':'),
  1185. $this->evaluate($value),
  1186. );
  1187. }
  1188. $output = $this->{$f}('text', 'resource', strval($subject)) . $this->{$f}('group', $this->{$f}('section', $section), $resType);
  1189. $this->level--;
  1190. return $output;
  1191. // string
  1192. case 'string':
  1193. $length = static::strLen($subject);
  1194. $encoding = $this->env['mbStr'] ? mb_detect_encoding($subject) : false;
  1195. $info = $encoding && ($encoding !== 'ASCII') ? $length . '; ' . $encoding : $length;
  1196. $add = '';
  1197. if($specialStr)
  1198. return $this->{$f}('sep', '"') . $this->{$f}('text', 'specialString', $subject, "string({$info})") . $this->{$f}('sep', '"');
  1199. // advanced checks only if there are 3 characteres or more
  1200. if(static::$config['extendedInfo'] && $length > 2){
  1201. $isNumeric = is_numeric($subject);
  1202. // very simple check to determine if the string could match a file path
  1203. // @note: this part of the code is very expensive
  1204. $isFile = ($length < 2048)
  1205. && (max(array_map('strlen', explode('/', str_replace('\\', '/', $subject)))) < 128)
  1206. && !preg_match('/[^\w\.\-\/\\\\:]|\..*\.|\.$|:(?!(?<=^[a-zA-Z]:)[\/\\\\])/', $subject);
  1207. if($isFile){
  1208. try{
  1209. $file = new \SplFileInfo($subject);
  1210. $flags = array();
  1211. $perms = $file->getPerms();
  1212. if(($perms & 0xC000) === 0xC000)

Large files files are truncated, but you can click here to view the full file