All pastes #500376 Raw Edit

Mine

public text v1 · immutable
·published 2007-05-21 15:52 UTC
rendered paste body
<?php  if (!defined('BASEPATH')) exit('No direct script access allowed');
/**
 * Code Igniter
 *
 * An open source application development framework for PHP 4.3.2 or newer
 *
 * @package		CodeIgniter
 * @author		Rick Ellis
 * @copyright	Copyright (c) 2006-2007, pMachine, Inc.
 * @license		http://www.codeignitor.com/user_guide/license.html
 * @link		http://www.codeigniter.com
 * @since		Version 1.0
 * @filesource
 */

// ------------------------------------------------------------------------

/**
 * Code Igniter FORm GEneration Class (Forge)
 *
 * @package		CodeIgniter
 * @subpackage	Libraries
 * @category	Forms
 * @author		Woody Gilk
 * @link		http://wgilk.com/code_igniter/libraries/Forge.html
 * @license		http://creativecommons.org/licenses/by-sa/3.0/
 */

class Forge {
	
	private $action;
	private $form_name;
	private $group_name;
	private $last_group;
	private $template;
	private $rules;
	private $table;
	private $groups;
	private $uploads;
	private $errors;
	private $elements;
	private $attributes;
	private $validated;
	private $has_error;
	private $has_upload;
	
	public function __construct ($params = array()) {
		// Load the necessary stuff
		$this->assign_libs  ('uri', 'load');
		$this->load->helper (array('url', 'security', 'inflector', 'string', 'form', 'tab', 'forge'));
		// Let's go!
		$this->initialize($params);
		// Why not...
		log_message('debug', "Forge Class Initialized");
	}
	
	public function initialize ($params = array()) {
		// Reset defaults
		$this->action       = '';
		$this->form_name    = '';
		$this->template     = '';
		$this->upload_path  = '';
		$this->max_size     = '';
		$this->rules        = array();
		$this->upload_rules = array();
		$this->errors       = array();
		
		// Unset disallowed params
		$disallowed = array ('CI');
		foreach ($disallowed as $param)
			unset ($params[$param]);
		
		// Set params
		if (count ($params) > 0) {
			foreach ($params as $param => $value)
				if (isset($this->$param)) $this->$param = $value;
		}
		
		// We can't allow the following variables to be set by the user
		$this->group_name = '[default]';
		$this->last_group = '[default]';
		$this->tab_index  = 1;
		$this->validated  = false;
		$this->has_upload = false;
		$this->has_error  = false;
		$this->tree       = array();
		$this->uploads    = array();
		$this->elements   = array();
		$this->attributes = array (
			'type', 'maxlength', 'value', 'options',
			'rows', 'cols', 'size',
			'selected', 'checked', 'disabled',
			'label', 'id', 'style',
			'before', 'after');
		
		// Make sure action is set
		if ($this->action == false) $this->set_action();
		// Check upload path
		if ($this->upload_path != false) $this->set_upload_path ($this->upload_path);
		// Max size must be set
		if ($this->max_size == false) {
			// Cache the size
			static $size;
			// Fetch the size, if not cached
			if ($size == false) {
				// Use php.ini to set the size
				$size = @ini_get ('upload_max_filesize');
				// Check if the last character of the string is a number
				$byte = strtolower (substr ($size, -1, 1));
				if (! is_numeric ($byte)) {
					$size = substr ($size, 0, (strlen($size)-1));
					switch ($byte) {
						case 'g': $size *= 1024; // to MB
						case 'm': $size *= 1024; // to KB
						default: break; // Done
					}
				} else
					// The size in php.ini is set in bytes, we need kilobytes
					$size /= 1024;
			}
			// Set maximum size
			$this->max_size = $size;
		}
		// Create our default group
		$this->new_group ('default');
		$this->groups['default']['legend'] = $this->form_name;
	}
	
	public function form_name ($name = false) {
		if ($name == false) $name = $this->form_name;
		return $name;
	}
	
	public function form_open ($data = false) {
		if ($data == false) $data = array();
		$form_open = $this->has_upload ? 'form_open_multipart' : 'form_open';
		
		return $form_open ($this->action, $data);
	}
	
