You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
PlexShare/fuel/core/classes/fieldset/field.php

667 lines
16 KiB

<?php
/**
* Part of the Fuel framework.
*
* @package Fuel
* @version 1.8
* @author Fuel Development Team
* @license MIT License
* @copyright 2010 - 2016 Fuel Development Team
* @link http://fuelphp.com
*/
namespace Fuel\Core;
/**
* Fieldset Class
*
* Define a set of fields that can be used to generate a form or to validate input.
*
* @package Fuel
* @category Core
*/
class Fieldset_Field
{
/**
* @var Fieldset Fieldset this field belongs to
*/
protected $fieldset;
/**
* @var string Name of this field
*/
protected $name = '';
/**
* @var string Base name of this field
*/
protected $basename = '';
/**
* @var string Field type for form generation, false to prevent it showing
*/
protected $type = 'text';
/**
* @var string Field label for validation errors and form label generation
*/
protected $label = '';
/**
* @var mixed (Default) value of this field
*/
protected $value;
/**
* @var string Description text to show with the field
*/
protected $description = '';
/**
* @var array Rules for validation
*/
protected $rules = array();
/**
* @var array Attributes for form generation
*/
protected $attributes = array();
/**
* @var array Options, only available for select, radio & checkbox types
*/
protected $options = array();
/**
* @var string Template for form building
*/
protected $template;
/**
* @var array overwrites for default error messages
*/
protected $error_messages = array();
/**
* Constructor
*
* @param string $name
* @param string $label
* @param array $attributes
* @param array $rules
* @param Fieldset $fieldset
* @throws \RuntimeException
*/
public function __construct($name, $label = '', array $attributes = array(), array $rules = array(), $fieldset = null)
{
$this->name = (string) $name;
if ($this->name === "")
{
throw new \RuntimeException('Fieldset field name may not be empty.');
}
// determine the field's base name (for fields with array indices)
$this->basename = ($pos = strpos($this->name, '[')) ? rtrim(substr(strrchr($this->name, '['), 1), ']') : $this->name;
$this->fieldset = $fieldset instanceof Fieldset ? $fieldset : null;
// Don't allow name in attributes
unset($attributes['name']);
// Take rules out of attributes
unset($attributes['rules']);
// Use specific setter when available
foreach ($attributes as $attr => $val)
{
if (method_exists($this, $method = 'set_'.$attr))
{
$this->{$method}($val);
unset($attributes[$attr]);
}
}
// Add default "type" attribute if not specified
empty($attributes['type']) and $this->set_type($this->type);
// only when non-empty, will supersede what was given in $attributes
$label and $this->set_label($label);
$this->attributes = array_merge($this->attributes, $attributes);
foreach ($rules as $rule)
{
call_fuel_func_array(array($this, 'add_rule'), (array) $rule);
}
}
/**
* @param Fieldset $fieldset Fieldset to assign the field to
* @return Fieldset_Field
* @throws \RuntimeException
*/
public function set_fieldset(Fieldset $fieldset)
{
// if we currently have a fieldset
if ($this->fieldset)
{
// remove the field from the fieldset
$this->fieldset->delete($this->name);
// reset the fieldset
$this->fieldset = null;
// add this field to the new fieldset
$fieldset->add($this);
}
// assign the new fieldset
$this->fieldset = $fieldset;
return $this;
}
/**
* Change the field label
*
* @param string $label
* @return Fieldset_Field this, to allow chaining
*/
public function set_label($label)
{
$this->label = $label;
$this->set_attribute('label', $label);
return $this;
}
/**
* Change the field type for form generation
*
* @param string $type
* @return Fieldset_Field this, to allow chaining
*/
public function set_type($type)
{
$this->type = $type;
$this->set_attribute('type', $type);
return $this;
}
/**
* Change the field's current or default value
*
* @param string $value
* @param bool $repopulate
* @return Fieldset_Field this, to allow chaining
*/
public function set_value($value, $repopulate = false)
{
// Repopulation is handled slightly different in some cases
if ($repopulate)
{
if (($this->type == 'radio' or $this->type == 'checkbox') and empty($this->options))
{
if ($this->value == $value)
{
$this->set_attribute('checked', 'checked');
}
return $this;
}
}
$this->value = $value;
$this->set_attribute('value', $value);
return $this;
}
/**
* Change the field description
*
* @param string $description
* @return Fieldset_Field this, to allow chaining
*/
public function set_description($description)
{
$this->description = strval($description);
return $this;
}
/**
* Template the output
*
* @param string $template
* @return Fieldset_Field this, to allow chaining
*/
public function set_template($template = null)
{
$this->template = $template;
return $this;
}
/**
* Overwrite a default error message
*
* @param string $rule
* @param string $msg
* @return Fieldset_Field
*/
public function set_error_message($rule, $msg)
{
empty($rule) and $rule = 0;
$this->error_messages[$rule] = strval($msg);
return $this;
}
/**
* Check if a rule has an error message overwrite
*
* @param string $rule
* @return null|string
*/
public function get_error_message($rule)
{
if (isset($this->error_messages[$rule]))
{
return $this->error_messages[$rule];
}
elseif (isset($this->error_messages[0]))
{
return $this->error_messages[0];
}
return null;
}
/**
* Add a validation rule
* any further arguements after the callback will be used as arguements for the callback
*
* @param string|Callback either a validation rule or full callback
* @return Fieldset_Field this, to allow chaining
*/
public function add_rule($callback)
{
$args = array_slice(func_get_args(), 1);
$this->rules[] = array($callback, $args);
// Set required setting for forms when rule was applied
if ($callback === 'required')
{
$this->set_attribute('required', 'required');
}
return $this;
}
/**
* Delete a validation rule
*
* @param string|Callback either a validation rule or full callback
* @param bool whether to also reset related attributes
* @return Fieldset_Field this, to allow chaining
*/
public function delete_rule($callback, $set_attr = true)
{
foreach($this->rules as $index => $rule)
{
if ($rule[0] === $callback)
{
unset($this->rules[$index]);
break;
}
}
if ($callback === 'required' and $set_attr)
{
unset($this->attributes[$callback]);
}
return $this;
}
/**
* Sets an attribute on the field
*
* @param string
* @param mixed new value or null to unset
* @return Fieldset_Field this, to allow chaining
*/
public function set_attribute($attr, $value = null)
{
$attr = is_array($attr) ? $attr : array($attr => $value);
foreach ($attr as $key => $value)
{
if ($value === null)
{
unset($this->attributes[$key]);
}
else
{
$this->attributes[$key] = $value;
}
}
return $this;
}
/**
* Get a single or multiple attributes by key
*
* @param string|array a single key or multiple in an array, empty to fetch all
* @param mixed default output when attribute wasn't set
* @return mixed|array a single attribute or multiple in an array when $key input was an array
*/
public function get_attribute($key = null, $default = null)
{
if ($key === null)
{
return $this->attributes;
}
if (is_array($key))
{
$output = array();
foreach ($key as $k)
{
$output[$k] = array_key_exists($k, $this->attributes) ? $this->attributes[$k] : $default;
}
return $output;
}
return array_key_exists($key, $this->attributes) ? $this->attributes[$key] : $default;
}
/**
* Add an option value with label
*
* @param string|array one option value, or multiple value=>label pairs in an array
* @param string
* @param bool Whether or not to replace the current options
* @return Fieldset_Field this, to allow chaining
*/
public function set_options($value, $label = null, $replace_options = false)
{
if ( ! is_array($value))
{
\Arr::set($this->options, $value, $label);
return $this;
}
$merge = function(&$array, $new, $merge)
{
foreach ($new as $k => $v)
{
if (isset($array[$k]) and is_array($array[$k]) and is_array($v))
{
$merge($array[$k], $v);
}
else
{
$array[$k] = $v;
}
}
};
($replace_options or empty($this->options)) ? $this->options = $value : $merge($this->options, $value, $merge);
return $this;
}
/**
* Magic get method to allow getting class properties but still having them protected
* to disallow writing.
*
* @return mixed
*/
public function __get($property)
{
return $this->$property;
}
/**
* Build the field
*
* @return string
*/
public function __toString()
{
try
{
return $this->build();
}
catch (\Exception $e)
{
return $e->getMessage();
}
}
/**
* Return the parent Fieldset object
*
* @return Fieldset
*/
public function fieldset()
{
return $this->fieldset;
}
/**
* Alias for $this->fieldset->add() to allow chaining
*
* @return Fieldset_Field
*/
public function add($name, $label = '', array $attributes = array(), array $rules = array())
{
return $this->fieldset()->add($name, $label, $attributes, $rules);
}
/**
* Alias for $this->fieldset->add_before() to allow chaining
*
* @return Fieldset_Field
*/
public function add_before($name, $label = '', array $attributes = array(), array $rules = array(), $fieldname = null)
{
return $this->fieldset()->add_before($name, $label, $attributes, $rules, $fieldname);
}
/**
* Alias for $this->fieldset->add_after() to allow chaining
*
* @return Fieldset_Field
*/
public function add_after($name, $label = '', array $attributes = array(), array $rules = array(), $fieldname = null)
{
return $this->fieldset()->add_after($name, $label, $attributes, $rules, $fieldname);
}
/**
* Build the field
*
* @return string
*/
public function build()
{
$form = $this->fieldset()->form();
// Add IDs when auto-id is on
if ($form->get_config('auto_id', false) === true and $this->get_attribute('id') == '')
{
$auto_id = $form->get_config('auto_id_prefix', '')
.str_replace(array('[', ']'), array('-', ''), $this->name);
$this->set_attribute('id', $auto_id);
}
switch( ! empty($this->attributes['tag']) ? $this->attributes['tag'] : $this->type)
{
case 'hidden':
$build_field = $form->hidden($this->name, $this->value, $this->attributes);
break;
case 'radio':
case 'checkbox':
if ($this->options)
{
$build_field = array();
$i = 0;
foreach ($this->options as $value => $label)
{
$attributes = $this->attributes;
$attributes['name'] = $this->name;
$this->type == 'checkbox' and $attributes['name'] .= '['.$i.']';
$attributes['value'] = $value;
$attributes['label'] = $label;
if (is_array($this->value) ? in_array($value, $this->value) : $value == $this->value)
{
$attributes['checked'] = 'checked';
}
if( ! empty($attributes['id']))
{
$attributes['id'] .= '_'.$i;
}
else
{
$attributes['id'] = null;
}
$build_field[$form->label($label, null, array('for' => $attributes['id']))] = $this->type == 'radio'
? $form->radio($attributes)
: $form->checkbox($attributes);
$i++;
}
}
else
{
$build_field = $this->type == 'radio'
? $form->radio($this->name, $this->value, $this->attributes)
: $form->checkbox($this->name, $this->value, $this->attributes);
}
break;
case 'select':
$attributes = $this->attributes;
$name = $this->name;
unset($attributes['type']);
array_key_exists('multiple', $attributes) and $name .= '[]';
$build_field = $form->select($name, $this->value, $this->options, $attributes);
break;
case 'textarea':
$attributes = $this->attributes;
unset($attributes['type']);
$build_field = $form->textarea($this->name, $this->value, $attributes);
break;
case 'button':
$build_field = $form->button($this->name, $this->value, $this->attributes);
break;
case false:
$build_field = '';
break;
default:
$build_field = $form->input($this->name, $this->value, $this->attributes);
break;
}
if (empty($build_field) or $this->type == 'hidden')
{
return $build_field;
}
return $this->template($build_field);
}
protected function template($build_field)
{
$form = $this->fieldset()->form();
$required_mark = $this->get_attribute('required', null) ? $form->get_config('required_mark', null) : null;
$label = $this->label ? $form->label($this->label, null, array('id' => 'label_'.$this->name, 'for' => $this->get_attribute('id', null), 'class' => $form->get_config('label_class', null))) : '';
$error_template = $form->get_config('error_template', '');
$error_msg = ($form->get_config('inline_errors') && $this->error()) ? str_replace('{error_msg}', $this->error(), $error_template) : '';
$error_class = $this->error() ? $form->get_config('error_class') : '';
if (is_array($build_field))
{
$label = $this->label ? str_replace('{label}', $this->label, $form->get_config('group_label', '<span>{label}</span>')) : '';
$template = $this->template ?: $form->get_config('multi_field_template', "\t\t<tr>\n\t\t\t<td class=\"{error_class}\">{group_label}{required}</td>\n\t\t\t<td class=\"{error_class}\">{fields}\n\t\t\t\t{field} {label}<br />\n{fields}\t\t\t{error_msg}\n\t\t\t</td>\n\t\t</tr>\n");
if ($template && preg_match('#\{fields\}(.*)\{fields\}#Dus', $template, $match) > 0)
{
$build_fields = '';
foreach ($build_field as $lbl => $bf)
{
$bf_temp = str_replace('{label}', $lbl, $match[1]);
$bf_temp = str_replace('{required}', $required_mark, $bf_temp);
$bf_temp = str_replace('{field}', $bf, $bf_temp);
$build_fields .= $bf_temp;
}
$template = str_replace($match[0], '{fields}', $template);
$template = str_replace(array('{group_label}', '{required}', '{fields}', '{error_msg}', '{error_class}', '{description}'), array($label, $required_mark, $build_fields, $error_msg, $error_class, $this->description), $template);
return $template;
}
// still here? wasn't a multi field template available, try the normal one with imploded $build_field
$build_field = implode(' ', $build_field);
}
// determine the field_id, which allows us to identify the field for CSS purposes
$field_id = 'col_'.$this->name;
if ($parent = $this->fieldset()->parent())
{
$parent->get_tabular_form() and $field_id = $parent->get_tabular_form().'_col_'.$this->basename;
}
$template = $this->template ?: $form->get_config('field_template', "\t\t<tr>\n\t\t\t<td class=\"{error_class}\">{label}{required}</td>\n\t\t\t<td class=\"{error_class}\">{field} {description} {error_msg}</td>\n\t\t</tr>\n");
$template = str_replace(array('{label}', '{required}', '{field}', '{error_msg}', '{error_class}', '{description}', '{field_id}'),
array($label, $required_mark, $build_field, $error_msg, $error_class, $this->description, $field_id),
$template);
return $template;
}
/**
* Alias for $this->fieldset->validation->input() for this field
*
* @return mixed
*/
public function input()
{
return $this->fieldset()->validation()->input($this->name);
}
/**
* Alias for $this->fieldset->validation->validated() for this field
*
* @return mixed
*/
public function validated()
{
return $this->fieldset()->validation()->validated($this->name);
}
/**
* Alias for $this->fieldset->validation->error() for this field
*
* @return Validation_Error
*/
public function error()
{
return $this->fieldset()->validation()->error($this->name);
}
}