Beispiel #1
0
 public static function get($for = '', $activeTrail = null)
 {
     if (empty(static::$tree)) {
         /** @var Restler $restler */
         $restler = Scope::get('Restler');
         if (static::$addExtension) {
             static::$extension = isset($restler->responseFormat) ? '.' . $restler->responseFormat->getExtension() : '.html';
         }
         static::$url = $restler->getBaseUrl();
         if (empty(static::$url)) {
             static::$url = '';
         }
         static::$activeTrail = $activeTrail = empty($activeTrail) ? empty($restler->url) || $restler->url == 'index' ? static::$root : $restler->url : $activeTrail;
         if (static::$addExtension) {
             static::$extension = isset($restler->responseFormat) ? '.' . $restler->responseFormat->getExtension() : '.html';
         }
         static::addUrls(static::$prepends);
         $map = Routes::findAll(static::$excludedPaths, array('POST', 'DELETE', 'PUT', 'PATCH'), $restler->getRequestedApiVersion());
         foreach ($map as $path => $data) {
             foreach ($data as $item) {
                 $access = $item['access'];
                 $route = $item['route'];
                 $url = $route['url'];
                 if ($access && !Text::contains($url, '{')) {
                     $label = Util::nestedValue($route, 'metadata', CommentParser::$embeddedDataName, 'label');
                     if (!empty($url)) {
                         $url .= static::$extension;
                     }
                     static::add($url, $label);
                 }
             }
         }
         static::addUrls(static::$appends);
     } elseif (empty($activeTrail)) {
         $activeTrail = static::$activeTrail;
     }
     $tree = static::$tree;
     $activeTrail = explode('/', $activeTrail);
     $nested =& static::nested($tree, $activeTrail);
     if (is_array($nested)) {
         $nested['active'] = true;
     }
     if (!empty($for)) {
         $for = explode('/', $for);
         $tree = static::nested($tree, $for)['children'];
     }
     return array_filter($tree);
 }
 /**
  * Route the public and protected methods of an Api class
  *
  * @param string $className
  * @param string $resourcePath
  * @param int    $version
  *
  * @throws RestException
  */
 public static function addAPIClass($className, $resourcePath = '', $version = 1)
 {
     /*
      * Mapping Rules
      * =============
      *
      * - Optional parameters should not be mapped to URL
      * - If a required parameter is of primitive type
      *      - If one of the self::$prefixingParameterNames
      *              - Map it to URL
      *      - Else If request method is POST/PUT/PATCH
      *              - Map it to body
      *      - Else If request method is GET/DELETE
      *              - Map it to body
      * - If a required parameter is not primitive type
      *      - Do not include it in URL
      */
     $class = new ReflectionClass($className);
     $dataName = CommentParser::$embeddedDataName;
     try {
         $classMetadata = CommentParser::parse($class->getDocComment());
     } catch (Exception $e) {
         throw new RestException(500, "Error while parsing comments of `{$className}` class. " . $e->getMessage());
     }
     $classMetadata['scope'] = $scope = static::scope($class);
     $methods = $class->getMethods(ReflectionMethod::IS_PUBLIC + ReflectionMethod::IS_PROTECTED);
     foreach ($methods as $method) {
         $methodUrl = strtolower($method->getName());
         //method name should not begin with _
         if ($methodUrl[0] == '_') {
             continue;
         }
         $doc = $method->getDocComment();
         try {
             $metadata = CommentParser::parse($doc) + $classMetadata;
         } catch (Exception $e) {
             throw new RestException(500, "Error while parsing comments of `{$className}::{$method->getName()}` method. " . $e->getMessage());
         }
         //@access should not be private
         if (isset($metadata['access']) && $metadata['access'] == 'private') {
             continue;
         }
         $arguments = array();
         $defaults = array();
         $params = $method->getParameters();
         $position = 0;
         $pathParams = array();
         $allowAmbiguity = isset($metadata['smart-auto-routing']) && $metadata['smart-auto-routing'] != 'true' || !Defaults::$smartAutoRouting;
         $metadata['resourcePath'] = trim($resourcePath, '/');
         if (isset($classMetadata['description'])) {
             $metadata['classDescription'] = $classMetadata['description'];
         }
         if (isset($classMetadata['classLongDescription'])) {
             $metadata['classLongDescription'] = $classMetadata['longDescription'];
         }
         if (!isset($metadata['param'])) {
             $metadata['param'] = array();
         }
         if (isset($metadata['return']['type'])) {
             if ($qualified = Scope::resolve($metadata['return']['type'], $scope)) {
                 list($metadata['return']['type'], $metadata['return']['children']) = static::getTypeAndModel(new ReflectionClass($qualified), $scope);
             }
         } else {
             //assume return type is array
             $metadata['return']['type'] = 'array';
         }
         foreach ($params as $param) {
             $children = array();
             $type = $param->isArray() ? 'array' : $param->getClass();
             $arguments[$param->getName()] = $position;
             $defaults[$position] = $param->isDefaultValueAvailable() ? $param->getDefaultValue() : null;
             if (!isset($metadata['param'][$position])) {
                 $metadata['param'][$position] = array();
             }
             $m =& $metadata['param'][$position];
             $m['name'] = $param->getName();
             if (!isset($m[$dataName])) {
                 $m[$dataName] = array();
             }
             $p =& $m[$dataName];
             if (empty($m['label'])) {
                 $m['label'] = Text::title($m['name']);
             }
             if (is_null($type) && isset($m['type'])) {
                 $type = $m['type'];
             }
             if (isset(static::$fieldTypesByName[$m['name']]) && empty($p['type']) && $type == 'string') {
                 $p['type'] = static::$fieldTypesByName[$m['name']];
             }
             $m['default'] = $defaults[$position];
             $m['required'] = !$param->isOptional();
             $contentType = Util::nestedValue($p, 'type');
             if ($type == 'array' && $contentType && ($qualified = Scope::resolve($contentType, $scope))) {
                 list($p['type'], $children, $modelName) = static::getTypeAndModel(new ReflectionClass($qualified), $scope, $className . Text::title($methodUrl), $p);
             }
             if ($type instanceof ReflectionClass) {
                 list($type, $children, $modelName) = static::getTypeAndModel($type, $scope, $className . Text::title($methodUrl), $p);
             } elseif ($type && is_string($type) && ($qualified = Scope::resolve($type, $scope))) {
                 list($type, $children, $modelName) = static::getTypeAndModel(new ReflectionClass($qualified), $scope, $className . Text::title($methodUrl), $p);
             }
             if (isset($type)) {
                 $m['type'] = $type;
             }
             $m['children'] = $children;
             if (isset($modelName)) {
                 $m['model'] = $modelName;
             }
             if ($m['name'] == Defaults::$fullRequestDataName) {
                 $from = 'body';
                 if (!isset($m['type'])) {
                     $type = $m['type'] = 'array';
                 }
             } elseif (isset($p['from'])) {
                 $from = $p['from'];
             } else {
                 if (isset($type) && Util::isObjectOrArray($type)) {
                     $from = 'body';
                     if (!isset($type)) {
                         $type = $m['type'] = 'array';
                     }
                 } elseif ($m['required'] && in_array($m['name'], static::$prefixingParameterNames)) {
                     $from = 'path';
                 } else {
                     $from = 'body';
                 }
             }
             $p['from'] = $from;
             if (!isset($m['type'])) {
                 $type = $m['type'] = static::type($defaults[$position]);
             }
             if ($allowAmbiguity || $from == 'path') {
                 $pathParams[] = $position;
             }
             $position++;
         }
         $accessLevel = 0;
         if ($method->isProtected()) {
             $accessLevel = 3;
         } elseif (isset($metadata['access'])) {
             if ($metadata['access'] == 'protected') {
                 $accessLevel = 2;
             } elseif ($metadata['access'] == 'hybrid') {
                 $accessLevel = 1;
             }
         } elseif (isset($metadata['protected'])) {
             $accessLevel = 2;
         }
         /*
         echo " access level $accessLevel for $className::"
         .$method->getName().$method->isProtected().PHP_EOL;
         */
         // take note of the order
         $call = array('url' => null, 'className' => $className, 'path' => rtrim($resourcePath, '/'), 'methodName' => $method->getName(), 'arguments' => $arguments, 'defaults' => $defaults, 'metadata' => $metadata, 'accessLevel' => $accessLevel);
         // if manual route
         if (preg_match_all('/@url\\s+(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)' . '[ \\t]*\\/?(\\S*)/s', $doc, $matches, PREG_SET_ORDER)) {
             foreach ($matches as $match) {
                 $httpMethod = $match[1];
                 $url = rtrim($resourcePath . $match[2], '/');
                 //deep copy the call, as it may change for each @url
                 $copy = unserialize(serialize($call));
                 foreach ($copy['metadata']['param'] as $i => $p) {
                     $inPath = strpos($url, '{' . $p['name'] . '}') || strpos($url, ':' . $p['name']);
                     if ($inPath) {
                         $copy['metadata']['param'][$i][$dataName]['from'] = 'path';
                     } elseif ($httpMethod == 'GET' || $httpMethod == 'DELETE') {
                         $copy['metadata']['param'][$i][$dataName]['from'] = 'query';
                     } elseif (empty($p[$dataName]['from']) || $p[$dataName]['from'] == 'path') {
                         $copy['metadata']['param'][$i][$dataName]['from'] = 'body';
                     }
                 }
                 $url = preg_replace_callback('/{[^}]+}|:[^\\/]+/', function ($matches) use($copy) {
                     $match = trim($matches[0], '{}:');
                     $index = $copy['arguments'][$match];
                     return '{' . Routes::typeChar(isset($copy['metadata']['param'][$index]['type']) ? $copy['metadata']['param'][$index]['type'] : null) . $index . '}';
                 }, $url);
                 static::addPath($url, $copy, $httpMethod, $version);
             }
             //if auto route enabled, do so
         } elseif (Defaults::$autoRoutingEnabled) {
             // no configuration found so use convention
             if (preg_match_all('/^(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)/i', $methodUrl, $matches)) {
                 $httpMethod = strtoupper($matches[0][0]);
                 $methodUrl = substr($methodUrl, strlen($httpMethod));
             } else {
                 $httpMethod = 'GET';
             }
             if ($methodUrl == 'index') {
                 $methodUrl = '';
             }
             $url = empty($methodUrl) ? rtrim($resourcePath, '/') : $resourcePath . $methodUrl;
             $lastPathParam = array_keys($pathParams);
             $lastPathParam = end($lastPathParam);
             for ($position = 0; $position < count($params); $position++) {
                 $from = $metadata['param'][$position][$dataName]['from'];
                 if ($from == 'body' && ($httpMethod == 'GET' || $httpMethod == 'DELETE')) {
                     $call['metadata']['param'][$position][$dataName]['from'] = 'query';
                 }
             }
             if (empty($pathParams) || $allowAmbiguity) {
                 static::addPath($url, $call, $httpMethod, $version);
             }
             foreach ($pathParams as $position) {
                 if (!empty($url)) {
                     $url .= '/';
                 }
                 $url .= '{' . static::typeChar(isset($call['metadata']['param'][$position]['type']) ? $call['metadata']['param'][$position]['type'] : null) . $position . '}';
                 if ($allowAmbiguity || $position == $lastPathParam) {
                     static::addPath($url, $call, $httpMethod, $version);
                 }
             }
         }
     }
 }
