This commit is contained in:
Laurent Destailleur 2022-06-14 16:21:12 +02:00
commit 866fc4d83e
3 changed files with 485 additions and 479 deletions

View File

@ -275,6 +275,13 @@ RESTLER:
with with
$loaders = array_unique(static::$rogueLoaders, SORT_REGULAR); $loaders = array_unique(static::$rogueLoaders, SORT_REGULAR);
* Replace CommentParser.php line 423
elseif (count($value) && is_numeric($value[0]))
with
elseif (count($value) && isset($value[0]) && is_numeric($value[0]))
+With swagger 2 provided into /explorer: +With swagger 2 provided into /explorer:

View File

@ -124,7 +124,7 @@ $usercancreateorder = $user->rights->commande->creer;
$usercancreateinvoice = $user->rights->facture->creer; $usercancreateinvoice = $user->rights->facture->creer;
$usercancreatecontract = $user->rights->contrat->creer; $usercancreatecontract = $user->rights->contrat->creer;
$usercancreateintervention = $user->hasRight('ficheinter', 'creer'); $usercancreateintervention = $user->hasRight('ficheinter', 'creer');
$usercancreatepurchaseorder = ($user->rights->fournisseur->commande->creer || $user->rights->supplier_order->creer); $usercancreatepurchaseorder = ($user->hasRight('fournisseur', 'commande', 'creer') || $user->hasRight('supplier_order', 'creer'));
$permissionnote = $usercancreate; // Used by the include of actions_setnotes.inc.php $permissionnote = $usercancreate; // Used by the include of actions_setnotes.inc.php
$permissiondellink = $usercancreate; // Used by the include of actions_dellink.inc.php $permissiondellink = $usercancreate; // Used by the include of actions_dellink.inc.php

View File

