Update Restler to 3.0.0RC6

This commit is contained in:
nka11 2016-04-21 08:13:19 +02:00
parent 2d558174f2
commit b60389cec4
84 changed files with 12995 additions and 583 deletions

View File

@ -13,7 +13,7 @@ use Luracast\Restler\iCache;
* @copyright 2013 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
* @version 3.0.0rc6
*/
class ApcCache implements iCache
{

View File

@ -12,7 +12,7 @@ namespace Luracast\Restler {
* @subpackage helper
* @author Nick Lombard <github@jigsoft.co.za>
* @copyright 2012 Luracast
* @version 3.0.0rc5
* @version 3.0.0rc6
*/
class AutoLoader
{
@ -301,8 +301,6 @@ class AutoLoader
*/
private function alias($className, $currentClass)
{
if ($className == 'Luracast\Restler\string') return;
if ($className == 'Luracast\Restler\mixed') return;
if ($className != $currentClass
&& false !== strpos($className, $currentClass))
if (!class_exists($currentClass, false)

View File

@ -2,6 +2,7 @@
namespace Luracast\Restler;
use Exception;
use Luracast\Restler\Data\Text;
/**
* Parses the PHPDoc comments for metadata. Inspired by `Documentor` code base.
@ -13,7 +14,7 @@ use Exception;
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
* @version 3.0.0rc6
*/
class CommentParser
{
@ -50,6 +51,15 @@ class CommentParser
*/
public static $arrayDelimiter = ',';
/**
* @var array annotations that support array value
*/
public static $allowsArrayValue = array(
'choice' => true,
'select' => true,
'properties' => true,
);
/**
* character sequence used to escape \@
*/
@ -195,7 +205,7 @@ class CommentParser
list(, $param, $value) = preg_split('/\@|\s/', $line, 3)
+ array('', '', '');
list($value, $embedded) = $this->parseEmbeddedData($value);
$value = array_filter(preg_split('/\s+/msu', $value));
$value = array_filter(preg_split('/\s+/msu', $value), 'strlen');
$this->parseParam($param, $value, $embedded);
}
return $this->_data;
@ -214,6 +224,9 @@ class CommentParser
$allowMultiple = false;
switch ($param) {
case 'param' :
case 'property' :
case 'property-read' :
case 'property-write' :
$value = $this->formatParam($value);
$allowMultiple = true;
break;
@ -280,7 +293,12 @@ class CommentParser
$value[self::$embeddedDataName]
+= $data[$param][self::$embeddedDataName];
}
$data[$param] = $value + $data[$param];
if (!is_array($data[$param])) {
$data[$param] = array('description' => (string) $data[$param]);
}
if (is_array($value)) {
$data[$param] = $value + $data[$param];
}
}
}
@ -303,14 +321,15 @@ class CommentParser
}
while (preg_match('/{@(\w+)\s?([^}]*)}/ms', $subject, $matches)) {
$subject = str_replace($matches[0], '', $subject);
if ($matches[2] == 'true' || $matches[2] == 'false') {
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 (isset(static::$allowsArrayValue[$matches[1]])) {
$matches[2] = explode(static::$arrayDelimiter, $matches[2]);
} elseif ($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)) {
} elseif ($matches[1] == 'required') {
$matches[2] = explode(static::$arrayDelimiter, $matches[2]);
}
$data[$matches[1]] = $matches[2];
@ -376,12 +395,36 @@ class CommentParser
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;
$code = 500;
$exception = 'Exception';
if(count($value)>1){
$v1 = $value[0];
$v2 = $value[1];
if(is_numeric($v1)){
$code = $v1;
$exception = $v2;
array_shift($value);
array_shift($value);
} elseif(is_numeric($v2)){
$code = $v2;
$exception = $v1;
array_shift($value);
array_shift($value);
} else {
$exception = $v1;
array_shift($value);
}
} elseif(count($value) && is_numeric($value[0])) {
$code = $value[0];
array_shift($value);
}
$message = implode(' ', $value);
if(!isset(RestException::$codes[$code])){
$code = 500;
} elseif(empty($message)){
$message = RestException::$codes[$code];
}
return compact('code','message','exception');
}
private function formatClass(array $value)
@ -439,6 +482,10 @@ class CommentParser
$r['name'] = substr($data, 1);
}
}
if (isset($r['type']) && Text::endsWith($r['type'], '[]')) {
$r[static::$embeddedDataName]['type'] = substr($r['type'], 0, -2);
$r['type'] = 'array';
}
if ($value) {
$r['description'] = implode(' ', $value);
}
@ -458,6 +505,10 @@ class CommentParser
$data = explode('|', $data);
$r['type'] = count($data) == 1 ? $data[0] : $data;
}
if (isset($r['type']) && Text::endsWith($r['type'], '[]')) {
$r[static::$embeddedDataName]['type'] = substr($r['type'], 0, -2);
$r['type'] = 'array';
}
if ($value) {
$r['description'] = implode(' ', $value);
}

View File

