2
0
forked from Wavyzz/dolibarr

NEW : add restler framework

First step to build REST API into Dolibarr
This commit is contained in:
jfefe
2015-05-01 15:42:05 +02:00
parent f6c93525a6
commit b503b16f07
69 changed files with 12229 additions and 0 deletions

View File

@@ -0,0 +1,114 @@
<?php
namespace Luracast\Restler;
use Luracast\Restler\iCache;
/**
* Class ApcCache provides an APC based cache for Restler
*
* @category Framework
* @package Restler
* @author Joel R. Simpson <joel.simpson@gmail.com>
* @copyright 2013 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class ApcCache implements iCache
{
/**
* The namespace that all of the cached entries will be stored under. This allows multiple APIs to run concurrently.
*
* @var string
*/
static public $namespace = 'restler';
/**
* store data in the cache
*
*
* @param string $name
* @param mixed $data
*
* @return boolean true if successful
*/
public function set($name, $data)
{
function_exists('apc_store') || $this->apcNotAvailable();
try {
return apc_store(self::$namespace . "-" . $name, $data);
} catch
(\Exception $exception) {
return false;
}
}
private function apcNotAvailable()
{
throw new \Exception('APC is not available for use as Restler Cache. Please make sure the module is installed. http://php.net/manual/en/apc.installation.php');
}
/**
* retrieve data from the cache
*
*
* @param string $name
* @param bool $ignoreErrors
*
* @throws \Exception
* @return mixed
*/
public function get($name, $ignoreErrors = false)
{
function_exists('apc_fetch') || $this->apcNotAvailable();
try {
return apc_fetch(self::$namespace . "-" . $name);
} catch (\Exception $exception) {
if (!$ignoreErrors) {
throw $exception;
}
return null;
}
}
/**
* delete data from the cache
*
*
* @param string $name
* @param bool $ignoreErrors
*
* @throws \Exception
* @return boolean true if successful
*/
public function clear($name, $ignoreErrors = false)
{
function_exists('apc_delete') || $this->apcNotAvailable();
try {
apc_delete(self::$namespace . "-" . $name);
} catch (\Exception $exception) {
if (!$ignoreErrors) {
throw $exception;
}
}
}
/**
* check if the given name is cached
*
*
* @param string $name
*
* @return boolean true if cached
*/
public function isCached($name)
{
function_exists('apc_exists') || $this->apcNotAvailable();
return apc_exists(self::$namespace . "-" . $name);
}
}

View File

@@ -0,0 +1,438 @@
<?php
namespace Luracast\Restler {
/**
* Class that implements spl_autoload facilities and multiple
* conventions support.
* Supports composer libraries and 100% PSR-0 compliant.
* In addition we enable namespace prefixing and class aliases.
*
* @category Framework
* @package Restler
* @subpackage helper
* @author Nick Lombard <github@jigsoft.co.za>
* @copyright 2012 Luracast
* @version 3.0.0rc5
*/
class AutoLoader
{
protected static $instance, // the singleton instance reference
$perfectLoaders, // used to keep the ideal list of loaders
$rogueLoaders = array(), // other auto loaders now unregistered
$classMap = array(), // the class to include file mapping
$aliases = array( // aliases and prefixes instead of null list aliases
'Luracast\\Restler' => null,
'Luracast\\Restler\\Format' => null,
'Luracast\\Restler\\Data' => null,
'Luracast\\Restler\\Filter' => null,
);
/**
* Singleton instance facility.
*
* @static
* @return AutoLoader the current instance or new instance if none exists.
*/
public static function instance()
{
static::$instance = static::$instance ?: new static();
return static::thereCanBeOnlyOne();
}
/**
* Helper function to add a path to the include path.
* AutoLoader uses the include path to discover classes.
*
* @static
*
* @param $path string absolute or relative path.
*
* @return bool false if the path cannot be resolved
* or the resolved absolute path.
*/
public static function addPath($path) {
if (false === $path = stream_resolve_include_path($path))
return false;
else
set_include_path($path.PATH_SEPARATOR.get_include_path());
return $path;
}
/**
* Other autoLoaders interfere and cause duplicate class loading.
* AutoLoader is capable enough to handle all standards so no need
* for others stumbling about.
*
* @return callable the one true auto loader.
*/
public static function thereCanBeOnlyOne() {
if (static::$perfectLoaders === spl_autoload_functions())
return static::$instance;
if (false !== $loaders = spl_autoload_functions())
if (0 < $count = count($loaders))
for ($i = 0, static::$rogueLoaders += $loaders;
$i < $count && false != ($loader = $loaders[$i]);
$i++)
if ($loader !== static::$perfectLoaders[0])
spl_autoload_unregister($loader);
return static::$instance;
}
/**
* Seen this before cache handler.
* Facilitates both lookup and persist operations as well as convenience,
* load complete map functionality. The key can only be given a non falsy
* value once, this will be truthy for life.
*
* @param $key mixed class name considered or a collection of
* classMap entries
* @param $value mixed optional not required when doing a query on
* key. Default is false we haven't seen this
* class. Most of the time it will be the filename
* for include and is set to true if we are unable
* to load this class iow true == it does not exist.
* value may also be a callable auto loader function.
*
* @return mixed The known value for the key or false if key has no value
*/
public static function seen($key, $value = false)
{
if (is_array($key)) {
static::$classMap = $key + static::$classMap;
return false;
}
if (empty(static::$classMap[$key]))
static::$classMap[$key] = $value;
if (is_string($alias = static::$classMap[$key]))
if (isset(static::$classMap[$alias]))
return static::$classMap[$alias];
return static::$classMap[$key];
}
/**
* Protected constructor to enforce singleton pattern.
* Populate a default include path.
* All possible includes cant possibly be catered for and if you
* require another path then simply add it calling set_include_path.
*/
protected function __construct()
{
static::$perfectLoaders = array($this);
if (false === static::seen('__include_path')) {
$paths = explode(PATH_SEPARATOR, get_include_path());
$slash = DIRECTORY_SEPARATOR;
$dir = dirname(__DIR__);
$source_dir = dirname($dir);
$dir = dirname($source_dir);
foreach (
array(
array($source_dir),
array($dir, '..', '..', 'composer'),
array($dir, 'vendor', 'composer'),
array($dir, '..', '..', '..', 'php'),
array($dir, 'vendor', 'php'))
as $includePath)
if (false !== $path = stream_resolve_include_path(
implode($slash, $includePath)
))
if ('composer' == end($includePath) &&
false !== $classmapPath = stream_resolve_include_path(
"$path{$slash}autoload_classmap.php"
)
) {
static::seen(static::loadFile(
$classmapPath
));
$paths = array_merge(
$paths,
array_values(static::loadFile(
"$path{$slash}autoload_namespaces.php"
))
);
} else
$paths[] = $path;
$paths = array_filter(array_map(
function ($path) {
if (false == $realPath = @realpath($path))
return null;
return $realPath . DIRECTORY_SEPARATOR;
},
$paths
));
natsort($paths);
static::seen(
'__include_path',
implode(PATH_SEPARATOR, array_unique($paths))
);
}
set_include_path(static::seen('__include_path'));
}
/**
* Attempt to include the path location.
* Called from a static context which will not expose the AutoLoader
* instance itself.
*
* @param $path string location of php file on the include path
*
* @return bool|mixed returns reference obtained from the include or false
*/
private static function loadFile($path)
{
return \Luracast_Restler_autoloaderInclude($path);
}
/**
* Attempt to load class with namespace prefixes.
*
* @param $className string class name
*
* @return bool|mixed reference to discovered include or false
*/
private function loadPrefixes($className)
{
$currentClass = $className;
if (false !== $pos = strrpos($className, '\\'))
$className = substr($className, $pos);
else
$className = "\\$className";
for (
$i = 0,
$file = false,
$count = count(static::$aliases),
$prefixes = array_keys(static::$aliases);
$i < $count
&& false === $file
&& false === $file = $this->discover(
$variant = $prefixes[$i++].$className,
$currentClass
);
$file = $this->loadAliases($variant)
);
return $file;
}
/**
* Attempt to load configured aliases based on namespace part of class name.
*
* @param $className string fully qualified class name.
*
* @return bool|mixed reference to discovered include or false
*/
private function loadAliases($className)
{
$file = false;
if (preg_match('/(.+)(\\\\\w+$)/U', $className, $parts))
for (
$i = 0,
$aliases = isset(static::$aliases[$parts[1]])
? static::$aliases[$parts[1]] : array(),
$count = count($aliases);
$i < $count && false === $file;
$file = $this->discover(
"{$aliases[$i++]}$parts[2]",
$className
)
) ;
return $file;
}
/**
* Load from rogueLoaders as last resort.
* It may happen that a custom auto loader may load classes in a unique way,
* these classes cannot be seen otherwise nor should we attempt to cover every
* possible deviation. If we still can't find a class, as a last resort, we will
* run through the list of rogue loaders and verify if we succeeded.
*
* @param $className string className that can't be found
* @param null $loader callable loader optional when the loader is known
*
* @return bool false unless className now exists
*/
private function loadLastResort($className, $loader = null) {
$loaders = array_unique(static::$rogueLoaders);
if (isset($loader)) {
if (false === array_search($loader, $loaders))
static::$rogueLoaders[] = $loader;
return $this->loadThisLoader($className, $loader);
}
foreach ($loaders as $loader)
if (false !== $file = $this->loadThisLoader($className, $loader))
return $file;
return false;
}
/**
* Helper for loadLastResort.
* Use loader with $className and see if className exists.
*
* @param $className string name of a class to load
* @param $loader callable autoLoader method
*
* @return bool false unless className exists
*/
private function loadThisLoader($className, $loader) {
if (is_callable($loader)
&& false !== $file = $loader($className)
&& $this->exists($className, $loader))
return $file;
return false;
}
/**
* Create an alias for class.
*
* @param $className string the name of the alias class
* @param $currentClass string the current class this alias references
*/
private function alias($className, $currentClass)
{
if ($className != $currentClass
&& false !== strpos($className, $currentClass))
if (!class_exists($currentClass, false)
&& class_alias($className, $currentClass))
static::seen($currentClass, $className);
}
/**
* Discovery process.
*
* @param $className string class name to discover
* @param $currentClass string optional name of current class when
* looking up an alias
*
* @return bool|mixed resolved include reference or false
*/
private function discover($className, $currentClass = null)
{
$currentClass = $currentClass ?: $className;
/** The short version we've done this before and found it in cache */
if (false !== $file = static::seen($className)) {
if (!$this->exists($className))
if (is_callable($file))
$file = $this->loadLastResort($className, $file);
elseif($file = stream_resolve_include_path($file))
$file = static::loadFile($file);
$this->alias($className, $currentClass);
return $file;
}
/** We did not find it in cache, lets look for it shall we */
/** replace \ with / and _ in CLASS NAME with / = PSR-0 in 3 lines */
$file = preg_replace("/\\\|_(?=\w+$)/", DIRECTORY_SEPARATOR, $className);
if (false === $file = stream_resolve_include_path("$file.php"))
return false;
/** have we loaded this file before could this be an alias */
if (in_array($file, get_included_files())) {
if (false !== $sameFile = array_search($file, static::$classMap))
if (!$this->exists($className, $file))
if (false !== strpos($sameFile, $className))
$this->alias($sameFile, $className);
return $file;
}
$state = array_merge(get_declared_classes(), get_declared_interfaces());
if (false !== $result = static::loadFile($file)) {
if ($this->exists($className, $file))
$this->alias($className, $currentClass);
elseif (false != $diff = array_diff(
array_merge(get_declared_classes(), get_declared_interfaces()), $state))
foreach ($diff as $autoLoaded)
if ($this->exists($autoLoaded, $file))
if (false !== strpos($autoLoaded, $className))
$this->alias($autoLoaded, $className);
if (!$this->exists($currentClass))
$result = false;
}
return $result;
}
/**
* Checks whether supplied string exists in a loaded class or interface.
* As a convenience the supplied $mapping can be the value for seen.
*
* @param $className string The class or interface to verify
* @param $mapping string (optional) value for map/seen if found to exist
*
* @return bool whether the class/interface exists without calling auto loader
*/
private function exists($className, $mapping = null)
{
if (class_exists($className, false)
|| interface_exists($className, false))
if (isset($mapping))
return static::seen($className, $mapping);
else
return true;
return false;
}
/**
* Auto loader callback through __invoke object as function.
*
* @param $className string class/interface name to auto load
*
* @return mixed|null the reference from the include or null
*/
public function __invoke($className)
{
if (empty($className))
return false;
if (false !== $includeReference = $this->discover($className))
return $includeReference;
static::thereCanBeOnlyOne();
if (false !== $includeReference = $this->loadAliases($className))
return $includeReference;
if (false !== $includeReference = $this->loadPrefixes($className))
return $includeReference;
if (false !== $includeReference = $this->loadLastResort($className))
return $includeReference;
static::seen($className, true);
return null;
}
}
}
namespace {
/**
* Include function in the root namespace to include files optimized
* for the global context.
*
* @param $path string path of php file to include into the global context.
*
* @return mixed|bool false if the file could not be included.
*/
function Luracast_Restler_autoloaderInclude($path) {
return include $path;
}
}

View File

@@ -0,0 +1,466 @@
<?php
namespace Luracast\Restler;
use Exception;
/**
* Parses the PHPDoc comments for metadata. Inspired by `Documentor` code base.
*
* @category Framework
* @package Restler
* @subpackage Helper
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class CommentParser
{
/**
* name for the embedded data
*
* @var string
*/
public static $embeddedDataName = 'properties';
/**
* Regular Expression pattern for finding the embedded data and extract
* the inner information. It is used with preg_match.
*
* @var string
*/
public static $embeddedDataPattern
= '/```(\w*)[\s]*(([^`]*`{0,2}[^`]+)*)```/ms';
/**
* Pattern will have groups for the inner details of embedded data
* this index is used to locate the data portion.
*
* @var int
*/
public static $embeddedDataIndex = 2;
/**
* Delimiter used to split the array data.
*
* 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
* the character specified will be used as the delimiter to find split
* array data.
*
* @var string
*/
public static $arrayDelimiter = ',';
/**
* character sequence used to escape \@
*/
const escapedAtChar = '\\@';
/**
* character sequence used to escape end of comment
*/
const escapedCommendEnd = '{@*}';
/**
* Instance of Restler class injected at runtime.
*
* @var Restler
*/
public $restler;
/**
* Comment information is parsed and stored in to this array.
*
* @var array
*/
private $_data = array();
/**
* Parse the comment and extract the data.
*
* @static
*
* @param $comment
* @param bool $isPhpDoc
*
* @return array associative array with the extracted values
*/
public static function parse($comment, $isPhpDoc = true)
{
$p = new self();
if (empty($comment)) {
return $p->_data;
}
if ($isPhpDoc) {
$comment = self::removeCommentTags($comment);
}
$p->extractData($comment);
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);
}
/**
* Extracts description and long description, uses other methods to get
* parameters.
*
* @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();
$longDescription = array();
$params = array();
$mode = 0; // extract short description;
$comments = preg_split("/(\r?\n)/", $comment);
// 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));
$this->parseParam($param, $value, $embedded);
}
return $this->_data;
}
/**
* Parse parameters that begin with (at)
*
* @param $param
* @param array $value
* @param array $embedded
*/
private function parseParam($param, array $value, array $embedded)
{
$data = & $this->_data;
$allowMultiple = false;
switch ($param) {
case 'param' :
$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];
}
$data[$param] = $value + $data[$param];
}
}
/**
* Parses the inline php doc comments and embedded data.
*
* @param $subject
*
* @return array
* @throws Exception
*/
private function parseEmbeddedData($subject)
{
$data = array();
//parse {@pattern } tags specially
while (preg_match('|(?s-m)({@pattern (/.+/[imsxuADSUXJ]*)})|', $subject, $matches)) {
$subject = str_replace($matches[0], '', $subject);
$data['pattern'] = $matches[2];
}
while (preg_match('/{@(\w+)\s?([^}]*)}/ms', $subject, $matches)) {
$subject = str_replace($matches[0], '', $subject);
if ($matches[2] == 'true' || $matches[2] == 'false') {
$matches[2] = $matches[2] == 'true';
} elseif ($matches[2] == '') {
$matches[2] = true;
}
if ($matches[1] == 'pattern') {
throw new Exception('Inline pattern tag should follow {@pattern /REGEX_PATTERN_HERE/} format and can optionally include PCRE modifiers following the ending `/`');
} elseif (false !== strpos($matches[2], static::$arrayDelimiter)) {
$matches[2] = explode(static::$arrayDelimiter, $matches[2]);
}
$data[$matches[1]] = $matches[2];
}
while (preg_match(self::$embeddedDataPattern, $subject, $matches)) {
$subject = str_replace($matches[0], '', $subject);
$str = $matches[self::$embeddedDataIndex];
if (isset ($this->restler)
&& self::$embeddedDataIndex > 1
&& !empty ($matches[1])
) {
$extension = $matches[1];
$formatMap = $this->restler->getFormatMap();
if (isset ($formatMap[$extension])) {
/**
* @var \Luracast\Restler\Format\iFormat
*/
$format = $formatMap[$extension];
$format = new $format();
$data = $format->decode($str);
}
} else { // auto detect
if ($str{0} == '{') {
$d = json_decode($str, true);
if (json_last_error() != JSON_ERROR_NONE) {
throw new Exception('Error parsing embedded JSON data'
. " $str");
}
$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);
}
private function formatThrows(array $value)
{
$r = array();
$r['code'] = count($value) && is_numeric($value[0])
? intval(array_shift($value)) : 500;
$reason = implode(' ', $value);
$r['reason'] = empty($reason) ? '' : $reason;
return $r;
}
private function formatClass(array $value)
{
$param = array_shift($value);
if (empty($param)) {
$param = 'Unknown';
}
$value = implode(' ', $value);
return array(
ltrim($param, '\\'),
array('description' => $value)
);
}
private function formatAuthor(array $value)
{
$r = array();
$email = end($value);
if ($email{0} == '<') {
$email = substr($email, 1, -1);
array_pop($value);
$r['email'] = $email;
}
$r['name'] = implode(' ', $value);
return $r;
}
private function formatReturn(array $value)
{
$data = explode('|', array_shift($value));
$r = array(
'type' => count($data) == 1 ? $data[0] : $data
);
$r['description'] = implode(' ', $value);
return $r;
}
private function formatParam(array $value)
{
$r = array();
$data = array_shift($value);
if (empty($data)) {
$r['type'] = 'mixed';
} elseif ($data{0} == '$') {
$r['name'] = substr($data, 1);
$r['type'] = 'mixed';
} else {
$data = explode('|', $data);
$r['type'] = count($data) == 1 ? $data[0] : $data;
$data = array_shift($value);
if (!empty($data) && $data{0} == '$') {
$r['name'] = substr($data, 1);
}
}
if ($value) {
$r['description'] = implode(' ', $value);
}
return $r;
}
private function formatVar(array $value)
{
$r = array();
$data = array_shift($value);
if (empty($data)) {
$r['type'] = 'mixed';
} elseif ($data{0} == '$') {
$r['name'] = substr($data, 1);
$r['type'] = 'mixed';
} else {
$data = explode('|', $data);
$r['type'] = count($data) == 1 ? $data[0] : $data;
}
if ($value) {
$r['description'] = implode(' ', $value);
}
return $r;
}
}

View File

@@ -0,0 +1,71 @@
<?php
namespace Luracast\Restler;
/**
* Default Composer to provide standard structure for all HTTP responses
*
* @category Framework
* @package Restler
* @subpackage result
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
*/
class Compose implements iCompose
{
/**
* @var bool When restler is not running in production mode, this value will
* be checked to include the debug information on error response
*/
public static $includeDebugInfo = true;
/**
* Current Restler instance
* Injected at runtime
*
* @var Restler
*/
public $restler;
/**
* Result of an api call is passed to this method
* to create a standard structure for the data
*
* @param mixed $result can be a primitive or array or object
*
* @return mixed
*/
public function response($result)
{
//TODO: check Defaults::language and change result accordingly
return $result;
}
/**
* When the api call results in RestException this method
* will be called to return the error message
*
* @param RestException $exception exception that has reasons for failure
*
* @return array
*/
public function message(RestException $exception)
{
//TODO: check Defaults::language and change result accordingly
$r = array(
'error' => array(
'code' => $exception->getCode(),
'message' => $exception->getErrorMessage(),
) + $exception->getDetails()
);
if (!Scope::get('Restler')->getProductionMode() && self::$includeDebugInfo) {
$r += array(
'debug' => array(
'source' => $exception->getSource(),
'stages' => $exception->getStages(),
)
);
}
return $r;
}
}

View File

@@ -0,0 +1,55 @@
<?php
namespace Luracast\Restler\Data;
/**
* ValueObject for api method info. All needed information about a api method
* is stored here
*
* @category Framework
* @package Restler
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class ApiMethodInfo extends ValueObject
{
/**
* @var string target url
*/
public $url;
/**
* @var string
*/
public $className;
/**
* @var string
*/
public $methodName;
/**
* @var array parameters to be passed to the api method
*/
public $parameters = array();
/**
* @var array information on parameters in the form of array(name => index)
*/
public $arguments = array();
/**
* @var array default values for parameters if any
* in the form of array(index => value)
*/
public $defaults = array();
/**
* @var array key => value pair of method meta information
*/
public $metadata = array();
/**
* @var int access level
* 0 - @public - available for all
* 1 - @hybrid - both public and protected (enhanced info for authorized)
* 2 - @protected comment - only for authenticated users
* 3 - protected method - only for authenticated users
*/
public $accessLevel = 0;
}