	public function form_close() {
		return form_close();
	}
	
	// INPUT CREATION ---------------------------------------------------------
	
	public function set_action ($action = false) {
		if ($action == false) {
			if ($this->action != false) return false;
			$action = $this->uri->uri_string();
		}
		
		$this->action = $action;
		return true;
	}
	
	public function set_upload_path ($path) {
		// Make sure the upload path is writable
		if (! is_writable ($path))
			show_error ("The file upload path <tt>\"$path\"</tt> must be server writable.");
		
		$this->upload_path = $path;
		return true;
	}
	
	public function new_group ($name, $legend = false, $cols = false) {
		// If name is false, we can't continue
		if ($name == false) return false;
		// Create index array
		$index = explode ('-', $name);
		if ($name != 'default') array_unshift ($index, 'default');
		// Compile index location
		$index = "['". implode ("']['", $index) ."']";
		// Save last group
		$this->last_group = $this->group_name;
		$this->groups[$name]['legend'] = $legend;
		$this->groups[$name]['type']   = 'list';
		if (is_array ($cols)) {
			// This is a table type
			$this->groups[$name]['type'] = 'table';
			// Check each column for valid data and add it
			foreach ($cols as $_name => $_data) {
				$this->filter_data ($_name, $_data);
				$this->groups[$name]['cols'][$_name] = $_data;
			}
		}
		
		return $this->switch_group ($index);
	}
	
	public function prev_group() {
		$index  = $this->last_group;
		// Save the last group, if not default
		if ($index != '[default]') {
			$offset = strrpos ($index, '[');
			$this->last_group = substr ($index, 0, $offset);
		}
		
		return $this->switch_group ($index);
	}
	
	public function set_group_text ($name, $text) {
		$text = trim ($text);
		if ($text != false) $this->groups[$name]['text'] = $text;
	}
	
	public function get ($name, $key = false) {
		if ($key == false)
			$return = isset ($this->elements[$name]) ? $this->elements[$name] : false;
		else
			$return = isset ($this->elements[$name][$key]) ? $this->elements[$name][$key] : false;
		
		return $return;
	}
	
	public function set ($name, $key, $value = false) {
		if (! isset ($this->elements[$name])) return false;
		$this->elements[$name][$key] = $value;
		return true;
	}
	
	public function set_rules ($name, $rule) {
		if ($rule == false) return false;
		if (is_array ($rule)) {
			// Recursive ftw
			foreach ($rule as $r) $this->set_rules ($name, $r);
			return true;
		}
		
		$pipe = (isset ($this->rules[$name]) && @strlen($this->rules[$name]) > 1) ? '|' : '';
		$offset = strpos ($rule, '[');
		$offset = ($offset === false) ? strlen ($rule) : $offset;
		switch (substr ($rule, 0, $offset)) {
			case 'required':
				$this->rules[$name] .= "{$pipe}required";
				// Check if we are using meta-arrays
				if ($size = strtoarray ($rule)) {
					// Make min an integer
					$min = (int) $size[0];
					// Max is an integer if the format was [2,3], otherwise false
					$max = (isset ($size[1])) ? (int) $size[1] : false;
					// No max and a min means exact length
					if ($max === false && $min > 0)
						$this->set_rules ($name, "exact_length[$min]");
					// If min and max are the same, exact length
					elseif (($min == $max) && $min > 0)
						$this->set_rules ($name, "exact_length[$min]");
					// Min and max are not both zero
					else {
						if ($min > 0)    $this->set_rules ($name, "min_length[$min]");
						if ($max > $min) $this->set_rules ($name, "max_length[$max]"); 
					}
				}
			break;
			case 'allow':
				$types = strtoarray ($rule); 
				$this->upload_rules[$name] = implode ('|', $types);
			break;
			default:
				$this->rules[$name] .= $pipe.$rule;
			break;
		}
		
		return true;
	}
	
	public function add ($name, $data = false) {
		if ($name == false) return false;
		// Filter our data
		$this->filter_data ($name, $data);
		// Set upload status
		if ($data['type'] == 'file') $this->has_upload = true;
		// Add the input to the current group
		$this->group[]         = $name;
		$this->elements[$name] = $data;
		
		return true;
	}
	
