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