cakephp2-php8/lib/Cake/View/Helper/FormHelper.php
mark_story ade9d8a811 Restore backwards compatibility with old 2.x in FormHelper.
Restore the behavior of the string 'action' option to its former glory.
While we've deprecated this it needs to continue working as it did
before.

Refs #8628
2016-05-01 22:21:23 -04:00

3104 lines
98 KiB
PHP

<?php
/**
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
* @link http://cakephp.org CakePHP(tm) Project
* @package Cake.View.Helper
* @since CakePHP(tm) v 0.10.0.1076
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
App::uses('ClassRegistry', 'Utility');
App::uses('AppHelper', 'View/Helper');
App::uses('Hash', 'Utility');
App::uses('Inflector', 'Utility');
/**
* Form helper library.
*
* Automatic generation of HTML FORMs from given data.
*
* @package Cake.View.Helper
* @property HtmlHelper $Html
* @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html
*/
class FormHelper extends AppHelper {
/**
* Other helpers used by FormHelper
*
* @var array
*/
public $helpers = array('Html');
/**
* Options used by DateTime fields
*
* @var array
*/
protected $_options = array(
'day' => array(), 'minute' => array(), 'hour' => array(),
'month' => array(), 'year' => array(), 'meridian' => array()
);
/**
* List of fields created, used with secure forms.
*
* @var array
*/
public $fields = array();
/**
* Constant used internally to skip the securing process,
* and neither add the field to the hash or to the unlocked fields.
*
* @var string
*/
const SECURE_SKIP = 'skip';
/**
* Defines the type of form being created. Set by FormHelper::create().
*
* @var string
*/
public $requestType = null;
/**
* The default model being used for the current form.
*
* @var string
*/
public $defaultModel = null;
/**
* Persistent default options used by input(). Set by FormHelper::create().
*
* @var array
*/
protected $_inputDefaults = array();
/**
* An array of field names that have been excluded from
* the Token hash used by SecurityComponent's validatePost method
*
* @see FormHelper::_secure()
* @see SecurityComponent::validatePost()
* @var array
*/
protected $_unlockedFields = array();
/**
* Holds the model references already loaded by this helper
* product of trying to inspect them out of field names
*
* @var array
*/
protected $_models = array();
/**
* Holds all the validation errors for models loaded and inspected
* it can also be set manually to be able to display custom error messages
* in the any of the input fields generated by this helper
*
* @var array
*/
public $validationErrors = array();
/**
* Holds already used DOM ID suffixes to avoid collisions with multiple form field elements.
*
* @var array
*/
protected $_domIdSuffixes = array();
/**
* The action attribute value of the last created form.
* Used to make form/request specific hashes for SecurityComponent.
*
* @var string
*/
protected $_lastAction = '';
/**
* Copies the validationErrors variable from the View object into this instance
*
* @param View $View The View this helper is being attached to.
* @param array $settings Configuration settings for the helper.
*/
public function __construct(View $View, $settings = array()) {
parent::__construct($View, $settings);
$this->validationErrors =& $View->validationErrors;
}
/**
* Guess the location for a model based on its name and tries to create a new instance
* or get an already created instance of the model
*
* @param string $model Model name.
* @return Model|null Model instance
*/
protected function _getModel($model) {
$object = null;
if (!$model || $model === 'Model') {
return $object;
}
if (array_key_exists($model, $this->_models)) {
return $this->_models[$model];
}
if (ClassRegistry::isKeySet($model)) {
$object = ClassRegistry::getObject($model);
} elseif (isset($this->request->params['models'][$model])) {
$plugin = $this->request->params['models'][$model]['plugin'];
$plugin .= ($plugin) ? '.' : null;
$object = ClassRegistry::init(array(
'class' => $plugin . $this->request->params['models'][$model]['className'],
'alias' => $model
));
} elseif (ClassRegistry::isKeySet($this->defaultModel)) {
$defaultObject = ClassRegistry::getObject($this->defaultModel);
if ($defaultObject && in_array($model, array_keys($defaultObject->getAssociated()), true) && isset($defaultObject->{$model})) {
$object = $defaultObject->{$model};
}
} else {
$object = ClassRegistry::init($model, true);
}
$this->_models[$model] = $object;
if (!$object) {
return null;
}
$this->fieldset[$model] = array('fields' => null, 'key' => $object->primaryKey, 'validates' => null);
return $object;
}
/**
* Inspects the model properties to extract information from them.
* Currently it can extract information from the the fields, the primary key and required fields
*
* The $key parameter accepts the following list of values:
*
* - key: Returns the name of the primary key for the model
* - fields: Returns the model schema
* - validates: returns the list of fields that are required
* - errors: returns the list of validation errors
*
* If the $field parameter is passed if will return the information for that sole field.
*
* `$this->_introspectModel('Post', 'fields', 'title');` will return the schema information for title column
*
* @param string $model name of the model to extract information from
* @param string $key name of the special information key to obtain (key, fields, validates, errors)
* @param string $field name of the model field to get information from
* @return mixed information extracted for the special key and field in a model
*/
protected function _introspectModel($model, $key, $field = null) {
$object = $this->_getModel($model);
if (!$object) {
return null;
}
if ($key === 'key') {
return $this->fieldset[$model]['key'] = $object->primaryKey;
}
if ($key === 'fields') {
if (!isset($this->fieldset[$model]['fields'])) {
$this->fieldset[$model]['fields'] = $object->schema();
foreach ($object->hasAndBelongsToMany as $alias => $assocData) {
$this->fieldset[$object->alias]['fields'][$alias] = array('type' => 'multiple');
}
}
if ($field === null || $field === false) {
return $this->fieldset[$model]['fields'];
} elseif (isset($this->fieldset[$model]['fields'][$field])) {
return $this->fieldset[$model]['fields'][$field];
}
return isset($object->hasAndBelongsToMany[$field]) ? array('type' => 'multiple') : null;
}
if ($key === 'errors' && !isset($this->validationErrors[$model])) {
$this->validationErrors[$model] =& $object->validationErrors;
return $this->validationErrors[$model];
} elseif ($key === 'errors' && isset($this->validationErrors[$model])) {
return $this->validationErrors[$model];
}
if ($key === 'validates' && !isset($this->fieldset[$model]['validates'])) {
$validates = array();
foreach (iterator_to_array($object->validator(), true) as $validateField => $validateProperties) {
if ($this->_isRequiredField($validateProperties)) {
$validates[$validateField] = true;
}
}
$this->fieldset[$model]['validates'] = $validates;
}
if ($key === 'validates') {
if (empty($field)) {
return $this->fieldset[$model]['validates'];
}
return isset($this->fieldset[$model]['validates'][$field]) ?
$this->fieldset[$model]['validates'] : null;
}
}
/**
* Returns if a field is required to be filled based on validation properties from the validating object.
*
* @param CakeValidationSet $validationRules Validation rules set.
* @return bool true if field is required to be filled, false otherwise
*/
protected function _isRequiredField($validationRules) {
if (empty($validationRules) || count($validationRules) === 0) {
return false;
}
$isUpdate = $this->requestType === 'put';
foreach ($validationRules as $rule) {
$rule->isUpdate($isUpdate);
if ($rule->skip()) {
continue;
}
return !$rule->allowEmpty;
}
return false;
}
/**
* Returns false if given form field described by the current entity has no errors.
* Otherwise it returns the validation message
*
* @return mixed Either false when there are no errors, or an array of error
* strings. An error string could be ''.
* @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::tagIsInvalid
*/
public function tagIsInvalid() {
$entity = $this->entity();
$model = array_shift($entity);
// 0.Model.field. Fudge entity path
if (empty($model) || is_numeric($model)) {
array_splice($entity, 1, 0, $model);
$model = array_shift($entity);
}
$errors = array();
if (!empty($entity) && isset($this->validationErrors[$model])) {
$errors = $this->validationErrors[$model];
}
if (!empty($entity) && empty($errors)) {
$errors = $this->_introspectModel($model, 'errors');
}
if (empty($errors)) {
return false;
}
$errors = Hash::get($errors, implode('.', $entity));
return $errors === null ? false : $errors;
}
/**
* Returns an HTML FORM element.
*
* ### Options:
*
* - `type` Form method defaults to POST
* - `action` The controller action the form submits to, (optional). Deprecated since 2.8, use `url`.
* - `url` The URL the form submits to. Can be a string or a URL array. If you use 'url'
* you should leave 'action' undefined.
* - `default` Allows for the creation of AJAX forms. Set this to false to prevent the default event handler.
* Will create an onsubmit attribute if it doesn't not exist. If it does, default action suppression
* will be appended.
* - `onsubmit` Used in conjunction with 'default' to create AJAX forms.
* - `inputDefaults` set the default $options for FormHelper::input(). Any options that would
* be set when using FormHelper::input() can be set here. Options set with `inputDefaults`
* can be overridden when calling input()
* - `encoding` Set the accept-charset encoding for the form. Defaults to `Configure::read('App.encoding')`
*
* @param mixed|null $model The model name for which the form is being defined. Should
* include the plugin name for plugin models. e.g. `ContactManager.Contact`.
* If an array is passed and $options argument is empty, the array will be used as options.
* If `false` no model is used.
* @param array $options An array of html attributes and options.
* @return string A formatted opening FORM tag.
* @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#options-for-create
*/
public function create($model = null, $options = array()) {
$created = $id = false;
$append = '';
if (is_array($model) && empty($options)) {
$options = $model;
$model = null;
}
if (empty($model) && $model !== false && !empty($this->request->params['models'])) {
$model = key($this->request->params['models']);
} elseif (empty($model) && empty($this->request->params['models'])) {
$model = false;
}
$this->defaultModel = $model;
$key = null;
if ($model !== false) {
list($plugin, $model) = pluginSplit($model, true);
$key = $this->_introspectModel($plugin . $model, 'key');
$this->setEntity($model, true);
}
if ($model !== false && $key) {
$recordExists = (
isset($this->request->data[$model]) &&
!empty($this->request->data[$model][$key]) &&
!is_array($this->request->data[$model][$key])
);
if ($recordExists) {
$created = true;
$id = $this->request->data[$model][$key];
}
}
$options += array(
'type' => ($created && empty($options['action'])) ? 'put' : 'post',
'action' => null,
'url' => null,
'default' => true,
'encoding' => strtolower(Configure::read('App.encoding')),
'inputDefaults' => array()
);
$this->inputDefaults($options['inputDefaults']);
unset($options['inputDefaults']);
if (isset($options['action'])) {
trigger_error('Using key `action` is deprecated, use `url` directly instead.', E_USER_DEPRECATED);
}
if (is_array($options['url']) && isset($options['url']['action'])) {
$options['action'] = $options['url']['action'];
}
if (!isset($options['id'])) {
$domId = isset($options['action']) ? $options['action'] : $this->request['action'];
$options['id'] = $this->domId($domId . 'Form');
}
if ($options['action'] === null && $options['url'] === null) {
$options['action'] = $this->request->here(false);
} elseif (empty($options['url']) || is_array($options['url'])) {
if (empty($options['url']['controller'])) {
if (!empty($model)) {
$options['url']['controller'] = Inflector::underscore(Inflector::pluralize($model));
} elseif (!empty($this->request->params['controller'])) {
$options['url']['controller'] = Inflector::underscore($this->request->params['controller']);
}
}
if (empty($options['action'])) {
$options['action'] = $this->request->params['action'];
}
$plugin = null;
if ($this->plugin) {
$plugin = Inflector::underscore($this->plugin);
}
$actionDefaults = array(
'plugin' => $plugin,
'controller' => $this->_View->viewPath,
'action' => $options['action'],
);
$options['action'] = array_merge($actionDefaults, (array)$options['url']);
if (!isset($options['action'][0]) && !empty($id)) {
$options['action'][0] = $id;
}
} elseif (is_string($options['url'])) {
$options['action'] = $options['url'];
}
switch (strtolower($options['type'])) {
case 'get':
$htmlAttributes['method'] = 'get';
break;
case 'file':
$htmlAttributes['enctype'] = 'multipart/form-data';
$options['type'] = ($created) ? 'put' : 'post';
case 'post':
case 'put':
case 'delete':
$append .= $this->hidden('_method', array(
'name' => '_method', 'value' => strtoupper($options['type']), 'id' => null,
'secure' => static::SECURE_SKIP
));
default:
$htmlAttributes['method'] = 'post';
}
$this->requestType = strtolower($options['type']);
$action = null;
if ($options['action'] !== false && $options['url'] !== false) {
$action = $this->url($options['action']);
}
unset($options['url']);
$this->_lastAction($options['action']);
unset($options['type'], $options['action']);
if (!$options['default']) {
if (!isset($options['onsubmit'])) {
$options['onsubmit'] = '';
}
$htmlAttributes['onsubmit'] = $options['onsubmit'] . 'event.returnValue = false; return false;';
}
unset($options['default']);
if (!empty($options['encoding'])) {
$htmlAttributes['accept-charset'] = $options['encoding'];
unset($options['encoding']);
}
$htmlAttributes = array_merge($options, $htmlAttributes);
$this->fields = array();
if ($this->requestType !== 'get') {
$append .= $this->_csrfField();
}
if (!empty($append)) {
$append = $this->Html->useTag('hiddenblock', $append);
}
if ($model !== false) {
$this->setEntity($model, true);
$this->_introspectModel($model, 'fields');
}
if ($action === null) {
return $this->Html->useTag('formwithoutaction', $htmlAttributes) . $append;
}
return $this->Html->useTag('form', $action, $htmlAttributes) . $append;
}
/**
* Return a CSRF input if the _Token is present.
* Used to secure forms in conjunction with SecurityComponent
*
* @return string
*/
protected function _csrfField() {
if (empty($this->request->params['_Token'])) {
return '';
}
if (!empty($this->request['_Token']['unlockedFields'])) {
foreach ((array)$this->request['_Token']['unlockedFields'] as $unlocked) {
$this->_unlockedFields[] = $unlocked;
}
}
return $this->hidden('_Token.key', array(
'value' => $this->request->params['_Token']['key'], 'id' => 'Token' . mt_rand(),
'secure' => static::SECURE_SKIP
));
}
/**
* Closes an HTML form, cleans up values set by FormHelper::create(), and writes hidden
* input fields where appropriate.
*
* If $options is set a form submit button will be created. Options can be either a string or an array.
*
* ```
* array usage:
*
* array('label' => 'save'); value="save"
* array('label' => 'save', 'name' => 'Whatever'); value="save" name="Whatever"
* array('name' => 'Whatever'); value="Submit" name="Whatever"
* array('label' => 'save', 'name' => 'Whatever', 'div' => 'good') <div class="good"> value="save" name="Whatever"
* array('label' => 'save', 'name' => 'Whatever', 'div' => array('class' => 'good')); <div class="good"> value="save" name="Whatever"
* ```
*
* If $secureAttributes is set, these html attributes will be merged into the hidden input tags generated for the
* Security Component. This is especially useful to set HTML5 attributes like 'form'
*
* @param string|array $options as a string will use $options as the value of button,
* @param array $secureAttributes will be passed as html attributes into the hidden input elements generated for the
* Security Component.
* @return string a closing FORM tag optional submit button.
* @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#closing-the-form
*/
public function end($options = null, $secureAttributes = array()) {
$out = null;
$submit = null;
if ($options !== null) {
$submitOptions = array();
if (is_string($options)) {
$submit = $options;
} else {
if (isset($options['label'])) {
$submit = $options['label'];
unset($options['label']);
}
$submitOptions = $options;
}
$out .= $this->submit($submit, $submitOptions);
}
if ($this->requestType !== 'get' &&
isset($this->request['_Token']) &&
!empty($this->request['_Token'])
) {
$out .= $this->secure($this->fields, $secureAttributes);
$this->fields = array();
}
$this->setEntity(null);
$out .= $this->Html->useTag('formend');
$this->_View->modelScope = false;
$this->requestType = null;
return $out;
}
/**
* Generates a hidden field with a security hash based on the fields used in
* the form.
*
* If $secureAttributes is set, these html attributes will be merged into
* the hidden input tags generated for the Security Component. This is
* especially useful to set HTML5 attributes like 'form'.
*
* @param array|null $fields If set specifies the list of fields to use when
* generating the hash, else $this->fields is being used.
* @param array $secureAttributes will be passed as html attributes into the hidden
* input elements generated for the Security Component.
* @return string|null A hidden input field with a security hash, otherwise null.
* @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::secure
*/
public function secure($fields = array(), $secureAttributes = array()) {
if (!isset($this->request['_Token']) || empty($this->request['_Token'])) {
return null;
}
$locked = array();
$unlockedFields = $this->_unlockedFields;
foreach ($fields as $key => $value) {
if (!is_int($key)) {
$locked[$key] = $value;
unset($fields[$key]);
}
}
sort($unlockedFields, SORT_STRING);
sort($fields, SORT_STRING);
ksort($locked, SORT_STRING);
$fields += $locked;
$locked = implode(array_keys($locked), '|');
$unlocked = implode($unlockedFields, '|');
$hashParts = array(
$this->_lastAction,
serialize($fields),
$unlocked,
Configure::read('Security.salt')
);
$fields = Security::hash(implode('', $hashParts), 'sha1');
$tokenFields = array_merge($secureAttributes, array(
'value' => urlencode($fields . ':' . $locked),
'id' => 'TokenFields' . mt_rand(),
'secure' => static::SECURE_SKIP,
));
$out = $this->hidden('_Token.fields', $tokenFields);
$tokenUnlocked = array_merge($secureAttributes, array(
'value' => urlencode($unlocked),
'id' => 'TokenUnlocked' . mt_rand(),
'secure' => static::SECURE_SKIP,
));
$out .= $this->hidden('_Token.unlocked', $tokenUnlocked);
return $this->Html->useTag('hiddenblock', $out);
}
/**
* Add to or get the list of fields that are currently unlocked.
* Unlocked fields are not included in the field hash used by SecurityComponent
* unlocking a field once its been added to the list of secured fields will remove
* it from the list of fields.
*
* @param string $name The dot separated name for the field.
* @return mixed Either null, or the list of fields.
* @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::unlockField
*/
public function unlockField($name = null) {
if ($name === null) {
return $this->_unlockedFields;
}
if (!in_array($name, $this->_unlockedFields)) {
$this->_unlockedFields[] = $name;
}
$index = array_search($name, $this->fields);
if ($index !== false) {
unset($this->fields[$index]);
}
unset($this->fields[$name]);
}
/**
* Determine which fields of a form should be used for hash.
* Populates $this->fields
*
* @param bool $lock Whether this field should be part of the validation
* or excluded as part of the unlockedFields.
* @param string|array $field Reference to field to be secured. Should be dot separated to indicate nesting.
* @param mixed $value Field value, if value should not be tampered with.
* @return void
*/
protected function _secure($lock, $field = null, $value = null) {
if (!$field) {
$field = $this->entity();
} elseif (is_string($field)) {
$field = Hash::filter(explode('.', $field));
}
foreach ($this->_unlockedFields as $unlockField) {
$unlockParts = explode('.', $unlockField);
if (array_values(array_intersect($field, $unlockParts)) === $unlockParts) {
return;
}
}
$field = implode('.', $field);
$field = preg_replace('/(\.\d+)+$/', '', $field);
if ($lock) {
if (!in_array($field, $this->fields)) {
if ($value !== null) {
return $this->fields[$field] = $value;
} elseif (isset($this->fields[$field]) && $value === null) {
unset($this->fields[$field]);
}
$this->fields[] = $field;
}
} else {
$this->unlockField($field);
}
}
/**
* Returns true if there is an error for the given field, otherwise false
*
* @param string $field This should be "Modelname.fieldname"
* @return bool If there are errors this method returns true, else false.
* @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::isFieldError
*/
public function isFieldError($field) {
$this->setEntity($field);
return (bool)$this->tagIsInvalid();
}
/**
* Returns a formatted error message for given FORM field, NULL if no errors.
*
* ### Options:
*
* - `escape` boolean - Whether or not to html escape the contents of the error.
* - `wrap` mixed - Whether or not the error message should be wrapped in a div. If a
* string, will be used as the HTML tag to use.
* - `class` string - The class name for the error message
*
* @param string $field A field name, like "Modelname.fieldname"
* @param string|array $text Error message as string or array of messages.
* If array contains `attributes` key it will be used as options for error container
* @param array $options Rendering options for <div /> wrapper tag
* @return string|null If there are errors this method returns an error message, otherwise null.
* @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::error
*/
public function error($field, $text = null, $options = array()) {
$defaults = array('wrap' => true, 'class' => 'error-message', 'escape' => true);
$options += $defaults;
$this->setEntity($field);
$error = $this->tagIsInvalid();
if ($error === false) {
return null;
}
if (is_array($text)) {
if (isset($text['attributes']) && is_array($text['attributes'])) {
$options = array_merge($options, $text['attributes']);
unset($text['attributes']);
}
$tmp = array();
foreach ($error as &$e) {
if (isset($text[$e])) {
$tmp[] = $text[$e];
} else {
$tmp[] = $e;
}
}
$text = $tmp;
}
if ($text !== null) {
$error = $text;
}
if (is_array($error)) {
foreach ($error as &$e) {
if (is_numeric($e)) {
$e = __d('cake', 'Error in field %s', Inflector::humanize($this->field()));
}
}
}
if ($options['escape']) {
$error = h($error);
unset($options['escape']);
}
if (is_array($error)) {
if (count($error) > 1) {
$listParams = array();
if (isset($options['listOptions'])) {
if (is_string($options['listOptions'])) {
$listParams[] = $options['listOptions'];
} else {
if (isset($options['listOptions']['itemOptions'])) {
$listParams[] = $options['listOptions']['itemOptions'];
unset($options['listOptions']['itemOptions']);
} else {
$listParams[] = array();
}
if (isset($options['listOptions']['tag'])) {
$listParams[] = $options['listOptions']['tag'];
unset($options['listOptions']['tag']);
}
array_unshift($listParams, $options['listOptions']);
}
unset($options['listOptions']);
}
array_unshift($listParams, $error);
$error = call_user_func_array(array($this->Html, 'nestedList'), $listParams);
} else {
$error = array_pop($error);
}
}
if ($options['wrap']) {
$tag = is_string($options['wrap']) ? $options['wrap'] : 'div';
unset($options['wrap']);
return $this->Html->tag($tag, $error, $options);
}
return $error;
}
/**
* Returns a formatted LABEL element for HTML FORMs. Will automatically generate
* a `for` attribute if one is not provided.
*
* ### Options
*
* - `for` - Set the for attribute, if its not defined the for attribute
* will be generated from the $fieldName parameter using
* FormHelper::domId().
*
* Examples:
*
* The text and for attribute are generated off of the fieldname
*
* ```
* echo $this->Form->label('Post.published');
* <label for="PostPublished">Published</label>
* ```
*
* Custom text:
*
* ```
* echo $this->Form->label('Post.published', 'Publish');
* <label for="PostPublished">Publish</label>
* ```
*
* Custom class name:
*
* ```
* echo $this->Form->label('Post.published', 'Publish', 'required');
* <label for="PostPublished" class="required">Publish</label>
* ```
*
* Custom attributes:
*
* ```
* echo $this->Form->label('Post.published', 'Publish', array(
* 'for' => 'post-publish'
* ));
* <label for="post-publish">Publish</label>
* ```
*
* *Warning* Unlike most FormHelper methods, this method does not automatically
* escape the $text parameter. You must escape the $text parameter yourself if you
* are using user supplied data.
*
* @param string $fieldName This should be "Modelname.fieldname"
* @param string $text Text that will appear in the label field. If
* $text is left undefined the text will be inflected from the
* fieldName.
* @param array|string $options An array of HTML attributes, or a string, to be used as a class name.
* @return string The formatted LABEL element
* @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::label
*/
public function label($fieldName = null, $text = null, $options = array()) {
if ($fieldName === null) {
$fieldName = implode('.', $this->entity());
}
if ($text === null) {
if (strpos($fieldName, '.') !== false) {
$fieldElements = explode('.', $fieldName);
$text = array_pop($fieldElements);
} else {
$text = $fieldName;
}
if (substr($text, -3) === '_id') {
$text = substr($text, 0, -3);
}
$text = __(Inflector::humanize(Inflector::underscore($text)));
}
if (is_string($options)) {
$options = array('class' => $options);
}
if (isset($options['for'])) {
$labelFor = $options['for'];
unset($options['for']);
} else {
$labelFor = $this->domId($fieldName);
}
return $this->Html->useTag('label', $labelFor, $options, $text);
}
/**
* Generate a set of inputs for `$fields`. If $fields is null the fields of current model
* will be used.
*
* You can customize individual inputs through `$fields`.
* ```
* $this->Form->inputs(array(
* 'name' => array('label' => 'custom label')
* ));
* ```
*
* In addition to controller fields output, `$fields` can be used to control legend
* and fieldset rendering.
* `$this->Form->inputs('My legend');` Would generate an input set with a custom legend.
* Passing `fieldset` and `legend` key in `$fields` array has been deprecated since 2.3,
* for more fine grained control use the `fieldset` and `legend` keys in `$options` param.
*
* @param array $fields An array of fields to generate inputs for, or null.
* @param array $blacklist A simple array of fields to not create inputs for.
* @param array $options Options array. Valid keys are:
* - `fieldset` Set to false to disable the fieldset. If a string is supplied it will be used as
* the class name for the fieldset element.
* - `legend` Set to false to disable the legend for the generated input set. Or supply a string
* to customize the legend text.
* @return string Completed form inputs.
* @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::inputs
*/
public function inputs($fields = null, $blacklist = null, $options = array()) {
$fieldset = $legend = true;
$modelFields = array();
$model = $this->model();
if ($model) {
$modelFields = array_keys((array)$this->_introspectModel($model, 'fields'));
}
if (is_array($fields)) {
if (array_key_exists('legend', $fields) && !in_array('legend', $modelFields)) {
$legend = $fields['legend'];
unset($fields['legend']);
}
if (isset($fields['fieldset']) && !in_array('fieldset', $modelFields)) {
$fieldset = $fields['fieldset'];
unset($fields['fieldset']);
}
} elseif ($fields !== null) {
$fieldset = $legend = $fields;
if (!is_bool($fieldset)) {
$fieldset = true;
}
$fields = array();
}
if (isset($options['legend'])) {
$legend = $options['legend'];
}
if (isset($options['fieldset'])) {
$fieldset = $options['fieldset'];
}
if (empty($fields)) {
$fields = $modelFields;
}
if ($legend === true) {
$actionName = __d('cake', 'New %s');
$isEdit = (
strpos($this->request->params['action'], 'update') !== false ||
strpos($this->request->params['action'], 'edit') !== false
);
if ($isEdit) {
$actionName = __d('cake', 'Edit %s');
}
$modelName = Inflector::humanize(Inflector::underscore($model));
$legend = sprintf($actionName, __($modelName));
}
$out = null;
foreach ($fields as $name => $options) {
if (is_numeric($name) && !is_array($options)) {
$name = $options;
$options = array();
}
$entity = explode('.', $name);
$blacklisted = (
is_array($blacklist) &&
(in_array($name, $blacklist) || in_array(end($entity), $blacklist))
);
if ($blacklisted) {
continue;
}
$out .= $this->input($name, $options);
}
if (is_string($fieldset)) {
$fieldsetClass = sprintf(' class="%s"', $fieldset);
} else {
$fieldsetClass = '';
}
if ($fieldset) {
if ($legend) {
$out = $this->Html->useTag('legend', $legend) . $out;
}
$out = $this->Html->useTag('fieldset', $fieldsetClass, $out);
}
return $out;
}
/**
* Generates a form input element complete with label and wrapper div
*
* ### Options
*
* See each field type method for more information. Any options that are part of
* $attributes or $options for the different **type** methods can be included in `$options` for input().i
* Additionally, any unknown keys that are not in the list below, or part of the selected type's options
* will be treated as a regular html attribute for the generated input.
*
* - `type` - Force the type of widget you want. e.g. `type => 'select'`
* - `label` - Either a string label, or an array of options for the label. See FormHelper::label().
* - `div` - Either `false` to disable the div, or an array of options for the div.
* See HtmlHelper::div() for more options.
* - `options` - For widgets that take options e.g. radio, select.
* - `error` - Control the error message that is produced. Set to `false` to disable any kind of error reporting (field
* error and error messages).
* - `errorMessage` - Boolean to control rendering error messages (field error will still occur).
* - `empty` - String or boolean to enable empty select box options.
* - `before` - Content to place before the label + input.
* - `after` - Content to place after the label + input.
* - `between` - Content to place between the label + input.
* - `format` - Format template for element order. Any element that is not in the array, will not be in the output.
* - Default input format order: array('before', 'label', 'between', 'input', 'after', 'error')
* - Default checkbox format order: array('before', 'input', 'between', 'label', 'after', 'error')
* - Hidden input will not be formatted
* - Radio buttons cannot have the order of input and label elements controlled with these settings.
*
* @param string $fieldName This should be "Modelname.fieldname"
* @param array $options Each type of input takes different options.
* @return string Completed form widget.
* @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#creating-form-elements
*/
public function input($fieldName, $options = array()) {
$this->setEntity($fieldName);
$options = $this->_parseOptions($options);
$divOptions = $this->_divOptions($options);
unset($options['div']);
if ($options['type'] === 'radio' && isset($options['options'])) {
$radioOptions = (array)$options['options'];
unset($options['options']);
}
$label = $this->_getLabel($fieldName, $options);
if ($options['type'] !== 'radio') {
unset($options['label']);
}
$error = $this->_extractOption('error', $options, null);
unset($options['error']);
$errorMessage = $this->_extractOption('errorMessage', $options, true);
unset($options['errorMessage']);
$selected = $this->_extractOption('selected', $options, null);
unset($options['selected']);
if ($options['type'] === 'datetime' || $options['type'] === 'date' || $options['type'] === 'time') {
$dateFormat = $this->_extractOption('dateFormat', $options, 'MDY');
$timeFormat = $this->_extractOption('timeFormat', $options, 12);
unset($options['dateFormat'], $options['timeFormat']);
}
$type = $options['type'];
$out = array('before' => $options['before'], 'label' => $label, 'between' => $options['between'], 'after' => $options['after']);
$format = $this->_getFormat($options);
unset($options['type'], $options['before'], $options['between'], $options['after'], $options['format']);
$out['error'] = null;
if ($type !== 'hidden' && $error !== false) {
$errMsg = $this->error($fieldName, $error);
if ($errMsg) {
$divOptions = $this->addClass($divOptions, 'error');
if ($errorMessage) {
$out['error'] = $errMsg;
}
}
}
if ($type === 'radio' && isset($out['between'])) {
$options['between'] = $out['between'];
$out['between'] = null;
}
$out['input'] = $this->_getInput(compact('type', 'fieldName', 'options', 'radioOptions', 'selected', 'dateFormat', 'timeFormat'));
$output = '';
foreach ($format as $element) {
$output .= $out[$element];
}
if (!empty($divOptions['tag'])) {
$tag = $divOptions['tag'];
unset($divOptions['tag']);
$output = $this->Html->tag($tag, $output, $divOptions);
}
return $output;
}
/**
* Generates an input element
*
* @param array $args The options for the input element
* @return string The generated input element
*/
protected function _getInput($args) {
extract($args);
switch ($type) {
case 'hidden':
return $this->hidden($fieldName, $options);
case 'checkbox':
return $this->checkbox($fieldName, $options);
case 'radio':
return $this->radio($fieldName, $radioOptions, $options);
case 'file':
return $this->file($fieldName, $options);
case 'select':
$options += array('options' => array(), 'value' => $selected);
$list = $options['options'];
unset($options['options']);
return $this->select($fieldName, $list, $options);
case 'time':
$options += array('value' => $selected);
return $this->dateTime($fieldName, null, $timeFormat, $options);
case 'date':
$options += array('value' => $selected);
return $this->dateTime($fieldName, $dateFormat, null, $options);
case 'datetime':
$options += array('value' => $selected);
return $this->dateTime($fieldName, $dateFormat, $timeFormat, $options);
case 'textarea':
return $this->textarea($fieldName, $options + array('cols' => '30', 'rows' => '6'));
case 'url':
return $this->text($fieldName, array('type' => 'url') + $options);
default:
return $this->{$type}($fieldName, $options);
}
}
/**
* Generates input options array
*
* @param array $options Options list.
* @return array Options
*/
protected function _parseOptions($options) {
$options = array_merge(
array('before' => null, 'between' => null, 'after' => null, 'format' => null),
$this->_inputDefaults,
$options
);
if (!isset($options['type'])) {
$options = $this->_magicOptions($options);
}
if (in_array($options['type'], array('radio', 'select'))) {
$options = $this->_optionsOptions($options);
}
$options = $this->_maxLength($options);
if (isset($options['rows']) || isset($options['cols'])) {
$options['type'] = 'textarea';
}
if ($options['type'] === 'datetime' || $options['type'] === 'date' || $options['type'] === 'time' || $options['type'] === 'select') {
$options += array('empty' => false);
}
return $options;
}
/**
* Generates list of options for multiple select
*
* @param array $options Options list.
* @return array
*/
protected function _optionsOptions($options) {
if (isset($options['options'])) {
return $options;
}
$varName = Inflector::variable(
Inflector::pluralize(preg_replace('/_id$/', '', $this->field()))
);
$varOptions = $this->_View->get($varName);
if (!is_array($varOptions)) {
return $options;
}
if ($options['type'] !== 'radio') {
$options['type'] = 'select';
}
$options['options'] = $varOptions;
return $options;
}
/**
* Magically set option type and corresponding options
*
* @param array $options Options list.
* @return array
*/
protected function _magicOptions($options) {
$modelKey = $this->model();
$fieldKey = $this->field();
$options['type'] = 'text';
if (isset($options['options'])) {
$options['type'] = 'select';
} elseif (in_array($fieldKey, array('psword', 'passwd', 'password'))) {
$options['type'] = 'password';
} elseif (in_array($fieldKey, array('tel', 'telephone', 'phone'))) {
$options['type'] = 'tel';
} elseif ($fieldKey === 'email') {
$options['type'] = 'email';
} elseif (isset($options['checked'])) {
$options['type'] = 'checkbox';
} elseif ($fieldDef = $this->_introspectModel($modelKey, 'fields', $fieldKey)) {
$type = $fieldDef['type'];
$primaryKey = $this->fieldset[$modelKey]['key'];
$map = array(
'string' => 'text', 'datetime' => 'datetime',
'boolean' => 'checkbox', 'timestamp' => 'datetime',
'text' => 'textarea', 'time' => 'time',
'date' => 'date', 'float' => 'number',
'integer' => 'number', 'decimal' => 'number',
'binary' => 'file'
);
if (isset($this->map[$type])) {
$options['type'] = $this->map[$type];
} elseif (isset($map[$type])) {
$options['type'] = $map[$type];
}
if ($fieldKey === $primaryKey) {
$options['type'] = 'hidden';
}
if ($options['type'] === 'number' &&
!isset($options['step'])
) {
if ($type === 'decimal' && isset($fieldDef['length'])) {
$decimalPlaces = substr($fieldDef['length'], strpos($fieldDef['length'], ',') + 1);
$options['step'] = sprintf('%.' . $decimalPlaces . 'F', pow(10, -1 * $decimalPlaces));
} elseif ($type === 'float' || $type === 'decimal') {
$options['step'] = 'any';
}
}
}
if (preg_match('/_id$/', $fieldKey) && $options['type'] !== 'hidden') {
$options['type'] = 'select';
}
if ($modelKey === $fieldKey) {
$options['type'] = 'select';
if (!isset($options['multiple'])) {
$options['multiple'] = 'multiple';
}
}
if (in_array($options['type'], array('text', 'number'))) {
$options = $this->_optionsOptions($options);
}
if ($options['type'] === 'select' && array_key_exists('step', $options)) {
unset($options['step']);
}
return $options;
}
/**
* Generate format options
*
* @param array $options Options list.
* @return array
*/
protected function _getFormat($options) {
if ($options['type'] === 'hidden') {
return array('input');
}
if (is_array($options['format']) && in_array('input', $options['format'])) {
return $options['format'];
}
if ($options['type'] === 'checkbox') {
return array('before', 'input', 'between', 'label', 'after', 'error');
}
return array('before', 'label', 'between', 'input', 'after', 'error');
}
/**
* Generate label for input
*
* @param string $fieldName Field name.
* @param array $options Options list.
* @return bool|string false or Generated label element
*/
protected function _getLabel($fieldName, $options) {
if ($options['type'] === 'radio') {
return false;
}
$label = null;
if (isset($options['label'])) {
$label = $options['label'];
}
if ($label === false) {
return false;
}
return $this->_inputLabel($fieldName, $label, $options);
}
/**
* Calculates maxlength option
*
* @param array $options Options list.
* @return array
*/
protected function _maxLength($options) {
$fieldDef = $this->_introspectModel($this->model(), 'fields', $this->field());
$autoLength = (
!array_key_exists('maxlength', $options) &&
isset($fieldDef['length']) &&
is_scalar($fieldDef['length']) &&
$fieldDef['length'] < 1000000 &&
$fieldDef['type'] !== 'decimal' &&
$options['type'] !== 'select'
);
if ($autoLength &&
in_array($options['type'], array('text', 'textarea', 'email', 'tel', 'url', 'search'))
) {
$options['maxlength'] = (int)$fieldDef['length'];
}
return $options;
}
/**
* Generate div options for input
*
* @param array $options Options list.
* @return array
*/
protected function _divOptions($options) {
if ($options['type'] === 'hidden') {
return array();
}
$div = $this->_extractOption('div', $options, true);
if (!$div) {
return array();
}
$divOptions = array('class' => 'input');
$divOptions = $this->addClass($divOptions, $options['type']);
if (is_string($div)) {
$divOptions['class'] = $div;
} elseif (is_array($div)) {
$divOptions = array_merge($divOptions, $div);
}
if ($this->_extractOption('required', $options) !== false &&
$this->_introspectModel($this->model(), 'validates', $this->field())
) {
$divOptions = $this->addClass($divOptions, 'required');
}
if (!isset($divOptions['tag'])) {
$divOptions['tag'] = 'div';
}
return $divOptions;
}
/**
* Extracts a single option from an options array.
*
* @param string $name The name of the option to pull out.
* @param array $options The array of options you want to extract.
* @param mixed $default The default option value
* @return mixed the contents of the option or default
*/
protected function _extractOption($name, $options, $default = null) {
if (array_key_exists($name, $options)) {
return $options[$name];
}
return $default;
}
/**
* Generate a label for an input() call.
*
* $options can contain a hash of id overrides. These overrides will be
* used instead of the generated values if present.
*
* @param string $fieldName Field name.
* @param string|array $label Label text or array with text and options.
* @param array $options Options for the label element. 'NONE' option is
* deprecated and will be removed in 3.0
* @return string Generated label element
*/
protected function _inputLabel($fieldName, $label, $options) {
$labelAttributes = $this->domId(array(), 'for');
$idKey = null;
if ($options['type'] === 'date' || $options['type'] === 'datetime') {
$firstInput = 'M';
if (array_key_exists('dateFormat', $options) &&
($options['dateFormat'] === null || $options['dateFormat'] === 'NONE')
) {
$firstInput = 'H';
} elseif (!empty($options['dateFormat'])) {
$firstInput = substr($options['dateFormat'], 0, 1);
}
switch ($firstInput) {
case 'D':
$idKey = 'day';
$labelAttributes['for'] .= 'Day';
break;
case 'Y':
$idKey = 'year';
$labelAttributes['for'] .= 'Year';
break;
case 'M':
$idKey = 'month';
$labelAttributes['for'] .= 'Month';
break;
case 'H':
$idKey = 'hour';
$labelAttributes['for'] .= 'Hour';
}
}
if ($options['type'] === 'time') {
$labelAttributes['for'] .= 'Hour';
$idKey = 'hour';
}
if (isset($idKey) && isset($options['id']) && isset($options['id'][$idKey])) {
$labelAttributes['for'] = $options['id'][$idKey];
}
if (is_array($label)) {
$labelText = null;
if (isset($label['text'])) {
$labelText = $label['text'];
unset($label['text']);
}
$labelAttributes = array_merge($labelAttributes, $label);
} else {
$labelText = $label;
}
if (isset($options['id']) && is_string($options['id'])) {
$labelAttributes = array_merge($labelAttributes, array('for' => $options['id']));
}
return $this->label($fieldName, $labelText, $labelAttributes);
}
/**
* Creates a checkbox input widget.
*
* ### Options:
*
* - `value` - the value of the checkbox
* - `checked` - boolean indicate that this checkbox is checked.
* - `hiddenField` - boolean to indicate if you want the results of checkbox() to include
* a hidden input with a value of ''.
* - `disabled` - create a disabled input.
* - `default` - Set the default value for the checkbox. This allows you to start checkboxes
* as checked, without having to check the POST data. A matching POST data value, will overwrite
* the default value.
*
* @param string $fieldName Name of a field, like this "Modelname.fieldname"
* @param array $options Array of HTML attributes.
* @return string An HTML text input element.
* @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#options-for-select-checkbox-and-radio-inputs
*/
public function checkbox($fieldName, $options = array()) {
$valueOptions = array();
if (isset($options['default'])) {
$valueOptions['default'] = $options['default'];
unset($options['default']);
}
$options += array('value' => 1, 'required' => false);
$options = $this->_initInputField($fieldName, $options) + array('hiddenField' => true);
$value = current($this->value($valueOptions));
$output = '';
if ((!isset($options['checked']) && !empty($value) && $value == $options['value']) ||
!empty($options['checked'])
) {
$options['checked'] = 'checked';
}
if ($options['hiddenField']) {
$hiddenOptions = array(
'id' => $options['id'] . '_',
'name' => $options['name'],
'value' => ($options['hiddenField'] !== true ? $options['hiddenField'] : '0'),
'form' => isset($options['form']) ? $options['form'] : null,
'secure' => false,
);
if (isset($options['disabled']) && $options['disabled']) {
$hiddenOptions['disabled'] = 'disabled';
}
$output = $this->hidden($fieldName, $hiddenOptions);
}
unset($options['hiddenField']);
return $output . $this->Html->useTag('checkbox', $options['name'], array_diff_key($options, array('name' => null)));
}
/**
* Creates a set of radio widgets. Will create a legend and fieldset
* by default. Use $options to control this
*
* You can also customize each radio input element using an array of arrays:
*
* ```
* $options = array(
* array('name' => 'United states', 'value' => 'US', 'title' => 'My title'),
* array('name' => 'Germany', 'value' => 'DE', 'class' => 'de-de', 'title' => 'Another title'),
* );
* ```
*
* ### Attributes:
*
* - `separator` - define the string in between the radio buttons
* - `between` - the string between legend and input set or array of strings to insert
* strings between each input block
* - `legend` - control whether or not the widget set has a fieldset & legend
* - `value` - indicate a value that is should be checked
* - `label` - boolean to indicate whether or not labels for widgets show be displayed
* - `hiddenField` - boolean to indicate if you want the results of radio() to include
* a hidden input with a value of ''. This is useful for creating radio sets that non-continuous
* - `disabled` - Set to `true` or `disabled` to disable all the radio buttons.
* - `empty` - Set to `true` to create an input with the value '' as the first option. When `true`
* the radio label will be 'empty'. Set this option to a string to control the label value.
*
* @param string $fieldName Name of a field, like this "Modelname.fieldname"
* @param array $options Radio button options array.
* @param array $attributes Array of HTML attributes, and special attributes above.
* @return string Completed radio widget set.
* @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#options-for-select-checkbox-and-radio-inputs
*/
public function radio($fieldName, $options = array(), $attributes = array()) {
$attributes['options'] = $options;
$attributes = $this->_initInputField($fieldName, $attributes);
unset($attributes['options']);
$showEmpty = $this->_extractOption('empty', $attributes);
if ($showEmpty) {
$showEmpty = ($showEmpty === true) ? __d('cake', 'empty') : $showEmpty;
$options = array('' => $showEmpty) + $options;
}
unset($attributes['empty']);
$legend = false;
if (isset($attributes['legend'])) {
$legend = $attributes['legend'];
unset($attributes['legend']);
} elseif (count($options) > 1) {
$legend = __(Inflector::humanize($this->field()));
}
$label = true;
if (isset($attributes['label'])) {
$label = $attributes['label'];
unset($attributes['label']);
}
$separator = null;
if (isset($attributes['separator'])) {
$separator = $attributes['separator'];
unset($attributes['separator']);
}
$between = null;
if (isset($attributes['between'])) {
$between = $attributes['between'];
unset($attributes['between']);
}
$value = null;
if (isset($attributes['value'])) {
$value = $attributes['value'];
} else {
$value = $this->value($fieldName);
}
$disabled = array();
if (isset($attributes['disabled'])) {
$disabled = $attributes['disabled'];
}
$out = array();
$hiddenField = isset($attributes['hiddenField']) ? $attributes['hiddenField'] : true;
unset($attributes['hiddenField']);
if (isset($value) && is_bool($value)) {
$value = $value ? 1 : 0;
}
$this->_domIdSuffixes = array();
foreach ($options as $optValue => $optTitle) {
$optionsHere = array('value' => $optValue, 'disabled' => false);
if (is_array($optTitle)) {
if (isset($optTitle['value'])) {
$optionsHere['value'] = $optTitle['value'];
}
$optionsHere += $optTitle;
$optTitle = $optionsHere['name'];
unset($optionsHere['name']);
}
if (isset($value) && strval($optValue) === strval($value)) {
$optionsHere['checked'] = 'checked';
}
$isNumeric = is_numeric($optValue);
if ($disabled && (!is_array($disabled) || in_array((string)$optValue, $disabled, !$isNumeric))) {
$optionsHere['disabled'] = true;
}
$tagName = $attributes['id'] . $this->domIdSuffix($optValue);
if ($label) {
$labelOpts = is_array($label) ? $label : array();
$labelOpts += array('for' => $tagName);
$optTitle = $this->label($tagName, $optTitle, $labelOpts);
}
if (is_array($between)) {
$optTitle .= array_shift($between);
}
$allOptions = $optionsHere + $attributes;
$out[] = $this->Html->useTag('radio', $attributes['name'], $tagName,
array_diff_key($allOptions, array('name' => null, 'type' => null, 'id' => null)),
$optTitle
);
}
$hidden = null;
if ($hiddenField) {
if (!isset($value) || $value === '') {
$hidden = $this->hidden($fieldName, array(
'form' => isset($attributes['form']) ? $attributes['form'] : null,
'id' => $attributes['id'] . '_',
'value' => '',
'name' => $attributes['name']
));
}
}
$out = $hidden . implode($separator, $out);
if (is_array($between)) {
$between = '';
}
if ($legend) {
$out = $this->Html->useTag('fieldset', '', $this->Html->useTag('legend', $legend) . $between . $out);
}
return $out;
}
/**
* Missing method handler - implements various simple input types. Is used to create inputs
* of various types. e.g. `$this->Form->text();` will create `<input type="text" />` while
* `$this->Form->range();` will create `<input type="range" />`
*
* ### Usage
*
* `$this->Form->search('User.query', array('value' => 'test'));`
*
* Will make an input like:
*
* `<input type="search" id="UserQuery" name="data[User][query]" value="test" />`
*
* The first argument to an input type should always be the fieldname, in `Model.field` format.
* The second argument should always be an array of attributes for the input.
*
* @param string $method Method name / input type to make.
* @param array $params Parameters for the method call
* @return string Formatted input method.
* @throws CakeException When there are no params for the method call.
*/
public function __call($method, $params) {
$options = array();
if (empty($params)) {
throw new CakeException(__d('cake_dev', 'Missing field name for FormHelper::%s', $method));
}
if (isset($params[1])) {
$options = $params[1];
}
if (!isset($options['type'])) {
$options['type'] = $method;
}
$options = $this->_initInputField($params[0], $options);
return $this->Html->useTag('input', $options['name'], array_diff_key($options, array('name' => null)));
}
/**
* Creates a textarea widget.
*
* ### Options:
*
* - `escape` - Whether or not the contents of the textarea should be escaped. Defaults to true.
*
* @param string $fieldName Name of a field, in the form "Modelname.fieldname"
* @param array $options Array of HTML attributes, and special options above.
* @return string A generated HTML text input element
* @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::textarea
*/
public function textarea($fieldName, $options = array()) {
$options = $this->_initInputField($fieldName, $options);
$value = null;
if (array_key_exists('value', $options)) {
$value = $options['value'];
if (!array_key_exists('escape', $options) || $options['escape'] !== false) {
$value = h($value);
}
unset($options['value']);
}
return $this->Html->useTag('textarea', $options['name'], array_diff_key($options, array('type' => null, 'name' => null)), $value);
}
/**
* Creates a hidden input field.
*
* @param string $fieldName Name of a field, in the form of "Modelname.fieldname"
* @param array $options Array of HTML attributes.
* @return string A generated hidden input
* @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::hidden
*/
public function hidden($fieldName, $options = array()) {
$options += array('required' => false, 'secure' => true);
$secure = $options['secure'];
unset($options['secure']);
$options = $this->_initInputField($fieldName, array_merge(
$options, array('secure' => static::SECURE_SKIP)
));
if ($secure === true) {
$this->_secure(true, null, '' . $options['value']);
}
return $this->Html->useTag('hidden', $options['name'], array_diff_key($options, array('name' => null)));
}
/**
* Creates file input widget.
*
* @param string $fieldName Name of a field, in the form "Modelname.fieldname"
* @param array $options Array of HTML attributes.
* @return string A generated file input.
* @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::file
*/
public function file($fieldName, $options = array()) {
$options += array('secure' => true);
$secure = $options['secure'];
$options['secure'] = static::SECURE_SKIP;
$options = $this->_initInputField($fieldName, $options);
$field = $this->entity();
foreach (array('name', 'type', 'tmp_name', 'error', 'size') as $suffix) {
$this->_secure($secure, array_merge($field, array($suffix)));
}
$exclude = array('name' => null, 'value' => null);
return $this->Html->useTag('file', $options['name'], array_diff_key($options, $exclude));
}
/**
* Creates a `<button>` tag. The type attribute defaults to `type="submit"`
* You can change it to a different value by using `$options['type']`.
*
* ### Options:
*
* - `escape` - HTML entity encode the $title of the button. Defaults to false.
*
* @param string $title The button's caption. Not automatically HTML encoded
* @param array $options Array of options and HTML attributes.
* @return string A HTML button tag.
* @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::button
*/
public function button($title, $options = array()) {
$options += array('type' => 'submit', 'escape' => false, 'secure' => false);
if ($options['escape']) {
$title = h($title);
}
if (isset($options['name'])) {
$name = str_replace(array('[', ']'), array('.', ''), $options['name']);
$this->_secure($options['secure'], $name);
}
return $this->Html->useTag('button', $options, $title);
}
/**
* Create a `<button>` tag with a surrounding `<form>` that submits via POST.
*
* This method creates a `<form>` element. So do not use this method in an already opened form.
* Instead use FormHelper::submit() or FormHelper::button() to create buttons inside opened forms.
*
* ### Options:
*
* - `data` - Array with key/value to pass in input hidden
* - Other options is the same of button method.
*
* @param string $title The button's caption. Not automatically HTML encoded
* @param string|array $url URL as string or array
* @param array $options Array of options and HTML attributes.
* @return string A HTML button tag.
* @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::postButton
*/
public function postButton($title, $url, $options = array()) {
$out = $this->create(false, array('id' => false, 'url' => $url));
if (isset($options['data']) && is_array($options['data'])) {
foreach (Hash::flatten($options['data']) as $key => $value) {
$out .= $this->hidden($key, array('value' => $value, 'id' => false));
}
unset($options['data']);
}
$out .= $this->button($title, $options);
$out .= $this->end();
return $out;
}
/**
* Creates an HTML link, but access the URL using the method you specify (defaults to POST).
* Requires javascript to be enabled in browser.
*
* This method creates a `<form>` element. If you want to use this method inside of an
* existing form, you must use the `inline` or `block` options so that the new form is
* being set to a view block that can be rendered outside of the main form.
*
* If all you are looking for is a button to submit your form, then you should use
* `FormHelper::submit()` instead.
*
* ### Options:
*
* - `data` - Array with key/value to pass in input hidden
* - `method` - Request method to use. Set to 'delete' to simulate HTTP/1.1 DELETE request. Defaults to 'post'.
* - `confirm` - Can be used instead of $confirmMessage.
* - `inline` - Whether or not the associated form tag should be output inline.
* Set to false to have the form tag appended to the 'postLink' view block.
* Defaults to true.
* - `block` - Choose a custom block to append the form tag to. Using this option
* will override the inline option.
* - Other options are the same of HtmlHelper::link() method.
* - The option `onclick` will be replaced.
*
* @param string $title The content to be wrapped by <a> tags.
* @param string|array $url Cake-relative URL or array of URL parameters, or external URL (starts with http://)
* @param array $options Array of HTML attributes.
* @param bool|string $confirmMessage JavaScript confirmation message. This
* argument is deprecated as of 2.6. Use `confirm` key in $options instead.
* @return string An `<a />` element.
* @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::postLink
*/
public function postLink($title, $url = null, $options = array(), $confirmMessage = false) {
$options = (array)$options + array('inline' => true, 'block' => null);
if (!$options['inline'] && empty($options['block'])) {
$options['block'] = __FUNCTION__;
}
unset($options['inline']);
$requestMethod = 'POST';
if (!empty($options['method'])) {
$requestMethod = strtoupper($options['method']);
unset($options['method']);
}
if (!empty($options['confirm'])) {
$confirmMessage = $options['confirm'];
unset($options['confirm']);
}
$formName = str_replace('.', '', uniqid('post_', true));
$formUrl = $this->url($url);
$formOptions = array(
'name' => $formName,
'id' => $formName,
'style' => 'display:none;',
'method' => 'post',
);
if (isset($options['target'])) {
$formOptions['target'] = $options['target'];
unset($options['target']);
}
$previousLastAction = $this->_lastAction;
$this->_lastAction($url);
$out = $this->Html->useTag('form', $formUrl, $formOptions);
$out .= $this->Html->useTag('hidden', '_method', array(
'value' => $requestMethod
));
$out .= $this->_csrfField();
$fields = array();
if (isset($options['data']) && is_array($options['data'])) {
foreach (Hash::flatten($options['data']) as $key => $value) {
$fields[$key] = $value;
$out .= $this->hidden($key, array('value' => $value, 'id' => false, 'secure' => static::SECURE_SKIP));
}
unset($options['data']);
}
$out .= $this->secure($fields);
$out .= $this->Html->useTag('formend');
if ($options['block']) {
$this->_View->append($options['block'], $out);
$out = '';
// Reset security-relevant fields for outer form
$this->_lastAction = $previousLastAction;
}
unset($options['block']);
$url = '#';
$onClick = 'document.' . $formName . '.submit();';
if ($confirmMessage) {
$options['onclick'] = $this->_confirm($confirmMessage, $onClick, '', $options);
} else {
$options['onclick'] = $onClick . ' ';
}
$options['onclick'] .= 'event.returnValue = false; return false;';
$out .= $this->Html->link($title, $url, $options);
return $out;
}
/**
* Creates a submit button element. This method will generate `<input />` elements that
* can be used to submit, and reset forms by using $options. image submits can be created by supplying an
* image path for $caption.
*
* ### Options
*
* - `div` - Include a wrapping div? Defaults to true. Accepts sub options similar to
* FormHelper::input().
* - `before` - Content to include before the input.
* - `after` - Content to include after the input.
* - `type` - Set to 'reset' for reset inputs. Defaults to 'submit'
* - Other attributes will be assigned to the input element.
*
* ### Options
*
* - `div` - Include a wrapping div? Defaults to true. Accepts sub options similar to
* FormHelper::input().
* - Other attributes will be assigned to the input element.
*
* @param string $caption The label appearing on the button OR if string contains :// or the
* extension .jpg, .jpe, .jpeg, .gif, .png use an image if the extension
* exists, AND the first character is /, image is relative to webroot,
* OR if the first character is not /, image is relative to webroot/img.
* @param array $options Array of options. See above.
* @return string A HTML submit button
* @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::submit
*/
public function submit($caption = null, $options = array()) {
if (!is_string($caption) && empty($caption)) {
$caption = __d('cake', 'Submit');
}
$out = null;
$div = true;
if (isset($options['div'])) {
$div = $options['div'];
unset($options['div']);
}
$options += array('type' => 'submit', 'before' => null, 'after' => null, 'secure' => false);
$divOptions = array('tag' => 'div');
if ($div === true) {
$divOptions['class'] = 'submit';
} elseif ($div === false) {
unset($divOptions);
} elseif (is_string($div)) {
$divOptions['class'] = $div;
} elseif (is_array($div)) {
$divOptions = array_merge(array('class' => 'submit', 'tag' => 'div'), $div);
}
if (isset($options['name'])) {
$name = str_replace(array('[', ']'), array('.', ''), $options['name']);
$this->_secure($options['secure'], $name);
}
unset($options['secure']);
$before = $options['before'];
$after = $options['after'];
unset($options['before'], $options['after']);
$isUrl = strpos($caption, '://') !== false;
$isImage = preg_match('/\.(jpg|jpe|jpeg|gif|png|ico)$/', $caption);
if ($isUrl || $isImage) {
$unlockFields = array('x', 'y');
if (isset($options['name'])) {
$unlockFields = array(
$options['name'] . '_x', $options['name'] . '_y'
);
}
foreach ($unlockFields as $ignore) {
$this->unlockField($ignore);
}
}
if ($isUrl) {
unset($options['type']);
$tag = $this->Html->useTag('submitimage', $caption, $options);
} elseif ($isImage) {
unset($options['type']);
if ($caption{0} !== '/') {
$url = $this->webroot(Configure::read('App.imageBaseUrl') . $caption);
} else {
$url = $this->webroot(trim($caption, '/'));
}
$url = $this->assetTimestamp($url);
$tag = $this->Html->useTag('submitimage', $url, $options);
} else {
$options['value'] = $caption;
$tag = $this->Html->useTag('submit', $options);
}
$out = $before . $tag . $after;
if (isset($divOptions)) {
$tag = $divOptions['tag'];
unset($divOptions['tag']);
$out = $this->Html->tag($tag, $out, $divOptions);
}
return $out;
}
/**
* Returns a formatted SELECT element.
*
* ### Attributes:
*
* - `showParents` - If included in the array and set to true, an additional option element
* will be added for the parent of each option group. You can set an option with the same name
* and it's key will be used for the value of the option.
* - `multiple` - show a multiple select box. If set to 'checkbox' multiple checkboxes will be
* created instead.
* - `empty` - If true, the empty select option is shown. If a string,
* that string is displayed as the empty element.
* - `escape` - If true contents of options will be HTML entity encoded. Defaults to true.
* - `value` The selected value of the input.
* - `class` - When using multiple = checkbox the class name to apply to the divs. Defaults to 'checkbox'.
* - `disabled` - Control the disabled attribute. When creating a select box, set to true to disable the
* select box. When creating checkboxes, `true` will disable all checkboxes. You can also set disabled
* to a list of values you want to disable when creating checkboxes.
*
* ### Using options
*
* A simple array will create normal options:
*
* ```
* $options = array(1 => 'one', 2 => 'two);
* $this->Form->select('Model.field', $options));
* ```
*
* While a nested options array will create optgroups with options inside them.
* ```
* $options = array(
* 1 => 'bill',
* 'fred' => array(
* 2 => 'fred',
* 3 => 'fred jr.'
* )
* );
* $this->Form->select('Model.field', $options);
* ```
*
* In the above `2 => 'fred'` will not generate an option element. You should enable the `showParents`
* attribute to show the fred option.
*
* If you have multiple options that need to have the same value attribute, you can
* use an array of arrays to express this:
*
* ```
* $options = array(
* array('name' => 'United states', 'value' => 'USA'),
* array('name' => 'USA', 'value' => 'USA'),
* );
* ```
*
* @param string $fieldName Name attribute of the SELECT
* @param array $options Array of the OPTION elements (as 'value'=>'Text' pairs) to be used in the
* SELECT element
* @param array $attributes The HTML attributes of the select element.
* @return string Formatted SELECT element
* @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#options-for-select-checkbox-and-radio-inputs
*/
public function select($fieldName, $options = array(), $attributes = array()) {
$select = array();
$style = null;
$tag = null;
$attributes += array(
'class' => null,
'escape' => true,
'secure' => true,
'empty' => '',
'showParents' => false,
'hiddenField' => true,
'disabled' => false
);
$escapeOptions = $this->_extractOption('escape', $attributes);
$secure = $this->_extractOption('secure', $attributes);
$showEmpty = $this->_extractOption('empty', $attributes);
$showParents = $this->_extractOption('showParents', $attributes);
$hiddenField = $this->_extractOption('hiddenField', $attributes);
unset($attributes['escape'], $attributes['secure'], $attributes['empty'], $attributes['showParents'], $attributes['hiddenField']);
$id = $this->_extractOption('id', $attributes);
$attributes = $this->_initInputField($fieldName, array_merge(
(array)$attributes, array('secure' => static::SECURE_SKIP)
));
if (is_string($options) && isset($this->_options[$options])) {
$options = $this->_generateOptions($options);
} elseif (!is_array($options)) {
$options = array();
}
if (isset($attributes['type'])) {
unset($attributes['type']);
}
if (!empty($attributes['multiple'])) {
$style = ($attributes['multiple'] === 'checkbox') ? 'checkbox' : null;
$template = ($style) ? 'checkboxmultiplestart' : 'selectmultiplestart';
$tag = $template;
if ($hiddenField) {
$hiddenAttributes = array(
'value' => '',
'id' => $attributes['id'] . ($style ? '' : '_'),
'secure' => false,
'form' => isset($attributes['form']) ? $attributes['form'] : null,
'name' => $attributes['name'],
'disabled' => $attributes['disabled'] === true || $attributes['disabled'] === 'disabled'
);
$select[] = $this->hidden(null, $hiddenAttributes);
}
} else {
$tag = 'selectstart';
}
if ($tag === 'checkboxmultiplestart') {
unset($attributes['required']);
}
if (!empty($tag) || isset($template)) {
$hasOptions = (count($options) > 0 || $showEmpty);
// Secure the field if there are options, or its a multi select.
// Single selects with no options don't submit, but multiselects do.
if ((!isset($secure) || $secure) &&
empty($attributes['disabled']) &&
(!empty($attributes['multiple']) || $hasOptions)
) {
$this->_secure(true, $this->_secureFieldName($attributes));
}
$filter = array('name' => null, 'value' => null);
if (is_array($attributes['disabled'])) {
$filter['disabled'] = null;
}
$select[] = $this->Html->useTag($tag, $attributes['name'], array_diff_key($attributes, $filter));
}
$emptyMulti = (
$showEmpty !== null && $showEmpty !== false && !(
empty($showEmpty) && (isset($attributes) &&
array_key_exists('multiple', $attributes))
)
);
if ($emptyMulti) {
$showEmpty = ($showEmpty === true) ? '' : $showEmpty;
$options = array('' => $showEmpty) + $options;
}
if (!$id) {
$attributes['id'] = Inflector::camelize($attributes['id']);
}
$select = array_merge($select, $this->_selectOptions(
array_reverse($options, true),
array(),
$showParents,
array(
'escape' => $escapeOptions,
'style' => $style,
'name' => $attributes['name'],
'value' => $attributes['value'],
'class' => $attributes['class'],
'id' => $attributes['id'],
'disabled' => $attributes['disabled'],
)
));
$template = ($style === 'checkbox') ? 'checkboxmultipleend' : 'selectend';
$select[] = $this->Html->useTag($template);
return implode("\n", $select);
}
/**
* Generates a valid DOM ID suffix from a string.
* Also avoids collisions when multiple values are coverted to the same suffix by
* appending a numeric value.
*
* For pre-HTML5 IDs only characters like a-z 0-9 - _ are valid. HTML5 doesn't have that
* limitation, but to avoid layout issues it still filters out some sensitive chars.
*
* @param string $value The value that should be transferred into a DOM ID suffix.
* @param string $type Doctype to use. Defaults to html4.
* @return string DOM ID
*/
public function domIdSuffix($value, $type = 'html4') {
if ($type === 'html5') {
$value = str_replace(array('@', '<', '>', ' ', '"', '\''), '_', $value);
} else {
$value = Inflector::camelize(Inflector::slug($value));
}
$value = Inflector::camelize($value);
$count = 1;
$suffix = $value;
while (in_array($suffix, $this->_domIdSuffixes)) {
$suffix = $value . $count++;
}
$this->_domIdSuffixes[] = $suffix;
return $suffix;
}
/**
* Returns a SELECT element for days.
*
* ### Attributes:
*
* - `empty` - If true, the empty select option is shown. If a string,
* that string is displayed as the empty element.
* - `value` The selected value of the input.
*
* @param string $fieldName Prefix name for the SELECT element
* @param array $attributes HTML attributes for the select element
* @return string A generated day select box.
* @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::day
*/
public function day($fieldName = null, $attributes = array()) {
$attributes += array('empty' => true, 'value' => null);
$attributes = $this->_dateTimeSelected('day', $fieldName, $attributes);
if (strlen($attributes['value']) > 2) {
$date = date_create($attributes['value']);
$attributes['value'] = null;
if ($date) {
$attributes['value'] = $date->format('d');
}
} elseif ($attributes['value'] === false) {
$attributes['value'] = null;
}
return $this->select($fieldName . ".day", $this->_generateOptions('day'), $attributes);
}
/**
* Returns a SELECT element for years
*
* ### Attributes:
*
* - `empty` - If true, the empty select option is shown. If a string,
* that string is displayed as the empty element.
* - `orderYear` - Ordering of year values in select options.
* Possible values 'asc', 'desc'. Default 'desc'
* - `value` The selected value of the input.
*
* @param string $fieldName Prefix name for the SELECT element
* @param int $minYear First year in sequence
* @param int $maxYear Last year in sequence
* @param array $attributes Attribute array for the select elements.
* @return string Completed year select input
* @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::year
*/
public function year($fieldName, $minYear = null, $maxYear = null, $attributes = array()) {
if (is_array($minYear)) {
$attributes = $minYear;
$minYear = null;
}
$attributes += array('empty' => true, 'value' => null);
if ((empty($attributes['value']) || $attributes['value'] === true) && $value = $this->value($fieldName)) {
if (is_array($value)) {
$year = null;
extract($value);
$attributes['value'] = $year;
} else {
if (empty($value)) {
if (!$attributes['empty'] && !$maxYear) {
$attributes['value'] = 'now';
} elseif (!$attributes['empty'] && $maxYear && !$attributes['value']) {
$attributes['value'] = $maxYear;
}
} else {
$attributes['value'] = $value;
}
}
}
if (strlen($attributes['value']) > 4 || $attributes['value'] === 'now') {
$date = date_create($attributes['value']);
$attributes['value'] = null;
if ($date) {
$attributes['value'] = $date->format('Y');
}
} elseif ($attributes['value'] === false) {
$attributes['value'] = null;
}
$yearOptions = array('value' => $attributes['value'], 'min' => $minYear, 'max' => $maxYear, 'order' => 'desc');
if (isset($attributes['orderYear'])) {
$yearOptions['order'] = $attributes['orderYear'];
unset($attributes['orderYear']);
}
return $this->select(
$fieldName . '.year', $this->_generateOptions('year', $yearOptions),
$attributes
);
}
/**
* Returns a SELECT element for months.
*
* ### Attributes:
*
* - `monthNames` - If false, 2 digit numbers will be used instead of text.
* If an array, the given array will be used.
* - `empty` - If true, the empty select option is shown. If a string,
* that string is displayed as the empty element.
* - `value` The selected value of the input.
*
* @param string $fieldName Prefix name for the SELECT element
* @param array $attributes Attributes for the select element
* @return string A generated month select dropdown.
* @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::month
*/
public function month($fieldName, $attributes = array()) {
$attributes += array('empty' => true, 'value' => null);
$attributes = $this->_dateTimeSelected('month', $fieldName, $attributes);
if (strlen($attributes['value']) > 2) {
$date = date_create($attributes['value']);
$attributes['value'] = null;
if ($date) {
$attributes['value'] = $date->format('m');
}
} elseif ($attributes['value'] === false) {
$attributes['value'] = null;
}
$defaults = array('monthNames' => true);
$attributes = array_merge($defaults, (array)$attributes);
$monthNames = $attributes['monthNames'];
unset($attributes['monthNames']);
return $this->select(
$fieldName . ".month",
$this->_generateOptions('month', array('monthNames' => $monthNames)),
$attributes
);
}
/**
* Returns a SELECT element for hours.
*
* ### Attributes:
*
* - `empty` - If true, the empty select option is shown. If a string,
* that string is displayed as the empty element.
* - `value` The selected value of the input.
*
* @param string $fieldName Prefix name for the SELECT element
* @param bool $format24Hours True for 24 hours format
* @param array $attributes List of HTML attributes
* @return string Completed hour select input
* @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::hour
*/
public function hour($fieldName, $format24Hours = false, $attributes = array()) {
if (is_array($format24Hours)) {
$attributes = $format24Hours;
$format24Hours = false;
}
$attributes += array('empty' => true, 'value' => null);
$attributes = $this->_dateTimeSelected('hour', $fieldName, $attributes);
if (strlen($attributes['value']) > 2) {
try {
$date = new DateTime($attributes['value']);
if ($format24Hours) {
$attributes['value'] = $date->format('H');
} else {
$attributes['value'] = $date->format('g');
}
} catch (Exception $e) {
$attributes['value'] = null;
}
} elseif ($attributes['value'] === false) {
$attributes['value'] = null;
}
if ($attributes['value'] > 12 && !$format24Hours) {
$attributes['value'] -= 12;
}
if (($attributes['value'] === 0 || $attributes['value'] === '00') && !$format24Hours) {
$attributes['value'] = 12;
}
return $this->select(
$fieldName . ".hour",
$this->_generateOptions($format24Hours ? 'hour24' : 'hour'),
$attributes
);
}
/**
* Returns a SELECT element for minutes.
*
* ### Attributes:
*
* - `empty` - If true, the empty select option is shown. If a string,
* that string is displayed as the empty element.
* - `value` The selected value of the input.
*
* @param string $fieldName Prefix name for the SELECT element
* @param array $attributes Array of Attributes
* @return string Completed minute select input.
* @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::minute
*/
public function minute($fieldName, $attributes = array()) {
$attributes += array('empty' => true, 'value' => null);
$attributes = $this->_dateTimeSelected('min', $fieldName, $attributes);
if (strlen($attributes['value']) > 2) {
$date = date_create($attributes['value']);
$attributes['value'] = null;
if ($date) {
$attributes['value'] = $date->format('i');
}
} elseif ($attributes['value'] === false) {
$attributes['value'] = null;
}
$minuteOptions = array();
if (isset($attributes['interval'])) {
$minuteOptions['interval'] = $attributes['interval'];
unset($attributes['interval']);
}
return $this->select(
$fieldName . ".min", $this->_generateOptions('minute', $minuteOptions),
$attributes
);
}
/**
* Selects values for dateTime selects.
*
* @param string $select Name of element field. ex. 'day'
* @param string $fieldName Name of fieldName being generated ex. Model.created
* @param array $attributes Array of attributes, must contain 'empty' key.
* @return array Attributes array with currently selected value.
*/
protected function _dateTimeSelected($select, $fieldName, $attributes) {
if ((empty($attributes['value']) || $attributes['value'] === true) && $value = $this->value($fieldName)) {
if (is_array($value)) {
$attributes['value'] = isset($value[$select]) ? $value[$select] : null;
} else {
if (empty($value)) {
if (!$attributes['empty']) {
$attributes['value'] = 'now';
}
} else {
$attributes['value'] = $value;
}
}
}
return $attributes;
}
/**
* Returns a SELECT element for AM or PM.
*
* ### Attributes:
*
* - `empty` - If true, the empty select option is shown. If a string,
* that string is displayed as the empty element.
* - `value` The selected value of the input.
*
* @param string $fieldName Prefix name for the SELECT element
* @param array $attributes Array of Attributes
* @return string Completed meridian select input
* @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::meridian
*/
public function meridian($fieldName, $attributes = array()) {
$attributes += array('empty' => true, 'value' => null);
if ((empty($attributes['value']) || $attributes['value'] === true) && $value = $this->value($fieldName)) {
if (is_array($value)) {
$meridian = null;
extract($value);
$attributes['value'] = $meridian;
} else {
if (empty($value)) {
if (!$attributes['empty']) {
$attributes['value'] = date('a');
}
} else {
$date = date_create($attributes['value']);
$attributes['value'] = null;
if ($date) {
$attributes['value'] = $date->format('a');
}
}
}
}
if ($attributes['value'] === false) {
$attributes['value'] = null;
}
return $this->select(
$fieldName . ".meridian", $this->_generateOptions('meridian'),
$attributes
);
}
/**
* Returns a set of SELECT elements for a full datetime setup: day, month and year, and then time.
*
* ### Attributes:
*
* - `monthNames` If false, 2 digit numbers will be used instead of text.
* If an array, the given array will be used.
* - `minYear` The lowest year to use in the year select
* - `maxYear` The maximum year to use in the year select
* - `interval` The interval for the minutes select. Defaults to 1
* - `separator` The contents of the string between select elements. Defaults to '-'
* - `empty` - If true, the empty select option is shown. If a string,
* that string is displayed as the empty element.
* - `round` - Set to `up` or `down` if you want to force rounding in either direction. Defaults to null.
* - `value` | `default` The default value to be used by the input. A value in `$this->data`
* matching the field name will override this value. If no default is provided `time()` will be used.
*
* @param string $fieldName Prefix name for the SELECT element
* @param string $dateFormat DMY, MDY, YMD, or null to not generate date inputs.
* @param string $timeFormat 12, 24, or null to not generate time inputs.
* @param array $attributes Array of Attributes
* @return string Generated set of select boxes for the date and time formats chosen.
* @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::dateTime
*/
public function dateTime($fieldName, $dateFormat = 'DMY', $timeFormat = '12', $attributes = array()) {
$attributes += array('empty' => true, 'value' => null);
$year = $month = $day = $hour = $min = $meridian = null;
if (empty($attributes['value'])) {
$attributes = $this->value($attributes, $fieldName);
}
if ($attributes['value'] === null && $attributes['empty'] != true) {
$attributes['value'] = time();
if (!empty($attributes['maxYear']) && $attributes['maxYear'] < date('Y')) {
$attributes['value'] = strtotime(date($attributes['maxYear'] . '-m-d'));
}
}
if (!empty($attributes['value'])) {
list($year, $month, $day, $hour, $min, $meridian) = $this->_getDateTimeValue(
$attributes['value'],
$timeFormat
);
}
$defaults = array(
'minYear' => null, 'maxYear' => null, 'separator' => '-',
'interval' => 1, 'monthNames' => true, 'round' => null
);
$attributes = array_merge($defaults, (array)$attributes);
if (isset($attributes['minuteInterval'])) {
$attributes['interval'] = $attributes['minuteInterval'];
unset($attributes['minuteInterval']);
}
$minYear = $attributes['minYear'];
$maxYear = $attributes['maxYear'];
$separator = $attributes['separator'];
$interval = $attributes['interval'];
$monthNames = $attributes['monthNames'];
$round = $attributes['round'];
$attributes = array_diff_key($attributes, $defaults);
if (!empty($interval) && $interval > 1 && !empty($min)) {
$current = new DateTime();
if ($year !== null) {
$current->setDate($year, $month, $day);
}
if ($hour !== null) {
$current->setTime($hour, $min);
}
$changeValue = $min * (1 / $interval);
switch ($round) {
case 'up':
$changeValue = ceil($changeValue);
break;
case 'down':
$changeValue = floor($changeValue);
break;
default:
$changeValue = round($changeValue);
}
$change = ($changeValue * $interval) - $min;
$current->modify($change > 0 ? "+$change minutes" : "$change minutes");
$format = ($timeFormat == 12) ? 'Y m d h i a' : 'Y m d H i a';
$newTime = explode(' ', $current->format($format));
list($year, $month, $day, $hour, $min, $meridian) = $newTime;
}
$keys = array('Day', 'Month', 'Year', 'Hour', 'Minute', 'Meridian');
$attrs = array_fill_keys($keys, $attributes);
$hasId = isset($attributes['id']);
if ($hasId && is_array($attributes['id'])) {
// check for missing ones and build selectAttr for each element
$attributes['id'] += array(
'month' => '',
'year' => '',
'day' => '',
'hour' => '',
'minute' => '',
'meridian' => ''
);
foreach ($keys as $key) {
$attrs[$key]['id'] = $attributes['id'][strtolower($key)];
}
}
if ($hasId && is_string($attributes['id'])) {
// build out an array version
foreach ($keys as $key) {
$attrs[$key]['id'] = $attributes['id'] . $key;
}
}
if (is_array($attributes['empty'])) {
$attributes['empty'] += array(
'month' => true,
'year' => true,
'day' => true,
'hour' => true,
'minute' => true,
'meridian' => true
);
foreach ($keys as $key) {
$attrs[$key]['empty'] = $attributes['empty'][strtolower($key)];
}
}
$selects = array();
foreach (preg_split('//', $dateFormat, -1, PREG_SPLIT_NO_EMPTY) as $char) {
switch ($char) {
case 'Y':
$attrs['Year']['value'] = $year;
$selects[] = $this->year(
$fieldName, $minYear, $maxYear, $attrs['Year']
);
break;
case 'M':
$attrs['Month']['value'] = $month;
$attrs['Month']['monthNames'] = $monthNames;
$selects[] = $this->month($fieldName, $attrs['Month']);
break;
case 'D':
$attrs['Day']['value'] = $day;
$selects[] = $this->day($fieldName, $attrs['Day']);
break;
}
}
$opt = implode($separator, $selects);
$attrs['Minute']['interval'] = $interval;
switch ($timeFormat) {
case '24':
$attrs['Hour']['value'] = $hour;
$attrs['Minute']['value'] = $min;
$opt .= $this->hour($fieldName, true, $attrs['Hour']) . ':' .
$this->minute($fieldName, $attrs['Minute']);
break;
case '12':
$attrs['Hour']['value'] = $hour;
$attrs['Minute']['value'] = $min;
$attrs['Meridian']['value'] = $meridian;
$opt .= $this->hour($fieldName, false, $attrs['Hour']) . ':' .
$this->minute($fieldName, $attrs['Minute']) . ' ' .
$this->meridian($fieldName, $attrs['Meridian']);
break;
}
return $opt;
}
/**
* Parse the value for a datetime selected value
*
* @param string|array $value The selected value.
* @param int $timeFormat The time format
* @return array Array of selected value.
*/
protected function _getDateTimeValue($value, $timeFormat) {
$year = $month = $day = $hour = $min = $meridian = null;
if (is_array($value)) {
extract($value);
if ($meridian === 'pm') {
$hour += 12;
}
return array($year, $month, $day, $hour, $min, $meridian);
}
if (is_numeric($value)) {
$value = strftime('%Y-%m-%d %H:%M:%S', $value);
}
$meridian = 'am';
$pos = strpos($value, '-');
if ($pos !== false) {
$date = explode('-', $value);
$days = explode(' ', $date[2]);
$day = $days[0];
$month = $date[1];
$year = $date[0];
} else {
$days[1] = $value;
}
if (!empty($timeFormat)) {
$time = explode(':', $days[1]);
if ($time[0] >= 12) {
$meridian = 'pm';
}
$hour = $min = null;
if (isset($time[1])) {
$hour = $time[0];
$min = $time[1];
}
}
return array($year, $month, $day, $hour, $min, $meridian);
}
/**
* Gets the input field name for the current tag
*
* @param array $options Options list.
* @param string $field Field name.
* @param string $key Key name.
* @return array
*/
protected function _name($options = array(), $field = null, $key = 'name') {
if ($this->requestType === 'get') {
if ($options === null) {
$options = array();
} elseif (is_string($options)) {
$field = $options;
$options = 0;
}
if (!empty($field)) {
$this->setEntity($field);
}
if (is_array($options) && isset($options[$key])) {
return $options;
}
$entity = $this->entity();
$model = $this->model();
$name = $model === $entity[0] && isset($entity[1]) ? $entity[1] : $entity[0];
$last = $entity[count($entity) - 1];
if (in_array($last, $this->_fieldSuffixes)) {
$name .= '[' . $last . ']';
}
if (is_array($options)) {
$options[$key] = $name;
return $options;
}
return $name;
}
return parent::_name($options, $field, $key);
}
/**
* Returns an array of formatted OPTION/OPTGROUP elements
*
* @param array $elements Elements to format.
* @param array $parents Parents for OPTGROUP.
* @param bool $showParents Whether to show parents.
* @param array $attributes HTML attributes.
* @return array
*/
protected function _selectOptions($elements = array(), $parents = array(), $showParents = null, $attributes = array()) {
$select = array();
$attributes = array_merge(
array('escape' => true, 'style' => null, 'value' => null, 'class' => null),
$attributes
);
$selectedIsEmpty = ($attributes['value'] === '' || $attributes['value'] === null);
$selectedIsArray = is_array($attributes['value']);
// Cast boolean false into an integer so string comparisons can work.
if ($attributes['value'] === false) {
$attributes['value'] = 0;
}
$this->_domIdSuffixes = array();
foreach ($elements as $name => $title) {
$htmlOptions = array();
if (is_array($title) && (!isset($title['name']) || !isset($title['value']))) {
if (!empty($name)) {
if ($attributes['style'] === 'checkbox') {
$select[] = $this->Html->useTag('fieldsetend');
} else {
$select[] = $this->Html->useTag('optiongroupend');
}
$parents[] = $name;
}
$select = array_merge($select, $this->_selectOptions(
$title, $parents, $showParents, $attributes
));
if (!empty($name)) {
$name = $attributes['escape'] ? h($name) : $name;
if ($attributes['style'] === 'checkbox') {
$select[] = $this->Html->useTag('fieldsetstart', $name);
} else {
$select[] = $this->Html->useTag('optiongroup', $name, '');
}
}
$name = null;
} elseif (is_array($title)) {
$htmlOptions = $title;
$name = $title['value'];
$title = $title['name'];
unset($htmlOptions['name'], $htmlOptions['value']);
}
if ($name !== null) {
$isNumeric = is_numeric($name);
if ((!$selectedIsArray && !$selectedIsEmpty && (string)$attributes['value'] == (string)$name) ||
($selectedIsArray && in_array((string)$name, $attributes['value'], !$isNumeric))
) {
if ($attributes['style'] === 'checkbox') {
$htmlOptions['checked'] = true;
} else {
$htmlOptions['selected'] = 'selected';
}
}
if ($showParents || (!in_array($title, $parents))) {
$title = ($attributes['escape']) ? h($title) : $title;
$hasDisabled = !empty($attributes['disabled']);
if ($hasDisabled) {
$disabledIsArray = is_array($attributes['disabled']);
if ($disabledIsArray) {
$disabledIsNumeric = is_numeric($name);
}
}
if ($hasDisabled &&
$disabledIsArray &&
in_array((string)$name, $attributes['disabled'], !$disabledIsNumeric)
) {
$htmlOptions['disabled'] = 'disabled';
}
if ($hasDisabled && !$disabledIsArray && $attributes['style'] === 'checkbox') {
$htmlOptions['disabled'] = $attributes['disabled'] === true ? 'disabled' : $attributes['disabled'];
}
if ($attributes['style'] === 'checkbox') {
$htmlOptions['value'] = $name;
$tagName = $attributes['id'] . $this->domIdSuffix($name);
$htmlOptions['id'] = $tagName;
$label = array('for' => $tagName);
if (isset($htmlOptions['checked']) && $htmlOptions['checked'] === true) {
$label['class'] = 'selected';
}
$name = $attributes['name'];
if (empty($attributes['class'])) {
$attributes['class'] = 'checkbox';
} elseif ($attributes['class'] === 'form-error') {
$attributes['class'] = 'checkbox ' . $attributes['class'];
}
$label = $this->label(null, $title, $label);
$item = $this->Html->useTag('checkboxmultiple', $name, $htmlOptions);
$select[] = $this->Html->div($attributes['class'], $item . $label);
} else {
if ($attributes['escape']) {
$name = h($name);
}
$select[] = $this->Html->useTag('selectoption', $name, $htmlOptions, $title);
}
}
}
}
return array_reverse($select, true);
}
/**
* Generates option lists for common <select /> menus
*
* @param string $name List type name.
* @param array $options Options list.
* @return array
*/
protected function _generateOptions($name, $options = array()) {
if (!empty($this->options[$name])) {
return $this->options[$name];
}
$data = array();
switch ($name) {
case 'minute':
if (isset($options['interval'])) {
$interval = $options['interval'];
} else {
$interval = 1;
}
$i = 0;
while ($i < 60) {
$data[sprintf('%02d', $i)] = sprintf('%02d', $i);
$i += $interval;
}
break;
case 'hour':
for ($i = 1; $i <= 12; $i++) {
$data[sprintf('%02d', $i)] = $i;
}
break;
case 'hour24':
for ($i = 0; $i <= 23; $i++) {
$data[sprintf('%02d', $i)] = $i;
}
break;
case 'meridian':
$data = array('am' => 'am', 'pm' => 'pm');
break;
case 'day':
for ($i = 1; $i <= 31; $i++) {
$data[sprintf('%02d', $i)] = $i;
}
break;
case 'month':
if ($options['monthNames'] === true) {
$data['01'] = __d('cake', 'January');
$data['02'] = __d('cake', 'February');
$data['03'] = __d('cake', 'March');
$data['04'] = __d('cake', 'April');
$data['05'] = __d('cake', 'May');
$data['06'] = __d('cake', 'June');
$data['07'] = __d('cake', 'July');
$data['08'] = __d('cake', 'August');
$data['09'] = __d('cake', 'September');
$data['10'] = __d('cake', 'October');
$data['11'] = __d('cake', 'November');
$data['12'] = __d('cake', 'December');
} elseif (is_array($options['monthNames'])) {
$data = $options['monthNames'];
} else {
for ($m = 1; $m <= 12; $m++) {
$data[sprintf("%02s", $m)] = strftime("%m", mktime(1, 1, 1, $m, 1, 1999));
}
}
break;
case 'year':
$current = (int)date('Y');
$min = !isset($options['min']) ? $current - 20 : (int)$options['min'];
$max = !isset($options['max']) ? $current + 20 : (int)$options['max'];
if ($min > $max) {
list($min, $max) = array($max, $min);
}
if (!empty($options['value']) &&
(int)$options['value'] < $min &&
(int)$options['value'] > 0
) {
$min = (int)$options['value'];
} elseif (!empty($options['value']) && (int)$options['value'] > $max) {
$max = (int)$options['value'];
}
for ($i = $min; $i <= $max; $i++) {
$data[$i] = $i;
}
if ($options['order'] !== 'asc') {
$data = array_reverse($data, true);
}
break;
}
$this->_options[$name] = $data;
return $this->_options[$name];
}
/**
* Sets field defaults and adds field to form security input hash.
* Will also add a 'form-error' class if the field contains validation errors.
*
* ### Options
*
* - `secure` - boolean whether or not the field should be added to the security fields.
* Disabling the field using the `disabled` option, will also omit the field from being
* part of the hashed key.
*
* This method will convert a numerically indexed 'disabled' into an associative
* value. FormHelper's internals expect associative options.
*
* @param string $field Name of the field to initialize options for.
* @param array $options Array of options to append options into.
* @return array Array of options for the input.
*/
protected function _initInputField($field, $options = array()) {
if (isset($options['secure'])) {
$secure = $options['secure'];
unset($options['secure']);
} else {
$secure = (isset($this->request['_Token']) && !empty($this->request['_Token']));
}
$disabledIndex = array_search('disabled', $options, true);
if (is_int($disabledIndex)) {
unset($options[$disabledIndex]);
$options['disabled'] = true;
}
$result = parent::_initInputField($field, $options);
if ($this->tagIsInvalid() !== false) {
$result = $this->addClass($result, 'form-error');
}
$isDisabled = false;
if (isset($result['disabled'])) {
$isDisabled = (
$result['disabled'] === true ||
$result['disabled'] === 'disabled' ||
(is_array($result['disabled']) &&
!empty($result['options']) &&
array_diff($result['options'], $result['disabled']) === array()
)
);
}
if ($isDisabled) {
return $result;
}
if (!isset($result['required']) &&
$this->_introspectModel($this->model(), 'validates', $this->field())
) {
$result['required'] = true;
}
if ($secure === static::SECURE_SKIP) {
return $result;
}
$this->_secure($secure, $this->_secureFieldName($options));
return $result;
}
/**
* Get the field name for use with _secure().
*
* Parses the name attribute to create a dot separated name value for use
* in secured field hash.
*
* @param array $options An array of options possibly containing a name key.
* @return string|null
*/
protected function _secureFieldName($options) {
if (isset($options['name'])) {
preg_match_all('/\[(.*?)\]/', $options['name'], $matches);
if (isset($matches[1])) {
return $matches[1];
}
}
return null;
}
/**
* Sets the last created form action.
*
* @param string|array $url URL.
* @return void
*/
protected function _lastAction($url) {
$action = Router::url($url, true);
$query = parse_url($action, PHP_URL_QUERY);
$query = $query ? '?' . $query : '';
$this->_lastAction = parse_url($action, PHP_URL_PATH) . $query;
}
/**
* Set/Get inputDefaults for form elements
*
* @param array $defaults New default values
* @param bool $merge Merge with current defaults
* @return array inputDefaults
*/
public function inputDefaults($defaults = null, $merge = false) {
if ($defaults !== null) {
if ($merge) {
$this->_inputDefaults = array_merge($this->_inputDefaults, (array)$defaults);
} else {
$this->_inputDefaults = (array)$defaults;
}
}
return $this->_inputDefaults;
}
}