/** * This static method is called for creating an instance of the class by * passing the initiation values as an array. * * @static * * @param array $properties * * @return iValueObject */ public static function __set_state(array $properties) { $task = new Task(); $task->id = Util::nestedValue($properties, 'id'); $task->position = Util::nestedValue($properties, 'position'); $task->text = Util::nestedValue($properties, 'text'); return $task; }
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); }
/** * Parses the request to figure out the best format for response. * Extension, if present, overrides the Accept header * * @throws RestException * @return iFormat * @example JsonFormat */ protected function negotiateResponseFormat() { $metadata = Util::nestedValue($this, 'apiMethodInfo', 'metadata'); //check if the api method insists on response format using @format comment if ($metadata && isset($metadata['format'])) { $formats = explode(',', (string) $metadata['format']); foreach ($formats as $i => $f) { $f = trim($f); if (!in_array($f, $this->formatOverridesMap)) { throw new RestException(500, "Given @format is not present in overriding formats. Please call `\$r->setOverridingFormats('{$f}');` first."); } $formats[$i] = $f; } call_user_func_array(array($this, 'setSupportedFormats'), $formats); } // check if client has specified an extension /** @var $format iFormat*/ $format = null; $extensions = explode('.', parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH)); while ($extensions) { $extension = array_pop($extensions); $extension = explode('/', $extension); $extension = array_shift($extension); if ($extension && isset($this->formatMap[$extension])) { $format = Scope::get($this->formatMap[$extension]); $format->setExtension($extension); // echo "Extension $extension"; return $format; } } // check if client has sent list of accepted data formats if (isset($_SERVER['HTTP_ACCEPT'])) { $acceptList = Util::sortByPriority($_SERVER['HTTP_ACCEPT']); foreach ($acceptList as $accept => $quality) { if (isset($this->formatMap[$accept])) { $format = Scope::get($this->formatMap[$accept]); $format->setMIME($accept); //echo "MIME $accept"; // Tell cache content is based on Accept header @header('Vary: Accept'); return $format; } elseif (false !== ($index = strrpos($accept, '+'))) { $mime = substr($accept, 0, $index); if (is_string(Defaults::$apiVendor) && 0 === stripos($mime, 'application/vnd.' . Defaults::$apiVendor . '-v')) { $extension = substr($accept, $index + 1); if (isset($this->formatMap[$extension])) { //check the MIME and extract version $version = intval(substr($mime, 18 + strlen(Defaults::$apiVendor))); if ($version > 0 && $version <= $this->apiVersion) { $this->requestedApiVersion = $version; $format = Scope::get($this->formatMap[$extension]); $format->setExtension($extension); // echo "Extension $extension"; Defaults::$useVendorMIMEVersioning = true; @header('Vary: Accept'); return $format; } } } } } } else { // RFC 2616: If no Accept header field is // present, then it is assumed that the // client accepts all media types. $_SERVER['HTTP_ACCEPT'] = '*/*'; } if (strpos($_SERVER['HTTP_ACCEPT'], '*') !== false) { if (false !== strpos($_SERVER['HTTP_ACCEPT'], 'application/*')) { $format = Scope::get('JsonFormat'); } elseif (false !== strpos($_SERVER['HTTP_ACCEPT'], 'text/*')) { $format = Scope::get('XmlFormat'); } elseif (false !== strpos($_SERVER['HTTP_ACCEPT'], '*/*')) { $format = Scope::get($this->formatMap['default']); } } if (empty($format)) { // RFC 2616: If an Accept header field is present, and if the // server cannot send a response which is acceptable according to // the combined Accept field value, then the server SHOULD send // a 406 (not acceptable) response. $format = Scope::get($this->formatMap['default']); $this->responseFormat = $format; throw new RestException(406, 'Content negotiation failed. ' . 'Try `' . $format->getMIME() . '` instead.'); } else { // Tell cache content is based at Accept header @header("Vary: Accept"); return $format; } }
private function _model($className, $instance = null) { $id = $this->_noNamespace($className); if (isset($this->_models->{$id})) { return; } $properties = array(); if (!$instance) { if (!class_exists($className)) { return; } $instance = new $className(); } $data = get_object_vars($instance); $reflectionClass = new \ReflectionClass($className); foreach ($data as $key => $value) { $propertyMetaData = null; try { $property = $reflectionClass->getProperty($key); if ($c = $property->getDocComment()) { $propertyMetaData = Util::nestedValue(CommentParser::parse($c), 'var'); } } catch (\ReflectionException $e) { } if (is_null($propertyMetaData)) { $type = $this->getType($value, true); $description = ''; } else { $type = Util::nestedValue($propertyMetaData, 'type') ?: $this->getType($value, true); $description = Util::nestedValue($propertyMetaData, 'description') ?: ''; if (class_exists($type)) { $this->_model($type); $type = $this->_noNamespace($type); } } if (isset(static::$dataTypeAlias[$type])) { $type = static::$dataTypeAlias[$type]; } $properties[$key] = array('type' => $type, 'description' => $description); if (Util::nestedValue($propertyMetaData, CommentParser::$embeddedDataName, 'required')) { $properties[$key]['required'] = true; } if ($type == 'Array') { $itemType = Util::nestedValue($propertyMetaData, CommentParser::$embeddedDataName, 'type') ?: (count($value) ? $this->getType(end($value), true) : 'string'); if (class_exists($itemType)) { $this->_model($itemType); $itemType = $this->_noNamespace($itemType); } $properties[$key]['item'] = array('type' => $itemType); } else { if (preg_match('/^Array\\[(.+)\\]$/', $type, $matches)) { $itemType = $matches[1]; $properties[$key]['type'] = 'Array'; $properties[$key]['item']['type'] = $itemType; if (class_exists($itemType)) { $this->_model($itemType); } } } } if (!empty($properties)) { $model = new stdClass(); $model->id = $id; $model->properties = $properties; $this->_models->{$id} = $model; } }
/** * Set the id attribute of the current tag * * @param string $value * * @return string */ public function id($value) { $this->attributes['id'] = isset($value) ? (string) $value : Util::nestedValue($this->attributes, 'name'); static::$instances[$value] = $this; return $this; }
/** * @param ReflectionClass $class * * @return array * * @access protected */ protected static function getTypeAndModel(ReflectionClass $class) { $children = array(); $props = $class->getProperties(ReflectionProperty::IS_PUBLIC); foreach ($props as $prop) { if ($c = $prop->getDocComment()) { $children[$prop->getName()] = array('name' => $prop->getName()) + Util::nestedValue(CommentParser::parse($c), 'var') + array('type' => 'string'); } } return array($class->getName(), $children); }
/** * Encode the given data in the format * * @param array $data resulting data that needs to * be encoded in the given format * @param boolean $humanReadable set to TRUE when restler * is not running in production mode. * Formatter has to make the encoded * output more human readable * * @throws \Exception * @return string encoded string */ public function encode($data, $humanReadable = false) { try { $events = $this->restler->getEvents(); $data = array_merge(array('response' => Object::toArray($data), 'stages' => $events, 'success' => end($events) != 'message'), static::$data); $params = array(); //print_r($this->restler); if (isset($this->restler->apiMethodInfo->metadata)) { $info = $data['api'] = $this->restler->apiMethodInfo; $metadata = $info->metadata; $params = $metadata['param']; } foreach ($params as $index => &$param) { $index = intval($index); if (is_numeric($index)) { $param['value'] = $this->restler->apiMethodInfo->parameters[$index]; } } $data['request']['parameters'] = $params; $inner = null; if (!$data['success'] && !is_null(static::$errorView)) { self::$view = static::$errorView; } elseif (static::$parseViewMetadata && isset($metadata['view'])) { if (is_array($metadata['view'])) { self::$view = $metadata['view']['description']; if ($value = Util::nestedValue($metadata['view'], 'properties', 'value')) { $inner = explode('.', $value); } } else { self::$view = $metadata['view']; } } if (false === ($i = strpos(self::$view, '.'))) { $extension = self::$format; self::$view .= '.' . $extension; } else { $extension = substr(self::$view, $i + 1); } switch ($extension) { case 'php': $view = self::$viewPath . DIRECTORY_SEPARATOR . self::$view; if (!is_readable($view)) { throw new RestException(500, "view file `{$view}` is not readable. Check for file presence and file permissions"); } $data = $inner ? Util::nestedValue($data, $inner) : $data; $template = function ($view) use($data) { $_ = function () use($data) { extract($data); $args = func_get_args(); $task = array_shift($args); switch ($task) { case 'require': case 'include': $file = HtmlFormat::$viewPath . DIRECTORY_SEPARATOR . $args[0]; if (is_readable($file)) { if (isset($args[1]) && ($arrays = Util::nestedValue($data, $args[1]))) { $str = ''; foreach ($arrays as $arr) { extract($arr); $str .= (include $file); } return $str; } else { return include $file; } } break; case 'if': if (count($args) < 2) { $args[1] = ''; } if (count($args) < 3) { $args[2] = ''; } return $args[0] ? $args[1] : $args[2]; break; default: return call_user_func_array($task, $args); } }; extract($data); return @(include $view); }; $value = $template($view); if (is_string($value)) { echo $value; } break; case 'twig': if (!class_exists('\\Twig_Environment', true)) { throw new RestException(500, 'Twig templates require twig classes to be installed using `composer install`'); } $loader = new \Twig_Loader_Filesystem(static::$viewPath); $twig = new \Twig_Environment($loader, array('cache' => Defaults::$cacheDirectory, 'debug' => true)); $template = $twig->loadTemplate(self::$view); return $template->render($data); case 'handlebar': case 'mustache': if (!class_exists('\\Mustache_Engine', true)) { throw new RestException(500, 'Mustache/Handlebar templates require mustache classes to be installed using `composer install`'); } $view = self::$viewPath . DIRECTORY_SEPARATOR . self::$view; $m = new \Mustache_Engine(); return $m->render(file_get_contents($view), $data); default: throw new RestException(500, "Unsupported template system `{$extension}`"); } } catch (Exception $e) { static::$parseViewMetadata = false; $this->reset(); throw $e; } }
private function setType(&$object, ValidationInfo $info) { //TODO: proper type management if ($info->type == 'array') { if ($info->children) { $this->model($info->contentType, $info->children); $object->items = (object) array('$ref' => $info->contentType); } elseif ($info->contentType && $info->contentType == 'associative') { unset($info->contentType); $this->model($info->type = 'Object', array(array('name' => 'property', 'type' => 'string', 'default' => '', 'required' => false, 'description' => ''))); } elseif ($info->contentType && $info->contentType != 'indexed') { $object->items = (object) array('type' => $info->contentType); } else { $object->items = (object) array('type' => 'string'); } } elseif ($info->children) { $this->model($info->type, $info->children); } elseif ($t = Util::nestedValue(static::$dataTypeAlias, strtolower($info->type))) { if (is_array($t)) { $info->type = $t[0]; $object->format = $t[1]; } else { $info->type = $t; } } else { $info->type = 'string'; } $object->type = $info->type; $has64bit = PHP_INT_MAX > 2147483647; if ($object->type == 'integer') { $object->format = $has64bit ? 'int64' : 'int32'; } elseif ($object->type == 'number') { $object->format = $has64bit ? 'double' : 'float'; } }
/** * Get the type and associated model * * @param ReflectionClass $class * @param array $scope * * @throws RestException * @throws \Exception * @return array * * @access protected */ protected static function getTypeAndModel(ReflectionClass $class, array $scope) { $className = $class->getName(); if (isset(static::$models[$className])) { return static::$models[$className]; } $children = array(); try { $props = $class->getProperties(ReflectionProperty::IS_PUBLIC); foreach ($props as $prop) { $name = $prop->getName(); $child = array('name' => $name); if ($c = $prop->getDocComment()) { $child += Util::nestedValue(CommentParser::parse($c), 'var') ?: []; } else { $o = $class->newInstance(); $p = $prop->getValue($o); if (is_object($p)) { $child['type'] = get_class($p); } elseif (is_array($p)) { $child['type'] = 'array'; if (count($p)) { $pc = reset($p); if (is_object($pc)) { $child['contentType'] = get_class($pc); } } } } $child += array('type' => $child['name'] == 'email' ? 'email' : 'string', 'label' => static::label($child['name'])); isset($child[CommentParser::$embeddedDataName]) ? $child[CommentParser::$embeddedDataName] += array('required' => true) : ($child[CommentParser::$embeddedDataName]['required'] = true); if ($qualified = Scope::resolve($child['type'], $scope)) { list($child['type'], $child['children']) = static::getTypeAndModel(new ReflectionClass($qualified), $scope); } elseif (($contentType = Util::nestedValue($child, CommentParser::$embeddedDataName, 'type')) && ($qualified = Scope::resolve($contentType, $scope))) { list($child['contentType'], $child['children']) = static::getTypeAndModel(new ReflectionClass($qualified), $scope); } $children[$name] = $child; } } catch (Exception $e) { if (String::endsWith($e->getFile(), 'CommentParser.php')) { throw new RestException(500, "Error while parsing comments of `{$className}` class. " . $e->getMessage()); } throw $e; } static::$models[$className] = array($className, $children); return static::$models[$className]; }
/** * Validate the given input * * Validates the input and attempts to fix it when fix is requested * * @param mixed $input * @param ValidationInfo $info * @param null $full * * @throws \Exception * @return array|bool|float|int|mixed|null|number|string */ public static function validate($input, ValidationInfo $info, $full = null) { $html = Scope::get('Restler')->responseFormat instanceof HtmlFormat; $name = $html ? "<strong>{$info->label}</strong>" : "`{$info->name}`"; try { if (is_null($input)) { if ($info->required) { throw new RestException(400, "{$name} is required."); } return null; } $error = isset($info->message) ? $info->message : "Invalid value specified for {$name}"; //if a validation method is specified if (!empty($info->method)) { $method = $info->method; $info->method = ''; $r = self::validate($input, $info); return $info->apiClassInstance->{$method}($r); } // when type is an array check if it passes for any type if (is_array($info->type)) { //trace("types are ".print_r($info->type, true)); $types = $info->type; foreach ($types as $type) { $info->type = $type; try { $r = self::validate($input, $info); if ($r !== false) { return $r; } } catch (RestException $e) { // just continue } } throw new RestException(400, $error); } //patterns are supported only for non numeric types if (isset($info->pattern) && $info->type != 'int' && $info->type != 'float' && $info->type != 'number') { if (!preg_match($info->pattern, $input)) { throw new RestException(400, $error); } } if (isset($info->choice)) { if (is_array($input)) { foreach ($input as $i) { if (!in_array($i, $info->choice)) { $error .= ". Expected one of (" . implode(',', $info->choice) . ")."; throw new RestException(400, $error); } } } elseif (!in_array($input, $info->choice)) { $error .= ". Expected one of (" . implode(',', $info->choice) . ")."; throw new RestException(400, $error); } } if (method_exists($class = get_called_class(), $info->type) && $info->type != 'validate') { try { return call_user_func("{$class}::{$info->type}", $input, $info); } catch (Invalid $e) { throw new RestException(400, $error . '. ' . $e->getMessage()); } } switch ($info->type) { case 'int': case 'float': case 'number': if (!is_numeric($input)) { $error .= '. Expecting ' . ($info->type == 'int' ? 'integer' : 'numeric') . ' value'; break; } if ($info->type == 'int' && (int) $input != $input) { if ($info->fix) { $r = (int) $input; } else { $error .= '. Expecting integer value'; break; } } else { $r = $info->numericValue($input); } if (isset($info->min) && $r < $info->min) { if ($info->fix) { $r = $info->min; } else { $error .= ". Minimum required value is {$info->min}."; break; } } if (isset($info->max) && $r > $info->max) { if ($info->fix) { $r = $info->max; } else { $error .= ". Maximum allowed value is {$info->max}."; break; } } return $r; case 'string': if (!is_string($input)) { $error .= '. Expecting alpha numeric value'; break; } if ($info->required && $input === '') { $error = "{$name} is required."; break; } $r = strlen($input); if (isset($info->min) && $r < $info->min) { if ($info->fix) { $input = str_pad($input, $info->min, $input); } else { $char = $info->min > 1 ? 'characters' : 'character'; $error .= ". Minimum {$info->min} {$char} required."; break; } } if (isset($info->max) && $r > $info->max) { if ($info->fix) { $input = substr($input, 0, $info->max); } else { $char = $info->max > 1 ? 'characters' : 'character'; $error .= ". Maximum {$info->max} {$char} allowed."; break; } } return $input; case 'bool': case 'boolean': if ($input === 'true' || $input === true) { return true; } if (is_numeric($input)) { return $input > 0; } return false; case 'array': if ($info->fix && is_string($input)) { $input = explode(CommentParser::$arrayDelimiter, $input); } if (is_array($input)) { $contentType = Util::nestedValue($info, 'contentType') ?: null; if ($info->fix) { if ($contentType == 'indexed') { $input = $info->filterArray($input, true); } elseif ($contentType == 'associative') { $input = $info->filterArray($input, true); } } elseif ($contentType == 'indexed' && array_values($input) != $input) { $error .= '. Expecting a list of items but an item is given'; break; } elseif ($contentType == 'associative' && array_values($input) == $input && count($input)) { $error .= '. Expecting an item but a list is given'; break; } $r = count($input); if (isset($info->min) && $r < $info->min) { $item = $info->max > 1 ? 'items' : 'item'; $error .= ". Minimum {$info->min} {$item} required."; break; } if (isset($info->max) && $r > $info->max) { if ($info->fix) { $input = array_slice($input, 0, $info->max); } else { $item = $info->max > 1 ? 'items' : 'item'; $error .= ". Maximum {$info->max} {$item} allowed."; break; } } if (isset($contentType) && $contentType != 'associative' && $contentType != 'indexed') { $name = $info->name; $info->type = $contentType; unset($info->contentType); foreach ($input as $key => $chinput) { $info->name = "{$name}[{$key}]"; $input[$key] = static::validate($chinput, $info); } } return $input; } elseif (isset($contentType)) { $error .= '. Expecting items of type ' . ($html ? "<strong>{$contentType}</strong>" : "`{$contentType}`"); break; } break; case 'mixed': case 'unknown_type': case 'unknown': case null: //treat as unknown return $input; default: if (!is_array($input)) { break; } //do type conversion if (class_exists($info->type)) { $input = $info->filterArray($input, false); $implements = class_implements($info->type); if (is_array($implements) && in_array('Luracast\\Restler\\Data\\iValueObject', $implements)) { return call_user_func("{$info->type}::__set_state", $input); } $class = $info->type; $instance = new $class(); if (is_array($info->children)) { if (empty($input) || !is_array($input) || $input === array_values($input)) { $error .= '. Expecting an item of type ' . ($html ? "<strong>{$info->type}</strong>" : "`{$info->type}`"); break; } foreach ($info->children as $key => $value) { $cv = new ValidationInfo($value); if (array_key_exists($key, $input) || $cv->required) { $instance->{$key} = static::validate(Util::nestedValue($input, $key), $cv); } } } return $instance; } } throw new RestException(400, $error); } catch (\Exception $e) { static::$exceptions[] = $e; if (static::$holdException) { return null; } throw $e; } }
public function __isset($name) { return !is_null(Util::nestedValue($_SESSION, 'flash', $name)); }
/** * Get Tag by id * * Retrieve a tag by its id attribute * * @param string $id * * @return Tags|null */ public static function byId($id) { return Util::nestedValue(static::$instances, $id); }
/** * Stage 2: Users response is recorded by the server * * Server redirects the user back with the result. * * If successful, * * Client exchanges the authorization code by a direct call (not through * user's browser) to get access token which can then be used call protected * APIs, if completed it calls a protected api and displays the result * otherwise client ends up showing the error message * * Else * * Client renders the error message to the user * * @param string $code * @param string $error_description * @param string $error_uri * * @return array * * @format HtmlFormat */ public function authorized($code = null, $error_description = null, $error_uri = null) { // the user denied the authorization request if (!$code) { HtmlFormat::$view = 'oauth2/client/denied.twig'; return array('error' => compact('error_description', 'error_uri')); } // exchange authorization code for access token $query = array('grant_type' => 'authorization_code', 'code' => $code, 'client_id' => self::$clientId, 'client_secret' => self::$clientSecret, 'redirect_uri' => self::$authorizeRedirectUrl); //call the API using cURL $curl = new Curl(); $endpoint = self::$serverUrl . '/grant'; $response = $curl->request($endpoint, $query, 'POST'); if (!json_decode($response['response'], true)) { $status = $response['headers']['http_code']; echo '<h1>something went wrong - see the raw response</h1>'; echo '<h2> Http ' . $status . ' - ' . RestException::$codes[$status] . '</h2>'; exit('<pre>' . print_r($response, true) . '</pre>'); } $error = array(); $response = json_decode($response['response'], true); // render error if applicable ($error['error_description'] = Util::nestedValue($response, 'error_description')) || ($error['error_description'] = Util::nestedValue($response, 'error', 'message')) || ($error['error_description'] = Util::nestedValue($response, 'errorMessage')) || ($error['error_description'] = Util::nestedValue($response, 'errorNumber')) || ($error['error_description'] = 'Unknown Error'); $error_uri = Util::nestedValue($response, 'error_uri'); if ($error_uri) { $error['error_uri'] = $error_uri; } // if it is successful, call the API with the retrieved token if ($token = Util::nestedValue($response, 'access_token')) { // make request to the API for awesome data $endpoint = self::$serverUrl . '/access?access_token=' . $token; $response = $curl->request($endpoint); HtmlFormat::$view = 'oauth2/client/granted.twig'; return array('token' => $token, 'endpoint' => $endpoint) + json_decode($response['response'], true); } HtmlFormat::$view = 'oauth2/client/error.twig'; return array('error' => $error); }
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; }
public function __construct(array $info) { $properties = get_object_vars($this); unset($properties['contentType']); foreach ($properties as $property => $value) { $this->{$property} = $this->getProperty($info, $property); } $this->rules = Util::nestedValue($info, 'properties') ?: $info; unset($this->rules['properties']); if (is_string($this->type) && $this->type == 'integer') { $this->type = 'int'; } }
/** * Create the needed tag hierarchy from emmet string * * @param string $string * * @param array|string $data * * @return array|T */ public static function make($string, $data = null) { if (!strlen($string)) { return array(); } $implicitTag = function () use(&$tag) { if (empty($tag->tag)) { switch ($tag->parent->tag) { case 'ul': case 'ol': $tag->tag = 'li'; break; case 'em': $tag->tag = 'span'; break; case 'table': case 'tbody': case 'thead': case 'tfoot': $tag->tag = 'tr'; break; case 'tr': $tag->tag = 'td'; break; case 'select': case 'optgroup': $tag->tag = 'option'; break; default: $tag->tag = 'div'; } } }; $parseText = function ($text, $round, $total, $data, $delimiter = null) use(&$tokens, &$tag) { $digits = 0; if ($delimiter == null) { $delimiter = array('.' => true, '#' => true, '*' => true, '>' => true, '+' => true, '^' => true, '[' => true, ']' => true, '=' => true); } while (!empty($tokens) && !isset($delimiter[$t = array_shift($tokens)])) { while ('$' === $t) { $digits++; $t = array_shift($tokens); } if ($digits) { $negative = false; $offset = 0; if ('@' == $t) { if ('-' == ($t = array_shift($tokens))) { $negative = true; if (is_numeric(reset($tokens))) { $offset = array_shift($tokens); } } elseif (is_numeric($t)) { $offset = $t; } else { array_unshift($tokens, $t); } } elseif ('#' == ($h = array_shift($tokens))) { if (!empty($t)) { $data = Util::nestedValue($data, $t); if (is_null($data)) { return null; } } if (is_numeric($data)) { $text .= sprintf("%0{$digits}d", (int) $data); } elseif (is_string($data)) { $text .= $data; } $digits = 0; continue; } else { array_unshift($tokens, $t, $h); } if ($negative) { $n = $total + 1 - $round + $offset; } else { $n = $round + $offset; } $text .= sprintf("%0{$digits}d", $n); $digits = 0; } else { $text .= $t; } } if (isset($t)) { array_unshift($tokens, $t); } return $text; }; $parseAttributes = function (callable $self, $round, $total, $data) use(&$tokens, &$tag, $parseText) { $a = $parseText('', $round, $total, $data); if (is_null($a)) { return; } if ('=' == ($v = array_shift($tokens))) { //value if ('"' == ($v = array_shift($tokens))) { $text = ''; $tag->{$a}($parseText($text, $round, $total, $data, array('"' => true))); } else { array_unshift($tokens, $v); $text = ''; $tag->{$a}($parseText($text, $round, $total, $data, array(' ' => true, ']' => true))); } if (' ' == ($v = array_shift($tokens))) { $self($self, $round, $total, $data); } } elseif (']' == $v) { //end $tag->{$a}(''); return; } elseif (' ' == $v) { $tag->{$a}(''); $self($self, $round, $total, $data); } }; $tokens = static::tokenize($string); $tag = new T(array_shift($tokens)); $parent = $root = new T(); $parse = function (callable $self, $round = 1, $total = 1) use(&$tokens, &$parent, &$tag, &$data, $parseAttributes, $implicitTag, $parseText) { $offsetTokens = null; $parent[] = $tag; $isInChild = false; while ($tokens) { switch (array_shift($tokens)) { //class case '.': $offsetTokens = array_values($tokens); array_unshift($offsetTokens, '.'); $implicitTag(); $e = array_filter(explode(' ', $tag->class)); $e[] = $parseText('', $round, $total, $data); $tag->class(implode(' ', array_unique($e))); break; //id //id case '#': $offsetTokens = array_values($tokens); array_unshift($offsetTokens, '#'); $implicitTag(); $tag->id($parseText(array_shift($tokens), $round, $total, $data)); break; //attributes //attributes case '[': $offsetTokens = array_values($tokens); array_unshift($offsetTokens, '['); $implicitTag(); $parseAttributes($parseAttributes, $round, $total, $data); break; //child //child case '{': $text = ''; $tag[] = $parseText($text, $round, $total, $data, array('}' => true)); break; case '>': $isInChild = true; $offsetTokens = null; if ('{' == ($t = array_shift($tokens))) { array_unshift($tokens, $t); $child = new T(); $tag[] = $child; $parent = $tag; $tag = $child; } elseif ('[' == $t) { array_unshift($tokens, $t); } else { $child = new T($t); $tag[] = $child; $parent = $tag; $tag = $child; } break; //sibling //sibling case '+': $offsetTokens = null; if (!$isInChild && $round != $total) { $tokens = array(); break; } if ('{' == ($t = array_shift($tokens))) { $tag = $tag->parent; array_unshift($tokens, $t); break; } elseif ('[' == $t) { array_unshift($tokens, $t); } else { $child = new T($t); $tag = $tag->parent; $tag[] = $child; $tag = $child; } break; //sibling of parent //sibling of parent case '^': if ($round != $total) { $tokens = array(); break; } $tag = $tag->parent; if ($tag->parent) { $tag = $tag->parent; } while ('^' == ($t = array_shift($tokens))) { if ($tag->parent) { $tag = $tag->parent; } } $child = new T($t); $tag[] = $child; $tag = $child; break; //clone //clone case '*': $times = array_shift($tokens); $removeCount = 2; $delimiter = array('.' => true, '#' => true, '*' => true, '>' => true, '+' => true, '^' => true, '[' => true, ']' => true, '=' => true); if (!is_numeric($times)) { if (is_string($times)) { if (!isset($delimiter[$times])) { $data = Util::nestedValue($data, $times) ?: $data; } else { array_unshift($tokens, $times); $removeCount = 1; } } $indexed = array_values($data); $times = is_array($data) && $indexed == $data ? count($data) : 0; } $source = $tag; if (!empty($offsetTokens)) { if (false !== strpos($source->class, ' ')) { $class = explode(' ', $source->class); array_pop($class); $class = implode(' ', $class); } else { $class = null; } $tag->class($class); $star = array_search('*', $offsetTokens); array_splice($offsetTokens, $star, $removeCount); $remainingTokens = $offsetTokens; } else { $remainingTokens = $tokens; } $source->parent = null; $sourceData = $data; $currentParent = $parent; for ($i = 1; $i <= $times; $i++) { $tag = clone $source; $parent = $currentParent; $data = is_array($sourceData) && isset($sourceData[$i - 1]) ? $sourceData[$i - 1] : @(string) $sourceData; $tokens = array_values($remainingTokens); $self($self, $i, $times); } $round = 1; $offsetTokens = null; $tag = $source; $tokens = array(); //$remainingTokens; break; } } }; $parse($parse); return count($root) == 1 ? $root[0] : $root; }
/** * Encode the given data in the format * * @param array $data resulting data that needs to * be encoded in the given format * @param boolean $humanReadable set to TRUE when restler * is not running in production mode. * Formatter has to make the encoded * output more human readable * * @throws \Exception * @return string encoded string */ public function encode($data, $humanReadable = false) { if (!is_readable(static::$viewPath)) { throw new \Exception('The views directory `' . self::$viewPath . '` should exist with read permission.'); } static::$data['basePath'] = dirname($_SERVER['SCRIPT_NAME']); static::$data['baseUrl'] = $this->restler->getBaseUrl(); static::$data['currentPath'] = $this->restler->url; try { $exception = $this->restler->exception; $success = is_null($exception); $error = $success ? null : $exception->getMessage(); $data = array('response' => static::$convertResponseToArray ? Object::toArray($data) : $data, 'stages' => $this->restler->getEvents(), 'success' => $success, 'error' => $error); $info = $data['api'] = $this->restler->apiMethodInfo; $metadata = Util::nestedValue($this->restler, 'apiMethodInfo', 'metadata'); $view = $success ? 'view' : 'errorView'; $value = false; if (static::$parseViewMetadata && isset($metadata[$view])) { if (is_array($metadata[$view])) { self::$view = $metadata[$view]['description']; $value = Util::nestedValue($metadata[$view], 'properties', 'value'); } else { self::$view = $metadata[$view]; } } elseif (!self::$view) { $file = static::$viewPath . '/' . $this->restler->url . '.' . static::getViewExtension(); self::$view = static::$useSmartViews && is_readable($file) ? $this->restler->url : static::$errorView; } if (isset($metadata['param']) && (!$value || 0 === strpos($value, 'request'))) { $params = $metadata['param']; foreach ($params as $index => &$param) { $index = intval($index); if (is_numeric($index)) { $param['value'] = $this->restler->apiMethodInfo->parameters[$index]; } } $data['request']['parameters'] = $params; } if ($value) { $data = Util::nestedValue($data, explode('.', $value)); } $data += static::$data; if (false === ($i = strrpos(self::$view, '.'))) { $template = self::$template; } else { self::$template = $template = substr(self::$view, $i + 1); self::$view = substr(self::$view, 0, $i); } if (!static::$cacheDirectory) { static::$cacheDirectory = Defaults::$cacheDirectory . DIRECTORY_SEPARATOR . $template; if (!file_exists(static::$cacheDirectory)) { if (!mkdir(static::$cacheDirectory, 0770, true)) { throw new RestException(500, 'Unable to create cache directory `' . static::$cacheDirectory . '`'); } } } if (method_exists($class = get_called_class(), $template)) { if ($template == 'blade') { $this->checkDependency('Illuminate\\View\\View'); } elseif ($template == 'twig') { $this->checkDependency('Twig_Environment'); } elseif ($template == 'mustache' || $template == 'handlebar') { $this->checkDependency('Mustache_Engine'); } return call_user_func("{$class}::{$template}", $data, $humanReadable); } throw new RestException(500, "Unsupported template system `{$template}`"); } catch (Exception $e) { static::$parseViewMetadata = false; $this->reset(); throw $e; } }
/** * @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; }
public static function fields($dataOnly = false) { $m = static::$info->metadata; $params = $m['param']; $values = static::$info->parameters; $r = array(); foreach ($params as $k => $p) { $value = Util::nestedValue($values, $k); if (is_scalar($value) || $p['type'] == 'array' && is_array($value) && $value == array_values($value) || is_object($value) && $p['type'] == get_class($value)) { $p['value'] = $value; } static::$validationInfo = $v = new ValidationInfo($p); if ($v->from == 'path') { continue; } if (!empty($v->children)) { $t = Emmet::make(static::style('fieldset', $m), array('label' => $v->label)); foreach ($v->children as $n => $c) { $value = Util::nestedValue($v->value, $n); if (is_scalar($value) || $c['type'] == 'array' && is_array($value) && $value == array_values($value) || is_object($value) && $c['type'] == get_class($value)) { $c['value'] = $value; } static::$validationInfo = $vc = new ValidationInfo($c); if ($vc->from == 'path') { continue; } $vc->name = $v->name . '[' . $vc->name . ']'; $t[] = static::field($vc, $dataOnly); } $r[] = $t; static::$validationInfo = null; } else { $f = static::field($v, $dataOnly); $r[] = $f; } static::$validationInfo = null; } return $r; }
/** * Get the type and associated model * * @param ReflectionClass $class * @param array $scope * * @throws RestException * @throws \Exception * @return array * * @access protected */ protected static function getTypeAndModel(ReflectionClass $class, array $scope, $prefix = '', array $rules = array()) { $className = $class->getName(); $dataName = CommentParser::$embeddedDataName; if (isset(static::$models[$prefix . $className])) { return static::$models[$prefix . $className]; } $children = array(); try { if ($magic_properties = static::parseMagic($class, empty($prefix))) { foreach ($magic_properties as $prop) { if (!isset($prop['name'])) { throw new Exception('@property comment is not properly defined in ' . $className . ' class'); } if (!isset($prop[$dataName]['label'])) { $prop[$dataName]['label'] = Text::title($prop['name']); } if (isset(static::$fieldTypesByName[$prop['name']]) && $prop['type'] == 'string' && !isset($prop[$dataName]['type'])) { $prop[$dataName]['type'] = static::$fieldTypesByName[$prop['name']]; } $children[$prop['name']] = $prop; } } else { $props = $class->getProperties(ReflectionProperty::IS_PUBLIC); foreach ($props as $prop) { $name = $prop->getName(); $child = array('name' => $name); if ($c = $prop->getDocComment()) { $child += Util::nestedValue(CommentParser::parse($c), 'var') ?: []; } else { $o = $class->newInstance(); $p = $prop->getValue($o); if (is_object($p)) { $child['type'] = get_class($p); } elseif (is_array($p)) { $child['type'] = 'array'; if (count($p)) { $pc = reset($p); if (is_object($pc)) { $child['contentType'] = get_class($pc); } } } } $child += array('type' => isset(static::$fieldTypesByName[$child['name']]) ? static::$fieldTypesByName[$child['name']] : 'string', 'label' => Text::title($child['name'])); isset($child[$dataName]) ? $child[$dataName] += array('required' => true) : ($child[$dataName]['required'] = true); if ($prop->class != $className && ($qualified = Scope::resolve($child['type'], $scope))) { list($child['type'], $child['children']) = static::getTypeAndModel(new ReflectionClass($qualified), $scope); } elseif (($contentType = Util::nestedValue($child, $dataName, 'type')) && ($qualified = Scope::resolve($contentType, $scope))) { list($child['contentType'], $child['children']) = static::getTypeAndModel(new ReflectionClass($qualified), $scope); } $children[$name] = $child; } } } catch (Exception $e) { if (Text::endsWith($e->getFile(), 'CommentParser.php')) { throw new RestException(500, "Error while parsing comments of `{$className}` class. " . $e->getMessage()); } throw $e; } if ($properties = Util::nestedValue($rules, 'properties')) { if (is_string($properties)) { $properties = array($properties); } $c = array(); foreach ($properties as $property) { if (isset($children[$property])) { $c[$property] = $children[$property]; } } $children = $c; } if ($required = Util::nestedValue($rules, 'required')) { //override required on children if (is_bool($required)) { // true means all are required false means none are required $required = $required ? array_keys($children) : array(); } elseif (is_string($required)) { $required = array($required); } $required = array_fill_keys($required, true); foreach ($children as $name => $child) { $children[$name][$dataName]['required'] = isset($required[$name]); } } static::$models[$prefix . $className] = array($className, $children, $prefix . $className); return static::$models[$prefix . $className]; }
/** * Validate the given input * * Validates the input and attempts to fix it when fix is requested * * @param mixed $input * @param ValidationInfo $info * @param null $full * * @return array|bool|float|int|mixed|null|number|string * @throws \Luracast\Restler\RestException */ public static function validate($input, ValidationInfo $info, $full = null) { if (is_null($input)) { if ($info->required) { throw new RestException(400, "`{$info->name}` is required but missing."); } return null; } $error = isset($info->rules['message']) ? $info->rules['message'] : "invalid value specified for `{$info->name}`"; //if a validation method is specified if (!empty($info->method)) { $method = $info->method; $info->method = ''; $r = self::validate($input, $info); return $info->apiClassInstance->{$method}($r); } // when type is an array check if it passes for any type if (is_array($info->type)) { //trace("types are ".print_r($info->type, true)); $types = $info->type; foreach ($types as $type) { $info->type = $type; try { $r = self::validate($input, $info); if ($r !== false) { return $r; } } catch (RestException $e) { // just continue } } throw new RestException(400, $error); } //patterns are supported only for non numeric types if (isset($info->pattern) && $info->type != 'int' && $info->type != 'float' && $info->type != 'number') { if (!preg_match($info->pattern, $input)) { throw new RestException(400, $error); } } if (isset($info->choice)) { if (!in_array($input, $info->choice)) { throw new RestException(400, $error); } } if (method_exists(__CLASS__, $info->type) && $info->type != 'validate') { try { return call_user_func(__CLASS__ . '::' . $info->type, $input, $info); } catch (Invalid $e) { throw new RestException(400, $error . '. ' . $e->getMessage()); } } switch ($info->type) { case 'int': case 'float': case 'number': if (!is_numeric($input)) { $error .= '. Expecting ' . ($info->type == 'int' ? 'integer' : 'numeric') . ' value'; break; } if ($info->type == 'int' && (int) $input != $input) { if ($info->fix) { $r = (int) $input; } else { $error .= '. Expecting integer value'; break; } } else { $r = $info->numericValue($input); } if (isset($info->min) && $r < $info->min) { if ($info->fix) { $r = $info->min; } else { $error .= '. Given value is too low'; break; } } if (isset($info->max) && $r > $info->max) { if ($info->fix) { $r = $info->max; } else { $error .= '. Given value is too high'; break; } } return $r; case 'string': $r = strlen($input); if (isset($info->min) && $r < $info->min) { if ($info->fix) { $input = str_pad($input, $info->min, $input); } else { $error .= '. Given string is too short'; break; } } if (isset($info->max) && $r > $info->max) { if ($info->fix) { $input = substr($input, 0, $info->max); } else { $error .= '. Given string is too long'; break; } } return $input; case 'bool': case 'boolean': if ($input == 'true') { return true; } if (is_numeric($input)) { return $input > 0; } return false; case 'array': if (is_array($input)) { $contentType = Util::nestedValue($info, 'contentType') ?: null; if ($info->fix) { if ($contentType == 'indexed') { $input = $info->filterArray($input, true); } elseif ($contentType == 'associative') { $input = $info->filterArray($input, true); } } elseif ($contentType == 'indexed' && array_values($input) != $input) { $error .= '. Expecting an array but an object is given'; break; } elseif ($contentType == 'associative' && array_values($input) == $input && count($input)) { $error .= '. Expecting an object but an array is given'; break; } $r = count($input); if (isset($info->min) && $r < $info->min) { $error .= '. Given array is too small'; break; } if (isset($info->max) && $r > $info->max) { if ($info->fix) { $input = array_slice($input, 0, $info->max); } else { $error .= '. Given array is too big'; break; } } if (isset($contentType) && $contentType != 'associative' && $contentType != 'indexed') { $name = $info->name; $info->type = $contentType; unset($info->contentType); foreach ($input as $key => $chinput) { $info->name = "{$name}[{$key}]"; $input[$key] = static::validate($chinput, $info); } } return $input; } elseif (isset($contentType)) { $error .= ". Expecting an array with contents of type `{$contentType}`"; break; } elseif ($info->fix && is_string($input)) { return array($input); } break; case 'mixed': case 'unknown_type': case 'unknown': case null: //treat as unknown return $input; default: if (!is_array($input)) { break; } //do type conversion if (class_exists($info->type)) { $input = $info->filterArray($input, false); $implements = class_implements($info->type); if (is_array($implements) && in_array('Luracast\\Restler\\Data\\iValueObject', $implements)) { return call_user_func("{$info->type}::__set_state", $input); } $class = $info->type; $instance = new $class(); if (is_array($info->children)) { if (empty($input) || !is_array($input) || $input === array_values($input)) { $error .= ". Expecting an object of type `{$info->type}`"; break; } foreach ($info->children as $key => $value) { $instance->{$key} = static::validate(Util::nestedValue($input, $key), new ValidationInfo($value)); } } return $instance; } } throw new RestException(400, $error); }