Beispiel #3
0
 private function apis($version = 1, $resource = false)
 {
     $map = Routes::findAll(static::$excludedPaths + array($this->base()), static::$excludedHttpMethods, $version);
     $r = array();
     $a = array();
     foreach ($map as $path => $data) {
         $route = $data[0]['route'];
         $access = $data[0]['access'];
         if ($access && !Text::contains($path, '{')) {
             $r[] = array('path' => empty($path) ? '/root' : "/{$path}");
         }
         if (static::$hideProtected && !$access) {
             continue;
         }
         $grouper = array();
         foreach ($data as $item) {
             $route = $item['route'];
             $access = $item['access'];
             if (static::$hideProtected && !$access) {
                 continue;
             }
             $url = $route['url'];
             if (isset($grouper[$url])) {
                 $grouper[$url]['operations'][] = $this->operation($route);
             } else {
                 $api = array('path' => "/{$url}", 'description' => Util::nestedValue($route, 'metadata', 'classDescription') ?: '', 'operations' => array($this->operation($route)));
                 static::$groupOperations ? $grouper[$url] = $api : ($a[$path][] = $api);
             }
         }
         if (!empty($grouper)) {
             $a[$path] = array_values($grouper);
         } else {
             $order = array('GET' => 1, 'POST' => 2, 'PUT' => 3, 'PATCH' => 4, 'DELETE' => 5);
             foreach ($a as &$b) {
                 usort($b, function ($x, $y) use($order) {
                     return $x['operations'][0]->method == $y['operations'][0]->method ? $x['path'] > $y['path'] : $order[$x['operations'][0]->method] > $order[$y['operations'][0]->method];
                 });
             }
         }
     }
     if (false !== $resource) {
         if ($resource == 'root') {
             $resource = '';
         }
         if (isset($a[$resource])) {
             return $a[$resource];
         }
     }
     return $r;
 }
