From b60389cec44e0e9a00251b4bd151fe3a5aaede88 Mon Sep 17 00:00:00 2001 From: nka11 Date: Thu, 21 Apr 2016 08:13:19 +0200 Subject: [PATCH] Update Restler to 3.0.0RC6 --- .../framework/Luracast/Restler/ApcCache.php | 2 +- .../framework/Luracast/Restler/AutoLoader.php | 4 +- .../Luracast/Restler/CommentParser.php | 79 +- .../framework/Luracast/Restler/Compose.php | 1 + .../Luracast/Restler/Data/ApiMethodInfo.php | 2 +- .../framework/Luracast/Restler/Data/Arr.php | 1 + .../Luracast/Restler/Data/Invalid.php | 2 +- .../Luracast/Restler/Data/Object.php | 2 +- .../framework/Luracast/Restler/Data/Text.php | 96 + .../Luracast/Restler/Data/ValidationInfo.php | 2 +- .../Luracast/Restler/Data/Validator.php | 52 +- .../Luracast/Restler/Data/ValueObject.php | 2 +- .../Luracast/Restler/Data/iValidate.php | 2 +- .../Luracast/Restler/Data/iValueObject.php | 2 +- .../framework/Luracast/Restler/Defaults.php | 8 +- .../Luracast/Restler/EventDispatcher.php | 2 +- .../framework/Luracast/Restler/Explorer.php | 582 ++++ .../Luracast/Restler/ExplorerInfo.php | 17 + .../Luracast/Restler/Filter/RateLimit.php | 4 +- .../framework/Luracast/Restler/Flash.php | 38 +- .../Luracast/Restler/Format/AmfFormat.php | 7 +- .../Luracast/Restler/Format/CsvFormat.php | 2 +- .../Restler/Format/DependentFormat.php | 56 + .../Restler/Format/DependentMultiFormat.php | 39 + .../Luracast/Restler/Format/Format.php | 2 +- .../Luracast/Restler/Format/HtmlFormat.php | 83 +- .../Luracast/Restler/Format/JsFormat.php | 2 +- .../Luracast/Restler/Format/JsonFormat.php | 162 +- .../Luracast/Restler/Format/MultiFormat.php | 2 +- .../Luracast/Restler/Format/PlistFormat.php | 19 +- .../Luracast/Restler/Format/TsvFormat.php | 2 +- .../Luracast/Restler/Format/UploadFormat.php | 2 +- .../Restler/Format/UrlEncodedFormat.php | 2 +- .../Luracast/Restler/Format/XmlFormat.php | 79 +- .../Luracast/Restler/Format/YamlFormat.php | 11 +- .../Luracast/Restler/Format/iDecodeStream.php | 2 +- .../Luracast/Restler/Format/iFormat.php | 2 +- .../Luracast/Restler/HumanReadableCache.php | 2 +- .../Restler/InvalidAuthCredentials.php | 22 + .../Luracast/Restler/MemcacheCache.php | 140 + .../Luracast/Restler/PassThrough.php | 91 + .../framework/Luracast/Restler/Redirect.php | 21 +- .../framework/Luracast/Restler/Resources.php | 26 +- .../Luracast/Restler/RestException.php | 1 + .../framework/Luracast/Restler/Restler.php | 179 +- .../framework/Luracast/Restler/Routes.php | 311 +- .../framework/Luracast/Restler/Scope.php | 89 +- .../framework/Luracast/Restler/UI/Emmet.php | 6 + .../Luracast/Restler/UI/FormStyles.php | 40 +- .../framework/Luracast/Restler/UI/Forms.php | 32 +- .../framework/Luracast/Restler/UI/Nav.php | 300 +- .../framework/Luracast/Restler/UI/Tags.php | 10 +- .../framework/Luracast/Restler/User.php | 2 +- .../framework/Luracast/Restler/Util.php | 38 +- .../framework/Luracast/Restler/composer.json | 36 +- .../Luracast/Restler/explorer/css/reset.css | 125 + .../Luracast/Restler/explorer/css/screen.css | 1243 ++++++++ .../explorer/images/explorer_icons.png | Bin 0 -> 5763 bytes .../Restler/explorer/images/logo_small.png | Bin 0 -> 1620 bytes .../Restler/explorer/images/throbber.gif | Bin 0 -> 9257 bytes .../Luracast/Restler/explorer/index.html | 82 + .../Restler/explorer/lib/backbone-min.js | 38 + .../Restler/explorer/lib/handlebars-1.0.0.js | 2278 ++++++++++++++ .../explorer/lib/highlight.7.3.pack.js | 1 + .../Restler/explorer/lib/jquery-1.8.0.min.js | 2 + .../Restler/explorer/lib/jquery.ba-bbq.min.js | 18 + .../explorer/lib/jquery.slideto.min.js | 1 + .../Restler/explorer/lib/jquery.wiggle.min.js | 8 + .../Restler/explorer/lib/shred.bundle.js | 2765 +++++++++++++++++ .../Restler/explorer/lib/shred/content.js | 193 ++ .../Restler/explorer/lib/swagger-oauth.js | 218 ++ .../Luracast/Restler/explorer/lib/swagger.js | 1538 +++++++++ .../Restler/explorer/lib/underscore-min.js | 32 + .../Luracast/Restler/explorer/o2c.html | 15 + .../Luracast/Restler/explorer/swagger-ui.js | 2276 ++++++++++++++ .../Restler/explorer/swagger-ui.min.js | 1 + .../Luracast/Restler/iAuthenticate.php | 2 +- .../framework/Luracast/Restler/iCache.php | 2 +- .../framework/Luracast/Restler/iCompose.php | 2 +- .../framework/Luracast/Restler/iFilter.php | 2 +- .../Luracast/Restler/iIdentifyUser.php | 2 +- .../Restler/iProvideMultiVersionApi.php | 10 +- .../Luracast/Restler/iUseAuthentication.php | 2 +- .../Luracast/Restler/views/debug.php | 2 +- 84 files changed, 12995 insertions(+), 583 deletions(-) create mode 100644 htdocs/includes/restler/framework/Luracast/Restler/Data/Text.php create mode 100644 htdocs/includes/restler/framework/Luracast/Restler/Explorer.php create mode 100644 htdocs/includes/restler/framework/Luracast/Restler/ExplorerInfo.php create mode 100644 htdocs/includes/restler/framework/Luracast/Restler/Format/DependentFormat.php create mode 100644 htdocs/includes/restler/framework/Luracast/Restler/Format/DependentMultiFormat.php create mode 100644 htdocs/includes/restler/framework/Luracast/Restler/InvalidAuthCredentials.php create mode 100644 htdocs/includes/restler/framework/Luracast/Restler/MemcacheCache.php create mode 100644 htdocs/includes/restler/framework/Luracast/Restler/PassThrough.php create mode 100644 htdocs/includes/restler/framework/Luracast/Restler/explorer/css/reset.css create mode 100644 htdocs/includes/restler/framework/Luracast/Restler/explorer/css/screen.css create mode 100644 htdocs/includes/restler/framework/Luracast/Restler/explorer/images/explorer_icons.png create mode 100644 htdocs/includes/restler/framework/Luracast/Restler/explorer/images/logo_small.png create mode 100644 htdocs/includes/restler/framework/Luracast/Restler/explorer/images/throbber.gif create mode 100644 htdocs/includes/restler/framework/Luracast/Restler/explorer/index.html create mode 100644 htdocs/includes/restler/framework/Luracast/Restler/explorer/lib/backbone-min.js create mode 100644 htdocs/includes/restler/framework/Luracast/Restler/explorer/lib/handlebars-1.0.0.js create mode 100644 htdocs/includes/restler/framework/Luracast/Restler/explorer/lib/highlight.7.3.pack.js create mode 100644 htdocs/includes/restler/framework/Luracast/Restler/explorer/lib/jquery-1.8.0.min.js create mode 100644 htdocs/includes/restler/framework/Luracast/Restler/explorer/lib/jquery.ba-bbq.min.js create mode 100644 htdocs/includes/restler/framework/Luracast/Restler/explorer/lib/jquery.slideto.min.js create mode 100644 htdocs/includes/restler/framework/Luracast/Restler/explorer/lib/jquery.wiggle.min.js create mode 100644 htdocs/includes/restler/framework/Luracast/Restler/explorer/lib/shred.bundle.js create mode 100644 htdocs/includes/restler/framework/Luracast/Restler/explorer/lib/shred/content.js create mode 100644 htdocs/includes/restler/framework/Luracast/Restler/explorer/lib/swagger-oauth.js create mode 100644 htdocs/includes/restler/framework/Luracast/Restler/explorer/lib/swagger.js create mode 100644 htdocs/includes/restler/framework/Luracast/Restler/explorer/lib/underscore-min.js create mode 100644 htdocs/includes/restler/framework/Luracast/Restler/explorer/o2c.html create mode 100644 htdocs/includes/restler/framework/Luracast/Restler/explorer/swagger-ui.js create mode 100644 htdocs/includes/restler/framework/Luracast/Restler/explorer/swagger-ui.min.js diff --git a/htdocs/includes/restler/framework/Luracast/Restler/ApcCache.php b/htdocs/includes/restler/framework/Luracast/Restler/ApcCache.php index 55b22107726..b8cdd95b373 100644 --- a/htdocs/includes/restler/framework/Luracast/Restler/ApcCache.php +++ b/htdocs/includes/restler/framework/Luracast/Restler/ApcCache.php @@ -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 { diff --git a/htdocs/includes/restler/framework/Luracast/Restler/AutoLoader.php b/htdocs/includes/restler/framework/Luracast/Restler/AutoLoader.php index be9aa9c11df..d1aa93c2543 100644 --- a/htdocs/includes/restler/framework/Luracast/Restler/AutoLoader.php +++ b/htdocs/includes/restler/framework/Luracast/Restler/AutoLoader.php @@ -12,7 +12,7 @@ namespace Luracast\Restler { * @subpackage helper * @author Nick Lombard * @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) diff --git a/htdocs/includes/restler/framework/Luracast/Restler/CommentParser.php b/htdocs/includes/restler/framework/Luracast/Restler/CommentParser.php index 839983b73db..666c2e57fd4 100644 --- a/htdocs/includes/restler/framework/Luracast/Restler/CommentParser.php +++ b/htdocs/includes/restler/framework/Luracast/Restler/CommentParser.php @@ -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); } diff --git a/htdocs/includes/restler/framework/Luracast/Restler/Compose.php b/htdocs/includes/restler/framework/Luracast/Restler/Compose.php index c4a6f03409c..594e5902004 100644 --- a/htdocs/includes/restler/framework/Luracast/Restler/Compose.php +++ b/htdocs/includes/restler/framework/Luracast/Restler/Compose.php @@ -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 { diff --git a/htdocs/includes/restler/framework/Luracast/Restler/Data/ApiMethodInfo.php b/htdocs/includes/restler/framework/Luracast/Restler/Data/ApiMethodInfo.php index c97c27098a9..4d0fa4bfd48 100644 --- a/htdocs/includes/restler/framework/Luracast/Restler/Data/ApiMethodInfo.php +++ b/htdocs/includes/restler/framework/Luracast/Restler/Data/ApiMethodInfo.php @@ -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 { diff --git a/htdocs/includes/restler/framework/Luracast/Restler/Data/Arr.php b/htdocs/includes/restler/framework/Luracast/Restler/Data/Arr.php index 727f14d8080..6e98a4132ea 100644 --- a/htdocs/includes/restler/framework/Luracast/Restler/Data/Arr.php +++ b/htdocs/includes/restler/framework/Luracast/Restler/Data/Arr.php @@ -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 { diff --git a/htdocs/includes/restler/framework/Luracast/Restler/Data/Invalid.php b/htdocs/includes/restler/framework/Luracast/Restler/Data/Invalid.php index 3eaec18017d..6b06d266f13 100644 --- a/htdocs/includes/restler/framework/Luracast/Restler/Data/Invalid.php +++ b/htdocs/includes/restler/framework/Luracast/Restler/Data/Invalid.php @@ -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 { diff --git a/htdocs/includes/restler/framework/Luracast/Restler/Data/Object.php b/htdocs/includes/restler/framework/Luracast/Restler/Data/Object.php index b34f82f0b79..5ef5850b86e 100644 --- a/htdocs/includes/restler/framework/Luracast/Restler/Data/Object.php +++ b/htdocs/includes/restler/framework/Luracast/Restler/Data/Object.php @@ -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 { diff --git a/htdocs/includes/restler/framework/Luracast/Restler/Data/Text.php b/htdocs/includes/restler/framework/Luracast/Restler/Data/Text.php new file mode 100644 index 00000000000..506ea25fb20 --- /dev/null +++ b/htdocs/includes/restler/framework/Luracast/Restler/Data/Text.php @@ -0,0 +1,96 @@ + + * @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))); + } +} \ No newline at end of file diff --git a/htdocs/includes/restler/framework/Luracast/Restler/Data/ValidationInfo.php b/htdocs/includes/restler/framework/Luracast/Restler/Data/ValidationInfo.php index aacc3398c9a..298b72ec125 100644 --- a/htdocs/includes/restler/framework/Luracast/Restler/Data/ValidationInfo.php +++ b/htdocs/includes/restler/framework/Luracast/Restler/Data/ValidationInfo.php @@ -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 { diff --git a/htdocs/includes/restler/framework/Luracast/Restler/Data/Validator.php b/htdocs/includes/restler/framework/Luracast/Restler/Data/Validator.php index 5e1940ba1b6..be2ef28f40a 100644 --- a/htdocs/includes/restler/framework/Luracast/Restler/Data/Validator.php +++ b/htdocs/includes/restler/framework/Luracast/Restler/Data/Validator.php @@ -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 ? "$info->label" : "`$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; } diff --git a/htdocs/includes/restler/framework/Luracast/Restler/Data/ValueObject.php b/htdocs/includes/restler/framework/Luracast/Restler/Data/ValueObject.php index 811c170acbd..46857b45329 100644 --- a/htdocs/includes/restler/framework/Luracast/Restler/Data/ValueObject.php +++ b/htdocs/includes/restler/framework/Luracast/Restler/Data/ValueObject.php @@ -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 { diff --git a/htdocs/includes/restler/framework/Luracast/Restler/Data/iValidate.php b/htdocs/includes/restler/framework/Luracast/Restler/Data/iValidate.php index 8ed28b27b1b..66688b066ba 100644 --- a/htdocs/includes/restler/framework/Luracast/Restler/Data/iValidate.php +++ b/htdocs/includes/restler/framework/Luracast/Restler/Data/iValidate.php @@ -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 { diff --git a/htdocs/includes/restler/framework/Luracast/Restler/Data/iValueObject.php b/htdocs/includes/restler/framework/Luracast/Restler/Data/iValueObject.php index 4597cdac825..dbf36cb744c 100644 --- a/htdocs/includes/restler/framework/Luracast/Restler/Data/iValueObject.php +++ b/htdocs/includes/restler/framework/Luracast/Restler/Data/iValueObject.php @@ -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 { diff --git a/htdocs/includes/restler/framework/Luracast/Restler/Defaults.php b/htdocs/includes/restler/framework/Luracast/Restler/Defaults.php index 98b97202495..8913db316b5 100644 --- a/htdocs/includes/restler/framework/Luracast/Restler/Defaults.php +++ b/htdocs/includes/restler/framework/Luracast/Restler/Defaults.php @@ -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 */ diff --git a/htdocs/includes/restler/framework/Luracast/Restler/EventDispatcher.php b/htdocs/includes/restler/framework/Luracast/Restler/EventDispatcher.php index 8e913bc563d..1c173d38baa 100644 --- a/htdocs/includes/restler/framework/Luracast/Restler/EventDispatcher.php +++ b/htdocs/includes/restler/framework/Luracast/Restler/EventDispatcher.php @@ -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; diff --git a/htdocs/includes/restler/framework/Luracast/Restler/Explorer.php b/htdocs/includes/restler/framework/Luracast/Restler/Explorer.php new file mode 100644 index 00000000000..9522441c5b0 --- /dev/null +++ b/htdocs/includes/restler/framework/Luracast/Restler/Explorer.php @@ -0,0 +1,582 @@ + '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 => '  ', //public api + 1 => '  ', //hybrid api + 2 => '  ', //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 = '
'; + foreach ($firstChild['children'] as $child) { + $description .= isset($child['required']) && $child['required'] + ? '' . $child['name'] . ' (required)
' + : $child['name'] . '
'; + } + $description .= '
'; + } + $r[] = $this->parameter(new ValidationInfo($firstChild), $description); + } else { + $description = '
'; + foreach ($children as $child) { + $description .= isset($child['required']) && $child['required'] + ? '' . $child['name'] . ' (required)
' + : $child['name'] . '
'; + } + $description .= '
'; + + //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(); + } +} \ No newline at end of file diff --git a/htdocs/includes/restler/framework/Luracast/Restler/ExplorerInfo.php b/htdocs/includes/restler/framework/Luracast/Restler/ExplorerInfo.php new file mode 100644 index 00000000000..41d969f65d9 --- /dev/null +++ b/htdocs/includes/restler/framework/Luracast/Restler/ExplorerInfo.php @@ -0,0 +1,17 @@ +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 + } } \ No newline at end of file diff --git a/htdocs/includes/restler/framework/Luracast/Restler/Format/AmfFormat.php b/htdocs/includes/restler/framework/Luracast/Restler/Format/AmfFormat.php index e477badb01c..3d146fbcfac 100644 --- a/htdocs/includes/restler/framework/Luracast/Restler/Format/AmfFormat.php +++ b/htdocs/includes/restler/framework/Luracast/Restler/Format/AmfFormat.php @@ -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) { diff --git a/htdocs/includes/restler/framework/Luracast/Restler/Format/CsvFormat.php b/htdocs/includes/restler/framework/Luracast/Restler/Format/CsvFormat.php index c0dedf1b515..d6cdb63a943 100644 --- a/htdocs/includes/restler/framework/Luracast/Restler/Format/CsvFormat.php +++ b/htdocs/includes/restler/framework/Luracast/Restler/Format/CsvFormat.php @@ -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 { diff --git a/htdocs/includes/restler/framework/Luracast/Restler/Format/DependentFormat.php b/htdocs/includes/restler/framework/Luracast/Restler/Format/DependentFormat.php new file mode 100644 index 00000000000..55255b8a105 --- /dev/null +++ b/htdocs/includes/restler/framework/Luracast/Restler/Format/DependentFormat.php @@ -0,0 +1,56 @@ + 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(); + } + +} \ No newline at end of file diff --git a/htdocs/includes/restler/framework/Luracast/Restler/Format/DependentMultiFormat.php b/htdocs/includes/restler/framework/Luracast/Restler/Format/DependentMultiFormat.php new file mode 100644 index 00000000000..60065f59e24 --- /dev/null +++ b/htdocs/includes/restler/framework/Luracast/Restler/Format/DependentMultiFormat.php @@ -0,0 +1,39 @@ + 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(); + } + +} \ No newline at end of file diff --git a/htdocs/includes/restler/framework/Luracast/Restler/Format/Format.php b/htdocs/includes/restler/framework/Luracast/Restler/Format/Format.php index d04f74cf4ac..10b2c5834e6 100644 --- a/htdocs/includes/restler/framework/Luracast/Restler/Format/Format.php +++ b/htdocs/includes/restler/framework/Luracast/Restler/Format/Format.php @@ -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 { diff --git a/htdocs/includes/restler/framework/Luracast/Restler/Format/HtmlFormat.php b/htdocs/includes/restler/framework/Luracast/Restler/Format/HtmlFormat.php index 65d2bb6279b..6e7d4b7303e 100644 --- a/htdocs/includes/restler/framework/Luracast/Restler/Format/HtmlFormat.php +++ b/htdocs/includes/restler/framework/Luracast/Restler/Format/HtmlFormat.php @@ -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]) diff --git a/htdocs/includes/restler/framework/Luracast/Restler/Format/JsFormat.php b/htdocs/includes/restler/framework/Luracast/Restler/Format/JsFormat.php index 067fd1aaa37..0f04b7f0ee5 100644 --- a/htdocs/includes/restler/framework/Luracast/Restler/Format/JsFormat.php +++ b/htdocs/includes/restler/framework/Luracast/Restler/Format/JsFormat.php @@ -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 { diff --git a/htdocs/includes/restler/framework/Luracast/Restler/Format/JsonFormat.php b/htdocs/includes/restler/framework/Luracast/Restler/Format/JsonFormat.php index 1b7fd938c3c..6986ce65c90 100644 --- a/htdocs/includes/restler/framework/Luracast/Restler/Format/JsonFormat.php +++ b/htdocs/includes/restler/framework/Luracast/Restler/Format/JsonFormat.php @@ -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); + } + } +} \ No newline at end of file diff --git a/htdocs/includes/restler/framework/Luracast/Restler/Format/MultiFormat.php b/htdocs/includes/restler/framework/Luracast/Restler/Format/MultiFormat.php index 2c93cd04fe9..18fdd54b842 100644 --- a/htdocs/includes/restler/framework/Luracast/Restler/Format/MultiFormat.php +++ b/htdocs/includes/restler/framework/Luracast/Restler/Format/MultiFormat.php @@ -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 { diff --git a/htdocs/includes/restler/framework/Luracast/Restler/Format/PlistFormat.php b/htdocs/includes/restler/framework/Luracast/Restler/Format/PlistFormat.php index 2c645eb0dfe..2f4faa0769f 100644 --- a/htdocs/includes/restler/framework/Luracast/Restler/Format/PlistFormat.php +++ b/htdocs/includes/restler/framework/Luracast/Restler/Format/PlistFormat.php @@ -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' + ); + } } diff --git a/htdocs/includes/restler/framework/Luracast/Restler/Format/TsvFormat.php b/htdocs/includes/restler/framework/Luracast/Restler/Format/TsvFormat.php index f67ebfcd677..502b02d2758 100644 --- a/htdocs/includes/restler/framework/Luracast/Restler/Format/TsvFormat.php +++ b/htdocs/includes/restler/framework/Luracast/Restler/Format/TsvFormat.php @@ -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 { diff --git a/htdocs/includes/restler/framework/Luracast/Restler/Format/UploadFormat.php b/htdocs/includes/restler/framework/Luracast/Restler/Format/UploadFormat.php index aaebf6b5695..f785beeaa02 100644 --- a/htdocs/includes/restler/framework/Luracast/Restler/Format/UploadFormat.php +++ b/htdocs/includes/restler/framework/Luracast/Restler/Format/UploadFormat.php @@ -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 { diff --git a/htdocs/includes/restler/framework/Luracast/Restler/Format/UrlEncodedFormat.php b/htdocs/includes/restler/framework/Luracast/Restler/Format/UrlEncodedFormat.php index a37d5d6c5cf..12d5a410333 100644 --- a/htdocs/includes/restler/framework/Luracast/Restler/Format/UrlEncodedFormat.php +++ b/htdocs/includes/restler/framework/Luracast/Restler/Format/UrlEncodedFormat.php @@ -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 { diff --git a/htdocs/includes/restler/framework/Luracast/Restler/Format/XmlFormat.php b/htdocs/includes/restler/framework/Luracast/Restler/Format/XmlFormat.php index 52fe50c6755..b006409e800 100644 --- a/htdocs/includes/restler/framework/Luracast/Restler/Format/XmlFormat.php +++ b/htdocs/includes/restler/framework/Luracast/Restler/Format/XmlFormat.php @@ -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; } } - diff --git a/htdocs/includes/restler/framework/Luracast/Restler/Format/YamlFormat.php b/htdocs/includes/restler/framework/Luracast/Restler/Format/YamlFormat.php index d87ad7bbc99..0cb1564f4ed 100644 --- a/htdocs/includes/restler/framework/Luracast/Restler/Format/YamlFormat.php +++ b/htdocs/includes/restler/framework/Luracast/Restler/Format/YamlFormat.php @@ -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); } } diff --git a/htdocs/includes/restler/framework/Luracast/Restler/Format/iDecodeStream.php b/htdocs/includes/restler/framework/Luracast/Restler/Format/iDecodeStream.php index 8f2765fcfd8..080e3da506c 100644 --- a/htdocs/includes/restler/framework/Luracast/Restler/Format/iDecodeStream.php +++ b/htdocs/includes/restler/framework/Luracast/Restler/Format/iDecodeStream.php @@ -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 { diff --git a/htdocs/includes/restler/framework/Luracast/Restler/Format/iFormat.php b/htdocs/includes/restler/framework/Luracast/Restler/Format/iFormat.php index 60c6f0a7ede..d70d2019a94 100644 --- a/htdocs/includes/restler/framework/Luracast/Restler/Format/iFormat.php +++ b/htdocs/includes/restler/framework/Luracast/Restler/Format/iFormat.php @@ -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 { diff --git a/htdocs/includes/restler/framework/Luracast/Restler/HumanReadableCache.php b/htdocs/includes/restler/framework/Luracast/Restler/HumanReadableCache.php index f134605e4d1..6e1c22b432c 100644 --- a/htdocs/includes/restler/framework/Luracast/Restler/HumanReadableCache.php +++ b/htdocs/includes/restler/framework/Luracast/Restler/HumanReadableCache.php @@ -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 { diff --git a/htdocs/includes/restler/framework/Luracast/Restler/InvalidAuthCredentials.php b/htdocs/includes/restler/framework/Luracast/Restler/InvalidAuthCredentials.php new file mode 100644 index 00000000000..233e543e0aa --- /dev/null +++ b/htdocs/includes/restler/framework/Luracast/Restler/InvalidAuthCredentials.php @@ -0,0 +1,22 @@ + + * @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 +{ + +} \ No newline at end of file diff --git a/htdocs/includes/restler/framework/Luracast/Restler/MemcacheCache.php b/htdocs/includes/restler/framework/Luracast/Restler/MemcacheCache.php new file mode 100644 index 00000000000..3132965d374 --- /dev/null +++ b/htdocs/includes/restler/framework/Luracast/Restler/MemcacheCache.php @@ -0,0 +1,140 @@ + + * @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); + } + +} diff --git a/htdocs/includes/restler/framework/Luracast/Restler/PassThrough.php b/htdocs/includes/restler/framework/Luracast/Restler/PassThrough.php new file mode 100644 index 00000000000..e1fc21eae6b --- /dev/null +++ b/htdocs/includes/restler/framework/Luracast/Restler/PassThrough.php @@ -0,0 +1,91 @@ + + * @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; + } +} \ No newline at end of file diff --git a/htdocs/includes/restler/framework/Luracast/Restler/Redirect.php b/htdocs/includes/restler/framework/Luracast/Restler/Redirect.php index 7c494bc3c19..63319106759 100644 --- a/htdocs/includes/restler/framework/Luracast/Restler/Redirect.php +++ b/htdocs/includes/restler/framework/Luracast/Restler/Redirect.php @@ -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']); + } } \ No newline at end of file diff --git a/htdocs/includes/restler/framework/Luracast/Restler/Resources.php b/htdocs/includes/restler/framework/Luracast/Restler/Resources.php index 23d2407c486..43e95fa02be 100644 --- a/htdocs/includes/restler/framework/Luracast/Restler/Resources.php +++ b/htdocs/includes/restler/framework/Luracast/Restler/Resources.php @@ -1,7 +1,7 @@ $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; } } diff --git a/htdocs/includes/restler/framework/Luracast/Restler/RestException.php b/htdocs/includes/restler/framework/Luracast/Restler/RestException.php index 763ec8361f1..b2e51f8b122 100644 --- a/htdocs/includes/restler/framework/Luracast/Restler/RestException.php +++ b/htdocs/includes/restler/framework/Luracast/Restler/RestException.php @@ -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 diff --git a/htdocs/includes/restler/framework/Luracast/Restler/Restler.php b/htdocs/includes/restler/framework/Luracast/Restler/Restler.php index 99c00a89ed3..0cef46a7e41 100644 --- a/htdocs/includes/restler/framework/Luracast/Restler/Restler.php +++ b/htdocs/includes/restler/framework/Luracast/Restler/Restler.php @@ -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(); } diff --git a/htdocs/includes/restler/framework/Luracast/Restler/Routes.php b/htdocs/includes/restler/framework/Luracast/Restler/Routes.php index 5b1128cb54c..067603a0f58 100644 --- a/htdocs/includes/restler/framework/Luracast/Restler/Routes.php +++ b/htdocs/includes/restler/framework/Luracast/Restler/Routes.php @@ -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; } -} \ No newline at end of file +} diff --git a/htdocs/includes/restler/framework/Luracast/Restler/Scope.php b/htdocs/includes/restler/framework/Luracast/Restler/Scope.php index a6b1baae57d..91eea3ef6a1 100644 --- a/htdocs/includes/restler/framework/Luracast/Restler/Scope.php +++ b/htdocs/includes/restler/framework/Luracast/Restler/Scope.php @@ -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; } diff --git a/htdocs/includes/restler/framework/Luracast/Restler/UI/Emmet.php b/htdocs/includes/restler/framework/Luracast/Restler/UI/Emmet.php index 658cdea91bd..abd4a3a73ba 100644 --- a/htdocs/includes/restler/framework/Luracast/Restler/UI/Emmet.php +++ b/htdocs/includes/restler/framework/Luracast/Restler/UI/Emmet.php @@ -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 = '.#*>+^[=" ]{$@-#}'; diff --git a/htdocs/includes/restler/framework/Luracast/Restler/UI/FormStyles.php b/htdocs/includes/restler/framework/Luracast/Restler/UI/FormStyles.php index 486cb972e4f..dc816d63a0a 100644 --- a/htdocs/includes/restler/framework/Luracast/Restler/UI/FormStyles.php +++ b/htdocs/includes/restler/framework/Luracast/Restler/UI/FormStyles.php @@ -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{   }^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{   }^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# :  }+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# :  }+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 ---------------------// ); diff --git a/htdocs/includes/restler/framework/Luracast/Restler/UI/Forms.php b/htdocs/includes/restler/framework/Luracast/Restler/UI/Forms.php index 289f40b6f0f..7073bfcf837 100644 --- a/htdocs/includes/restler/framework/Luracast/Restler/UI/Forms.php +++ b/htdocs/includes/restler/framework/Luracast/Restler/UI/Forms.php @@ -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 "
";
         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;
             }
         }
