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 \"$path\" 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('
%s
%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' => '
'));
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 *" : $label;
$label = sprintf ('', $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 cols 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 \"$type\" 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 ('
', '
');
$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 ('
', '
');
$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 ("
POST DATA (debug)
\n
\n%s
\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