@ -19,505 +19,504 @@ use Luracast\Restler\Data\Text;
*/ */
class CommentParser class CommentParser
{ {
/** /**
* name for the embedded data * name for the embedded data
* *
* @var string * @var string
*/ */
public static $embeddedDataName = 'properties'; public static $embeddedDataName = 'properties';
/** /**
* Regular Expression pattern for finding the embedded data and extract * Regular Expression pattern for finding the embedded data and extract
* the inner information. It is used with preg_match. * the inner information. It is used with preg_match.
* *
* @var string * @var string
*/ */
public static $embeddedDataPattern public static $embeddedDataPattern
= '/```(\w*)[\s]*(([^`]*`{0,2}[^`]+)*)```/ms'; = '/```(\w*)[\s]*(([^`]*`{0,2}[^`]+)*)```/ms';
/** /**
* Pattern will have groups for the inner details of embedded data * Pattern will have groups for the inner details of embedded data
* this index is used to locate the data portion. * this index is used to locate the data portion.
* *
* @var int * @var int
*/ */
public static $embeddedDataIndex = 2; public static $embeddedDataIndex = 2;
/** /**
* Delimiter used to split the array data. * Delimiter used to split the array data.
* *
* When the name portion is of the embedded data is blank auto detection * When the name portion is of the embedded data is blank auto detection
* will be used and if URLEncodedFormat is detected as the data format * will be used and if URLEncodedFormat is detected as the data format
* the character specified will be used as the delimiter to find split * the character specified will be used as the delimiter to find split
* array data. * array data.
* *
* @var string * @var string
*/ */
public static $arrayDelimiter = ','; public static $arrayDelimiter = ',';
/** /**
* @var array annotations that support array value * @var array annotations that support array value
*/ */
public static $allowsArrayValue = array( public static $allowsArrayValue = array(
'choice' => true, 'choice' => true,
'select' => true, 'select' => true,
'properties' => true, 'properties' => true,
); );
/** /**
* character sequence used to escape \@ * character sequence used to escape \@
*/ */
const escapedAtChar = '\\@'; const escapedAtChar = '\\@';
/** /**
* character sequence used to escape end of comment * character sequence used to escape end of comment
*/ */
const escapedCommendEnd = '{@*}'; const escapedCommendEnd = '{@*}';
/** /**
* Instance of Restler class injected at runtime. * Instance of Restler class injected at runtime.
* *
* @var Restler * @var Restler
*/ */
public $restler; public $restler;
/** /**
* Comment information is parsed and stored in to this array. * Comment information is parsed and stored in to this array.
* *
* @var array * @var array
*/ */
private $_data = array(); private $_data = array();
/** /**
* Parse the comment and extract the data. * Parse the comment and extract the data.
* *
* @static * @static
* *
* @param $comment * @param $comment
* @param bool $isPhpDoc * @param bool $isPhpDoc
* *
* @return array associative array with the extracted values * @return array associative array with the extracted values
*/ */
public static function parse($comment, $isPhpDoc = true) public static function parse($comment, $isPhpDoc = true)
{ {
$p = new self(); $p = new self();
if (empty($comment)) { if (empty($comment)) {
return $p->_data; return $p->_data;
} }
if ($isPhpDoc) { if ($isPhpDoc) {
$comment = self::removeCommentTags($comment); $comment = self::removeCommentTags($comment);
} }
$p->extractData($comment); $p->extractData($comment);
return $p->_data; return $p->_data;
}
} /**
* Removes the comment tags from each line of the comment.
*
* @static
*
* @param string $comment PhpDoc style comment
*
* @return string comments with out the tags
*/
public static function removeCommentTags($comment)
{
$pattern = '/(^\/\*\*)|(^\s*\**[ \/]?)|\s(?=@)|\s\*\//m';
return preg_replace($pattern, '', $comment);
}
/** /**
* Removes the comment tags from each line of the comment. * Extracts description and long description, uses other methods to get
* * parameters.
* @static *
* * @param $comment
* @param string $comment PhpDoc style comment *
* * @return array
* @return string comments with out the tags */
*/ private function extractData($comment)
public static function removeCommentTags($comment) {
{ //to use @ as part of comment we need to
$pattern = '/(^\/\*\*)|(^\s*\**[ \/]?)|\s(?=@)|\s\*\//m'; $comment = str_replace(
return preg_replace($pattern, '', $comment); array(self::escapedCommendEnd, self::escapedAtChar),
} array('*/', '@'),
$comment);
/** $description = array();
* Extracts description and long description, uses other methods to get $longDescription = array();
* parameters. $params = array();
*
* @param $comment
*
* @return array
*/
private function extractData($comment)
{
//to use @ as part of comment we need to
$comment = str_replace(
array(self::escapedCommendEnd, self::escapedAtChar),
array('*/', '@'),
$comment);
$description = array(); $mode = 0; // extract short description;
$longDescription = array(); $comments = preg_split("/(\r?\n)/", $comment);
$params = array(); // remove first blank line;
array_shift($comments);
$addNewline = false;
foreach ($comments as $line) {
$line = trim($line);
$newParam = false;
if (empty($line)) {
if ($mode == 0) {
$mode++;
} else {
$addNewline = true;
}
continue;
} elseif ($line[0] == '@') {
$mode = 2;
$newParam = true;
}
switch ($mode) {
case 0 :
$description[] = $line;
if (count($description) > 3) {
// if more than 3 lines take only first line
$longDescription = $description;
$description[] = array_shift($longDescription);
$mode = 1;
} elseif (substr($line, -1) == '.') {
$mode = 1;
}
break;
case 1 :
if ($addNewline) {
$line = ' ' . $line;
}
$longDescription[] = $line;
break;
case 2 :
$newParam
? $params[] = $line
: $params[count($params) - 1] .= ' ' . $line;
}
$addNewline = false;
}
$description = implode(' ', $description);
$longDescription = implode(' ', $longDescription);
$description = preg_replace('/\s+/msu', ' ', $description);
$longDescription = preg_replace('/\s+/msu', ' ', $longDescription);
list($description, $d1)
= $this->parseEmbeddedData($description);
list($longDescription, $d2)
= $this->parseEmbeddedData($longDescription);
$this->_data = compact('description', 'longDescription');
$d2 += $d1;
if (!empty($d2)) {
$this->_data[self::$embeddedDataName] = $d2;
}
foreach ($params as $key => $line) {
list(, $param, $value) = preg_split('/\@|\s/', $line, 3)
+ array('', '', '');
list($value, $embedded) = $this->parseEmbeddedData($value);
$value = array_filter(preg_split('/\s+/msu', $value), 'strlen');
$this->parseParam($param, $value, $embedded);
}
return $this->_data;
}
$mode = 0; // extract short description; /**
$comments = preg_split("/(\r?\n)/", $comment); * Parse parameters that begin with (at)
// remove first blank line; *
array_shift($comments); * @param $param
$addNewline = false; * @param array $value
foreach ($comments as $line) { * @param array $embedded
$line = trim($line); */
$newParam = false; private function parseParam($param, array $value, array $embedded)
if (empty ($line)) { {
if ($mode == 0) { $data = &$this->_data;
$mode++; $allowMultiple = false;
} else { switch ($param) {
$addNewline = true; case 'param' :
} case 'property' :
continue; case 'property-read' :
} elseif ($line[0] == '@') { case 'property-write' :
$mode = 2; $value = $this->formatParam($value);
$newParam = true; $allowMultiple = true;
} break;
switch ($mode) { case 'var' :
case 0 : $value = $this->formatVar($value);
$description[] = $line; break;
if (count($description) > 3) { case 'return' :
// if more than 3 lines take only first line $value = $this->formatReturn($value);
$longDescription = $description; break;
$description[] = array_shift($longDescription); case 'class' :
$mode = 1; $data = &$data[$param];
} elseif (substr($line, -1) == '.') { list ($param, $value) = $this->formatClass($value);
$mode = 1; break;
} case 'access' :
break; $value = reset($value);
case 1 : break;
if ($addNewline) { case 'expires' :
$line = ' ' . $line; case 'status' :
} $value = intval(reset($value));
$longDescription[] = $line; break;
break; case 'throws' :
case 2 : $value = $this->formatThrows($value);
$newParam $allowMultiple = true;
? $params[] = $line break;
: $params[count($params) - 1] .= ' ' . $line; case 'author':
} $value = $this->formatAuthor($value);
$addNewline = false; $allowMultiple = true;
} break;
$description = implode(' ', $description); case 'header' :
$longDescription = implode(' ', $longDescription); case 'link':
$description = preg_replace('/\s+/msu', ' ', $description); case 'example':
$longDescription = preg_replace('/\s+/msu', ' ', $longDescription); case 'todo':
list($description, $d1) $allowMultiple = true;
= $this->parseEmbeddedData($description); //don't break, continue with code for default:
list($longDescription, $d2) default :
= $this->parseEmbeddedData($longDescription); $value = implode(' ', $value);
$this->_data = compact('description', 'longDescription'); }
$d2 += $d1; if (!empty($embedded)) {
if (!empty($d2)) { if (is_string($value)) {
$this->_data[self::$embeddedDataName] = $d2; $value = array('description' => $value);
} }
foreach ($params as $key => $line) { $value[self::$embeddedDataName] = $embedded;
list(, $param, $value) = preg_split('/\@|\s/', $line, 3) }
+ array('', '', ''); if (empty($data[$param])) {
list($value, $embedded) = $this->parseEmbeddedData($value); if ($allowMultiple) {
$value = array_filter(preg_split('/\s+/msu', $value), 'strlen'); $data[$param] = array(
$this->parseParam($param, $value, $embedded); $value
} );
return $this->_data; } else {
} $data[$param] = $value;
}
} elseif ($allowMultiple) {
$data[$param][] = $value;
} elseif ($param == 'param') {
$arr = array(
$data[$param],
$value
);
$data[$param] = $arr;
} else {
if (!is_string($value) && isset($value[self::$embeddedDataName])
&& isset($data[$param][self::$embeddedDataName])
) {
$value[self::$embeddedDataName]
+= $data[$param][self::$embeddedDataName];
}
if (!is_array($data[$param])) {
$data[$param] = array('description' => (string) $data[$param]);
}
if (is_array($value)) {
$data[$param] = $value + $data[$param];
}
}
}
/** /**
* Parse parameters that begin with (at) * Parses the inline php doc comments and embedded data.
* *
* @param $param * @param $subject
* @param array $value *
* @param array $embedded * @return array
*/ * @throws Exception
private function parseParam($param, array $value, array $embedded) */
{ private function parseEmbeddedData($subject)
$data = &$this->_data; {
$allowMultiple = false; $data = array();
switch ($param) {
case 'param' :
case 'property' :
case 'property-read' :
case 'property-write' :
$value = $this->formatParam($value);
$allowMultiple = true;
break;
case 'var' :
$value = $this->formatVar($value);
break;
case 'return' :
$value = $this->formatReturn($value);
break;
case 'class' :
$data = &$data[$param];
list ($param, $value) = $this->formatClass($value);
break;
case 'access' :
$value = reset($value);
break;
case 'expires' :
case 'status' :
$value = intval(reset($value));
break;
case 'throws' :
$value = $this->formatThrows($value);
$allowMultiple = true;
break;
case 'author':
$value = $this->formatAuthor($value);
$allowMultiple = true;
break;
case 'header' :
case 'link':
case 'example':
case 'todo':
$allowMultiple = true;
//don't break, continue with code for default:
default :
$value = implode(' ', $value);
}
if (!empty($embedded)) {
if (is_string($value)) {
$value = array('description' => $value);
}
$value[self::$embeddedDataName] = $embedded;
}
if (empty ($data[$param])) {
if ($allowMultiple) {
$data[$param] = array(
$value
);
} else {
$data[$param] = $value;
}
} elseif ($allowMultiple) {
$data[$param][] = $value;
} elseif ($param == 'param') {
$arr = array(
$data[$param],
$value
);
$data[$param] = $arr;
} else {
if (!is_string($value) && isset($value[self::$embeddedDataName])
&& isset($data[$param][self::$embeddedDataName])
) {
$value[self::$embeddedDataName]
+= $data[$param][self::$embeddedDataName];
}
if (!is_array($data[$param])) {
$data[$param] = array('description' => (string)$data[$param]);
}
if (is_array($value)) {
$data[$param] = $value + $data[$param];
}
}
}
/** //parse {@pattern } tags specially
* Parses the inline php doc comments and embedded data. while (preg_match('|(?s-m)({@pattern (/.+/[imsxuADSUXJ]*)})|', $subject, $matches)) {
* $subject = str_replace($matches[0], '', $subject);
* @param $subject $data['pattern'] = $matches[2];
* }
* @return array while (preg_match('/{@(\w+)\s?([^}]*)}/ms', $subject, $matches)) {
* @throws Exception $name = $matches[1];
*/ $value = $matches[2];
private function parseEmbeddedData($subject) $subject = str_replace($matches[0], '', $subject);
{ if ($name == 'pattern') {
$data = array(); throw new Exception('Inline pattern tag should follow {@pattern /REGEX_PATTERN_HERE/} format and can optionally include PCRE modifiers following the ending `/`');
} elseif (isset(static::$allowsArrayValue[$name])) {
$value = explode(static::$arrayDelimiter, $value);
} elseif ($value == 'true' || $value == 'false') {
$value = $value == 'true';
} elseif ($value == '') {
$value = true;
} elseif ($name == 'required') {
$value = explode(static::$arrayDelimiter, $value);
}
if (defined('Luracast\\Restler\\UI\\HtmlForm::'.$name)) {
$value = constant($value);
}
$data[$name] = $value;
}
//parse {@pattern } tags specially while (preg_match(self::$embeddedDataPattern, $subject, $matches)) {
while (preg_match('|(?s-m)({@pattern (/.+/[imsxuADSUXJ]*)})|', $subject, $matches)) { $subject = str_replace($matches[0], '', $subject);
$subject = str_replace($matches[0], '', $subject); $str = $matches[self::$embeddedDataIndex];
$data['pattern'] = $matches[2]; if (isset($this->restler)
} && self::$embeddedDataIndex > 1
while (preg_match('/{@(\w+)\s?([^}]*)}/ms', $subject, $matches)) { && !empty($name)
$name = $matches[1]; ) {
$value = $matches[2]; $extension = $name;
$subject = str_replace($matches[0], '', $subject); $formatMap = $this->restler->getFormatMap();
if ($name == 'pattern') { if (isset($formatMap[$extension])) {
throw new Exception('Inline pattern tag should follow {@pattern /REGEX_PATTERN_HERE/} format and can optionally include PCRE modifiers following the ending `/`'); /**
} elseif (isset(static::$allowsArrayValue[$name])) { * @var \Luracast\Restler\Format\iFormat
$value = explode(static::$arrayDelimiter, $value); */
} elseif ($value == 'true' || $value == 'false') { $format = $formatMap[$extension];
$value = $value == 'true'; $format = new $format();
} elseif ($value == '') { $data = $format->decode($str);
$value = true; }
} elseif ($name == 'required') { } else { // auto detect
$value = explode(static::$arrayDelimiter, $value); if ($str[0] == '{') {
} $d = json_decode($str, true);
if (defined('Luracast\\Restler\\UI\\HtmlForm::'.$name)) { if (json_last_error() != JSON_ERROR_NONE) {
$value = constant($value); throw new Exception('Error parsing embedded JSON data'
} . " $str");
$data[$name] = $value; }
} $data = $d + $data;
} else {
parse_str($str, $d);
//clean up
$d = array_filter($d);
foreach ($d as $key => $val) {
$kt = trim($key);
if ($kt != $key) {
unset($d[$key]);
$key = $kt;
$d[$key] = $val;
}
if (is_string($val)) {
if ($val == 'true' || $val == 'false') {
$d[$key] = $val == 'true' ? true : false;
} else {
$val = explode(self::$arrayDelimiter, $val);
if (count($val) > 1) {
$d[$key] = $val;
} else {
$d[$key] =
preg_replace('/\s+/msu', ' ',
$d[$key]);
}
}
}
}
$data = $d + $data;
}
}
}
return array($subject, $data);
}
while (preg_match(self::$embeddedDataPattern, $subject, $matches)) { private function formatThrows(array $value)
$subject = str_replace($matches[0], '', $subject); {
$str = $matches[self::$embeddedDataIndex]; $code = 500;
if (isset ($this->restler) $exception = 'Exception';
&& self::$embeddedDataIndex > 1 if (count($value) > 1) {
&& !empty ($name) $v1 = $value[0];
) { $v2 = $value[1];
$extension = $name; if (is_numeric($v1)) {
$formatMap = $this->restler->getFormatMap(); $code = $v1;
if (isset ($formatMap[$extension])) { $exception = $v2;
/** array_shift($value);
* @var \Luracast\Restler\Format\iFormat array_shift($value);
*/ } elseif (is_numeric($v2)) {
$format = $formatMap[$extension]; $code = $v2;
$format = new $format(); $exception = $v1;
$data = $format->decode($str); array_shift($value);
} array_shift($value);
} else { // auto detect } else {
if ($str[0] == '{') { $exception = $v1;
$d = json_decode($str, true); array_shift($value);
if (json_last_error() != JSON_ERROR_NONE) { }
throw new Exception('Error parsing embedded JSON data' } elseif (count($value) && isset($value[0]) && is_numeric($value[0])) {
. " $str"); $code = $value[0];
} array_shift($value);
$data = $d + $data; }
} else { $message = implode(' ', $value);
parse_str($str, $d); if (!isset(RestException::$codes[$code])) {
//clean up $code = 500;
$d = array_filter($d); } elseif (empty($message)) {
foreach ($d as $key => $val) { $message = RestException::$codes[$code];
$kt = trim($key); }
if ($kt != $key) { return compact('code', 'message', 'exception');
unset($d[$key]); }
$key = $kt;
$d[$key] = $val;
}
if (is_string($val)) {
if ($val == 'true' || $val == 'false') {
$d[$key] = $val == 'true' ? true : false;
} else {
$val = explode(self::$arrayDelimiter, $val);
if (count($val) > 1) {
$d[$key] = $val;
} else {
$d[$key] =
preg_replace('/\s+/msu', ' ',
$d[$key]);
}
}
}
}
$data = $d + $data;
}
}
}
return array($subject, $data);
}
private function formatThrows(array $value) private function formatClass(array $value)
{ {
$code = 500; $param = array_shift($value);
$exception = 'Exception';
if (count($value) > 1) {
$v1 = $value[0];
$v2 = $value[1];
if (is_numeric($v1)) {
$code = $v1;
$exception = $v2;
array_shift($value);
array_shift($value);
} elseif (is_numeric($v2)) {
$code = $v2;
$exception = $v1;
array_shift($value);
array_shift($value);
} else {
$exception = $v1;
array_shift($value);
}
} elseif (count($value) && is_numeric($value[0])) {
$code = $value[0];
array_shift($value);
}
$message = implode(' ', $value);
if (!isset(RestException::$codes[$code])) {
$code = 500;
} elseif (empty($message)) {
$message = RestException::$codes[$code];
}
return compact('code', 'message', 'exception');
}
private function formatClass(array $value) if (empty($param)) {
{ $param = 'Unknown';
$param = array_shift($value); }
$value = implode(' ', $value);
return array(
ltrim($param, '\\'),
array('description' => $value)
);
}
if (empty($param)) { private function formatAuthor(array $value)
$param = 'Unknown'; {
} $r = array();
$value = implode(' ', $value); $email = end($value);
return array( if ($email[0] == '<') {
ltrim($param, '\\'), $email = substr($email, 1, -1);
array('description' => $value) array_pop($value);
); $r['email'] = $email;
} }
$r['name'] = implode(' ', $value);
return $r;
}
private function formatAuthor(array $value) private function formatReturn(array $value)
{ {
$r = array(); $data = explode('|', array_shift($value));
$email = end($value); $r = array(
if ($email[0] == '<') { 'type' => count($data) == 1 ? $data[0] : $data
$email = substr($email, 1, -1); );
array_pop($value); $r['description'] = implode(' ', $value);
$r['email'] = $email; return $r;
} }
$r['name'] = implode(' ', $value);
return $r;
}
private function formatReturn(array $value) private function formatParam(array $value)
{ {
$data = explode('|', array_shift($value)); $r = array();
$r = array( $data = array_shift($value);
'type' => count($data) == 1 ? $data[0] : $data if (empty($data)) {
); $r['type'] = 'mixed';
$r['description'] = implode(' ', $value); } elseif ($data[0] == '$') {
return $r; $r['name'] = substr($data, 1);
} $r['type'] = 'mixed';
} else {
$data = explode('|', $data);
$r['type'] = count($data) == 1 ? $data[0] : $data;
private function formatParam(array $value) $data = array_shift($value);
{ if (!empty($data) && $data[0] == '$') {
$r = array(); $r['name'] = substr($data, 1);
$data = array_shift($value); }
if (empty($data)) { }
$r['type'] = 'mixed'; if (isset($r['type']) && is_string($r['type']) && Text::endsWith($r['type'], '[]')) {
} elseif ($data[0] == '$') { $r[static::$embeddedDataName]['type'] = substr($r['type'], 0, -2);
$r['name'] = substr($data, 1); $r['type'] = 'array';
$r['type'] = 'mixed'; }
} else { if ($value) {
$data = explode('|', $data); $r['description'] = implode(' ', $value);
$r['type'] = count($data) == 1 ? $data[0] : $data; }
return $r;
}
$data = array_shift($value); private function formatVar(array $value)
if (!empty($data) && $data[0] == '$') { {
$r['name'] = substr($data, 1); $r = array();
} $data = array_shift($value);
} if (empty($data)) {
if (isset($r['type']) && is_string($r['type']) && Text::endsWith($r['type'], '[]')) { $r['type'] = 'mixed';
$r[static::$embeddedDataName]['type'] = substr($r['type'], 0, -2); } elseif ($data[0] == '$') {
$r['type'] = 'array'; $r['name'] = substr($data, 1);
} $r['type'] = 'mixed';
if ($value) { } else {
$r['description'] = implode(' ', $value); $data = explode('|', $data);
} $r['type'] = count($data) == 1 ? $data[0] : $data;
return $r; }
} if (isset($r['type']) && Text::endsWith($r['type'], '[]')) {
$r[static::$embeddedDataName]['type'] = substr($r['type'], 0, -2);
private function formatVar(array $value) $r['type'] = 'array';
{ }
$r = array(); if ($value) {
$data = array_shift($value); $r['description'] = implode(' ', $value);
if (empty($data)) { }
$r['type'] = 'mixed'; return $r;
} elseif ($data[0] == '$') { }
$r['name'] = substr($data, 1);
$r['type'] = 'mixed';
} else {
$data = explode('|', $data);
$r['type'] = count($data) == 1 ? $data[0] : $data;
}
if (isset($r['type']) && Text::endsWith($r['type'], '[]')) {
$r[static::$embeddedDataName]['type'] = substr($r['type'], 0, -2);
$r['type'] = 'array';
}
if ($value) {
$r['description'] = implode(' ', $value);
}
return $r;
}
} }