diff --git a/htdocs/includes/restler/framework/Luracast/Restler/UI/Nav.php b/htdocs/includes/restler/framework/Luracast/Restler/UI/Nav.php
index 7245dbddf8d..b74054a99c5 100644
--- a/htdocs/includes/restler/framework/Luracast/Restler/UI/Nav.php
+++ b/htdocs/includes/restler/framework/Luracast/Restler/UI/Nav.php
@@ -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));
-    }
-
-} 
\ No newline at end of file
+}
diff --git a/htdocs/includes/restler/framework/Luracast/Restler/UI/Tags.php b/htdocs/includes/restler/framework/Luracast/Restler/UI/Tags.php
index b6302a28236..2c194cdbd1b 100644
--- a/htdocs/includes/restler/framework/Luracast/Restler/UI/Tags.php
+++ b/htdocs/includes/restler/framework/Luracast/Restler/UI/Tags.php
@@ -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;
     }
 
diff --git a/htdocs/includes/restler/framework/Luracast/Restler/User.php b/htdocs/includes/restler/framework/Luracast/Restler/User.php
index ea7ff79d603..57465e26e46 100644
--- a/htdocs/includes/restler/framework/Luracast/Restler/User.php
+++ b/htdocs/includes/restler/framework/Luracast/Restler/User.php
@@ -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
 {
diff --git a/htdocs/includes/restler/framework/Luracast/Restler/Util.php b/htdocs/includes/restler/framework/Luracast/Restler/Util.php
index 0a1fc8a40ea..e1c6f60c317 100644
--- a/htdocs/includes/restler/framework/Luracast/Restler/Util.php
+++ b/htdocs/includes/restler/framework/Luracast/Restler/Util.php
@@ -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
      *
diff --git a/htdocs/includes/restler/framework/Luracast/Restler/composer.json b/htdocs/includes/restler/framework/Luracast/Restler/composer.json
index 1e096989b01..aee7ffc136b 100644
--- a/htdocs/includes/restler/framework/Luracast/Restler/composer.json
+++ b/htdocs/includes/restler/framework/Luracast/Restler/composer.json
@@ -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":{
diff --git a/htdocs/includes/restler/framework/Luracast/Restler/explorer/css/reset.css b/htdocs/includes/restler/framework/Luracast/Restler/explorer/css/reset.css
new file mode 100644
index 00000000000..b2b078943c4
--- /dev/null
+++ b/htdocs/includes/restler/framework/Luracast/Restler/explorer/css/reset.css
@@ -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;
+}
diff --git a/htdocs/includes/restler/framework/Luracast/Restler/explorer/css/screen.css b/htdocs/includes/restler/framework/Luracast/Restler/explorer/css/screen.css
new file mode 100644
index 00000000000..2ac5625af06
--- /dev/null
+++ b/htdocs/includes/restler/framework/Luracast/Restler/explorer/css/screen.css
@@ -0,0 +1,1243 @@
+/* Original style from softwaremaniacs.org (c) Ivan Sagalaev  */
+.swagger-section pre code {
+  display: block;
+  padding: 0.5em;
+  background: #F0F0F0;
+}
+.swagger-section pre code,
+.swagger-section pre .subst,
+.swagger-section pre .tag .title,
+.swagger-section pre .lisp .title,
+.swagger-section pre .clojure .built_in,
+.swagger-section pre .nginx .title {
+  color: black;
+}
+.swagger-section pre .string,
+.swagger-section pre .title,
+.swagger-section pre .constant,
+.swagger-section pre .parent,
+.swagger-section pre .tag .value,
+.swagger-section pre .rules .value,
+.swagger-section pre .rules .value .number,
+.swagger-section pre .preprocessor,
+.swagger-section pre .ruby .symbol,
+.swagger-section pre .ruby .symbol .string,
+.swagger-section pre .aggregate,
+.swagger-section pre .template_tag,
+.swagger-section pre .django .variable,
+.swagger-section pre .smalltalk .class,
+.swagger-section pre .addition,
+.swagger-section pre .flow,
+.swagger-section pre .stream,
+.swagger-section pre .bash .variable,
+.swagger-section pre .apache .tag,
+.swagger-section pre .apache .cbracket,
+.swagger-section pre .tex .command,
+.swagger-section pre .tex .special,
+.swagger-section pre .erlang_repl .function_or_atom,
+.swagger-section pre .markdown .header {
+  color: #800;
+}
+.swagger-section pre .comment,
+.swagger-section pre .annotation,
+.swagger-section pre .template_comment,
+.swagger-section pre .diff .header,
+.swagger-section pre .chunk,
+.swagger-section pre .markdown .blockquote {
+  color: #888;
+}
+.swagger-section pre .number,
+.swagger-section pre .date,
+.swagger-section pre .regexp,
+.swagger-section pre .literal,
+.swagger-section pre .smalltalk .symbol,
+.swagger-section pre .smalltalk .char,
+.swagger-section pre .go .constant,
+.swagger-section pre .change,
+.swagger-section pre .markdown .bullet,
+.swagger-section pre .markdown .link_url {
+  color: #080;
+}
+.swagger-section pre .label,
+.swagger-section pre .javadoc,
+.swagger-section pre .ruby .string,
+.swagger-section pre .decorator,
+.swagger-section pre .filter .argument,
+.swagger-section pre .localvars,
+.swagger-section pre .array,
+.swagger-section pre .attr_selector,
+.swagger-section pre .important,
+.swagger-section pre .pseudo,
+.swagger-section pre .pi,
+.swagger-section pre .doctype,
+.swagger-section pre .deletion,
+.swagger-section pre .envvar,
+.swagger-section pre .shebang,
+.swagger-section pre .apache .sqbracket,
+.swagger-section pre .nginx .built_in,
+.swagger-section pre .tex .formula,
+.swagger-section pre .erlang_repl .reserved,
+.swagger-section pre .prompt,
+.swagger-section pre .markdown .link_label,
+.swagger-section pre .vhdl .attribute,
+.swagger-section pre .clojure .attribute,
+.swagger-section pre .coffeescript .property {
+  color: #8888ff;
+}
+.swagger-section pre .keyword,
+.swagger-section pre .id,
+.swagger-section pre .phpdoc,
+.swagger-section pre .title,
+.swagger-section pre .built_in,
+.swagger-section pre .aggregate,
+.swagger-section pre .css .tag,
+.swagger-section pre .javadoctag,
+.swagger-section pre .phpdoc,
+.swagger-section pre .yardoctag,
+.swagger-section pre .smalltalk .class,
+.swagger-section pre .winutils,
+.swagger-section pre .bash .variable,
+.swagger-section pre .apache .tag,
+.swagger-section pre .go .typename,
+.swagger-section pre .tex .command,
+.swagger-section pre .markdown .strong,
+.swagger-section pre .request,
+.swagger-section pre .status {
+  font-weight: bold;
+}
+.swagger-section pre .markdown .emphasis {
+  font-style: italic;
+}
+.swagger-section pre .nginx .built_in {
+  font-weight: normal;
+}
+.swagger-section pre .coffeescript .javascript,
+.swagger-section pre .javascript .xml,
+.swagger-section pre .tex .formula,
+.swagger-section pre .xml .javascript,
+.swagger-section pre .xml .vbscript,
+.swagger-section pre .xml .css,
+.swagger-section pre .xml .cdata {
+  opacity: 0.5;
+}
+.swagger-section .swagger-ui-wrap {
+  line-height: 1;
+  font-family: "Droid Sans", sans-serif;
+  max-width: 960px;
+  margin-left: auto;
+  margin-right: auto;
+}
+.swagger-section .swagger-ui-wrap b,
+.swagger-section .swagger-ui-wrap strong {
+  font-family: "Droid Sans", sans-serif;
+  font-weight: bold;
+}
+.swagger-section .swagger-ui-wrap q,
+.swagger-section .swagger-ui-wrap blockquote {
+  quotes: none;
+}
+.swagger-section .swagger-ui-wrap p {
+  line-height: 1.4em;
+  padding: 0 0 10px;
+  color: #333333;
+}
+.swagger-section .swagger-ui-wrap q:before,
+.swagger-section .swagger-ui-wrap q:after,
+.swagger-section .swagger-ui-wrap blockquote:before,
+.swagger-section .swagger-ui-wrap blockquote:after {
+  content: none;
+}
+.swagger-section .swagger-ui-wrap .heading_with_menu h1,
+.swagger-section .swagger-ui-wrap .heading_with_menu h2,
+.swagger-section .swagger-ui-wrap .heading_with_menu h3,
+.swagger-section .swagger-ui-wrap .heading_with_menu h4,
+.swagger-section .swagger-ui-wrap .heading_with_menu h5,
+.swagger-section .swagger-ui-wrap .heading_with_menu h6 {
+  display: block;
+  clear: none;
+  float: left;
+  -moz-box-sizing: border-box;
+  -webkit-box-sizing: border-box;
+  -ms-box-sizing: border-box;
+  box-sizing: border-box;
+  width: 60%;
+}
+.swagger-section .swagger-ui-wrap table {
+  border-collapse: collapse;
+  border-spacing: 0;
+}
+.swagger-section .swagger-ui-wrap table thead tr th {
+  padding: 5px;
+  font-size: 0.9em;
+  color: #666666;
+  border-bottom: 1px solid #999999;
+}
+.swagger-section .swagger-ui-wrap table tbody tr:last-child td {
+  border-bottom: none;
+}
+.swagger-section .swagger-ui-wrap table tbody tr.offset {
+  background-color: #f0f0f0;
+}
+.swagger-section .swagger-ui-wrap table tbody tr td {
+  padding: 6px;
+  font-size: 0.9em;
+  border-bottom: 1px solid #cccccc;
+  vertical-align: top;
+  line-height: 1.3em;
+}
+.swagger-section .swagger-ui-wrap ol {
+  margin: 0px 0 10px;
+  padding: 0 0 0 18px;
+  list-style-type: decimal;
+}
+.swagger-section .swagger-ui-wrap ol li {
+  padding: 5px 0px;
+  font-size: 0.9em;
+  color: #333333;
+}
+.swagger-section .swagger-ui-wrap ol,
+.swagger-section .swagger-ui-wrap ul {
+  list-style: none;
+}
+.swagger-section .swagger-ui-wrap h1 a,
+.swagger-section .swagger-ui-wrap h2 a,
+.swagger-section .swagger-ui-wrap h3 a,
+.swagger-section .swagger-ui-wrap h4 a,
+.swagger-section .swagger-ui-wrap h5 a,
+.swagger-section .swagger-ui-wrap h6 a {
+  text-decoration: none;
+}
+.swagger-section .swagger-ui-wrap h1 a:hover,
+.swagger-section .swagger-ui-wrap h2 a:hover,
+.swagger-section .swagger-ui-wrap h3 a:hover,
+.swagger-section .swagger-ui-wrap h4 a:hover,
+.swagger-section .swagger-ui-wrap h5 a:hover,
+.swagger-section .swagger-ui-wrap h6 a:hover {
+  text-decoration: underline;
+}
+.swagger-section .swagger-ui-wrap h1 span.divider,
+.swagger-section .swagger-ui-wrap h2 span.divider,
+.swagger-section .swagger-ui-wrap h3 span.divider,
+.swagger-section .swagger-ui-wrap h4 span.divider,
+.swagger-section .swagger-ui-wrap h5 span.divider,
+.swagger-section .swagger-ui-wrap h6 span.divider {
+  color: #aaaaaa;
+}
+.swagger-section .swagger-ui-wrap a {
+  color: #547f00;
+}
+.swagger-section .swagger-ui-wrap a img {
+  border: none;
+}
+.swagger-section .swagger-ui-wrap article,
+.swagger-section .swagger-ui-wrap aside,
+.swagger-section .swagger-ui-wrap details,
+.swagger-section .swagger-ui-wrap figcaption,
+.swagger-section .swagger-ui-wrap figure,
+.swagger-section .swagger-ui-wrap footer,
+.swagger-section .swagger-ui-wrap header,
+.swagger-section .swagger-ui-wrap hgroup,
+.swagger-section .swagger-ui-wrap menu,
+.swagger-section .swagger-ui-wrap nav,
+.swagger-section .swagger-ui-wrap section,
+.swagger-section .swagger-ui-wrap summary {
+  display: block;
+}
+.swagger-section .swagger-ui-wrap pre {
+  font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace;
+  background-color: #fcf6db;
+  border: 1px solid #e5e0c6;
+  padding: 10px;
+}
+.swagger-section .swagger-ui-wrap pre code {
+  line-height: 1.6em;
+  background: none;
+}
+.swagger-section .swagger-ui-wrap .content > .content-type > div > label {
+  clear: both;
+  display: block;
+  color: #0F6AB4;
+  font-size: 1.1em;
+  margin: 0;
+  padding: 15px 0 5px;
+}
+.swagger-section .swagger-ui-wrap .content pre {
+  font-size: 12px;
+  margin-top: 5px;
+  padding: 5px;
+}
+.swagger-section .swagger-ui-wrap .icon-btn {
+  cursor: pointer;
+}
+.swagger-section .swagger-ui-wrap .info_title {
+  padding-bottom: 10px;
+  font-weight: bold;
+  font-size: 25px;
+}
+.swagger-section .swagger-ui-wrap p.big,
+.swagger-section .swagger-ui-wrap div.big p {
+  font-size: 1em;
+  margin-bottom: 10px;
+}
+.swagger-section .swagger-ui-wrap form.fullwidth ol li.string input,
+.swagger-section .swagger-ui-wrap form.fullwidth ol li.url input,
+.swagger-section .swagger-ui-wrap form.fullwidth ol li.text textarea,
+.swagger-section .swagger-ui-wrap form.fullwidth ol li.numeric input {
+  width: 500px !important;
+}
+.swagger-section .swagger-ui-wrap .info_license {
+  padding-bottom: 5px;
+}
+.swagger-section .swagger-ui-wrap .info_tos {
+  padding-bottom: 5px;
+}
+.swagger-section .swagger-ui-wrap .message-fail {
+  color: #cc0000;
+}
+.swagger-section .swagger-ui-wrap .info_contact {
+  padding-bottom: 5px;
+}
+.swagger-section .swagger-ui-wrap .info_description {
+  padding-bottom: 10px;
+  font-size: 15px;
+}
+.swagger-section .swagger-ui-wrap .markdown ol li,
+.swagger-section .swagger-ui-wrap .markdown ul li {
+  padding: 3px 0px;
+  line-height: 1.4em;
+  color: #333333;
+}
+.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.string input,
+.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.url input,
+.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.numeric input {
+  display: block;
+  padding: 4px;
+  width: auto;
+  clear: both;
+}
+.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.string input.title,
+.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.url input.title,
+.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.numeric input.title {
+  font-size: 1.3em;
+}
+.swagger-section .swagger-ui-wrap table.fullwidth {
+  width: 100%;
+}
+.swagger-section .swagger-ui-wrap .model-signature {
+  font-family: "Droid Sans", sans-serif;
+  font-size: 1em;
+  line-height: 1.5em;
+}
+.swagger-section .swagger-ui-wrap .model-signature .signature-nav a {
+  text-decoration: none;
+  color: #AAA;
+}
+.swagger-section .swagger-ui-wrap .model-signature .signature-nav a:hover {
+  text-decoration: underline;
+  color: black;
+}
+.swagger-section .swagger-ui-wrap .model-signature .signature-nav .selected {
+  color: black;
+  text-decoration: none;
+}
+.swagger-section .swagger-ui-wrap .model-signature .propType {
+  color: #5555aa;
+}
+.swagger-section .swagger-ui-wrap .model-signature pre:hover {
+  background-color: #ffffdd;
+}
+.swagger-section .swagger-ui-wrap .model-signature pre {
+  font-size: .85em;
+  line-height: 1.2em;
+  overflow: auto;
+  max-height: 400px;
+  cursor: pointer;
+}
+.swagger-section .swagger-ui-wrap .model-signature ul.signature-nav {
+  display: block;
+  margin: 0;
+  padding: 0;
+}
+.swagger-section .swagger-ui-wrap .model-signature ul.signature-nav li:last-child {
+  padding-right: 0;
+  border-right: none;
+}
+.swagger-section .swagger-ui-wrap .model-signature ul.signature-nav li {
+  float: left;
+  margin: 0 5px 5px 0;
+  padding: 2px 5px 2px 0;
+  border-right: 1px solid #ddd;
+}
+.swagger-section .swagger-ui-wrap .model-signature .propOpt {
+  color: #555;
+}
+.swagger-section .swagger-ui-wrap .model-signature .snippet small {
+  font-size: 0.75em;
+}
+.swagger-section .swagger-ui-wrap .model-signature .propOptKey {
+  font-style: italic;
+}
+.swagger-section .swagger-ui-wrap .model-signature .description .strong {
+  font-weight: bold;
+  color: #000;
+  font-size: .9em;
+}
+.swagger-section .swagger-ui-wrap .model-signature .description div {
+  font-size: 0.9em;
+  line-height: 1.5em;
+  margin-left: 1em;
+}
+.swagger-section .swagger-ui-wrap .model-signature .description .stronger {
+  font-weight: bold;
+  color: #000;
+}
+.swagger-section .swagger-ui-wrap .model-signature .propName {
+  font-weight: bold;
+}
+.swagger-section .swagger-ui-wrap .model-signature .signature-container {
+  clear: both;
+}
+.swagger-section .swagger-ui-wrap .body-textarea {
+  width: 300px;
+  height: 100px;
+  border: 1px solid #aaa;
+}
+.swagger-section .swagger-ui-wrap .markdown p code,
+.swagger-section .swagger-ui-wrap .markdown li code {
+  font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace;
+  background-color: #f0f0f0;
+  color: black;
+  padding: 1px 3px;
+}
+.swagger-section .swagger-ui-wrap .required {
+  font-weight: bold;
+}
+.swagger-section .swagger-ui-wrap input.parameter {
+  width: 300px;
+  border: 1px solid #aaa;
+}
+.swagger-section .swagger-ui-wrap h1 {
+  color: black;
+  font-size: 1.5em;
+  line-height: 1.3em;
+  padding: 10px 0 10px 0;
+  font-family: "Droid Sans", sans-serif;
+  font-weight: bold;
+}
+.swagger-section .swagger-ui-wrap .heading_with_menu {
+  float: none;
+  clear: both;
+  overflow: hidden;
+  display: block;
+}
+.swagger-section .swagger-ui-wrap .heading_with_menu ul {
+  display: block;
+  clear: none;
+  float: right;
+  -moz-box-sizing: border-box;
+  -webkit-box-sizing: border-box;
+  -ms-box-sizing: border-box;
+  box-sizing: border-box;
+  margin-top: 10px;
+}
+.swagger-section .swagger-ui-wrap h2 {
+  color: black;
+  font-size: 1.3em;
+  padding: 10px 0 10px 0;
+}
+.swagger-section .swagger-ui-wrap h2 a {
+  color: black;
+}
+.swagger-section .swagger-ui-wrap h2 span.sub {
+  font-size: 0.7em;
+  color: #999999;
+  font-style: italic;
+}
+.swagger-section .swagger-ui-wrap h2 span.sub a {
+  color: #777777;
+}
+.swagger-section .swagger-ui-wrap span.weak {
+  color: #666666;
+}
+.swagger-section .swagger-ui-wrap .message-success {
+  color: #89BF04;
+}
+.swagger-section .swagger-ui-wrap caption,
+.swagger-section .swagger-ui-wrap th,
+.swagger-section .swagger-ui-wrap td {
+  text-align: left;
+  font-weight: normal;
+  vertical-align: middle;
+}
+.swagger-section .swagger-ui-wrap .code {
+  font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace;
+}
+.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.text textarea {
+  font-family: "Droid Sans", sans-serif;
+  height: 250px;
+  padding: 4px;
+  display: block;
+  clear: both;
+}
+.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.select select {
+  display: block;
+  clear: both;
+}
+.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.boolean {
+  float: none;
+  clear: both;
+  overflow: hidden;
+  display: block;
+}
+.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.boolean label {
+  display: block;
+  float: left;
+  clear: none;
+  margin: 0;
+  padding: 0;
+}
+.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.boolean input {
+  display: block;
+  float: left;
+  clear: none;
+  margin: 0 5px 0 0;
+}
+.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.required label {
+  color: black;
+}
+.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li label {
+  display: block;
+  clear: both;
+  width: auto;
+  padding: 0 0 3px;
+  color: #666666;
+}
+.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li label abbr {
+  padding-left: 3px;
+  color: #888888;
+}
+.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li p.inline-hints {
+  margin-left: 0;
+  font-style: italic;
+  font-size: 0.9em;
+  margin: 0;
+}
+.swagger-section .swagger-ui-wrap form.formtastic fieldset.buttons {
+  margin: 0;
+  padding: 0;
+}
+.swagger-section .swagger-ui-wrap span.blank,
+.swagger-section .swagger-ui-wrap span.empty {
+  color: #888888;
+  font-style: italic;
+}
+.swagger-section .swagger-ui-wrap .markdown h3 {
+  color: #547f00;
+}
+.swagger-section .swagger-ui-wrap .markdown h4 {
+  color: #666666;
+}
+.swagger-section .swagger-ui-wrap .markdown pre {
+  font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace;
+  background-color: #fcf6db;
+  border: 1px solid #e5e0c6;
+  padding: 10px;
+  margin: 0 0 10px 0;
+}
+.swagger-section .swagger-ui-wrap .markdown pre code {
+  line-height: 1.6em;
+}
+.swagger-section .swagger-ui-wrap div.gist {
+  margin: 20px 0 25px 0 !important;
+}
+.swagger-section .swagger-ui-wrap ul#resources {
+  font-family: "Droid Sans", sans-serif;
+  font-size: 0.9em;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource {
+  border-bottom: 1px solid #dddddd;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource:hover div.heading h2 a,
+.swagger-section .swagger-ui-wrap ul#resources li.resource.active div.heading h2 a {
+  color: black;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource:hover div.heading ul.options li a,
+.swagger-section .swagger-ui-wrap ul#resources li.resource.active div.heading ul.options li a {
+  color: #555555;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource:last-child {
+  border-bottom: none;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading {
+  border: 1px solid transparent;
+  float: none;
+  clear: both;
+  overflow: hidden;
+  display: block;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options {
+  overflow: hidden;
+  padding: 0;
+  display: block;
+  clear: none;
+  float: right;
+  margin: 14px 10px 0 0;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li {
+  float: left;
+  clear: none;
+  margin: 0;
+  padding: 2px 10px;
+  border-right: 1px solid #dddddd;
+  color: #666666;
+  font-size: 0.9em;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a {
+  color: #aaaaaa;
+  text-decoration: none;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a:hover {
+  text-decoration: underline;
+  color: black;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a:hover,
+.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a:active,
+.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a.active {
+  text-decoration: underline;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li:first-child,
+.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li.first {
+  padding-left: 0;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li:last-child,
+.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li.last {
+  padding-right: 0;
+  border-right: none;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options:first-child,
+.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options.first {
+  padding-left: 0;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 {
+  color: #999999;
+  padding-left: 0;
+  display: block;
+  clear: none;
+  float: left;
+  font-family: "Droid Sans", sans-serif;
+  font-weight: bold;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 a {
+  color: #999999;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 a:hover {
+  color: black;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation {
+  float: none;
+  clear: both;
+  overflow: hidden;
+  display: block;
+  margin: 0 0 10px;
+  padding: 0;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading {
+  float: none;
+  clear: both;
+  overflow: hidden;
+  display: block;
+  margin: 0;
+  padding: 0;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 {
+  display: block;
+  clear: none;
+  float: left;
+  width: auto;
+  margin: 0;
+  padding: 0;
+  line-height: 1.1em;
+  color: black;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.path {
+  padding-left: 10px;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.path a {
+  color: black;
+  text-decoration: none;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.path a:hover {
+  text-decoration: underline;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.http_method a {
+  text-transform: uppercase;
+  text-decoration: none;
+  color: white;
+  display: inline-block;
+  width: 50px;
+  font-size: 0.7em;
+  text-align: center;
+  padding: 7px 0 4px;
+  -moz-border-radius: 2px;
+  -webkit-border-radius: 2px;
+  -o-border-radius: 2px;
+  -ms-border-radius: 2px;
+  -khtml-border-radius: 2px;
+  border-radius: 2px;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span {
+  margin: 0;
+  padding: 0;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options {
+  overflow: hidden;
+  padding: 0;
+  display: block;
+  clear: none;
+  float: right;
+  margin: 6px 10px 0 0;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options li {
+  float: left;
+  clear: none;
+  margin: 0;
+  padding: 2px 10px;
+  font-size: 0.9em;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options li a {
+  text-decoration: none;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options li.access {
+  color: black;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content {
+  border-top: none;
+  padding: 10px;
+  -moz-border-radius-bottomleft: 6px;
+  -webkit-border-bottom-left-radius: 6px;
+  -o-border-bottom-left-radius: 6px;
+  -ms-border-bottom-left-radius: 6px;
+  -khtml-border-bottom-left-radius: 6px;
+  border-bottom-left-radius: 6px;
+  -moz-border-radius-bottomright: 6px;
+  -webkit-border-bottom-right-radius: 6px;
+  -o-border-bottom-right-radius: 6px;
+  -ms-border-bottom-right-radius: 6px;
+  -khtml-border-bottom-right-radius: 6px;
+  border-bottom-right-radius: 6px;
+  margin: 0 0 20px;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content h4 {
+  font-size: 1.1em;
+  margin: 0;
+  padding: 15px 0 5px;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.sandbox_header {
+  float: none;
+  clear: both;
+  overflow: hidden;
+  display: block;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.sandbox_header a {
+  padding: 4px 0 0 10px;
+  display: inline-block;
+  font-size: 0.9em;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.sandbox_header img {
+  display: block;
+  clear: none;
+  float: right;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.sandbox_header input.submit {
+  display: block;
+  clear: none;
+  float: left;
+  padding: 6px 8px;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content form input[type='text'].error {
+  outline: 2px solid black;
+  outline-color: #cc0000;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.response div.block pre {
+  font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace;
+  padding: 10px;
+  font-size: 0.9em;
+  max-height: 400px;
+  overflow-y: auto;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading {
+  background-color: #f9f2e9;
+  border: 1px solid #f0e0ca;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading h3 span.http_method a {
+  background-color: #c5862b;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading ul.options li {
+  border-right: 1px solid #dddddd;
+  border-right-color: #f0e0ca;
+  color: #c5862b;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading ul.options li a {
+  color: #c5862b;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.content {
+  background-color: #faf5ee;
+  border: 1px solid #f0e0ca;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.content h4 {
+  color: #c5862b;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.content div.sandbox_header a {
+  color: #dcb67f;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading {
+  background-color: #fcffcd;
+  border: 1px solid black;
+  border-color: #ffd20f;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading h3 span.http_method a {
+  text-transform: uppercase;
+  background-color: #ffd20f;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading ul.options li {
+  border-right: 1px solid #dddddd;
+  border-right-color: #ffd20f;
+  color: #ffd20f;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading ul.options li a {
+  color: #ffd20f;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.content {
+  background-color: #fcffcd;
+  border: 1px solid black;
+  border-color: #ffd20f;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.content h4 {
+  color: #ffd20f;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.content div.sandbox_header a {
+  color: #6fc992;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading {
+  background-color: #f5e8e8;
+  border: 1px solid #e8c6c7;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading h3 span.http_method a {
+  text-transform: uppercase;
+  background-color: #a41e22;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading ul.options li {
+  border-right: 1px solid #dddddd;
+  border-right-color: #e8c6c7;
+  color: #a41e22;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading ul.options li a {
+  color: #a41e22;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.content {
+  background-color: #f7eded;
+  border: 1px solid #e8c6c7;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.content h4 {
+  color: #a41e22;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.content div.sandbox_header a {
+  color: #c8787a;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading {
+  background-color: #e7f6ec;
+  border: 1px solid #c3e8d1;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading h3 span.http_method a {
+  background-color: #10a54a;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading ul.options li {
+  border-right: 1px solid #dddddd;
+  border-right-color: #c3e8d1;
+  color: #10a54a;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading ul.options li a {
+  color: #10a54a;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.content {
+  background-color: #ebf7f0;
+  border: 1px solid #c3e8d1;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.content h4 {
+  color: #10a54a;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.content div.sandbox_header a {
+  color: #6fc992;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading {
+  background-color: #FCE9E3;
+  border: 1px solid #F5D5C3;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading h3 span.http_method a {
+  background-color: #D38042;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading ul.options li {
+  border-right: 1px solid #dddddd;
+  border-right-color: #f0cecb;
+  color: #D38042;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading ul.options li a {
+  color: #D38042;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.content {
+  background-color: #faf0ef;
+  border: 1px solid #f0cecb;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.content h4 {
+  color: #D38042;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.content div.sandbox_header a {
+  color: #dcb67f;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading {
+  background-color: #e7f0f7;
+  border: 1px solid #c3d9ec;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading h3 span.http_method a {
+  background-color: #0f6ab4;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading ul.options li {
+  border-right: 1px solid #dddddd;
+  border-right-color: #c3d9ec;
+  color: #0f6ab4;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading ul.options li a {
+  color: #0f6ab4;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.content {
+  background-color: #ebf3f9;
+  border: 1px solid #c3d9ec;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.content h4 {
+  color: #0f6ab4;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.content div.sandbox_header a {
+  color: #6fa5d2;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.heading {
+  background-color: #e7f0f7;
+  border: 1px solid #c3d9ec;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.heading h3 span.http_method a {
+  background-color: #0f6ab4;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.heading ul.options li {
+  border-right: 1px solid #dddddd;
+  border-right-color: #c3d9ec;
+  color: #0f6ab4;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.heading ul.options li a {
+  color: #0f6ab4;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.content {
+  background-color: #ebf3f9;
+  border: 1px solid #c3d9ec;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.content h4 {
+  color: #0f6ab4;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.content div.sandbox_header a {
+  color: #6fa5d2;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.content,
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.content,
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.content,
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.content,
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.content,
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.content {
+  border-top: none;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading ul.options li:last-child,
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading ul.options li:last-child,
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading ul.options li:last-child,
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading ul.options li:last-child,
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading ul.options li:last-child,
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading ul.options li:last-child,
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading ul.options li.last,
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading ul.options li.last,
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading ul.options li.last,
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading ul.options li.last,
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading ul.options li.last,
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading ul.options li.last {
+  padding-right: 0;
+  border-right: none;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li a:hover,
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li a:active,
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li a.active {
+  text-decoration: underline;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li:first-child,
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li.first {
+  padding-left: 0;
+}
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations:first-child,
+.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations.first {
+  padding-left: 0;
+}
+.swagger-section .swagger-ui-wrap p#colophon {
+  margin: 0 15px 40px 15px;
+  padding: 10px 0;
+  font-size: 0.8em;
+  border-top: 1px solid #dddddd;
+  font-family: "Droid Sans", sans-serif;
+  color: #999999;
+  font-style: italic;
+}
+.swagger-section .swagger-ui-wrap p#colophon a {
+  text-decoration: none;
+  color: #547f00;
+}
+.swagger-section .swagger-ui-wrap h3 {
+  color: black;
+  font-size: 1.1em;
+  padding: 10px 0 10px 0;
+}
+.swagger-section .swagger-ui-wrap .markdown ol,
+.swagger-section .swagger-ui-wrap .markdown ul {
+  font-family: "Droid Sans", sans-serif;
+  margin: 5px 0 10px;
+  padding: 0 0 0 18px;
+  list-style-type: disc;
+}
+.swagger-section .swagger-ui-wrap form.form_box {
+  background-color: #ebf3f9;
+  border: 1px solid #c3d9ec;
+  padding: 10px;
+}
+.swagger-section .swagger-ui-wrap form.form_box label {
+  color: #0f6ab4 !important;
+}
+.swagger-section .swagger-ui-wrap form.form_box input[type=submit] {
+  display: block;
+  padding: 10px;
+}
+.swagger-section .swagger-ui-wrap form.form_box p.weak {
+  font-size: 0.8em;
+}
+.swagger-section .swagger-ui-wrap form.form_box p {
+  font-size: 0.9em;
+  padding: 0 0 15px;
+  color: #7e7b6d;
+}
+.swagger-section .swagger-ui-wrap form.form_box p a {
+  color: #646257;
+}
+.swagger-section .swagger-ui-wrap form.form_box p strong {
+  color: black;
+}
+.swagger-section .title {
+  font-style: bold;
+}
+.swagger-section .secondary_form {
+  display: none;
+}
+.swagger-section .main_image {
+  display: block;
+  margin-left: auto;
+  margin-right: auto;
+}
+.swagger-section .oauth_body {
+  margin-left: 100px;
+  margin-right: 100px;
+}
+.swagger-section .oauth_submit {
+  text-align: center;
+}
+.swagger-section .api-popup-dialog {
+  z-index: 10000;
+  position: absolute;
+  width: 500px;
+  background: #FFF;
+  padding: 20px;
+  border: 1px solid #ccc;
+  border-radius: 5px;
+  display: none;
+  font-size: 13px;
+  color: #777;
+}
+.swagger-section .api-popup-dialog .api-popup-title {
+  font-size: 24px;
+  padding: 10px 0;
+}
+.swagger-section .api-popup-dialog .api-popup-title {
+  font-size: 24px;
+  padding: 10px 0;
+}
+.swagger-section .api-popup-dialog p.error-msg {
+  padding-left: 5px;
+  padding-bottom: 5px;
+}
+.swagger-section .api-popup-dialog button.api-popup-authbtn {
+  height: 30px;
+}
+.swagger-section .api-popup-dialog button.api-popup-cancel {
+  height: 30px;
+}
+.swagger-section .api-popup-scopes {
+  padding: 10px 20px;
+}
+.swagger-section .api-popup-scopes li {
+  padding: 5px 0;
+  line-height: 20px;
+}
+.swagger-section .api-popup-scopes .api-scope-desc {
+  padding-left: 20px;
+  font-style: italic;
+}
+.swagger-section .api-popup-scopes li input {
+  position: relative;
+  top: 2px;
+}
+.swagger-section .api-popup-actions {
+  padding-top: 10px;
+}
+.swagger-section .access {
+  float: right;
+}
+.swagger-section .auth {
+  float: right;
+}
+.swagger-section #api_information_panel {
+  position: absolute;
+  background: #FFF;
+  border: 1px solid #ccc;
+  border-radius: 5px;
+  display: none;
+  font-size: 13px;
+  max-width: 300px;
+  line-height: 30px;
+  color: black;
+  padding: 5px;
+}
+.swagger-section #api_information_panel p .api-msg-enabled {
+  color: green;
+}
+.swagger-section #api_information_panel p .api-msg-disabled {
+  color: red;
+}
+.swagger-section .api-ic {
+  height: 18px;
+  vertical-align: middle;
+  display: inline-block;
+  background: url(../images/explorer_icons.png) no-repeat;
+}
+.swagger-section .ic-info {
+  background-position: 0 0;
+  width: 18px;
+  margin-top: -7px;
+  margin-left: 4px;
+}
+.swagger-section .ic-warning {
+  background-position: -60px 0;
+  width: 18px;
+  margin-top: -7px;
+  margin-left: 4px;
+}
+.swagger-section .ic-error {
+  background-position: -30px 0;
+  width: 18px;
+  margin-top: -7px;
+  margin-left: 4px;
+}
+.swagger-section .ic-off {
+  background-position: -90px 0;
+  width: 58px;
+  margin-top: -4px;
+  cursor: pointer;
+}
+.swagger-section .ic-on {
+  background-position: -160px 0;
+  width: 58px;
+  margin-top: -4px;
+  cursor: pointer;
+}
+.swagger-section #header {
+  background-color: #646257;
+  padding: 14px;
+}
+.swagger-section #header a#logo {
+  font-size: 1.5em;
+  font-weight: bold;
+  text-decoration: none;
+  background: transparent url(../images/logo_small.png) no-repeat left center;
+  padding: 20px 0 20px 40px;
+  color: white;
+}
+.swagger-section #header form#api_selector {
+  display: block;
+  clear: none;
+  float: right;
+}
+.swagger-section #header form#api_selector .input {
+  display: block;
+  clear: none;
+  float: left;
+  margin: 0 10px 0 0;
+}
+.swagger-section #header form#api_selector .input input#input_apiKey {
+  width: 200px;
+}
+.swagger-section #header form#api_selector .input input#input_baseUrl {
+  width: 400px;
+}
+.swagger-section #header form#api_selector .input a#explore {
+  display: block;
+  text-decoration: none;
+  font-weight: bold;
+  padding: 6px 8px;
+  font-size: 0.9em;
+  color: white;
+  background-color: #000000;
+  -moz-border-radius: 4px;
+  -webkit-border-radius: 4px;
+  -o-border-radius: 4px;
+  -ms-border-radius: 4px;
+  -khtml-border-radius: 4px;
+  border-radius: 4px;
+}
+.swagger-section #header form#api_selector .input a#explore:hover {
+  background-color: #a41e22;
+}
+.swagger-section #header form#api_selector .input input {
+  font-size: 0.9em;
+  padding: 3px;
+  margin: 0;
+}
+.swagger-section #footer-nav {
+  margin-top: 50px;
+  color: #bbb;
+  float: left;
+  list-style: none;
+  font-size: 0.8em;
+}
+.swagger-section #footer-nav li {
+  display: block;
+  float: left;
+}
+.swagger-section #footer-nav a {
+  margin-right: 4px;
+  text-decoration: none;
+  font-weight: none;
+  padding: 4px 2px;
+  font-size: 0.9em;
+  color: #999;
+}
+.swagger-section #footer-nav a:hover {
+  color: #555;
+}
+.swagger-section #content_message {
+  margin: 10px 15px;
+  font-style: italic;
+  color: #999999;
+}
+.swagger-section #message-bar {
+  min-height: 30px;
+  text-align: center;
+  padding-top: 10px;
+}
diff --git a/htdocs/includes/restler/framework/Luracast/Restler/explorer/images/explorer_icons.png b/htdocs/includes/restler/framework/Luracast/Restler/explorer/images/explorer_icons.png
new file mode 100644
index 0000000000000000000000000000000000000000..ed9d2fffb64a2e6e282b90e489d83fc4ddfac3cd
GIT binary patch
literal 5763
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

literal 0
HcmV?d00001

diff --git a/htdocs/includes/restler/framework/Luracast/Restler/explorer/images/throbber.gif b/htdocs/includes/restler/framework/Luracast/Restler/explorer/images/throbber.gif
new file mode 100644
index 0000000000000000000000000000000000000000..06393889242fb3ea9e0205fa84369ec7bb66d15a
GIT binary patch
literal 9257
zcmd^^X;@R|x`tQg5wbE8AV3mAn1TjmQ&en2CK8~ENEH<+P_)pZ24y2E+7O0>K^a6u
zQ3;5MiU^7p6*M3qDk!2=YEcHMQ>nzEYP;R`e2C@r+U+?#XaC*&gKPcB#k$`o&;7mu
zYNhYYXe|Uo84#4ZIko#rcU5K8*yFL{qT47O&^5fZH$
zVZ@%(l~vVHjnm;H@KL8@r%yUHoo;rbHI_4lIH(_nsTT>S2`DFOD~uCb9_dF4`#QgI
zy7ldMcLs+A_s%|e1pRPrbX-tpeNP!9(IpMFTce`t_5U%lP99z%&i6`1d~
zWeM!Rxc50<+d$e^9LT`?B+aMK~apR
zHm?q;p<7{wN2g|I^aGlSws;VP84j(z%aQwvAWv83Z$}p(%
zZ^?2;gxg(ey_`V5J7{;!o;o;KslW@z5EP~JGs|U)J7dF&(ff#A=6vU?cGQ$-4+;Jf
z-ggJEa!yStn`_EWvl)#yhm6XVs}UUbsi;+agri;mCfjH^Uy;lH+Zw^h)4N?oZgZz4
zJk(fTZ|Bi^;+s_M=~+d#vyoxEPzTlOS=mX@sbl*uRj>=MaMr}cFIY8i?UM61>86uB
zV$DlOUCiUJwbzJMP@D$urzK|lL2-PC!p1l47V-ZG<5Ev0Z5h~Kx?`KOp7gkAjV93A
z-Gc7MrlxTf?wF;CbNc@tCHJH{TB3c;#{SVu%97}tyAM2n&|9W_?qv}$*Jt*%7Yxb#
zV0;d;7|lDEltJYS+U)#aiJO};?_Jyy_4%syQ(uy?-J-Yx-9O5nKRk@@XSS~X<(2u~
zV-LamWm~!iqtH9wkpf8mAXZhOD&L#aA_%)4h2M;1M5jt
zIR>Us+%W-GXa_f^opKg=DSrAs)AXeRa;Hp0aC1OgbxQ%Qr_QvTleM1jkR!2mkcX$3
ztsR8~G9iqh(-FJ@F_rQBIYDXV_6s7G9SxaVF^laZqcx$!D97m|7t16j6@Jt6UdDRy49Qyvs|c>RuA|@b%}`*wU}2^7q;&Vtc6@lb
zcXl)T!6nYDzmMJ~%n$KNXyNlCG)GkJ4!82;v6@d3>s5r~E+3!O?049JDr14Y^PeMI02R`0lJ^=oJ
zYd|*u9|SU(j7hY?+<=(?fP*mtV*zFhOrz6%{VA?ozdm&(Jf^V
zMfPZ?>l`mS3{Uq8IM;e!+1YjJy2!mzK$O|wPeU{*QSbs9m+@`f5KxO3PBnQ=%RsZg%go*fJ`*w9TL{-WgZVIA$!YV}3BRcfeXaR$x#b
zW)Tpd#8E4)^MyYdkH;4_;ChJuw%n+Be7Ko4;w-nHvyo$d_0e-YiF78Df&)_)(}fcr_r0mPH(4RRYWIu+d@t0&Ss@O^s!
zOKyX&13)%N@83r^;QsgN{rl(!0|RF1FA)b1{CRXAy&1ySz@>olPiR4r$aMdq&_=nK
zq|cFs8phWJ1@%dZ-gXd{zDbTILD>)qEvH-NU*Rf1b2J1Ri79`rBFl@
z8E^0I)OqEi{pH(a24b9YPG;Kz@t-qZW;3Mpe`MRlmYx{7bH-XZ&`RQ7Rb^%}gc&X|
zd}Q-FZf|RWxHU?PR!(C?80zu(^l>*h{#ulSiid(O!J(8P-41bNM3tnX@U6NS5yo0?
zdcF)~xFE&+&|gZ$23dV5t~?$$&ymZ;F8j7GGMncGSsDo%>J`26=&l=X#rSKv_64;0
zr;k6no@=gV`P)K!=kaHl>q?!`X>(A;84tg^Md<`zA%qbRLby1Z=fn*ZRdNqs%Tq|3
zOt}lZu0q9oKJhgz&+^7PCt$=UFW=R*w?a1)ePoL*`R$Gxj?TU@12tTHsT$giHQU+sqf;fS0FpT!<
z
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
+
+
+  Api Explorer
+  
+  
+  
+  
+  
+  
+  
+  
+  
+  
+  
+  
+  
+  
+  
+  
+  
+
+  
+  
+
+  
+
+
+
+
+
+
 
+
+ + diff --git a/htdocs/includes/restler/framework/Luracast/Restler/explorer/lib/backbone-min.js b/htdocs/includes/restler/framework/Luracast/Restler/explorer/lib/backbone-min.js new file mode 100644 index 00000000000..c1c0d4fff28 --- /dev/null +++ b/htdocs/includes/restler/framework/Luracast/Restler/explorer/lib/backbone-min.js @@ -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=b))this.iframe=i('