Beispiel #4
0
 /**
  * @access hybrid
  * @return \stdClass
  */
 public function index()
 {
     if (!static::$accessControlFunction && Defaults::$accessControlFunction) {
         static::$accessControlFunction = Defaults::$accessControlFunction;
     }
     $version = $this->restler->getRequestedApiVersion();
     $allRoutes = Util::nestedValue(Routes::toArray(), "v{$version}");
     $r = $this->_resourceListing();
     $map = array();
     if (isset($allRoutes['*'])) {
         $this->_mapResources($allRoutes['*'], $map, $version);
         unset($allRoutes['*']);
     }
     $this->_mapResources($allRoutes, $map, $version);
     foreach ($map as $path => $description) {
         if (!String::contains($path, '{')) {
             //add id
             $r->apis[] = array('path' => $path . $this->formatString, 'description' => $description);
         }
     }
     if (Defaults::$useUrlBasedVersioning && static::$listHigherVersions) {
         $nextVersion = $version + 1;
         if ($nextVersion <= $this->restler->getApiVersion()) {
             list($status, $data) = $this->_loadResource("/v{$nextVersion}/resources.json");
             if ($status == 200) {
                 $r->apis = array_merge($r->apis, $data->apis);
                 $r->apiVersion = $data->apiVersion;
             }
         }
     }
     return $r;
 }