View File

@@ -0,0 +1,33 @@
<?php
namespace Luracast\Restler\Data;
/**
* Convenience class for Array manipulation
*
* @category Framework
* @package Restler
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
*/
class Arr
{
/**
* Deep copy given array
*
* @param array $arr
*
* @return array
*/
public static function copy(array $arr)
{
$copy = array();
foreach ($arr as $key => $value) {
if (is_array($value)) $copy[$key] = static::copy($value);
else if (is_object($value)) $copy[$key] = clone $value;
else $copy[$key] = $value;
}
return $copy;
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace Luracast\Restler\Data;
use Exception;
/**
* Invalid Exception
*
* @category Framework
* @package Restler
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class Invalid extends Exception
{
}

View File

@@ -0,0 +1,157 @@
<?php
namespace Luracast\Restler\Data;
/**
* Convenience class that converts the given object
* in to associative array
*
* @category Framework
* @package Restler
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class Object
{
/**
* @var bool|string|callable
*/
public static $stringEncoderFunction = false;
/**
* @var bool|string|callable
*/
public static $numberEncoderFunction = false;
/**
* @var array key value pairs for fixing value types using functions.
* For example
*
* 'id'=>'intval' will make sure all values of the id properties
* will be converted to integers intval function
* 'password'=> null will remove all the password entries
*/
public static $fix = array();
/**
* @var string character that is used to identify sub objects
*
* For example
*
* when Object::$separatorChar = '.';
*
* array('my.object'=>true) will result in
*
* array(
* 'my'=>array('object'=>true)
* );
*/
public static $separatorChar = null;
/**
* @var bool set it to true when empty arrays, blank strings, null values
* to be automatically removed from response
*/
public static $removeEmpty = false;
/**
* @var bool set it to true to remove all null values from the result
*/
public static $removeNull = false;
/**
* Convenience function that converts the given object
* in to associative array
*
* @static
*
* @param mixed $object that needs to be converted
*
* @param bool $forceObjectTypeWhenEmpty when set to true outputs
* actual type (array or
* object) rather than
* always an array when the
* array/object is empty
*
* @return array
*/
public static function toArray($object,
$forceObjectTypeWhenEmpty = false)
{
//if ($object instanceof JsonSerializable) { //wont work on PHP < 5.4
if (is_object($object)) {
if (method_exists($object, 'jsonSerialize')) {
$object = $object->jsonSerialize();
} elseif (method_exists($object, '__sleep')) {
$properties = $object->__sleep();
$array = array();
foreach ($properties as $key) {
$value = self::toArray($object->{$key},
$forceObjectTypeWhenEmpty);
if (self::$stringEncoderFunction && is_string($value)) {
$value = self::$stringEncoderFunction ($value);
} elseif (self::$numberEncoderFunction && is_numeric($value)) {
$value = self::$numberEncoderFunction ($value);
}
$array [$key] = $value;
}
return $array;
}
}
if (is_array($object) || is_object($object)) {
$count = 0;
$array = array();
foreach ($object as $key => $value) {
if (
is_string(self::$separatorChar) &&
false !== strpos($key, self::$separatorChar)
) {
list($key, $obj) = explode(self::$separatorChar, $key, 2);
$object[$key][$obj] = $value;
$value = $object[$key];
}
if (self::$removeEmpty && empty($value) && !is_numeric($value) && !is_bool($value)) {
continue;
} elseif (self::$removeNull && is_null($value)) {
continue;
}
if (array_key_exists($key, self::$fix)) {
if (isset(self::$fix[$key])) {
$value = call_user_func(self::$fix[$key], $value);
} else {
continue;
}
}
$value = self::toArray($value, $forceObjectTypeWhenEmpty);
if (self::$stringEncoderFunction && is_string($value)) {
$value = self::$encoderFunctionName ($value);
} elseif (self::$numberEncoderFunction && is_numeric($value)) {
$value = self::$numberEncoderFunction ($value);
}
$array [$key] = $value;
$count++;
}
return $forceObjectTypeWhenEmpty && $count == 0 ? $object : $array;
}
return $object;
}
public function __get($name)
{
isset(self::$fix[$name]) ? self::$fix[$name] : null;
}
public function __set($name, $function)
{
self::$fix[$name] = $function;
}
public function __isset($name)
{
return isset(self::$fix[$name]);
}
public function __unset($name)
{
unset(self::$fix[$name]);
}
}

View File

@@ -0,0 +1,84 @@
<?php
namespace Luracast\Restler\Data;
/**
* Convenience class for String manipulation
*
* @category Framework
* @package Restler
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
*/
class String
{
/**
* Given haystack contains the needle or not?
*
* @param string $haystack
* @param string $needle
* @param bool $caseSensitive
*
* @return bool
*/
public static function contains($haystack, $needle, $caseSensitive = true)
{
if (empty($needle))
return true;
return $caseSensitive
? strpos($haystack, $needle) !== false
: stripos($haystack, $needle) !== false;
}
/**
* Given haystack begins with the needle or not?
*
* @param string $haystack
* @param string $needle
*
* @return bool
*/
public static function beginsWith($haystack, $needle)
{
$length = strlen($needle);
return (substr($haystack, 0, $length) === $needle);
}
/**
* Given haystack ends with the needle or not?
*
* @param string $haystack
* @param string $needle
*
* @return bool
*/
public static function endsWith($haystack, $needle)
{
$length = strlen($needle);
if ($length == 0) {
return true;
}
return (substr($haystack, -$length) === $needle);
}
/**
* Convert camelCased or underscored string in to a title
*
* @param string $name
*
* @return string
*/
public static function title($name)
{
return
ucwords(
preg_replace(
array('/(?<=[^A-Z])([A-Z])/', '/(?<=[^0-9])([0-9])/', '/(_)/'),
array(' $0', ' $0', ' '),
$name
)
);
}
}

View File

@@ -0,0 +1,273 @@
<?php
namespace Luracast\Restler\Data;
use Luracast\Restler\CommentParser;
use Luracast\Restler\Util;
/**
* ValueObject for validation information. An instance is created and
* populated by Restler to pass it to iValidate implementing classes for
* validation
*
* @category Framework
* @package Restler
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class ValidationInfo implements iValueObject
{
/**
* @var mixed given value for the parameter
*/
public $value;
/**
* @var string proper name for given parameter
*/
public $label;
/**
* @var string html element that can be used to represent the parameter for
* input
*/
public $field;
/**
* @var mixed default value for the parameter
*/
public $default;
/**
* Name of the variable being validated
*
* @var string variable name
*/
public $name;
/**
* @var bool is it required or not
*/
public $required;
/**
* @var string body or header or query where this parameter is coming from
* in the http request
*/
public $from;
/**
* Data type of the variable being validated.
* It will be mostly string
*
* @var string|array multiple types are specified it will be of
* type array otherwise it will be a string
*/
public $type;
/**
* When the type is array, this field is used to define the type of the
* contents of the array
*
* @var string|null when all the items in an array are of certain type, we
* can set this property. It will be null if the items can be of any type
*/
public $contentType;
/**
* Should we attempt to fix the value?
* When set to false validation class should throw
* an exception or return false for the validate call.
* When set to true it will attempt to fix the value if possible
* or throw an exception or return false when it cant be fixed.
*
* @var boolean true or false
*/
public $fix = false;
/**
* @var array of children to be validated
*/
public $children = null;
// ==================================================================
//
// VALUE RANGE
//
// ------------------------------------------------------------------
/**
* Given value should match one of the values in the array
*
* @var array of choices to match to
*/
public $choice;
/**
* If the type is string it will set the lower limit for length
* else will specify the lower limit for the value
*
* @var number minimum value
*/
public $min;
/**
* If the type is string it will set the upper limit limit for length
* else will specify the upper limit for the value
*
* @var number maximum value
*/
public $max;
// ==================================================================
//
// REGEX VALIDATION
//
// ------------------------------------------------------------------
/**
* RegEx pattern to match the value
*
* @var string regular expression
*/
public $pattern;
// ==================================================================
//
// CUSTOM VALIDATION
//
// ------------------------------------------------------------------
/**
* Rules specified for the parameter in the php doc comment.
* It is passed to the validation method as the second parameter
*
* @var array custom rule set
*/
public $rules;
/**
* Specifying a custom error message will override the standard error
* message return by the validator class
*
* @var string custom error response
*/
public $message;
// ==================================================================
//
// METHODS
//
// ------------------------------------------------------------------
/**
* Name of the method to be used for validation.
* It will be receiving two parameters $input, $rules (array)
*
* @var string validation method name
*/
public $method;
/**
* Instance of the API class currently being called. It will be null most of
* the time. Only when method is defined it will contain an instance.
* This behavior is for lazy loading of the API class
*
* @var null|object will be null or api class instance
*/
public $apiClassInstance = null;
public static function numericValue($value)
{
return ( int )$value == $value
? ( int )$value
: floatval($value);
}
public static function arrayValue($value)
{
return is_array($value) ? $value : array(
$value
);
}
public static function stringValue($value, $glue = ',')
{
return is_array($value)
? implode($glue, $value)
: ( string )$value;
}
public static function booleanValue($value)
{
return is_bool($value)
? $value
: $value !== 'false';
}
public static function filterArray(array $data, $keepNumericKeys)
{
$r = array();
foreach ($data as $key => $value) {
if (is_numeric($key)) {
if ($keepNumericKeys) {
$r[$key] = $value;
}
} elseif (!$keepNumericKeys) {
$r[$key] = $value;
}
}
return $r;
}
public function __toString()
{
return ' new ValidationInfo() ';
}
private function getProperty(array &$from, $property)
{
$p = Util::nestedValue($from, $property);
unset($from[$property]);
$p2 = Util::nestedValue(
$from, CommentParser::$embeddedDataName, $property
);
unset($from[CommentParser::$embeddedDataName][$property]);
if ($property == 'type' && $p == 'array' && $p2) {
$this->contentType = $p2;
return $p;
}
$r = is_null($p2) ? (is_null($p) ? null : $p) : $p2;
if (!is_null($r)) {
if ($property == 'min' || $property == 'max') {
return static::numericValue($r);
} elseif ($property == 'required' || $property == 'fix') {
return static::booleanValue($r);
} elseif ($property == 'choice') {
return static::arrayValue($r);
} elseif ($property == 'pattern') {
return static::stringValue($r);
}
}
return $r;
}
public function __construct(array $info)
{
$properties = get_object_vars($this);
unset($properties['contentType']);
foreach ($properties as $property => $value) {
$this->{$property} = $this->getProperty($info, $property);
}
$inner = Util::nestedValue($info, 'properties');
$this->rules = !empty($inner) ? $inner + $info : $info;
unset($this->rules['properties']);
if (is_string($this->type) && $this->type == 'integer') {
$this->type = 'int';
}
}
/**
* Magic Method used for creating instance at run time
*/
public static function __set_state(array $info)
{
$o = new self ($info);
return $o;
}
}

View File

@@ -0,0 +1,626 @@
<?php
namespace Luracast\Restler\Data;
use Luracast\Restler\CommentParser;
use Luracast\Restler\Format\HtmlFormat;
use Luracast\Restler\RestException;
use Luracast\Restler\Scope;
use Luracast\Restler\Util;
/**
* Default Validator class used by Restler. It can be replaced by any
* iValidate implementing class by setting Defaults::$validatorClass
*
* @category Framework
* @package Restler
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class Validator implements iValidate
{
public static $holdException = false;
public static $exceptions = array();
/**
* Validate alphabetic characters.
*
* Check that given value contains only alphabetic characters.
*
* @param $input
* @param ValidationInfo $info
*
* @return string
*
* @throws Invalid
*/
public static function alpha($input, ValidationInfo $info = null)
{
if (ctype_alpha($input)) {
return $input;
}
if ($info && $info->fix) {
//remove non alpha characters
return preg_replace("/[^a-z]/i", "", $input);
}
throw new Invalid('Expecting only alphabetic characters.');
}
/**
* Validate alpha numeric characters.
*
* Check that given value contains only alpha numeric characters.
*
* @param $input
* @param ValidationInfo $info
*
* @return string
*
* @throws Invalid
*/
public static function alphanumeric($input, ValidationInfo $info = null)
{
if (ctype_alnum($input)) {
return $input;
}
if ($info && $info->fix) {
//remove non alpha numeric and space characters
return preg_replace("/[^a-z0-9 ]/i", "", $input);
}
throw new Invalid('Expecting only alpha numeric characters.');
}
/**
* Validate printable characters.
*
* Check that given value contains only printable characters.
*
* @param $input
* @param ValidationInfo $info
*
* @return string
*
* @throws Invalid
*/
public static function printable($input, ValidationInfo $info = null)
{
if (ctype_print($input)) {
return $input;
}
if ($info && $info->fix) {
//remove non printable characters
return preg_replace('/[\x00-\x1F\x80-\xFF]/', '', $input);
}
throw new Invalid('Expecting only printable characters.');
}
/**
* Validate hexadecimal digits.
*
* Check that given value contains only hexadecimal digits.
*
* @param $input
* @param ValidationInfo $info
*
* @return string
*
* @throws Invalid
*/
public static function hex($input, ValidationInfo $info = null)
{
if (ctype_xdigit($input)) {
return $input;
}
throw new Invalid('Expecting only hexadecimal digits.');
}
/**
* Validate Telephone number
*
* Check if the given value is numeric with or without a `+` prefix
*
* @param $input
* @param ValidationInfo $info
*
* @return string
*
* @throws Invalid
*/
public static function tel($input, ValidationInfo $info = null)
{
if (is_numeric($input) && '-' != substr($input, 0, 1)) {
return $input;
}
throw new Invalid('Expecting phone number, a numeric value ' .
'with optional `+` prefix');
}
/**
* Validate Email
*
* Check if the given string is a valid email
*
* @param String $input
* @param ValidationInfo $info
*
* @return string
* @throws Invalid
*/
public static function email($input, ValidationInfo $info = null)
{
$r = filter_var($input, FILTER_VALIDATE_EMAIL);
if ($r) {
return $r;
} elseif ($info && $info->fix) {
$r = filter_var($input, FILTER_SANITIZE_EMAIL);
return static::email($r);
}
throw new Invalid('Expecting email in `name@example.com` format');
}
/**
* Validate IP Address
*
* Check if the given string is a valid ip address
*
* @param String $input
* @param ValidationInfo $info
*
* @return string
* @throws Invalid
*/
public static function ip($input, ValidationInfo $info = null)
{
$r = filter_var($input, FILTER_VALIDATE_IP);
if ($r)
return $r;
throw new Invalid('Expecting IP address in IPV6 or IPV4 format');
}
/**
* Validate Url
*
* Check if the given string is a valid url
*
* @param String $input
* @param ValidationInfo $info
*
* @return string
* @throws Invalid
*/
public static function url($input, ValidationInfo $info = null)
{
$r = filter_var($input, FILTER_VALIDATE_URL);
if ($r) {
return $r;
} elseif ($info && $info->fix) {
$r = filter_var($input, FILTER_SANITIZE_URL);
return static::url($r);
}
throw new Invalid('Expecting url in `http://example.com` format');
}
/**
* MySQL Date
*
* Check if the given string is a valid date in YYYY-MM-DD format
*
* @param String $input
* @param ValidationInfo $info
*
* @return string
* @throws Invalid
*/
public static function date($input, ValidationInfo $info = null)
{
if (
preg_match(
'#^(?P<year>\d{2}|\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})$#',
$input,
$date
)
&& checkdate($date['month'], $date['day'], $date['year'])
) {
return $input;
}
throw new Invalid(
'Expecting date in `YYYY-MM-DD` format, such as `'
. date("Y-m-d") . '`'
);
}
/**
* MySQL DateTime
*
* Check if the given string is a valid date and time in YYY-MM-DD HH:MM:SS format
*
* @param String $input
* @param ValidationInfo $info
*
* @return string
* @throws Invalid
*/
public static function datetime($input, ValidationInfo $info = null)
{
if (
preg_match('/^(?P<year>19\d\d|20\d\d)\-(?P<month>0[1-9]|1[0-2])\-' .
'(?P<day>0\d|[1-2]\d|3[0-1]) (?P<h>0\d|1\d|2[0-3]' .
')\:(?P<i>[0-5][0-9])\:(?P<s>[0-5][0-9])$/',
$input, $date)
&& checkdate($date['month'], $date['day'], $date['year'])
) {
return $input;
}
throw new Invalid(
'Expecting date and time in `YYYY-MM-DD HH:MM:SS` format, such as `'
. date("Y-m-d H:i:s") . '`'
);
}
/**
* Alias for Time
*
* Check if the given string is a valid time in HH:MM:SS format
*
* @param String $input
* @param ValidationInfo $info
*
* @return string
* @throws Invalid
*/
public static function time24($input, ValidationInfo $info = null)
{
return static::time($input, $info);
}
/**
* Time
*
* Check if the given string is a valid time in HH:MM:SS format
*
* @param String $input
* @param ValidationInfo $info
*
* @return string
* @throws Invalid
*/
public static function time($input, ValidationInfo $info = null)
{
if (preg_match('/^([01]?[0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]$/', $input)) {
return $input;
}
throw new Invalid(
'Expecting time in `HH:MM:SS` format, such as `'
. date("H:i:s") . '`'
);
}
/**
* Time in 12 hour format
*
* Check if the given string is a valid time 12 hour format
*
* @param String $input
* @param ValidationInfo $info
*
* @return string
* @throws Invalid
*/
public static function time12($input, ValidationInfo $info = null)
{
if (preg_match(
'/^([1-9]|1[0-2]|0[1-9]){1}(:[0-5][0-9])?\s?([aApP][mM]{1})?$/',
$input)
) {
return $input;
}
throw new Invalid(
'Expecting time in 12 hour format, such as `08:00AM` and `10:05:11`'
);
}
/**
* Unix Timestamp
*
* Check if the given value is a valid timestamp
*
* @param String $input
* @param ValidationInfo $info
*
* @return int
* @throws Invalid
*/
public static function timestamp($input, ValidationInfo $info = null)
{
if ((string)(int)$input == $input
&& ($input <= PHP_INT_MAX)
&& ($input >= ~PHP_INT_MAX)
) {
return (int)$input;
}
throw new Invalid('Expecting unix timestamp, such as ' . time());
}
/**
* 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;
}
}
}

View File

@@ -0,0 +1,61 @@
<?php
namespace Luracast\Restler\Data;
/**
* ValueObject base class, you may use this class to create your
* iValueObjects quickly
*
* @category Framework
* @package Restler
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class ValueObject implements iValueObject
{
public function __toString()
{
return ' new ' . get_called_class() . '() ';
}
public static function __set_state(array $properties)
{
$class = get_called_class();
$instance = new $class ();
$vars = get_object_vars($instance);
foreach ($properties as $property => $value) {
if (property_exists($instance, $property)) {
// see if the property is accessible
if (array_key_exists($property, $vars)) {
$instance->{$property} = $value;
} else {
$method = 'set' . ucfirst($property);
if (method_exists($instance, $method)) {
call_user_func(array(
$instance,
$method
), $value);
}
}
}
}
return $instance;
}
public function __toArray()
{
$r = get_object_vars($this);
$methods = get_class_methods($this);
foreach ($methods as $m) {
if (substr($m, 0, 3) == 'get') {
$r [lcfirst(substr($m, 3))] = @$this->{$m} ();
}
}
return $r;
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace Luracast\Restler\Data;
/**
* Validation classes should implement this interface
*
* @category Framework
* @package Restler
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
interface iValidate {
/**
* method used for validation.
*
* @param mixed $input
* data that needs to be validated
* @param ValidationInfo $info
* information to be used for validation
* @return boolean false in case of failure or fixed value in the expected
* type
* @throws \Luracast\Restler\RestException 400 with information about the
* failed
* validation
*/
public static function validate($input, ValidationInfo $info);
}

View File

@@ -0,0 +1,39 @@
<?php
namespace Luracast\Restler\Data;
/**
* Restler is using many ValueObjects across to make it easy for the developers
* to use them with the help of code hinting etc.,
*
* @category Framework
* @package Restler
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
interface iValueObject
{
/**
* This static method is called for creating an instance of the class by
* passing the initiation values as an array.
*
* @static
* @abstract
*
* @param array $properties
*
* @return iValueObject
*/
public static function __set_state(array $properties);
/**
* This method provides a string representation for the instance
*
* @return string
*/
public function __toString();
}

View File