	public function remove ($name) {
		if ($name == false) return false;
		// Unset known variables
		unset (
			$this->elements[$name],
			$this->rules[$name],
			$this->upload_rules[$name]);
		// Remove it from the tree
		$this->tree_remove ($name, $this->tree);
		
		return true;
	}
	
	public function add_error ($name, $error) {
		// Get current errors
		$e = @$this->elements[$name]['error'];
		// Set new error
		$this->elements[$name]['error'] = $e.sprintf('<p class="error">%s</p>%s', $error, "\n");

		return true;
	}
	
	public function build ($template = false, $data = false) {
		if ($data == false) $data = array();
		
		// Data used for every type
		$data['name']   = $this->form_name();
		$data['open']   = $this->form_open();
		$data['close']  = $this->form_close();
		$data['tree']   = $this->tree;
		$data['groups'] = $this->groups;
		
		foreach ($this->elements as $name => $elem) {
			$type = array_remove ('type', $elem);
			$data['elements'][$name] = $this->input ($type, $name, $elem);
		}
		
		// Return array of elements
		if ($template === true) return $data;
		// Build with no template
		if ($template == false) {
			if (! is_object ($this->table)) {
				$this->load->library ('table');
				$this->assign_libs   ('table');
			}
			// Clear table and go
			$this->table->clear();
			$this->table->set_template (array('table_open' => '<table class="layout">'));
			ob_start();
				print $data['open']."\n";
				$this->build_fieldset($data['tree'], $data['elements'], $data['groups'], true);
				print $data['close']."\n";
				$output = ob_get_contents();
			ob_end_clean();
			
			return $output;
		}
		
		return $this->load->view ("forge/$template", $data, true);
	}
	
	public function label ($name, $label) {
		// If the name is false, it would return an invalid label
		if ($name == false) return '';
		// Check if the input is required
		$required = $this->is_required ($name);
		
		// Compile label
		$label = ($label == false) ? humanize ($name) : ucfirst ($label);
		$label = $required ? "$label <em>*</em>" : $label;
		$label = sprintf ('<label for="%s">%s</label>', $name, $label);
		
		return $label;
	}
	
