* @license GPLv2 or later * @link http://holisticnetworking.net/easy-inputs-wordpress/ */ namespace EasyInputs; use ReflectionMethod; /** * A class that defines an HTML form. * * An instance of class Form is created with every instance of EasyInputs. Regardless of * whether we create form tags, the Form class holds all the relevant form information * to which any Input class will need to refer. * * This class also becomes the user's primary point of entry for creating new HTML form * elements. */ class Form { /** * The name of the Easy Inputs instance. * * Unless otherwise specified, this will also be the ID of any output form element. * @var string */ public $name; /** * The type of form being created or simulated. * * Settings forms, post meta forms or custom forms are all allowed values. * @var string */ public $type; /** * The HTML action to send the form data to. * @var string */ public $action; /** * The HTML method of any output forms. * * GET, POST, PUT, DELETE * @var string */ public $method; /** * HTML attributes. * * All HTML5-compatible attributes, except for data attributes, are supported. * @var array */ public $attrs; /** * For data saved as an array, the array of subgroup names, in order of appearance. * @var array */ public $group; /** * Whether to prefix the form name to the input names * @var string */ public $prefix = true; /** * May or may not be useful. * @var string */ public $nonce_base; /** * List of supported attributes with regexes to validate against. */ public $supported_attributes = [ 'accesskey' => '/^[a-zA-Z\-_.]{1}$/', 'class' => '/^[a-zA-Z\-_.]{1,200}$/', 'tabindex' => '/[0-9]{1,20}/', 'width' => '/[0-9]{1,20}/', 'height' => '/[0-9]{1,20}/', 'size' => '/[0-9]{1,20}/', 'maxlength' => '/[0-9]{1,20}/', 'autocomplete' => '/on|off/', 'autofocus' => '/autofocus/', 'autosave' => '/^[a-zA-Z\-_.]{1,200}$/', 'results' => '/[0-9]{1,20}/', 'list' => '/^[a-zA-Z\-_.]{1,200}$/', 'min' => '/[0-9\-\/]{1,20}/', 'max' => '/[0-9\-\/]{1,20}/', 'placeholder' => '/^[a-zA-Z\-_. \?!,:]{1,200}$/', 'required' => '/required/', 'step' => '/[0-9]{1,20}/' ]; /** * Open a form element * * This function will allow you to create the opening
'; } /** * Creates a fieldset opening tag with optional legend * * The legend key of the $args array is identical to the legend() function. * The attrs array contains the same array of HTML attributes as always. * * @param array $args 'attrs' array and optional legend info * * @return string HTML containing the opening tag for a fieldset with * optional legend. */ public function fieldsetOpen($args) { extract($args); return sprintf( ''; } /** * Outputs an HTML legend * * @param array|string $args Contains 'title' and optional 'attrs' keys. 'attrs' includes * HTML attributes for the legend. * * @return string HTML containing a legend. */ public function legend($args = []) { $title = ''; $attr_str = ''; if (is_array($args)) : extract($args); $title = !empty($title) ? $title : null; $attr_str = !empty($attrs) ? Form::attrsToString($attrs) : $attr_str; else : $title = $args; endif; return sprintf('', $attr_str, $title); } /** * Create an HTML label * * @param string $for The ID of the input this label is for. * @param string $text Optional. Label text. The ID will be used if this value is left empty. * @param array $attrs HTML attributes. * * @return string The HTML string for this label. */ public static function label($for = null, $text = null, $attrs = null) { // Bounce bad requests. if (empty($for)) { return; } return sprintf( '', !empty($for) ? sprintf('for="%s"', $for) : '', is_array($attrs) ? $this->attrsToString($attrs) : '', !empty($text) && is_string($text) ? $text : ucfirst(preg_replace('/[_\-]/', ' ', $for)) // Convert fieldname ); } /** * Create an input. * This function creates an instance of Input, supplying it all the required arguments. * Input will return * * @param string $name The name of the input element. Note that this is not the HTML * "name" attribute, but does get used to create it. If grouping * is requested, this argument will be rolled into the combined * HTML name of the group. * * @param array $args The args. * * @return string The HTML string for this input. */ public function input($name, $args = []) { return ( new Input($name, $args, $this) )->create(); } /** * Display a group of inputs * * Defines a group of inputs, both logically and physically. * Logically, this group is associated with a single nonce to * which it is bound. Physically, all elements of a group will * be displayed together, in a fieldset, if requested. * * @param array $inputs Array of input arrays. Formatted with the name * of the input as the key and the $args as the * content. * @param array $args Arguments meant to be applied to either all inputs * or to the container element. * * @return string A string of HTML including all inputs from $inputs. */ public function inputs($inputs = [], $args = []) { $args = $this->setFieldsetDefaults($args); $output = ''; $open = is_array($args) && $args['fieldset'] ? $this->fieldsetOpen($args) : ''; $close = is_array($args) && $args['fieldset'] ? $this->fieldsetClose() : ''; foreach ($inputs as $name => $a) : if (is_array($a)) : $output .= $this->input($name, $a); else : $output .= $this->input($a); endif; endforeach; return sprintf( '%1$s%2$s%3$s', $open, $output, $close ); return $output; } /** * Set defaults for fieldsets. * * @param array $args Arguments meant to be applied to either all inputs * or to the container element. * * @return string A string of HTML including all inputs from $inputs. */ public function setFieldsetDefaults($args = []) { $defaults = [ 'fieldset' => true, 'legend' => ucfirst(preg_replace('/[_\-]/', ' ', $this->name)), 'attrs' => [] ]; return array_merge($defaults, $args); } /** * Display a group of inputs * * @param string $group The name of our group. * * @return bool true */ public function setGroup($group) { if (is_null($group)) : $this->group = null; else : $this->group = $this->splitGroup($group); endif; return true; } /** * Ensures a consistent format for group names. * * @param string $group a comma separated list of nesting groups. * * @return array An array of group elements. */ public function splitGroup($group) { if (is_array($group)) { return $group; } return explode(',', $group); } /** * For the Settings API, provide the required nonce fields. * * @param string $setting The Settings API setting to which this control * belongs. * * @return string Nonce fields. * * @api */ public function hiddenFields($setting) { if (empty($setting)) { return; } $fields = sprintf( ' ', esc_attr($setting) ); $fields .= (new Input($name, $args, $this))->nonce($setting); return $fields; } /** * Convert HTML attributes * Passed an indexed array of attribute/value pairs, this function will * return them as valid HTML attributes in a string. * * @param array $attrs An array of HTML-compatible attribute/value pairs. * * @return string The attributes as a string. * * @api */ public static function attrsToString($attrs) { if (empty($attrs)) { return null; } $to_string = array(); foreach ($attrs as $key => $val) : $to_string[] = sprintf('%s="%s"', $key, htmlspecialchars($val)); endforeach; return implode(' ', $to_string); } /** * Based on the passed type property, set the action and method values of our ojbect. * * @param string $type The WordPress-compatible form type. * post_meta, setting, custom * * @return null */ private function setType($type = null) { switch ($type) : case 'setting': $this->action = 'options.php'; $this->method = 'POST'; $this->prefix = isset($this->prefix) ? $this->prefix : false; break; case 'meta': $this->action = 'post.php'; $this->method = 'POST'; $this->prefix = isset($this->prefix) ? $this->prefix : false; break; default: $this->action = 'options.php'; $this->method = 'POST'; break; endswitch; } /** * Check the passed set of attributes against a list of supported types. * * Checks for the existence of a given attribute in our supported list, and also * checks it against a basic regex to be sure the value is also supported. * * @param array $attribs The passed options. * * @return array A sanitized array of HTML attributes */ public function doAttributes($attribs) { $attrs = []; if (!is_array($attribs)) { return false; } foreach ($attribs as $key => $value) : if (array_key_exists($key, $this->supported_attributes) && preg_match($this->supported_attributes[$key], $value) ) : $attrs[$key] = $value; endif; endforeach; return $attrs; } /** * Call the correct function if it exists. * * This function allows us to call Input types directly at the discretion of the * developer. * * @param string $name The function requested. * @param array $settings An optional array of arguments. * * @return string Either an error message or the HTML element. */ public function __call($name, $settings) { $reflector = new ReflectionMethod(__NAMESPACE__ . '\Input::' . $name); if ($reflector->isPublic()) : $input_name = isset($settings[0]) ? $settings[0]: $this->name; $input_args = isset($settings[1]) ? $settings[1] : array(); $input_args['type'] = $name; return ( new Input($input_name, $input_args, $this) )->create(); else : $bt = debug_backtrace(); $caller = array_shift($bt); $message = sprintf( 'Sorry. Invalid function %s called in %s on line %s.', $name, $caller['file'], $caller['line'] ); return $message; endif; } /** * Construct the Form object. * * @param array $args Includes all four static properties of our class. * * @return mixed|boolean True on creation. */ public function __construct($args) { // Bounce incomplete requests: if (empty($args) || empty($args['name'])) { return false; } $this->name = $args['name']; $this->type = !empty($args['type']) ? $args['type'] : 'post_meta'; $this->nonce_base = !empty($args['nonce_base']) ? $args['nonce_base'] : $this->name; $this->attrs = !empty($args['attrs']) ? $this->doAttributes($args['attrs']) : []; $this->group = !empty($args['group']) ? $this->splitGroup($args['group']) : null; $this->prefix = isset($args['prefix']) ? $args['prefix'] : true; $this->setType($this->type); return true; } }