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); }
/** * Redirect to given url * * @param string $url relative path or full url * @param array $params associative array of query parameters * @param array $flashData associative array of properties to be set in $_SESSION for one time use * @param int $status http status code to send the response with ideally 301 or 302 * * @return array */ public static function to($url, array $params = array(), array $flashData = array(), $status = 302) { $url = ltrim($url, '/'); /** @var $r Restler */ $r = Scope::get('Restler'); $base = $r->getBaseUrl() . '/'; if (0 !== strpos($url, 'http')) { $url = $base . $url; } if (!empty($flashData) || $base . $r->url !== $url || Util::getRequestMethod() != 'GET') { if ($r->responseFormat instanceof JsonFormat) { return array('redirect' => $url); } if (!empty($params)) { $url .= '?' . http_build_query($params); } Flash::set($flashData); header("{$_SERVER['SERVER_PROTOCOL']} {$status} " . (isset(RestException::$codes[$status]) ? RestException::$codes[$status] : '')); header("Location: {$url}"); die(''); } return array(); }
/** * 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]; }
public function getStages() { $e = Scope::get('Restler')->getEvents(); $i = array_search($this->stage, $e); return array('success' => array_slice($e, 0, $i), 'failure' => array_slice($e, $i)); }
/** * Maximum api version supported by the api class * @return int */ public static function __getMaximumSupportedVersion() { return Scope::get('Restler')->getApiVersion(); }
/** * 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]; }
/** * post call * * call _post_{methodName}_{extension} if exists with the composed and * serialized (applying the repose format) response data * * @example _post_get_json */ protected function postCall() { $o =& $this->apiMethodInfo; $postCall = '_post_' . $o->methodName . '_' . $this->responseFormat->getExtension(); if (method_exists($o->className, $postCall)) { $this->dispatch('postCall'); $this->responseData = call_user_func(array(Scope::get($o->className), $postCall), $this->responseData); } }
/** * Get the form key * * @param string $method http method for form key * @param string $action relative path from the web root. When set to null * it uses the current api method's path * * @return string generated form key */ public static function key($method = 'POST', $action = null) { if (is_null($action)) { $action = Scope::get('Restler')->url; } $target = "{$method} {$action}"; if (empty(static::$key[$target])) { static::$key[$target] = md5($target . User::getIpAddress() . uniqid(mt_rand())); } $_SESSION[static::FORM_KEY] = static::$key; return static::$key[$target]; }
public static function blade(array $data, $debug = true) { $resolver = new EngineResolver(); $files = new Filesystem(); $compiler = new BladeCompiler($files, static::$cacheDirectory); $engine = new CompilerEngine($compiler); $resolver->register('blade', function () use($engine) { return $engine; }); /** @var Restler $restler */ $restler = Scope::get('Restler'); //Lets expose shortcuts for our classes spl_autoload_register(function ($className) use($restler) { if (isset($restler->apiMethodInfo->metadata['scope'][$className])) { return class_alias($restler->apiMethodInfo->metadata['scope'][$className], $className); } if (isset(Scope::$classAliases[$className])) { return class_alias(Scope::$classAliases[$className], $className); } return false; }, true, true); $viewFinder = new FileViewFinder($files, array(static::$viewPath)); $factory = new Factory($resolver, $viewFinder, new Dispatcher()); $path = $viewFinder->find(self::$view); $view = new View($factory, $engine, self::$view, $path, $data); $factory->callCreator($view); return $view->render(); }
/** * 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 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; }