	public function input ($type, $name, $data, $extra = false) {
		// Need a valid name
		if ($name == false) return false;
		$data['name'] = $name;
		// The name for the label must not be an array type
		$_name = preg_replace ('@\[.*\]@', '', $name);
		// Add id to the beginning of data
		if ($_name == $name) $data = array_unshift_assoc ($data, 'id', $_name);
		// Get a label
		$label = array_remove ('label', $data);
		$label = ($type != 'submit' && $type != 'hidden') 
		       ? $this->label ($_name, $label) : '';
		// Remove error if it exists
		$error = array_remove ('error', $data);
		// Remove before and after strings
		$before = (string) array_remove ('before', $data);
		$after  = (string) array_remove ('after',  $data);
		// Create the input, using CI form_helper
		switch ($type) {
			case 'tablerow':
				$table = array_remove ('cols', $data);
				// This defines our inputs, so we need it
				if (! isset ($this->groups[$table]['cols']))
					show_error ("Tablerow type inputs required the <tt>cols</tt> attribute to be set.");
				// Fetch input data
				$inputs = $this->groups[$table]['cols'];
				// Remove label column
				array_shift ($inputs);
				// Loop through inputs and build
				foreach ($inputs as $_key => $_data) {
					unset ($_data['label']);
					$_type = array_remove ('type', $_data);
					$_name = sprintf ('%s[%s]', $name, $_key);
					$input[$_key] = array_remove ('input', $this->input ($_type, $_name, $_data));
				}
			break;
			case 'telephone':
				$value = array_remove ('value', $data);
				$input = $this->telephone ($name, $value);
			break;
			case 'citystatezip':
				$value = array_remove ('value', $data);
				$input = $this->citystatezip ($name, $value);
			break;
			case 'dateselect':
				$value = array_remove ('value', $data);
				$input = $this->dateselect ($name, $value);
			break;
			case 'dropdown':
				$options  = array_remove ('options', $data);
				$options  = is_array ($options) ? $options : array();
				$selected = array_remove ('selected', $data);
				$input = form_dropdown ($name, $options, $selected, $extra);
			break;
			case 'radio':
				$options  = array_remove ('options', $data);
				$options  = is_array ($options) ? $options : array();
				$selected = array_remove ('selected', $data);
				foreach ($options as $opt => $title) {
					$checked = ($opt == $selected) ? true : false;
					$out[] = trim (form_radio ($name, $opt, $checked, $extra)).$title;
				}
				$input = implode (' ', $out);
			break;
			case 'checkbox':
				$value   = array_remove ('value', $data);
				$value   = ($value == false) ? '1' : $value;
				$checked = array_remove ('checked', $data);
				$input = form_checkbox ($name, $value, $checked, $extra);
			break;
			case 'submit':
				$value = array_remove ('value', $data);
				$input = form_submit ($name, $value);
			break;
			case 'hidden':
				$value = array_remove ('value', $data);
				$input = form_hidden ($name, $value);
			break;
			case 'text':
				$data['size'] = isset ($data['size']) ? $data['size'] : 20;
				$input = form_input ($data);
			break;
			case 'password':
				$data['size'] = isset ($data['size']) ? $data['size'] : 20;
				$input = form_password ($data, $extra);
			break;
			case 'file':
				unset ($data['value']);
				$this->has_upload = true;
				$data['size'] = isset ($data['size']) ? $data['size'] : 20;
				$input = form_upload ($data, $extra);
			break;
			default:
				$func = "form_$type";
				// Bad input type request
				if (! function_exists ($func))
					show_error ("Invalid form input type <tt>\"$type\"</tt> requested for $name.");
				
				$input = $func ($data, $extra);
			break;
		}
		
		// The form helper adds a newline, we don't want it
		if (! is_array ($input)) $input = trim($input);
		// Add error if one exists
		if ($this->validated) {
			$e = "{$name}_error";
			$e = (isset ($this->validation->$e)) ? $this->validation->$e : '';
			$error = "$error$e";
		}
		
		// Compile our return
		if (is_array ($input)) {
			$return['label'] = $label.$error;
			$return['input'] = $input;
		} else {
			$return['label'] = $label;
			$return['input'] = $before.$input.$after.$error;
		}
		
		return $return;
	}
	
	// VALIDATION AND POST RETREVAL FUNCTIONS ---------------------------------
	
	public function validate() {
		// We have validated!
		$this->validated = true;
		// Validate uploads
		$this->validate_uploads();
		// No need to validate if there are no rules
		if (count ($this->rules) < 1) return true;
		// Import the rules
		$rules = $this->rules;
		// Set the fields
		foreach (array_keys ($rules) as $e) {
			$field = (isset ($this->elements[$e]['label'])) ? $this->elements[$e]['label'] : humanize ($e);
			$fields[$e] = strtolower ($field);
		}
		// Load Validation library
		$this->load->library ('validation');
		$this->assign_libs   ('validation');
		$this->validation->set_error_delimiters ('<p class="error">', '</p>');
		$this->validation->set_rules  ($rules);
		$this->validation->set_fields ($fields);
		
		// Run validation
		$return = $this->validation->run();
		// Set error state
		if ($return == false) $this->has_error = true;
		
		return $return;
	}
	
	public function validate_uploads() {
		// No rules, no reason to continue
		if (count ($this->upload_rules) < 1) return true;
		if ($this->upload_path == false) $this->set_upload_path ('./upload/');
		// Load Upload class
		$this->load->library ('upload');
		$this->assign_libs   ('upload');
		// Default configuration
		$conf['max_size']      = $this->max_size;
		$conf['upload_path']   = $this->upload_path;
		$conf['allowed_types'] = '';
		$conf['xss_clean']     = false;
		$conf['remove_space']  = true;
		// Run all uploads
		$uploads = array();
		foreach ($this->upload_rules as $name => $allow) {
			$conf['allowed_types'] = $allow;
			$this->upload->initialize ($conf);
			if ($this->upload->do_upload ($name))
				$this->uploads[$name] = $this->upload->data();
			elseif ($this->is_required ($name)) {
				$this->has_error = true;
				$error = $this->upload->display_errors ('<p class="error">', '</p>');
				$this->elements[$name]['error'] = $error;
			}
		}
		
		$this->has_error = isset($error) ? true : false;
		return (! $this->has_error);
	}
	