@@ -0,0 +1,360 @@
<?php
namespace Luracast\Restler;
use Luracast\Restler\Data\ValidationInfo;
use Luracast\Restler\Data\Validator;
/**
* Static class to hold all restler defaults, change the values to suit your
* needs in the gateway file (index.php), you may also allow the api users to
* change them per request by adding the properties to Defaults::$overridables
*
* @category Framework
* @package Restler
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class Defaults
{
// ==================================================================
//
// Class Mappings
//
// ------------------------------------------------------------------
/**
* @var string of name of the class that implements
* \Luracast\Restler\iCache the cache class to be used
*/
public static $cacheClass = 'Luracast\\Restler\\HumanReadableCache';
/**
* @var string full path of the directory where all the generated files will
* be kept. When set to null (default) it will use the cache folder that is
* in the same folder as index.php (gateway)
*/
public static $cacheDirectory;
/**
* @var string of name of the class that implements
* \Luracast\Restler\Data\iValidate the validator class to be used
*/
public static $validatorClass = 'Luracast\\Restler\\Data\\Validator';
/**
* @var string name of the class that implements \Luracast\Restler\iCompose
* the class to be used to compose the response
*/
public static $composeClass = 'Luracast\\Restler\\Compose';
// ==================================================================
//
// Routing
//
// ------------------------------------------------------------------
/**
* @var bool should auto routing for public and protected api methods
* should be enabled by default or not. Set this to false to get
* Restler 1.0 style behavior
*/
public static $autoRoutingEnabled = true;
/**
* @var boolean avoids creating multiple routes that can increase the
* ambiguity when set to true. when a method parameter is optional it is
* not mapped to the url and should only be used in request body or as
* query string `/resource?id=value`. When a parameter is required and is
* scalar, it will be mapped as part of the url `/resource/{id}`
*/
public static $smartAutoRouting = true;
/**
* @var boolean enables more ways of finding the parameter data in the request.
* If you need backward compatibility with Restler 2 or below turn this off
*/
public static $smartParameterParsing = true;
// ==================================================================
//
// API Version Management
//
// ------------------------------------------------------------------
/**
* @var null|string name that is used for vendor specific media type and
* api version using the Accept Header for example
* application/vnd.{vendor}-v1+json
*
* Keep this null if you do not want to use vendor MIME for specifying api version
*/
public static $apiVendor = null;
/**
* @var bool set it to true to force vendor specific MIME for versioning.
* It will be automatically set to true when Defaults::$vendor is not
* null and client is requesting for the custom MIME type
*/
public static $useVendorMIMEVersioning = false;
/**
* @var bool set it to true to use enableUrl based versioning
*/
public static $useUrlBasedVersioning = false;
// ==================================================================
//
// Request
//
// ------------------------------------------------------------------
/**
* @var string name to be used for the method parameter to capture the
* entire request data
*/
public static $fullRequestDataName = 'request_data';
/**
* @var string name of the property that can sent through $_GET or $_POST to
* override the http method of the request. Set it to null or
* blank string to disable http method override through request
* parameters.
*/
public static $httpMethodOverrideProperty = 'http_method';
/**
* @var bool should auto validating api parameters should be enabled by
* default or not. Set this to false to avoid validation.
*/
public static $autoValidationEnabled = true;
/**
* @var string name of the class that implements iUser interface to identify
* the user for caching purposes
*/
public static $userIdentifierClass = 'Luracast\\Restler\\User';
// ==================================================================
//
// Response
//
// ------------------------------------------------------------------
/**
* @var bool HTTP status codes are set on all responses by default.
* Some clients (like flash, mobile) have trouble dealing with non-200
* status codes on error responses.
*
* You can set it to true to force a HTTP 200 status code on all responses,
* even when errors occur. If you suppress status codes, look for an error
* response to determine if an error occurred.
*/
public static $suppressResponseCode = false;
public static $supportedCharsets = array('utf-8', 'iso-8859-1');
public static $supportedLanguages = array('en', 'en-US');
public static $charset = 'utf-8';
public static $language = 'en';
/**
* @var bool when set to true, it will exclude the response body
*/
public static $emptyBodyForNullResponse = true;
/**
* @var bool enables CORS support
*/
public static $crossOriginResourceSharing = false;
public static $accessControlAllowOrigin = '*';
public static $accessControlAllowMethods =
'GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD';
// ==================================================================
//
// Header
//
// ------------------------------------------------------------------
/**
* @var array default Cache-Control template that used to set the
* Cache-Control header and has two values, first one is used when
* Defaults::$headerExpires is 0 and second one when it has some time
* value specified. When only one value is specified it will be used for
* both cases
*/
public static $headerCacheControl = array(
'no-cache, must-revalidate',
/* "public, " or "private, " will be prepended based on api method
* called (public or protected)
*/
'max-age={expires}, must-revalidate',
);
/**
* @var int sets the content to expire immediately when set to zero
* alternatively you can specify the number of seconds the content will
* expire. This setting can be altered at api level using php doc comment
* with @expires numOfSeconds
*/
public static $headerExpires = 0;
// ==================================================================
//
// Access Control
//
// ------------------------------------------------------------------
/**
* @var null|callable if the api methods are under access control mechanism
* you can attach a function here that returns true or false to determine
* visibility of a protected api method. this function will receive method
* info as the only parameter.
*/
public static $accessControlFunction = null;
/**
* @var int set the default api access mode
* value of 0 = public api
* value of 1 = hybrid api using `@access hybrid` comment
* value of 2 = protected api using `@access protected` comment
* value of 3 = protected api using `protected function` method
*/
public static $apiAccessLevel = 0;
/**
* @var string authentication method to be called in iAuthenticate
* Interface
*/
public static $authenticationMethod = '__isAllowed';
/**
* @var int time in milliseconds for bandwidth throttling,
* which is the minimum response time for each api request. You can
* change it per api method by setting `@throttle 3000` in php doc
* comment either at the method level or class level
*/
public static $throttle = 0;
// ==================================================================
//
// Overrides for API User
//
// ------------------------------------------------------------------
/**
* @var array use 'alternativeName'=> 'actualName' to set alternative
* names that can be used to represent the api method parameters and/or
* static properties of Defaults
*/
public static $aliases = array(
/**
* suppress_response_codes=true as an URL parameter to force
* a HTTP 200 status code on all responses
*/
'suppress_response_codes' => 'suppressResponseCode',
);
/**
* @var array determines the defaults that can be overridden by the api
* user by passing them as URL parameters
*/
public static $overridables = array(
'suppressResponseCode',
);
/**
* @var array contains validation details for defaults to be used when
* set through URL parameters
*/
public static $validation = array(
'suppressResponseCode' => array('type' => 'bool'),
'headerExpires' => array('type' => 'int', 'min' => 0),
);
// ==================================================================
//
// Overrides API Developer
//
// ------------------------------------------------------------------
/**
* @var array determines what are the phpdoc comment tags that will
* override the Defaults here with their values
*/
public static $fromComments = array(
/**
* use PHPDoc comments such as the following
* `
*
* @cache no-cache, must-revalidate` to set the Cache-Control header
* for a specific api method
*/
'cache' => 'headerCacheControl',
/**
* use PHPDoc comments such as the following
* `
*
* @expires 50` to set the Expires header
* for a specific api method
*/
'expires' => 'headerExpires',
/**
* use PHPDoc comments such as the following
* `
*
* @throttle 300`
* to set the bandwidth throttling for 300 milliseconds
* for a specific api method
*/
'throttle' => 'throttle',
/**
* enable or disable smart auto routing from method comments
* this one is hardwired so cant be turned off
* it is placed here just for documentation purpose
*/
'smart-auto-routing' => 'smartAutoRouting',
);
// ==================================================================
//
// Util
//
// ------------------------------------------------------------------
/**
* Use this method to set value to a static properly of Defaults when
* you want to make sure only proper values are taken in with the help of
* validation
*
* @static
*
* @param string $name name of the static property
* @param mixed $value value to set the property to
*
* @return bool
*/
public static function setProperty($name, $value)
{
if (!property_exists(__CLASS__, $name)) return false;
if (@is_array(Defaults::$validation[$name])) {
$info = new ValidationInfo(Defaults::$validation[$name]);
$value = Validator::validate($value, $info);
}
Defaults::$$name = $value;
return true;
}
}

View File

@@ -0,0 +1,98 @@
<?php
namespace Luracast\Restler;
/**
* Static event broadcasting system for Restler
*
* @category Framework
* @package Restler
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
use Closure;
class EventDispatcher
{
private $listeners = array();
protected static $_waitList = array();
public static $self;
protected $events = array();
public function __construct() {
static::$self = $this;
if (!empty(static::$_waitList)) {
foreach (static::$_waitList as $param) {
call_user_func_array(array($this,$param[0]), $param[1]);
}
}
}
public static function __callStatic($eventName, $params)
{
if (0 === strpos($eventName, 'on')) {
if(static::$self){
return call_user_func_array(array(static::$self, $eventName), $params);
}
static::$_waitList[] = func_get_args();
return false;
}
}
public function __call($eventName, $params)
{
if (0 === strpos($eventName, 'on')) {
if (!@is_array($this->listeners[$eventName]))
$this->listeners[$eventName] = array();
$this->listeners[$eventName][] = $params[0];
}
return $this;
}
public static function addListener($eventName, Closure $callback)
{
return static::$eventName($callback);
}
public function on(array $eventHandlers)
{
for (
$count = count($eventHandlers),
$events = array_map(
'ucfirst',
$keys = array_keys(
$eventHandlers = array_change_key_case(
$eventHandlers,
CASE_LOWER
)
)
),
$i = 0;
$i < $count;
call_user_func(
array($this, "on{$events[$i]}"),
$eventHandlers[$keys[$i++]]
)
);
}
/**
* Fire an event to notify all listeners
*
* @param string $eventName name of the event
* @param array $params event related data
*/
protected function dispatch($eventName, array $params = array())
{
$this->events[] = $eventName;
$params = func_get_args();
$eventName = 'on'.ucfirst(array_shift($params));
if (isset($this->listeners[$eventName]))
foreach ($this->listeners[$eventName] as $callback)
call_user_func_array($callback, $params);
}
}

View File

@@ -0,0 +1,178 @@
<?php
namespace Luracast\Restler\Filter;
use Luracast\Restler\Defaults;
use Luracast\Restler\iFilter;
use Luracast\Restler\iUseAuthentication;
use Luracast\Restler\RestException;
/**
* Describe the purpose of this class/interface/trait
*
* @category Framework
* @package restler
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class RateLimit implements iFilter, iUseAuthentication
{
/**
* @var \Luracast\Restler\Restler;
*/
public $restler;
/**
* @var int
*/
public static $usagePerUnit = 1200;
/**
* @var int
*/
public static $authenticatedUsagePerUnit = 5000;
/**
* @var string
*/
public static $unit = 'hour';
/**
* @var string group the current api belongs to
*/
public static $group = 'common';
protected static $units = array(
'second' => 1,
'minute' => 60,
'hour' => 3600, // 60*60 seconds
'day' => 86400, // 60*60*24 seconds
'week' => 604800, // 60*60*24*7 seconds
'month' => 2592000, // 60*60*24*30 seconds
);
/**
* @var array all paths beginning with any of the following will be excluded
* from documentation
*/
public static $excludedPaths = array('resources');
/**
* @param string $unit
* @param int $usagePerUnit
* @param int $authenticatedUsagePerUnit set it to false to give unlimited access
*
* @throws \InvalidArgumentException
* @return void
*/
public static function setLimit(
$unit, $usagePerUnit, $authenticatedUsagePerUnit = null
)
{
static::$unit = $unit;
static::$usagePerUnit = $usagePerUnit;
static::$authenticatedUsagePerUnit =
is_null($authenticatedUsagePerUnit) ? $usagePerUnit : $authenticatedUsagePerUnit;
}
public function __isAllowed()
{
if (static::$authenticatedUsagePerUnit
== static::$usagePerUnit
) return $this->check();
return null;
}
public function __setAuthenticationStatus($isAuthenticated = false)
{
header('X-Auth-Status: ' . ($isAuthenticated ? 'true' : 'false'));
$this->check($isAuthenticated);
}
private static function validate($unit)
{
if (!isset(static::$units[$unit]))
throw new \InvalidArgumentException(
'Rate Limit time unit should be '
. implode('|', array_keys(static::$units)) . '.'
);
}
private function check($isAuthenticated = false)
{
$path = $this->restler->url;
foreach (static::$excludedPaths as $exclude) {
if (empty($exclude) && empty($path)) {
return true;
} elseif (0 === strpos($path, $exclude)) {
return true;
}
}
static::validate(static::$unit);
$timeUnit = static::$units[static::$unit];
$maxPerUnit = $isAuthenticated
? static::$authenticatedUsagePerUnit
: static::$usagePerUnit;
if ($maxPerUnit) {
$user = Defaults::$userIdentifierClass;
if (!method_exists($user, 'getUniqueIdentifier')) {
throw new \UnexpectedValueException('`Defaults::$userIdentifierClass` must implement `iIdentifyUser` interface');
}
$id = "RateLimit_" . $maxPerUnit . '_per_' . static::$unit
. '_for_' . static::$group
. '_' . $user::getUniqueIdentifier();
$lastRequest = $this->restler->cache->get($id, true)
? : array('time' => 0, 'used' => 0);
$time = $lastRequest['time'];
$diff = time() - $time; # in seconds
$used = $lastRequest['used'];
header("X-RateLimit-Limit: $maxPerUnit per " . static::$unit);
if ($diff >= $timeUnit) {
$used = 1;
$time = time();
} elseif ($used >= $maxPerUnit) {
header("X-RateLimit-Remaining: 0");
$wait = $timeUnit - $diff;
sleep(1);
throw new RestException(429,
'Rate limit of ' . $maxPerUnit . ' request' .
($maxPerUnit > 1 ? 's' : '') . ' per '
. static::$unit . ' exceeded. Please wait for '
. static::duration($wait) . '.'
);
} else {
$used++;
}
$remainingPerUnit = $maxPerUnit - $used;
header("X-RateLimit-Remaining: $remainingPerUnit");
$this->restler->cache->set($id,
array('time' => $time, 'used' => $used));
}
return true;
}
private function duration($secs)
{
$units = array(
'week' => (int)($secs / 86400 / 7),
'day' => $secs / 86400 % 7,
'hour' => $secs / 3600 % 24,
'minute' => $secs / 60 % 60,
'second' => $secs % 60);
$ret = array();
//$unit = 'days';
foreach ($units as $k => $v) {
if ($v > 0) {
$ret[] = $v > 1 ? "$v {$k}s" : "$v $k";
//$unit = $k;
}
}
$i = count($ret) - 1;
if ($i) {
$ret[$i] = 'and ' . $ret[$i];
}
return implode(' ', $ret); //." $unit.";
}
}

View File

@@ -0,0 +1,146 @@
<?php
namespace Luracast\Restler;
use Luracast\Restler\Format\HtmlFormat;
/**
* Storing and retrieving a message or array of key value pairs for one time use using $_SESSION
*
* They are typically used in view templates when using HtmlFormat
*
* @category Framework
* @package Restler
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class Flash //implements \JsonSerializable
{
const SUCCESS = 'success';
const INFO = 'info';
const WARNING = 'warning';
const DANGER = 'danger';
/**
* @var Flash
*/
private static $instance;
private $usedOnce = false;
/**
* Flash a success message to user
*
* @param string $message
* @param string $header
*
* @return Flash
*/
public static function success($message, $header = '')
{
return static::message($message, $header, Flash::SUCCESS);
}
/**
* Flash a info message to user
*
* @param string $message
* @param string $header
*
* @return Flash
*/
public static function info($message, $header = '')
{
return static::message($message, $header, Flash::INFO);
}
/**
* Flash a warning message to user
*
* @param string $message
* @param string $header
*
* @return Flash
*/
public static function warning($message, $header = '')
{
return static::message($message, $header, Flash::WARNING);
}
/**
* Flash a error message to user
*
* @param string $message
* @param string $header
*
* @return Flash
*/
public static function danger($message, $header = '')
{
return static::message($message, $header, Flash::DANGER);
}
/**
* Flash a message to user
*
* @param string $text message text
* @param string $header
* @param string $type
*
* @return Flash
*/
public static function message($text, $header = '', $type = Flash::WARNING)
{
return static::set(array('message' => $text, 'header' => $header, 'type' => $type));
}
/**
* Set some data for one time use
*
* @param array $data array of key value pairs {@type associative}
*
* @return Flash
*/
public static function set(array $data)
{
if (!static::$instance)
static::$instance = new Flash();
if (!isset($_SESSION['flash']))
$_SESSION['flash'] = array();
$_SESSION['flash'] += $data;
HtmlFormat::$data['flash'] = static::$instance;
return static::$instance;
}
public function __get($name)
{
$this->usedOnce = true;
return Util::nestedValue($_SESSION, 'flash', $name);
}
public function __isset($name)
{
return !is_null(Util::nestedValue($_SESSION, 'flash', $name));
}
public function __destruct()
{
if ($this->usedOnce)
unset($_SESSION['flash']);
}
/**
* Specify data which should be serialized to JSON
* @link http://php.net/manual/en/jsonserializable.jsonserialize.php
* @return mixed data which can be serialized by <b>json_encode</b>,
* which is a value of any type other than a resource.
*/
public function jsonSerialize()
{
$this->usedOnce = true;
return isset($_SESSION['flash'])
? $_SESSION['flash']
: array();
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace Luracast\Restler\Format;
use ZendAmf\Parser\Amf3\Deserializer;
use ZendAmf\Parser\Amf3\Serializer;
use ZendAmf\Parser\InputStream;
use ZendAmf\Parser\OutputStream;
/**
* AMF Binary Format for Restler Framework.
* Native format supported by Adobe Flash and Adobe AIR
*
* @category Framework
* @package Restler
* @subpackage format
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class AmfFormat extends Format
{
const MIME = 'application/x-amf';
const EXTENSION = 'amf';
public function encode($data, $humanReadable = false)
{
$stream = new OutputStream();
$serializer = new Serializer($stream);
$serializer->writeTypeMarker($data);
return $stream->getStream();
}
public function decode($data)
{
$stream = new InputStream(substr($data, 1));
$deserializer = new Deserializer($stream);
return $deserializer->readTypeMarker();
}
}

View File

@@ -0,0 +1,181 @@
<?php
namespace Luracast\Restler\Format;
use Luracast\Restler\Data\Object;
use Luracast\Restler\RestException;
/**
* Comma Separated Value Format
*
* @category Framework
* @package Restler
* @subpackage format
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class CsvFormat extends Format implements iDecodeStream
{
const MIME = 'text/csv';
const EXTENSION = 'csv';
public static $delimiter = ',';
public static $enclosure = '"';
public static $escape = '\\';
public static $haveHeaders = null;
/**
* Encode the given data in the csv 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
*
* @return string encoded string
*
* @throws RestException 500 on unsupported data
*/
public function encode($data, $humanReadable = false)
{
$char = Object::$separatorChar;
Object::$separatorChar = false;
$data = Object::toArray($data);
Object::$separatorChar = $char;
if (is_array($data) && array_values($data) == $data) {
//if indexed array
$lines = array();
$row = array_shift($data);
if (array_values($row) != $row) {
$lines[] = static::putRow(array_keys($row));
}
$lines[] = static::putRow(array_values($row));
foreach ($data as $row) {
$lines[] = static::putRow(array_values($row));
}
return implode(PHP_EOL, $lines) . PHP_EOL;
}
throw new RestException(
500,
'Unsupported data for ' . strtoupper(static::EXTENSION) . ' format'
);
}
protected static function putRow($data)
{
$fp = fopen('php://temp', 'r+');
fputcsv($fp, $data, static::$delimiter, static::$enclosure);
rewind($fp);
$data = fread($fp, 1048576);
fclose($fp);
return rtrim($data, PHP_EOL);
}
/**
* Decode the given data from the csv format
*
* @param string $data
* data sent from client to
* the api in the given format.
*
* @return array associative array of the parsed data
*/
public function decode($data)
{
$decoded = array();
if (empty($data)) {
return $decoded;
}
$lines = array_filter(explode(PHP_EOL, $data));
$keys = false;
$row = static::getRow(array_shift($lines));
if (is_null(static::$haveHeaders)) {
//try to guess with the given data
static::$haveHeaders = !count(array_filter($row, 'is_numeric'));
}
static::$haveHeaders ? $keys = $row : $decoded[] = $row;
while (($row = static::getRow(array_shift($lines), $keys)) !== FALSE)
$decoded [] = $row;
$char = Object::$separatorChar;
Object::$separatorChar = false;
$decoded = Object::toArray($decoded);
Object::$separatorChar = $char;
return $decoded;
}
protected static function getRow($data, $keys = false)
{
if (empty($data)) {
return false;
}
$line = str_getcsv(
$data,
static::$delimiter,
static::$enclosure,
static::$escape
);
$row = array();
foreach ($line as $key => $value) {
if (is_numeric($value))
$value = floatval($value);
if ($keys) {
if (isset($keys [$key]))
$row [$keys [$key]] = $value;
} else {
$row [$key] = $value;
}
}
if ($keys) {
for ($i = count($row); $i < count($keys); $i++) {
$row[$keys[$i]] = null;
}
}
return $row;
}
/**
* Decode the given data stream
*
* @param string $stream A stream resource with data
* sent from client to the api
* in the given format.
*
* @return array associative array of the parsed data
*/
public function decodeStream($stream)
{
$decoded = array();
$keys = false;
$row = static::getRow(stream_get_line($stream, 0, PHP_EOL));
if (is_null(static::$haveHeaders)) {
//try to guess with the given data
static::$haveHeaders = !count(array_filter($row, 'is_numeric'));
}
static::$haveHeaders ? $keys = $row : $decoded[] = $row;
while (($row = static::getRow(stream_get_line($stream, 0, PHP_EOL), $keys)) !== FALSE)
$decoded [] = $row;
$char = Object::$separatorChar;
Object::$separatorChar = false;
$decoded = Object::toArray($decoded);
Object::$separatorChar = $char;
return $decoded;
}
}

View File