Beispiel #5
0
 /**
  * Store the url map cache if needed
  */
 public function __destruct()
 {
     if ($this->productionMode && !$this->cached) {
         $this->cache->set('routes', Routes::toArray() + array('apiVersionMap' => $this->apiVersionMap));
     }
 }
Beispiel #6
0
 /**
  * @access hybrid
  * @return \stdClass
  */
 public function index()
 {
     $r = $this->_resourceListing();
     $map = array();
     $allRoutes = Routes::toArray();
     if (isset($allRoutes['*'])) {
         $this->_mapResources($allRoutes['*'], $map);
         unset($allRoutes['*']);
     }
     $this->_mapResources($allRoutes, $map);
     foreach ($map as $path => $description) {
         if (false === strpos($path, '{')) {
             //add id
             $r->apis[] = array('path' => "/resources/{$path}{$this->formatString}", 'description' => $description);
         }
     }
     return $r;
 }
Beispiel #7
0
 /**
  * Get the form
  *
  * @param string $method   http method to submit the form
  * @param string $action   relative path from the web root. When set to null
  *                         it uses the current api method's path
  * @param bool   $dataOnly if you want to render the form yourself use this
  *                         option
  * @param string $prefix   used for adjusting the spacing in front of
  *                         form elements
  * @param string $indent   used for adjusting indentation
  *
  * @return array|T
  *
  * @throws \Luracast\Restler\RestException
  */
 public static function get($method = 'POST', $action = null, $dataOnly = false, $prefix = '', $indent = '    ')
 {
     if (!static::$style) {
         static::$style = FormStyles::$html;
     }
     try {
         /** @var Restler $restler */
         $restler = Scope::get('Restler');
         if (is_null($action)) {
             $action = $restler->url;
         }
         $info = $restler->url == $action && Util::getRequestMethod() == $method ? $restler->apiMethodInfo : Routes::find(trim($action, '/'), $method, $restler->getRequestedApiVersion(), static::$preFill || $restler->requestMethod == $method && $restler->url == $action ? $restler->getRequestData() : array());
     } catch (RestException $e) {
         //echo $e->getErrorMessage();
         $info = false;
     }
     if (!$info) {
         throw new RestException(500, 'invalid action path for form `' . $method . ' ' . $action . '`');
     }
     static::$info = $info;
     $m = $info->metadata;
     $r = static::fields($dataOnly);
     if ($method != 'GET' && $method != 'POST') {
         if (empty(Defaults::$httpMethodOverrideProperty)) {
             throw new RestException(500, 'Forms require `Defaults::\\$httpMethodOverrideProperty`' . "for supporting HTTP {$method}");
         }
         if ($dataOnly) {
             $r[] = array('tag' => 'input', 'name' => Defaults::$httpMethodOverrideProperty, 'type' => 'hidden', 'value' => 'method');
         } else {
             $r[] = T::input()->name(Defaults::$httpMethodOverrideProperty)->value($method)->type('hidden');
         }
         $method = 'POST';
     }
     if (session_id() != '') {
         $form_key = static::key($method, $action);
         if ($dataOnly) {
             $r[] = array('tag' => 'input', 'name' => static::FORM_KEY, 'type' => 'hidden', 'value' => 'hidden');
         } else {
             $key = T::input()->name(static::FORM_KEY)->type('hidden')->value($form_key);
             $r[] = $key;
         }
     }
     $s = array('tag' => 'button', 'type' => 'submit', 'label' => Util::nestedValue($m, 'return', CommentParser::$embeddedDataName, 'label') ?: 'Submit');
     if (!$dataOnly) {
         $s = Emmet::make(static::style('submit', $m), $s);
     }
     $r[] = $s;
     $t = array('action' => $restler->getBaseUrl() . '/' . rtrim($action, '/'), 'method' => $method);
     if (static::$fileUpload) {
         static::$fileUpload = false;
         $t['enctype'] = 'multipart/form-data';
     }
     if (!$dataOnly) {
         $t = Emmet::make(static::style('form', $m), $t);
         $t->prefix = $prefix;
         $t->indent = $indent;
         $t[] = $r;
     } else {
         $t['fields'] = $r;
     }
     return $t;
 }
