File indexing completed on 2024-04-21 04:34:49

0001 <?php
0002 /*
0003     SPDX-FileCopyrightText: 2008 Niko Sams <niko.sams@gmail.com>
0004     SPDX-FileCopyrightText: 2010 Milian Wolff <mail@milianw.de>
0005 
0006     SPDX-License-Identifier: LGPL-2.0-or-later
0007 */
0008 
0009 if (!isset($_SERVER['argv'][1])) {
0010     $msg = "Usage:\n".$_SERVER['argv'][0]." [path to phpdoc]\n";
0011     $msg .= "you may checkout from the php svn server using this command:\n";
0012     $msg .= "svn checkout http://svn.php.net/repository/phpdoc/en/trunk ./phpdoc-en\n";
0013     $msg .= "\nTo debug files/directories use this: ".$_SERVER['argv'][0]." --debug PATH ...\n";
0014     file_put_contents('php://stderr', $msg);
0015     exit(-1);
0016 }
0017 
0018 $skipClasses = array();
0019 
0020 $skipClasses[] = 'self';
0021 $skipClasses[] = 'parent';
0022 $skipClasses[] = '__php_incomplete_class';
0023 $skipClasses[] = 'php_user_filter';
0024 $skipClasses[] = 'static'; // O_o where does that come from?
0025 $skipClasses[] = 'componere\abstract\definition'; // invalid namespace identifier, discussed in https://github.com/krakjoe/componere/issues/5
0026 
0027 // interfaces wrongly declared as classes
0028 $interfaceClasses = array();
0029 $interfaceClasses[] = 'sessionhandlerinterface';
0030 $interfaceClasses[] = 'yaf_route_interface';
0031 $interfaceClasses[] = 'yaf_view_interface';
0032 
0033 $abstractClasses[] = array();
0034 $abstractClasses[] = 'reflectionfunctionabstract';
0035 $abstractClasses[] = 'xmldiff\base';
0036 $abstractClasses[] = 'yaf_action_abstract';
0037 
0038 $skipComments = array();
0039 $skipComments[] = ':';
0040 $skipComments[] = '(method):';
0041 $skipComments[] = 'Description here.';
0042 $skipComments[] = 'Description here...';
0043 $skipComments[] = 'Description';
0044 $skipComments[] = 'The function description goes here.';
0045 
0046 $classes = array();
0047 $constants = array();
0048 $constants_comments = array();
0049 $variables = array();
0050 $existingFunctions = array();
0051 $versions = array();
0052 
0053 if ($_SERVER['argv'][1] == '--debug') {
0054     // only debug given file
0055     define('DEBUG', true);
0056     $dirs = array();
0057     foreach ( $_SERVER['argv'] as $i => $v ) {
0058         if ( $i <= 1 ) {
0059             continue;
0060         } else if ( is_dir($v) ) {
0061             $dirs[] = $v;
0062         } else if ( file_exists($v) ) {
0063             parseFile(new SplFileInfo($v));
0064         } else {
0065             trigger_error("bad argument: ".$v, E_USER_ERROR);
0066         }
0067     }
0068 } else {
0069     define('DEBUG', false);
0070 
0071     if (!file_exists($_SERVER['argv'][1])) {
0072         file_put_contents('php://stderr', "phpdoc path not found");
0073         exit(-1);
0074     }
0075 
0076     $dirs = array(
0077         $_SERVER['argv'][1]."/reference",
0078         $_SERVER['argv'][1]."/features",
0079         $_SERVER['argv'][1]."/appendices",
0080         $_SERVER['argv'][1]."/language/predefined/"
0081     );
0082 }
0083 
0084 foreach ($dirs as $dir) {
0085     $dirIt = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($dir));
0086     foreach ($dirIt as $file) {
0087         parseFile($file);
0088     }
0089 }
0090 
0091 unset($existingFunctions);
0092 
0093 /*
0094  Here be dirty hacks! PHP's documentation isn't as good as could be wished for...
0095  */
0096 
0097 // Add constant only classes
0098 foreach (array_keys($constants) as $c) {
0099     if ($pos = strpos($c, '::')) {
0100         $class = substr($c, 0, $pos);
0101         $isInterface = false;
0102         $isAbstractClass = false;
0103         newClassEntry($class);
0104     }
0105 }
0106 
0107 $skipFunctions = array();
0108 // remove delete() function which only acts as a pointer to unlink
0109 // in the documentation but does not actually exist as a alias in PHP
0110 $skipFunctions[] = 'delete';
0111 
0112 // awesome uncallable functions - noone knows wth that should be...
0113 $skipMethods = array();
0114 $skipMethods[] = 'list';
0115 $skipMethods[] = 'declare';
0116 $skipMethods[] = 'do';
0117 $skipMethods[] = 'echo';
0118 $skipMethods[] = 'function';
0119 
0120 /*
0121  Here ends the hackings...
0122  */
0123 
0124 function constTypeValue($ctype) {
0125     if ($ctype == 'integer' || $ctype == 'int') {
0126         return "0";
0127     } else if ($ctype == 'string') {
0128         return "''";
0129     } else if ($ctype == 'bool') {
0130         return "false";
0131     } else if ($ctype == 'float') {
0132         return "0.0";
0133     } else {
0134         // default to integer const type
0135         return "0";
0136     }
0137 }
0138 
0139 function removeTag($xml, $tag) {
0140     $tag = preg_quote($tag, '#');
0141     return trim(preg_replace('#(^<'.$tag.'[^>]*>|</'.$tag.'>$)#s', '', trim($xml)));
0142 }
0143 
0144 function cleanupComment($comment) {
0145     // <function|parameter>...</> to {@link ...}
0146     $comment = preg_replace('#<(function|parameter)>(.+)</\1>#U', '{@link $2}', $comment);
0147     // remove <para> and other stuff
0148     ///TODO: support web-links, lists and tables
0149     $comment = strip_tags($comment);
0150     $comment = html_entity_decode($comment);
0151 
0152     // make sure no */ occurs in a comment...
0153     $comment = preg_replace('#\*/#', '* /', $comment);
0154 
0155     $comment = preg_replace('#(?<=[^\n])\n(?=[^\n])#s', ' ', $comment);
0156 
0157     $comment = preg_replace('#  +#', ' ', $comment);
0158     $comment = preg_replace('#^ | $#m', '', $comment);
0159     $comment = preg_replace("#\n{3,}#", "\n\n", $comment);
0160 
0161     $comment = trim($comment);
0162     return $comment;
0163 }
0164 
0165 function prepareComment($comment, array $more, $indent = '') {
0166     $comment = cleanupComment($comment);
0167     if (empty($comment) && empty($more)) {
0168         return '';
0169     }
0170     $comment = wordwrap($comment, 70, "\n", false);
0171     if ( !empty($more) ) {
0172         if ( !empty($comment) ) {
0173             $comment .= "\n\n";
0174         }
0175         foreach($more as $s) {
0176             $comment .= str_replace("\n", "\n  ", // indent
0177                             wordwrap(cleanupComment($s), 68, "\n", false)
0178                         )."\n";
0179         }
0180     }
0181     $comment = rtrim($comment);
0182     // add indentation and asterisk
0183     $comment = preg_replace("#^#m", $indent." * ", $comment);
0184     $comment = str_replace(" * \n", " *\n", $comment);
0185     return $indent."/**\n".
0186                    $comment."\n".
0187            $indent." **/\n";
0188 }
0189 
0190 function sortByName($a, $b) {
0191     return strnatcasecmp($a['name'], $b['name']);
0192 }
0193 
0194 $fileHeader  = "<?php\n";
0195 $fileHeader .= "// THIS FILE IS GENERATED\n";
0196 $fileHeader .= "// WARNING! All changes made in this file will be lost!\n\n";
0197 
0198 $declarationCount = 0;
0199 $out = $fileHeader;
0200 
0201 // make sure the output it somehow ordered to prevent huge svn diffs
0202 uksort($variables, 'strnatcasecmp');
0203 uksort($classes, 'strnatcasecmp');
0204 uksort($constants, 'strnatcasecmp');
0205 
0206 // put some base classes up front
0207 $baseClasses = [ 'exception', 'error', 'throwable', 'iterator', 'traversable' ];
0208 foreach ($baseClasses as $baseClass) {
0209     if (array_key_exists($baseClass, $classes)) {
0210         $base = $classes[$baseClass];
0211         unset($classes[$baseClass]);
0212         $classes = array_merge(array($baseClass => $base), $classes);
0213         reset($classes);
0214     }
0215 }
0216 
0217 foreach ($variables as $name=>$var) {
0218     $declarationCount++;
0219     $moreDesc = array();
0220     if ($var['deprecated']) {
0221         $moreDesc[] = "@deprecated";
0222     }
0223     if (isset($var['superglobal']) && $var['superglobal']) {
0224         $moreDesc[] = "@superglobal";
0225     }
0226     $out .= prepareComment($var['desc'], $moreDesc);
0227     $out .= "$name = array();\n\n";
0228 }
0229 
0230 // make skipclasses lowercase
0231 foreach ($skipClasses as &$name) {
0232     $name = strtolower($name);
0233 }
0234 
0235 foreach ($classes as $class => $i) {
0236     if (in_array($class, $skipClasses)) continue; //skip those as they are documented in spl.php
0237     if ($class != 'global') {
0238         if (isset($i['desc'])) {
0239             $out .= prepareComment($i['desc'], array());
0240         }
0241         if (!empty($i['namespace'])) {
0242             $out .= 'namespace ' . $i['namespace'] . " {\n";
0243         }
0244         $class = $i['prettyName'];
0245         $out .= ($i['isAbstract']) ? 'abstract ' : '';
0246         $out .= ($i['isFinal'] && !$i['isInterface']) ? 'final ' : '';
0247         $out .= ($i['isInterface'] ? 'interface' : 'class') . " " . $class;
0248         if (isset($i['extends'])) {
0249             if (!is_array($i['extends']) && !in_array(strtolower($i['extends']), $skipClasses)) {
0250                 $out .= " extends {$i['extends']}";
0251             } elseif (is_array($i['extends'])) {
0252                 $out .= " extends ";
0253                 foreach ($i['extends'] as $entry) {
0254                     if (!in_array(strtolower($entry), $skipClasses)) {
0255                         $out .= "$entry, ";
0256                     }
0257                 }
0258                 $out = rtrim($out, ', ');
0259             }
0260         }
0261         if (isset($i['implements'])) {
0262             $out .= " implements ".implode(", ", $i['implements']);
0263         }
0264         $out .= " {\n";
0265         $declarationCount++;
0266     }
0267 
0268     $indent = '';
0269     if ($class != 'global') $indent = '    ';
0270 
0271     if (array_key_exists('extends', $i)) {
0272         if (!is_array($i['extends'])) {
0273             $extends = [ $i['extends'] ];
0274         } else {
0275             $extends = $i['extends'];
0276         }
0277 
0278         foreach ($extends as $entry) {
0279             if (in_array(strtolower($entry), $skipClasses)) {
0280                 $base = $classes[strtolower($entry)];
0281 
0282                 $i['properties'] = array_merge($i['properties'], $base['properties']);
0283                 $i['functions']  = array_merge($i['functions'], $base['functions']);
0284             }
0285         }
0286     }
0287 
0288     foreach ($constants as $c=>$ctype) {
0289         if ($pos = strpos($c, '::')) {
0290             if (substr($c, 0, $pos) == $class) {
0291                 $moreDesc = array();
0292                 $moreDesc[] = "@var {$ctype}";
0293                 if ( isset($constants_comments[$c]) ) {
0294                     $out .= prepareComment($constants_comments[$c], $moreDesc, $indent);
0295                 }
0296                 unset($constants[$c]);
0297                 $c = substr($c, $pos+2);
0298                 $out .= "    const $c = ".constTypeValue($ctype).";\n\n";
0299                 $declarationCount++;
0300             }
0301         }
0302     }
0303 
0304     usort($i['properties'], 'sortByName');
0305     $handled = [];
0306     foreach ($i['properties'] as $f) {
0307         if (in_array($f['name'], $handled)) {
0308             continue;
0309         }
0310         $handled[] = $f['name'];
0311         $moreDesc = array();
0312         if ($f['type']) {
0313             $moreDesc[] = "@var {$f['type']}";
0314         }
0315         ///HACK the directory stuff has really bad documentation
0316         if ($class != 'directory') {
0317             $out .= prepareComment($f['desc'], $moreDesc, $indent);
0318         }
0319         if ($class != 'global' && array_key_exists('modifiers', $f) && is_array($f['modifiers'])) {
0320             $f['modifiers'] = array_filter($f['modifiers'], function ($value){ return $value != 'readonly'; });
0321             $modifiers  = implode(' ', $f['modifiers']);
0322             $modifiers .= empty($modifiers) ? 'public ' : ' ';
0323         }
0324         $out .= "{$indent}{$modifiers}$".$f['name'].";\n\n";
0325         $declarationCount++;
0326     }
0327 
0328     usort($i['functions'], 'sortByName');
0329     $handled = [];
0330     foreach ($i['functions'] as $f) {
0331         if (in_array($f['name'], $handled)) {
0332             continue;
0333         }
0334         $handled[] = $f['name'];
0335         if ( $class == 'global' && in_array($f['name'], $skipFunctions) ) {
0336             continue;
0337         } else if ( $class != 'global' && in_array($f['name'], $skipMethods) ) {
0338             continue;
0339         }
0340         if ($endpos = strrpos($f['name'], '\\')) {
0341             $out .= 'namespace ' . substr($f['name'], 0, $endpos) . " {\n\n";
0342             $f['name'] = substr($f['name'], $endpos + 1);
0343         }
0344         $moreDesc = array();
0345         foreach ($f['params'] as $pi=>$param) {
0346             $desc = '';
0347             if ( isset($param['desc']) ) {
0348                 $desc = trim($param['desc']);
0349             }
0350             $param_name = str_replace('$...', '...$', '$'.$param['name']);
0351             $moreDesc[] = "@param {$param['type']} $param_name $desc";
0352         }
0353         if ($f['type']) {
0354             $moreDesc[] = rtrim("@return {$f['type']} {$f['return_desc']}");
0355         }
0356         $version_key = strtolower(($class == 'global' ? '' : $class.'::') . $f['name']);
0357         if (isset($versions[$version_key])) {
0358             $moreDesc[] = "@since {$versions[$version_key]}";
0359         }
0360         ///HACK the directory stuff has really bad documentation
0361         if ($class != 'directory') {
0362             $out .= prepareComment($f['desc'], $moreDesc, $indent);
0363         }
0364         if ($f['name'] == '__construct' && strtolower($class) == 'eventutil') {
0365             //HACK: PHP doesn't allow abstract final classes, so make the constructor private instead.
0366             $f['modifiers'] = [ 'private' ];
0367         }
0368         if ($class != 'global' && array_key_exists('modifiers', $f) && is_array($f['modifiers'])) {
0369             if ($i['isInterface'] === true) {
0370                 $f['modifiers'] = array_filter($f['modifiers'], function ($value){ return $value != 'abstract' && $value != 'final'; });
0371             }
0372             $modifiers  = implode(' ', $f['modifiers']);
0373             $modifiers .= empty($modifiers) ? '' : ' ';
0374         } else {
0375             $modifiers = '';
0376         }
0377         $out .= "{$indent}{$modifiers}function ".$f['name'];
0378         $out .= "(";
0379         $first = true;
0380         $params_total = count($f['params'])-1;
0381         foreach ($f['params'] as $pi=>$param) {
0382             $param_name = str_replace('$...', '...$', '$'.$param['name']);
0383             $param_name = str_replace('"', '', $param_name);
0384             if (substr($param_name, 0, 3) == '...' && $pi < $params_total) {
0385                 // varargs in the middle of arguments are invalid syntax.
0386                 // print the documentation, but ignore them in the function signature
0387                 continue;
0388             }
0389             if (!$first) $out .= ", ";
0390             $first = false;
0391             if ($param['isRef']) $out .= "&";
0392             $out .= $param_name;
0393         }
0394         $out .= ")";
0395         if ( $i['isInterface'] || (array_key_exists('modifiers', $f) && in_array('abstract', $f['modifiers'])) ) {
0396             $out .= ";";
0397         } else {
0398             $out .= "{}";
0399         }
0400         $out .= "\n\n";
0401         if ($endpos) $out .= "}\n\n";
0402         $declarationCount++;
0403     }
0404 
0405     if ($class != 'global') $out .= "}\n";
0406     if (!empty($i['namespace'])) $out .= "}\n";
0407 }
0408 foreach ($constants as $c=>$ctype) {
0409     if (strpos($c, '::')===false) {
0410         if ( isset($constants_comments[$c]) ) {
0411           $out .= prepareComment($constants_comments[$c], array());
0412         }
0413         $out .= "define('$c', ".constTypeValue($ctype).");\n";
0414         $declarationCount++;
0415     }
0416 }
0417 chdir(dirname(__FILE__));
0418 if ( !DEBUG ) {
0419     echo "saving phpfunctions.php file\n";
0420     file_put_contents("phpfunctions.php", $out);
0421 
0422     if ( shell_exec("which php-parser") ) {
0423         echo "making sure phpfunctions file is valid...\n";
0424         system("php-parser phpfunctions.php", $ret);
0425         if ( $ret != 0 ) {
0426             die("could not parse file, aborting\n");
0427         }
0428     } else {
0429         echo "note: put php-parser in your path and I can check the generated file directly...\n";
0430     }
0431 
0432     echo "done\n";
0433 } else {
0434     echo "phpfunctions.php\n~~~~\n$out\n~~~~\n";
0435 }
0436 echo "wrote ".$declarationCount." declarations\n";
0437 
0438 /**
0439  * Parse file
0440  *
0441  * @param SplFileInfo  $file  File handler
0442  * @return  bool
0443  */
0444 function parseFile($file, $funcOverload="", $classOverload="") {
0445 global $existingFunctions, $constants, $constants_comments, $variables, $classes, $isInterface, $isAbstractClass, $isFinalClass, $versions;
0446 
0447     if (substr($file->getFilename(), -4) != '.xml') return false;
0448     if (substr($file->getFilename(), 0, 9) == 'entities.') return false;
0449     $string = file_get_contents($file->getPathname());
0450     $isInterface = (strpos($string, '<phpdoc:classref') !== false &&
0451                    strpos($string, '&reftitle.interfacesynopsis;') !== false) ||
0452                    strpos($string, ' interface</title>') !== false;
0453     $isAbstractClass = false;
0454     $isFinalClass = false;
0455 
0456     $string = str_replace('&null;', 'NULL', $string);
0457     $string = str_replace('&true;', 'TRUE', $string);
0458     $string = str_replace('&false;', 'FALSE', $string);
0459     $string = preg_replace('#(?:(&amp;|&gt;|&lt;)|&[A-Za-z\\.0-9-_]+;)#', '$1', $string);
0460     $removeSections = array();
0461     $removeSections[] = 'apd.installwin32';
0462     $removeSections[] = 'intl.intldateformatter-constants.calendartypes';
0463     foreach ($removeSections as $i) {
0464         $string = preg_replace('#'.preg_quote('<section xml:id="'.$i.'">').'.*?</section>#s', '', $string);
0465     }
0466     echo "reading documentation from {$file->getPathname()}\n";
0467 
0468     libxml_use_internal_errors(TRUE);
0469     $xml = simplexml_load_string($string,  "SimpleXMLElement",  LIBXML_NOCDATA);
0470 
0471     if ($xml === false) {
0472       echo "  parsing XMl failed!\n";
0473       return false;
0474     }
0475 
0476     if ( $file->getFilename() == 'versions.xml' ) {
0477         foreach ( $xml->xpath('/versions/function') as $f ) {
0478             $attrs = $f->attributes();
0479             $versions[strtolower($attrs['name'])] = (string) $attrs['from'];
0480         }
0481         return;
0482     }
0483 
0484     $xml->registerXPathNamespace('db', 'http://docbook.org/ns/docbook');
0485     $xml->registerXPathNamespace('phpdoc', 'http://php.net/ns/phpdoc');
0486     if ($vars = $xml->xpath('//phpdoc:varentry//db:refnamediv')) {
0487         foreach ($vars as $var) {
0488             foreach ($var->refname as $i) {
0489                 $i = (string)$i;
0490                 if ( isset($variables[$i]) ) {
0491                     $v = $variables[$i];
0492                 } else {
0493                     $v = array();
0494                 }
0495                 if (substr($i, 0, 1) != '$') continue;
0496                 if (substr($i, -10) == ' [removed]') continue;
0497 
0498                 if (substr($i, -13) == ' [deprecated]') {
0499                     $i = substr($i, 0, -13);
0500                     $v['deprecated'] = true;
0501                 } else {
0502                     $v['deprecated'] = false;
0503                 }
0504                 $v['desc'] = (string)$var->refpurpose;
0505                 $variables[$i] = $v;
0506             }
0507         }
0508     }
0509     if ($vars = $xml->xpath("//phpdoc:varentry[@xml:id='language.variables.superglobals']//db:member/db:varname")) {
0510         foreach ($vars as $var) {
0511             $variables[(string)$var]['superglobal'] = true;
0512         }
0513     }
0514     if (isset($xml->variablelist)) {
0515         foreach ($xml->variablelist as $list) {
0516             foreach ($list->varlistentry as $i=>$varlistentry) {
0517                 if ($c = (string)$varlistentry->term->constant) {
0518                     if (!isset($constants[$c])) {
0519                         if (strpos($c, '=')) {
0520                             $c = substr($c, 0, strpos($c, '='));
0521                         }
0522                         $ctype = $varlistentry->term->type;
0523                         if (!$ctype) {
0524                             $ctype = $varlistentry->term->link;
0525                         }
0526                         $constants[$c] = (string)$ctype;
0527                     }
0528                 }
0529             }
0530         }
0531     }
0532     // handle class constants (and their global aliases) not defined as fieldsynopsis
0533     $consts = $xml->xpath("db:partintro//db:varlistentry");
0534     foreach ($consts as $c) {
0535         foreach ($c->term as $t) {
0536             if ($name = (string)$t->constant) {
0537                 if (!isset($constants[$name])) {
0538                     if (strpos($name, '=')) {
0539                         $c = substr($name, 0, strpos($name, '='));
0540                     }
0541                     $constants[$name] = 'mixed';
0542                     $constants_comments[$name] = cleanupComment($c->listitem->para->asXML());
0543                 }
0544             }
0545         }
0546     }
0547     // handle constants under section
0548     if ($file->getFilename() == 'constants.xml') {
0549         $consts = $xml->xpath("db:section//db:varlistentry");
0550         foreach ($consts as $c) {
0551             if ($name = (string)$c->term->constant) {
0552                 if (!isset($constants[$name])) {
0553                     if (strpos($name, '=')) {
0554                         $c = substr($name, 0, strpos($name, '='));
0555                     }
0556                     $ctype = $c->term->type;
0557                     if (!$ctype) {
0558                         $ctype = $c->term->link;
0559                     }
0560                     $constants[$name] = (string)$ctype;
0561                 }
0562             }
0563         }
0564     }
0565     // handle constants within tables
0566     if ($file->getFilename() == 'constants.xml') {
0567         $consts = $xml->xpath("db:section//db:row");
0568         foreach ($consts as $c) {
0569             if (!$c->entry && !$c->entry[0] && !$c->entry[0]->constant) {
0570                 continue;
0571             }
0572             $name = (string)$c->entry[0]->constant;
0573             switch ($name) {
0574                 case '':
0575                     continue 2;
0576                 default:
0577                     if ($c->entry[2]) {
0578                         $constants[$name] = $c->term->type;
0579                         $constants_comments[$name] = cleanupComment($c->entry[2]->asXML());
0580                     } else if ($c->entry[1]) {
0581                         $constants[$name] = 'mixed';
0582                         $constants_comments[$name] = cleanupComment($c->entry[1]->asXML());
0583                     }
0584             }
0585         }
0586     }
0587     // handle constants within para
0588     if ($file->getFilename() == 'constants.xml') {
0589         $consts = $xml->xpath("db:para//db:varlistentry");
0590         foreach ($consts as $c) {
0591             if ($name = (string)$c->term->constant) {
0592                 if ($name == '__default') {
0593                     continue;
0594                 }
0595                 if (!isset($constants[$name])) {
0596                     if (strpos($name, '=')) {
0597                         $c = substr($name, 0, strpos($name, '='));
0598                     }
0599                     $ctype = $c->term->type;
0600                     if (!$ctype) {
0601                         $ctype = $c->term->link;
0602                     }
0603                     $constants[$name] = (string)$ctype;
0604                 }
0605             }
0606         }
0607     }
0608     if ($file->getFilename() == 'constants.xml') {
0609         $consts = $xml->xpath("db:para//db:simpara");
0610         foreach ($consts as $c) {
0611             if ($name = (string)$c->constant) {
0612                 if ($name[0] == '"') {
0613                     continue;
0614                 }
0615                 if (!isset($constants[$name])) {
0616                     $constants[$name] = 'mixed';
0617                 }
0618             }
0619         }
0620     }
0621     if ($file->getFilename() == 'ciphers.xml') {
0622         $consts = $xml->xpath("db:para//db:simpara");
0623         foreach ($consts as $c) {
0624             if ($name = (string)$c) {
0625                 $name = explode(' ', $name)[0];
0626                 $name = explode('(', $name)[0];
0627                 if (!isset($constants[$name])) {
0628                     $constants[$name] = 'mixed';
0629                 }
0630             }
0631         }
0632     }
0633     // handle constants in tables within refsect1
0634     if ($file->getFilename() != 'attr.xml') {
0635         $consts = $xml->xpath("db:refsect1//db:row");
0636         foreach ($consts as $c) {
0637             if (!$c->entry && !$c->entry[0] && !$c->entry[0]->constant) {
0638                 continue;
0639             }
0640             $name = (string)$c->entry[0]->constant;
0641             switch ($name) {
0642                 case '':
0643                 case '0':
0644                     continue 2;
0645                 case 'ABDAY_(1-7)':
0646                 case 'DAY_(1-7)':
0647                     for ($i=1; $i<8;$i++) {
0648                         $cname = substr($name, 0, strpos($name, '_') + 1) . $i;
0649                         $constants[$cname] = 'mixed';
0650                         if ($c->entry[1]) {
0651                             $constants_comments[$cname] = cleanupComment($c->entry[1]->asXML());
0652                         }
0653                     }
0654                     break;
0655                 case 'ABMON_(1-12)':
0656                 case 'MON_(1-12)':
0657                     for ($i=1; $i<13;$i++) {
0658                         $cname = substr($name, 0, strpos($name, '_') + 1) . $i;
0659                         $constants[$cname] = 'mixed';
0660                         if ($c->entry[1]) {
0661                             $constants_comments[$cname] = cleanupComment($c->entry[1]->asXML());
0662                         }
0663                     }
0664                     break;
0665                 default:
0666                     $constants[$name] = 'mixed';
0667                     if ($c->entry[1]) {
0668                         $constants_comments[$name] = cleanupComment($c->entry[1]->asXML());
0669                     }
0670             }
0671         }
0672     }
0673     // handle constants within file-upload.xml
0674     if ($file->getFilename() == 'file-upload.xml') {
0675         $consts = $xml->xpath("db:sect1//db:varlistentry");
0676         foreach ($consts as $c) {
0677             if ($name = (string)$c->term->constant) {
0678                 if (!isset($constants[$name])) {
0679                     if (strpos($name, '=')) {
0680                         $c = substr($name, 0, strpos($name, '='));
0681                     }
0682                     $ctype = $c->term->type;
0683                     if (!$ctype) {
0684                         $ctype = $c->term->link;
0685                     }
0686                     $constants[$name] = (string)$ctype;
0687                     $constants_comments[$name] = cleanupComment($c->listitem->para->asXML());
0688                 }
0689             }
0690         }
0691     }
0692     // handle constants within tokens.xml
0693     if ($file->getFilename() == 'tokens.xml') {
0694         $consts = $xml->xpath("db:table//db:row");
0695         foreach ($consts as $c) {
0696             if (!$c->entry && !$c->entry[0] && !$c->entry[0]->constant) {
0697                 continue;
0698             }
0699             $name = (string)$c->entry[0]->constant;
0700             switch ($name) {
0701                 case '':
0702                     continue 2;
0703                 default:
0704                     $constants[$name] = 'mixed';
0705             }
0706         }
0707     }
0708     // handle url-stat constants
0709     if ($file->getFilename() == 'url-stat.xml') {
0710         $consts = $xml->xpath("db:refsect1//db:informaltable//db:row");
0711         foreach ($consts as $c) {
0712             if (!$c->entry && !$c->entry[0]) {
0713                 continue;
0714             }
0715             $name = (string)$c->entry[0];
0716             switch ($name) {
0717                 case '':
0718                 case 'Flag':
0719                     continue 2;
0720                 default:
0721                     $constants[$name] = 'mixed';
0722                     $constants_comments[$name] = cleanupComment($c->entry[1]->asXML());
0723             }
0724         }
0725     }
0726     // handle constants.xml with different layout as those above
0727     if ( !isset($xml->variablelist) && $file->getFilename() == 'constants.xml' && $xml->xpath("//db:constant") ) {
0728         $consts = $xml->xpath("//db:entry");
0729         foreach ( $consts as $i=>$p ) {
0730             if ( isset($p->constant) ) {
0731                 if ( !isset($p->type) ) {
0732                     // default to integer constants
0733                     $p->type = 'integer';
0734                 } else {
0735                     // check for comment
0736                     // next entry is the value of the constant which is followed by the comment
0737                     if ( isset($consts[$i+2]) && !$consts[$i+2]->children() ) {
0738                         $comment = $consts[$i+2]->asXml();
0739                         if ( !empty($comment) ) {
0740                             $constants_comments[(string)$p->constant] = $comment;
0741                         }
0742                     }
0743                 }
0744                 $name = (string)$p->constant;
0745                 if ($name == 'LOG_LOCAL0 ... LOG_LOCAL7') {
0746                     for ($i=0; $i<8; $i++) {
0747                     $constants['LOG_LOCAL' . $i] = (string)$p->type;
0748                     }
0749                 } else {
0750                     $constants[$name] = (string)$p->type;
0751                 }
0752             }
0753         }
0754     } else if (!isset($xml->variablelist) && $file->getFilename() == 'commandline.xml') {
0755         // yay for non-unified xml structures :-X
0756         $consts = $xml->xpath("//db:row");
0757         foreach ( $consts as $i=>$p ) {
0758             $constant = "";
0759                     // default to integer constants
0760             $type = "integer";
0761             if ( isset($p->entry[0]) && isset($p->entry[0]->constant) ) {
0762                 $constant = trim((string) $p->entry[0]->constant);
0763                 if ( isset($p->entry[0]->constant->type) ) {
0764                     $type = (string)$p->entry[0]->constant->type;
0765                 }
0766             }
0767             if (empty($constant)) {
0768                 continue;
0769             }
0770             // check for comment
0771             // next entry is the comment
0772             if ( isset($p->entry[1]) ) {
0773                 $comment = $p->entry[1]->para->asXml();
0774                 if ( !empty($comment) ) {
0775                     $constants_comments[$constant] = $comment;
0776                 }
0777             }
0778             $constants[$constant] = $type;
0779         }
0780     }
0781     if ($list = $xml->xpath('//db:sect2[starts-with(@xml:id, "reserved.classes")]/db:variablelist/db:varlistentry')) {
0782         foreach ($list as $l) {
0783             $classname = newClassEntry((string)$l->term->classname);
0784 
0785             $classes[$classname]['desc'] = cleanupComment(removeTag($l->listitem->asXML(), 'listitem'));
0786         }
0787     }
0788 
0789     $cEls = $xml->xpath('//db:classsynopsis/db:classsynopsisinfo');
0790     if ($cEls) {
0791         foreach ($cEls as $class) {
0792             $class->registerXPathNamespace('db', 'http://docbook.org/ns/docbook');
0793             $className = (string)$class->ooclass->classname;
0794             if ((string)$class->ooclass->modifier === 'abstract') {
0795                 $isAbstractClass = true;
0796             }
0797             if ((string)$class->ooclass->modifier === 'final') {
0798                 $isFinalClass = true;
0799             }
0800             if (!$className) continue;
0801             $className = newClassEntry($className);
0802             if ($interfaces = $class->xpath('//db:oointerface/db:interfacename')) {
0803                 $key = $isInterface ? 'extends' : 'implements';
0804                 foreach ($interfaces as $if) {
0805                     $classes[$className][$key][] = (string)$if;
0806                 }
0807             }
0808             if ($extends = $class->xpath('//db:ooclass')) {
0809                 foreach ($extends as $c) {
0810                     if ($c->modifier == 'extends') {
0811                         if (array_key_exists('extends', $classes[$className]) && is_array($classes[$className]['extends'])) {
0812                             array_unshift($classes[$className]['extends'], (string)$c->classname);
0813                         } else {
0814                             $classes[$className]['extends'] = (string)$c->classname;
0815                         }
0816                     } elseif ($c->modifier == 'implements') {
0817                         if (array_key_exists('implements', $classes[$className]) && is_array($classes[$className]['implements'])) {
0818                             array_unshift($classes[$className]['implements'], (string)$c->classname);
0819                         } else {
0820                             $classes[$className]['implements'][] = (string)$c->classname;
0821                         }
0822                     }
0823                 }
0824             }
0825             if ($paras = $xml->xpath('//db:section[starts-with(@xml:id, "'.$className.'")]/db:para')) {
0826                 foreach ($paras as $p) {
0827                     $classes[$className]['desc'] .= "\n". cleanupComment(removeTag($p->asXML(), 'para'));
0828                 }
0829             } elseif ($paras = $xml->xpath('//db:section[starts-with(@xml:id, "'. str_replace('_', '-', $className) .'")]/db:para')) {
0830                 foreach ($paras as $p) {
0831                     $classes[$className]['desc'] .= "\n". cleanupComment(removeTag($p->asXML(), 'para'));
0832                 }
0833             }
0834         }
0835     }
0836 
0837     $addedSomething = false;
0838 
0839     if (isset($xml->partintro) && !isset($xml->refsect1)) {
0840         if (isset($xml->partintro->section[1]->classsynopsis->fieldsynopsis)) {
0841             $synopsis = $xml->partintro->section[1]->classsynopsis->fieldsynopsis;
0842             $class    = $xml->partintro->section[1]->classsynopsis->ooclass->classname;
0843 
0844             $classname = newClassEntry($class);
0845 
0846             foreach ($synopsis as $property){
0847                 $name  = $property->varname;
0848 
0849                 $modifiers = (array) $property->modifier;
0850                 $type = isset($property->type) ? $property->type : 'mixed';
0851                 $desc = '';
0852 
0853                 if (in_array('const', $modifiers)) {
0854                     $constantname = strtolower(substr((string) $name, strrpos((string) $name, '::') + 2));
0855                     if ($constant_descs = $xml->xpath('//db:section[@xml:id="' . $classname .'.constants"]//db:varlistentry[@xml:id="'. $classname .'.constants.'. $constantname .'"]') ) {
0856                         foreach ($constant_descs as $constant_desc) {
0857                             $desc .= getDocumentationFromPartIntro($constant_desc);
0858                         }
0859                     }
0860                     $constants_comments[(string) $name] = $desc;
0861                     $constants[(string) $name] = $type;
0862                 } else {
0863                     if ($property_descs = $xml->xpath('//db:section[@xml:id="' . $classname .'.props"]//db:varlistentry[@xml:id="'. $classname .'.props.'. $name .'"]') ) {
0864                         foreach ($property_descs as $property_desc) {
0865                             $desc .= getDocumentationFromPartIntro($property_desc);
0866                         }
0867                     }
0868                     newPropertyEntry($class, $name, $desc, $type, $modifiers);
0869                 }
0870             }
0871 
0872             $addedSomething = true;
0873         }
0874     }
0875 
0876     if (!isset($xml->refsect1)) return $addedSomething;
0877 
0878     $desc = getDocumentation($xml);
0879 
0880     // file could contain function + property
0881     if (isset($xml->refsect1->classsynopsis) && isset($xml->refsect1->classsynopsis->fieldsynopsis)) {
0882         $class = (string)$xml->refsect1->classsynopsis->ooclass->classname;
0883 
0884         foreach ( $xml->refsect1->classsynopsis->fieldsynopsis as $synopsis ) {
0885             newPropertyEntry($class, $synopsis->varname, $desc, $synopsis->type );
0886             $addedSomething = true;
0887         }
0888     }
0889     if (isset($xml->refsect1->fieldsynopsis)) {
0890         $synopsis = $xml->refsect1->fieldsynopsis;
0891         $class = substr($synopsis->varname, 0, strpos($synopsis->varname, '->'));
0892         $name  = substr($synopsis->varname, strpos($synopsis->varname, '->') + 2);
0893 
0894         newPropertyEntry($class, $name, $desc, $synopsis->type);
0895         $addedSomething = true;
0896     }
0897 
0898     if (isset($xml->refsect1->methodsynopsis)) {
0899         $skip = false;
0900         if ($funcOverload != "") {
0901             foreach( $xml->refsect1->methodsynopsis as $synopsis ) {
0902                 if (isset($synopsis->methodname->replaceable)) {
0903                     if ($funcOverload == (string) $synopsis->methodname->replaceable) {
0904                         $skip = true;
0905                     }
0906                 } else {
0907                     if ($funcOverload == (string) $synopsis->methodname) {
0908                         $skip = true;
0909                     }
0910                 }
0911             }
0912         }
0913         if (!$skip) {
0914             foreach( $xml->refsect1->methodsynopsis as $synopsis ) {
0915                 if (isset($synopsis->methodname->replaceable)) {
0916                     newMethodEntry('global', $synopsis->methodname->replaceable, $funcOverload, $synopsis, $desc, $xml, $classOverload);
0917                 } else {
0918                     newMethodEntry('global', $synopsis->methodname, $funcOverload, $synopsis, $desc, $xml, $classOverload);
0919                 }
0920 
0921                 $addedSomething = true;
0922             }
0923         }
0924     }
0925     if (isset($xml->refsect1->classsynopsis) && isset($xml->refsect1->classsynopsis->methodsynopsis)) {
0926         $methodsynopsis = $xml->refsect1->classsynopsis->methodsynopsis;
0927         $classOverloadName = $classOverload ?? $xml->refsect1->classsynopsis->ooclass->classname;
0928         if (isset($synopsis->methodname->replaceable)) {
0929             newMethodEntry($classOverloadName, $methodsynopsis->methodname->replaceable, $funcOverload, $methodsynopsis, $desc, $xml, $classOverload);
0930         } else {
0931             newMethodEntry($classOverloadName, $methodsynopsis->methodname, $funcOverload, $methodsynopsis, $desc, $xml, $classOverload);
0932         }
0933         $addedSomething = true;
0934     }
0935     if ( !$addedSomething && (isset($xml->refnamediv->refpurpose->function) || isset($xml->refnamediv->refpurpose->methodname)) ) {
0936         // This is function alias
0937         $functionNames[] = (string)$xml->refnamediv->refname;
0938         if ($xml->refnamediv->refpurpose->function) {
0939             $aliasName    = (string)$xml->refnamediv->refpurpose->function;
0940             switch ($aliasName) {
0941                 case 'mysqli_stmt_execute':
0942                     $baseFileName = dirname($file->getPathname()).'/../mysqli_stmt/execute.xml';
0943                     $overload = "global";
0944                     break;
0945                 case 'mysqli_options':
0946                     $functionNames[] = 'mysqli_set_opt';
0947                     $baseFileName = dirname($file->getPathname()).'/../mysqli/options.xml';
0948                     $overload = "global";
0949                     break;
0950                 case 'mysqli_real_escape_string':
0951                     $baseFileName = dirname($file->getPathname()).'/../mysqli/real-escape-string.xml';
0952                     $overload = "global";
0953                     break;
0954                 case 'sql_regcase':
0955                     $baseFileName = dirname($file->getPathname()).'/../../regex/functions/sql-regcase.xml';
0956                     $overload = "global";
0957                     break;
0958                 default:
0959                     $baseFileName = dirname($file->getPathname()).'/'.str_replace('_', '-', $aliasName).'.xml';
0960                     $overload = "";
0961             }
0962         } else {
0963             $aliasName    = (string)$xml->refnamediv->refpurpose->methodname;
0964             $baseFileName = dirname($file->getPathname()).'/'.strtolower(str_replace('::', '/', $aliasName)).'.xml';
0965             $baseFileName = str_replace('/functions/', '/', $baseFileName);
0966             $overload = "global";
0967         }
0968         if ( $baseFileName == $file->getPathname() || !file_exists($baseFileName) ) {
0969             return false;
0970         }
0971         foreach ($functionNames as $functionName) {
0972             parseFile(new SplFileInfo($baseFileName), $functionName, $overload);
0973         }
0974         $addedSomething = true;
0975     }
0976 
0977     return $addedSomething;
0978 } // end of function parseFile()
0979 
0980 /**
0981  * Create a new class entry if it not exists.
0982  *
0983  * Key in $classes will be the lower-case @p $name.
0984  * The prettyName member will be @p $name, if it contains non-lowercase chars.
0985  *
0986  * Returns the lower-cased @p $name
0987  */
0988 function newClassEntry($name) {
0989     global $classes, $isInterface, $isAbstractClass, $isFinalClass, $interfaceClasses, $abstractClasses;
0990     if (strpos($name, '\\') !== false) {
0991       $endpos = strrpos($name, '\\');
0992       $class = substr($name, $endpos + 1);
0993       $namespace = substr($name, 0, $endpos);
0994     } else {
0995       $class = $name;
0996       $namespace = null;
0997     }
0998     // This affects OCI-Collection and OCI-Log
0999     // Technically, this creates wrong class names, but they are otherwise illegal syntax...
1000     $class = str_replace('-','',$class);
1001     $lower = strtolower($name);
1002 
1003     if (!$isInterface && in_array($lower, $interfaceClasses)) {
1004         $isInterface = true;
1005     }
1006 
1007     if (!$isAbstractClass && in_array($lower, $abstractClasses)) {
1008         $isAbstractClass = true;
1009     }
1010 
1011     if (!isset($classes[$lower])) {
1012         $classes[$lower] = array(
1013             'functions' => array(),
1014             'properties' => array(),
1015             'namespace' => $namespace,
1016             'prettyName' => $class,
1017             'desc' => '',
1018             'isInterface' => $isInterface,
1019             'isAbstract' => $isAbstractClass,
1020             'isFinal' => $isFinalClass,
1021         );
1022     } else {
1023         if ( $lower != $class ) {
1024             $classes[$lower]['prettyName'] = $class;
1025         }
1026         if ( $isInterface ) {
1027             $classes[$lower]['isInterface'] = true;
1028         }
1029         if ( $isAbstractClass ) {
1030             $classes[$lower]['isAbstract'] = true;
1031         }
1032         if ( $isFinalClass ) {
1033             $classes[$lower]['isFinal'] = true;
1034         }
1035     }
1036     return $lower;
1037 }
1038 
1039 /**
1040  * get the documentation for an entry
1041  * @return string
1042  */
1043 function getDocumentation(SimpleXMLElement $xml) {
1044     global $skipComments;
1045 
1046     $descs = array();
1047 
1048     $purpose = $xml->refnamediv->refpurpose;
1049 
1050     if (!in_array($purpose, $skipComments)) {
1051         $descs[] = $purpose;
1052     }
1053 
1054     foreach ($xml->refsect1->para as $p ) {
1055         $p = removeTag($p->asXML(), 'para');
1056         if ( stripos($p, 'procedural style') !== false || stripos($p, 'procedure style') !== false
1057             || stripos($p, 'object oriented style') !== false ) {
1058             // uninteresting
1059             continue;
1060         }
1061         if (in_array($p, $skipComments)) {
1062             continue;
1063         }
1064         if ($p == $purpose || $p == "$purpose.") {
1065             // avoid duplicate comments
1066             continue;
1067         }
1068         $descs[] = $p;
1069     }
1070     return implode("\n\n", $descs);
1071 }
1072 
1073 /**
1074  * get the documentation for an entry
1075  * @return string
1076  */
1077 function getDocumentationFromPartIntro(SimpleXMLElement $xml) {
1078     global $skipComments;
1079 
1080     $descs = array();
1081 
1082     foreach ($xml->listitem->para as $p ) {
1083         $p = removeTag($p->asXML(), 'para');
1084         if (in_array($p, $skipComments)) {
1085             continue;
1086         }
1087         $descs[] = $p;
1088     }
1089     return implode("\n\n", $descs);
1090 }
1091 
1092 /**
1093  * create a new property entry for @p $class
1094  */
1095 function newPropertyEntry($class, $name, $desc, $type, $modifiers = []) {
1096     global $classes;
1097     $class = newClassEntry($class);
1098     $classes[$class]['properties'][] = array(
1099         'name' => (string) $name,
1100         'desc' => (string) $desc,
1101         'type' => (string) $type,
1102         'modifiers' => $modifiers
1103     );
1104 }
1105 
1106 /**
1107  * create a new method entry for @p $class
1108  */
1109 function newMethodEntry($class, $function, $funcOverload, $methodsynopsis, $desc, SimpleXMLElement $xml, $classOverload = "") {
1110     global $existingFunctions, $classes;
1111     $class = (string) $class;
1112     $function = (string) $function;
1113     $funcOverload = (string) $funcOverload;
1114 
1115     if (strpos($function, '::')) {
1116         if ($classOverload == '') {
1117             $class = substr($function, 0, strpos($function, '::'));
1118         }
1119         $function = substr($function, strpos($function, '::')+2);
1120         if (strpos($funcOverload, '::')) {
1121             $class = substr($funcOverload, 0, strpos($funcOverload, '::'));
1122             $funcOverload = substr($funcOverload, strpos($funcOverload, '::')+2);
1123         }
1124     } else if (strpos($funcOverload, '::')) {
1125         $class = substr($funcOverload, 0, strpos($funcOverload, '::'));
1126         $funcOverload = substr($funcOverload, strpos($funcOverload, '::')+2);
1127     } else if (strpos($function, '->')) {
1128         if ($classOverload == '') {
1129             $class = substr($function, 0, strpos($function, '->'));
1130         }
1131         $function = substr($function, strpos($function, '->')+2);
1132     } else {
1133         if ($function == '__halt_compiler') return false;
1134         if ($function == 'exit') return false;
1135         if ($function == 'die') return false;
1136         if ($function == 'eval') return false;
1137         if ($function == 'echo') return false;
1138         if ($function == 'print') return false;
1139         if ($function == 'array') return false;
1140         if ($function == 'list') return false;
1141         if ($function == 'isset') return false;
1142         if ($function == 'unset') return false;
1143         if ($function == 'empty') return false;
1144     }
1145 
1146     if (strpos($function, '-')) return false;
1147     if (strpos($class, '-')) return false;
1148     if ($function == 'isSet') return false; //todo: bug in lexer
1149     if ($function == 'clone') return false; //todo: bug in lexer
1150     if (substr($class, 0, 3) == 'DOM') $class = 'Dom'.substr($class, 3);
1151     $class = trim($class);
1152     if ($class == 'imagick') $class = 'Imagick';
1153     if (in_array($class.'::'.($funcOverload ? $funcOverload : $function), $existingFunctions)) return false;
1154     $existingFunctions[] = $class.'::'.($funcOverload ? $funcOverload : $function);
1155 
1156     $params = array();
1157     foreach ($methodsynopsis->methodparam as $param) {
1158         $paramName = $param->parameter;
1159         if (trim($paramName) == '...') {
1160             // Add a variable name for functions taking variable arguments
1161             $paramName = '...$vararg';
1162         }
1163         if (!trim($paramName)) continue;
1164         $paramName = str_replace('/', '', $paramName);
1165         $paramName = str_replace('-', '', $paramName);
1166         $paramName = str_replace('$', '', $paramName);
1167         $paramName = trim(trim(trim($paramName), '*'), '&');
1168         if ($pipe_pos = strpos($paramName, '|')) $paramName = substr($paramName, 0, $pipe_pos);
1169         if (is_numeric(substr($paramName, 0, 1))) $paramName = '_'.$paramName;
1170         $params[] = array(
1171             'name' => $paramName,
1172             'type' => (string)$param->type,
1173             'isRef' => isset($param->parameter->attributes()->role) ? ($param->parameter->attributes()->role == "reference") : false,
1174         );
1175     }
1176     // get description of params
1177     if ( $param_descs = $xml->xpath('db:refsect1[@role="parameters"]//db:varlistentry') ) {
1178         $i = 0;
1179         foreach ( $param_descs as $d ) {
1180             if ( !isset($params[$i]) ) {
1181                 continue;
1182             }
1183             $paramName = (string) $d->term->parameter;
1184             $params[$i]['desc'] = '';
1185             foreach ( $d->listitem->para as $p ) {
1186                 $p = removeTag($p->asXML(), 'para');
1187                 $params[$i]['desc'] .= $p . "\n";
1188             }
1189             ++$i;
1190         }
1191     }
1192 
1193     if ($return_desc = $xml->xpath('db:refsect1[@role="returnvalues"]//db:para')) {
1194         $return_desc = removeTag($return_desc[0]->asXML(), 'para');
1195     } else {
1196         $return_desc = '';
1197     }
1198 
1199     $class = newClassEntry($class);
1200     $classes[$class]['functions'][] = array(
1201         'name'        => $funcOverload ? $funcOverload : $function,
1202         'modifiers'   => (array) $methodsynopsis->modifier,
1203         'params'      => $params,
1204         'type'        => (string)$methodsynopsis->type,
1205         'desc'        => $funcOverload ? str_replace($function, $funcOverload, $desc) : $desc,
1206         'return_desc' => $return_desc,
1207     );
1208 }
1209 
1210 /* don't add a closing ?> here, we use this file in a benchmark as well */