@@ -0,0 +1,140 @@
<?php
namespace Luracast\Restler\Format;
/**
* Abstract class to implement common methods of iFormat
*
* @category Framework
* @package Restler
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
abstract class Format implements iFormat
{
/**
* override in the extending class
*/
const MIME = 'text/plain';
/**
* override in the extending class
*/
const EXTENSION = 'txt';
/**
* @var string charset encoding defaults to UTF8
*/
protected $charset='utf-8';
/**
* Injected at runtime
*
* @var \Luracast\Restler\Restler
*/
public $restler;
/**
* Get MIME type => Extension mappings as an associative array
*
* @return array list of mime strings for the format
* @example array('application/json'=>'json');
*/
public function getMIMEMap()
{
return array(
static::MIME => static::EXTENSION
);
}
/**
* Set the selected MIME type
*
* @param string $mime
* MIME type
*/
public function setMIME($mime)
{
//do nothing
}
/**
* Content-Type field of the HTTP header can send a charset
* parameter in the HTTP header to specify the character
* encoding of the document.
* This information is passed
* here so that Format class can encode data accordingly
* Format class may choose to ignore this and use its
* default character set.
*
* @param string $charset
* Example utf-8
*/
public function setCharset($charset)
{
$this->charset = $charset;
}
/**
* Content-Type accepted by the Format class
*
* @return string $charset Example utf-8
*/
public function getCharset()
{
return $this->charset;
}
/**
* Get selected MIME type
*/
public function getMIME()
{
return static::MIME;
}
/**
* Set the selected file extension
*
* @param string $extension
* file extension
*/
public function setExtension($extension)
{
//do nothing;
}
/**
* Get the selected file extension
*
* @return string file extension
*/
public function getExtension()
{
return static::EXTENSION;
}
/**
* @return boolean is parsing the request supported?
*/
public function isReadable()
{
return true;
}
/**
* @return boolean is composing response supported?
*/
public function isWritable()
{
return true;
}
public function __toString()
{
return $this->getExtension();
}
}

View File

@@ -0,0 +1,485 @@
<?php
namespace Luracast\Restler\Format;
use Exception;
use Illuminate\Events\Dispatcher;
use Illuminate\Filesystem\Filesystem;
use Illuminate\View\Compilers\BladeCompiler;
use Illuminate\View\Engines\CompilerEngine;
use Illuminate\View\Engines\EngineResolver;
use Illuminate\View\Factory;
use Illuminate\View\FileViewFinder;
use Illuminate\View\View;
use Luracast\Restler\Data\ApiMethodInfo;
use Luracast\Restler\Data\Object;
use Luracast\Restler\Defaults;
use Luracast\Restler\RestException;
use Luracast\Restler\Restler;
use Luracast\Restler\Scope;
use Luracast\Restler\UI\Nav;
use Luracast\Restler\Util;
/**
* Html template format
*
* @category Framework
* @package Restler
* @subpackage format
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class HtmlFormat extends Format
{
public static $mime = 'text/html';
public static $extension = 'html';
public static $view;
public static $errorView = 'debug.php';
public static $template = 'php';
public static $handleSession = true;
public static $useSmartViews = true;
/**
* @var null|string defaults to template named folder in Defaults::$cacheDirectory
*/
public static $cacheDirectory = null;
/**
* @var array global key value pair to be supplied to the templates. All
* keys added here will be available as a variable inside the template
*/
public static $data = array();
/**
* @var string set it to the location of your the view files. Defaults to
* views folder which is same level as vendor directory.
*/
public static $viewPath;
/**
* @var array template and its custom extension key value pair
*/
public static $customTemplateExtensions = array('blade' => 'blade.php');
/**
* @var bool used internally for error handling
*/
protected static $parseViewMetadata = true;
/**
* @var Restler;
*/
public $restler;
public function __construct()
{
//============ SESSION MANAGEMENT =============//
if (static::$handleSession) {
if (session_start() && isset($_SESSION['flash'])) {
static::$data['flash'] = $_SESSION['flash'];
unset($_SESSION['flash']);
}
}
if (!static::$viewPath) {
$array = explode('vendor', __DIR__, 2);
static::$viewPath = $array[0] . 'views';
}
}
public static function blade(array $data, $debug = true)
{
if (!class_exists('\Illuminate\View\View', true))
throw new RestException(500,
'Blade templates require laravel view classes to be installed using `composer install`');
$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();
}
public static function twig(array $data, $debug = true)
{
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' => static::$cacheDirectory,
'debug' => $debug,
'use_strict_variables' => $debug,
));
if ($debug)
$twig->addExtension(new \Twig_Extension_Debug());
$twig->addFunction(
new \Twig_SimpleFunction(
'form',
'Luracast\Restler\UI\Forms::get',
array('is_safe' => array('html'))
)
);
$twig->addFunction(
new \Twig_SimpleFunction(
'form_key',
'Luracast\Restler\UI\Forms::key'
)
);
$twig->addFunction(
new \Twig_SimpleFunction(
'nav',
'Luracast\Restler\UI\Nav::get'
)
);
$twig->registerUndefinedFunctionCallback(function ($name) {
if (
isset(HtmlFormat::$data[$name]) &&
is_callable(HtmlFormat::$data[$name])
) {
return new \Twig_SimpleFunction(
$name,
HtmlFormat::$data[$name]
);
}
return false;
});
$template = $twig->loadTemplate(static::getViewFile());
return $template->render($data);
}
public static function handlebar(array $data, $debug = true)
{
return static::mustache($data, $debug);
}
public static function mustache(array $data, $debug = true)
{
if (!class_exists('\Mustache_Engine', true))
throw new RestException(
500,
'Mustache/Handlebar templates require mustache classes ' .
'to be installed using `composer install`'
);
if (!isset($data['nav']))
$data['nav'] = array_values(Nav::get());
$options = array(
'loader' => new \Mustache_Loader_FilesystemLoader(
static::$viewPath,
array('extension' => static::getViewExtension())
),
'helpers' => array(
'form' => function ($text, \Mustache_LambdaHelper $m) {
$params = explode(',', $m->render($text));
return call_user_func_array(
'Luracast\Restler\UI\Forms::get',
$params
);
},
)
);
if (!$debug)
$options['cache'] = static::$cacheDirectory;
$m = new \Mustache_Engine($options);
return $m->render(static::getViewFile(), $data);
}
public static function php(array $data, $debug = true)
{
if (static::$view == 'debug')
static::$viewPath = dirname(__DIR__) . '/views';
$view = static::getViewFile(true);
if (!is_readable($view)) {
throw new RestException(
500,
"view file `$view` is not readable. " .
'Check for file presence and file permissions'
);
}
$path = static::$viewPath . DIRECTORY_SEPARATOR;
$template = function ($view) use ($data, $path) {
$form = function () {
return call_user_func_array(
'Luracast\Restler\UI\Forms::get',
func_get_args()
);
};
if (!isset($data['form']))
$data['form'] = $form;
$nav = function () {
return call_user_func_array(
'Luracast\Restler\UI\Nav::get',
func_get_args()
);
};
if (!isset($data['nav']))
$data['nav'] = $nav;
$_ = function () use ($data, $path) {
extract($data);
$args = func_get_args();
$task = array_shift($args);
switch ($task) {
case 'require':
case 'include':
$file = $path . $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:
if (isset($data[$task]) && is_callable($data[$task]))
return call_user_func_array($data[$task], $args);
}
return '';
};
extract($data);
return @include $view;
};
$value = $template($view);
if (is_string($value))
return $value;
}
/**
* 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' => Object::toArray($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)) {
throw new RestException(500, 'Unable to create cache directory `' . static::$cacheDirectory . '`');
}
}
}
if (method_exists($class = get_called_class(), $template)) {
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;
}
}
public static function getViewExtension()
{
return isset(static::$customTemplateExtensions[static::$template])
? static::$customTemplateExtensions[static::$template]
: static::$template;
}
public static function getViewFile($fullPath = false, $includeExtension = true)
{
$v = $fullPath ? static::$viewPath . '/' : '';
$v .= static::$view;
if ($includeExtension)
$v .= '.' . static::getViewExtension();
return $v;
}
private function reset()
{
static::$mime = 'text/html';
static::$extension = 'html';
static::$view = 'debug';
static::$template = 'php';
}
/**
* Decode the given data from the format
*
* @param string $data
* data sent from client to
* the api in the given format.
*
* @return array associative array of the parsed data
*
* @throws RestException
*/
public function decode($data)
{
throw new RestException(500, 'HtmlFormat is write only');
}
/**
* @return bool false as HTML format is write only
*/
public function isReadable()
{
return false;
}
/**
* Get MIME type => Extension mappings as an associative array
*
* @return array list of mime strings for the format
* @example array('application/json'=>'json');
*/
public function getMIMEMap()
{
return array(
static::$mime => static::$extension
);
}
/**
* Set the selected MIME type
*
* @param string $mime MIME type
*/
public function setMIME($mime)
{
static::$mime = $mime;
}
/**
* Get selected MIME type
*/
public function getMIME()
{
return static::$mime;
}
/**
* Get the selected file extension
*
* @return string file extension
*/
public function getExtension()
{
return static::$extension;
}
/**
* Set the selected file extension
*
* @param string $extension file extension
*/
public function setExtension($extension)
{
static::$extension = $extension;
}
}

View File

@@ -0,0 +1,48 @@
<?php
namespace Luracast\Restler\Format;
/**
* Javascript Object Notation Packaged in a method (JSONP)
*
* @category Framework
* @package Restler
* @subpackage format
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class JsFormat extends JsonFormat
{
const MIME = 'text/javascript';
const EXTENSION = 'js';
public static $callbackMethodName = 'parseResponse';
public static $callbackOverrideQueryString = 'callback';
public static $includeHeaders = true;
public function encode($data, $human_readable = false)
{
$r = array();
if (static::$includeHeaders) {
$r['meta'] = array();
foreach (headers_list() as $header) {
list($h, $v) = explode(': ', $header, 2);
$r['meta'][$h] = $v;
}
}
$r['data'] = $data;
if (isset($_GET[static::$callbackOverrideQueryString])) {
static::$callbackMethodName
= (string) $_GET[static::$callbackOverrideQueryString];
}
return static::$callbackMethodName . '('
. parent::encode($r, $human_readable) . ');';
}
public function isReadable()
{
return false;
}
}

View File

@@ -0,0 +1,210 @@
<?php
namespace Luracast\Restler\Format;
use Luracast\Restler\Data\Object;
use Luracast\Restler\RestException;
/**
* Javascript Object Notation Format
*
* @category Framework
* @package Restler
* @subpackage format
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class JsonFormat extends Format
{
/**
* @var boolean|null shim for json_encode option JSON_PRETTY_PRINT set
* it to null to use smart defaults
*/
public static $prettyPrint = null;
/**
* @var boolean|null shim for json_encode option JSON_UNESCAPED_SLASHES
* set it to null to use smart defaults
*/
public static $unEscapedSlashes = null;
/**
* @var boolean|null shim for json_encode JSON_UNESCAPED_UNICODE set it
* to null to use smart defaults
*/
public static $unEscapedUnicode = null;
/**
* @var boolean|null shim for json_decode JSON_BIGINT_AS_STRING set it to
* null to
* use smart defaults
*/
public static $bigIntAsString = null;
const MIME = 'application/json';
const EXTENSION = 'json';
public function encode($data, $humanReadable = false)
{
if (!is_null(self::$prettyPrint)) {
$humanReadable = self::$prettyPrint;
}
if (is_null(self::$unEscapedSlashes)) {
self::$unEscapedSlashes = $humanReadable;
}
if (is_null(self::$unEscapedUnicode)) {
self::$unEscapedUnicode = $this->charset == 'utf-8';
}
$options = 0;
if ((PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION >= 4) // PHP >= 5.4
|| PHP_MAJOR_VERSION > 5 // PHP >= 6.0
) {
if ($humanReadable) $options |= JSON_PRETTY_PRINT;
if (self::$unEscapedSlashes) $options |= JSON_UNESCAPED_SLASHES;
if (self::$bigIntAsString) $options |= JSON_BIGINT_AS_STRING;
if (self::$unEscapedUnicode) $options |= JSON_UNESCAPED_UNICODE;
return json_encode(
Object::toArray($data, true), $options
);
}
$result = json_encode(Object::toArray($data, true));
if ($humanReadable) $result = $this->formatJson($result);
if (self::$unEscapedUnicode) {
$result = preg_replace_callback('/\\\u(\w\w\w\w)/',
function($matches)
{
if (function_exists('mb_convert_encoding'))
{
return mb_convert_encoding(pack('H*', $matches[1]), 'UTF-8', 'UTF-16BE');
}
else
{
return iconv('UTF-16BE','UTF-8',pack('H*', $matches[1]));
}
}
, $result);
}
if (self::$unEscapedSlashes) $result = str_replace('\/', '/', $result);
return $result;
}
public function decode($data)
{
$options = 0;
if (self::$bigIntAsString) {
if ((PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION >= 4) // PHP >= 5.4
|| PHP_MAJOR_VERSION > 5 // PHP >= 6.0
) {
$options |= JSON_BIGINT_AS_STRING;
} else {
$data = preg_replace(
'/:\s*(\-?\d+(\.\d+)?([e|E][\-|\+]\d+)?)/',
': "$1"', $data
);
}
}
$decoded = json_decode($data, $options);
if (function_exists('json_last_error')) {
switch (json_last_error()) {
case JSON_ERROR_NONE :
return Object::toArray($decoded);
break;
case JSON_ERROR_DEPTH :
$message = 'maximum stack depth exceeded';
break;
case JSON_ERROR_STATE_MISMATCH :
$message = 'underflow or the modes mismatch';
break;
case JSON_ERROR_CTRL_CHAR :
$message = 'unexpected control character found';
break;
case JSON_ERROR_SYNTAX :
$message = 'malformed JSON';
break;
case JSON_ERROR_UTF8 :
$message = 'malformed UTF-8 characters, possibly ' .
'incorrectly encoded';
break;
default :
$message = 'unknown error';
break;
}
throw new RestException (400, 'Error parsing JSON, ' . $message);
} elseif (strlen($data) && $decoded === null || $decoded === $data) {
throw new RestException (400, 'Error parsing JSON');
}
return Object::toArray($decoded);
}
/**
* Pretty print JSON string
*
* @param string $json
*
* @return string formatted json
*/
private function formatJson($json)
{
$tab = ' ';
$newJson = '';
$indentLevel = 0;
$inString = false;
$len = strlen($json);
for ($c = 0; $c < $len; $c++) {
$char = $json [$c];
switch ($char) {
case '{' :
case '[' :
if (!$inString) {
$newJson .= $char . "\n" .
str_repeat($tab, $indentLevel + 1);
$indentLevel++;
} else {
$newJson .= $char;
}
break;
case '}' :
case ']' :
if (!$inString) {
$indentLevel--;
$newJson .= "\n" .
str_repeat($tab, $indentLevel) . $char;
} else {
$newJson .= $char;
}
break;
case ',' :
if (!$inString) {
$newJson .= ",\n" .
str_repeat($tab, $indentLevel);
} else {
$newJson .= $char;
}
break;
case ':' :
if (!$inString) {
$newJson .= ': ';
} else {
$newJson .= $char;
}
break;
case '"' :
if ($c == 0) {
$inString = true;
} elseif ($c > 0 && $json [$c - 1] != '\\') {
$inString = !$inString;
}
default :
$newJson .= $char;
break;
}
}
return $newJson;
}
}

View File

@@ -0,0 +1,144 @@
<?php
namespace Luracast\Restler\Format;
/**
* Describe the purpose of this class/interface/trait
*
* @category Framework
* @package Restler
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
abstract class MultiFormat implements iFormat
{
/**
* override in the extending class
*/
const MIME = 'text/plain,text/html';
/**
* override in the extending class
*/
const EXTENSION = 'txt,html';
/**
* @var string charset encoding defaults to UTF8
*/
protected $charset='utf-8';
public static $mime;
public static $extension;
/**
* Injected at runtime
*
* @var \Luracast\Restler\Restler
*/
public $restler;
/**
* Get MIME type => Extension mappings as an associative array
*
* @return array list of mime strings for the format
* @example array('application/json'=>'json');
*/
public function getMIMEMap()
{
$extensions = explode(',',static::EXTENSION);
$mimes = explode(',',static::MIME);
$count = max(count($extensions), count($mimes));
$extensions += array_fill(0, $count, end($extensions));
$mimes += array_fill(0, $count, end($mimes));
return array_combine($mimes,$extensions);
}
/**
* Set the selected MIME type
*
* @param string $mime
* MIME type
*/
public function setMIME($mime)
{
static::$mime = $mime;
}
/**
* Content-Type field of the HTTP header can send a charset
* parameter in the HTTP header to specify the character
* encoding of the document.
* This information is passed
* here so that Format class can encode data accordingly
* Format class may choose to ignore this and use its
* default character set.
*
* @param string $charset
* Example utf-8
*/
public function setCharset($charset)
{
$this->charset = $charset;
}
/**
* Content-Type accepted by the Format class
*
* @return string $charset Example utf-8
*/
public function getCharset()
{
return $this->charset;
}
/**
* Get selected MIME type
*/
public function getMIME()
{
return static::$mime;
}
/**
* Set the selected file extension
*
* @param string $extension
* file extension
*/
public function setExtension($extension)
{
static::$extension = $extension;
}
/**
* Get the selected file extension
*
* @return string file extension
*/
public function getExtension()
{
return static::$extension;
}
/**
* @return boolean is parsing the request supported?
*/
public function isReadable()
{
return true;
}
/**
* @return boolean is composing response supported?
*/
public function isWritable()
{
return true;
}
public function __toString()
{
return $this->getExtension();
}
}

View File

@@ -0,0 +1,91 @@
<?php
namespace Luracast\Restler\Format;
use Luracast\Restler\Data\Object;
use CFPropertyList\CFTypeDetector;
use CFPropertyList\CFPropertyList;
/**
* Plist Format for Restler Framework.
* Plist is the native data exchange format for Apple iOS and Mac platform.
* Use this format to talk to mac applications and iOS devices.
* This class is capable of serving both xml plist and binary plist.
*
* @category Framework
* @package Restler
* @subpackage format
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class PlistFormat extends MultiFormat
{
/**
* @var boolean set it to true binary plist is preferred
*/
public static $compact = null;
const MIME = 'application/xml,application/x-plist';
const EXTENSION = 'plist';
public function setMIME($mime)
{
static::$mime = $mime;
static::$compact = $mime == 'application/x-plist';
}
/**
* Encode the given data in plist format
*
* @param array $data
* resulting data that needs to
* be encoded in plist 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
*
* @return string encoded string
*/
public function encode($data, $humanReadable = false)
{
//require_once 'CFPropertyList.php';
if (!isset(self::$compact)) {
self::$compact = !$humanReadable;
}
/**
*
* @var CFPropertyList
*/
$plist = new CFPropertyList ();
$td = new CFTypeDetector ();
$guessedStructure = $td->toCFType(
Object::toArray($data)
);
$plist->add($guessedStructure);
return self::$compact
? $plist->toBinary()
: $plist->toXML(true);
}
/**
* Decode the given data from plist format
*
* @param string $data
* data sent from client to
* the api in the given format.
*
* @return array associative array of the parsed data
*/
public function decode($data)
{
//require_once 'CFPropertyList.php';
$plist = new CFPropertyList ();
$plist->parse($data);
return $plist->toArray();
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Luracast\Restler\Format;
/**
* Tab Separated Value Format
*
* @category Framework
* @package Restler
* @subpackage format
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class TsvFormat extends CsvFormat
{
const MIME = 'text/csv';
const EXTENSION = 'csv';
public static $delimiter = "\t";
public static $enclosure = '"';
public static $escape = '\\';
public static $haveHeaders = null;
}

View File

@@ -0,0 +1,145 @@
<?php
namespace Luracast\Restler\Format;
use Luracast\Restler\RestException;
/**
* Support for Multi Part Form Data and File Uploads
*
* @category Framework
* @package Restler
* @subpackage format
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class UploadFormat extends Format
{
const MIME = 'multipart/form-data';
const EXTENSION = 'post';
public static $errors = array(
0 => false,
1 => "The uploaded file exceeds the maximum allowed size",
2 => "The uploaded file exceeds the maximum allowed size",
3 => "The uploaded file was only partially uploaded",
4 => "No file was uploaded",
6 => "Missing a temporary folder"
);
/**
* use it if you need to restrict uploads based on file type
* setting it as an empty array allows all file types
* default is to allow only png and jpeg images
*
* @var array
*/
public static $allowedMimeTypes = array('image/jpeg', 'image/png');
/**
* use it to restrict uploads based on file size
* set it to 0 to allow all sizes
* please note that it upload restrictions in the server
* takes precedence so it has to be lower than or equal to that
* default value is 1MB (1024x1024)bytes
* usual value for the server is 8388608
*
* @var int
*/
public static $maximumFileSize = 1048576;
/**
* Your own validation function for validating each uploaded file
* it can return false or throw an exception for invalid file
* use anonymous function / closure in PHP 5.3 and above
* use function name in other cases
*
* @var Callable
*/
public static $customValidationFunction;
/**
* Since exceptions are triggered way before at the `get` stage
*
* @var bool
*/
public static $suppressExceptionsAsError = false;
protected static function checkFile(& $file, $doMimeCheck = false, $doSizeCheck = false)
{
try {
if ($file['error']) {
//server is throwing an error
//assume that the error is due to maximum size limit
throw new RestException($file['error'] > 5 ? 500 : 413, static::$errors[$file['error']]);
}
$typeElements = explode('/', $file['type']);
$genericType = $typeElements[0].'/*';
if (
$doMimeCheck
&& !(
in_array($file['type'], self::$allowedMimeTypes)
|| in_array($genericType, self::$allowedMimeTypes)
)
) {
throw new RestException(403, "File type ({$file['type']}) is not supported.");
}
if ($doSizeCheck && $file['size'] > self::$maximumFileSize) {
throw new RestException(413, "Uploaded file ({$file['name']}) is too big.");
}
if (self::$customValidationFunction) {
if (!call_user_func(self::$customValidationFunction, $file)) {
throw new RestException(403, "File ({$file['name']}) is not supported.");
}
}
} catch (RestException $e) {
if (static::$suppressExceptionsAsError) {
$file['error'] = $e->getCode() == 413 ? 1 : 6;
$file['exception'] = $e;
} else {
throw $e;
}
}
}
public function encode($data, $humanReadable = false)
{
throw new RestException(500, 'UploadFormat is read only');
}
public function decode($data)
{
$doMimeCheck = !empty(self::$allowedMimeTypes);
$doSizeCheck = self::$maximumFileSize ? TRUE : FALSE;
//validate
foreach ($_FILES as & $file) {
if (is_array($file['error'])) {
foreach ($file['error'] as $i => $error) {
$innerFile = array();
foreach ($file as $property => $value) {
$innerFile[$property] = $value[$i];
}
if ($innerFile['name'])
static::checkFile($innerFile, $doMimeCheck, $doSizeCheck);
if (isset($innerFile['exception'])) {
$file['error'][$i] = $innerFile['error'];
$file['exception'] = $innerFile['exception'];
break;
}
}
} else {
if ($file['name'])
static::checkFile($file, $doMimeCheck, $doSizeCheck);
if (isset($innerFile['exception'])) {
break;
}
}
}
//sort file order if needed;
return UrlEncodedFormat::decoderTypeFix($_FILES + $_POST);
}
function isWritable()
{
return false;
}
}