Beispiel #8
0
 /**
  * Store the url map cache if needed
  */
 protected function saveRoutesToCache()
 {
     if ($this->productionMode && !$this->cached && $this->enableRouteCache) {
         $this->cache->set('routes', Routes::toArray() + array('apiVersionMap' => $this->apiVersionMap));
     }
 }
Beispiel #9
0
 private function paths($version = 1)
 {
     $map = Routes::findAll(static::$excludedPaths + array($this->base()), static::$excludedHttpMethods, $version);
     $paths = array();
     foreach ($map as $path => $data) {
         $access = $data[0]['access'];
         if (static::$hideProtected && !$access) {
             continue;
         }
         foreach ($data as $item) {
             $route = $item['route'];
             $access = $item['access'];
             if (static::$hideProtected && !$access) {
                 continue;
             }
             $url = $route['url'];
             $paths["/{$url}"][strtolower($route['httpMethod'])] = $this->operation($route);
         }
     }
     return $paths;
 }
Beispiel #10
0
 public static function get($for = '', $activeUrl = null)
 {
     if (!static::$accessControlFunction && Defaults::$accessControlFunction) {
         static::$accessControlFunction = Defaults::$accessControlFunction;
     }
     /** @var Restler $restler */
     $restler = Scope::get('Restler');
     if (static::$addExtension) {
         static::$extension = '.' . $restler->responseFormat->getExtension();
     }
     if (is_null($activeUrl)) {
         $activeUrl = $restler->url;
     }
     $tree = array();
     foreach (static::$prepends as $path => $text) {
         $url = null;
         if (is_array($text)) {
             if (isset($text['url'])) {
                 $url = $text['url'];
                 $text = $text['text'];
             } else {
                 $url = current(array_keys($text));
                 $text = current($text);
             }
         }
         if (is_numeric($path)) {
             $path = $text;
             $text = null;
         }
         if (empty($for) || 0 === strpos($path, "{$for}/")) {
             static::build($tree, $path, $url, $text, $activeUrl);
         }
     }
     $routes = Routes::toArray();
     $routes = $routes['v' . $restler->getRequestedApiVersion()];
     foreach ($routes as $value) {
         foreach ($value as $httpMethod => $route) {
             if ($httpMethod != 'GET') {
                 continue;
             }
             $path = $route['url'];
             if (false !== strpos($path, '{')) {
                 continue;
             }
             if ($route['accessLevel'] > 1 && !Util::$restler->_authenticated) {
                 continue;
             }
             foreach (static::$excludedPaths as $exclude) {
                 if (empty($exclude)) {
                     if (empty($path)) {
                         continue 2;
                     }
                 } elseif (0 === strpos($path, $exclude)) {
                     continue 2;
                 }
             }
             if ($restler->_authenticated && static::$accessControlFunction && !call_user_func(static::$accessControlFunction, $route['metadata'])) {
                 continue;
             }
             $text = Util::nestedValue($route, 'metadata', CommentParser::$embeddedDataName, 'label');
             if (empty($for) || 0 === strpos($path, "{$for}/")) {
                 static::build($tree, $path, null, $text, $activeUrl);
             }
         }
     }
     foreach (static::$appends as $path => $text) {
         $url = null;
         if (is_array($text)) {
             if (isset($text['url'])) {
                 $url = $text['url'];
                 $text = $text['text'];
             } else {
                 $url = current(array_keys($text));
                 $text = current($text);
             }
         }
         if (is_numeric($path)) {
             $path = $text;
             $text = null;
         }
         if (empty($for) || 0 === strpos($path, "{$for}/")) {
             static::build($tree, $path, $url, $text, $activeUrl);
         }
     }
     if (!empty($for)) {
         $for = explode('/', $for);
         $p =& $tree;
         foreach ($for as $f) {
             if (isset($p[$f]['children'])) {
                 $p =& $p[$f]['children'];
             } else {
                 return array();
             }
         }
         return $p;
     }
     return $tree;
 }
Beispiel #11
0
 /**
  * Store the url map cache if needed
  */
 public function __destruct()
 {
     if ($this->productionMode && !$this->cached) {
         $this->cache->set('routes', Routes::toArray());
     }
 }