| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486 |
- <?php
- namespace Luracast\Restler\UI;
- use Luracast\Restler\CommentParser;
- use Luracast\Restler\Data\ApiMethodInfo;
- 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;
- use Luracast\Restler\iFilter;
- use Luracast\Restler\RestException;
- use Luracast\Restler\Restler;
- use Luracast\Restler\Routes;
- use Luracast\Restler\Scope;
- use Luracast\Restler\UI\Tags as T;
- use Luracast\Restler\User;
- use Luracast\Restler\Util;
- /**
- * Utility class for automatically generating forms for the given http method
- * and api url
- *
- * @category Framework
- * @package Restler
- * @author R.Arul Kumaran <arul@luracast.com>
- * @copyright 2010 Luracast
- * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
- * @link http://luracast.com/products/restler/
- *
- */
- class Forms implements iFilter
- {
- const FORM_KEY = 'form_key';
- public static $filterFormRequestsOnly = false;
- public static $excludedPaths = array();
- private static $style;
- /**
- * @var bool should we fill up the form using given data?
- */
- public static $preFill = true;
- /**
- * @var ValidationInfo
- */
- public static $validationInfo = null;
- protected static $inputTypes = array(
- 'hidden',
- 'password',
- 'button',
- 'image',
- 'file',
- 'reset',
- 'submit',
- 'search',
- 'checkbox',
- 'radio',
- 'email',
- 'text',
- 'color',
- 'date',
- 'datetime',
- 'datetime-local',
- 'email',
- 'month',
- 'number',
- 'range',
- 'search',
- 'tel',
- 'time',
- 'url',
- 'week',
- );
- protected static $fileUpload = false;
- private static $key = array();
- /**
- * @var ApiMethodInfo;
- */
- private static $info;
- public static function setStyles(HtmlForm $style)
- {
- static::$style = get_class($style);
- }
- /**
- * Get the form
- *
- * @param string $method http method to submit the form
- * @param string $action relative path from the web root. When set to null
- * it uses the current api method's path
- * @param bool $dataOnly if you want to render the form yourself use this
- * option
- * @param string $prefix used for adjusting the spacing in front of
- * form elements
- * @param string $indent used for adjusting indentation
- *
- * @return array|T
- *
- * @throws RestException
- */
- public static function get($method = 'POST', $action = null, $dataOnly = false, $prefix = '', $indent = ' ')
- {
- if (!static::$style) {
- static::$style = 'Luracast\\Restler\\UI\HtmlForm';
- }
- try {
- /** @var Restler $restler */
- $restler = Scope::get('Restler');
- if (is_null($action)) {
- $action = $restler->url;
- }
- $info = $restler->url == $action
- && Util::getRequestMethod() == $method
- ? $restler->apiMethodInfo
- : Routes::find(
- trim($action, '/'),
- $method,
- $restler->getRequestedApiVersion(),
- static::$preFill ||
- ($restler->requestMethod == $method &&
- $restler->url == $action)
- ? $restler->getRequestData()
- : array()
- );
- } catch (RestException $e) {
- //echo $e->getErrorMessage();
- $info = false;
- }
- if (!$info) {
- throw new RestException(500, 'invalid action path for form `' . $method . ' ' . $action . '`');
- }
- static::$info = $info;
- $m = $info->metadata;
- $r = static::fields($dataOnly);
- if ($method != 'GET' && $method != 'POST') {
- if (empty(Defaults::$httpMethodOverrideProperty)) {
- throw new RestException(
- 500,
- 'Forms require `Defaults::\$httpMethodOverrideProperty`' .
- "for supporting HTTP $method"
- );
- }
- if ($dataOnly) {
- $r[] = array(
- 'tag' => 'input',
- 'name' => Defaults::$httpMethodOverrideProperty,
- 'type' => 'hidden',
- 'value' => 'method',
- );
- } else {
- $r[] = T::input()
- ->name(Defaults::$httpMethodOverrideProperty)
- ->value($method)
- ->type('hidden');
- }
- $method = 'POST';
- }
- if (session_id() != '') {
- $form_key = static::key($method, $action);
- if ($dataOnly) {
- $r[] = array(
- 'tag' => 'input',
- 'name' => static::FORM_KEY,
- 'type' => 'hidden',
- 'value' => 'hidden',
- );
- } else {
- $key = T::input()
- ->name(static::FORM_KEY)
- ->type('hidden')
- ->value($form_key);
- $r[] = $key;
- }
- }
- $s = array(
- 'tag' => 'button',
- 'type' => 'submit',
- 'label' =>
- Util::nestedValue($m, 'return', CommentParser::$embeddedDataName, 'label')
- ?: 'Submit'
- );
- if (!$dataOnly) {
- $s = Emmet::make(static::style('submit', $m), $s);
- }
- $r[] = $s;
- $t = array(
- 'action' => $restler->getBaseUrl() . '/' . rtrim($action, '/'),
- 'method' => $method,
- );
- if (static::$fileUpload) {
- static::$fileUpload = false;
- $t['enctype'] = 'multipart/form-data';
- }
- if (isset($m[CommentParser::$embeddedDataName])) {
- $t += $m[CommentParser::$embeddedDataName];
- }
- if (!$dataOnly) {
- $t = Emmet::make(static::style('form', $m), $t);
- $t->prefix = $prefix;
- $t->indent = $indent;
- $t[] = $r;
- } else {
- $t['fields'] = $r;
- }
- return $t;
- }
- public static function style($name, array $metadata, $type = '')
- {
- if (isset($metadata[CommentParser::$embeddedDataName][$name])) {
- return $metadata[CommentParser::$embeddedDataName][$name];
- }
- $style = static::$style . '::' . $name;
- $typedStyle = $style . '_' . $type;
- if (defined($typedStyle)) {
- return constant($typedStyle);
- }
- if (defined($style)) {
- return constant($style);
- }
- return null;
- }
- public static function fields($dataOnly = false)
- {
- $m = static::$info->metadata;
- $params = $m['param'];
- $values = static::$info->parameters;
- $r = array();
- foreach ($params as $k => $p) {
- $value = Util::nestedValue($values, $k);
- if (
- is_scalar($value) ||
- ($p['type'] == 'array' && is_array($value) && $value == array_values($value)) ||
- is_object($value) && $p['type'] == get_class($value)
- ) {
- $p['value'] = $value;
- }
- static::$validationInfo = $v = new ValidationInfo($p);
- if ($v->from == 'path') {
- continue;
- }
- if (!empty($v->children)) {
- $t = Emmet::make(static::style('fieldset', $m), array('label' => $v->label));
- foreach ($v->children as $n => $c) {
- $value = Util::nestedValue($v->value, $n);
- if (
- is_scalar($value) ||
- ($c['type'] == 'array' && is_array($value) && $value == array_values($value)) ||
- is_object($value) && $c['type'] == get_class($value)
- ) {
- $c['value'] = $value;
- }
- static::$validationInfo = $vc = new ValidationInfo($c);
- if ($vc->from == 'path') {
- continue;
- }
- $vc->name = $v->name . '[' . $vc->name . ']';
- $t [] = static::field($vc, $dataOnly);
- }
- $r[] = $t;
- static::$validationInfo = null;
- } else {
- $f = static::field($v, $dataOnly);
- $r [] = $f;
- }
- static::$validationInfo = null;
- }
- return $r;
- }
- /**
- * @param ValidationInfo $p
- *
- * @param bool $dataOnly
- *
- * @return array|T
- */
- public static function field(ValidationInfo $p, $dataOnly = false)
- {
- if (is_string($p->value)) {
- //prevent XSS attacks
- $p->value = htmlspecialchars($p->value, ENT_QUOTES | ENT_HTML401, 'UTF-8');
- }
- $type = $p->field ?: static::guessFieldType($p);
- $tag = in_array($type, static::$inputTypes)
- ? 'input' : $type;
- $options = array();
- $name = $p->name;
- $multiple = null;
- if ($p->type == 'array' && $p->contentType != 'associative') {
- $name .= '[]';
- $multiple = true;
- }
- if ($p->choice) {
- foreach ($p->choice as $i => $choice) {
- $option = array('name' => $name, 'value' => $choice);
- $option['text'] = isset($p->rules['select'][$i])
- ? $p->rules['select'][$i]
- : $choice;
- if ($choice == $p->value) {
- $option['selected'] = true;
- }
- $options[] = $option;
- }
- } elseif ($p->type == 'boolean' || $p->type == 'bool') {
- if (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 { //checkbox
- $r = array(
- 'tag' => $tag,
- 'name' => $name,
- 'type' => $type,
- 'label' => $p->label,
- 'value' => 'true',
- 'default' => $p->default,
- );
- $r['text'] = 'Yes';
- if ($p->default) {
- $r['selected'] = true;
- }
- if (isset($p->rules)) {
- $r += $p->rules;
- }
- }
- }
- if (empty($r)) {
- $r = array(
- 'tag' => $tag,
- 'name' => $name,
- 'type' => $type,
- 'label' => $p->label,
- 'value' => $p->value,
- 'default' => $p->default,
- 'options' => & $options,
- 'multiple' => $multiple,
- );
- if (isset($p->rules)) {
- $r += $p->rules;
- }
- }
- if ($type == 'file') {
- static::$fileUpload = true;
- 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'] = 'required';
- }
- if (isset($p->rules['autofocus'])) {
- $r['autofocus'] = 'autofocus';
- }
- /*
- echo "<pre>";
- print_r($r);
- echo "</pre>";
- */
- if ($dataOnly) {
- return $r;
- }
- if (isset($p->rules['form'])) {
- return Emmet::make($p->rules['form'], $r);
- }
- $m = static::$info->metadata;
- $t = Emmet::make(static::style($type, $m, $p->type) ?: static::style($tag, $m, $p->type), $r);
- return $t;
- }
- protected static function guessFieldType(ValidationInfo $p, $type = 'type')
- {
- if (in_array($p->$type, static::$inputTypes)) {
- return $p->$type;
- }
- if ($p->choice) {
- return $p->type == 'array' ? 'checkbox' : 'select';
- }
- switch ($p->$type) {
- case 'boolean':
- return 'radio';
- case 'int':
- case 'number':
- case 'float':
- return 'number';
- case 'array':
- return static::guessFieldType($p, 'contentType');
- }
- if ($p->name == 'password') {
- return 'password';
- }
- return 'text';
- }
- /**
- * Get the form key
- *
- * @param string $method http method for form key
- * @param string $action relative path from the web root. When set to null
- * it uses the current api method's path
- *
- * @return string generated form key
- */
- public static function key($method = 'POST', $action = null)
- {
- if (is_null($action)) {
- $action = Scope::get('Restler')->url;
- }
- $target = "$method $action";
- if (empty(static::$key[$target])) {
- static::$key[$target] = md5($target . User::getIpAddress() . uniqid(mt_rand()));
- }
- $_SESSION[static::FORM_KEY] = static::$key;
- return static::$key[$target];
- }
- /**
- * Access verification method.
- *
- * API access will be denied when this method returns false
- *
- * @return boolean true when api access is allowed false otherwise
- *
- * @throws RestException 403 security violation
- */
- public function __isAllowed()
- {
- if (session_id() == '') {
- session_start();
- }
- /** @var Restler $restler */
- $restler = $this->restler;
- $url = $restler->url;
- foreach (static::$excludedPaths as $exclude) {
- if (empty($exclude)) {
- if ($url == $exclude) {
- return true;
- }
- } elseif (Text::beginsWith($url, $exclude)) {
- return true;
- }
- }
- $check = static::$filterFormRequestsOnly
- ? $restler->requestFormat instanceof UrlEncodedFormat || $restler->requestFormat instanceof UploadFormat
- : true;
- if (!empty($_POST) && $check) {
- if (
- isset($_POST[static::FORM_KEY]) &&
- ($target = Util::getRequestMethod() . ' ' . $restler->url) &&
- isset($_SESSION[static::FORM_KEY][$target]) &&
- $_POST[static::FORM_KEY] == $_SESSION[static::FORM_KEY][$target]
- ) {
- return true;
- }
- throw new RestException(403, 'Insecure form submission');
- }
- return true;
- }
- }
|