View File

@@ -0,0 +1,58 @@
<?php
namespace Luracast\Restler\Format;
/**
* URL Encoded String Format
*
* @category Framework
* @package Restler
* @subpackage format
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class UrlEncodedFormat extends Format
{
const MIME = 'application/x-www-form-urlencoded';
const EXTENSION = 'post';
public function encode($data, $humanReadable = false)
{
return http_build_query(static::encoderTypeFix($data));
}
public function decode($data)
{
parse_str($data, $r);
return self::decoderTypeFix($r);
}
public static function encoderTypeFix(array $data)
{
foreach ($data as $k => $v) {
if (is_bool($v)) {
$data[$k] = $v = $v ? 'true' : 'false';
} elseif (is_array($v)) {
$data[$k] = $v = static::decoderTypeFix($v);
}
}
return $data;
}
public static function decoderTypeFix(array $data)
{
foreach ($data as $k => $v) {
if ($v === 'true' || $v === 'false') {
$data[$k] = $v = $v === 'true';
} elseif (is_array($v)) {
$data[$k] = $v = static::decoderTypeFix($v);
} elseif (empty($v) && $v != 0) {
unset($data[$k]);
}
}
return $data;
}
}

View File

@@ -0,0 +1,348 @@
<?php
namespace Luracast\Restler\Format;
use Luracast\Restler\Data\Object;
use Luracast\Restler\RestException;
use SimpleXMLElement;
use XMLWriter;
/**
* XML Markup Format for Restler Framework
*
* @category Framework
* @package Restler
* @subpackage format
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class XmlFormat extends Format
{
const MIME = 'application/xml';
const EXTENSION = 'xml';
// ==================================================================
//
// Properties related to reading/parsing/decoding xml
//
// ------------------------------------------------------------------
public static $importSettingsFromXml = false;
public static $parseAttributes = true;
public static $parseNamespaces = true;
public static $parseTextNodeAsProperty = true;
// ==================================================================
//
// Properties related to writing/encoding xml
//
// ------------------------------------------------------------------
public static $useTextNodeProperty = true;
public static $useNamespaces = true;
public static $cdataNames = array();
// ==================================================================
//
// Common Properties
//
// ------------------------------------------------------------------
public static $attributeNames = array();
public static $textNodeName = 'text';
public static $namespaces = array();
public static $namespacedProperties = array();
/**
* Default name for the root node.
*
* @var string $rootNodeName
*/
public static $rootName = 'response';
public static $defaultTagName = 'item';
/**
* When you decode an XML its structure is copied to the static vars
* we can use this function to echo them out and then copy paste inside
* our service methods
*
* @return string PHP source code to reproduce the configuration
*/
public static function exportCurrentSettings()
{
$s = 'XmlFormat::$rootName = "' . (self::$rootName) . "\";\n";
$s .= 'XmlFormat::$attributeNames = ' .
(var_export(self::$attributeNames, true)) . ";\n";
$s .= 'XmlFormat::$defaultTagName = "' .
self::$defaultTagName . "\";\n";
$s .= 'XmlFormat::$parseAttributes = ' .
(self::$parseAttributes ? 'true' : 'false') . ";\n";
$s .= 'XmlFormat::$parseNamespaces = ' .
(self::$parseNamespaces ? 'true' : 'false') . ";\n";
if (self::$parseNamespaces) {
$s .= 'XmlFormat::$namespaces = ' .
(var_export(self::$namespaces, true)) . ";\n";
$s .= 'XmlFormat::$namespacedProperties = ' .
(var_export(self::$namespacedProperties, true)) . ";\n";
}
return $s;
}
public function encode($data, $humanReadable = false)
{
$data = Object::toArray($data);
$xml = new XMLWriter();
$xml->openMemory();
$xml->startDocument('1.0', $this->charset);
if ($humanReadable) {
$xml->setIndent(true);
$xml->setIndentString(' ');
}
static::$useNamespaces && isset(static::$namespacedProperties[static::$rootName])
?
$xml->startElementNs(
static::$namespacedProperties[static::$rootName],
static::$rootName,
static::$namespaces[static::$namespacedProperties[static::$rootName]]
)
:
$xml->startElement(static::$rootName);
if (static::$useNamespaces) {
foreach (static::$namespaces as $prefix => $ns) {
if (isset(static::$namespacedProperties[static::$rootName])
&& static::$namespacedProperties[static::$rootName] == $prefix
)
continue;
$prefix = 'xmlns' . (empty($prefix) ? '' : ':' . $prefix);
$xml->writeAttribute($prefix, $ns);
}
}
$this->write($xml, $data, static::$rootName);
$xml->endElement();
return $xml->outputMemory();
}
public function write(XMLWriter $xml, $data, $parent)
{
$text = array();
if (is_array($data)) {
if (static::$useTextNodeProperty && isset($data[static::$textNodeName])) {
$text [] = $data[static::$textNodeName];
unset($data[static::$textNodeName]);
}
$attributes = array_flip(static::$attributeNames);
//make sure we deal with attributes first
$temp = array();
foreach ($data as $key => $value) {
if (isset($attributes[$key])) {
$temp[$key] = $data[$key];
unset($data[$key]);
}
}
$data = array_merge($temp, $data);
foreach ($data as $key => $value) {
if (is_numeric($key)) {
if (!is_array($value)) {
$text [] = $value;
continue;
}
$key = static::$defaultTagName;
}
$useNS = static::$useNamespaces
&& !empty(static::$namespacedProperties[$key])
&& false === strpos($key, ':');
if (is_array($value)) {
if ($value == array_values($value)) {
//numeric array, create siblings
foreach ($value as $v) {
$useNS
? $xml->startElementNs(
static::$namespacedProperties[$key],
$key,
null
)
: $xml->startElement($key);
$this->write($xml, $v, $key);
$xml->endElement();
}
} else {
$useNS
? $xml->startElementNs(
static::$namespacedProperties[$key],
$key,
null
)
: $xml->startElement($key);
$this->write($xml, $value, $key);
$xml->endElement();
}
continue;
} elseif (is_bool($value)) {
$value = $value ? 'true' : 'false';
}
if (isset($attributes[$key])) {
$xml->writeAttribute($useNS ? static::$namespacedProperties[$key] . ':' . $key : $key, $value);
} else {
$useNS
?
$xml->startElementNs(
static::$namespacedProperties[$key],
$key,
null
)
: $xml->startElement($key);
$this->write($xml, $value, $key);
$xml->endElement();
}
}
} else {
$text [] = (string)$data;
}
if (!empty($text)) {
if (count($text) == 1) {
in_array($parent, static::$cdataNames)
? $xml->writeCdata(implode('', $text))
: $xml->text(implode('', $text));
} else {
foreach ($text as $t) {
$xml->writeElement(static::$textNodeName, $t);
}
}
}
}
public function decode($data)
{
try {
if ($data == '') {
return array();
}
libxml_use_internal_errors(true);
$xml = simplexml_load_string($data,
"SimpleXMLElement", LIBXML_NOBLANKS | LIBXML_NOCDATA | LIBXML_COMPACT);
if (false === $xml) {
$error = libxml_get_last_error();
throw new RestException(400, 'Malformed XML. '
. trim($error->message, "\r\n") . ' at line ' . $error->line);
}
libxml_clear_errors();
if (static::$importSettingsFromXml) {
static::$attributeNames = array();
static::$namespacedProperties = array();
static::$namespaces = array();
static::$rootName = $xml->getName();
$namespaces = $xml->getNamespaces();
if (count($namespaces)) {
$p = strpos($data, $xml->getName());
if ($p && $data{$p - 1} == ':') {
$s = strpos($data, '<') + 1;
$prefix = substr($data, $s, $p - $s - 1);
static::$namespacedProperties[static::$rootName] = $prefix;
}
}
}
$data = $this->read($xml);
if (count($data) == 1 && isset($data[static::$textNodeName]))
$data = $data[static::$textNodeName];
return $data;
} catch (\RuntimeException $e) {
throw new RestException(400,
"Error decoding request. " . $e->getMessage());
}
}
public function read(SimpleXMLElement $xml, $namespaces = null)
{
$r = array();
$text = (string)$xml;
if (static::$parseAttributes) {
$attributes = $xml->attributes();
foreach ($attributes as $key => $value) {
if (static::$importSettingsFromXml
&& !in_array($key, static::$attributeNames)
) {
static::$attributeNames[] = $key;
}
$r[$key] = static::setType((string)$value);
}
}
$children = $xml->children();
foreach ($children as $key => $value) {
if (isset($r[$key])) {
if (is_array($r[$key])) {
if ($r[$key] != array_values($r[$key]))
$r[$key] = array($r[$key]);
} else {
$r[$key] = array($r[$key]);
}
$r[$key][] = $this->read($value, $namespaces);
} else {
$r[$key] = $this->read($value);
}
}
if (static::$parseNamespaces) {
if (is_null($namespaces))
$namespaces = $xml->getDocNamespaces(true);
foreach ($namespaces as $prefix => $ns) {
static::$namespaces[$prefix] = $ns;
if (static::$parseAttributes) {
$attributes = $xml->attributes($ns);
foreach ($attributes as $key => $value) {
if (isset($r[$key])) {
$key = "{$prefix}:$key";
}
if (static::$importSettingsFromXml
&& !in_array($key, static::$attributeNames)
) {
static::$namespacedProperties[$key] = $prefix;
static::$attributeNames[] = $key;
}
$r[$key] = static::setType((string)$value);
}
}
$children = $xml->children($ns);
foreach ($children as $key => $value) {
if (static::$importSettingsFromXml)
static::$namespacedProperties[$key] = $prefix;
if (isset($r[$key])) {
if (is_array($r[$key])) {
if ($r[$key] != array_values($r[$key]))
$r[$key] = array($r[$key]);
} else {
$r[$key] = array($r[$key]);
}
$r[$key][] = $this->read($value, $namespaces);
} else {
$r[$key] = $this->read($value, $namespaces);
}
}
}
}
if (empty($text) && $text !== '0') {
if (empty($r)) return null;
} else {
empty($r)
? $r = static::setType($text)
: (
static::$parseTextNodeAsProperty
? $r[static::$textNodeName] = static::setType($text)
: $r[] = static::setType($text)
);
}
return $r;
}
public static function setType($value)
{
if (empty($value) && $value !== '0')
return null;
if ($value == 'true')
return true;
if ($value == 'false')
return true;
return $value;
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace Luracast\Restler\Format;
use Symfony\Component\Yaml\Yaml;
use Luracast\Restler\Data\Object;
/**
* YAML Format for Restler Framework
*
* @category Framework
* @package Restler
* @subpackage format
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class YamlFormat extends Format
{
const MIME = 'text/plain';
const EXTENSION = 'yaml';
public function encode($data, $humanReadable = false)
{
// require_once 'sfyaml.php';
return @Yaml::dump(Object::toArray($data));
}
public function decode($data)
{
// require_once 'sfyaml.php';
return Yaml::parse($data);
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace Luracast\Restler\Format;
/**
* Interface for creating formats that accept steams for decoding
*
* @category Framework
* @package Restler
* @subpackage format
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
interface iDecodeStream
{
/**
* Decode the given data stream
*
* @param string $stream A stream resource with data
* sent from client to the api
* in the given format.
*
* @return array associative array of the parsed data
*/
public function decodeStream($stream);
}

View File

@@ -0,0 +1,109 @@
<?php
namespace Luracast\Restler\Format;
/**
* Interface for creating custom data formats
* like xml, json, yaml, amf etc
* @category Framework
* @package Restler
* @subpackage format
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
interface iFormat
{
/**
* Get MIME type => Extension mappings as an associative array
*
* @return array list of mime strings for the format
* @example array('application/json'=>'json');
*/
public function getMIMEMap();
/**
* Set the selected MIME type
*
* @param string $mime
* MIME type
*/
public function setMIME($mime);
/**
* Content-Type field of the HTTP header can send a charset
* parameter in the HTTP header to specify the character
* encoding of the document.
* This information is passed
* here so that Format class can encode data accordingly
* Format class may choose to ignore this and use its
* default character set.
*
* @param string $charset
* Example utf-8
*/
public function setCharset($charset);
/**
* Content-Type accepted by the Format class
*
* @return string $charset Example utf-8
*/
public function getCharset();
/**
* Get selected MIME type
*/
public function getMIME();
/**
* Set the selected file extension
*
* @param string $extension
* file extension
*/
public function setExtension($extension);
/**
* Get the selected file extension
*
* @return string file extension
*/
public function getExtension();
/**
* 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
* @return string encoded string
*/
public function encode($data, $humanReadable = false);
/**
* Decode the given data from the format
*
* @param string $data
* data sent from client to
* the api in the given format.
* @return array associative array of the parsed data
*/
public function decode($data);
/**
* @return boolean is parsing the request supported?
*/
public function isReadable();
/**
* @return boolean is composing response supported?
*/
public function isWritable();
}

View File

@@ -0,0 +1,129 @@
<?php
namespace Luracast\Restler;
/**
* Default Cache that writes/reads human readable files for caching purpose
*
* @category Framework
* @package Restler
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class HumanReadableCache implements iCache
{
/**
* @var string path of the folder to hold cache files
*/
public static $cacheDir;
public function __construct()
{
if (is_null(self::$cacheDir)) {
self::$cacheDir = Defaults::$cacheDirectory;
}
}
/**
* store data in the cache
*
* @param string $name
* @param mixed $data
*
* @throws \Exception
* @return boolean true if successful
*/
public function set($name, $data)
{
if (is_array($data)) {
$s = '$o = array();' . PHP_EOL . PHP_EOL;
$s .= '// ** THIS IS AN AUTO GENERATED FILE.'
. ' DO NOT EDIT MANUALLY ** ';
foreach ($data as $key => $value) {
$s .= PHP_EOL . PHP_EOL .
"//==================== $key ===================="
. PHP_EOL . PHP_EOL;
if (is_array($value)) {
$s .= '$o[\'' . $key . '\'] = array();';
foreach ($value as $ke => $va) {
$s .= PHP_EOL . PHP_EOL . "//==== $key $ke ===="
. PHP_EOL . PHP_EOL;
$s .= '$o[\'' . $key . '\'][\'' . $ke . '\'] = ' .
str_replace(' ', ' ',
var_export($va, true)) . ';';
}
} else {
$s .= '$o[\'' . $key . '\'] = '
. var_export($value, true) . ';';
}
}
$s .= PHP_EOL . 'return $o;';
} else {
$s = 'return ' . var_export($data, true) . ';';
}
$file = $this->_file($name);
$r = @file_put_contents($file, "<?php $s");
@chmod($file, 0777);
if ($r === false) {
$this->throwException();
}
return $r;
}
/**
* retrieve data from the cache
*
* @param string $name
* @param bool $ignoreErrors
*
* @return mixed
*/
public function get($name, $ignoreErrors = false)
{
$file = $this->_file($name);
if (file_exists($file)) {
return include($file);
}
}
/**
* delete data from the cache
*
* @param string $name
* @param bool $ignoreErrors
*
* @return boolean true if successful
*/
public function clear($name, $ignoreErrors = false)
{
return @unlink($this->_file($name));
}
/**
* check if the given name is cached
*
* @param string $name
*
* @return boolean true if cached
*/
public function isCached($name)
{
return file_exists($this->_file($name));
}
private function _file($name)
{
return self::$cacheDir . '/' . $name . '.php';
}
private function throwException()
{
throw new \Exception(
'The cache directory `'
. self::$cacheDir . '` should exist with write permission.'
);
}
}

View File