	public function populate ($return = false) {
		$this->assign_libs ('input');
		$return = (bool) $return;
		$values = array();
		foreach (array_keys($this->elements) as $name) {
			// The key we will set
			$key = 'value';
			// Retrieve value for this input 
			if ($this->validated)
				$value = $this->input->post ($name);
			else
				$value = $this->elements[$name]['value'];
			// Some elements require a different key to be set
			switch ($this->elements[$name]['type']) {
				// This one is strange because we have to return the value of checkbox,
				// but set the "checked" status checking the POST against the set value
				case 'checkbox':
					$retval = $this->elements[$name]['value'];
					// Set checked only if the POST is the same as value
					if ($this->validated) $this->elements[$name]['checked'] = 
						($value === $retval) ? true : false;
					// We always set the value to the previous value
					$value = $retval;
					// If the element is not checked, we return false
					if ($this->elements[$name]['checked'] == false) $retval = false;
				break;
				case 'dropdown': $key = 'selected'; break;
				case 'radio':    $key = 'selected'; break;
				case 'submit': $value = $this->elements[$name]['value']; break;
				case 'file':  $retval = $this->uploads[$name]; break;
			}
			// If specific return is set, use $value
			if (! isset ($retval)) $retval = $value;
			// Set the value of this input
			$this->elements[$name][$key] = $value;
			$values[$name] = $retval;
			// Don't leave garbage around
			unset($retval);
		}
		
		return ($return ? $values : true);
	}
	
	public function post_data ($print = false) {
		// Run validation if it hasn't already
		if ($this->validated == false) $this->validate();
		// Fetch POST data
		$post = $this->populate (true);
		// Print request outputs the POST data
		if ($print == true) {
			$pre = '';
			foreach ($post as $key => $val)
				$pre .= sprintf ("%20s: %s \n", $key, print_r($val,true));
			
			return sprintf ("<h6>POST DATA (debug)</h6>\n<pre>\n%s</pre>\n", $pre);
		}
		// If validation causes an error, return nothing
		if ($this->has_error == true) return false;
		
		return $this->populate (true);
	}
	
	// XML FORM PARSING FUNCTIONS ---------------------------------------------
	
	public function load ($name) {
		// We can't load XML via load_class, because it prefixes it with CI_
		$obj =& get_instance();
		$obj->load->library ('xml');
		// Load the form
		if (! $obj->xml->load ("forms/$name"))
			show_error ("Requested form does not exist: $name.");
		// Parse the XMl into an array
		$data = $obj->xml->parse();
		if (! is_array($data)) 
			show_error ("Requested form could not be parsed: $name.");

		$data = $data['form'][0];
		// Set up our configuration
		$conf['action']    = $data['__attrs']['action'];
		$conf['form_name'] = $data['fieldset'][0]['__attrs']['name'];
		// Initialize our shiny form
		$this->initialize ($conf);
		// Build the tree, extract inputs
		$this->get_fields ($data['fieldset'][0]);
		
		return true;
	}
	
	private function get_fields ($array) {
		foreach ($array as $name => $val) {
			if ($name == 'fieldset') {
				// Add fieldsets into the tree
				foreach ($val as $set) {
					// Get a name and a legend for recursion
					$legend = ucfirst ($set['__attrs']['name']);
					$text   = @$set['__attrs']['text'];
					$name   = url_title ($legend, 'underscore');
					// Create new fieldset and build it
					$this->new_group  ($name, $legend);
					$this->set_group_text ($name, $text);
					$this->get_fields ($set);
					// Switch to previous fieldset
					$this->prev_group();
				}
			} elseif (! is_numeric ($key) && $key != '__attrs') {
				// Add inputs
				foreach ($val as $data) {
					if (! is_array ($data)) continue;
					// Parse attributes
					if ($attrs = array_remove ('__attrs', $data)) {
						$rules = array_remove ('rules', $attrs);
						$allow = array_remove ('allow', $attrs);
						// Add allowed file types into rules
						if ($allow != false) {
							$pipe  = (strlen ($rules) > 1) ? '|' : '';
							$allow = str_replace ('|', ',', $allow);
							$rules = "{$rules}{$pipe}allow[{$allow}]";
						}
						// Have to use an zero index because of next fix
						if ($rules != false) $data['rules'][0] = $rules;
					}
					// Fix indexing
					foreach ($data as $k => $v) {
						$val = $v[0];
						// Options need to be to shifted
						if ($k == 'options') $val = array_map('array_shift', $val);
						$data[$k] = $val;
					}
					// Add the input
					$this->add ($name, $data);
				}
			}
		}
	}
	
