diff --git a/forms/FormField.php b/forms/FormField.php index 693c50253..0d0ebe525 100644 --- a/forms/FormField.php +++ b/forms/FormField.php @@ -1,19 +1,25 @@ Subclassing - * - * Define a {@link dataValue()} method that returns a value suitable for inserting into a single database field. - * For example, you might tidy up the format of a date or currency field. - * Define {@link saveInto()} to totally customise saving. - * For example, data might be saved to the filesystem instead of the data record, - * or saved to a component of the data record instead of the data record itself. - * + * + * In addition to single fields, FormField objects can be "composite", for example, the + * {@link TabSet} field. Composite fields let us define complex forms without having to resort to + * custom HTML. + * + * To subclass: + * + * Define a {@link dataValue()} method that returns a value suitable for inserting into a single + * database field. + * + * For example, you might tidy up the format of a date or currency field. Define {@link saveInto()} + * to totally customise saving. + * + * For example, data might be saved to the filesystem instead of the data record, or saved to a + * component of the data record instead of the data record itself. + * * @package forms * @subpackage core */ @@ -24,153 +30,244 @@ class FormField extends RequestHandler { */ protected $form; - protected $name, $title, $value ,$message, $messageType, $extraClass; - /** - * @var $description string Adds a "title"-attribute to the markup. + * @var string + */ + protected $name; + + /** + * @var null|string + */ + protected $title; + + /** + * @var mixed + */ + protected $value; + + /** + * @var string + */ + protected $message; + + /** + * @var string + */ + protected $messageType; + + /** + * @var string + */ + protected $extraClass; + + /** + * Adds a title attribute to the markup. + * + * @var string + * * @todo Implement in all subclasses */ protected $description; - + /** - * @var $extraClasses array Extra CSS-classes for the formfield-container + * Extra CSS classes for the FormField container. + * + * @var array */ protected $extraClasses; - - public $dontEscape; - + /** - * @var $rightTitle string Used in SmallFieldHolder to force a right-aligned label, or in FieldHolder - * to create contextual label. + * @var bool + */ + public $dontEscape; + + /** + * Right-aligned, contextual label for the field. + * + * @var string */ protected $rightTitle; - + /** - * @var $leftTitle string Used in SmallFieldHolder() to force a left-aligned label with correct spacing. - * Please use $title for FormFields rendered with FieldHolder(). + * Left-aligned, contextual label for the field. + * + * @var string */ protected $leftTitle; - + /** * Stores a reference to the FieldList that contains this object. + * * @var FieldList */ protected $containerFieldList; - + /** - * @var boolean + * @var bool */ protected $readonly = false; /** - * @var boolean + * @var bool */ protected $disabled = false; - + /** - * @var string custom validation message for the Field - */ - protected $customValidationMessage = ""; - - /** - * Name of the template used to render this form field. If not set, then - * will look up the class ancestry for the first matching template where - * the template name equals the class name. - * - * To explicitly use a custom template or one named other than the form - * field see {@link setTemplate()}, {@link setFieldHolderTemplate()} + * Custom validation message for the field. * * @var string */ - protected - $template, - $fieldHolderTemplate, - $smallFieldHolderTemplate; - + protected $customValidationMessage = ''; + /** - * @var array All attributes on the form field (not the field holder). - * Partially determined based on other instance properties, please use {@link getAttributes()}. + * Name of the template used to render this form field. If not set, then will look up the class + * ancestry for the first matching template where the template name equals the class name. + * + * To explicitly use a custom template or one named other than the form field see + * {@link setTemplate()}. + * + * @var string + */ + protected $template; + + /** + * Name of the template used to render this form field. If not set, then will look up the class + * ancestry for the first matching template where the template name equals the class name. + * + * To explicitly use a custom template or one named other than the form field see + * {@link setFieldHolderTemplate()}. + * + * @var string + */ + protected $fieldHolderTemplate; + + /** + * @var string + */ + protected $smallFieldHolderTemplate; + + /** + * All attributes on the form field (not the field holder). + * + * Partially determined based on other instance properties. + * + * @see getAttributes() + * + * @var array */ protected $attributes = array(); /** - * Takes a fieldname and converts camelcase to spaced - * words. Also resolves combined fieldnames with dot syntax - * to spaced words. + * Takes a field name and converts camelcase to spaced words. Also resolves combined field + * names with dot syntax to spaced words. * * Examples: + * * - 'TotalAmount' will return 'Total Amount' * - 'Organisation.ZipCode' will return 'Organisation Zip Code' * * @param string $fieldName + * * @return string */ public static function name_to_label($fieldName) { if(strpos($fieldName, '.') !== false) { $parts = explode('.', $fieldName); - $label = $parts[count($parts)-2] . ' ' . $parts[count($parts)-1]; + + $label = $parts[count($parts) - 2] . ' ' . $parts[count($parts) - 1]; } else { $label = $fieldName; } - $label = preg_replace("/([a-z]+)([A-Z])/","$1 $2", $label); - - return $label; + + return preg_replace('/([a-z]+)([A-Z])/', '$1 $2', $label); } /** * Construct and return HTML tag. + * + * @param string $tag + * @param array $attributes + * @param null|string $content + * + * @return string */ public static function create_tag($tag, $attributes, $content = null) { $preparedAttributes = ''; - foreach($attributes as $k => $v) { - // Note: as indicated by the $k == value item here; the decisions over what to include in the attributes - // can sometimes get finicky - if(!empty($v) || $v === '0' || ($k == 'value' && $v !== null) ) { - $preparedAttributes .= " $k=\"" . Convert::raw2att($v) . "\""; + + foreach($attributes as $attributeKey => $attributeValue) { + if(!empty($attributeValue) || $attributeValue === '0' || ($attributeKey == 'value' && $attributeValue !== null)) { + $preparedAttributes .= sprintf( + ' %s="%s"', $attributeKey, Convert::raw2att($attributeValue) + ); } } - if($content || $tag != 'input') return "<$tag$preparedAttributes>$content"; - else return "<$tag$preparedAttributes />"; + if($content || $tag != 'input') { + return sprintf( + '<%s%s>%s', $tag, $preparedAttributes, $content, $tag + ); + } + + return sprintf( + '<%s%s />', $tag, $preparedAttributes + ); } /** * Creates a new field. * * @param string $name The internal field name, passed to forms. - * @param string $title The human-readable field label. + * @param null|string $title The human-readable field label. * @param mixed $value The value of the field. */ public function __construct($name, $title = null, $value = null) { $this->name = $name; - $this->title = ($title === null) ? self::name_to_label($name) : $title; - if($value !== NULL) $this->setValue($value); + if($title === null) { + $this->title = self::name_to_label($name); + } else { + $this->title = $title; + } + + if($value !== null) { + $this->setValue($value); + } parent::__construct(); } - + /** - * Return a Link to this field + * Return a Link to this field. + * + * @param null|string $action + * + * @return string */ public function Link($action = null) { return Controller::join_links($this->form->FormAction(), 'field/' . $this->name, $action); } - + /** - * Returns the HTML ID of the field - used in the template by label tags. - * The ID is generated as FormName_FieldName. All Field functions should ensure - * that this ID is included in the field. + * Returns the HTML ID of the field. + * + * The ID is generated as FormName_FieldName. All Field functions should ensure that this ID is + * included in the field. */ public function ID() { - $name = preg_replace('/(^-)|(-$)/', '', preg_replace('/[^A-Za-z0-9_-]+/', '-', $this->name)); - if($this->form) return $this->form->FormName() . '_' . $name; - else return $name; + $name = $this->name; + $name = preg_replace('/[^A-Za-z0-9_-]+/', '-', $name); + $name = preg_replace('/(^-)|(-$)/', '', $name); + + if($this->form) { + return $this->form->FormName() . '_' . $name; + } + + return $name; } - + /** - * Returns the field name - used by templates. - * + * Returns the field name. + * * @return string */ public function getName() { @@ -179,130 +276,160 @@ class FormField extends RequestHandler { /** * Returns the field message, used by form validation. + * * Use {@link setError()} to set this property. - * + * * @return string */ public function Message() { return $this->message; - } - - /** - * Returns the field message type, used by form validation. - * Arbitrary value which is mostly used for CSS classes - * in the rendered HTML, e.g. "required". + } + + /** + * Returns the field message type. + * + * Arbitrary value which is mostly used for CSS classes in the rendered HTML, e.g "required". + * * Use {@link setError()} to set this property. - * + * * @return string */ public function MessageType() { return $this->messageType; - } - + } + /** - * Returns the field value - used by templates. + * Returns the field value. + * + * @return mixed */ public function Value() { return $this->value; } - + /** * Method to save this form field into the given data object. - * By default, makes use of $this->dataValue() - * - * @param DataObjectInterface $record DataObject to save data into + * + * @param DataObjectInterface $record */ public function saveInto(DataObjectInterface $record) { if($this->name) { $record->setCastedField($this->name, $this->dataValue()); } } - + /** - * Returns the field value suitable for insertion into the data object + * Returns the field value suitable for insertion into the data object. + * + * @return mixed */ public function dataValue() { return $this->value; } - + /** - * Returns the field label - used by templates. + * Returns the field label. */ public function Title() { return $this->title; } - - public function setTitle($val) { - $this->title = $val; + + /** + * @param string $title + * + * @return $this + */ + public function setTitle($title) { + $this->title = $title; + return $this; } /** - * Gets the contextual label than can be used for additional field description. - * Can be shown to the right or under the field in question. - * - * @return string Contextual label text. + * @return string */ public function RightTitle() { return $this->rightTitle; } /** - * Sets the contextual label. + * @param string $rightTitle * - * @param $val string Text to set on the label. + * @return $this */ - public function setRightTitle($val) { - $this->rightTitle = $val; - return $this; - } + public function setRightTitle($rightTitle) { + $this->rightTitle = $rightTitle; - public function LeftTitle() { - return $this->leftTitle; - } - - public function setLeftTitle($val) { - $this->leftTitle = $val; return $this; } /** - * Compiles all CSS-classes. Optionally includes a "nolabel"-class - * if no title was set on the formfield. - * Uses {@link Message()} and {@link MessageType()} to add validatoin - * error classes which can be used to style the contained tags. - * - * @return string CSS-classnames + * @return string + */ + public function LeftTitle() { + return $this->leftTitle; + } + + /** + * @param string $leftTitle + * + * @return $this + */ + public function setLeftTitle($leftTitle) { + $this->leftTitle = $leftTitle; + + return $this; + } + + /** + * Compiles all CSS-classes. Optionally includes a "nolabel" class if no title was set on the + * FormField. + * + * Uses {@link Message()} and {@link MessageType()} to add validation error classes which can + * be used to style the contained tags. + * + * @return string */ public function extraClass() { $classes = array(); $classes[] = $this->Type(); - if($this->extraClasses) $classes = array_merge($classes, array_values($this->extraClasses)); - - // Allow customization of label and field tag positioning - if(!$this->Title()) $classes[] = "nolabel"; - - // Allow custom styling of any element in the container based - // on validation errors, e.g. red borders on input tags. - // CSS-Class needs to be different from the one rendered - // through {@link FieldHolder()} - if($this->Message()) $classes[] .= "holder-" . $this->MessageType(); - + if($this->extraClasses) { + $classes = array_merge( + $classes, + array_values($this->extraClasses) + ); + } + + if(!$this->Title()) { + $classes[] = 'nolabel'; + } + + // Allow custom styling of any element in the container based on validation errors, + // e.g. red borders on input tags. + // + // CSS class needs to be different from the one rendered through {@link FieldHolder()}. + if($this->Message()) { + $classes[] .= 'holder-' . $this->MessageType(); + } + return implode(' ', $classes); } - + /** - * Add one or more CSS-classes to the formfield-container. Multiple class - * names should be space delimited. - * + * Add one or more CSS-classes to the FormField container. + * + * Multiple class names should be space delimited. + * * @param string $class + * + * @return $this */ public function addExtraClass($class) { $classes = preg_split('/\s+/', $class); - foreach ($classes as $class) { + foreach($classes as $class) { $this->extraClasses[$class] = $class; } @@ -310,199 +437,252 @@ class FormField extends RequestHandler { } /** - * Remove one or more CSS-classes from the formfield-container. - * + * Remove one or more CSS-classes from the FormField container. + * * @param string $class + * + * @return $this */ public function removeExtraClass($class) { $classes = preg_split('/\s+/', $class); - foreach ($classes as $class) { + foreach($classes as $class) { unset($this->extraClasses[$class]); } - + return $this; } /** * Set an HTML attribute on the field element, mostly an tag. - * - * Some attributes are best set through more specialized methods, to avoid interfering with built-in behaviour: + * + * Some attributes are best set through more specialized methods, to avoid interfering with + * built-in behaviour: + * * - 'class': {@link addExtraClass()} * - 'title': {@link setDescription()} * - 'value': {@link setValue} * - 'name': {@link setName} - * - * CAUTION Doesn't work on most fields which are composed of more than one HTML form field: - * AjaxUniqueTextField, CheckboxSetField, ComplexTableField, CompositeField, ConfirmedPasswordField, - * CountryDropdownField, CreditCardField, CurrencyField, DateField, DatetimeField, FieldGroup, GridField, - * HtmlEditorField, ImageField, ImageFormAction, InlineFormAction, ListBoxField, etc. - * - * @param string - * @param string + * + * Caution: this doesn't work on most fields which are composed of more than one HTML form + * field. + * + * @param string $name + * @param string $value + * + * @return $this */ public function setAttribute($name, $value) { $this->attributes[$name] = $value; + return $this; } /** * Get an HTML attribute defined by the field, or added through {@link setAttribute()}. - * Caution: Doesn't work on all fields, see {@link setAttribute()}. - * - * @return string + * + * Caution: this doesn't work on all fields, see {@link setAttribute()}. + * + * @return null|string */ public function getAttribute($name) { - $attrs = $this->getAttributes(); - if(isset($attrs[$name])) return $attrs[$name]; + $attributes = $this->getAttributes(); + + if(isset($attributes[$name])) { + return $attributes[$name]; + } + + return null; } - + /** * @return array */ public function getAttributes() { - $attrs = array( + $attributes = array( 'type' => 'text', 'name' => $this->getName(), - 'value' => $this->Value(), + 'value' => $this->Value(), 'class' => $this->extraClass(), 'id' => $this->ID(), 'disabled' => $this->isDisabled(), 'readonly' => $this->isReadonly() ); - - if ($this->Required()) { - $attrs['required'] = 'required'; - $attrs['aria-required'] = 'true'; + + if($this->Required()) { + $attributes['required'] = 'required'; + $attributes['aria-required'] = 'true'; } - return array_merge($attrs, $this->attributes); + return array_merge($attributes, $this->attributes); } /** - * @param Array Custom attributes to process. Falls back to {@link getAttributes()}. - * If at least one argument is passed as a string, all arguments act as excludes by name. - * @return string HTML attributes, ready for insertion into an HTML tag + * Custom attributes to process. Falls back to {@link getAttributes()}. + * + * If at least one argument is passed as a string, all arguments act as excludes, by name. + * + * @param array $attributes + * + * @return string */ - public function getAttributesHTML($attrs = null) { - $exclude = (is_string($attrs)) ? func_get_args() : null; + public function getAttributesHTML($attributes = null) { + $exclude = null; - if(!$attrs || is_string($attrs)) $attrs = $this->getAttributes(); + if(is_string($attributes)) { + $exclude = func_get_args(); + } - // Remove empty - $attrs = array_filter((array)$attrs, function($v) { + if(!$attributes || is_string($attributes)) { + $attributes = $this->getAttributes(); + } + + $attributes = (array) $attributes; + + $attributes = array_filter($attributes, function ($v) { return ($v || $v === 0 || $v === '0'); - }); + }); - // Remove excluded - if($exclude) $attrs = array_diff_key($attrs, array_flip($exclude)); + if($exclude) { + $attributes = array_diff_key( + $attributes, + array_flip($exclude) + ); + } - // Create markkup $parts = array(); - foreach($attrs as $name => $value) { - $parts[] = ($value === true) ? "{$name}=\"{$name}\"" : "{$name}=\"" . Convert::raw2att($value) . "\""; + + foreach($attributes as $name => $value) { + if($value === true) { + $parts[] = sprintf('%s="%s"', $name, $name); + } else { + $parts[] = sprintf('%s="%s"', $name, Convert::raw2att($value)); + } } return implode(' ', $parts); } /** - * Returns a version of a title suitable for insertion into an HTML attribute + * Returns a version of a title suitable for insertion into an HTML attribute. + * + * @return string */ public function attrTitle() { return Convert::raw2att($this->title); } + /** - * Returns a version of a title suitable for insertion into an HTML attribute + * Returns a version of a title suitable for insertion into an HTML attribute. + * + * @return string */ public function attrValue() { return Convert::raw2att($this->value); } - + /** * Set the field value. - * + * * @param mixed $value - * @param mixed $data Optional data source passed in by {@see Form::loadDataFrom} - * @return FormField Self reference + * @param null|array|DataObject $data {@see Form::loadDataFrom} + * + * @return $this */ public function setValue($value) { $this->value = $value; + return $this; } - + /** - * Set the field name + * Set the field name. + * + * @param string $name + * + * @return $this */ public function setName($name) { $this->name = $name; + return $this; } - + /** * Set the container form. - * This is called whenever you create a new form and put fields inside it, so that you don't - * have to worry about linking the two. + * + * This is called automatically when fields are added to forms. + * + * @param Form $form + * + * @return $this */ public function setForm($form) { $this->form = $form; + return $this; } - + /** * Get the currently used form. * * @return Form */ public function getForm() { - return $this->form; + return $this->form; } - + /** - * Return TRUE if security token protection is enabled on the parent {@link Form}. + * Return true if security token protection is enabled on the parent {@link Form}. * * @return bool */ public function securityTokenEnabled() { $form = $this->getForm(); - if(!$form) return false; - + + if(!$form) { + return false; + } + return $form->getSecurityToken()->isEnabled(); } - + /** - * Sets the error message to be displayed on the form field - * Set by php validation of the form. + * Sets the error message to be displayed on the form field. * - * @param string $message Message to show to the user. Allows HTML content, - * which means you need to use Convert::raw2xml() for any user supplied data. + * Allows HTML content, so remember to use Convert::raw2xml(). + * + * @param string $message + * @param string $messageType + * + * @return $this */ public function setError($message, $messageType) { - $this->message = $message; - $this->messageType = $messageType; - + $this->message = $message; + $this->messageType = $messageType; + return $this; } - + /** - * Set the custom error message to show instead of the default - * format of Please Fill In XXX. Different from setError() as - * that appends it to the standard error messaging - * - * @param string Message for the error - */ - public function setCustomValidationMessage($msg) { - $this->customValidationMessage = $msg; - - return $this; - } - - /** - * Get the custom error message for this form field. If a custom - * message has not been defined then just return blank. The default - * error is defined on {@link Validator}. + * Set the custom error message to show instead of the default format. + * + * Different from setError() as that appends it to the standard error messaging. + * + * @param string $customValidationMessage + * + * @return $this + */ + public function setCustomValidationMessage($customValidationMessage) { + $this->customValidationMessage = $customValidationMessage; + + return $this; + } + + /** + * Get the custom error message for this form field. If a custom message has not been defined + * then just return blank. The default error is defined on {@link Validator}. * - * @todo Should the default error message be stored here instead * @return string */ public function getCustomValidationMessage() { @@ -511,98 +691,120 @@ class FormField extends RequestHandler { /** * Set name of template (without path or extension). - * Caution: Not consistently implemented in all subclasses, - * please check the {@link Field()} method on the subclass for support. - * - * @param string + * + * Caution: Not consistently implemented in all subclasses, please check the {@link Field()} + * method on the subclass for support. + * + * @param string $template + * + * @return $this */ public function setTemplate($template) { $this->template = $template; - + return $this; } - + /** * @return string */ public function getTemplate() { return $this->template; } - + /** * @return string */ public function getFieldHolderTemplate() { return $this->fieldHolderTemplate; } - + /** - * Set name of template (without path or extension) for the holder, - * which in turn is responsible for rendering {@link Field()}. - * - * Caution: Not consistently implemented in all subclasses, - * please check the {@link Field()} method on the subclass for support. - * - * @param string + * Set name of template (without path or extension) for the holder, which in turn is + * responsible for rendering {@link Field()}. + * + * Caution: Not consistently implemented in all subclasses, please check the {@link Field()} + * method on the subclass for support. + * + * @param string $fieldHolderTemplate + * + * @return $this */ - public function setFieldHolderTemplate($template) { - $this->fieldHolderTemplate = $template; - + public function setFieldHolderTemplate($fieldHolderTemplate) { + $this->fieldHolderTemplate = $fieldHolderTemplate; + return $this; } - + /** * @return string */ public function getSmallFieldHolderTemplate() { return $this->smallFieldHolderTemplate; } - + /** - * Set name of template (without path or extension) for the small holder, - * which in turn is responsible for rendering {@link Field()}. - * - * Caution: Not consistently implemented in all subclasses, - * please check the {@link Field()} method on the subclass for support. - * - * @param string + * Set name of template (without path or extension) for the small holder, which in turn is + * responsible for rendering {@link Field()}. + * + * Caution: Not consistently implemented in all subclasses, please check the {@link Field()} + * method on the subclass for support. + * + * @param string $smallFieldHolderTemplate + * + * @return $this */ - public function setSmallFieldHolderTemplate($template) { - $this->smallFieldHolderTemplate = $template; - + public function setSmallFieldHolderTemplate($smallFieldHolderTemplate) { + $this->smallFieldHolderTemplate = $smallFieldHolderTemplate; + return $this; } - + /** - * Returns the form field - used by templates. + * Returns the form field. + * * Although FieldHolder is generally what is inserted into templates, all of the field holder - * templates make use of $Field. It's expected that FieldHolder will give you the "complete" + * templates make use of $Field. It's expected that FieldHolder will give you the "complete" * representation of the field on the form, whereas Field will give you the core editing widget, * such as an input tag. - * - * @param array $properties key value pairs of template variables + * + * @param array $properties + * * @return string */ public function Field($properties = array()) { - $obj = ($properties) ? $this->customise($properties) : $this; + $context = $this; + + if(count($properties)) { + $context = $context->customise($properties); + } + $this->extend('onBeforeRender', $this); - return $obj->renderWith($this->getTemplates()); + + return $context->renderWith($this->getTemplates()); } /** - * Returns a "field holder" for this field - used by templates. - * + * Returns a "field holder" for this field. + * * Forms are constructed by concatenating a number of these field holders. + * * The default field holder is a label and a form field inside a div. + * * @see FieldHolder.ss - * - * @param array $properties key value pairs of template variables + * + * @param array $properties + * * @return string */ public function FieldHolder($properties = array()) { - $obj = ($properties) ? $this->customise($properties) : $this; + $context = $this; - return $obj->renderWith($this->getFieldHolderTemplates()); + if(count($properties)) { + $context = $this->customise($properties); + } + + return $context->renderWith($this->getFieldHolderTemplates()); } /** @@ -613,71 +815,81 @@ class FormField extends RequestHandler { * @return string */ public function SmallFieldHolder($properties = array()) { - $obj = ($properties) ? $this->customise($properties) : $this; + $context = $this; - return $obj->renderWith($this->getSmallFieldHolderTemplates()); + if(count($properties)) { + $context = $this->customise($properties); + } + + return $context->renderWith($this->getSmallFieldHolderTemplates()); } - + /** - * Returns an array of templates to use for rendering {@link FieldH} + * Returns an array of templates to use for rendering {@link FieldHolder}. * * @return array */ public function getTemplates() { return $this->_templates($this->getTemplate()); } - + /** - * Returns an array of templates to use for rendering {@link FieldHolder} + * Returns an array of templates to use for rendering {@link FieldHolder}. * * @return array */ public function getFieldHolderTemplates() { return $this->_templates( - $this->getFieldHolderTemplate(), + $this->getFieldHolderTemplate(), '_holder' ); } /** - * Returns an array of templates to use for rendering {@link SmallFieldHolder} + * Returns an array of templates to use for rendering {@link SmallFieldHolder}. * * @return array - */ + */ public function getSmallFieldHolderTemplates() { return $this->_templates( - $this->getSmallFieldHolderTemplate(), + $this->getSmallFieldHolderTemplate(), '_holder_small' ); } /** - * Generate an array of classname strings to use for rendering this form - * field into HTML + * Generate an array of class name strings to use for rendering this form field into HTML. * - * @param string $custom custom template (if set) - * @param string $suffix template suffix + * @param string $customTemplate + * @param string $customTemplateSuffix * - * @return array $stack a stack of + * @return array */ - private function _templates($custom = null, $suffix = null) { + private function _templates($customTemplate = null, $customTemplateSuffix = null) { $matches = array(); - + foreach(array_reverse(ClassInfo::ancestry($this)) as $className) { - $matches[] = $className . $suffix; - - if($className == "FormField") break; + $matches[] = $className . $customTemplateSuffix; + + if($className == "FormField") { + break; + } } - - if($custom) array_unshift($matches, $custom); - + + if($customTemplate) { + array_unshift($matches, $customTemplate); + } + return $matches; } - + /** * Returns true if this field is a composite field. + * * To create composite field types, you should subclass {@link CompositeField}. + * + * @return bool */ public function isComposite() { return false; @@ -685,68 +897,91 @@ class FormField extends RequestHandler { /** * Returns true if this field has its own data. - * Some fields, such as titles and composite fields, don't actually have any data. It doesn't - * make sense for data-focused methods to look at them. By overloading hasData() to return false, - * you can prevent any data-focused methods from looking at it. + * + * Some fields, such as titles and composite fields, don't actually have any data. It doesn't + * make sense for data-focused methods to look at them. By overloading hasData() to return + * false, you can prevent any data-focused methods from looking at it. * * @see FieldList::collateDataFields() + * + * @return bool */ public function hasData() { return true; } /** - * @return boolean + * @return bool */ public function isReadonly() { - return $this->readonly; + return $this->readonly; } /** - * Sets readonly-flag on form-field. Please use performReadonlyTransformation() - * to actually transform this instance. - * @param $bool boolean Setting "false" has no effect on the field-state. + * Sets a read-only flag on this FormField. + * + * Use performReadonlyTransformation() to transform this instance. + * + * Setting this to false has no effect on the field. + * + * @param bool $readonly + * + * @return $this */ - public function setReadonly($bool) { - $this->readonly = $bool; + public function setReadonly($readonly) { + $this->readonly = $readonly; + return $this; } - + /** - * @return boolean + * @return bool */ public function isDisabled() { return $this->disabled; } /** - * Sets disabed-flag on form-field. Please use performDisabledTransformation() - * to actually transform this instance. - * @param $bool boolean Setting "false" has no effect on the field-state. + * Sets a disabled flag on this FormField. + * + * Use performDisabledTransformation() to transform this instance. + * + * Setting this to false has no effect on the field. + * + * @param bool $disabled + * + * @return $this */ - public function setDisabled($bool) { - $this->disabled = $bool; + public function setDisabled($disabled) { + $this->disabled = $disabled; + return $this; } - + /** - * Returns a readonly version of this field + * Returns a read-only version of this field. + * + * @return FormField */ public function performReadonlyTransformation() { $copy = $this->castedCopy('ReadonlyField'); + $copy->setReadonly(true); + return $copy; } - + /** * Return a disabled version of this field. - * Tries to find a class of the class name of this field suffixed with "_Disabled", - * failing that, finds a method {@link setDisabled()}. + * + * Tries to find a class of the class name of this field suffixed with "_Disabled", failing + * that, finds a method {@link setDisabled()}. * * @return FormField */ public function performDisabledTransformation() { $disabledClassName = $this->class . '_Disabled'; + if(ClassInfo::exists($disabledClassName)) { $clone = $this->castedCopy($disabledClassName); } else { @@ -754,46 +989,70 @@ class FormField extends RequestHandler { $clone->setDisabled(true); } - return $clone; - } - - public function transform(FormTransformation $trans) { - return $trans->transform($this); + return $clone; } - - public function hasClass($class){ - $patten = '/'.strtolower($class).'/i'; - $subject = strtolower($this->class." ".$this->extraClass()); + + /** + * @param FormTransformation $transformation + * + * @return mixed + */ + public function transform(FormTransformation $transformation) { + return $transformation->transform($this); + } + + /** + * @param string $class + * + * @return int + */ + public function hasClass($class) { + $patten = '/' . strtolower($class) . '/i'; + + $subject = strtolower($this->class . ' ' . $this->extraClass()); + return preg_match($patten, $subject); } - + /** - * Returns the field type - used by templates. + * Returns the field type. + * * The field type is the class name with the word Field dropped off the end, all lowercase. - * It's handy for assigning HTML classes. Doesn't signify the attribute, - * see {link getAttributes()}. - * + * + * It's handy for assigning HTML classes. Doesn't signify the attribute. + * + * @see {link getAttributes()}. + * * @return string */ public function Type() { - return strtolower(preg_replace('/Field$/', '', $this->class)); + return strtolower(preg_replace('/Field$/', '', $this->class)); } /** * @deprecated 3.2 Use FormField::create_tag() + * + * @param string $tag + * @param array $attributes + * @param null|string $content + * + * @return string */ public function createTag($tag, $attributes, $content = null) { Deprecation::notice('3.2', 'Use FormField::create_tag()'); + return self::create_tag($tag, $attributes, $content); - } + } /** - * Abstract method each {@link FormField} subclass must implement, - * determines whether the field is valid or not based on the value. + * Abstract method each {@link FormField} subclass must implement, determines whether the field + * is valid or not based on the value. + * * @todo Make this abstract. * - * @param Validator - * @return boolean + * @param Validator $validator + * + * @return bool */ public function validate($validator) { return true; @@ -801,13 +1060,16 @@ class FormField extends RequestHandler { /** * Describe this field, provide help text for it. - * By default, renders as a - * underneath the form field. - * - * @return string Description + * + * By default, renders as a underneath the form field. + * + * @param string $description + * + * @return $this */ public function setDescription($description) { $this->description = $description; + return $this; } @@ -817,38 +1079,52 @@ class FormField extends RequestHandler { public function getDescription() { return $this->description; } - - public function debug() { - return "$this->class ($this->name: $this->title : $this->message)" - . " = $this->value"; - } - + /** - * This function is used by the template processor. If you refer to a field as a $ variable, it + * @return string + */ + public function debug() { + return sprintf( + '%s (%s: %s : %s) = %s', + $this->class, + $this->name, + $this->title, + $this->message, + $this->value + ); + } + + /** + * This function is used by the template processor. If you refer to a field as a $ variable, it * will return the $Field value. + * + * @return string */ public function forTemplate() { return $this->Field(); } - + /** - * @uses Validator->fieldIsRequired() - * @return boolean + * @return bool */ public function Required() { if($this->form && ($validator = $this->form->Validator)) { return $validator->fieldIsRequired($this->name); } + + return false; } /** * Set the FieldList that contains this field. * - * @param FieldList $list + * @param FieldList $containerFieldList + * * @return FieldList */ - public function setContainerFieldList($list) { - $this->containerFieldList = $list; + public function setContainerFieldList($containerFieldList) { + $this->containerFieldList = $containerFieldList; + return $this; } @@ -861,47 +1137,61 @@ class FormField extends RequestHandler { return $this->containerFieldList; } - public function rootFieldList() { - if(is_object($this->containerFieldList)) return $this->containerFieldList->rootFieldList(); - else user_error("rootFieldList() called on $this->class object without a containerFieldList", E_USER_ERROR); - } - /** - * Returns another instance of this field, but "cast" to a different class. - * The logic tries to retain all of the instance properties, - * and may be overloaded by subclasses to set additional ones. + * @return null|FieldList + */ + public function rootFieldList() { + if(is_object($this->containerFieldList)) { + return $this->containerFieldList->rootFieldList(); + } + + user_error( + "rootFieldList() called on $this->class object without a containerFieldList", + E_USER_ERROR + ); + + return null; + } + + /** + * Returns another instance of this field, but "cast" to a different class. The logic tries to + * retain all of the instance properties, and may be overloaded by subclasses to set additional + * ones. * - * Assumes the standard FormField parameter signature with - * its name as the only mandatory argument. Mainly geared towards - * creating *_Readonly or *_Disabled subclasses of the same type, - * or casting to a {@link ReadonlyField}. + * Assumes the standard FormField parameter signature with its name as the only mandatory + * argument. Mainly geared towards creating *_Readonly or *_Disabled subclasses of the same + * type, or casting to a {@link ReadonlyField}. + * + * Does not copy custom field templates, since they probably won't apply to the new instance. + * + * @param mixed $classOrCopy Class name for copy, or existing copy instance to update * - * Does not copy custom field templates, since they probably won't apply to - * the new instance. - * - * @param String $classOrCopy Class name for copy, or existing copy instance to update * @return FormField */ public function castedCopy($classOrCopy) { - $field = (is_object($classOrCopy)) ? $classOrCopy : new $classOrCopy($this->name); + $field = $classOrCopy; + + if(!is_object($field)) { + $field = new $classOrCopy($this->name); + } + $field - ->setValue($this->value) // get value directly from property, avoid any conversions + ->setValue($this->value) ->setForm($this->form) ->setTitle($this->Title()) ->setLeftTitle($this->LeftTitle()) ->setRightTitle($this->RightTitle()) ->addExtraClass($this->extraClass()) ->setDescription($this->getDescription()); - - // Only include built-in attributes, ignore anything - // set through getAttributes(), since those might change important characteristics - // of the field, e.g. its "type" attribute. - foreach($this->attributes as $k => $v) { - $field->setAttribute($k, $v); -} + + // Only include built-in attributes, ignore anything set through getAttributes(). + // Those might change important characteristics of the field, e.g. its "type" attribute. + foreach($this->attributes as $attributeKey => $attributeValue) { + $field->setAttribute($attributeKey, $attributeValue); + } + $field->dontEscape = $this->dontEscape; return $field; } - }