@@ -0,0 +1,8 @@
Luracast Restler Framework
==========================
Restler is a simple and effective multi-format Web API Server written in PHP.
This repository contains just the framework files for installing the framework core using composer require statements.
For more information, usage examples, pull requests, and issues go to the [Main Repository](https://github.com/Luracast/Restler)

View File

@@ -0,0 +1,53 @@
<?php
namespace Luracast\Restler;
use Luracast\Restler\Format\JsonFormat;
/**
* Static class for handling redirection
*
* @category Framework
* @package Restler
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class Redirect
{
/**
* 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();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,138 @@
<?php
namespace Luracast\Restler;
use Exception;
/**
* Special Exception for raising API errors
* that can be used in API methods
*
* @category Framework
* @package Restler
* @subpackage exception
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
*/
class RestException extends Exception
{
/**
* HTTP status codes
*
* @var array
*/
public static $codes = array(
100 => 'Continue',
101 => 'Switching Protocols',
200 => 'OK',
201 => 'Created',
202 => 'Accepted',
203 => 'Non-Authoritative Information',
204 => 'No Content',
205 => 'Reset Content',
206 => 'Partial Content',
300 => 'Multiple Choices',
301 => 'Moved Permanently',
302 => 'Found',
303 => 'See Other',
304 => 'Not Modified',
305 => 'Use Proxy',
306 => '(Unused)',
307 => 'Temporary Redirect',
400 => 'Bad Request',
401 => 'Unauthorized',
402 => 'Payment Required',
403 => 'Forbidden',
404 => 'Not Found',
405 => 'Method Not Allowed',
406 => 'Not Acceptable',
407 => 'Proxy Authentication Required',
408 => 'Request Timeout',
409 => 'Conflict',
410 => 'Gone',
411 => 'Length Required',
412 => 'Precondition Failed',
413 => 'Request Entity Too Large',
414 => 'Request-URI Too Long',
415 => 'Unsupported Media Type',
416 => 'Requested Range Not Satisfiable',
417 => 'Expectation Failed',
429 => 'Too Many Requests', //still in draft but used for rate limiting
500 => 'Internal Server Error',
501 => 'Not Implemented',
502 => 'Bad Gateway',
503 => 'Service Unavailable',
504 => 'Gateway Timeout',
505 => 'HTTP Version Not Supported'
);
private $details;
private $stage;
/**
* @param string $httpStatusCode http status code
* @param string|null $errorMessage error message
* @param array $details any extra detail about the exception
* @param Exception $previous previous exception if any
*/
public function __construct($httpStatusCode, $errorMessage = null, array $details = array(), Exception $previous = null)
{
$events = Scope::get('Restler')->getEvents();
if(count($events)<= 1){
$this->stage = 'setup';
} else {
$this->stage = $previous ? $events[count($events)-2] : end($events);
}
$this->details = $details;
parent::__construct($errorMessage, $httpStatusCode, $previous);
}
/**
* Get extra details about the exception
*
* @return array details array
*/
public function getDetails()
{
return $this->details;
}
public function getStage()
{
return $this->stage;
}
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),
);
}
public function getErrorMessage()
{
$statusCode = $this->getCode();
$message = $this->getMessage();
if (isset(RestException::$codes[$statusCode])) {
$message = RestException::$codes[$statusCode] .
(empty($message) ? '' : ': ' . $message);
}
return $message;
}
public function getSource()
{
$e = $this;
while ($e->getPrevious()) {
$e = $e->getPrevious();
}
return basename($e->getFile()) . ':'
. $e->getLine() . ' at '
. $this->getStage() . ' stage';
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,696 @@
<?php
namespace Luracast\Restler;
use Luracast\Restler\Data\ApiMethodInfo;
use Luracast\Restler\Data\String;
use ReflectionClass;
use ReflectionMethod;
use ReflectionProperty;
use Exception;
/**
* Router class that routes the urls to api methods along with parameters
*
* @category Framework
* @package Restler
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class Routes
{
public static $prefixingParameterNames = array(
'id'
);
protected static $routes = array();
protected static $models = array();
/**
* 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);
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'] = $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 (empty($m['label']))
$m['label'] = static::label($m['name']);
if (is_null($type) && isset($m['type'])) {
$type = $m['type'];
}
if ($m['name'] == 'email' && empty($m[CommentParser::$embeddedDataName]['type']) && $type == 'string')
$m[CommentParser::$embeddedDataName]['type'] = 'email';
$m ['default'] = $defaults [$position];
$m ['required'] = !$param->isOptional();
$contentType = Util::nestedValue(
$m,
CommentParser::$embeddedDataName,
'type'
);
if ($contentType && $qualified = Scope::resolve($contentType, $scope)) {
list($m[CommentParser::$embeddedDataName]['type'], $children) = static::getTypeAndModel(
new ReflectionClass($qualified), $scope
);
}
if ($type instanceof ReflectionClass) {
list($type, $children) = static::getTypeAndModel($type, $scope);
} elseif ($type && is_string($type) && $qualified = Scope::resolve($type, $scope)) {
list($type, $children)
= static::getTypeAndModel(new ReflectionClass($qualified), $scope);
}
if (isset($type)) {
$m['type'] = $type;
}
$m['children'] = $children;
if ($m['name'] == Defaults::$fullRequestDataName) {
$from = 'body';
if (!isset($m['type'])) {
$type = $m['type'] = 'array';
}
} elseif (isset($m[CommentParser::$embeddedDataName]['from'])) {
$from = $m[CommentParser::$embeddedDataName]['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';
}
}
$m[CommentParser::$embeddedDataName]['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][CommentParser::$embeddedDataName]['from'] = 'path';
} elseif ($httpMethod == 'GET' || $httpMethod == 'DELETE') {
$copy['metadata']['param'][$i][CommentParser::$embeddedDataName]['from'] = 'query';
} elseif ($p[CommentParser::$embeddedDataName]['from'] == 'path') {
$copy['metadata']['param'][$i][CommentParser::$embeddedDataName]['from'] = 'body';
}
}
$url = preg_replace_callback('/{[^}]+}|:[^\/]+/',
function ($matches) use ($call) {
$match = trim($matches[0], '{}:');
$index = $call['arguments'][$match];
return '{' .
Routes::typeChar(isset(
$call['metadata']['param'][$index]['type'])
? $call['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][CommentParser::$embeddedDataName]['from'];
if ($from == 'body' && ($httpMethod == 'GET' ||
$httpMethod == 'DELETE')
) {
$call['metadata']['param'][$position][CommentParser::$embeddedDataName]['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);
}
}
}
}
}
/**
* @access private
*/
public static function typeChar($type = null)
{
if (!$type) {
return 's';
}
switch ($type{0}) {
case 'i':
case 'f':
return 'n';
}
return 's';
}
protected static function addPath($path, array $call,
$httpMethod = 'GET', $version = 1)
{
$call['url'] = preg_replace_callback(
"/\{\S(\d+)\}/",
function ($matches) use ($call) {
return '{' .
$call['metadata']['param'][$matches[1]]['name'] . '}';
},
$path
);
//check for wildcard routes
if (substr($path, -1, 1) == '*') {
$path = rtrim($path, '/*');
static::$routes["v$version"]['*'][$path][$httpMethod] = $call;
} else {
static::$routes["v$version"][$path][$httpMethod] = $call;
//create an alias with index if the method name is index
if ($call['methodName'] == 'index')
static::$routes["v$version"][ltrim("$path/index", '/')][$httpMethod] = $call;
}
}
/**
* Find the api method for the given url and http method
*
* @param string $path Requested url path
* @param string $httpMethod GET|POST|PUT|PATCH|DELETE etc
* @param int $version Api Version number
* @param array $data Data collected from the request
*
* @throws RestException
* @return ApiMethodInfo
*/
public static function find($path, $httpMethod,
$version = 1, array $data = array())
{
$p = Util::nestedValue(static::$routes, "v$version");
if (!$p) {
throw new RestException(
404,
$version == 1 ? '' : "Version $version is not supported"
);
}
$status = 404;
$message = null;
$methods = array();
if (isset($p[$path][$httpMethod])) {
//================== static routes ==========================
return static::populate($p[$path][$httpMethod], $data);
} elseif (isset($p['*'])) {
//================== wildcard routes ========================
uksort($p['*'], function ($a, $b) {
return strlen($b) - strlen($a);
});
foreach ($p['*'] as $key => $value) {
if (strpos($path, $key) === 0 && isset($value[$httpMethod])) {
//path found, convert rest of the path to parameters
$path = substr($path, strlen($key) + 1);
$call = ApiMethodInfo::__set_state($value[$httpMethod]);
$call->parameters = empty($path)
? array()
: explode('/', $path);
return $call;
}
}
}
//================== dynamic routes =============================
//add newline char if trailing slash is found
if (substr($path, -1) == '/')
$path .= PHP_EOL;
//if double slash is found fill in newline char;
$path = str_replace('//', '/' . PHP_EOL . '/', $path);
ksort($p);
foreach ($p as $key => $value) {
if (!isset($value[$httpMethod])) {
continue;
}
$regex = str_replace(array('{', '}'),
array('(?P<', '>[^/]+)'), $key);
if (preg_match_all(":^$regex$:i", $path, $matches, PREG_SET_ORDER)) {
$matches = $matches[0];
$found = true;
foreach ($matches as $k => $v) {
if (is_numeric($k)) {
unset($matches[$k]);
continue;
}
$index = intval(substr($k, 1));
$details = $value[$httpMethod]['metadata']['param'][$index];
if ($k{0} == 's' || strpos($k, static::pathVarTypeOf($v)) === 0) {
//remove the newlines
$data[$details['name']] = trim($v, PHP_EOL);
} else {
$status = 400;
$message = 'invalid value specified for `'
. $details['name'] . '`';
$found = false;
break;
}
}
if ($found) {
return static::populate($value[$httpMethod], $data);
}
}
}
if ($status == 404) {
//check if other methods are allowed
if (isset($p[$path])) {
$status = 405;
$methods = array_keys($p[$path]);
}
}
if ($status == 405) {
header('Allow: ' . implode(', ', $methods));
}
throw new RestException($status, $message);
}
/**
* Populates the parameter values
*
* @param array $call
* @param $data
*
* @return ApiMethodInfo
*
* @access private
*/
protected static function populate(array $call, $data)
{
$call['parameters'] = $call['defaults'];
$p = & $call['parameters'];
foreach ($data as $key => $value) {
if (isset($call['arguments'][$key])) {
$p[$call['arguments'][$key]] = $value;
}
}
if (Defaults::$smartParameterParsing && 'post' != (string)Util::$restler->requestFormat) {
if (
count($p) == 1 &&
($m = Util::nestedValue($call, 'metadata', 'param', 0)) &&
!array_key_exists($m['name'], $data) &&
array_key_exists(Defaults::$fullRequestDataName, $data) &&
!is_null($d = $data[Defaults::$fullRequestDataName]) &&
isset($m['type']) &&
static::typeMatch($m['type'], $d)
) {
$p[0] = $d;
} else {
$bodyParamCount = 0;
$lastBodyParamIndex = -1;
$lastM = null;
foreach ($call['metadata']['param'] as $k => $m) {
if ($m[CommentParser::$embeddedDataName]['from'] == 'body') {
$bodyParamCount++;
$lastBodyParamIndex = $k;
$lastM = $m;
}
}
if (
$bodyParamCount == 1 &&
!array_key_exists($lastM['name'], $data) &&
array_key_exists(Defaults::$fullRequestDataName, $data) &&
!is_null($d = $data[Defaults::$fullRequestDataName])
) {
$p[$lastBodyParamIndex] = $d;
}
}
}
$r = ApiMethodInfo::__set_state($call);
$modifier = "_modify_{$r->methodName}_api";
if (method_exists($r->className, $modifier)) {
$stage = end(Scope::get('Restler')->getEvents());
if (empty($stage))
$stage = 'setup';
$r = Scope::get($r->className)->$modifier($r, $stage) ? : $r;
}
return $r;
}
/**
* @access private
*/
protected static function pathVarTypeOf($var)
{
if (is_numeric($var)) {
return 'n';
}
if ($var === 'true' || $var === 'false') {
return 'b';
}
return 's';
}
protected static function typeMatch($type, $var)
{
switch ($type) {
case 'boolean':
case 'bool':
return is_bool($var);
case 'array':
case 'object':
return is_array($var);
case 'string':
case 'int':
case 'integer':
case 'float':
case 'number':
return is_scalar($var);
}
return true;
}
/**
* 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];
}
/**
* Import previously created routes from cache
*
* @param array $routes
*/
public static function fromArray(array $routes)
{
static::$routes = $routes;
}
/**
* Export current routes for cache
*
* @return array
*/
public static function toArray()
{
return static::$routes;
}
public static function type($var)
{
if (is_object($var)) return get_class($var);
if (is_array($var)) return 'array';
if (is_bool($var)) return 'boolean';
if (is_numeric($var)) return is_float($var) ? 'float' : 'int';
return 'string';
}
/**
* Create a label from name of the parameter or property
*
* Convert `camelCase` style names into proper `Title Case` names
*
* @param string $name
*
* @return string
*/
public static function label($name)
{
return ucfirst(preg_replace(array('/(?<=[^A-Z])([A-Z])/', '/(?<=[^0-9])([0-9])/'), ' $0', $name));
}
public static function scope(ReflectionClass $class)
{
$namespace = $class->getNamespaceName();
$imports = array(
'*' => empty($namespace) ? '' : $namespace . '\\'
);
$file = file_get_contents($class->getFileName());
$tokens = token_get_all($file);
$namespace = '';
$alias = '';
$reading = false;
$last = 0;
foreach ($tokens as $token) {
if (is_string($token)) {
if ($reading && ',' == $token) {
//===== STOP =====//
$reading = false;
if (!empty($namespace))
$imports[$alias] = trim($namespace, '\\');
//===== START =====//
$reading = true;
$namespace = '';
$alias = '';
} else {
//===== STOP =====//
$reading = false;
if (!empty($namespace))
$imports[$alias] = trim($namespace, '\\');
}
} elseif (T_USE == $token[0]) {
//===== START =====//
$reading = true;
$namespace = '';
$alias = '';
} elseif ($reading) {
//echo token_name($token[0]) . ' ' . $token[1] . PHP_EOL;
switch ($token[0]) {
case T_WHITESPACE:
continue 2;
case T_STRING:
$alias = $token[1];
if (T_AS == $last) {
break;
}
//don't break;
case T_NS_SEPARATOR:
$namespace .= $token[1];
break;
}
$last = $token[0];
}
}
return $imports;
}
}

View File

@@ -0,0 +1,190 @@
<?php
namespace Luracast\Restler;
/**
* Scope resolution class, manages instantiation and acts as a dependency
* injection container
*
* @category Framework
* @package Restler
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class Scope
{
public static $classAliases = array(
//Core
'Restler' => 'Luracast\Restler\Restler',
//Format classes
'AmfFormat' => 'Luracast\Restler\Format\AmfFormat',
'JsFormat' => 'Luracast\Restler\Format\JsFormat',
'JsonFormat' => 'Luracast\Restler\Format\JsonFormat',
'HtmlFormat' => 'Luracast\Restler\Format\HtmlFormat',
'PlistFormat' => 'Luracast\Restler\Format\PlistFormat',
'UploadFormat' => 'Luracast\Restler\Format\UploadFormat',
'UrlEncodedFormat' => 'Luracast\Restler\Format\UrlEncodedFormat',
'XmlFormat' => 'Luracast\Restler\Format\XmlFormat',
'YamlFormat' => 'Luracast\Restler\Format\YamlFormat',
'CsvFormat' => 'Luracast\Restler\Format\CsvFormat',
'TsvFormat' => 'Luracast\Restler\Format\TsvFormat',
//Filter classes
'RateLimit' => 'Luracast\Restler\Filter\RateLimit',
//UI classes
'Forms' => 'Luracast\Restler\UI\Forms',
'Nav' => 'Luracast\Restler\UI\Nav',
'Emmet' => 'Luracast\Restler\UI\Emmet',
'T' => 'Luracast\Restler\UI\Tags',
//API classes
'Resources' => 'Luracast\Restler\Resources',
//Cache classes
'HumanReadableCache' => 'Luracast\Restler\HumanReadableCache',
'ApcCache' => 'Luracast\Restler\ApcCache',
//Utility classes
'Object' => 'Luracast\Restler\Data\Object',
'String' => 'Luracast\Restler\Data\String',
'Arr' => 'Luracast\Restler\Data\Arr',
//Exception
'RestException' => 'Luracast\Restler\RestException'
);
public static $properties = array();
protected static $instances = array();
protected static $registry = array();
public static function register($name, Callable $function, $singleton = true)
{
static::$registry[$name] = (object)compact('function', 'singleton');
}
public static function set($name, $instance)
{
static::$instances[$name] = (object)array('instance' => $instance);
}
public static function get($name)
{
$r = null;
$initialized = false;
$properties = array();
if (array_key_exists($name, static::$instances)) {
$initialized = true;
$r = static::$instances[$name]->instance;
} elseif (!empty(static::$registry[$name])) {
$function = static::$registry[$name]->function;
$r = $function();
if (static::$registry[$name]->singleton)
static::$instances[$name] = (object)array('instance' => $r);
} else {
$fullName = $name;
if (isset(static::$classAliases[$name])) {
$fullName = static::$classAliases[$name];
}
if (class_exists($fullName)) {
$shortName = Util::getShortName($name);
$r = new $fullName();
static::$instances[$name] = (object)array('instance' => $r);
if ($name != 'Restler') {
$r->restler = static::get('Restler');
$m = Util::nestedValue($r->restler, 'apiMethodInfo', 'metadata');
if ($m) {
$properties = Util::nestedValue(
$m, 'class', $fullName,
CommentParser::$embeddedDataName
) ? : (Util::nestedValue(
$m, 'class', $shortName,
CommentParser::$embeddedDataName
) ? : array());
} else {
static::$instances[$name]->initPending = true;
}
}
}
}
if (
$r instanceof iUseAuthentication &&
static::get('Restler')->_authVerified &&
!isset(static::$instances[$name]->authVerified)
) {
static::$instances[$name]->authVerified = true;
$r->__setAuthenticationStatus
(static::get('Restler')->_authenticated);
}
if (isset(static::$instances[$name]->initPending)) {
$m = Util::nestedValue(static::get('Restler'), 'apiMethodInfo', 'metadata');
$fullName = $name;
if (class_exists($name)) {
$shortName = Util::getShortName($name);
} else {
$shortName = $name;
if (isset(static::$classAliases[$name]))
$fullName = static::$classAliases[$name];
}
if ($m) {
$properties = Util::nestedValue(
$m, 'class', $fullName,
CommentParser::$embeddedDataName
) ? : (Util::nestedValue(
$m, 'class', $shortName,
CommentParser::$embeddedDataName
) ? : array());
unset(static::$instances[$name]->initPending);
$initialized = false;
}
}
if (!$initialized && is_object($r)) {
$properties += static::$properties;
$objectVars = get_object_vars($r);
$className = get_class($r);
foreach ($properties as $property => $value) {
if (property_exists($className, $property)) {
//if not a static property
array_key_exists($property, $objectVars)
? $r->{$property} = $value
: $r::$$property = $value;
}
}
}
return $r;
}
/**
* Get fully qualified class name for the given scope
*
* @param string $className
* @param array $scope local scope
*
* @return string|boolean returns the class name or false
*/
public static function resolve($className, array $scope)
{
if (empty($className) || !is_string($className))
return false;
$divider = '\\';
$qualified = false;
if ($className{0} == $divider) {
$qualified = trim($className, $divider);
} elseif (array_key_exists($className, $scope)) {
$qualified = $scope[$className];
} else {
$qualified = $scope['*'] . $className;
}
if (class_exists($qualified))
return $qualified;
if (isset(static::$classAliases[$className])) {
$qualified = static::$classAliases[$className];
if (class_exists($qualified))
return $qualified;
}
return false;
}
}

View File

@@ -0,0 +1,383 @@
<?php
namespace Luracast\Restler\UI;
use Luracast\Restler\UI\Tags as T;
use Luracast\Restler\Util;
class Emmet
{
const DELIMITERS = '.#*>+^[=" ]{$@-#}';
/**
* 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
case '#':
$offsetTokens = array_values($tokens);
array_unshift($offsetTokens, '#');
$implicitTag();
$tag->id(
$parseText(
array_shift($tokens), $round, $total, $data
)
);
break;
//attributes
case '[':
$offsetTokens = array_values($tokens);
array_unshift($offsetTokens, '[');
$implicitTag();
$parseAttributes(
$parseAttributes, $round, $total, $data
);
break;
//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
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
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
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;
}
public static function tokenize($string)
{
$r = array();
$f = strtok($string, static::DELIMITERS);
$pos = 0;
do {
$start = $pos;
$pos = strpos($string, $f, $start);
$tokens = array();
for ($i = $start; $i < $pos; $i++) {
$token = $string{$i};
if (('#' == $token || '.' == $token) &&
(!empty($tokens) || $i == 0)
) {
$r[] = '';
}
$r[] = $tokens[] = $token;
}
$pos += strlen($f);
$r[] = $f;
} while (false != ($f = strtok(static::DELIMITERS)));
for ($i = $pos; $i < strlen($string); $i++) {
$token = $string{$i};
$r[] = $tokens[] = $token;
}
return $r;
/* sample output produced by ".row*3>.col*3"
[0] => div
[1] => .
[2] => row
[3] => *
[4] => 3
[5] => >
[6] => div
[7] => .
[8] => col
[9] => *
[10] => 4
*/
}
}

View File