	// LEGACY ALIASES
	
	public function disable ($name) { return $this->remove($name); }
	
	// PRIVATE HELPER FUNCTIONS -----------------------------------------------
	
	private function assign_libs() {
		static $loaded;
		
		$libs = func_get_args();
		if (count ($libs) > 0) {
			$obj =& get_instance();
			foreach ($libs as $lib) {
				if (isset ($loaded[$lib])) continue;
				if (isset ($obj->$lib) && is_object($obj->$lib)) {
					$this->$lib =& $obj->$lib;
					$loaded[$lib] = true;
				}
			}
		}
	}
	
	private function filter_data ($name, &$data) {
		if (! is_array($data)) $data = array();
		if (! isset ($data['type'])) $data['type'] = 'text';
		
		if ($rules = array_remove ('rules', $data)) {
			$rules = explode ('|', $rules);
			$this->set_rules ($name, $rules);
		}
		
		$filter = array();
		foreach ($this->attributes as $a) 
			if (isset ($data[$a])) $filter[$a] = $data[$a];
		
		$data = $filter;
	}
	
	private function is_required ($name) {
		// No rules? Not required.
		if (! isset ($this->rules[$name])) return false;
		// Check if required
		return ((strpos ($this->rules[$name], 'required') !== false) ? true : false);
	}
	
	private function switch_group ($index) {
		if ($index == false) return;
		// Compile selection
		$switch = '$this->group =& $this->tree'.$index.';';
		// Unset current group
		unset ($this->group);
		// Make our switch
		@eval($switch);
		// Save group name
		$this->group_name = $index;
	}
	
	private function tree_remove ($name, &$array) {
		foreach ($array as $key => $val) {
			// Recursive FTW!
			if (is_array ($val)) {
				$this->tree_remove ($name, $array[$key]);
				// No elements means we can remove the group
				if (count ($array[$key]) < 1) unset ($array[$key], $this->tree[$key]);
				continue;
			}
			// Kill it!
			if ($val == $name) unset ($array[$key]);
		}
	}
	
	private function build_fieldset ($set, $elements, $groups, $first = false) {
		// Recursive for the win!
		static $tabs;

		if ($tabs  === null) $tabs = 1;
		foreach ($set as $g => $e) {
			if (is_array ($e)) {
				if ($first == false) printf ('%s<li>%s', repeater ("\t", $tabs++), "\n");
				printf ('%s<fieldset class="%s"><legend>%s</legend>%s', repeater ("\t", $tabs++), $g, $groups[$g]['legend'], "\n");
				if (isset($groups[$g]['text'])) printf ('%s<p>%s</p>%s', repeater ("\t", $tabs+1), $groups[$g]['text'], "\n");
			}

			if ($groups[$g]['type'] == 'table') {
				$table = array();
				foreach ($groups[$g]['cols'] as $group) $cols[] = $group['label'];
				$table[] = $cols;
				foreach ($e as $_e) {
					$t = array();
					$row = $elements[$_e];
					$t[] = $row['label'];
					foreach ($row['input'] as $col) $t[] = $col;
					$table[] = $t;
				}

				printf ('%s%s%s', repeater ("\t", $tabs), indent ($tabs, $this->table->generate($table)), "\n");
			} else {
				if (is_array ($e)) {
					printf ('%s<ol class="layout">%s', repeater ("\t", $tabs), "\n");

					$tabs++; $this->build_fieldset ($e, $elements, $groups, $obj); $tabs--;

					printf ('%s</ol>%s', repeater ("\t", $tabs), "\n");
				} else {
					$label = $elements[$e]['label'];
					$input = $elements[$e]['input'];

					if (strpos ($input, 'type="hidden"') === false)
						printf ('%s<li class="row">%s %s</li>%s', repeater ("\t", $tabs), $label, $input, "\n");
					else
						printf ('%s%s%s', repeater ("\t", $tabs), $input, "\n");
				}
			}

			if (is_array ($e)) {
				$tabs--;
				printf ('%s</fieldset>%s', repeater ("\t", $tabs), "\n");
				if ($first == false) { $tabs--; printf ('%s</li>%s', repeater ("\t", $tabs), "\n"); }
			}
		}
	}
	