@ -11,6 +11,7 @@ namespace Luracast\Restler;
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc6
*/
class Compose implements iCompose
{

View File

@ -11,7 +11,7 @@ namespace Luracast\Restler\Data;
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
* @version 3.0.0rc6
*/
class ApiMethodInfo extends ValueObject
{

View File

@ -10,6 +10,7 @@ namespace Luracast\Restler\Data;
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc6
*/
class Arr
{

View File

@ -12,7 +12,7 @@ use Exception;
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
* @version 3.0.0rc6
*/
class Invalid extends Exception
{

View File

@ -11,7 +11,7 @@ namespace Luracast\Restler\Data;
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
* @version 3.0.0rc6
*/
class Object
{

View File

@ -0,0 +1,96 @@
<?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/
* @version 3.0.0rc6
*/
class Text
{
/**
* 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])/', '/([_-])/', '/[^a-zA-Z0-9\s]|\s\s+/'),
array(' $0', ' $0', ' ', ' '),
$name
)
);
}
/**
* Convert given string to be used as a slug or css class
*
* @param string $name
* @return string
*/
public static function slug($name)
{
return preg_replace('/[^a-zA-Z]+/', '-', strtolower(strip_tags($name)));
}
}

View File

@ -15,7 +15,7 @@ use Luracast\Restler\Util;
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
* @version 3.0.0rc6
*/
class ValidationInfo implements iValueObject
{

View File

@ -17,13 +17,24 @@ use Luracast\Restler\Util;
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
* @version 3.0.0rc6
*/
class Validator implements iValidate
{
public static $holdException = false;
public static $exceptions = array();
public static $preFilters = array(
'*' => 'trim',
//'string' => 'strip_tags',
//'string' => 'htmlspecialchars',
//'int' => 'abs',
//'float' => 'abs',
//'CustomClass' => 'MyFilterClass::custom',
// please note that you wont get an instance
// of CustomClass. you will get an array instead
);
/**
* Validate alphabetic characters.
*
@ -116,6 +127,25 @@ class Validator implements iValidate
throw new Invalid('Expecting only hexadecimal digits.');
}
/**
* Color specified as hexadecimals
*
* Check that given value contains only color.
*
* @param $input
* @param ValidationInfo|null $info
*
* @return string
* @throws Invalid
*/
public static function color($input, ValidationInfo $info = null)
{
if (preg_match('/^#[a-f0-9]{6}$/i', $input)) {
return $input;
}
throw new Invalid('Expecting color as hexadecimal digits.');
}
/**
* Validate Telephone number
*
@ -360,6 +390,20 @@ class Validator implements iValidate
{
$html = Scope::get('Restler')->responseFormat instanceof HtmlFormat;
$name = $html ? "<strong>$info->label</strong>" : "`$info->name`";
if (
isset(static::$preFilters['*']) &&
is_scalar($input) &&
is_callable($func = static::$preFilters['*'])
) {
$input = $func($input);
}
if (
isset(static::$preFilters[$info->type]) &&
(is_scalar($input) || !empty($info->children)) &&
is_callable($func = static::$preFilters[$info->type])
) {
$input = $func($input);
}
try {
if (is_null($input)) {
if ($info->required) {
@ -470,6 +514,8 @@ class Validator implements iValidate
return $r;
case 'string' :
case 'password' : //password fields with string
case 'search' : //search field with string
if (!is_string($input)) {
$error .= '. Expecting alpha numeric value';
break;
@ -602,13 +648,13 @@ class Validator implements iValidate
}
foreach ($info->children as $key => $value) {
$cv = new ValidationInfo($value);
$cv->name = "{$info->name}[$key]";
if (array_key_exists($key, $input) || $cv->required) {
$instance->{$key} = static::validate(
Util::nestedValue($input, $key),
$cv
);
}
}
}
return $instance;
@ -616,7 +662,7 @@ class Validator implements iValidate
}
throw new RestException (400, $error);
} catch (\Exception $e) {
static::$exceptions[] = $e;
static::$exceptions[$info->name] = $e;
if (static::$holdException) {
return null;
}

View File

@ -11,7 +11,7 @@ namespace Luracast\Restler\Data;
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
* @version 3.0.0rc6
*/
class ValueObject implements iValueObject
{

View File

@ -10,7 +10,7 @@ namespace Luracast\Restler\Data;
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
* @version 3.0.0rc6
*/
interface iValidate {

View File

@ -11,7 +11,7 @@ namespace Luracast\Restler\Data;
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
* @version 3.0.0rc6
*/
interface iValueObject
{

View File

@ -15,7 +15,7 @@ use Luracast\Restler\Data\Validator;
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
* @version 3.0.0rc6
*/
class Defaults
{
@ -166,6 +166,12 @@ class Defaults
*/
public static $emptyBodyForNullResponse = true;
/**
* @var bool when set to true, the response will not be outputted directly into the buffer.
* If set, Restler::handle() will return the response as a string.
*/
public static $returnResponse = false;
/**
* @var bool enables CORS support
*/

View File

@ -9,7 +9,7 @@ namespace Luracast\Restler;
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
* @version 3.0.0rc6
*/
use Closure;

View File

@ -0,0 +1,582 @@
<?php
namespace Luracast\Restler;
use stdClass;
use Luracast\Restler\Data\Text;
use Luracast\Restler\Data\ValidationInfo;
use Luracast\Restler\Scope;
/**
* Class Explorer
*
* @package Luracast\Restler
*
* @access hybrid
* @version 3.0.0rc6
*/
class Explorer implements iProvideMultiVersionApi
{
const SWAGGER_VERSION = '1.2';
/**
* @var bool should protected resources be shown to unauthenticated users?
*/
public static $hideProtected = true;
/**
* @var bool should we use format as extension?
*/
public static $useFormatAsExtension = true;
/*
* @var bool can we accept scalar values (string, int, float etc) as the request body?
*/
public static $allowScalarValueOnRequestBody = false;
/**
* @var array all http methods specified here will be excluded from
* documentation
*/
public static $excludedHttpMethods = array('OPTIONS');
/**
* @var array all paths beginning with any of the following will be excluded
* from documentation
*/
public static $excludedPaths = array();
/**
* @var bool
*/
public static $placeFormatExtensionBeforeDynamicParts = true;
/**
* @var bool should we group all the operations with the same url or not
*/
public static $groupOperations = false;
/**
* @var string class that holds metadata as static properties
*/
public static $infoClass = 'Luracast\Restler\ExplorerInfo';
/**
* Injected at runtime
*
* @var Restler instance of restler
*/
public $restler;
/**
* @var string when format is not used as the extension this property is
* used to set the extension manually
*/
public $formatString = '';
/**
* @var array type mapping for converting data types to JSON-Schema Draft 4
* Which is followed by swagger 1.2 spec
*/
public static $dataTypeAlias = array(
//'string' => 'string',
'int' => 'integer',
'number' => 'number',
'float' => array('number', 'float'),
'bool' => 'boolean',
//'boolean' => 'boolean',
//'NULL' => 'null',
'array' => 'array',
//'object' => 'object',
'stdClass' => 'object',
'mixed' => 'string',
'date' => array('string', 'date'),
'datetime' => array('string', 'date-time'),
);
/**
* @var array configurable symbols to differentiate public, hybrid and
* protected api
*/
public static $apiDescriptionSuffixSymbols = array(
0 => '&nbsp; <i class="fa fa-lg fa-unlock-alt"></i>', //public api
1 => '&nbsp; <i class="fa fa-lg fa-adjust"></i>', //hybrid api
2 => '&nbsp; <i class="fa fa-lg fa-lock"></i>', //protected api
);
protected $models = array();
/**
* @var bool|stdClass
*/
protected $_fullDataRequested = false;
protected $crud = array(
'POST' => 'create',
'GET' => 'retrieve',
'PUT' => 'update',
'DELETE' => 'delete',
'PATCH' => 'partial update'
);
protected static $prefixes = array(
'get' => 'retrieve',
'index' => 'list',
'post' => 'create',
'put' => 'update',
'patch' => 'modify',
'delete' => 'remove',
);
protected $_authenticated = false;
protected $cacheName = '';
public function __construct()
{
}
/**
* Serve static files for exploring
*
* Serves explorer html, css, and js files
*
* @url GET *
*/
public function get()
{
if (func_num_args() > 1 && func_get_arg(0) == 'resources') {
/**
* BUGFIX:
* If we use common resourcePath (e.g. $r->addAPIClass([api-class], 'api/shop')), than we must determine resource-ID of e.g. 'api/shop'!
*/
$arguments = func_get_args();
// remove first entry
array_shift($arguments);
// create ID
$id = implode('/', $arguments);
return $this->getResources($id);
}
$filename = implode('/', func_get_args());
$redirect = false;
if (
(empty($filename) && substr($_SERVER['REQUEST_URI'], -1, 1) != '/') ||
$filename == 'index.html'
) {
$status = 302;
$url = $this->restler->getBaseUrl() . '/' . $this->base() . '/';
header("{$_SERVER['SERVER_PROTOCOL']} $status " . RestException::$codes[$status]);
header("Location: $url");
exit;
}
if (
isset($this->restler->responseFormat) &&
$this->restler->responseFormat->getExtension() == 'js'
) {
$filename .= '.js';
}
PassThrough::file(__DIR__ . '/explorer/' . (empty($filename) ? 'index.html' : $filename), false, 0); //60 * 60 * 24);
}
public function resources()
{
$r = new stdClass();
$r->apiVersion = (string)$this->restler->getRequestedApiVersion();
$r->swaggerVersion = static::SWAGGER_VERSION;
$r->apis = $this->apis($r->apiVersion);
$r->authorizations = $this->authorizations();
$r->info = array_filter(get_class_vars(static::$infoClass));
return $r;
}
public function getResources($id)
{
$r = new stdClass();
$r->apiVersion = (string)$this->restler->getRequestedApiVersion();
$r->swaggerVersion = static::SWAGGER_VERSION;
$r->basePath = $this->restler->getBaseUrl();
$r->resourcePath = "/$id";
$r->apis = $this->apis($r->apiVersion, $id);
$r->models = (object)$this->models;
$r->produces = $this->restler->getWritableMimeTypes();
$r->consumes = $this->restler->getReadableMimeTypes();
$r->authorizations = $this->authorizations();
return $r;
}
private function apis($version = 1, $resource = false)
{
$map = Routes::findAll(static::$excludedPaths + array($this->base()), static::$excludedHttpMethods, $version);
$r = array();
$a = array();
foreach ($map as $path => $data) {
$route = $data[0]['route'];
$access = $data[0]['access'];
if ($access && !Text::contains($path, '{')) {
$r[] = array(
'path' => empty($path) ? '/root' : "/$path",
//'description' => ''
//TODO: Util::nestedValue($route, 'metadata', 'classDescription') ? : ''
);
}
if (static::$hideProtected && !$access)
continue;
$grouper = array();
foreach ($data as $item) {
$route = $item['route'];
$access = $item['access'];
if (static::$hideProtected && !$access)
continue;
$url = $route['url'];
if (isset($grouper[$url])) {
$grouper[$url]['operations'][] = $this->operation($route);
} else {
$api = array(
'path' => "/$url",
'description' =>
Util::nestedValue($route, 'metadata', 'classDescription') ? : '',
'operations' => array($this->operation($route))
);
static::$groupOperations
? $grouper[$url] = $api
: $a[$path][] = $api;
}
}
if (!empty($grouper)) {
$a[$path] = array_values($grouper);
// sort REST-endpoints by path
foreach ($a as & $b) {
usort(
$b,
function ($x, $y) {
return $x['path'] > $y['path'];
}
);
}
} else {
$order = array(
'GET' => 1,
'POST' => 2,
'PUT' => 3,
'PATCH' => 4,
'DELETE' => 5
);
foreach ($a as & $b) {
usort(
$b,
function ($x, $y) use ($order) {
return
$x['operations'][0]->method ==
$y['operations'][0]->method
? $x['path'] > $y['path']
: $order[$x['operations'][0]->method] >
$order[$y['operations'][0]->method];
}
);
}
}
}
if (false !== $resource) {
if ($resource == 'root') $resource = '';
if (isset($a[$resource])) return $a[$resource];
}
return $r;
}
private function operation($route)
{
$r = new stdClass();
$r->method = $route['httpMethod'];
$r->nickname = $this->nickname($route);
$r->parameters = $this->parameters($route);
$m = $route['metadata'];
$r->summary = isset($m['description'])
? $m['description']
: '';
$r->summary .= $route['accessLevel'] > 2
? static::$apiDescriptionSuffixSymbols[2]
: static::$apiDescriptionSuffixSymbols[$route['accessLevel']];
$r->notes = isset($m['longDescription'])
? $m['longDescription']
: '';
$r->responseMessages = $this->responseMessages($route);
$this->setType(
$r,
new ValidationInfo(Util::nestedValue($m, 'return') ? : array())
);
if (is_null($r->type) || 'mixed' == $r->type) {
$r->type = 'array';
} elseif ($r->type == 'null') {
$r->type = 'void';
} elseif (Text::contains($r->type, '|')) {
$r->type = 'array';
}
//TODO: add $r->authorizations
//A list of authorizations required to execute this operation. While not mandatory, if used, it overrides
//the value given at the API Declaration's authorizations. In order to completely remove API Declaration's
//authorizations completely, an empty object ({}) may be applied.
//TODO: add $r->produces
//TODO: add $r->consumes
//A list of MIME types this operation can produce/consume. This is overrides the global produces definition at the root of the API Declaration. Each string value SHOULD represent a MIME type.
//TODO: add $r->deprecated
//Declares this operation to be deprecated. Usage of the declared operation should be refrained. Valid value MUST be either "true" or "false". Note: This field will change to type boolean in the future.
return $r;
}
private function parameters(array $route)
{
$r = array();
$children = array();
$required = false;
foreach ($route['metadata']['param'] as $param) {
$info = new ValidationInfo($param);
$description = isset($param['description']) ? $param['description'] : '';
if ('body' == $info->from) {
if ($info->required)
$required = true;
$param['description'] = $description;
$children[] = $param;
} else {
$r[] = $this->parameter($info, $description);
}
}
if (!empty($children)) {
if (
1 == count($children) &&
(static::$allowScalarValueOnRequestBody || !empty($children[0]['children']))
) {
$firstChild = $children[0];
if (empty($firstChild['children'])) {
$description = $firstChild['description'];
} else {
$description = '<section class="body-param">';
foreach ($firstChild['children'] as $child) {
$description .= isset($child['required']) && $child['required']
? '<strong>' . $child['name'] . '</strong> (required)<br/>'
: $child['name'] . '<br/>';
}
$description .= '</section>';
}
$r[] = $this->parameter(new ValidationInfo($firstChild), $description);
} else {
$description = '<section class="body-param">';
foreach ($children as $child) {
$description .= isset($child['required']) && $child['required']
? '<strong>' . $child['name'] . '</strong> (required)<br/>'
: $child['name'] . '<br/>';
}
$description .= '</section>';
//lets group all body parameters under a generated model name
$name = $this->nameModel($route);
$r[] = $this->parameter(
new ValidationInfo(array(
'name' => $name,
'type' => $name,
'from' => 'body',
'required' => $required,
'children' => $children
)),
$description
);
}
}
return $r;
}
private function parameter(ValidationInfo $info, $description = '')
{
$p = new stdClass();
if(isset($info->rules['model'])){
$info->type = $info->rules['model'];
}
$p->name = $info->name;
$this->setType($p, $info);
if (empty($info->children) || $info->type != 'array') {
//primitives
if ($info->default)
$p->defaultValue = $info->default;
if ($info->choice)
$p->enum = $info->choice;
if ($info->min)
$p->minimum = $info->min;
if ($info->max)
$p->maximum = $info->max;
//TODO: $p->items and $p->uniqueItems boolean
}
$p->description = $description;
$p->paramType = $info->from; //$info->from == 'body' ? 'form' : $info->from;
$p->required = $info->required;
$p->allowMultiple = false;
return $p;
}
private function responseMessages(array $route)
{
$r = array();
if (is_array($throws = Util::nestedValue($route, 'metadata', 'throws'))) {
foreach ($throws as $message) {
$m = (object)$message;
//TODO: add $m->responseModel from composer class
$r[] = $m;
}
}
return $r;
}
private function model($type, array $children)
{
/**
* Bugfix:
* If we use namespaces, than the model will not be correct, if we use a short name for the type!
*
* Example (phpDoc/annotations in API-class, which uses custom domain-model with namespace):
* @param Car $car {@from body} {@type Aoe\RestServices\Domain\Model\Car}
* @return Car {@type Aoe\RestServices\Domain\Model\Car}
* Than, the model (in swagger-spec) must also be 'Aoe\RestServices\Domain\Model\Car' and not 'Car'
*
* When we use namespaces, than we must use the @type-annotation, otherwise the automatic reconstitution
* from request-data (e.g. when it is a POST-request) to custom domain-model-object will not work!
*
* Summary:
* - When we use no namespaces, than the type would not be changed, if we would call 'Util::getShortName'
* - When we use namespaces, than the model will not be correct, if we would call 'Util::getShortName'
* ...so this method-call is either needless or will create a bug/error
*/
//$type = Util::getShortName($type);
if (isset($this->models[$type]))
return $this->models[$type];
$r = new stdClass();
$r->id = $type;
$r->description = "$type Model"; //TODO: enhance this on Router
$r->required = array();
$r->properties = array();
foreach ($children as $child) {
$info = new ValidationInfo($child);
$p = new stdClass();
$this->setType($p, $info);
$p->description = isset($child['description']) ? $child['description'] : '';
if ($info->default)
$p->defaultValue = $info->default;
if ($info->choice)
$p->enum = $info->choice;
if ($info->min)
$p->minimum = $info->min;
if ($info->max)
$p->maximum = $info->max;
if ($info->required)
$r->required[] = $info->name;
$r->properties[$info->name] = $p;
}
//TODO: add $r->subTypes https://github.com/wordnik/swagger-spec/blob/master/versions/1.2.md#527-model-object
//TODO: add $r->discriminator https://github.com/wordnik/swagger-spec/blob/master/versions/1.2.md#527-model-object
$this->models[$type] = $r;
return $r;
}
private function setType(&$object, ValidationInfo $info)
{
//TODO: proper type management
if ($info->type == 'array') {
if ($info->children) {
$this->model($info->contentType, $info->children);
$object->items = (object)array(
'$ref' => $info->contentType
);
} elseif ($info->contentType && $info->contentType == 'associative') {
unset($info->contentType);
$this->model($info->type = 'Object', array(
array(
'name' => 'property',
'type' => 'string',
'default' => '',
'required' => false,
'description' => ''
)
));
} elseif ($info->contentType && $info->contentType != 'indexed') {
$object->items = (object)array(
'type' => $info->contentType
);
} else {
$object->items = (object)array(
'type' => 'string'
);
}
} elseif ($info->children) {
$this->model($info->type, $info->children);
} elseif (is_string($info->type) && $t = Util::nestedValue(static::$dataTypeAlias, strtolower($info->type))) {
if (is_array($t)) {
list($info->type, $object->format) = $t;
} else {
$info->type = $t;
}
} else {
$info->type = 'string';
}
$object->type = $info->type;
$has64bit = PHP_INT_MAX > 2147483647;
if ($object->type == 'integer') {
$object->format = $has64bit
? 'int64'
: 'int32';
} elseif ($object->type == 'number') {
$object->format = $has64bit
? 'double'
: 'float';
}
}
private function nickname(array $route)
{
static $hash = array();
$method = $route['methodName'];
if (isset(static::$prefixes[$method])) {
$method = static::$prefixes[$method];
} else {
$method = str_replace(
array_keys(static::$prefixes),
array_values(static::$prefixes),
$method
);
}
while (isset($hash[$method]) && $route['url'] != $hash[$method]) {
//create another one
$method .= '_';
}
$hash[$method] = $route['url'];
return $method;
}
private function nameModel(array $route)
{
static $hash = array();
$count = 1;
//$name = str_replace('/', '-', $route['url']) . 'Model';
$name = $route['className'] . 'Model';
while (isset($hash[$name . $count])) {
//create another one
$count++;
}
$name .= $count;
$hash[$name] = $route['url'];
return $name;
}
private function authorizations()
{
$r = new stdClass();
$r->apiKey = (object)array(
'type' => 'apiKey',
'passAs' => 'query',
'keyname' => 'api_key',
);
return $r;
}
private function base()
{
return strtok($this->restler->url, '/');
}
/**
* Maximum api version supported by the api class
* @return int
*/
public static function __getMaximumSupportedVersion()
{
return Scope::get('Restler')->getApiVersion();
}
}

View File

@ -0,0 +1,17 @@
<?php namespace Luracast\Restler;
/**
* Class ExplorerInfo
* @package Luracast\Restler
*
* @version 3.0.0rc6
*/
class ExplorerInfo
{
public static $title = 'Restler API Explorer';
public static $description = 'Live API Documentation';
public static $termsOfServiceUrl = null;
public static $contact = 'arul@luracast.com';
public static $license = 'LGPL-2.1';
public static $licenseUrl = 'https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html';
}

View File

@ -15,7 +15,7 @@ use Luracast\Restler\RestException;
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
* @version 3.0.0rc6
*/
class RateLimit implements iFilter, iUseAuthentication
{
@ -53,7 +53,7 @@ class RateLimit implements iFilter, iUseAuthentication
* @var array all paths beginning with any of the following will be excluded
* from documentation
*/
public static $excludedPaths = array('resources');
public static $excludedPaths = array('explorer');
/**

View File

@ -2,6 +2,7 @@
namespace Luracast\Restler;
use Luracast\Restler\Format\HtmlFormat;
use ArrayAccess;
/**
* Storing and retrieving a message or array of key value pairs for one time use using $_SESSION
@ -14,9 +15,9 @@ use Luracast\Restler\Format\HtmlFormat;
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
* @version 3.0.0rc6
*/
class Flash //implements \JsonSerializable
class Flash implements ArrayAccess
{
const SUCCESS = 'success';
const INFO = 'info';
@ -104,18 +105,22 @@ class Flash //implements \JsonSerializable
*/
public static function set(array $data)
{
if (!static::$instance)
if (!static::$instance) {
static::$instance = new Flash();
if (!isset($_SESSION['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);
}
@ -126,8 +131,9 @@ class Flash //implements \JsonSerializable
public function __destruct()
{
if ($this->usedOnce)
if ($this->usedOnce) {
unset($_SESSION['flash']);
}
}
/**
@ -139,8 +145,30 @@ class Flash //implements \JsonSerializable
public function jsonSerialize()
{
$this->usedOnce = true;
return isset($_SESSION['flash'])
? $_SESSION['flash']
: array();
}
public function offsetExists($offset)
{
return $this->__isset($offset);
}
public function offsetGet($offset)
{
return $this->__get($offset);
}
public function offsetSet($offset, $value)
{
//not implemented
}
public function offsetUnset($offset)
{
//not implemented
}
}

View File

@ -17,13 +17,16 @@ use ZendAmf\Parser\OutputStream;
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
* @version 3.0.0rc6
*/
class AmfFormat extends Format
class AmfFormat extends DependentFormat
{
const MIME = 'application/x-amf';
const EXTENSION = 'amf';
const PACKAGE_NAME = 'zendframework/zendamf:dev-master';
const EXTERNAL_CLASS = 'ZendAmf\\Parser\\Amf3\\Deserializer';
public function encode($data, $humanReadable = false)
{

View File

@ -15,7 +15,7 @@ use Luracast\Restler\RestException;
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
* @version 3.0.0rc6
*/
class CsvFormat extends Format implements iDecodeStream
{

View File

@ -0,0 +1,56 @@
<?php
namespace Luracast\Restler\Format;
use Luracast\Restler\RestException;
abstract class DependentFormat extends Format
{
/**
* override in the extending class
*
* @example symfony/yaml:*
*/
const PACKAGE_NAME = 'vendor/project:version';
/**
* override in the extending class
*
* fully qualified class name
*/
const EXTERNAL_CLASS = 'Namespace\\ClassName';
/**
* Get external class => packagist package name as an associative array
*
* @return array list of dependencies for the format
*/
public function getDependencyMap()
{
return array(
static::EXTERNAL_CLASS => static::PACKAGE_NAME
);
}
protected function checkDependency($class = null)
{
if (empty($class)) {
$class = key($this->getDependencyMap());
}
if (!class_exists($class, true)) {
$map = $this->getDependencyMap();
$package = $map[$class];
throw new RestException(
500,
get_called_class() . ' has external dependency. Please run `composer require ' .
$package . '` from the project root. Read https://getcomposer.org for more info'
);
}
}
public function __construct()
{
$this->checkDependency();
}
}

View File

@ -0,0 +1,39 @@
<?php
namespace Luracast\Restler\Format;
use Luracast\Restler\RestException;
abstract class DependentMultiFormat extends MultiFormat
{
/**
* Get external class => packagist package name as an associative array
*
* @return array list of dependencies for the format
*
* @example return ['Illuminate\\View\\View' => 'illuminate/view:4.2.*']
*/
abstract public function getDependencyMap();
protected function checkDependency($class = null)
{
if (empty($class)) {
$class = key($this->getDependencyMap());
}
if (!class_exists($class, true)) {
$map = $this->getDependencyMap();
$package = $map[$class];
throw new RestException(
500,
get_called_class() . ' has external dependency. Please run `composer require ' .
$package . '` from the project root. Read https://getcomposer.org for more info'
);
}
}
public function __construct()
{
$this->checkDependency();
}
}

View File

@ -10,7 +10,7 @@ namespace Luracast\Restler\Format;
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
* @version 3.0.0rc6
*/
abstract class Format implements iFormat
{

View File

@ -6,11 +6,11 @@ use Illuminate\Events\Dispatcher;
use Illuminate\Filesystem\Filesystem;
use Illuminate\View\Compilers\BladeCompiler;
use Illuminate\View\Engines\CompilerEngine;
use Illuminate\View\Engines\PhpEngine;
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;
@ -29,9 +29,9 @@ use Luracast\Restler\Util;
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
* @version 3.0.0rc6
*/
class HtmlFormat extends Format
class HtmlFormat extends DependentFormat
{
public static $mime = 'text/html';
public static $extension = 'html';
@ -39,6 +39,7 @@ class HtmlFormat extends Format
public static $errorView = 'debug.php';
public static $template = 'php';
public static $handleSession = true;
public static $convertResponseToArray = false;
public static $useSmartViews = true;
/**
@ -83,11 +84,16 @@ class HtmlFormat extends Format
}
}
public function getDependencyMap(){
return array(
'Illuminate\View\View' => 'illuminate/view:4.2.*',
'Twig_Environment' => 'twig/twig:v1.13.*',
'Mustache_Engine' => 'mustache/mustache:dev-master',
);
}
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);
@ -95,6 +101,10 @@ class HtmlFormat extends Format
$resolver->register('blade', function () use ($engine) {
return $engine;
});
$phpEngine = new PhpEngine();
$resolver->register('php', function () use ($phpEngine) {
return $phpEngine;
});
/** @var Restler $restler */
$restler = Scope::get('Restler');
@ -109,7 +119,7 @@ class HtmlFormat extends Format
}
return false;
}, true, true);
$viewFinder = new FileViewFinder($files, array(static::$viewPath));
$factory = new Factory($resolver, $viewFinder, new Dispatcher());
$path = $viewFinder->find(self::$view);
@ -120,9 +130,6 @@ class HtmlFormat extends Format
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,
@ -176,27 +183,21 @@ class HtmlFormat extends Format
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())
),
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
);
},
$params = explode(',', $m->render($text));
return call_user_func_array(
'Luracast\Restler\UI\Forms::get',
$params
);
},
)
);
if (!$debug)
@ -313,7 +314,9 @@ class HtmlFormat extends Format
$success = is_null($exception);
$error = $success ? null : $exception->getMessage();
$data = array(
'response' => Object::toArray($data),
'response' => static::$convertResponseToArray
? Object::toArray($data)
: $data,
'stages' => $this->restler->getEvents(),
'success' => $success,
'error' => $error
@ -334,10 +337,7 @@ class HtmlFormat extends Format
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;
self::$view = static::guessViewName($this->restler->url);
}
if (
isset($metadata['param'])
@ -368,12 +368,19 @@ class HtmlFormat extends Format
if (!static::$cacheDirectory) {
static::$cacheDirectory = Defaults::$cacheDirectory . DIRECTORY_SEPARATOR . $template;
if (!file_exists(static::$cacheDirectory)) {
if (!mkdir(static::$cacheDirectory)) {
if (!mkdir(static::$cacheDirectory, 0770, true)) {
throw new RestException(500, 'Unable to create cache directory `' . static::$cacheDirectory . '`');
}
}
}
if (method_exists($class = get_called_class(), $template)) {
if ($template == 'blade') {
$this->checkDependency('Illuminate\View\View');
} elseif ($template == 'twig') {
$this->checkDependency('Twig_Environment');
} elseif ($template == 'mustache' || $template == 'handlebar') {
$this->checkDependency('Mustache_Engine');
}
return call_user_func("$class::$template", $data, $humanReadable);
}
throw new RestException(500, "Unsupported template system `$template`");
@ -384,6 +391,20 @@ class HtmlFormat extends Format
}
}
public static function guessViewName($path)
{
if (empty($path)) {
$path = 'index';
} elseif (strpos($path, '/')) {
$path .= '/index';
}
$file = static::$viewPath . '/' . $path . '.' . static::getViewExtension();
return static::$useSmartViews && is_readable($file)
? $path
: static::$errorView;
}
public static function getViewExtension()
{
return isset(static::$customTemplateExtensions[static::$template])

View File

@ -11,7 +11,7 @@ namespace Luracast\Restler\Format;
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
* @version 3.0.0rc6
*/
class JsFormat extends JsonFormat
{

View File

@ -14,7 +14,7 @@ use Luracast\Restler\RestException;
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
* @version 3.0.0rc6
*/
class JsonFormat extends Format
{
@ -57,37 +57,60 @@ class JsonFormat extends Format
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
);
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;
}
$result = json_encode(Object::toArray($data, true), $options);
$this->handleJsonError();
return $result;
}
$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);
$this->handleJsonError();
if ($humanReadable) {
$result = $this->formatJson($result);
}
if (self::$unEscapedSlashes) $result = str_replace('\/', '/', $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;
}
@ -102,39 +125,21 @@ class JsonFormat extends Format
} else {
$data = preg_replace(
'/:\s*(\-?\d+(\.\d+)?([e|E][\-|\+]\d+)?)/',
': "$1"', $data
': "$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');
try {
$decoded = json_decode($data, $options);
$this->handleJsonError();
} catch (\RuntimeException $e) {
throw new RestException(400, $e->getMessage());
}
if (strlen($data) && $decoded === null || $decoded === $data) {
throw new RestException(400, 'Error parsing JSON');
}
return Object::toArray($decoded);
@ -206,5 +211,52 @@ class JsonFormat extends Format
return $newJson;
}
}
/**
* Throws an exception if an error occurred during the last JSON encoding/decoding
*
* @return void
* @throws \RuntimeException
*/
protected function handleJsonError()
{
if (function_exists('json_last_error_msg') && json_last_error() !== JSON_ERROR_NONE) {
// PHP >= 5.5.0
$message = json_last_error_msg();
} elseif (function_exists('json_last_error')) {
// PHP >= 5.3.0
switch (json_last_error()) {
case JSON_ERROR_NONE:
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;
}
}
if (isset($message)) {
throw new \RuntimeException('Error encoding/decoding JSON: '. $message);
}
}
}

View File

@ -9,7 +9,7 @@ namespace Luracast\Restler\Format;
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
* @version 3.0.0rc6
*/
abstract class MultiFormat implements iFormat
{

View File

@ -18,9 +18,9 @@ use CFPropertyList\CFPropertyList;
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
* @version 3.0.0rc6
*/
class PlistFormat extends MultiFormat
class PlistFormat extends DependentMultiFormat
{
/**
* @var boolean set it to true binary plist is preferred
@ -81,11 +81,24 @@ class PlistFormat extends MultiFormat
*/
public function decode($data)
{
//require_once 'CFPropertyList.php';
$plist = new CFPropertyList ();
$plist->parse($data);
return $plist->toArray();
}
/**
* Get external class => packagist package name as an associative array
*
* @return array list of dependencies for the format
*
* @example return ['Illuminate\\View\\View' => 'illuminate/view:4.2.*']
*/
public function getDependencyMap()
{
return array(
'CFPropertyList\CFPropertyList' => 'rodneyrehm/plist:dev-master'
);
}
}

View File

@ -11,7 +11,7 @@ namespace Luracast\Restler\Format;
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
* @version 3.0.0rc6
*/
class TsvFormat extends CsvFormat
{

View File

@ -13,7 +13,7 @@ use Luracast\Restler\RestException;
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
* @version 3.0.0rc6
*/
class UploadFormat extends Format
{

View File

@ -11,7 +11,7 @@ namespace Luracast\Restler\Format;
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
* @version 3.0.0rc6
*/
class UrlEncodedFormat extends Format
{

View File

@ -16,7 +16,7 @@ use XMLWriter;
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
* @version 3.0.0rc6
*/
class XmlFormat extends Format
{
@ -110,14 +110,16 @@ class XmlFormat extends Format
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();
}
@ -151,30 +153,15 @@ class XmlFormat extends Format
&& !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();
}
$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';
@ -217,6 +204,7 @@ class XmlFormat extends Format
return array();
}
libxml_use_internal_errors(true);
libxml_disable_entity_loader(true);
$xml = simplexml_load_string($data,
"SimpleXMLElement", LIBXML_NOBLANKS | LIBXML_NOCDATA | LIBXML_COMPACT);
if (false === $xml) {
@ -241,8 +229,10 @@ class XmlFormat extends Format
}
}
$data = $this->read($xml);
if (count($data) == 1 && isset($data[static::$textNodeName]))
if (count($data) == 1 && isset($data[static::$textNodeName])) {
$data = $data[static::$textNodeName];
}
return $data;
} catch (\RuntimeException $e) {
throw new RestException(400,
@ -268,10 +258,13 @@ class XmlFormat extends Format
}
$children = $xml->children();
foreach ($children as $key => $value) {
if (isset($r[$key])) {
if ($key == static::$defaultTagName) {
$r[] = $this->read($value);
} elseif (isset($r[$key])) {
if (is_array($r[$key])) {
if ($r[$key] != array_values($r[$key]))
if ($r[$key] != array_values($r[$key])) {
$r[$key] = array($r[$key]);
}
} else {
$r[$key] = array($r[$key]);
}
@ -282,8 +275,9 @@ class XmlFormat extends Format
}
if (static::$parseNamespaces) {
if (is_null($namespaces))
if (is_null($namespaces)) {
$namespaces = $xml->getDocNamespaces(true);
}
foreach ($namespaces as $prefix => $ns) {
static::$namespaces[$prefix] = $ns;
if (static::$parseAttributes) {
@ -303,12 +297,14 @@ class XmlFormat extends Format
}
$children = $xml->children($ns);
foreach ($children as $key => $value) {
if (static::$importSettingsFromXml)
if (static::$importSettingsFromXml) {
static::$namespacedProperties[$key] = $prefix;
}
if (isset($r[$key])) {
if (is_array($r[$key])) {
if ($r[$key] != array_values($r[$key]))
if ($r[$key] != array_values($r[$key])) {
$r[$key] = array($r[$key]);
}
} else {
$r[$key] = array($r[$key]);
}
@ -321,7 +317,9 @@ class XmlFormat extends Format
}
if (empty($text) && $text !== '0') {
if (empty($r)) return null;
if (empty($r)) {
return null;
}
} else {
empty($r)
? $r = static::setType($text)
@ -331,18 +329,25 @@ class XmlFormat extends Format
: $r[] = static::setType($text)
);
}
return $r;
}
public static function setType($value)
{
if (empty($value) && $value !== '0')
if (empty($value) && $value !== '0') {
return null;
if ($value == 'true')
}
if ($value == 'true') {
return true;
if ($value == 'false')
}
if ($value == 'false') {
return true;
}
if (is_numeric($value)) {
return 0 + $value;
}
return $value;
}
}

View File

@ -14,22 +14,23 @@ use Luracast\Restler\Data\Object;
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
* @version 3.0.0rc6
*/
class YamlFormat extends Format
class YamlFormat extends DependentFormat
{
const MIME = 'text/plain';
const EXTENSION = 'yaml';
const PACKAGE_NAME = 'symfony/yaml:*';
const EXTERNAL_CLASS = 'Symfony\Component\Yaml\Yaml';
public function encode($data, $humanReadable = false)
{
// require_once 'sfyaml.php';
return @Yaml::dump(Object::toArray($data));
return @Yaml::dump(Object::toArray($data), $humanReadable ? 10 : 4);
}
public function decode($data)
{
// require_once 'sfyaml.php';
return Yaml::parse($data);
}
}

View File

@ -11,7 +11,7 @@ namespace Luracast\Restler\Format;
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
* @version 3.0.0rc6
*/
interface iDecodeStream
{

View File

@ -11,7 +11,7 @@ namespace Luracast\Restler\Format;
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
* @version 3.0.0rc6
*/
interface iFormat
{

View File

@ -10,7 +10,7 @@ namespace Luracast\Restler;
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
* @version 3.0.0rc6
*/
class HumanReadableCache implements iCache
{

View File

@ -0,0 +1,22 @@
<?php
namespace Luracast\Restler;
/**
* Special RestException for forcing the exception even when
* in hybrid method
*
* @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/
* @version 3.0.0rc6
*/
class InvalidAuthCredentials extends RestException
{
}

View File

@ -0,0 +1,140 @@
<?php
namespace Luracast\Restler;
/**
* Class MemcacheCache provides a memcache based cache for Restler
*
* @category Framework
* @package Restler
* @author Dave Drager <ddrager@gmail.com>
* @copyright 2014 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
*/
class MemcacheCache 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;
/**
* @var string the memcache server hostname / IP address. For the memcache
* cache method.
*/
static public $memcacheServer = '127.0.0.1';
/**
* @var int the memcache server port. For the memcache cache method.
*/
static public $memcachePort = 11211;
private $memcache;
/**
* @param string $namespace
*/
function __construct($namespace = 'restler')
{
self::$namespace = $namespace;
if (function_exists('memcache_connect')) {
$this->memcache = new \Memcache;
$this->memcache->connect(self::$memcacheServer, self::$memcachePort);
} else {
$this->memcacheNotAvailable('Memcache is not available for use as Restler Cache. Please make sure the the memcache php extension is installed.');
}
}
/**
* store data in the cache
*
*
* @param string $name
* @param mixed $data
*
* @return boolean true if successful
*/
public function set($name, $data)
{
function_exists('memcache_set') || $this->memcacheNotAvailable();
try {
return $this->memcache->set(self::$namespace . "-" . $name, $data);
} catch
(\Exception $exception) {
return false;
}
}
private function memcacheNotAvailable($message = 'Memcache is not available.')
{
throw new \Exception($message);
}
/**
* retrieve data from the cache
*
*
* @param string $name
* @param bool $ignoreErrors
*
* @throws \Exception
* @return mixed
*/
public function get($name, $ignoreErrors = false)
{
function_exists('memcache_get') || $this->memcacheNotAvailable();
try {
return $this->memcache->get(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('memcache_delete') || $this->memcacheNotAvailable();
try {
$this->memcache->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('memcache_get') || $this->memcacheNotAvailable();
$data = $this->memcache->get(self::$namespace . "-" . $name);
return !empty($data);
}
}

View File

@ -0,0 +1,91 @@
<?php
namespace Luracast\Restler;
/**
* Static Class to pass through content outside of web root
*
* @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.0rc6
*/
class PassThrough
{
public static $mimeTypes = array(
'js' => 'text/javascript',
'css' => 'text/css',
'png' => 'image/png',
'jpg' => 'image/jpeg',
'gif' => 'image/gif',
'html' => 'text/html',
);
/**
* Serve a file outside web root
*
* Respond with a file stored outside web accessible path
*
* @param string $filename full path for the file to be served
* @param bool $forceDownload should the we download instead of viewing
* @param int $expires cache expiry in number of seconds
* @param bool $isPublic cache control, is it public or private
*
* @throws RestException
* @internal param string $pragma
*
*/
public static function file($filename, $forceDownload = false, $expires = 0, $isPublic = true)
{
if (!is_file($filename))
throw new RestException(404);
if (!is_readable($filename))
throw new RestException(403);
$extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
if (!$mime = Util::nestedValue(static::$mimeTypes, $extension)) {
if (!function_exists('finfo_open')) {
throw new RestException(
500,
'Unable to find media type of ' .
basename($filename) .
' either enable fileinfo php extension or update ' .
'PassThrough::$mimeTypes to include mime type for ' . $extension .
' extension'
);
}
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $filename);
}
if (!is_array(Defaults::$headerCacheControl))
Defaults::$headerCacheControl = array(Defaults::$headerCacheControl);
$cacheControl = Defaults::$headerCacheControl[0];
if ($expires > 0) {
$cacheControl = $isPublic ? 'public' : 'private';
$cacheControl .= end(Defaults::$headerCacheControl);
$cacheControl = str_replace('{expires}', $expires, $cacheControl);
$expires = gmdate('D, d M Y H:i:s \G\M\T', time() + $expires);
}
header('Cache-Control: ' . $cacheControl);
header('Expires: ' . $expires);
$lastModified = filemtime($filename);
if (
isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) &&
strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) >= $lastModified
) {
header("{$_SERVER['SERVER_PROTOCOL']} 304 Not Modified");
exit;
}
header('Last-Modified: ' . date('r', $lastModified));
header('X-Powered-By: Luracast Restler v' . Restler::VERSION);
header('Content-type: ' . $mime);
header("Content-Length: " . filesize($filename));
if ($forceDownload) {
header("Content-Transfer-Encoding: binary");
header('Content-Disposition: attachment; filename="' . $filename . '"');
}
readfile($filename);
exit;
}
}

View File

@ -12,7 +12,7 @@ use Luracast\Restler\Format\JsonFormat;
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
* @version 3.0.0rc6
*/
class Redirect
{
@ -32,11 +32,13 @@ class Redirect
/** @var $r Restler */
$r = Scope::get('Restler');
$base = $r->getBaseUrl() . '/';
if (0 !== strpos($url, 'http'))
if (0 !== strpos($url, 'http')) {
$url = $base . $url;
}
if (!empty($flashData) || $base . $r->url !== $url || Util::getRequestMethod() != 'GET') {
if ($r->responseFormat instanceof JsonFormat)
if ($r->responseFormat instanceof JsonFormat) {
return array('redirect' => $url);
}
if (!empty($params)) {
$url .= '?' . http_build_query($params);
}
@ -48,6 +50,19 @@ class Redirect
header("Location: $url");
die('');
}
return array();
}
/**
* Redirect back to the previous page
*
* Makes use of http referrer for redirection
*
* @return array
*/
public static function back()
{
return static::to($_SERVER['HTTP_REFERER']);
}
}

View File

@ -1,7 +1,7 @@
<?php
namespace Luracast\Restler;
use Luracast\Restler\Data\String;
use Luracast\Restler\Data\Text;
use Luracast\Restler\Scope;
use stdClass;
@ -15,7 +15,7 @@ use stdClass;
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
* @version 3.0.0rc6
*/
class Resources implements iUseAuthentication, iProvideMultiVersionApi
{
@ -226,14 +226,14 @@ class Resources implements iUseAuthentication, iProvideMultiVersionApi
continue;
}
$fullPath = $route['url'];
if ($fullPath !== $target && !String::beginsWith($fullPath, $target)) {
if ($fullPath !== $target && !Text::beginsWith($fullPath, $target)) {
continue;
}
$fLen = strlen($fullPath);
if ($tSlash) {
if ($fLen != $tLen && !String::beginsWith($fullPath, $target . '/'))
if ($fLen != $tLen && !Text::beginsWith($fullPath, $target . '/'))
continue;
} elseif ($fLen > $tLen + 1 && $fullPath{$tLen + 1} != '{' && !String::beginsWith($fullPath, '{')) {
} elseif ($fLen > $tLen + 1 && $fullPath{$tLen + 1} != '{' && !Text::beginsWith($fullPath, '{')) {
//when mapped to root exclude paths that have static parts
//they are listed else where under that static part name
continue;
@ -246,7 +246,7 @@ class Resources implements iUseAuthentication, iProvideMultiVersionApi
if (empty($exclude)) {
if ($fullPath == $exclude)
continue 2;
} elseif (String::beginsWith($fullPath, $exclude)) {
} elseif (Text::beginsWith($fullPath, $exclude)) {
continue 2;
}
}
@ -317,7 +317,7 @@ class Resources implements iUseAuthentication, iProvideMultiVersionApi
if (isset($m['throws'])) {
foreach ($m['throws'] as $exception) {
$operation->errorResponses[] = array(
'reason' => $exception['reason'],
'reason' => $exception['message'],
'code' => $exception['code']);
}
}
@ -370,7 +370,7 @@ class Resources implements iUseAuthentication, iProvideMultiVersionApi
if (static::$groupOperations) {
foreach ($r->apis as $a) {
if ($a->path == "/$fullPath") {
if ($a->path == "$prefix/$fullPath") {
$api = $a;
break;
}
@ -774,14 +774,14 @@ class Resources implements iUseAuthentication, iProvideMultiVersionApi
$this->_model($itemType);
$itemType = $this->_noNamespace($itemType);
}
$properties[$key]['item'] = array(
$properties[$key]['items'] = array(
'type' => $itemType,
/*'description' => '' */ //TODO: add description
);
} else if (preg_match('/^Array\[(.+)\]$/', $type, $matches)) {
$itemType = $matches[1];
$properties[$key]['type'] = 'Array';
$properties[$key]['item']['type'] = $itemType;
$properties[$key]['items']['type'] = $this->_noNamespace($itemType);
if (class_exists($itemType)) {
$this->_model($itemType);
@ -891,7 +891,7 @@ class Resources implements iUseAuthentication, iProvideMultiVersionApi
}
$this->_mapResources($allRoutes, $map, $version);
foreach ($map as $path => $description) {
if (!String::contains($path, '{')) {
if (!Text::contains($path, '{')) {
//add id
$r->apis[] = array(
'path' => $path . $this->formatString,
@ -934,7 +934,7 @@ class Resources implements iUseAuthentication, iProvideMultiVersionApi
foreach ($allRoutes as $fullPath => $routes) {
$path = explode('/', $fullPath);
$resource = isset($path[0]) ? $path[0] : '';
if ($resource == 'resources' || String::endsWith($resource, 'index'))
if ($resource == 'resources' || Text::endsWith($resource, 'index'))
continue;
foreach ($routes as $httpMethod => $route) {
if (in_array($httpMethod, static::$excludedHttpMethods)) {
@ -948,7 +948,7 @@ class Resources implements iUseAuthentication, iProvideMultiVersionApi
if (empty($exclude)) {
if ($fullPath == $exclude)
continue 2;
} elseif (String::beginsWith($fullPath, $exclude)) {
} elseif (Text::beginsWith($fullPath, $exclude)) {
continue 2;
}
}

View File

@ -14,6 +14,7 @@ use Exception;
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc6
*/
class RestException extends Exception

View File

@ -21,11 +21,37 @@ use Luracast\Restler\Format\UrlEncodedFormat;
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
* @version 3.0.0rc6
*
* @method static void onGet() onGet(Callable $function) fired before reading the request details
* @method static void onRoute() onRoute(Callable $function) fired before finding the api method
* @method static void onNegotiate() onNegotiate(Callable $function) fired before content negotiation
* @method static void onPreAuthFilter() onPreAuthFilter(Callable $function) fired before pre auth filtering
* @method static void onAuthenticate() onAuthenticate(Callable $function) fired before auth
* @method static void onPostAuthFilter() onPostAuthFilter(Callable $function) fired before post auth filtering
* @method static void onValidate() onValidate(Callable $function) fired before validation
* @method static void onCall() onCall(Callable $function) fired before api method call
* @method static void onCompose() onCompose(Callable $function) fired before composing response
* @method static void onRespond() onRespond(Callable $function) fired before sending response
* @method static void onComplete() onComplete(Callable $function) fired after sending response
* @method static void onMessage() onMessage(Callable $function) fired before composing error response
*
* @method void onGet() onGet(Callable $function) fired before reading the request details
* @method void onRoute() onRoute(Callable $function) fired before finding the api method
* @method void onNegotiate() onNegotiate(Callable $function) fired before content negotiation
* @method void onPreAuthFilter() onPreAuthFilter(Callable $function) fired before pre auth filtering
* @method void onAuthenticate() onAuthenticate(Callable $function) fired before auth
* @method void onPostAuthFilter() onPostAuthFilter(Callable $function) fired before post auth filtering
* @method void onValidate() onValidate(Callable $function) fired before validation
* @method void onCall() onCall(Callable $function) fired before api method call
* @method void onCompose() onCompose(Callable $function) fired before composing response
* @method void onRespond() onRespond(Callable $function) fired before sending response
* @method void onComplete() onComplete(Callable $function) fired after sending response
* @method void onMessage() onMessage(Callable $function) fired before composing error response
*/
class Restler extends EventDispatcher
{
const VERSION = '3.0.0rc5';
const VERSION = '3.0.0rc6';
// ==================================================================
//
@ -78,9 +104,9 @@ class Restler extends EventDispatcher
/**
* Http status code
*
* @var int
* @var int|null when specified it will override @status comment
*/
public $responseCode=200;
public $responseCode=null;
/**
* @var string base url of the api service
*/
@ -278,11 +304,20 @@ class Restler extends EventDispatcher
$this->call();
$this->compose();
$this->postCall();
if (Defaults::$returnResponse) {
return $this->respond();
}
$this->respond();
} catch (Exception $e) {
try{
if (Defaults::$returnResponse) {
return $this->message($e);
}
$this->message($e);
} catch (Exception $e2) {
if (Defaults::$returnResponse) {
return $this->message($e2);
}
$this->message($e2);
}
}
@ -426,6 +461,31 @@ class Restler extends EventDispatcher
$this->formatOverridesMap['extensions'] = array_keys($extensions);
}
/**
* Set one or more string to be considered as the base url
*
* When more than one base url is provided, restler will make
* use of $_SERVER['HTTP_HOST'] to find the right one
*
* @param string ,... $url
*/
public function setBaseUrls($url /*[, $url2...$urlN]*/)
{
if (func_num_args() > 1) {
$urls = func_get_args();
usort($urls, function ($a, $b) {
return strlen($a) - strlen($b);
});
foreach ($urls as $u) {
if (0 === strpos($_SERVER['HTTP_HOST'], parse_url($u, PHP_URL_HOST))) {
$this->baseUrl = $u;
return;
}
}
}
$this->baseUrl = $url;
}
/**
* Parses the request url and get the api path
*
@ -436,35 +496,43 @@ class Restler extends EventDispatcher
// fix SCRIPT_NAME for PHP 5.4 built-in web server
if (false === strpos($_SERVER['SCRIPT_NAME'], '.php'))
$_SERVER['SCRIPT_NAME']
= '/' . Util::removeCommonPath($_SERVER['SCRIPT_FILENAME'], $_SERVER['DOCUMENT_ROOT']);
= '/' . substr($_SERVER['SCRIPT_FILENAME'], strlen($_SERVER['DOCUMENT_ROOT']) + 1);
$fullPath = urldecode($_SERVER['REQUEST_URI']);
$path = Util::removeCommonPath(
$fullPath,
list($base, $path) = Util::splitCommonPath(
strtok(urldecode($_SERVER['REQUEST_URI']), '?'), //remove query string
$_SERVER['SCRIPT_NAME']
);
$port = isset($_SERVER['SERVER_PORT']) ? $_SERVER['SERVER_PORT'] : '80';
$https = $port == '443' ||
(isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') || // Amazon ELB
(isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on');
$baseUrl = ($https ? 'https://' : 'http://') . $_SERVER['SERVER_NAME'];
if (!$this->baseUrl) {
// Fix port number retrieval if port is specified in HOST header.
$host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : '';
$portPos = strpos($host,":");
if ($portPos){
$port = substr($host,$portPos+1);
} else {
$port = isset($_SERVER['SERVER_PORT']) ? $_SERVER['SERVER_PORT'] : '80';
$port = isset($_SERVER['HTTP_X_FORWARDED_PORT']) ? $_SERVER['HTTP_X_FORWARDED_PORT'] : $port; // Amazon ELB
}
$https = $port == '443' ||
(isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') || // Amazon ELB
(isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on');
$baseUrl = ($https ? 'https://' : 'http://') . $_SERVER['SERVER_NAME'];
if (!$https && $port != '80' || $https && $port != '443')
$baseUrl .= ':' . $port;
$this->baseUrl = $baseUrl . $base;
} elseif (!empty($base) && false === strpos($this->baseUrl, $base)) {
$this->baseUrl .= $base;
}
if (!$https && $port != '80' || $https && $port != '443')
$baseUrl .= ':' . $port;
$this->baseUrl = rtrim($baseUrl
. substr($fullPath, 0, strlen($fullPath) - strlen($path)), '/');
$path = rtrim(strtok($path, '?'), '/'); //remove query string and trailing slash if found any
$path = str_replace(
array_merge(
$this->formatMap['extensions'],
$this->formatOverridesMap['extensions']
),
'',
$path
rtrim($path, '/') //remove trailing slash if found
);
if (Defaults::$useUrlBasedVersioning && strlen($path) && $path{0} == 'v') {
$version = intval(substr($path, 1));
if ($version && $version <= $this->apiVersion) {
@ -865,23 +933,21 @@ class Restler extends EventDispatcher
protected function authenticate()
{
$o = & $this->apiMethodInfo;
$accessLevel = max(Defaults::$apiAccessLevel,
$o->accessLevel);
try {
if ($accessLevel || count($this->postAuthFilterClasses)) {
$this->dispatch('authenticate');
if (!count($this->authClasses)) {
throw new RestException(
403,
'at least one Authentication Class is required'
);
}
foreach ($this->authClasses as $authClass) {
$o = &$this->apiMethodInfo;
$accessLevel = max(Defaults::$apiAccessLevel, $o->accessLevel);
if ($accessLevel || count($this->postAuthFilterClasses)) {
$this->dispatch('authenticate');
if (!count($this->authClasses) && $accessLevel > 1) {
throw new RestException(
403,
'at least one Authentication Class is required'
);
}
$unauthorized = false;
foreach ($this->authClasses as $authClass) {
try {
$authObj = Scope::get($authClass);
if (!method_exists($authObj,
Defaults::$authenticationMethod)
) {
if (!method_exists($authObj, Defaults::$authenticationMethod)) {
throw new RestException (
500, 'Authentication Class ' .
'should implement iAuthenticate');
@ -890,16 +956,26 @@ class Restler extends EventDispatcher
) {
throw new RestException(401);
}
$unauthorized = false;
break;
} catch (InvalidAuthCredentials $e) {
$this->authenticated = false;
throw $e;
} catch (RestException $e) {
if (!$unauthorized) {
$unauthorized = $e;
}
}
$this->authenticated = true;
}
$this->authVerified = true;
} catch (RestException $e) {
$this->authVerified = true;
if ($accessLevel > 1) { //when it is not a hybrid api
throw ($e);
if ($unauthorized) {
if ($accessLevel > 1) { //when it is not a hybrid api
throw $unauthorized;
} else {
$this->authenticated = false;
}
} else {
$this->authenticated = false;
$this->authenticated = true;
}
}
}
@ -936,6 +1012,8 @@ class Restler extends EventDispatcher
}
//convert to instance of ValidationInfo
$info = new ValidationInfo($param);
//initialize validator
Scope::get(Defaults::$validatorClass);
$validator = Defaults::$validatorClass;
//if(!is_subclass_of($validator, 'Luracast\\Restler\\Data\\iValidate')) {
//changed the above test to below for addressing this php bug
@ -964,6 +1042,8 @@ class Restler extends EventDispatcher
$o = & $this->apiMethodInfo;
$accessLevel = max(Defaults::$apiAccessLevel,
$o->accessLevel);
if (function_exists('newrelic_name_transaction'))
newrelic_name_transaction("{$o->className}/{$o->methodName}");
$object = Scope::get($o->className);
switch ($accessLevel) {
case 3 : //protected method
@ -1057,6 +1137,8 @@ class Restler extends EventDispatcher
if (!Defaults::$suppressResponseCode) {
if ($e) {
$code = $e->getCode();
} elseif ($this->responseCode) {
$code = $this->responseCode;
} elseif (isset($this->apiMethodInfo->metadata['status'])) {
$code = $this->apiMethodInfo->metadata['status'];
}
@ -1084,9 +1166,13 @@ class Restler extends EventDispatcher
: 'Unknown';
@header('WWW-Authenticate: ' . $authString, false);
}
echo $this->responseData;
$this->dispatch('complete');
exit;
if (Defaults::$returnResponse) {
return $this->responseData;
} else {
echo $this->responseData;
exit;
}
}
protected function message(Exception $exception)
@ -1128,6 +1214,9 @@ class Restler extends EventDispatcher
$compose->message($exception),
!$this->productionMode
);
if (Defaults::$returnResponse) {
return $this->respond();
}
$this->respond();
}

View File

@ -2,7 +2,7 @@
namespace Luracast\Restler;
use Luracast\Restler\Data\ApiMethodInfo;
use Luracast\Restler\Data\String;
use Luracast\Restler\Data\Text;
use ReflectionClass;
use ReflectionMethod;
use ReflectionProperty;
@ -17,13 +17,32 @@ use Exception;
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
* @version 3.0.0rc6
*/
class Routes
{
public static $prefixingParameterNames = array(
'id'
);
public static $fieldTypesByName = array(
'email' => 'email',
'password' => 'password',
'phone' => 'tel',
'mobile' => 'tel',
'tel' => 'tel',
'search' => 'search',
'date' => 'date',
'created_at' => 'datetime',
'modified_at' => 'datetime',
'url' => 'url',
'link' => 'url',
'href' => 'url',
'website' => 'url',
'color' => 'color',
'colour' => 'color',
);
protected static $routes = array();
protected static $models = array();
@ -56,6 +75,7 @@ class Routes
* - Do not include it in URL
*/
$class = new ReflectionClass($className);
$dataName = CommentParser::$embeddedDataName;
try {
$classMetadata = CommentParser::parse($class->getDocComment());
} catch (Exception $e) {
@ -92,7 +112,7 @@ class Routes
= (isset($metadata['smart-auto-routing'])
&& $metadata['smart-auto-routing'] != 'true')
|| !Defaults::$smartAutoRouting;
$metadata['resourcePath'] = $resourcePath;
$metadata['resourcePath'] = trim($resourcePath, '/');
if (isset($classMetadata['description'])) {
$metadata['classDescription'] = $classMetadata['description'];
}
@ -123,44 +143,51 @@ class Routes
}
$m = & $metadata ['param'] [$position];
$m ['name'] = $param->getName();
if (!isset($m[$dataName])) {
$m[$dataName] = array();
}
$p = &$m[$dataName];
if (empty($m['label']))
$m['label'] = static::label($m['name']);
$m['label'] = Text::title($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';
if (isset(static::$fieldTypesByName[$m['name']]) && empty($p['type']) && $type == 'string') {
$p['type'] = static::$fieldTypesByName[$m['name']];
}
$m ['default'] = $defaults [$position];
$m ['required'] = !$param->isOptional();
$contentType = Util::nestedValue(
$m,
CommentParser::$embeddedDataName,
'type'
);
if ($contentType && $qualified = Scope::resolve($contentType, $scope)) {
list($m[CommentParser::$embeddedDataName]['type'], $children) = static::getTypeAndModel(
new ReflectionClass($qualified), $scope
$contentType = Util::nestedValue($p,'type');
if ($type == 'array' && $contentType && $qualified = Scope::resolve($contentType, $scope)) {
list($p['type'], $children, $modelName) = static::getTypeAndModel(
new ReflectionClass($qualified), $scope,
$className . Text::title($methodUrl), $p
);
}
if ($type instanceof ReflectionClass) {
list($type, $children) = static::getTypeAndModel($type, $scope);
list($type, $children, $modelName) = static::getTypeAndModel($type, $scope,
$className . Text::title($methodUrl), $p);
} elseif ($type && is_string($type) && $qualified = Scope::resolve($type, $scope)) {
list($type, $children)
= static::getTypeAndModel(new ReflectionClass($qualified), $scope);
list($type, $children, $modelName)
= static::getTypeAndModel(new ReflectionClass($qualified), $scope,
$className . Text::title($methodUrl), $p);
}
if (isset($type)) {
$m['type'] = $type;
}
$m['children'] = $children;
$m['children'] = $children;
if (isset($modelName)) {
$m['model'] = $modelName;
}
if ($m['name'] == Defaults::$fullRequestDataName) {
$from = 'body';
if (!isset($m['type'])) {
$type = $m['type'] = 'array';
}
} elseif (isset($m[CommentParser::$embeddedDataName]['from'])) {
$from = $m[CommentParser::$embeddedDataName]['from'];
} elseif (isset($p['from'])) {
$from = $p['from'];
} else {
if ((isset($type) && Util::isObjectOrArray($type))
) {
@ -174,7 +201,7 @@ class Routes
$from = 'body';
}
}
$m[CommentParser::$embeddedDataName]['from'] = $from;
$p['from'] = $from;
if (!isset($m['type'])) {
$type = $m['type'] = static::type($defaults[$position]);
}
@ -229,21 +256,21 @@ class Routes
strpos($url, '{' . $p['name'] . '}') ||
strpos($url, ':' . $p['name']);
if ($inPath) {
$copy['metadata']['param'][$i][CommentParser::$embeddedDataName]['from'] = 'path';
$copy['metadata']['param'][$i][$dataName]['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';
$copy['metadata']['param'][$i][$dataName]['from'] = 'query';
} elseif (empty($p[$dataName]['from']) || $p[$dataName]['from'] == 'path') {
$copy['metadata']['param'][$i][$dataName]['from'] = 'body';
}
}
$url = preg_replace_callback('/{[^}]+}|:[^\/]+/',
function ($matches) use ($call) {
function ($matches) use ($copy) {
$match = trim($matches[0], '{}:');
$index = $call['arguments'][$match];
$index = $copy['arguments'][$match];
return '{' .
Routes::typeChar(isset(
$call['metadata']['param'][$index]['type'])
? $call['metadata']['param'][$index]['type']
$copy['metadata']['param'][$index]['type'])
? $copy['metadata']['param'][$index]['type']
: null)
. $index . '}';
}, $url);
@ -269,11 +296,11 @@ class Routes
$lastPathParam = array_keys($pathParams);
$lastPathParam = end($lastPathParam);
for ($position = 0; $position < count($params); $position++) {
$from = $metadata['param'][$position][CommentParser::$embeddedDataName]['from'];
$from = $metadata['param'][$position][$dataName]['from'];
if ($from == 'body' && ($httpMethod == 'GET' ||
$httpMethod == 'DELETE')
) {
$call['metadata']['param'][$position][CommentParser::$embeddedDataName]['from']
$call['metadata']['param'][$position][$dataName]['from']
= 'query';
}
}
@ -313,7 +340,7 @@ class Routes
}
protected static function addPath($path, array $call,
$httpMethod = 'GET', $version = 1)
$httpMethod = 'GET', $version = 1)
{
$call['url'] = preg_replace_callback(
"/\{\S(\d+)\}/",
@ -347,7 +374,7 @@ class Routes
* @return ApiMethodInfo
*/
public static function find($path, $httpMethod,
$version = 1, array $data = array())
$version = 1, array $data = array())
{
$p = Util::nestedValue(static::$routes, "v$version");
if (!$p) {
@ -431,6 +458,62 @@ class Routes
throw new RestException($status, $message);
}
public static function findAll(array $excludedPaths = array(), array $excludedHttpMethods = array(), $version = 1)
{
$map = array();
$all = Util::nestedValue(self::$routes, "v$version");
$filter = array();
if (isset($all['*'])) {
$all = $all['*'] + $all;
unset($all['*']);
}
if(is_array($all)){
foreach ($all as $fullPath => $routes) {
foreach ($routes as $httpMethod => $route) {
if (in_array($httpMethod, $excludedHttpMethods)) {
continue;
}
foreach ($excludedPaths as $exclude) {
if (empty($exclude)) {
if ($fullPath == $exclude || $fullPath == 'index')
continue 2;
} elseif (Text::beginsWith($fullPath, $exclude)) {
continue 2;
}
}
$hash = "$httpMethod " . $route['url'];
if (!isset($filter[$hash])) {
$route['httpMethod'] = $httpMethod;
$map[$route['metadata']['resourcePath']][]
= array('access' => static::verifyAccess($route), 'route' => $route, 'hash' => $hash);
$filter[$hash] = true;
}
}
}
}
return $map;
}
public static function verifyAccess($route)
{
if ($route['accessLevel'] < 2)
return true;
/** @var Restler $r */
$r = Scope::get('Restler');
$authenticated = $r->_authenticated;
if (!$authenticated && $route['accessLevel'] > 1)
return false;
if (
$authenticated &&
Defaults::$accessControlFunction &&
(!call_user_func(Defaults::$accessControlFunction, $route['metadata']))
) {
return false;
}
return true;
}
/**
* Populates the parameter values
*
@ -445,14 +528,14 @@ class Routes
{
$call['parameters'] = $call['defaults'];
$p = & $call['parameters'];
$dataName = CommentParser::$embeddedDataName;
foreach ($data as $key => $value) {
if (isset($call['arguments'][$key])) {
$p[$call['arguments'][$key]] = $value;
}
}
if (Defaults::$smartParameterParsing && 'post' != (string)Util::$restler->requestFormat) {
if (Defaults::$smartParameterParsing) {
if (
count($p) == 1 &&
($m = Util::nestedValue($call, 'metadata', 'param', 0)) &&
!array_key_exists($m['name'], $data) &&
array_key_exists(Defaults::$fullRequestDataName, $data) &&
@ -466,7 +549,7 @@ class Routes
$lastBodyParamIndex = -1;
$lastM = null;
foreach ($call['metadata']['param'] as $k => $m) {
if ($m[CommentParser::$embeddedDataName]['from'] == 'body') {
if ($m[$dataName]['from'] == 'body') {
$bodyParamCount++;
$lastBodyParamIndex = $k;
$lastM = $m;
@ -526,6 +609,21 @@ class Routes
return true;
}
protected static function parseMagic(ReflectionClass $class, $forResponse = true)
{
if (!$c = CommentParser::parse($class->getDocComment())) {
return false;
}
$p = 'property';
$r = empty($c[$p]) ? array() : $c[$p];
$p .= '-' . ($forResponse ? 'read' : 'write');
if (!empty($c[$p])) {
$r = array_merge($r, $c[$p]);
}
return $r;
}
/**
* Get the type and associated model
*
@ -538,62 +636,105 @@ class Routes
*
* @access protected
*/
protected static function getTypeAndModel(ReflectionClass $class, array $scope)
protected static function getTypeAndModel(ReflectionClass $class, array $scope, $prefix='', array $rules=array())
{
$className = $class->getName();
if (isset(static::$models[$className])) {
return static::$models[$className];
$dataName = CommentParser::$embeddedDataName;
if (isset(static::$models[$prefix.$className])) {
return static::$models[$prefix.$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') ?: array();
} 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);
if ($magic_properties = static::parseMagic($class, empty($prefix))) {
foreach ($magic_properties as $prop) {
if (!isset($prop['name'])) {
throw new Exception('@property comment is not properly defined in ' . $className . ' class');
}
if (!isset($prop[$dataName]['label'])) {
$prop[$dataName]['label'] = Text::title($prop['name']);
}
if (isset(static::$fieldTypesByName[$prop['name']]) && $prop['type'] == 'string' && !isset($prop[$dataName]['type'])) {
$prop[$dataName]['type'] = static::$fieldTypesByName[$prop['name']];
}
$children[$prop['name']] = $prop;
}
} else {
$props = $class->getProperties(ReflectionProperty::IS_PUBLIC);
foreach ($props as $prop) {
$name = $prop->getName();
$child = array('name' => $name);
if ($c = $prop->getDocComment()) {
$child += Util::nestedValue(CommentParser::parse($c), 'var') ?: array();
} else {
$o = $class->newInstance();
$p = $prop->getValue($o);
if (is_object($p)) {
$child['type'] = get_class($p);
} elseif (is_array($p)) {
$child['type'] = 'array';
if (count($p)) {
$pc = reset($p);
if (is_object($pc)) {
$child['contentType'] = get_class($pc);
}
}
}
}
$child += array(
'type' => isset(static::$fieldTypesByName[$child['name']])
? static::$fieldTypesByName[$child['name']]
: 'string',
'label' => Text::title($child['name'])
);
isset($child[$dataName])
? $child[$dataName] += array('required' => true)
: $child[$dataName]['required'] = true;
if ($prop->class != $className && $qualified = Scope::resolve($child['type'], $scope)) {
list($child['type'], $child['children'])
= static::getTypeAndModel(new ReflectionClass($qualified), $scope);
} elseif (
($contentType = Util::nestedValue($child, $dataName, 'type')) &&
($qualified = Scope::resolve($contentType, $scope))
) {
list($child['contentType'], $child['children'])
= static::getTypeAndModel(new ReflectionClass($qualified), $scope);
}
$children[$name] = $child;
}
$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')) {
if (Text::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];
if ($properties = Util::nestedValue($rules, 'properties')) {
if (is_string($properties)) {
$properties = array($properties);
}
$c = array();
foreach ($properties as $property) {
if (isset($children[$property])) {
$c[$property] = $children[$property];
}
}
$children = $c;
}
if ($required = Util::nestedValue($rules, 'required')) {
//override required on children
if (is_bool($required)) {
// true means all are required false means none are required
$required = $required ? array_keys($children) : array();
} elseif (is_string($required)) {
$required = array($required);
}
$required = array_fill_keys($required, true);
foreach ($children as $name => $child) {
$children[$name][$dataName]['required'] = isset($required[$name]);
}
}
static::$models[$prefix.$className] = array($className, $children, $prefix.$className);
return static::$models[$prefix.$className];
}
/**
@ -625,20 +766,6 @@ class Routes
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();
@ -693,4 +820,4 @@ class Routes
}
return $imports;
}
}
}

View File

@ -11,57 +11,70 @@ namespace Luracast\Restler;
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
* @version 3.0.0rc6
*/
class Scope
{
public static $classAliases = array(
//Core
'Restler' => 'Luracast\Restler\Restler',
'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',
'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',
'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',
'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',
'Resources' => 'Luracast\Restler\Resources',
'Explorer' => 'Luracast\Restler\Explorer',
//Cache classes
'HumanReadableCache' => 'Luracast\Restler\HumanReadableCache',
'ApcCache' => 'Luracast\Restler\ApcCache',
'ApcCache' => 'Luracast\Restler\ApcCache',
'MemcacheCache' => 'Luracast\Restler\MemcacheCache',
//Utility classes
'Object' => 'Luracast\Restler\Data\Object',
'String' => 'Luracast\Restler\Data\String',
'Arr' => 'Luracast\Restler\Data\Arr',
'Object' => 'Luracast\Restler\Data\Object',
'Text' => 'Luracast\Restler\Data\Text',
'Arr' => 'Luracast\Restler\Data\Arr',
//Exception
'RestException' => 'Luracast\Restler\RestException'
'RestException' => 'Luracast\Restler\RestException'
);
/**
* @var null|Callable adding a resolver function that accepts
* the class name as the parameter and returns an instance of the class
* as a singleton. Allows the use of your favourite DI container
*/
public static $resolver = null;
public static $properties = array();
protected static $instances = array();
protected static $registry = array();
public static function register($name, Callable $function, $singleton = true)
/**
* @param string $name
* @param callable $function
* @param bool $singleton
*/
public static function register($name, $function, $singleton = true)
{
static::$registry[$name] = (object)compact('function', 'singleton');
}
@ -82,8 +95,19 @@ class Scope
} elseif (!empty(static::$registry[$name])) {
$function = static::$registry[$name]->function;
$r = $function();
if (static::$registry[$name]->singleton)
if (static::$registry[$name]->singleton) {
static::$instances[$name] = (object)array('instance' => $r);
}
} elseif (is_callable(static::$resolver) && false === stristr($name, 'Luracast\Restler')) {
$fullName = $name;
if (isset(static::$classAliases[$name])) {
$fullName = static::$classAliases[$name];
}
/** @var Callable $function */
$function = static::$resolver;
$r = $function($fullName);
static::$instances[$name] = (object)array('instance' => $r);
static::$instances[$name]->initPending = true;
} else {
$fullName = $name;
if (isset(static::$classAliases[$name])) {
@ -100,10 +124,10 @@ class Scope
$properties = Util::nestedValue(
$m, 'class', $fullName,
CommentParser::$embeddedDataName
) ? : (Util::nestedValue(
) ?: (Util::nestedValue(
$m, 'class', $shortName,
CommentParser::$embeddedDataName
) ? : array());
) ?: array());
} else {
static::$instances[$name]->initPending = true;
}
@ -117,7 +141,7 @@ class Scope
) {
static::$instances[$name]->authVerified = true;
$r->__setAuthenticationStatus
(static::get('Restler')->_authenticated);
(static::get('Restler')->_authenticated);
}
if (isset(static::$instances[$name]->initPending)) {
$m = Util::nestedValue(static::get('Restler'), 'apiMethodInfo', 'metadata');
@ -126,17 +150,18 @@ class Scope
$shortName = Util::getShortName($name);
} else {
$shortName = $name;
if (isset(static::$classAliases[$name]))
if (isset(static::$classAliases[$name])) {
$fullName = static::$classAliases[$name];
}
}
if ($m) {
$properties = Util::nestedValue(
$m, 'class', $fullName,
CommentParser::$embeddedDataName
) ? : (Util::nestedValue(
) ?: (Util::nestedValue(
$m, 'class', $shortName,
CommentParser::$embeddedDataName
) ? : array());
) ?: array());
unset(static::$instances[$name]->initPending);
$initialized = false;
}

View File

@ -4,6 +4,12 @@ namespace Luracast\Restler\UI;
use Luracast\Restler\UI\Tags as T;
use Luracast\Restler\Util;
/**
* Class Emmet
* @package Luracast\Restler\UI
*
* @version 3.0.0rc6
*/
class Emmet
{
const DELIMITERS = '.#*>+^[=" ]{$@-#}';

View File

@ -10,32 +10,32 @@ namespace Luracast\Restler\UI;
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
* @version 3.0.0rc6
*/
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#}',
'input' => '.row>section>label{$label#}^input[id=$id# name=$name# value=$value# type=$type# required=$required# autofocus=$autofocus# placeholder=$default# accept=$accept# disabled=$disabled#]',
'textarea' => '.row>label{$label#}^textarea[id=$id# name=$name# required=$required# autofocus=$autofocus# placeholder=$default# rows=3 disabled=$disabled#]{$value#}',
'radio' => '.row>section>label{$label#}^span>label*options>input[id=$id# name=$name# value=$value# type=radio checked=$selected# required=$required# disabled=$disabled#]+{ $text#}',
'select' => '.row>label{$label#}^select[id=$id# name=$name# required=$required#]>option[value]+option[value=$value# selected=$selected# disabled=$disabled#]{$text#}*options',
'submit' => '.row>label{ &nbsp; }^button[id=$id# type=submit disabled=$disabled#]{$label#}',
'fieldset' => 'fieldset>legend{$label#}',
'checkbox' => '.row>label>input[name=$name# value=$value# type=checkbox checked=$selected# required=$required# autofocus=$autofocus# accept=$accept#]+{$label#}',
'checkbox' => '.row>label>input[id=$id# name=$name# value=$value# type=checkbox checked=$selected# required=$required# autofocus=$autofocus# accept=$accept# disabled=$disabled#]+{$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#}',
'input' => '.form-group.$error#>label{$label#}+input.form-control[id=$id# name=$name# value=$value# type=$type# required=$required# autofocus=$autofocus# placeholder=$default# accept=$accept# disabled=$disabled#]+small.help-block>{$message#}',
'textarea' => '.form-group>label{$label#}+textarea.form-control[id=$id# name=$name# required=$required# autofocus=$autofocus# placeholder=$default# rows=3 disabled=$disabled#]{$value#}+small.help-block>{$message#}',
'radio' => 'fieldset>legend{$label#}>.radio*options>label>input.radio[name=$name# value=$value# type=radio checked=$selected# required=$required# disabled=$disabled#]{$text#}+p.help-block>{$message#}',
'select' => '.form-group>label{$label#}+select.form-control[id=$id# name=$name# multiple=$multiple# required=$required#]>option[value]+option[value=$value# selected=$selected# disabled=$disabled#]{$text#}*options',
'submit' => 'button.btn.btn-primary[id=$id# type=submit]{$label#} disabled=$disabled#',
'fieldset' => 'fieldset>legend{$label#}',
'checkbox' => '.checkbox>label>input[name=$name# value=$value# type=checkbox checked=$selected# required=$required# autofocus=$autofocus# accept=$accept#]+{$label#}',
'checkbox' => '.checkbox>label>input[id=$id# name=$name# value=$value# type=checkbox checked=$selected# required=$required# autofocus=$autofocus# disabled=$disabled#]+{$label#}^p.help-block>{$error#}',
//------------- 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',
@ -44,15 +44,15 @@ class FormStyles
);
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#}',
'input' => 'label{$label#}+input[id=$id# name=$name# value=$value# type=$type# required=$required# autofocus=$autofocus# placeholder=$default# accept=$accept# disabled=$disabled#]',
'textarea' => 'label{$label#}+textarea[id=$id# name=$name# required=$required# autofocus=$autofocus# placeholder=$default# rows=3 disabled=$disabled#]{$value#}',
'radio' => 'label{$label# : &nbsp;}+label.radio-inline*options>input.radio[name=$name# value=$value# type=radio checked=$selected# required=$required# disabled=$disabled#]+{$text#}',
'select' => 'label{$label#}+select[id=$id# name=$name# required=$required#]>option[value]+option[value=$value# selected=$selected# disabled=$disabled#]{$text#}*options',
'submit' => 'button.button[id=$id# type=submit disabled=$disabled#]{$label#}',
'fieldset' => 'fieldset>legend{$label#}',
'checkbox' => 'label>input[name=$name# value=$value# type=checkbox checked=$selected# required=$required# autofocus=$autofocus# accept=$accept#]+{ $label#}',
'checkbox' => 'label>input[id=$id# name=$name# value=$value# type=checkbox checked=$selected# required=$required# autofocus=$autofocus# disabled=$disabled#]+{ $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#}',
'checkbox-array' => 'fieldset>legend{$label#}+label*options>input[name=$name# value=$value# type=checkbox checked=$selected# required=$required# autofocus=$autofocus#]+{ $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

@ -3,8 +3,9 @@ namespace Luracast\Restler\UI;
use Luracast\Restler\CommentParser;
use Luracast\Restler\Data\ApiMethodInfo;
use Luracast\Restler\Data\String;
use Luracast\Restler\Data\Text;
use Luracast\Restler\Data\ValidationInfo;
use Luracast\Restler\Data\Validator;
use Luracast\Restler\Defaults;
use Luracast\Restler\Format\UploadFormat;
use Luracast\Restler\Format\UrlEncodedFormat;
@ -28,7 +29,7 @@ use Luracast\Restler\Util;
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
* @version 3.0.0rc6
*/
class Forms implements iFilter
{
@ -190,6 +191,9 @@ class Forms implements iFilter
static::$fileUpload = false;
$t['enctype'] = 'multipart/form-data';
}
if (isset($m[CommentParser::$embeddedDataName])) {
$t += $m[CommentParser::$embeddedDataName];
}
if (!$dataOnly) {
$t = Emmet::make(static::style('form', $m), $t);
$t->prefix = $prefix;
@ -292,14 +296,14 @@ class Forms implements iFilter
$options[] = $option;
}
} elseif ($p->type == 'boolean' || $p->type == 'bool') {
if (String::beginsWith($type, 'radio')) {
if (Text::beginsWith($type, 'radio') || Text::beginsWith($type, 'select')) {
$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 {
} else { //checkbox
$r = array(
'tag' => $tag,
'name' => $name,
@ -312,6 +316,9 @@ class Forms implements iFilter
if ($p->default) {
$r['selected'] = true;
}
if (isset($p->rules)) {
$r += $p->rules;
}
}
}
if (empty($r)) {
@ -325,16 +332,25 @@ class Forms implements iFilter
'options' => & $options,
'multiple' => $multiple,
);
if (isset($p->rules)) {
$r += $p->rules;
}
}
if ($type == 'file') {
static::$fileUpload = true;
$r['accept'] = implode(', ', UploadFormat::$allowedMimeTypes);
if (empty($r['accept'])) {
$r['accept'] = implode(', ', UploadFormat::$allowedMimeTypes);
}
}
if (!empty(Validator::$exceptions[$name]) && static::$info->url == Scope::get('Restler')->url) {
$r['error'] = 'has-error';
$r['message'] = Validator::$exceptions[$p->name]->getMessage();
}
if (true === $p->required)
$r['required'] = true;
$r['required'] = 'required';
if (isset($p->rules['autofocus']))
$r['autofocus'] = true;
$r['autofocus'] = 'autofocus';
/*
echo "<pre>";
print_r($r);
@ -411,7 +427,7 @@ class Forms implements iFilter
if (empty($exclude)) {
if ($url == $exclude)
return true;
} elseif (String::beginsWith($url, $exclude)) {
} elseif (Text::beginsWith($url, $exclude)) {
return true;
}
}

View File

@ -2,7 +2,7 @@
namespace Luracast\Restler\UI;
use Luracast\Restler\CommentParser;
use Luracast\Restler\Defaults;
use Luracast\Restler\Data\Text;
use Luracast\Restler\Restler;
use Luracast\Restler\Routes;
use Luracast\Restler\Scope;
@ -19,18 +19,13 @@ use Luracast\Restler\Util;
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
* @version 3.0.0rc6
*/
class Nav
{
protected static $tree = array();
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
@ -40,169 +35,188 @@ class Nav
* @var array prefix additional menu items with one of the following syntax
* [$path => $text]
* [$path]
* [$path => ['text' => $text, 'url' => $url]]
* [$path => ['text' => $text, 'url' => $url, 'trail'=> $trail]]
*/
public static $prepends = array();
/**
* @var array suffix additional menu items with one of the following syntax
* [$path => $text]
* [$path]
* [$path => ['text' => $text, 'url' => $url]]
* [$path => ['text' => $text, 'url' => $url, 'trail'=> $trail]]
*/
public static $appends = array();
public static $addExtension = true;
protected static $extension = '';
protected static $activeTrail = '';
protected static $url;
public static function get($for = '', $activeUrl = null)
public static function get($for = '', $activeTrail = 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 (empty(static::$tree)) {
/** @var Restler $restler */
$restler = Scope::get('Restler');
if (static::$addExtension)
static::$extension = isset($restler->responseFormat)
? '.' . $restler->responseFormat->getExtension()
: '.html';
static::$url = $restler->getBaseUrl();
if (empty(static::$url))
static::$url = '';
static::$activeTrail = $activeTrail = empty($activeTrail)
? (empty($restler->url) || $restler->url == 'index'
? static::$root
: $restler->url
)
: $activeTrail;
if (static::$addExtension)
static::$extension = isset($restler->responseFormat)
? '.' . $restler->responseFormat->getExtension()
: '.html';
static::addUrls(static::$prepends);
$map = Routes::findAll(
static::$excludedPaths,
array('POST', 'DELETE', 'PUT', 'PATCH'),
$restler->getRequestedApiVersion()
);
foreach ($map as $path => $data) {
foreach ($data as $item) {
$access = $item['access'];
$route = $item['route'];
$url = $route['url'];
if ($access && !Text::contains($url, '{')) {
$label = Util::nestedValue(
$route,
'metadata',
CommentParser::$embeddedDataName,
'label'
);
if (!empty($url)) {
$url .= static::$extension;
}
static::add($url, $label);
}
}
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);
}
static::addUrls(static::$appends);
} elseif (empty($activeTrail)) {
$activeTrail = static::$activeTrail;
}
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);
$tree = static::$tree;
$activeTrail = explode('/', $activeTrail);
$nested = & static::nested($tree, $activeTrail);
if (is_array($nested)) {
$nested['active'] = true;
}
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;
$tree = static::nested($tree, $for)['children'];
}
return $tree;
return array_filter($tree);
}
protected static function build(&$tree, $path,
$url = null, $text = null, $activeUrl = null)
protected static function & nested(array & $tree, array $parts)
{
$parts = explode('/', $path);
if (!empty($parts)) {
$part = array_shift($parts);
if (empty($tree[$part])) {
return $tree[$part];
} elseif (empty($parts)) {
return static::nested($tree[$part], $parts);
} elseif (!empty($tree[$part]['children'])) {
return static::nested($tree[$part]['children'], $parts);
}
} else {
return $tree;
}
return null;
}
public static function addUrls(array $urls)
{
foreach ($urls as $url => $label) {
$trail = null;
if (is_array($label)) {
if (isset($label['trail'])) {
$trail = $label['trail'];
}
if (isset($label['url'])) {
$url = $label['url'];
$label = isset($label['label']) ? $label['label'] : null;
} else {
$url = current(array_keys($label));
$label = current($label);
}
}
if (is_numeric($url)) {
$url = $label;
$label = null;
}
static::add($url, $label, $trail);
}
return static::$tree;
}
public static function add($url, $label = null, $trail = null)
{
$r = parse_url($url);
if (is_null($trail)) {
$trail = isset($r['path']) ? $r['path'] : static::$root;
}
//remove / prefix and / suffixes and any extension
$trail = strtok(trim($trail, '/'), '.');
$parts = explode('/', $trail);
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();
if (isset($r['fragment'])) {
$parts[] = $r['fragment'];
if (is_null($label)) {
$label = Text::title($r['fragment']);
}
}
if (empty($r['scheme'])) {
//relative url found
if (empty($url)) {
$label = Text::title(static::$root);
$url = static::$url;
} else {
$url = static::$url . '/' . ltrim($url, '/');
}
}
if (is_null($label)) {
$label = Text::title(strtok(end($parts), '.'));
}
$r['url'] = $url;
$r['path'] = $trail;
$r['parts'] = $parts;
$r['label'] = $label;
static::build($r);
return $r;
}
public static function build(array $r)
{
$p = & static::$tree;
$parts = $r['parts'];
$last = count($parts) - 1;
foreach ($parts as $i => $part) {
if ($i == $last) {
$p[$part]['text'] = $r['label'];
$p[$part]['href'] = $r['url'];
$p[$part]['class'] = Text::slug($part);
/* dynamically do it at run time instead
if ($r['path'] == static::$activeTrail)
$p[$part]['active'] = true;
*/
} elseif (!isset($p[$part])) {
$p[$part] = array();
$p[$part]['text'] = Text::title($part);
$p[$part]['href'] = '#';
$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

@ -14,7 +14,7 @@ use Luracast\Restler\Util;
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
* @version 3.0.0rc6
*
* ============================ magic properties ==============================
* @property Tags parent parent tag
@ -154,10 +154,10 @@ class Tags implements ArrayAccess, Countable
*/
public function id($value)
{
$this->attributes['id'] = isset($value)
? (string)$value
: Util::nestedValue($this->attributes, 'name');
static::$instances[$value] = $this;
if (!empty($value) && is_string($value)) {
$this->attributes['id'] = $value;
static::$instances[$value] = $this;
}
return $this;
}

View File

@ -11,7 +11,7 @@ namespace Luracast\Restler;
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
* @version 3.0.0rc6
*/
class User implements iIdentifyUser
{

View File

@ -9,7 +9,7 @@ namespace Luracast\Restler;
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
* @version 3.0.0rc6
*/
class Util
{
@ -50,8 +50,8 @@ class Util
* 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
* @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
@ -129,6 +129,38 @@ class Util
return implode($char, $fromPath);
}
/**
* Compare two strings and split the common
* sub string from the first string and return it as array
*
* @static
*
* @param string $fromPath
* @param string $usingPath
* @param string $char
* optional, set it as
* blank string for char by char comparison
*
* @return array with 2 strings first is the common string and second is the remaining in $fromPath
*/
public static function splitCommonPath($fromPath, $usingPath, $char = '/')
{
if (empty($fromPath))
return array('', '');
$fromPath = explode($char, $fromPath);
$usingPath = explode($char, $usingPath);
$commonPath = array();
while (count($usingPath)) {
if (count($fromPath) && $fromPath[0] == $usingPath[0]) {
$commonPath [] = array_shift($fromPath);
} else {
break;
}
array_shift($usingPath);
}
return array(implode($char, $commonPath), implode($char, $fromPath));
}
/**
* Parses the request to figure out the http request type
*

View File

@ -9,10 +9,6 @@
{
"name":"Luracast",
"email":"arul@luracast.com"
},
{
"name":"Nick nickl- Lombard",
"email":"github@jigsoft.co.za"
}
],
"extra":{
@ -21,41 +17,23 @@
}
},
"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)"
"rodneyrehm/plist":"If you need Apple plist binary/xml format",
"zendframework/zendamf":"If you need AMF format",
"symfony/yaml":"If you need YAML format",
"twig/twig":"If you want to use twig templates with Html format",
"mustache/mustache":"If you want to use mustache/handlebar templates with Html format",
"illuminate/view":"If you want to use laravel blade templates with Html format",
"bshaffer/oauth2-server-php":"If you want to use OAuth2 for authentication"
},
"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":{

View File

@ -0,0 +1,125 @@
/* http://meyerweb.com/eric/tools/css/reset/ v2.0 | 20110126 */
html,
body,
div,
span,
applet,
object,
iframe,
h1,
h2,
h3,
h4,
h5,
h6,
p,
blockquote,
pre,
a,
abbr,
acronym,
address,
big,
cite,
code,
del,
dfn,
em,
img,
ins,
kbd,
q,
s,
samp,
small,
strike,
strong,
sub,
sup,
tt,
var,
b,
u,
i,
center,
dl,
dt,
dd,
ol,
ul,
li,
fieldset,
form,
label,
legend,
table,
caption,
tbody,
tfoot,
thead,
tr,
th,
td,
article,
aside,
canvas,
details,
embed,
figure,
figcaption,
footer,
header,
hgroup,
menu,
nav,
output,
ruby,
section,
summary,
time,
mark,
audio,
video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
menu,
nav,
section {
display: block;
}
body {
line-height: 1;
}
ol,
ul {
list-style: none;
}
blockquote,
q {
quotes: none;
}
blockquote:before,
blockquote:after,
q:before,
q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

View File

@ -0,0 +1,82 @@
<!DOCTYPE html>
<html>
<head>
<title>Api Explorer</title>
<link href='https://fonts.googleapis.com/css?family=Droid+Sans:400,700' rel='stylesheet' type='text/css'/>
<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css" rel="stylesheet">
<link href='css/reset.css' media='screen' rel='stylesheet' type='text/css'/>
<link href='css/screen.css' media='screen' rel='stylesheet' type='text/css'/>
<link href='css/reset.css' media='print' rel='stylesheet' type='text/css'/>
<link href='css/screen.css' media='print' rel='stylesheet' type='text/css'/>
<script type="text/javascript" src="lib/shred.bundle.js"></script>
<script src='lib/jquery-1.8.0.min.js' type='text/javascript'></script>
<script src='lib/jquery.slideto.min.js' type='text/javascript'></script>
<script src='lib/jquery.wiggle.min.js' type='text/javascript'></script>
<script src='lib/jquery.ba-bbq.min.js' type='text/javascript'></script>
<script src='lib/handlebars-1.0.0.js' type='text/javascript'></script>
<script src='lib/underscore-min.js' type='text/javascript'></script>
<script src='lib/backbone-min.js' type='text/javascript'></script>
<script src='lib/swagger.js' type='text/javascript'></script>
<script src='swagger-ui.js' type='text/javascript'></script>
<script src='lib/highlight.7.3.pack.js' type='text/javascript'></script>
<!-- enabling this will enable oauth2 implicit scope support -->
<script src='lib/swagger-oauth.js' type='text/javascript'></script>
<script type="text/javascript">
$(function () {
window.swaggerUi = new SwaggerUi({
url: "resources.json",
dom_id: "swagger-ui-container",
supportedSubmitMethods: ['get', 'post', 'put', 'patch', 'delete'],
onComplete: function(swaggerApi, swaggerUi){
log("Loaded Api Explorer");
if(typeof initOAuth == "function") {
/*
initOAuth({
clientId: "your-client-id",
realm: "your-realms",
appName: "your-app-name"
});
*/
}
$('pre code').each(function(i, e) {
hljs.highlightBlock(e)
});
},
onFailure: function(data) {
log("Unable to Load Api Explorer");
},
docExpansion: "none"
});
$('#input_apiKey').change(function() {
var key = $('#input_apiKey')[0].value;
log("key: " + key);
if(key && key.trim() != "") {
log("added key " + key);
window.authorizations.add("key", new ApiKeyAuthorization("api_key", key, "query"));
}
})
window.swaggerUi.load();
});
</script>
</head>
<body class="swagger-section">
<div id='header'>
<div class="swagger-ui-wrap">
<a id="logo" href="#">API Explorer</a>
<form id='api_selector'>
<div class='input'><input placeholder="http://example.com/api" id="input_baseUrl" name="baseUrl" type="hidden" value="resources.json"/></div>
<div class='input'><input placeholder="api_key" id="input_apiKey" name="apiKey" type="text"/></div>
<div class='input'><a id="explore" href="#">Explore</a></div>
</form>
</div>
</div>
<div id="message-bar" class="swagger-ui-wrap">&nbsp;</div>
<div id="swagger-ui-container" class="swagger-ui-wrap"></div>
</body>
</html>

View File

@ -0,0 +1,38 @@
// Backbone.js 0.9.2
// (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
// Backbone may be freely distributed under the MIT license.
// For all details and documentation:
// http://backbonejs.org
(function(){var l=this,y=l.Backbone,z=Array.prototype.slice,A=Array.prototype.splice,g;g="undefined"!==typeof exports?exports:l.Backbone={};g.VERSION="0.9.2";var f=l._;!f&&"undefined"!==typeof require&&(f=require("underscore"));var i=l.jQuery||l.Zepto||l.ender;g.setDomLibrary=function(a){i=a};g.noConflict=function(){l.Backbone=y;return this};g.emulateHTTP=!1;g.emulateJSON=!1;var p=/\s+/,k=g.Events={on:function(a,b,c){var d,e,f,g,j;if(!b)return this;a=a.split(p);for(d=this._callbacks||(this._callbacks=
{});e=a.shift();)f=(j=d[e])?j.tail:{},f.next=g={},f.context=c,f.callback=b,d[e]={tail:g,next:j?j.next:f};return this},off:function(a,b,c){var d,e,h,g,j,q;if(e=this._callbacks){if(!a&&!b&&!c)return delete this._callbacks,this;for(a=a?a.split(p):f.keys(e);d=a.shift();)if(h=e[d],delete e[d],h&&(b||c))for(g=h.tail;(h=h.next)!==g;)if(j=h.callback,q=h.context,b&&j!==b||c&&q!==c)this.on(d,j,q);return this}},trigger:function(a){var b,c,d,e,f,g;if(!(d=this._callbacks))return this;f=d.all;a=a.split(p);for(g=
z.call(arguments,1);b=a.shift();){if(c=d[b])for(e=c.tail;(c=c.next)!==e;)c.callback.apply(c.context||this,g);if(c=f){e=c.tail;for(b=[b].concat(g);(c=c.next)!==e;)c.callback.apply(c.context||this,b)}}return this}};k.bind=k.on;k.unbind=k.off;var o=g.Model=function(a,b){var c;a||(a={});b&&b.parse&&(a=this.parse(a));if(c=n(this,"defaults"))a=f.extend({},c,a);b&&b.collection&&(this.collection=b.collection);this.attributes={};this._escapedAttributes={};this.cid=f.uniqueId("c");this.changed={};this._silent=
{};this._pending={};this.set(a,{silent:!0});this.changed={};this._silent={};this._pending={};this._previousAttributes=f.clone(this.attributes);this.initialize.apply(this,arguments)};f.extend(o.prototype,k,{changed:null,_silent:null,_pending:null,idAttribute:"id",initialize:function(){},toJSON:function(){return f.clone(this.attributes)},get:function(a){return this.attributes[a]},escape:function(a){var b;if(b=this._escapedAttributes[a])return b;b=this.get(a);return this._escapedAttributes[a]=f.escape(null==
b?"":""+b)},has:function(a){return null!=this.get(a)},set:function(a,b,c){var d,e;f.isObject(a)||null==a?(d=a,c=b):(d={},d[a]=b);c||(c={});if(!d)return this;d instanceof o&&(d=d.attributes);if(c.unset)for(e in d)d[e]=void 0;if(!this._validate(d,c))return!1;this.idAttribute in d&&(this.id=d[this.idAttribute]);var b=c.changes={},h=this.attributes,g=this._escapedAttributes,j=this._previousAttributes||{};for(e in d){a=d[e];if(!f.isEqual(h[e],a)||c.unset&&f.has(h,e))delete g[e],(c.silent?this._silent:
b)[e]=!0;c.unset?delete h[e]:h[e]=a;!f.isEqual(j[e],a)||f.has(h,e)!=f.has(j,e)?(this.changed[e]=a,c.silent||(this._pending[e]=!0)):(delete this.changed[e],delete this._pending[e])}c.silent||this.change(c);return this},unset:function(a,b){(b||(b={})).unset=!0;return this.set(a,null,b)},clear:function(a){(a||(a={})).unset=!0;return this.set(f.clone(this.attributes),a)},fetch:function(a){var a=a?f.clone(a):{},b=this,c=a.success;a.success=function(d,e,f){if(!b.set(b.parse(d,f),a))return!1;c&&c(b,d)};
a.error=g.wrapError(a.error,b,a);return(this.sync||g.sync).call(this,"read",this,a)},save:function(a,b,c){var d,e;f.isObject(a)||null==a?(d=a,c=b):(d={},d[a]=b);c=c?f.clone(c):{};if(c.wait){if(!this._validate(d,c))return!1;e=f.clone(this.attributes)}a=f.extend({},c,{silent:!0});if(d&&!this.set(d,c.wait?a:c))return!1;var h=this,i=c.success;c.success=function(a,b,e){b=h.parse(a,e);if(c.wait){delete c.wait;b=f.extend(d||{},b)}if(!h.set(b,c))return false;i?i(h,a):h.trigger("sync",h,a,c)};c.error=g.wrapError(c.error,
h,c);b=this.isNew()?"create":"update";b=(this.sync||g.sync).call(this,b,this,c);c.wait&&this.set(e,a);return b},destroy:function(a){var a=a?f.clone(a):{},b=this,c=a.success,d=function(){b.trigger("destroy",b,b.collection,a)};if(this.isNew())return d(),!1;a.success=function(e){a.wait&&d();c?c(b,e):b.trigger("sync",b,e,a)};a.error=g.wrapError(a.error,b,a);var e=(this.sync||g.sync).call(this,"delete",this,a);a.wait||d();return e},url:function(){var a=n(this,"urlRoot")||n(this.collection,"url")||t();
return this.isNew()?a:a+("/"==a.charAt(a.length-1)?"":"/")+encodeURIComponent(this.id)},parse:function(a){return a},clone:function(){return new this.constructor(this.attributes)},isNew:function(){return null==this.id},change:function(a){a||(a={});var b=this._changing;this._changing=!0;for(var c in this._silent)this._pending[c]=!0;var d=f.extend({},a.changes,this._silent);this._silent={};for(c in d)this.trigger("change:"+c,this,this.get(c),a);if(b)return this;for(;!f.isEmpty(this._pending);){this._pending=
{};this.trigger("change",this,a);for(c in this.changed)!this._pending[c]&&!this._silent[c]&&delete this.changed[c];this._previousAttributes=f.clone(this.attributes)}this._changing=!1;return this},hasChanged:function(a){return!arguments.length?!f.isEmpty(this.changed):f.has(this.changed,a)},changedAttributes:function(a){if(!a)return this.hasChanged()?f.clone(this.changed):!1;var b,c=!1,d=this._previousAttributes,e;for(e in a)if(!f.isEqual(d[e],b=a[e]))(c||(c={}))[e]=b;return c},previous:function(a){return!arguments.length||
!this._previousAttributes?null:this._previousAttributes[a]},previousAttributes:function(){return f.clone(this._previousAttributes)},isValid:function(){return!this.validate(this.attributes)},_validate:function(a,b){if(b.silent||!this.validate)return!0;var a=f.extend({},this.attributes,a),c=this.validate(a,b);if(!c)return!0;b&&b.error?b.error(this,c,b):this.trigger("error",this,c,b);return!1}});var r=g.Collection=function(a,b){b||(b={});b.model&&(this.model=b.model);b.comparator&&(this.comparator=b.comparator);
this._reset();this.initialize.apply(this,arguments);a&&this.reset(a,{silent:!0,parse:b.parse})};f.extend(r.prototype,k,{model:o,initialize:function(){},toJSON:function(a){return this.map(function(b){return b.toJSON(a)})},add:function(a,b){var c,d,e,g,i,j={},k={},l=[];b||(b={});a=f.isArray(a)?a.slice():[a];c=0;for(d=a.length;c<d;c++){if(!(e=a[c]=this._prepareModel(a[c],b)))throw Error("Can't add an invalid model to a collection");g=e.cid;i=e.id;j[g]||this._byCid[g]||null!=i&&(k[i]||this._byId[i])?
l.push(c):j[g]=k[i]=e}for(c=l.length;c--;)a.splice(l[c],1);c=0;for(d=a.length;c<d;c++)(e=a[c]).on("all",this._onModelEvent,this),this._byCid[e.cid]=e,null!=e.id&&(this._byId[e.id]=e);this.length+=d;A.apply(this.models,[null!=b.at?b.at:this.models.length,0].concat(a));this.comparator&&this.sort({silent:!0});if(b.silent)return this;c=0;for(d=this.models.length;c<d;c++)if(j[(e=this.models[c]).cid])b.index=c,e.trigger("add",e,this,b);return this},remove:function(a,b){var c,d,e,g;b||(b={});a=f.isArray(a)?
a.slice():[a];c=0;for(d=a.length;c<d;c++)if(g=this.getByCid(a[c])||this.get(a[c]))delete this._byId[g.id],delete this._byCid[g.cid],e=this.indexOf(g),this.models.splice(e,1),this.length--,b.silent||(b.index=e,g.trigger("remove",g,this,b)),this._removeReference(g);return this},push:function(a,b){a=this._prepareModel(a,b);this.add(a,b);return a},pop:function(a){var b=this.at(this.length-1);this.remove(b,a);return b},unshift:function(a,b){a=this._prepareModel(a,b);this.add(a,f.extend({at:0},b));return a},
shift:function(a){var b=this.at(0);this.remove(b,a);return b},get:function(a){return null==a?void 0:this._byId[null!=a.id?a.id:a]},getByCid:function(a){return a&&this._byCid[a.cid||a]},at:function(a){return this.models[a]},where:function(a){return f.isEmpty(a)?[]:this.filter(function(b){for(var c in a)if(a[c]!==b.get(c))return!1;return!0})},sort:function(a){a||(a={});if(!this.comparator)throw Error("Cannot sort a set without a comparator");var b=f.bind(this.comparator,this);1==this.comparator.length?
this.models=this.sortBy(b):this.models.sort(b);a.silent||this.trigger("reset",this,a);return this},pluck:function(a){return f.map(this.models,function(b){return b.get(a)})},reset:function(a,b){a||(a=[]);b||(b={});for(var c=0,d=this.models.length;c<d;c++)this._removeReference(this.models[c]);this._reset();this.add(a,f.extend({silent:!0},b));b.silent||this.trigger("reset",this,b);return this},fetch:function(a){a=a?f.clone(a):{};void 0===a.parse&&(a.parse=!0);var b=this,c=a.success;a.success=function(d,
e,f){b[a.add?"add":"reset"](b.parse(d,f),a);c&&c(b,d)};a.error=g.wrapError(a.error,b,a);return(this.sync||g.sync).call(this,"read",this,a)},create:function(a,b){var c=this,b=b?f.clone(b):{},a=this._prepareModel(a,b);if(!a)return!1;b.wait||c.add(a,b);var d=b.success;b.success=function(e,f){b.wait&&c.add(e,b);d?d(e,f):e.trigger("sync",a,f,b)};a.save(null,b);return a},parse:function(a){return a},chain:function(){return f(this.models).chain()},_reset:function(){this.length=0;this.models=[];this._byId=
{};this._byCid={}},_prepareModel:function(a,b){b||(b={});a instanceof o?a.collection||(a.collection=this):(b.collection=this,a=new this.model(a,b),a._validate(a.attributes,b)||(a=!1));return a},_removeReference:function(a){this==a.collection&&delete a.collection;a.off("all",this._onModelEvent,this)},_onModelEvent:function(a,b,c,d){("add"==a||"remove"==a)&&c!=this||("destroy"==a&&this.remove(b,d),b&&a==="change:"+b.idAttribute&&(delete this._byId[b.previous(b.idAttribute)],this._byId[b.id]=b),this.trigger.apply(this,
arguments))}});f.each("forEach,each,map,reduce,reduceRight,find,detect,filter,select,reject,every,all,some,any,include,contains,invoke,max,min,sortBy,sortedIndex,toArray,size,first,initial,rest,last,without,indexOf,shuffle,lastIndexOf,isEmpty,groupBy".split(","),function(a){r.prototype[a]=function(){return f[a].apply(f,[this.models].concat(f.toArray(arguments)))}});var u=g.Router=function(a){a||(a={});a.routes&&(this.routes=a.routes);this._bindRoutes();this.initialize.apply(this,arguments)},B=/:\w+/g,
C=/\*\w+/g,D=/[-[\]{}()+?.,\\^$|#\s]/g;f.extend(u.prototype,k,{initialize:function(){},route:function(a,b,c){g.history||(g.history=new m);f.isRegExp(a)||(a=this._routeToRegExp(a));c||(c=this[b]);g.history.route(a,f.bind(function(d){d=this._extractParameters(a,d);c&&c.apply(this,d);this.trigger.apply(this,["route:"+b].concat(d));g.history.trigger("route",this,b,d)},this));return this},navigate:function(a,b){g.history.navigate(a,b)},_bindRoutes:function(){if(this.routes){var a=[],b;for(b in this.routes)a.unshift([b,
this.routes[b]]);b=0;for(var c=a.length;b<c;b++)this.route(a[b][0],a[b][1],this[a[b][1]])}},_routeToRegExp:function(a){a=a.replace(D,"\\$&").replace(B,"([^/]+)").replace(C,"(.*?)");return RegExp("^"+a+"$")},_extractParameters:function(a,b){return a.exec(b).slice(1)}});var m=g.History=function(){this.handlers=[];f.bindAll(this,"checkUrl")},s=/^[#\/]/,E=/msie [\w.]+/;m.started=!1;f.extend(m.prototype,k,{interval:50,getHash:function(a){return(a=(a?a.location:window.location).href.match(/#(.*)$/))?a[1]:
""},getFragment:function(a,b){if(null==a)if(this._hasPushState||b){var a=window.location.pathname,c=window.location.search;c&&(a+=c)}else a=this.getHash();a.indexOf(this.options.root)||(a=a.substr(this.options.root.length));return a.replace(s,"")},start:function(a){if(m.started)throw Error("Backbone.history has already been started");m.started=!0;this.options=f.extend({},{root:"/"},this.options,a);this._wantsHashChange=!1!==this.options.hashChange;this._wantsPushState=!!this.options.pushState;this._hasPushState=
!(!this.options.pushState||!window.history||!window.history.pushState);var a=this.getFragment(),b=document.documentMode;if(b=E.exec(navigator.userAgent.toLowerCase())&&(!b||7>=b))this.iframe=i('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo("body")[0].contentWindow,this.navigate(a);this._hasPushState?i(window).bind("popstate",this.checkUrl):this._wantsHashChange&&"onhashchange"in window&&!b?i(window).bind("hashchange",this.checkUrl):this._wantsHashChange&&(this._checkUrlInterval=setInterval(this.checkUrl,
this.interval));this.fragment=a;a=window.location;b=a.pathname==this.options.root;if(this._wantsHashChange&&this._wantsPushState&&!this._hasPushState&&!b)return this.fragment=this.getFragment(null,!0),window.location.replace(this.options.root+"#"+this.fragment),!0;this._wantsPushState&&this._hasPushState&&b&&a.hash&&(this.fragment=this.getHash().replace(s,""),window.history.replaceState({},document.title,a.protocol+"//"+a.host+this.options.root+this.fragment));if(!this.options.silent)return this.loadUrl()},
stop:function(){i(window).unbind("popstate",this.checkUrl).unbind("hashchange",this.checkUrl);clearInterval(this._checkUrlInterval);m.started=!1},route:function(a,b){this.handlers.unshift({route:a,callback:b})},checkUrl:function(){var a=this.getFragment();a==this.fragment&&this.iframe&&(a=this.getFragment(this.getHash(this.iframe)));if(a==this.fragment)return!1;this.iframe&&this.navigate(a);this.loadUrl()||this.loadUrl(this.getHash())},loadUrl:function(a){var b=this.fragment=this.getFragment(a);return f.any(this.handlers,
function(a){if(a.route.test(b))return a.callback(b),!0})},navigate:function(a,b){if(!m.started)return!1;if(!b||!0===b)b={trigger:b};var c=(a||"").replace(s,"");this.fragment!=c&&(this._hasPushState?(0!=c.indexOf(this.options.root)&&(c=this.options.root+c),this.fragment=c,window.history[b.replace?"replaceState":"pushState"]({},document.title,c)):this._wantsHashChange?(this.fragment=c,this._updateHash(window.location,c,b.replace),this.iframe&&c!=this.getFragment(this.getHash(this.iframe))&&(b.replace||
this.iframe.document.open().close(),this._updateHash(this.iframe.location,c,b.replace))):window.location.assign(this.options.root+a),b.trigger&&this.loadUrl(a))},_updateHash:function(a,b,c){c?a.replace(a.toString().replace(/(javascript:|#).*$/,"")+"#"+b):a.hash=b}});var v=g.View=function(a){this.cid=f.uniqueId("view");this._configure(a||{});this._ensureElement();this.initialize.apply(this,arguments);this.delegateEvents()},F=/^(\S+)\s*(.*)$/,w="model,collection,el,id,attributes,className,tagName".split(",");
f.extend(v.prototype,k,{tagName:"div",$:function(a){return this.$el.find(a)},initialize:function(){},render:function(){return this},remove:function(){this.$el.remove();return this},make:function(a,b,c){a=document.createElement(a);b&&i(a).attr(b);c&&i(a).html(c);return a},setElement:function(a,b){this.$el&&this.undelegateEvents();this.$el=a instanceof i?a:i(a);this.el=this.$el[0];!1!==b&&this.delegateEvents();return this},delegateEvents:function(a){if(a||(a=n(this,"events"))){this.undelegateEvents();
for(var b in a){var c=a[b];f.isFunction(c)||(c=this[a[b]]);if(!c)throw Error('Method "'+a[b]+'" does not exist');var d=b.match(F),e=d[1],d=d[2],c=f.bind(c,this),e=e+(".delegateEvents"+this.cid);""===d?this.$el.bind(e,c):this.$el.delegate(d,e,c)}}},undelegateEvents:function(){this.$el.unbind(".delegateEvents"+this.cid)},_configure:function(a){this.options&&(a=f.extend({},this.options,a));for(var b=0,c=w.length;b<c;b++){var d=w[b];a[d]&&(this[d]=a[d])}this.options=a},_ensureElement:function(){if(this.el)this.setElement(this.el,
!1);else{var a=n(this,"attributes")||{};this.id&&(a.id=this.id);this.className&&(a["class"]=this.className);this.setElement(this.make(this.tagName,a),!1)}}});o.extend=r.extend=u.extend=v.extend=function(a,b){var c=G(this,a,b);c.extend=this.extend;return c};var H={create:"POST",update:"PUT","delete":"DELETE",read:"GET"};g.sync=function(a,b,c){var d=H[a];c||(c={});var e={type:d,dataType:"json"};c.url||(e.url=n(b,"url")||t());if(!c.data&&b&&("create"==a||"update"==a))e.contentType="application/json",
e.data=JSON.stringify(b.toJSON());g.emulateJSON&&(e.contentType="application/x-www-form-urlencoded",e.data=e.data?{model:e.data}:{});if(g.emulateHTTP&&("PUT"===d||"DELETE"===d))g.emulateJSON&&(e.data._method=d),e.type="POST",e.beforeSend=function(a){a.setRequestHeader("X-HTTP-Method-Override",d)};"GET"!==e.type&&!g.emulateJSON&&(e.processData=!1);return i.ajax(f.extend(e,c))};g.wrapError=function(a,b,c){return function(d,e){e=d===b?e:d;a?a(b,e,c):b.trigger("error",b,e,c)}};var x=function(){},G=function(a,
b,c){var d;d=b&&b.hasOwnProperty("constructor")?b.constructor:function(){a.apply(this,arguments)};f.extend(d,a);x.prototype=a.prototype;d.prototype=new x;b&&f.extend(d.prototype,b);c&&f.extend(d,c);d.prototype.constructor=d;d.__super__=a.prototype;return d},n=function(a,b){return!a||!a[b]?null:f.isFunction(a[b])?a[b]():a[b]},t=function(){throw Error('A "url" property or function must be specified');}}).call(this);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,18 @@
/*
* jQuery BBQ: Back Button & Query Library - v1.2.1 - 2/17/2010
* http://benalman.com/projects/jquery-bbq-plugin/
*
* Copyright (c) 2010 "Cowboy" Ben Alman
* Dual licensed under the MIT and GPL licenses.
* http://benalman.com/about/license/
*/
(function($,p){var i,m=Array.prototype.slice,r=decodeURIComponent,a=$.param,c,l,v,b=$.bbq=$.bbq||{},q,u,j,e=$.event.special,d="hashchange",A="querystring",D="fragment",y="elemUrlAttr",g="location",k="href",t="src",x=/^.*\?|#.*$/g,w=/^.*\#/,h,C={};function E(F){return typeof F==="string"}function B(G){var F=m.call(arguments,1);return function(){return G.apply(this,F.concat(m.call(arguments)))}}function n(F){return F.replace(/^[^#]*#?(.*)$/,"$1")}function o(F){return F.replace(/(?:^[^?#]*\?([^#]*).*$)?.*/,"$1")}function f(H,M,F,I,G){var O,L,K,N,J;if(I!==i){K=F.match(H?/^([^#]*)\#?(.*)$/:/^([^#?]*)\??([^#]*)(#?.*)/);J=K[3]||"";if(G===2&&E(I)){L=I.replace(H?w:x,"")}else{N=l(K[2]);I=E(I)?l[H?D:A](I):I;L=G===2?I:G===1?$.extend({},I,N):$.extend({},N,I);L=a(L);if(H){L=L.replace(h,r)}}O=K[1]+(H?"#":L||!K[1]?"?":"")+L+J}else{O=M(F!==i?F:p[g][k])}return O}a[A]=B(f,0,o);a[D]=c=B(f,1,n);c.noEscape=function(G){G=G||"";var F=$.map(G.split(""),encodeURIComponent);h=new RegExp(F.join("|"),"g")};c.noEscape(",/");$.deparam=l=function(I,F){var H={},G={"true":!0,"false":!1,"null":null};$.each(I.replace(/\+/g," ").split("&"),function(L,Q){var K=Q.split("="),P=r(K[0]),J,O=H,M=0,R=P.split("]["),N=R.length-1;if(/\[/.test(R[0])&&/\]$/.test(R[N])){R[N]=R[N].replace(/\]$/,"");R=R.shift().split("[").concat(R);N=R.length-1}else{N=0}if(K.length===2){J=r(K[1]);if(F){J=J&&!isNaN(J)?+J:J==="undefined"?i:G[J]!==i?G[J]:J}if(N){for(;M<=N;M++){P=R[M]===""?O.length:R[M];O=O[P]=M<N?O[P]||(R[M+1]&&isNaN(R[M+1])?{}:[]):J}}else{if($.isArray(H[P])){H[P].push(J)}else{if(H[P]!==i){H[P]=[H[P],J]}else{H[P]=J}}}}else{if(P){H[P]=F?i:""}}});return H};function z(H,F,G){if(F===i||typeof F==="boolean"){G=F;F=a[H?D:A]()}else{F=E(F)?F.replace(H?w:x,""):F}return l(F,G)}l[A]=B(z,0);l[D]=v=B(z,1);$[y]||($[y]=function(F){return $.extend(C,F)})({a:k,base:k,iframe:t,img:t,input:t,form:"action",link:k,script:t});j=$[y];function s(I,G,H,F){if(!E(H)&&typeof H!=="object"){F=H;H=G;G=i}return this.each(function(){var L=$(this),J=G||j()[(this.nodeName||"").toLowerCase()]||"",K=J&&L.attr(J)||"";L.attr(J,a[I](K,H,F))})}$.fn[A]=B(s,A);$.fn[D]=B(s,D);b.pushState=q=function(I,F){if(E(I)&&/^#/.test(I)&&F===i){F=2}var H=I!==i,G=c(p[g][k],H?I:{},H?F:2);p[g][k]=G+(/#/.test(G)?"":"#")};b.getState=u=function(F,G){return F===i||typeof F==="boolean"?v(F):v(G)[F]};b.removeState=function(F){var G={};if(F!==i){G=u();$.each($.isArray(F)?F:arguments,function(I,H){delete G[H]})}q(G,2)};e[d]=$.extend(e[d],{add:function(F){var H;function G(J){var I=J[D]=c();J.getState=function(K,L){return K===i||typeof K==="boolean"?l(I,K):l(I,L)[K]};H.apply(this,arguments)}if($.isFunction(F)){H=F;return G}else{H=F.handler;F.handler=G}}})})(jQuery,this);
/*
* jQuery hashchange event - v1.2 - 2/11/2010
* http://benalman.com/projects/jquery-hashchange-plugin/
*
* Copyright (c) 2010 "Cowboy" Ben Alman
* Dual licensed under the MIT and GPL licenses.
* http://benalman.com/about/license/
*/
(function($,i,b){var j,k=$.event.special,c="location",d="hashchange",l="href",f=$.browser,g=document.documentMode,h=f.msie&&(g===b||g<8),e="on"+d in i&&!h;function a(m){m=m||i[c][l];return m.replace(/^[^#]*#?(.*)$/,"$1")}$[d+"Delay"]=100;k[d]=$.extend(k[d],{setup:function(){if(e){return false}$(j.start)},teardown:function(){if(e){return false}$(j.stop)}});j=(function(){var m={},r,n,o,q;function p(){o=q=function(s){return s};if(h){n=$('<iframe src="javascript:0"/>').hide().insertAfter("body")[0].contentWindow;q=function(){return a(n.document[c][l])};o=function(u,s){if(u!==s){var t=n.document;t.open().close();t[c].hash="#"+u}};o(a())}}m.start=function(){if(r){return}var t=a();o||p();(function s(){var v=a(),u=q(t);if(v!==t){o(t=v,u);$(i).trigger(d)}else{if(u!==t){i[c][l]=i[c][l].replace(/#.*/,"")+"#"+u}}r=setTimeout(s,$[d+"Delay"])})()};m.stop=function(){if(!n){r&&clearTimeout(r);r=0}};return m})()})(jQuery,this);

View File

@ -0,0 +1 @@
(function(b){b.fn.slideto=function(a){a=b.extend({slide_duration:"slow",highlight_duration:3E3,highlight:true,highlight_color:"#FFFF99"},a);return this.each(function(){obj=b(this);b("body").animate({scrollTop:obj.offset().top},a.slide_duration,function(){a.highlight&&b.ui.version&&obj.effect("highlight",{color:a.highlight_color},a.highlight_duration)})})}})(jQuery);

View File

@ -0,0 +1,8 @@
/*
jQuery Wiggle
Author: WonderGroup, Jordan Thomas
URL: http://labs.wondergroup.com/demos/mini-ui/index.html
License: MIT (http://en.wikipedia.org/wiki/MIT_License)
*/
jQuery.fn.wiggle=function(o){var d={speed:50,wiggles:3,travel:5,callback:null};var o=jQuery.extend(d,o);return this.each(function(){var cache=this;var wrap=jQuery(this).wrap('<div class="wiggle-wrap"></div>').css("position","relative");var calls=0;for(i=1;i<=o.wiggles;i++){jQuery(this).animate({left:"-="+o.travel},o.speed).animate({left:"+="+o.travel*2},o.speed*2).animate({left:"-="+o.travel},o.speed,function(){calls++;if(jQuery(cache).parent().hasClass('wiggle-wrap')){jQuery(cache).parent().replaceWith(cache);}
if(calls==o.wiggles&&jQuery.isFunction(o.callback)){o.callback();}});}});};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,193 @@
// The purpose of the `Content` object is to abstract away the data conversions
// to and from raw content entities as strings. For example, you want to be able
// to pass in a Javascript object and have it be automatically converted into a
// JSON string if the `content-type` is set to a JSON-based media type.
// Conversely, you want to be able to transparently get back a Javascript object
// in the response if the `content-type` is a JSON-based media-type.
// One limitation of the current implementation is that it [assumes the `charset` is UTF-8](https://github.com/spire-io/shred/issues/5).
// The `Content` constructor takes an options object, which *must* have either a
// `body` or `data` property and *may* have a `type` property indicating the
// media type. If there is no `type` attribute, a default will be inferred.
var Content = function(options) {
this.body = options.body;
this.data = options.data;
this.type = options.type;
};
Content.prototype = {
// Treat `toString()` as asking for the `content.body`. That is, the raw content entity.
//
// toString: function() { return this.body; }
//
// Commented out, but I've forgotten why. :/
};
// `Content` objects have the following attributes:
Object.defineProperties(Content.prototype,{
// - **type**. Typically accessed as `content.type`, reflects the `content-type`
// header associated with the request or response. If not passed as an options
// to the constructor or set explicitly, it will infer the type the `data`
// attribute, if possible, and, failing that, will default to `text/plain`.
type: {
get: function() {
if (this._type) {
return this._type;
} else {
if (this._data) {
switch(typeof this._data) {
case "string": return "text/plain";
case "object": return "application/json";
}
}
}
return "text/plain";
},
set: function(value) {
this._type = value;
return this;
},
enumerable: true
},
// - **data**. Typically accessed as `content.data`, reflects the content entity
// converted into Javascript data. This can be a string, if the `type` is, say,
// `text/plain`, but can also be a Javascript object. The conversion applied is
// based on the `processor` attribute. The `data` attribute can also be set
// directly, in which case the conversion will be done the other way, to infer
// the `body` attribute.
data: {
get: function() {
if (this._body) {
return this.processor.parser(this._body);
} else {
return this._data;
}
},
set: function(data) {
if (this._body&&data) Errors.setDataWithBody(this);
this._data = data;
return this;
},
enumerable: true
},
// - **body**. Typically accessed as `content.body`, reflects the content entity
// as a UTF-8 string. It is the mirror of the `data` attribute. If you set the
// `data` attribute, the `body` attribute will be inferred and vice-versa. If
// you attempt to set both, an exception is raised.
body: {
get: function() {
if (this._data) {
return this.processor.stringify(this._data);
} else {
return this._body.toString();
}
},
set: function(body) {
if (this._data&&body) Errors.setBodyWithData(this);
this._body = body;
return this;
},
enumerable: true
},
// - **processor**. The functions that will be used to convert to/from `data` and
// `body` attributes. You can add processors. The two that are built-in are for
// `text/plain`, which is basically an identity transformation and
// `application/json` and other JSON-based media types (including custom media
// types with `+json`). You can add your own processors. See below.
processor: {
get: function() {
var processor = Content.processors[this.type];
if (processor) {
return processor;
} else {
// Return the first processor that matches any part of the
// content type. ex: application/vnd.foobar.baz+json will match json.
var main = this.type.split(";")[0];
var parts = main.split(/\+|\//);
for (var i=0, l=parts.length; i < l; i++) {
processor = Content.processors[parts[i]]
}
return processor || {parser:identity,stringify:toString};
}
},
enumerable: true
},
// - **length**. Typically accessed as `content.length`, returns the length in
// bytes of the raw content entity.
length: {
get: function() {
if (typeof Buffer !== 'undefined') {
return Buffer.byteLength(this.body);
}
return this.body.length;
}
}
});
Content.processors = {};
// The `registerProcessor` function allows you to add your own processors to
// convert content entities. Each processor consists of a Javascript object with
// two properties:
// - **parser**. The function used to parse a raw content entity and convert it
// into a Javascript data type.
// - **stringify**. The function used to convert a Javascript data type into a
// raw content entity.
Content.registerProcessor = function(types,processor) {
// You can pass an array of types that will trigger this processor, or just one.
// We determine the array via duck-typing here.
if (types.forEach) {
types.forEach(function(type) {
Content.processors[type] = processor;
});
} else {
// If you didn't pass an array, we just use what you pass in.
Content.processors[types] = processor;
}
};
// Register the identity processor, which is used for text-based media types.
var identity = function(x) { return x; }
, toString = function(x) { return x.toString(); }
Content.registerProcessor(
["text/html","text/plain","text"],
{ parser: identity, stringify: toString });
// Register the JSON processor, which is used for JSON-based media types.
Content.registerProcessor(
["application/json; charset=utf-8","application/json","json"],
{
parser: function(string) {
return JSON.parse(string);
},
stringify: function(data) {
return JSON.stringify(data); }});
var qs = require('querystring');
// Register the post processor, which is used for JSON-based media types.
Content.registerProcessor(
["application/x-www-form-urlencoded"],
{ parser : qs.parse, stringify : qs.stringify });
// Error functions are defined separately here in an attempt to make the code
// easier to read.
var Errors = {
setDataWithBody: function(object) {
throw new Error("Attempt to set data attribute of a content object " +
"when the body attributes was already set.");
},
setBodyWithData: function(object) {
throw new Error("Attempt to set body attribute of a content object " +
"when the data attributes was already set.");
}
}
module.exports = Content;

View File

@ -0,0 +1,218 @@
var appName;
var popupMask;
var popupDialog;
var clientId;
var realm;
function handleLogin() {
var scopes = [];
if(window.swaggerUi.api.authSchemes
&& window.swaggerUi.api.authSchemes.oauth2
&& window.swaggerUi.api.authSchemes.oauth2.scopes) {
scopes = window.swaggerUi.api.authSchemes.oauth2.scopes;
}
if(window.swaggerUi.api
&& window.swaggerUi.api.info) {
appName = window.swaggerUi.api.info.title;
}
if(popupDialog.length > 0)
popupDialog = popupDialog.last();
else {
popupDialog = $(
[
'<div class="api-popup-dialog">',
'<div class="api-popup-title">Select OAuth2.0 Scopes</div>',
'<div class="api-popup-content">',
'<p>Scopes are used to grant an application different levels of access to data on behalf of the end user. Each API may declare one or more scopes.',
'<a href="#">Learn how to use</a>',
'</p>',
'<p><strong>' + appName + '</strong> API requires the following scopes. Select which ones you want to grant to Swagger UI.</p>',
'<ul class="api-popup-scopes">',
'</ul>',
'<p class="error-msg"></p>',
'<div class="api-popup-actions"><button class="api-popup-authbtn api-button green" type="button">Authorize</button><button class="api-popup-cancel api-button gray" type="button">Cancel</button></div>',
'</div>',
'</div>'].join(''));
$(document.body).append(popupDialog);
popup = popupDialog.find('ul.api-popup-scopes').empty();
for (i = 0; i < scopes.length; i ++) {
scope = scopes[i];
str = '<li><input type="checkbox" id="scope_' + i + '" scope="' + scope.scope + '"/>' + '<label for="scope_' + i + '">' + scope.scope;
if (scope.description) {
str += '<br/><span class="api-scope-desc">' + scope.description + '</span>';
}
str += '</label></li>';
popup.append(str);
}
var $win = $(window),
dw = $win.width(),
dh = $win.height(),
st = $win.scrollTop(),
dlgWd = popupDialog.outerWidth(),
dlgHt = popupDialog.outerHeight(),
top = (dh -dlgHt)/2 + st,
left = (dw - dlgWd)/2;
popupDialog.css({
top: (top < 0? 0 : top) + 'px',
left: (left < 0? 0 : left) + 'px'
});
popupDialog.find('button.api-popup-cancel').click(function() {
popupMask.hide();
popupDialog.hide();
});
popupDialog.find('button.api-popup-authbtn').click(function() {
popupMask.hide();
popupDialog.hide();
var authSchemes = window.swaggerUi.api.authSchemes;
var location = window.location;
var locationUrl = location.protocol + '//' + location.host + location.pathname;
var redirectUrl = locationUrl.replace("index.html","").concat("/o2c.html").replace("//o2c.html","/o2c.html");
var url = null;
var p = window.swaggerUi.api.authSchemes;
for (var key in p) {
if (p.hasOwnProperty(key)) {
var o = p[key].grantTypes;
for(var t in o) {
if(o.hasOwnProperty(t) && t === 'implicit') {
var dets = o[t];
url = dets.loginEndpoint.url + "?response_type=token";
window.swaggerUi.tokenName = dets.tokenName;
}
}
}
}
var scopes = [];
var scopeForUrl='';
var o = $('.api-popup-scopes').find('input:checked');
for(var k =0; k < o.length; k++) {
scopes.push($(o[k]).attr("scope"));
if(k > 0){
scopeForUrl+=' ';
}
scopeForUrl+=$(o[k]).attr("scope");
}
window.enabledScopes=scopes;
url += '&redirect_uri=' + encodeURIComponent(redirectUrl);
url += '&realm=' + encodeURIComponent(realm);
url += '&client_id=' + encodeURIComponent(clientId);
url += '&scope=' + encodeURIComponent(scopeForUrl);
window.open(url);
});
}
popupMask.show();
popupDialog.show();
return;
}
function handleLogout() {
for(key in window.authorizations.authz){
window.authorizations.remove(key)
}
window.enabledScopes = null;
$('.api-ic.ic-on').addClass('ic-off');
$('.api-ic.ic-on').removeClass('ic-on');
// set the info box
$('.api-ic.ic-warning').addClass('ic-error');
$('.api-ic.ic-warning').removeClass('ic-warning');
}
function initOAuth(opts) {
var o = (opts||{});
var errors = [];
appName = (o.appName||errors.push("missing appName"));
popupMask = (o.popupMask||$('#api-common-mask'));
popupDialog = (o.popupDialog||$('.api-popup-dialog'));
clientId = (o.clientId||errors.push("missing client id"));
realm = (o.realm||errors.push("missing realm"));
if(errors.length > 0){
log("auth unable initialize oauth: " + errors);
return;
}
$('pre code').each(function(i, e) {hljs.highlightBlock(e)});
$('.api-ic').click(function(s) {
if($(s.target).hasClass('ic-off'))
handleLogin();
else {
handleLogout();
}
false;
});
}
function onOAuthComplete(token) {
if(token) {
if(token.error) {
var checkbox = $('input[type=checkbox],.secured')
checkbox.each(function(pos){
checkbox[pos].checked = false;
});
alert(token.error);
}
else {
var b = token[window.swaggerUi.tokenName];
if(b){
// if all roles are satisfied
var o = null;
$.each($('.auth #api_information_panel'), function(k, v) {
var children = v;
if(children && children.childNodes) {
var requiredScopes = [];
$.each((children.childNodes), function (k1, v1){
var inner = v1.innerHTML;
if(inner)
requiredScopes.push(inner);
});
var diff = [];
for(var i=0; i < requiredScopes.length; i++) {
var s = requiredScopes[i];
if(window.enabledScopes && window.enabledScopes.indexOf(s) == -1) {
diff.push(s);
}
}
if(diff.length > 0){
o = v.parentNode;
$(o.parentNode).find('.api-ic.ic-on').addClass('ic-off');
$(o.parentNode).find('.api-ic.ic-on').removeClass('ic-on');
// sorry, not all scopes are satisfied
$(o).find('.api-ic').addClass('ic-warning');
$(o).find('.api-ic').removeClass('ic-error');
}
else {
o = v.parentNode;
$(o.parentNode).find('.api-ic.ic-off').addClass('ic-on');
$(o.parentNode).find('.api-ic.ic-off').removeClass('ic-off');
// all scopes are satisfied
$(o).find('.api-ic').addClass('ic-info');
$(o).find('.api-ic').removeClass('ic-warning');
$(o).find('.api-ic').removeClass('ic-error');
}
}
});
window.authorizations.add("key", new ApiKeyAuthorization("Authorization", "Bearer " + b, "header"));
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,32 @@
// Underscore.js 1.3.3
// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
// Underscore is freely distributable under the MIT license.
// Portions of Underscore are inspired or borrowed from Prototype,
// Oliver Steele's Functional, and John Resig's Micro-Templating.
// For all details and documentation:
// http://documentcloud.github.com/underscore
(function(){function r(a,c,d){if(a===c)return 0!==a||1/a==1/c;if(null==a||null==c)return a===c;a._chain&&(a=a._wrapped);c._chain&&(c=c._wrapped);if(a.isEqual&&b.isFunction(a.isEqual))return a.isEqual(c);if(c.isEqual&&b.isFunction(c.isEqual))return c.isEqual(a);var e=l.call(a);if(e!=l.call(c))return!1;switch(e){case "[object String]":return a==""+c;case "[object Number]":return a!=+a?c!=+c:0==a?1/a==1/c:a==+c;case "[object Date]":case "[object Boolean]":return+a==+c;case "[object RegExp]":return a.source==
c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if("object"!=typeof a||"object"!=typeof c)return!1;for(var f=d.length;f--;)if(d[f]==a)return!0;d.push(a);var f=0,g=!0;if("[object Array]"==e){if(f=a.length,g=f==c.length)for(;f--&&(g=f in a==f in c&&r(a[f],c[f],d)););}else{if("constructor"in a!="constructor"in c||a.constructor!=c.constructor)return!1;for(var h in a)if(b.has(a,h)&&(f++,!(g=b.has(c,h)&&r(a[h],c[h],d))))break;if(g){for(h in c)if(b.has(c,h)&&!f--)break;
g=!f}}d.pop();return g}var s=this,I=s._,o={},k=Array.prototype,p=Object.prototype,i=k.slice,J=k.unshift,l=p.toString,K=p.hasOwnProperty,y=k.forEach,z=k.map,A=k.reduce,B=k.reduceRight,C=k.filter,D=k.every,E=k.some,q=k.indexOf,F=k.lastIndexOf,p=Array.isArray,L=Object.keys,t=Function.prototype.bind,b=function(a){return new m(a)};"undefined"!==typeof exports?("undefined"!==typeof module&&module.exports&&(exports=module.exports=b),exports._=b):s._=b;b.VERSION="1.3.3";var j=b.each=b.forEach=function(a,
c,d){if(a!=null)if(y&&a.forEach===y)a.forEach(c,d);else if(a.length===+a.length)for(var e=0,f=a.length;e<f;e++){if(e in a&&c.call(d,a[e],e,a)===o)break}else for(e in a)if(b.has(a,e)&&c.call(d,a[e],e,a)===o)break};b.map=b.collect=function(a,c,b){var e=[];if(a==null)return e;if(z&&a.map===z)return a.map(c,b);j(a,function(a,g,h){e[e.length]=c.call(b,a,g,h)});if(a.length===+a.length)e.length=a.length;return e};b.reduce=b.foldl=b.inject=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(A&&
a.reduce===A){e&&(c=b.bind(c,e));return f?a.reduce(c,d):a.reduce(c)}j(a,function(a,b,i){if(f)d=c.call(e,d,a,b,i);else{d=a;f=true}});if(!f)throw new TypeError("Reduce of empty array with no initial value");return d};b.reduceRight=b.foldr=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(B&&a.reduceRight===B){e&&(c=b.bind(c,e));return f?a.reduceRight(c,d):a.reduceRight(c)}var g=b.toArray(a).reverse();e&&!f&&(c=b.bind(c,e));return f?b.reduce(g,c,d,e):b.reduce(g,c)};b.find=b.detect=function(a,
c,b){var e;G(a,function(a,g,h){if(c.call(b,a,g,h)){e=a;return true}});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(C&&a.filter===C)return a.filter(c,b);j(a,function(a,g,h){c.call(b,a,g,h)&&(e[e.length]=a)});return e};b.reject=function(a,c,b){var e=[];if(a==null)return e;j(a,function(a,g,h){c.call(b,a,g,h)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=true;if(a==null)return e;if(D&&a.every===D)return a.every(c,b);j(a,function(a,g,h){if(!(e=e&&c.call(b,
a,g,h)))return o});return!!e};var G=b.some=b.any=function(a,c,d){c||(c=b.identity);var e=false;if(a==null)return e;if(E&&a.some===E)return a.some(c,d);j(a,function(a,b,h){if(e||(e=c.call(d,a,b,h)))return o});return!!e};b.include=b.contains=function(a,c){var b=false;if(a==null)return b;if(q&&a.indexOf===q)return a.indexOf(c)!=-1;return b=G(a,function(a){return a===c})};b.invoke=function(a,c){var d=i.call(arguments,2);return b.map(a,function(a){return(b.isFunction(c)?c||a:a[c]).apply(a,d)})};b.pluck=
function(a,c){return b.map(a,function(a){return a[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a)&&a[0]===+a[0])return Math.max.apply(Math,a);if(!c&&b.isEmpty(a))return-Infinity;var e={computed:-Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a)&&a[0]===+a[0])return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b<e.computed&&
(e={value:a,computed:b})});return e.value};b.shuffle=function(a){var b=[],d;j(a,function(a,f){d=Math.floor(Math.random()*(f+1));b[f]=b[d];b[d]=a});return b};b.sortBy=function(a,c,d){var e=b.isFunction(c)?c:function(a){return a[c]};return b.pluck(b.map(a,function(a,b,c){return{value:a,criteria:e.call(d,a,b,c)}}).sort(function(a,b){var c=a.criteria,d=b.criteria;return c===void 0?1:d===void 0?-1:c<d?-1:c>d?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]};
j(a,function(a,b){var c=e(a,b);(d[c]||(d[c]=[])).push(a)});return d};b.sortedIndex=function(a,c,d){d||(d=b.identity);for(var e=0,f=a.length;e<f;){var g=e+f>>1;d(a[g])<d(c)?e=g+1:f=g}return e};b.toArray=function(a){return!a?[]:b.isArray(a)||b.isArguments(a)?i.call(a):a.toArray&&b.isFunction(a.toArray)?a.toArray():b.values(a)};b.size=function(a){return b.isArray(a)?a.length:b.keys(a).length};b.first=b.head=b.take=function(a,b,d){return b!=null&&!d?i.call(a,0,b):a[0]};b.initial=function(a,b,d){return i.call(a,
0,a.length-(b==null||d?1:b))};b.last=function(a,b,d){return b!=null&&!d?i.call(a,Math.max(a.length-b,0)):a[a.length-1]};b.rest=b.tail=function(a,b,d){return i.call(a,b==null||d?1:b)};b.compact=function(a){return b.filter(a,function(a){return!!a})};b.flatten=function(a,c){return b.reduce(a,function(a,e){if(b.isArray(e))return a.concat(c?e:b.flatten(e));a[a.length]=e;return a},[])};b.without=function(a){return b.difference(a,i.call(arguments,1))};b.uniq=b.unique=function(a,c,d){var d=d?b.map(a,d):a,
e=[];a.length<3&&(c=true);b.reduce(d,function(d,g,h){if(c?b.last(d)!==g||!d.length:!b.include(d,g)){d.push(g);e.push(a[h])}return d},[]);return e};b.union=function(){return b.uniq(b.flatten(arguments,true))};b.intersection=b.intersect=function(a){var c=i.call(arguments,1);return b.filter(b.uniq(a),function(a){return b.every(c,function(c){return b.indexOf(c,a)>=0})})};b.difference=function(a){var c=b.flatten(i.call(arguments,1),true);return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=
i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e<c;e++)d[e]=b.pluck(a,""+e);return d};b.indexOf=function(a,c,d){if(a==null)return-1;var e;if(d){d=b.sortedIndex(a,c);return a[d]===c?d:-1}if(q&&a.indexOf===q)return a.indexOf(c);d=0;for(e=a.length;d<e;d++)if(d in a&&a[d]===c)return d;return-1};b.lastIndexOf=function(a,b){if(a==null)return-1;if(F&&a.lastIndexOf===F)return a.lastIndexOf(b);for(var d=a.length;d--;)if(d in a&&a[d]===b)return d;return-1};b.range=function(a,b,d){if(arguments.length<=
1){b=a||0;a=0}for(var d=arguments[2]||1,e=Math.max(Math.ceil((b-a)/d),0),f=0,g=Array(e);f<e;){g[f++]=a;a=a+d}return g};var H=function(){};b.bind=function(a,c){var d,e;if(a.bind===t&&t)return t.apply(a,i.call(arguments,1));if(!b.isFunction(a))throw new TypeError;e=i.call(arguments,2);return d=function(){if(!(this instanceof d))return a.apply(c,e.concat(i.call(arguments)));H.prototype=a.prototype;var b=new H,g=a.apply(b,e.concat(i.call(arguments)));return Object(g)===g?g:b}};b.bindAll=function(a){var c=
i.call(arguments,1);c.length==0&&(c=b.functions(a));j(c,function(c){a[c]=b.bind(a[c],a)});return a};b.memoize=function(a,c){var d={};c||(c=b.identity);return function(){var e=c.apply(this,arguments);return b.has(d,e)?d[e]:d[e]=a.apply(this,arguments)}};b.delay=function(a,b){var d=i.call(arguments,2);return setTimeout(function(){return a.apply(null,d)},b)};b.defer=function(a){return b.delay.apply(b,[a,1].concat(i.call(arguments,1)))};b.throttle=function(a,c){var d,e,f,g,h,i,j=b.debounce(function(){h=
g=false},c);return function(){d=this;e=arguments;f||(f=setTimeout(function(){f=null;h&&a.apply(d,e);j()},c));g?h=true:i=a.apply(d,e);j();g=true;return i}};b.debounce=function(a,b,d){var e;return function(){var f=this,g=arguments;d&&!e&&a.apply(f,g);clearTimeout(e);e=setTimeout(function(){e=null;d||a.apply(f,g)},b)}};b.once=function(a){var b=false,d;return function(){if(b)return d;b=true;return d=a.apply(this,arguments)}};b.wrap=function(a,b){return function(){var d=[a].concat(i.call(arguments,0));
return b.apply(this,d)}};b.compose=function(){var a=arguments;return function(){for(var b=arguments,d=a.length-1;d>=0;d--)b=[a[d].apply(this,b)];return b[0]}};b.after=function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=L||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var c=[],d;for(d in a)b.has(a,d)&&(c[c.length]=d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&
c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]=b[d]});return a};b.pick=function(a){var c={};j(b.flatten(i.call(arguments,1)),function(b){b in a&&(c[b]=a[b])});return c};b.defaults=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return r(a,b,[])};b.isEmpty=
function(a){if(a==null)return true;if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(b.has(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=p||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a===Object(a)};b.isArguments=function(a){return l.call(a)=="[object Arguments]"};b.isArguments(arguments)||(b.isArguments=function(a){return!(!a||!b.has(a,"callee"))});b.isFunction=function(a){return l.call(a)=="[object Function]"};
b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isFinite=function(a){return b.isNumber(a)&&isFinite(a)};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate=function(a){return l.call(a)=="[object Date]"};b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.has=function(a,
b){return K.call(a,b)};b.noConflict=function(){s._=I;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e<a;e++)b.call(d,e)};b.escape=function(a){return(""+a).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#x27;").replace(/\//g,"&#x2F;")};b.result=function(a,c){if(a==null)return null;var d=a[c];return b.isFunction(d)?d.call(a):d};b.mixin=function(a){j(b.functions(a),function(c){M(c,b[c]=a[c])})};var N=0;b.uniqueId=
function(a){var b=N++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var u=/.^/,n={"\\":"\\","'":"'",r:"\r",n:"\n",t:"\t",u2028:"\u2028",u2029:"\u2029"},v;for(v in n)n[n[v]]=v;var O=/\\|'|\r|\n|\t|\u2028|\u2029/g,P=/\\(\\|'|r|n|t|u2028|u2029)/g,w=function(a){return a.replace(P,function(a,b){return n[b]})};b.template=function(a,c,d){d=b.defaults(d||{},b.templateSettings);a="__p+='"+a.replace(O,function(a){return"\\"+n[a]}).replace(d.escape||
u,function(a,b){return"'+\n_.escape("+w(b)+")+\n'"}).replace(d.interpolate||u,function(a,b){return"'+\n("+w(b)+")+\n'"}).replace(d.evaluate||u,function(a,b){return"';\n"+w(b)+"\n;__p+='"})+"';\n";d.variable||(a="with(obj||{}){\n"+a+"}\n");var a="var __p='';var print=function(){__p+=Array.prototype.join.call(arguments, '')};\n"+a+"return __p;\n",e=new Function(d.variable||"obj","_",a);if(c)return e(c,b);c=function(a){return e.call(this,a,b)};c.source="function("+(d.variable||"obj")+"){\n"+a+"}";return c};
b.chain=function(a){return b(a).chain()};var m=function(a){this._wrapped=a};b.prototype=m.prototype;var x=function(a,c){return c?b(a).chain():a},M=function(a,c){m.prototype[a]=function(){var a=i.call(arguments);J.call(a,this._wrapped);return x(c.apply(b,a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];m.prototype[a]=function(){var d=this._wrapped;b.apply(d,arguments);var e=d.length;(a=="shift"||a=="splice")&&e===0&&delete d[0];return x(d,
this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];m.prototype[a]=function(){return x(b.apply(this._wrapped,arguments),this._chain)}});m.prototype.chain=function(){this._chain=true;return this};m.prototype.value=function(){return this._wrapped}}).call(this);

View File

@ -0,0 +1,15 @@
<script>
var qp = null;
if(window.location.hash) {
qp = location.hash.substring(1);
}
else {
qp = location.search.substring(1);
}
qp = qp ? JSON.parse('{"' + qp.replace(/&/g, '","').replace(/=/g,'":"') + '"}',
function(key, value) {
return key===""?value:decodeURIComponent(value) }
):{}
window.opener.onOAuthComplete(qp);
window.close();
</script>

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -11,7 +11,7 @@ namespace Luracast\Restler;
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
* @version 3.0.0rc6
*/
interface iAuthenticate extends iFilter
{

View File

@ -9,7 +9,7 @@ namespace Luracast\Restler;
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
* @version 3.0.0rc6
*/
interface iCache
{

View File

@ -13,7 +13,7 @@ use Exception;
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
* @version 3.0.0rc6
*/
interface iCompose {
/**

View File

@ -12,7 +12,7 @@ namespace Luracast\Restler;
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
* @version 3.0.0rc6
*/
interface iFilter
{

View File

@ -12,7 +12,7 @@ namespace Luracast\Restler;
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
* @version 3.0.0rc6
*/
interface iIdentifyUser
{

View File

@ -1,8 +1,14 @@
<?php
namespace Luracast\Restler;
interface iProvideMultiVersionApi {
/**
* Interface iProvideMultiVersionApi
* @package Luracast\Restler
*
* @version 3.0.0rc6
*/
interface iProvideMultiVersionApi
{
/**
* Maximum api version supported by the api class
* @return int

View File

@ -11,7 +11,7 @@ namespace Luracast\Restler;
* @copyright 2010 Luracast
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link http://luracast.com/products/restler/
* @version 3.0.0rc5
* @version 3.0.0rc6
*/
interface iUseAuthentication
{

View File

@ -64,7 +64,7 @@ if ($success && isset($api)) {
} else {
if (isset($response['error']['message'])) {
$icon = '<icon class="denied"></icon>';
$title = end(explode(':',$response['error']['message']));
$title = end(explode(':',$response['error']['message'],2));
} else {
$icon = '<icon class="warning"></icon>';
$title = 'No Matching Resource';