@@ -0,0 +1,59 @@
<?php
namespace Luracast\Restler\UI;
/**
* Utility class for providing preset styles for html forms
*
* @category Framework
* @package Restler
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class FormStyles
{
public static $html = array(
'form' => 'form[role=form id=$id# name=$name# method=$method# action=$action# enctype=$enctype#]',
'input' => '.row>section>label{$label#}^input[name=$name# value=$value# type=$type# required=$required# autofocus=$autofocus# placeholder=$default# accept=$accept#]',
'textarea' => '.row>label{$label#}^textarea[name=$name# required=$required# autofocus=$autofocus# placeholder=$default# rows=3]{$value#}',
'radio' => '.row>section>label{$label#}^span>label*options>input[name=$name# value=$value# type=radio checked=$selected# required=$required#]+{ $text#}',
'select' => '.row>label{$label#}^select[name=$name# required=$required#]>option[value]+option[value=$value# selected=$selected#]{$text#}*options',
'submit' => '.row>label{ &nbsp; }^button[type=submit]{$label#}',
'fieldset' => 'fieldset>legend{$label#}',
'checkbox' => '.row>label>input[name=$name# value=$value# type=checkbox checked=$selected# required=$required# autofocus=$autofocus# accept=$accept#]+{$label#}',
//------------- TYPE BASED STYLES ---------------------//
'checkbox-array' => 'fieldset>legend{$label#}+section*options>label>input[name=$name# value=$value# type=checkbox checked=$selected# required=$required# autofocus=$autofocus# accept=$accept#]+{ $text#}',
'select-array' => 'label{$label#}+select[name=$name# required=$required# multiple style="height: auto;background-image: none; outline: inherit;"]>option[value=$value# selected=$selected#]{$text#}*options',
);
public static $bootstrap3 = array(
'form' => 'form[role=form id=$id# name=$name# method=$method# action=$action# enctype=$enctype#]',
'input' => '.form-group>label{$label#}+input.form-control[name=$name# value=$value# type=$type# required=$required# autofocus=$autofocus# placeholder=$default# accept=$accept#]',
'textarea' => '.form-group>label{$label#}+textarea.form-control[name=$name# required=$required# autofocus=$autofocus# placeholder=$default# rows=3]{$value#}',
'radio' => 'fieldset>legend{$label#}>.radio*options>label>input.radio[name=$name# value=$value# type=radio checked=$selected# required=$required#]{$text#}',
'select' => '.form-group>label{$label#}+select.form-control[name=$name# multiple=$multiple# required=$required#]>option[value]+option[value=$value# selected=$selected#]{$text#}*options',
'submit' => 'button.btn.btn-primary[type=submit]{$label#}',
'fieldset' => 'fieldset>legend{$label#}',
'checkbox' => '.checkbox>label>input[name=$name# value=$value# type=checkbox checked=$selected# required=$required# autofocus=$autofocus# accept=$accept#]+{$label#}',
//------------- TYPE BASED STYLES ---------------------//
'checkbox-array' => 'fieldset>legend{$label#}>.checkbox*options>label>input[name=$name# value=$value# type=checkbox checked=$selected# required=$required#]{$text#}',
'select-array' => '.form-group>label{$label#}+select.form-control[name=$name# multiple=$multiple# required=$required#] size=$options#>option[value=$value# selected=$selected#]{$text#}*options',
//------------- CUSTOM STYLES ---------------------//
'radio-inline' => '.form-group>label{$label# : &nbsp;}+label.radio-inline*options>input.radio[name=$name# value=$value# type=radio checked=$selected# required=$required#]+{$text#}',
);
public static $foundation5 = array(
'form' => 'form[id=$id# name=$name# method=$method# action=$action# enctype=$enctype#]',
'input' => 'label{$label#}+input[name=$name# value=$value# type=$type# required=$required# autofocus=$autofocus# placeholder=$default# accept=$accept#]',
'textarea' => 'label{$label#}+textarea[name=$name# required=$required# autofocus=$autofocus# placeholder=$default# rows=3]{$value#}',
'radio' => 'label{$label# : &nbsp;}+label.radio-inline*options>input.radio[name=$name# value=$value# type=radio checked=$selected# required=$required#]+{$text#}',
'select' => 'label{$label#}+select[name=$name# required=$required#]>option[value]+option[value=$value# selected=$selected#]{$text#}*options',
'submit' => 'button.button[type=submit]{$label#}',
'fieldset' => 'fieldset>legend{$label#}',
'checkbox' => 'label>input[name=$name# value=$value# type=checkbox checked=$selected# required=$required# autofocus=$autofocus# accept=$accept#]+{ $label#}',
//------------- TYPE BASED STYLES ---------------------//
'checkbox-array' => 'fieldset>legend{$label#}+label*options>input[name=$name# value=$value# type=checkbox checked=$selected# required=$required# autofocus=$autofocus# accept=$accept#]+{ $text#}',
'select-array' => 'label{$label#}+select[name=$name# required=$required# multiple style="height: auto;background-image: none; outline: inherit;"]>option[value=$value# selected=$selected#]{$text#}*options',
//------------- CUSTOM STYLES ---------------------//
);
}

View File

@@ -0,0 +1,434 @@
<?php
namespace Luracast\Restler\UI;
use Luracast\Restler\CommentParser;
use Luracast\Restler\Data\ApiMethodInfo;
use Luracast\Restler\Data\String;
use Luracast\Restler\Data\ValidationInfo;
use Luracast\Restler\Defaults;
use Luracast\Restler\Format\UploadFormat;
use Luracast\Restler\Format\UrlEncodedFormat;
use Luracast\Restler\iFilter;
use Luracast\Restler\RestException;
use Luracast\Restler\Restler;
use Luracast\Restler\Routes;
use Luracast\Restler\Scope;
use Luracast\Restler\UI\Tags as T;
use Luracast\Restler\User;
use Luracast\Restler\Util;
/**
* Utility class for automatically generating forms for the given http method
* and api url
*
* @category Framework
* @package Restler
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class Forms implements iFilter
{
const FORM_KEY = 'form_key';
public static $filterFormRequestsOnly = false;
public static $excludedPaths = array();
public static $style;
/**
* @var bool should we fill up the form using given data?
*/
public static $preFill = true;
/**
* @var ValidationInfo
*/
public static $validationInfo = null;
protected static $inputTypes = array(
'hidden',
'password',
'button',
'image',
'file',
'reset',
'submit',
'search',
'checkbox',
'radio',
'email',
'text',
'color',
'date',
'datetime',
'datetime-local',
'email',
'month',
'number',
'range',
'search',
'tel',
'time',
'url',
'week',
);
protected static $fileUpload = false;
private static $key = array();
/**
* @var ApiMethodInfo;
*/
private static $info;
/**
* 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;
}
public static function style($name, array $metadata, $type = '')
{
return isset($metadata[CommentParser::$embeddedDataName][$name])
? $metadata[CommentParser::$embeddedDataName][$name]
: (!empty($type) && isset(static::$style["$name-$type"])
? static::$style["$name-$type"]
: (isset(static::$style[$name])
? static::$style[$name]
: null
)
);
}
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;
}
/**
* @param ValidationInfo $p
*
* @param bool $dataOnly
*
* @return array|T
*/
public static function field(ValidationInfo $p, $dataOnly = false)
{
if (is_string($p->value)) {
//prevent XSS attacks
$p->value = htmlspecialchars($p->value, ENT_QUOTES | ENT_HTML401, 'UTF-8');
}
$type = $p->field ? : static::guessFieldType($p);
$tag = in_array($type, static::$inputTypes)
? 'input' : $type;
$options = array();
$name = $p->name;
$multiple = null;
if ($p->type == 'array' && $p->contentType != 'associative') {
$name .= '[]';
$multiple = true;
}
if ($p->choice) {
foreach ($p->choice as $i => $choice) {
$option = array('name' => $name, 'value' => $choice);
$option['text'] = isset($p->rules['select'][$i])
? $p->rules['select'][$i]
: $choice;
if ($choice == $p->value)
$option['selected'] = true;
$options[] = $option;
}
} elseif ($p->type == 'boolean' || $p->type == 'bool') {
if (String::beginsWith($type, 'radio')) {
$options[] = array('name' => $p->name, 'text' => ' Yes ',
'value' => 'true');
$options[] = array('name' => $p->name, 'text' => ' No ',
'value' => 'false');
if ($p->value || $p->default)
$options[0]['selected'] = true;
} else {
$r = array(
'tag' => $tag,
'name' => $name,
'type' => $type,
'label' => $p->label,
'value' => 'true',
'default' => $p->default,
);
$r['text'] = 'Yes';
if ($p->default) {
$r['selected'] = true;
}
}
}
if (empty($r)) {
$r = array(
'tag' => $tag,
'name' => $name,
'type' => $type,
'label' => $p->label,
'value' => $p->value,
'default' => $p->default,
'options' => & $options,
'multiple' => $multiple,
);
}
if ($type == 'file') {
static::$fileUpload = true;
$r['accept'] = implode(', ', UploadFormat::$allowedMimeTypes);
}
if (true === $p->required)
$r['required'] = true;
if (isset($p->rules['autofocus']))
$r['autofocus'] = true;
/*
echo "<pre>";
print_r($r);
echo "</pre>";
*/
if ($dataOnly)
return $r;
if (isset($p->rules['form']))
return Emmet::make($p->rules['form'], $r);
$m = static::$info->metadata;
$t = Emmet::make(static::style($type, $m, $p->type) ? : static::style($tag, $m, $p->type), $r);
return $t;
}
protected static function guessFieldType(ValidationInfo $p, $type = 'type')
{
if (in_array($p->$type, static::$inputTypes))
return $p->$type;
if ($p->choice)
return $p->type == 'array' ? 'checkbox' : 'select';
switch ($p->$type) {
case 'boolean':
return 'radio';
case 'int':
case 'number':
case 'float':
return 'number';
case 'array':
return static::guessFieldType($p, 'contentType');
}
if ($p->name == 'password')
return 'password';
return 'text';
}
/**
* 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];
}
/**
* Access verification method.
*
* API access will be denied when this method returns false
*
* @return boolean true when api access is allowed false otherwise
*
* @throws RestException 403 security violation
*/
public function __isAllowed()
{
if (session_id() == '') {
session_start();
}
/** @var Restler $restler */
$restler = $this->restler;
$url = $restler->url;
foreach (static::$excludedPaths as $exclude) {
if (empty($exclude)) {
if ($url == $exclude)
return true;
} elseif (String::beginsWith($url, $exclude)) {
return true;
}
}
$check = static::$filterFormRequestsOnly
? $restler->requestFormat instanceof UrlEncodedFormat || $restler->requestFormat instanceof UploadFormat
: true;
if (!empty($_POST) && $check) {
if (
isset($_POST[static::FORM_KEY]) &&
($target = Util::getRequestMethod() . ' ' . $restler->url) &&
isset($_SESSION[static::FORM_KEY][$target]) &&
$_POST[static::FORM_KEY] == $_SESSION[static::FORM_KEY][$target]
) {
return true;
}
throw new RestException(403, 'Insecure form submission');
}
return true;
}
}

View File

@@ -0,0 +1,208 @@
<?php
namespace Luracast\Restler\UI;
use Luracast\Restler\CommentParser;
use Luracast\Restler\Defaults;
use Luracast\Restler\Restler;
use Luracast\Restler\Routes;
use Luracast\Restler\Scope;
use Luracast\Restler\Util;
/**
* Utility class for automatically creating data to build an navigation interface
* based on available routes that are accessible by the current user
*
* @category Framework
* @package Restler
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class Nav
{
public static $root = 'home';
/**
* @var null|callable if the api methods are under access control mechanism
* you can attach a function here that returns true or false to determine
* visibility of a protected api method. this function will receive method
* info as the only parameter.
*/
public static $accessControlFunction = null;
/**
* @var array all paths beginning with any of the following will be excluded
* from documentation. if an empty string is given it will exclude the root
*/
public static $excludedPaths = array('');
/**
* @var array prefix additional menu items with one of the following syntax
* [$path => $text]
* [$path]
* [$path => ['text' => $text, 'url' => $url]]
*/
public static $prepends = array();
/**
* @var array suffix additional menu items with one of the following syntax
* [$path => $text]
* [$path]
* [$path => ['text' => $text, 'url' => $url]]
*/
public static $appends = array();
public static $addExtension = true;
protected static $extension = '';
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;
}
protected static function build(&$tree, $path,
$url = null, $text = null, $activeUrl = null)
{
$parts = explode('/', $path);
if (count($parts) == 1 && empty($parts[0]))
$parts = array(static::$root);
$p = & $tree;
$end = end($parts);
foreach ($parts as $part) {
if (!isset($p[$part])) {
$p[$part] = array(
'href' => '#',
'text' => static::title($part)
);
if ($part == $end) {
$p[$part]['class'] = $part;
if ($text)
$p[$part]['text'] = $text;
if (is_null($url)) {
if (empty($path) && !empty(static::$extension))
$path = 'index';
$p[$part]['href'] = Util::$restler->getBaseUrl()
. '/' . $path . static::$extension;
} else {
if (empty($url) && !empty(static::$extension))
$url = 'index';
$p[$part]['href'] = $url . static::$extension;
}
if ($path == $activeUrl) {
$p[$part]['active'] = true;
}
}
$p[$part]['children'] = array();
}
$p = & $p[$part]['children'];
}
}
protected static function title($name)
{
if (empty($name)) {
$name = static::$root;
} else {
$name = ltrim($name, '#');
}
return ucfirst(preg_replace(array('/(?<=[^A-Z])([A-Z])/', '/(?<=[^0-9])([0-9])/'), ' $0', $name));
}
}

View File

@@ -0,0 +1,282 @@
<?php
namespace Luracast\Restler\UI;
use ArrayAccess;
use Countable;
use Luracast\Restler\Util;
/**
* Utility class for generating html tags in an object oriented way
*
* @category Framework
* @package Restler
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*
* ============================ magic properties ==============================
* @property Tags parent parent tag
* ============================== magic methods ===============================
* @method Tags name(string $value) name attribute
* @method Tags action(string $value) action attribute
* @method Tags placeholder(string $value) placeholder attribute
* @method Tags value(string $value) value attribute
* @method Tags required(boolean $value) required attribute
* @method Tags class(string $value) required attribute
*
* =========================== static magic methods ============================
* @method static Tags form() creates a html form
* @method static Tags input() creates a html input element
* @method static Tags button() creates a html button element
*
*/
class Tags implements ArrayAccess, Countable
{
public static $humanReadable = true;
public static $initializer = null;
protected static $instances = array();
public $prefix = '';
public $indent = ' ';
public $tag;
protected $attributes = array();
protected $children = array();
protected $_parent;
public function __construct($name = null, array $children = array())
{
$this->tag = $name;
$c = array();
foreach ($children as $child) {
is_array($child)
? $c = array_merge($c, $child)
: $c [] = $child;
}
$this->markAsChildren($c);
$this->children = $c;
if (static::$initializer)
call_user_func_array(static::$initializer, array(& $this));
}
/**
* 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);
}
/**
* @param $name
* @param array $children
*
* @return Tags
*/
public static function __callStatic($name, array $children)
{
return new static($name, $children);
}
public function toString($prefix = '', $indent = ' ')
{
$this->prefix = $prefix;
$this->indent = $indent;
return $this->__toString();
}
public function __toString()
{
$children = '';
if (static::$humanReadable) {
$lineBreak = false;
foreach ($this->children as $key => $child) {
$prefix = $this->prefix;
if (!is_null($this->tag))
$prefix .= $this->indent;
if ($child instanceof $this) {
$child->prefix = $prefix;
$child->indent = $this->indent;
$children .= PHP_EOL . $child;
$lineBreak = true;
} else {
$children .= $child;
}
}
if ($lineBreak)
$children .= PHP_EOL . $this->prefix;
} else {
$children = implode('', $this->children);
}
if (is_null($this->tag))
return $children;
$attributes = '';
foreach ($this->attributes as $attribute => &$value)
$attributes .= " $attribute=\"$value\"";
if (count($this->children))
return static::$humanReadable
? "$this->prefix<{$this->tag}{$attributes}>"
. "$children"
. "</{$this->tag}>"
: "<{$this->tag}{$attributes}>$children</{$this->tag}>";
return "$this->prefix<{$this->tag}{$attributes}/>";
}
public function toArray()
{
$r = array();
$r['attributes'] = $this->attributes;
$r['tag'] = $this->tag;
$children = array();
foreach ($this->children as $key => $child) {
$children[$key] = $child instanceof $this
? $child->toArray()
: $child;
}
$r['children'] = $children;
return $r;
}
/**
* 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;
}
public function __get($name)
{
if ('parent' == $name)
return $this->_parent;
if (isset($this->attributes[$name]))
return $this->attributes[$name];
return;
}
public function __set($name, $value)
{
if ('parent' == $name) {
if ($this->_parent) {
unset($this->_parent[array_search($this, $this->_parent->children)]);
}
if (!empty($value)) {
$value[] = $this;
}
}
}
public function __isset($name)
{
return isset($this->attributes[$name]);
}
/**
* @param $attribute
* @param $value
*
* @return Tags
*/
public function __call($attribute, $value)
{
if (is_null($value)) {
return isset($this->attributes[$attribute])
? $this->attributes[$attribute]
: null;
}
$value = $value[0];
if (is_null($value)) {
unset($this->attributes[$attribute]);
return $this;
}
$this->attributes[$attribute] = is_bool($value)
? ($value ? 'true' : 'false')
: @(string)$value;
return $this;
}
public function offsetGet($index)
{
if ($this->offsetExists($index)) {
return $this->children[$index];
}
return false;
}
public function offsetExists($index)
{
return isset($this->children[$index]);
}
public function offsetSet($index, $value)
{
if ($index) {
$this->children[$index] = $value;
} elseif (is_array($value)) {
$c = array();
foreach ($value as $child) {
is_array($child)
? $c = array_merge($c, $child)
: $c [] = $child;
}
$this->markAsChildren($c);
$this->children += $c;
} else {
$c = array($value);
$this->markAsChildren($c);
$this->children[] = $value;
}
return true;
}
public function offsetUnset($index)
{
$this->children[$index]->_parent = null;
unset($this->children[$index]);
return true;
}
public function getContents()
{
return $this->children;
}
public function count()
{
return count($this->children);
}
private function markAsChildren(& $children)
{
foreach ($children as $i => $child) {
if (is_string($child))
continue;
if (!is_object($child)) {
unset($children[$i]);
continue;
}
//echo $child;
if (isset($child->_parent) && $child->_parent != $this) {
//remove from current parent
unset($child->_parent[array_search($child, $child->_parent->children)]);
}
$child->_parent = $this;
}
}
}

View File

@@ -0,0 +1,100 @@
<?php
namespace Luracast\Restler;
/**
* Information gathered about the api user is kept here using static methods
* and properties for other classes to make use of them.
* Typically Authentication classes populate them
*
* @category Framework
* @package restler
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class User implements iIdentifyUser
{
private static $initialized = false;
public static $id = null;
public static $cacheId = null;
public static $ip;
public static $browser = '';
public static $platform = '';
public static function init()
{
static::$initialized = true;
static::$ip = static::getIpAddress();
}
public static function getUniqueIdentifier($includePlatform = false)
{
if (!static::$initialized) static::init();
return static::$id ? : base64_encode('ip:' . ($includePlatform
? static::$ip . '-' . static::$platform
: static::$ip
));
}
public static function getIpAddress($ignoreProxies = false)
{
foreach (array('HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR',
'HTTP_X_FORWARDED', 'HTTP_X_CLUSTER_CLIENT_IP',
'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED',
'REMOTE_ADDR') as $key) {
if (array_key_exists($key, $_SERVER) === true) {
foreach (explode(',', $_SERVER[$key]) as $ip) {
$ip = trim($ip); // just to be safe
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4
| FILTER_FLAG_NO_PRIV_RANGE
| FILTER_FLAG_NO_RES_RANGE) !== false
) {
return $ip;
}
}
}
}
}
/**
* Authentication classes should call this method
*
* @param string $id user id as identified by the authentication classes
*
* @return void
*/
public static function setUniqueIdentifier($id)
{
static::$id = $id;
}
/**
* User identity to be used for caching purpose
*
* When the dynamic cache service places an object in the cache, it needs to
* label it with a unique identifying string known as a cache ID. This
* method gives that identifier
*
* @return string
*/
public static function getCacheIdentifier()
{
return static::$cacheId ?: static::$id;
}
/**
* User identity for caching purpose
*
* In a role based access control system this will be based on role
*
* @param $id
*
* @return void
*/
public static function setCacheIdentifier($id)
{
static::$cacheId = $id;
}
}

View File

@@ -0,0 +1,201 @@
<?php
namespace Luracast\Restler;
/**
* Describe the purpose of this class/interface/trait
*
* @category Framework
* @package Restler
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class Util
{
/**
* @var Restler instance injected at runtime
*/
public static $restler;
/**
* verify if the given data type string is scalar or not
*
* @static
*
* @param string $type data type as string
*
* @return bool true or false
*/
public static function isObjectOrArray($type)
{
if (is_array($type)) {
foreach ($type as $t) {
if (static::isObjectOrArray($t)) {
return true;
}
}
return false;
}
return !(boolean)strpos('|bool|boolean|int|float|string|', $type);
}
/**
* Get the value deeply nested inside an array / object
*
* Using isset() to test the presence of nested value can give a false positive
*
* This method serves that need
*
* When the deeply nested property is found its value is returned, otherwise
* false is returned.
*
* @param array $from array to extract the value from
* @param string|array $key ... pass more to go deeply inside the array
* alternatively you can pass a single array
*
* @return null|mixed null when not found, value otherwise
*/
public static function nestedValue($from, $key/**, $key2 ... $key`n` */)
{
if (is_array($key)) {
$keys = $key;
} else {
$keys = func_get_args();
array_shift($keys);
}
foreach ($keys as $key) {
if (is_array($from) && isset($from[$key])) {
$from = $from[$key];
continue;
} elseif (is_object($from) && isset($from->{$key})) {
$from = $from->{$key};
continue;
}
return null;
}
return $from;
}
public static function getResourcePath($className,
$resourcePath = null,
$prefix = '')
{
if (is_null($resourcePath)) {
if (Defaults::$autoRoutingEnabled) {
$resourcePath = strtolower($className);
if (false !== ($index = strrpos($className, '\\')))
$resourcePath = substr($resourcePath, $index + 1);
if (false !== ($index = strrpos($resourcePath, '_')))
$resourcePath = substr($resourcePath, $index + 1);
} else {
$resourcePath = '';
}
} else
$resourcePath = trim($resourcePath, '/');
if (strlen($resourcePath) > 0)
$resourcePath .= '/';
return $prefix . $resourcePath;
}
/**
* Compare two strings and remove the common
* sub string from the first string and return it
*
* @static
*
* @param string $fromPath
* @param string $usingPath
* @param string $char
* optional, set it as
* blank string for char by char comparison
*
* @return string
*/
public static function removeCommonPath($fromPath, $usingPath, $char = '/')
{
if (empty($fromPath))
return '';
$fromPath = explode($char, $fromPath);
$usingPath = explode($char, $usingPath);
while (count($usingPath)) {
if (count($fromPath) && $fromPath[0] == $usingPath[0]) {
array_shift($fromPath);
} else {
break;
}
array_shift($usingPath);
}
return implode($char, $fromPath);
}
/**
* Parses the request to figure out the http request type
*
* @static
*
* @return string which will be one of the following
* [GET, POST, PUT, PATCH, DELETE]
* @example GET
*/
public static function getRequestMethod()
{
$method = $_SERVER['REQUEST_METHOD'];
if (isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])) {
$method = $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'];
} elseif (
!empty(Defaults::$httpMethodOverrideProperty)
&& isset($_REQUEST[Defaults::$httpMethodOverrideProperty])
) {
// support for exceptional clients who can't set the header
$m = strtoupper($_REQUEST[Defaults::$httpMethodOverrideProperty]);
if ($m == 'PUT' || $m == 'DELETE' ||
$m == 'POST' || $m == 'PATCH'
) {
$method = $m;
}
}
// support for HEAD request
if ($method == 'HEAD') {
$method = 'GET';
}
return $method;
}
/**
* Pass any content negotiation header such as Accept,
* Accept-Language to break it up and sort the resulting array by
* the order of negotiation.
*
* @static
*
* @param string $accept header value
*
* @return array sorted by the priority
*/
public static function sortByPriority($accept)
{
$acceptList = array();
$accepts = explode(',', strtolower($accept));
if (!is_array($accepts)) {
$accepts = array($accepts);
}
foreach ($accepts as $pos => $accept) {
$parts = explode(';q=', trim($accept));
$type = array_shift($parts);
$quality = count($parts) ?
floatval(array_shift($parts)) :
(1000 - $pos) / 1000;
$acceptList[$type] = $quality;
}
arsort($acceptList);
return $acceptList;
}
public static function getShortName($className)
{
$className = explode('\\', $className);
return end($className);
}
}

View File

@@ -0,0 +1,10 @@
<?php
/**
* Interface iAuthenticate only exists for compatibility mode for Restler 2 and below, it should
* not be used otherwise.
*/
interface iAuthenticate
{
public function __isAuthenticated();
}

View File

@@ -0,0 +1,14 @@
<?php
/**
* Restler 1 compatibility mode enabler
*/
use Luracast\Restler\Defaults;
//changes in iAuthenticate
Defaults::$authenticationMethod = 'isAuthenticated';
include __DIR__ . '/iAuthenticate.php';
//changes in routing
Defaults::$autoRoutingEnabled = false;
Defaults::$smartParameterParsing = false;
Defaults::$autoValidationEnabled = false;

View File