	// META FUNCTIONS ---------------------------------------------------------
	
	public function citystatezip ($name = false, $data = false, $label = false) {
		// These variables will need to be defined
		$required = array ('city', 'state', 'zip', 'plus4');
		foreach ($required as $req)
			if (! isset ($data[$req])) $data[$req] = '';
		
		// Sanity checks
		$name = ($name  == false) ? 'location' : $name;
		// Create a label
		if ($label !== false) $label = $this->label ($name, $label);
		// Set the basename of the input
		$bname = "{$name}[%s]";
		
		extract ($data, EXTR_OVERWRITE);
		list ($zip, $plus4) = explode ('-', valid_zip ($zip, (bool)$plus4));
		
		// Create City input
		$input = array();
		$input['size']  = 20;
		$input['value'] = valid_city ($city);
		$input['label'] = ($label == false) ? 'City/State/Zip' : $label;
		$city = $this->input ('text', sprintf($bname, 'city'), $input);
		
		// Create State dropdown
		$input = array();
		$state = valid_state ($state);
		$input['options']  = array_keys (valid_states());
		$input['options']  = array_combine ($input['options'], $input['options']);
		$input['selected'] = valid_state ($state);
		$state = $this->input ('dropdown', sprintf($bname, 'state'), $input);
		
		// Create ZIP code input
		$input = array();
		$input['size']  = 5;
		$input['value'] = $zip;
		$input['maxlength'] = 5;
		$zip = $this->input ('text', sprintf($bname, 'zip'), $input);
		
		// Create plus4 input and append it to the zip input
		if ($zip != false && $plus4 != false) {
			$input = array();
			$input['size'] = 4;
			$input['value'] = $plus4;
			$input['maxlength'] = 4;
			$plus4 = $this->input ('text', sprintf($bname, 'plus4'), $input);
			$plus4 = $plus4['input'];
			$zip['input'] .= "-$plus4";
		}
		
		// Compile our label and input
		$return = $city['input'].' '.$state['input'].' '.$zip['input'];
		if ($label !== false) {
			$r['label'] = $city['label'];
			$r['input'] = $return;
			$return = $r;
		}
		
		return $return;
	}
	
	public function telephone ($name = false, $number = false, $label = false) {
		// Sanity checks
		$name   = ($name  == false) ? 'telephone'    : $name;
		$number = ($number == true) ? valid_phone ($number, '-') : '--';
		// Create the label
		if ($label !== false) $label = $this->label ($name, $label);
		// Set our basename for the inputs
		$bname = "{$name}[%s]";
		
		// Extract the area code, prefix, and digits
		list ($area, $prefix, $digits) = explode ('-', $number);
		
		// Create the area, prefix, and digit inputs
		$inputs = array('area' => 3, 'prefix' => 3, 'digits' => 4);
		foreach ($inputs as $name => $size) {
			$input['size']  = $size;
			$input['value'] = $$name;
			$input['maxlength'] = $size;
			// We only want the input part
			$$name = array_remove ('input', $this->input ('text', sprintf ($bname, $name), $input));
		}
		
		// Compile a label and input
		$return = "($area) $prefix-$digits";
		if ($label !== false) {
			$r['label'] = $label;
			$r['input'] = $return;
			$return = $r;
		}
		
		return $return;
	}
	
	public function dateselect ($name = false, $data = false, $label = false) {
		// These variables will need to be defined
		$required = array ('month', 'day', 'year');
		foreach ($required as $req)
			if (! isset ($data[$req])) $data[$req] = '';
		
		// Sanity checks
		$name = ($name  == false) ? 'yearmonthday' : $name;
		// Create a label
		if ($label !== false) $label = $this->label ($name, $label);
		// Set the basename of the input
		$bname = "{$name}[%s]";
		
		extract ($data, EXTR_OVERWRITE);
		// Valid month
		if ($month !== '') {
			$month = (int) substr($month, 0, 2);
			$month = ($month > 12 || $month < 1) ? 1 : $month;
		} 
		else $month = date('n'); // This month
		// Valid year
		if ($year !== '') {
			$year = (int) substr($year, 0, 4);
			// If the year is only two digits long, and greater than the current last two
			// digits of this year, we make the year 19XX, if not, 20XX
			$year = (strlen ($year) < 4) ? ((substr ($year, -2, 2) > date('y')) ? "19$year" : "20$year") : $year;
		}
		else $year = date('Y'); // This year
		// Valid day
		if ($day !== '') {
			$day = (int) substr($day, 0, 2);
			// If the day is greater than the possible number of days for $month,
			// we change the day to the maximum possible date for $month
			$max = date('t', mktime(1, 0, 0, $month, 1, $year));
			$day = ($day > $max) ? $max : $day;
		}
		else $day = date('j'); // This day
		// Create dropdown options
		// Months
		for ($i=1;$i<13;$i++) $months[$i] = date('F', mktime(1, 0, 0, $i, 1, $year));
		// Years
		for ($i=date('Y');$i>1900;$i--) $years[$i] = $i;
		// Days
		for ($i=1;$i<32;$i++) $days[$i] = $i;
		
		// Create month dropdown
		$input['options']  = $months;
		$input['selected'] = $month;
		$input['label'] = ($label == false) ? 'Month/Day/Year' : $label;
		$month = $this->input ('dropdown', sprintf($bname, 'month'), $input);
		$input = array();
		// Create day dropdown
		$input['options']  = $days;
		$input['selected'] = $day;
		$day = $this->input ('dropdown', sprintf($bname, 'day'), $input);
		// Create year dropdown
		$input['options']  = $years;
		$input['selected'] = $year;
		$year = $this->input ('dropdown', sprintf($bname, 'year'), $input);
		// Compile our label and input
		$return['input'] = $month['input'].' '.$day['input'].' '.$year['input'];
		$return['label'] = $month['label'];
		// No label? Then just return the input
		if ($label == false) $return = $return['input'];
		
		return $return;
	}
	
	public function submit_cancel ($submit = false, $cancel = false) {
		$input = array();
		$input['value'] = ($submit == false) ? 'Submit' : $submit;
		$name = url_title ($submit, 'underscore');
		$submit = $this->input ('submit', $name, $input);
		
		$input = array();
		$input['value'] = ($cancel == false) ? 'Cancel' : $cancel;
		$name = url_title ($submit, 'underscore');
		$cancel = $this->input ('submit', $name, $input);
		
		return "$submit $cancel";
	}
	
	
}

if (! function_exists ('array_remove')) {
	function array_remove ($key, &$array) {
		// @variable: string $key
		// @variable: array &$array
		// @return: value of $array[$key]
		if (! is_array ($array))    return false;
		if (! isset ($array[$key])) return false;

		$return = $array[$key];
		unset ($array[$key]);

		return $return;
	}
}

if (! function_exists ('array_unshift_assoc')) {
	function array_unshift_assoc ($array, $key, $val = false) {
		$array = array_reverse ($array, true);
		$array[$key] = $val;
		$array = array_reverse ($array, true);
		// Return array
		return $array;
	} 
}

if (! function_exists ('strtoarray')) {
	function strtoarray ($string) {
		$return = false;
		// Matches a string "like[this]" or "maybe[7,h,1,5]"
		if (preg_match ('@(\w+)\[([\w\d,]+)\]@', $string, $matches)) {
			$vars = explode (',', $matches[2]);
			// Compile array
			while ($var = array_shift ($vars)) $array[] = $var;
			// Valid return
			$return = $array;
		}

		return $return;
	}
}

?>