@@ -0,0 +1,40 @@
<?php
/**
* Restler 2 compatibility mode enabler
*/
use Luracast\Restler\Defaults;
use Luracast\Restler\AutoLoader;
use Luracast\Restler\CommentParser;
//changes in auto loading
$classMap = array();
//find lowercase php files representing a class/interface
foreach (explode(PATH_SEPARATOR, get_include_path()) as $path)
foreach (new DirectoryIterator($path) as $fileInfo)
if ($fileInfo->isFile()
&& 'php' === $fileInfo->getExtension()
&& ctype_lower($fileInfo->getBasename('.php'))
&& preg_match(
'/^ *(class|interface|abstract +class)'
. ' +([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)/m',
file_get_contents($fileInfo->getPathname()),
$matches
)
)
$classMap[$matches[2]] = $fileInfo->getPathname();
AutoLoader::seen($classMap);
//changes in iAuthenticate
Defaults::$authenticationMethod = '__isAuthenticated';
include __DIR__ . '/iAuthenticate.php';
//changes in auto routing
Defaults::$smartAutoRouting = false;
Defaults::$smartParameterParsing = false;
Defaults::$autoValidationEnabled = false;
//changes in parsing embedded data in comments
CommentParser::$embeddedDataPattern = '/\((\S+)\)/ms';
CommentParser::$embeddedDataIndex = 1;

View File

@@ -0,0 +1,70 @@
{
"name":"restler/framework",
"description":"Just the Restler Framework without the tests and examples",
"type":"library",
"keywords":["server","api","framework","REST"],
"homepage":"http://luracast.com/products/restler/",
"license":"LGPL-2.1",
"authors":[
{
"name":"Luracast",
"email":"arul@luracast.com"
},
{
"name":"Nick nickl- Lombard",
"email":"github@jigsoft.co.za"
}
],
"extra":{
"branch-alias":{
"master":"v3.0.x-dev"
}
},
"suggest":{
"luracast/explorer":"Restler's very own api explorer (see require-dev for details)",
"rodneyrehm/plist":"Restler supports tho Apple plist xml format (see require-dev for details)",
"zendframework/zendamf":"Support for the amf document format (see require-dev for details)",
"symfony/yaml":"Restler can produce content in yaml format as well (see require-dev for details)",
"twig/twig":"Restler can render HtmlView using twig templates (see require-dev for details)",
"mustache/mustache":"Restler can render HtmlView using mustache/handlebar templates (see require-dev for details)",
"bshaffer/oauth2-server-php":"Restler can provide OAuth2 authentication using this library (see require-dev for details)"
},
"require":{
"php":">=5.3.0"
},
"require-dev":{
"luracast/explorer":"*",
"rodneyrehm/plist":"dev-master",
"zendframework/zendamf":"dev-master",
"symfony/yaml":"*",
"mustache/mustache": "dev-master",
"twig/twig": "v1.13.0",
"bshaffer/oauth2-server-php":"v1.0"
},
"repositories":[
{
"type":"vcs",
"url":"https://github.com/zendframework/ZendAmf.git"
},
{
"type":"package",
"package":{
"name":"luracast/explorer",
"version":"v3.0.0",
"dist":{
"type":"zip",
"url":"https://github.com/Luracast/Restler-API-Explorer/zipball/v3.0.0"
}
}
}
],
"autoload":{
"psr-0":{
"Luracast\\Restler":""
}
},
"target-dir": "Luracast/Restler",
"replace": {
"luracast/restler":"3.*"
}
}

23
htdocs/includes/restler/composer.lock generated Normal file
View File

@@ -0,0 +1,23 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"hash": "ee84444dcf34101555d20a813d528c44",
"packages": [],
"packages-dev": null,
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {
"rodneyrehm/plist": 20,
"zendframework/zendamf": 20,
"mustache/mustache": 20
},
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"php": ">=5.3.0"
},
"platform-dev": []
}

View File

@@ -0,0 +1,25 @@
<?php
namespace Luracast\Restler;
/**
* Interface for creating authentication classes
*
* @category Framework
* @package Restler
* @subpackage auth
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
interface iAuthenticate extends iFilter
{
/**
* @return string string to be used with WWW-Authenticate header
* @example Basic
* @example Digest
* @example OAuth
*/
public function __getWWWAuthenticateString();
}

View File

@@ -0,0 +1,63 @@
<?php
namespace Luracast\Restler;
/**
* Interface for the cache system that manages caching of given data
*
* @category Framework
* @package Restler
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
interface iCache
{
/**
* store data in the cache
*
* @abstract
*
* @param string $name
* @param mixed $data
*
* @return boolean true if successful
*/
public function set($name, $data);
/**
* retrieve data from the cache
*
* @abstract
*
* @param string $name
* @param bool $ignoreErrors
*
* @return mixed
*/
public function get($name, $ignoreErrors = false);
/**
* delete data from the cache
*
* @abstract
*
* @param string $name
* @param bool $ignoreErrors
*
* @return boolean true if successful
*/
public function clear($name, $ignoreErrors = false);
/**
* check if the given name is cached
*
* @abstract
*
* @param string $name
*
* @return boolean true if cached
*/
public function isCached($name);
}

View File

@@ -0,0 +1,36 @@
<?php
namespace Luracast\Restler;
use Exception;
/**
* Interface for composing response
*
* @category Framework
* @package Restler
* @subpackage result
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
interface iCompose {
/**
* Result of an api call is passed to this method
* to create a standard structure for the data
*
* @param mixed $result can be a primitive or array or object
*/
public function response($result);
/**
* When the api call results in RestException this method
* will be called to return the error message
*
* @param RestException $exception exception that has reasons for failure
*
* @return
*/
public function message(RestException $exception);
}

View File

@@ -0,0 +1,30 @@
<?php
namespace Luracast\Restler;
/**
* Interface for creating classes that perform authentication/access
* verification
*
* @category Framework
* @package Restler
* @subpackage auth
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
interface iFilter
{
/**
* Access verification method.
*
* API access will be denied when this method returns false
*
* @abstract
* @return boolean true when api access is allowed false otherwise
*/
public function __isAllowed();
}

View File

@@ -0,0 +1,63 @@
<?php
namespace Luracast\Restler;
/**
* Interface to identify the user
*
* When the user is known we will be able to monitor, rate limit and do more
*
* @category Framework
* @package restler
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
interface iIdentifyUser
{
/**
* A way to uniquely identify the current api consumer
*
* When his user id is known it should be used otherwise ip address
* can be used
*
* @param bool $includePlatform Should we consider user alone or should
* consider the application/platform/device
* as well for generating unique id
*
* @return string
*/
public static function getUniqueIdentifier($includePlatform = false);
/**
* User identity to be used for caching purpose
*
* When the dynamic cache service places an object in the cache, it needs to
* label it with a unique identifying string known as a cache ID. This
* method gives that identifier
*
* @return string
*/
public static function getCacheIdentifier();
/**
* Authentication classes should call this method
*
* @param string $id user id as identified by the authentication classes
*
* @return void
*/
public static function setUniqueIdentifier($id);
/**
* User identity for caching purpose
*
* In a role based access control system this will be based on role
*
* @param $id
*
* @return void
*/
public static function setCacheIdentifier($id);
}

View File

@@ -0,0 +1,11 @@
<?php
namespace Luracast\Restler;
interface iProvideMultiVersionApi {
/**
* Maximum api version supported by the api class
* @return int
*/
public static function __getMaximumSupportedVersion();
}

View File

@@ -0,0 +1,32 @@
<?php
namespace Luracast\Restler;
/**
* Api classes or filter classes can implement this interface to know about
* authentication status
*
* @category Framework
* @package Restler
* @author R.Arul Kumaran <arul@luracast.com>
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
interface iUseAuthentication
{
/**
* This method will be called first for filter classes and api classes so
* that they can respond accordingly for filer method call and api method
* calls
*
* @abstract
*
* @param bool $isAuthenticated passes true when the authentication is
* done false otherwise
*
* @return mixed
*/
public function __setAuthenticationStatus($isAuthenticated=false);
}

View File

@@ -0,0 +1,7 @@
<?php
// autoload.php @generated by Composer
require_once __DIR__ . '/composer' . '/autoload_real.php';
return ComposerAutoloaderInite65e15efc7e9ea1f2b7ba7fa697ba485::getLoader();

View File

@@ -0,0 +1,413 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Autoload;
/**
* ClassLoader implements a PSR-0 class loader
*
* See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md
*
* $loader = new \Composer\Autoload\ClassLoader();
*
* // register classes with namespaces
* $loader->add('Symfony\Component', __DIR__.'/component');
* $loader->add('Symfony', __DIR__.'/framework');
*
* // activate the autoloader
* $loader->register();
*
* // to enable searching the include path (eg. for PEAR packages)
* $loader->setUseIncludePath(true);
*
* In this example, if you try to use a class in the Symfony\Component
* namespace or one of its children (Symfony\Component\Console for instance),
* the autoloader will first look for the class under the component/
* directory, and it will then fallback to the framework/ directory if not
* found before giving up.
*
* This class is loosely based on the Symfony UniversalClassLoader.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class ClassLoader
{
// PSR-4
private $prefixLengthsPsr4 = array();
private $prefixDirsPsr4 = array();
private $fallbackDirsPsr4 = array();
// PSR-0
private $prefixesPsr0 = array();
private $fallbackDirsPsr0 = array();
private $useIncludePath = false;
private $classMap = array();
private $classMapAuthoritative = false;
public function getPrefixes()
{
if (!empty($this->prefixesPsr0)) {
return call_user_func_array('array_merge', $this->prefixesPsr0);
}
return array();
}
public function getPrefixesPsr4()
{
return $this->prefixDirsPsr4;
}
public function getFallbackDirs()
{
return $this->fallbackDirsPsr0;
}
public function getFallbackDirsPsr4()
{
return $this->fallbackDirsPsr4;
}
public function getClassMap()
{
return $this->classMap;
}
/**
* @param array $classMap Class to filename map
*/
public function addClassMap(array $classMap)
{
if ($this->classMap) {
$this->classMap = array_merge($this->classMap, $classMap);
} else {
$this->classMap = $classMap;
}
}
/**
* Registers a set of PSR-0 directories for a given prefix, either
* appending or prepending to the ones previously set for this prefix.
*
* @param string $prefix The prefix
* @param array|string $paths The PSR-0 root directories
* @param bool $prepend Whether to prepend the directories
*/
public function add($prefix, $paths, $prepend = false)
{
if (!$prefix) {
if ($prepend) {
$this->fallbackDirsPsr0 = array_merge(
(array) $paths,
$this->fallbackDirsPsr0
);
} else {
$this->fallbackDirsPsr0 = array_merge(
$this->fallbackDirsPsr0,
(array) $paths
);
}
return;
}
$first = $prefix[0];
if (!isset($this->prefixesPsr0[$first][$prefix])) {
$this->prefixesPsr0[$first][$prefix] = (array) $paths;
return;
}
if ($prepend) {
$this->prefixesPsr0[$first][$prefix] = array_merge(
(array) $paths,
$this->prefixesPsr0[$first][$prefix]
);
} else {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$this->prefixesPsr0[$first][$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-4 directories for a given namespace, either
* appending or prepending to the ones previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param array|string $paths The PSR-0 base directories
* @param bool $prepend Whether to prepend the directories
*
* @throws \InvalidArgumentException
*/
public function addPsr4($prefix, $paths, $prepend = false)
{
if (!$prefix) {
// Register directories for the root namespace.
if ($prepend) {
$this->fallbackDirsPsr4 = array_merge(
(array) $paths,
$this->fallbackDirsPsr4
);
} else {
$this->fallbackDirsPsr4 = array_merge(
$this->fallbackDirsPsr4,
(array) $paths
);
}
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
// Register directories for a new namespace.
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
} elseif ($prepend) {
// Prepend directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
(array) $paths,
$this->prefixDirsPsr4[$prefix]
);
} else {
// Append directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$this->prefixDirsPsr4[$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-0 directories for a given prefix,
* replacing any others previously set for this prefix.
*
* @param string $prefix The prefix
* @param array|string $paths The PSR-0 base directories
*/
public function set($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr0 = (array) $paths;
} else {
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
}
}
/**
* Registers a set of PSR-4 directories for a given namespace,
* replacing any others previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param array|string $paths The PSR-4 base directories
*
* @throws \InvalidArgumentException
*/
public function setPsr4($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr4 = (array) $paths;
} else {
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
}
}
/**
* Turns on searching the include path for class files.
*
* @param bool $useIncludePath
*/
public function setUseIncludePath($useIncludePath)
{
$this->useIncludePath = $useIncludePath;
}
/**
* Can be used to check if the autoloader uses the include path to check
* for classes.
*
* @return bool
*/
public function getUseIncludePath()
{
return $this->useIncludePath;
}
/**
* Turns off searching the prefix and fallback directories for classes
* that have not been registered with the class map.
*
* @param bool $classMapAuthoritative
*/
public function setClassMapAuthoritative($classMapAuthoritative)
{
$this->classMapAuthoritative = $classMapAuthoritative;
}
/**
* Should class lookup fail if not found in the current class map?
*
* @return bool
*/
public function isClassMapAuthoritative()
{
return $this->classMapAuthoritative;
}
/**
* Registers this instance as an autoloader.
*
* @param bool $prepend Whether to prepend the autoloader or not
*/
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
}
/**
* Unregisters this instance as an autoloader.
*/
public function unregister()
{
spl_autoload_unregister(array($this, 'loadClass'));
}
/**
* Loads the given class or interface.
*
* @param string $class The name of the class
* @return bool|null True if loaded, null otherwise
*/
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
includeFile($file);
return true;
}
}
/**
* Finds the path to the file where the class is defined.
*
* @param string $class The name of the class
*
* @return string|false The path if found, false otherwise
*/
public function findFile($class)
{
// work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731
if ('\\' == $class[0]) {
$class = substr($class, 1);
}
// class map lookup
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}
if ($this->classMapAuthoritative) {
return false;
}
$file = $this->findFileWithExtension($class, '.php');
// Search for Hack files if we are running on HHVM
if ($file === null && defined('HHVM_VERSION')) {
$file = $this->findFileWithExtension($class, '.hh');
}
if ($file === null) {
// Remember that this class does not exist.
return $this->classMap[$class] = false;
}
return $file;
}
private function findFileWithExtension($class, $ext)
{
// PSR-4 lookup
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
$first = $class[0];
if (isset($this->prefixLengthsPsr4[$first])) {
foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) {
if (0 === strpos($class, $prefix)) {
foreach ($this->prefixDirsPsr4[$prefix] as $dir) {
if (is_file($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) {
return $file;
}
}
}
}
}
// PSR-4 fallback dirs
foreach ($this->fallbackDirsPsr4 as $dir) {
if (is_file($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
return $file;
}
}
// PSR-0 lookup
if (false !== $pos = strrpos($class, '\\')) {
// namespaced class name
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
} else {
// PEAR-like class name
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
}
if (isset($this->prefixesPsr0[$first])) {
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
if (0 === strpos($class, $prefix)) {
foreach ($dirs as $dir) {
if (is_file($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
}
}
}
// PSR-0 fallback dirs
foreach ($this->fallbackDirsPsr0 as $dir) {
if (is_file($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
// PSR-0 include paths.
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
return $file;
}
}
}
/**
* Scope isolated include.
*
* Prevents access to $this/self from included files.
*/
function includeFile($file)
{
include $file;
}

View File

@@ -0,0 +1,9 @@
<?php
// autoload_classmap.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
);

View File

@@ -0,0 +1,10 @@
<?php
// autoload_namespaces.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'Luracast\\Restler' => array($baseDir . '/'),
);

View File

@@ -0,0 +1,9 @@
<?php
// autoload_psr4.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
);

View File

@@ -0,0 +1,70 @@
<?php
// autoload_real.php @generated by Composer
class ComposerAutoloaderInite65e15efc7e9ea1f2b7ba7fa697ba485
{
private static $loader;
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}
spl_autoload_register(array('ComposerAutoloaderInite65e15efc7e9ea1f2b7ba7fa697ba485', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
spl_autoload_unregister(array('ComposerAutoloaderInite65e15efc7e9ea1f2b7ba7fa697ba485', 'loadClassLoader'));
$map = require __DIR__ . '/autoload_namespaces.php';
foreach ($map as $namespace => $path) {
$loader->set($namespace, $path);
}
$map = require __DIR__ . '/autoload_psr4.php';
foreach ($map as $namespace => $path) {
$loader->setPsr4($namespace, $path);
}
$classMap = require __DIR__ . '/autoload_classmap.php';
if ($classMap) {
$loader->addClassMap($classMap);
}
spl_autoload_register(array('ComposerAutoloaderInite65e15efc7e9ea1f2b7ba7fa697ba485', 'autoload'), true, true);
$loader->register(true);
return $loader;
}
public static function autoload($class)
{
$dir = dirname(dirname(__DIR__)) . '/';
$prefixes = array('Luracast\\Restler');
foreach ($prefixes as $prefix) {
if (0 !== strpos($class, $prefix)) {
continue;
}
$path = $dir . implode('/', array_slice(explode('\\', $class), 2)).'.php';
if (!$path = stream_resolve_include_path($path)) {
return false;
}
require $path;
return true;
}
}
}
function composerRequiree65e15efc7e9ea1f2b7ba7fa697ba485($file)
{
require $file;
}

View File

@@ -0,0 +1 @@
[]

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,169 @@
<?php
use Luracast\Restler\Restler;
use Luracast\Restler\Util;
$template_vars = $data;//get_defined_vars();
unset($template_vars['response']);
unset($template_vars['api']);
unset($template_vars['request']);
unset($template_vars['stages']);
$template_vars['request'] = $data['request'];
$template_vars['stages'] = $data['stages'];
$call_trace = '';
function exceptions()
{
global $call_trace;
$r = Util::$restler;
$source = $r->_exceptions;
if (count($source)) {
$source = end($source);
$traces = array();
do {
$traces += $source->getTrace();
} while ($source = $source->getPrevious());
$traces += debug_backtrace();
$call_trace
= parse_backtrace($traces, 0);
} else {
$call_trace
= parse_backtrace(debug_backtrace());
}
}
exceptions();
function parse_backtrace($raw, $skip = 1)
{
$output = "";
foreach ($raw as $entry) {
if ($skip-- > 0) {
continue;
}
//$output .= print_r($entry, true) . "\n";
$output .= "\nFile: " . $entry['file'] . " (Line: " . $entry['line'] . ")\n";
if (isset($entry['class']))
$output .= $entry['class'] . "::";
$output .= $entry['function']
. "( " . json_encode($entry['args']) . " )\n";
}
return $output;
}
//print_r(get_defined_vars());
//print_r($response);
$icon;
if ($success && isset($api)) {
$arguments = implode(', ', $api->parameters);
$icon = "<icon class=\"success\"></icon>";
$title = "{$api->className}::"
. "{$api->methodName}({$arguments})";
} else {
if (isset($response['error']['message'])) {
$icon = '<icon class="denied"></icon>';
$title = end(explode(':',$response['error']['message']));
} else {
$icon = '<icon class="warning"></icon>';
$title = 'No Matching Resource';
}
}
function render($data, $shadow=true)
{
$r = '';
if (empty($data))
return $r;
$r .= $shadow ? "<ul class=\"shadow\">\n": "<ul>\n";
if (is_array($data)) {
// field name
foreach ($data as $key => $value) {
$r .= '<li>';
$r .= is_numeric($key)
? "<strong>[$key]</strong> "
: "<strong>$key: </strong>";
$r .= '<span>';
if (is_array($value)) {
// recursive
$r .= render($value,false);
} else {
// value, with hyperlinked hyperlinks
if (is_bool($value)) {
$value = $value ? 'true' : 'false';
}
$value = htmlentities($value, ENT_COMPAT, 'UTF-8');
if (strpos($value, 'http://') === 0) {
$r .= '<a href="' . $value . '">' . $value . '</a>';
} else {
$r .= $value;
}
}
$r .= "</span></li>\n";
}
} elseif (is_bool($data)) {
$r .= '<li>' . ($data ? 'true' : 'false') . '</li>';
} else {
$r .= "<li><strong>$data</strong></li>";
}
$r .= "</ul>\n";
return $r;
}
$reqHeadersArr = array();
$requestHeaders = $_SERVER['REQUEST_METHOD'] . ' ' . $_SERVER['REQUEST_URI'] . ' ' . $_SERVER['SERVER_PROTOCOL'] . PHP_EOL;
foreach ($reqHeadersArr as $key => $value) {
if ($key == 'Host')
continue;
$requestHeaders .= "$key: $value" . PHP_EOL;
}
// $requestHeaders = $this->encode(apache_request_headers(), FALSE,
// FALSE);
$responseHeaders = implode(PHP_EOL, headers_list()).PHP_EOL.'Status: HTTP/1.1 ';
$responseHeaders .= Util::$restler->responseCode.' '.\Luracast\Restler\RestException::$codes[Util::$restler->responseCode];
?>
<!DOCTYPE html>
<html>
<head>
<title><?php echo $title?></title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<style>
<?php include __DIR__.'/debug.css'; ?>
</style>
</head>
<body>
<div id="breadcrumbs-one">
<?php
if(Util::$restler->exception){
$stages = Util::$restler->exception->getStages();
$curStage = Util::$restler->exception->getStage();
foreach($stages['success'] as $stage){
echo "<a href=\"#\">$stage</a>";
}
foreach($stages['failure'] as $stage){
echo '<a href="#" class="failure">'
. $stage
. ($stage==$curStage ? ' <span class="state"/> ' : '')
. '</a>';
}
} else {
foreach(Util::$restler->_events as $stage){
echo "<a href=\"#\">$stage</a>";
}
}
?>
</div>
<header>
<h1><?php echo $title ?></h1>
</header>
<article>
<h2>Response:<right><?php echo $icon;?></right></h2>
<pre class="header"><?php echo $responseHeaders ?></pre>
<?php echo render($response); ?>
<h2>Additional Template Data:</h2>
<?php echo render($template_vars); ?>
<p>Restler v<?php echo Restler::VERSION?></p>
</article>
</body>
</html>