PageRenderTime 118ms CodeModel.GetById 40ms app.highlight 41ms RepoModel.GetById 30ms app.codeStats 0ms

/application/datamapper/htmlform.php

https://bitbucket.org/alexdeoliveira/ignitercms
PHP | 798 lines | 592 code | 65 blank | 141 comment | 90 complexity | 348a0f17d0a44e84c19218128004331b MD5 | raw file
  1<?php
  2
  3
  4/**
  5 * HTMLForm Extension for DataMapper classes.
  6 *
  7 * A powerful extension that allows one to quickly
  8 * generate standardized forms off a DMZ object.
  9 *
 10 * @license 	MIT License
 11 * @package		DMZ-Included-Extensions
 12 * @category	DMZ
 13 * @author  	Phil DeJarnett
 14 * @link    	http://www.overzealous.com/dmz/pages/extensions/htmlform.html
 15 * @version 	1.0
 16 */
 17
 18// --------------------------------------------------------------------------
 19
 20/**
 21 * DMZ_HTMLForm Class
 22 *
 23 * @package		DMZ-Included-Extensions
 24 */
 25class DMZ_HTMLForm {
 26
 27	// this is the default template (view) to use for the overall form
 28	var $form_template = 'dmz_htmlform/form';
 29	// this is the default template (view) to use for the individual rows
 30	var $row_template = 'dmz_htmlform/row';
 31	// this is the default template (view) to use for the individual rows
 32	var $section_template = 'dmz_htmlform/section';
 33
 34	var $auto_rule_classes = array(
 35		'integer' => 'integer',
 36		'numeric' => 'numeric',
 37		'is_natural' => 'natural',
 38		'is_natural_no_zero' => 'positive_int',
 39		'valid_email' => 'email',
 40		'valid_ip' => 'ip',
 41		'valid_base64' => 'base64',
 42		'valid_date' => 'date',
 43		'alpha_dash_dot' => 'alpha_dash_dot',
 44		'alpha_slash_dot' => 'alpha_slash_dot',
 45		'alpha' => 'alpha',
 46		'alpha_numeric' => 'alpha_numeric',
 47		'alpha_dash' => 'alpha_dash',
 48		'required' => 'required'
 49	);
 50
 51	function __construct($options = array(), $object = NULL) {
 52
 53		if (is_array($options) )
 54		{
 55			foreach($options as $k => $v)
 56			{
 57				$this->{$k} = $v;
 58			}
 59		}
 60
 61		$this->CI =& get_instance();
 62		$this->load = $this->CI->load;
 63	}
 64
 65	// --------------------------------------------------------------------------
 66
 67	/**
 68	 * Render a single field.  Can be used to chain together multiple fields in a column.
 69	 *
 70	 * @param object $object The DataMapper Object to use.
 71	 * @param string $field The field to render.
 72	 * @param string $type  The type of field to render.
 73	 * @param array  $options  Various options to modify the output.
 74	 * @return Rendered String.
 75	 */
 76	function render_field($object, $field, $type = NULL, $options = NULL)
 77	{
 78		$value = '';
 79
 80		if(array_key_exists($field, $object->has_one) || array_key_exists($field, $object->has_many))
 81		{
 82			// Create a relationship field
 83			$one = array_key_exists($field, $object->has_one);
 84
 85			// attempt to look up the current value(s)
 86			if( ! isset($options['value']))
 87			{
 88				if($this->CI->input->post($field))
 89				{
 90					$value = $this->CI->input->post($field);
 91				}
 92				else
 93				{
 94					// load the related object(s)
 95					$sel = $object->{$field}->select('id')->get();
 96					if($one)
 97					{
 98						// only a single value is allowed
 99						$value = $sel->id;
100					}
101					else
102					{
103						// save what might be multiple values
104						$value = array();
105						foreach($sel as $s)
106						{
107							$value[] = $s->id;
108						}
109					}
110				}
111
112			}
113			else
114			{
115				// value was already set in the options
116				$value = $options['value'];
117				unset($options['value']);
118			}
119
120			// Attempt to get a list of possible values
121			if( ! isset($options['list']) || is_object($options['list']))
122			{
123				if( ! isset($options['list']))
124				{
125					// look up all of the related values
126					$c = get_class($object->{$field});
127					$total_items = new $c;
128					// See if the custom method is defined
129					if(method_exists($total_items, 'get_htmlform_list'))
130					{
131						// Get customized list
132						$total_items->get_htmlform_list($object, $field);
133					}
134					else
135					{
136						// Get all items
137						$total_items->get_iterated();
138					}
139				}
140				else
141				{
142					// process a passed-in DataMapper object
143					$total_items = $options['list'];
144				}
145				$list = array();
146				foreach($total_items as $item)
147				{
148					// use the __toString value of the item for the label
149					$list[$item->id] = (string)$item;
150				}
151				$options['list'] = $list;
152			}
153
154			// By if there can be multiple items, use a dropdown for large lists,
155			// and a set of checkboxes for a small one.
156			if($one || count($options['list']) > 6)
157			{
158				$default_type = 'dropdown';
159				if( ! $one && ! isset($options['size']))
160				{
161					// limit to no more than 8 items high.
162					$options['size'] = min(count($options['list']), 8);
163				}
164			}
165			else
166			{
167				$default_type = 'checkbox';
168			}
169		}
170		else
171		{
172			// attempt to look up the current value(s)
173			if( ! isset($options['value']))
174			{
175				if($this->CI->input->post($field))
176				{
177					$value = $this->CI->input->post($field);
178					// clear default if set
179					unset($options['default_value']);
180				}
181				else
182				{
183					if(isset($options['default_value']))
184					{
185						$value = $options['default_value'];
186						unset($options['default_value']);
187					}
188					else
189					{
190						// the field IS the value.
191						$value = $object->{$field};
192					}
193				}
194
195			}
196			else
197			{
198				// value was already set in the options
199				$value = $options['value'];
200				unset($options['value']);
201			}
202			// default to text
203			$default_type = ($field == 'id') ? 'hidden' : 'text';
204
205			// determine default attributes
206			$a = array();
207			// such as the size of the field.
208			$max = $this->_get_validation_rule($object, $field, 'max_length');
209			if($max === FALSE)
210			{
211				$max = $this->_get_validation_rule($object, $field, 'exact_length');
212			}
213			if($max !== FALSE)
214			{
215				$a['maxlength'] = $max;
216				$a['size'] = min($max, 30);
217			}
218			$list = $this->_get_validation_info($object, $field, 'values', FALSE);
219			if($list !== FALSE)
220			{
221				$a['list'] = $list;
222			}
223			$options = $options + $a;
224			$extra_class = array();
225
226			// Add any of the known rules as classes (for JS processing)
227			foreach($this->auto_rule_classes as $rule => $c)
228			{
229				if($this->_get_validation_rule($object, $field, $rule) !== FALSE)
230				{
231					$extra_class[] = $c;
232				}
233			}
234
235			// add or set the class on the field.
236			if( ! empty($extra_class))
237			{
238				$extra_class = implode(' ', $extra_class);
239				if(isset($options['class']))
240				{
241					$options['class'] .= ' ' . $extra_class;
242				}
243				else
244				{
245					$options['class'] = $extra_class;
246				}
247			}
248		}
249
250		// determine the renderer type
251		$type = $this->_get_type($object, $field, $type);
252		if(empty($type))
253		{
254			$type = $default_type;
255		}
256
257		// attempt to find the renderer function
258		if(method_exists($this, '_input_' . $type))
259		{
260			return $this->{'_input_' . $type}($object, $field, $value, $options);
261		}
262		else if(function_exists('input_' . $type))
263		{
264			return call_user_func('input_' . $type, $object, $field, $value, $options);
265		}
266		else
267		{
268			log_message('error', 'FormMaker: Unable to find a renderer for '.$type);
269			return '<span style="color: Maroon; background-color: White; font-weight: bold">FormMaker: UNABLE TO FIND A RENDERER FOR '.$type.'</span>';
270		}
271
272	}
273
274	// --------------------------------------------------------------------------
275
276	/**
277	 * Render a row with a single field.  If $field does not exist on
278	 * $object->validation, then $field is output as-is.
279	 *
280	 * @param object $object The DataMapper Object to use.
281	 * @param string $field The field to render (or content)
282	 * @param string $type  The type of field to render.
283	 * @param array  $options  Various options to modify the output.
284	 * @param string $row_template  The template to use, or NULL to use the default.
285	 * @return Rendered String.
286	 */
287	function render_row($object, $field, $type = NULL, $options = array(), $row_template = NULL)
288	{
289		// try to determine type automatically
290		$type = $this->_get_type($object, $field, $type);
291
292		if( ! isset($object->validation[$field]) && (empty($type) || $type == 'section' || $type == 'none'))
293		{
294			// this could be a multiple-field row, or just some text.
295			// if $type is 'section, it will be rendered using the section template.
296			$error = '';
297			$label = '';
298			$content = $field;
299			$id = NULL;
300		}
301		else
302		{
303			// use validation information to render the field.
304			$content = $this->render_field($object, $field, $type, $options);
305			if(empty($row_template))
306			{
307				if($type == 'hidden' || $field == 'id')
308				{
309					$row_template = 'none';
310				}
311				else
312				{
313					$row_template = $this->row_template;
314				}
315			}
316			// determine if there is an existing error
317			$error = isset($object->error->{$field}) ? $object->error->{$field} : '';
318			// determine if there is a pre-defined label
319			$label = $this->_get_validation_info($object, $field, 'label', $field);
320			// the field IS the id
321			$id = $field;
322		}
323
324		$required = $this->_get_validation_rule($object, $field, 'required');
325
326		// Append these items.  Values in $options have priority
327		$view_data = $options + array(
328			'object' => $object,
329			'content' => $content,
330			'field' => $field,
331			'label' => $label,
332			'error' => $error,
333			'id' => $id,
334			'required' => $required
335		);
336
337		if(is_null($row_template))
338		{
339			if(empty($type))
340			{
341				$row_template = 'none';
342			}
343			else if($type == 'section')
344			{
345				$row_template = $this->section_template;
346			}
347			else
348			{
349				$row_template = $this->row_template;
350			}
351		}
352
353		if($row_template == 'none')
354		{
355			return $content;
356		}
357		else
358		{
359			return $this->load->view($row_template, $view_data, TRUE);
360		}
361	}
362
363	// --------------------------------------------------------------------------
364
365	/**
366	 * Renders an entire form.
367	 *
368	 * @param object $object The DataMapper Object to use.
369	 * @param string $fields An associative array that defines the form.
370	 * @param string $template  The template to use.
371	 * @param string $row_template  The template to use for rows.
372	 * @param array  $template_options  The template to use for rows.
373	 * @return Rendered String.
374	 */
375	function render_form($object, $fields, $url = '', $options = array(), $template = NULL, $row_template = NULL)
376	{
377		if(empty($url))
378		{
379			// set url to current url
380			$url =$this->CI->uri->uri_string();
381		}
382
383		if(is_null($template))
384		{
385			$template = $this->form_template;
386		}
387
388		$rows = '';
389		foreach($fields as $field => $field_options)
390		{
391			$rows .= $this->_render_row_from_form($object, $field, $field_options, $row_template);
392		}
393
394		$view_data = $options + array(
395			'object' => $object,
396			'fields' => $fields,
397			'url' => $url,
398			'rows' => $rows
399		);
400
401		return $this->load->view($template, $view_data, TRUE);
402	}
403
404	// --------------------------------------------------------------------------
405	// Private Methods
406	// --------------------------------------------------------------------------
407
408	// Converts information from render_form into a row of objects.
409	function _render_row_from_form($object, $field, $options, $row_template, $row = TRUE)
410	{
411		if(is_int($field))
412		{
413			// simple form, or HTML-content
414			$field = $options;
415			$options = NULL;
416		}
417		if(is_null($options))
418		{
419			// always have an array for options
420			$options = array();
421		}
422
423		$type = '';
424		if( ! is_array($options))
425		{
426			// if options is a single string, assume it is the type.
427			$type = $options;
428			$options = array();
429		}
430
431		if(isset($options['type']))
432		{
433			// type was set in options
434			$type = $options['type'];
435			unset($options['type']);
436		}
437
438		// see if a different row_template was in the options
439		$rt = $row_template;
440		if(isset($options['template']))
441		{
442			$rt = $options['template'];
443			unset($options['template']);
444		}
445
446		// Multiple fields, render them all as one.
447		if(is_array($field))
448		{
449			if(isset($field['row_options']))
450			{
451				$options = $field['row_options'];
452				unset($field['row_options']);
453			}
454			$ret = '';
455			$sep = ' ';
456			if(isset($field['input_separator']))
457			{
458				$sep = $field['input_separator'];
459				unset($field['input_separator']);
460			}
461			foreach($field as $f => $fo)
462			{
463				// add each field to a list
464				if( ! empty($ret))
465				{
466					$ret .= $sep;
467				}
468				$ret .= $this->_render_row_from_form($object, $f, $fo, $row_template, FALSE);
469			}
470
471			// renders into a row or field below.
472			$field = $ret;
473		}
474		if($row)
475		{
476			// if row is set, render the whole row.
477			return $this->render_row($object, $field, $type, $options, $rt);
478		}
479		else
480		{
481			// render just the field.
482			return $this->render_field($object, $field, $type, $options);
483		}
484	}
485
486	// --------------------------------------------------------------------------
487
488	// Attempts to look up the field's type
489	function _get_type($object, $field, $type)
490	{
491		if(empty($type))
492		{
493			$type = $this->_get_validation_info($object, $field, 'type', NULL);
494		}
495		return $type;
496	}
497
498	// --------------------------------------------------------------------------
499
500	// Returns a field from the validation array
501	function _get_validation_info($object, $field, $val, $default = '')
502	{
503		if(isset($object->validation[$field][$val]))
504		{
505			return $object->validation[$field][$val];
506		}
507		return $default;
508	}
509
510	// --------------------------------------------------------------------------
511
512	// Returns the value (or TRUE) of the validation rule, or FALSE if it does not exist.
513	function _get_validation_rule($object, $field, $rule)
514	{
515		$r = $this->_get_validation_info($object, $field, 'rules', FALSE);
516		if($r !== FALSE)
517		{
518			if(isset($r[$rule]))
519			{
520				return $r[$rule];
521			}
522			else if(in_array($rule, $r, TRUE))
523			{
524				return TRUE;
525			}
526		}
527		return FALSE;
528	}
529
530	// --------------------------------------------------------------------------
531	// Input Types
532	// --------------------------------------------------------------------------
533
534	// Render a hidden input
535	function _input_hidden($object, $id, $value, $options)
536	{
537		return $this->_render_simple_input('hidden', $id, $value, $options);
538	}
539
540	// render a single-line text input
541	function _input_text($object, $id, $value, $options)
542	{
543		return $this->_render_simple_input('text', $id, $value, $options);
544	}
545
546	// render a password input
547	function _input_password($object, $id, $value, $options)
548	{
549		if(isset($options['send_value']))
550		{
551			unset($options['send_value']);
552		}
553		else
554		{
555			$value = '';
556		}
557		return $this->_render_simple_input('password', $id, $value, $options);
558	}
559
560	// render a multiline text input
561	function _input_textarea($object, $id, $value, $options)
562	{
563		if(isset($options['value']))
564		{
565			$value = $options['value'];
566			unset($options['value']);
567		}
568		$a = $options + array(
569			'name' => $id,
570			'id' => $id
571		);
572		return $this->_render_node('textarea', $a, htmlspecialchars($value));
573	}
574
575	// render a dropdown
576	function _input_dropdown($object, $id, $value, $options)
577	{
578		$list = $options['list'];
579		unset($options['list']);
580		$selected = $value;
581		if(isset($options['value']))
582		{
583			$selected = $options['value'];
584			unset($options['value']);
585		}
586		if( ! is_array($selected))
587		{
588			$selected = array($selected);
589		}
590		else
591		{
592			// force multiple
593			$options['multiple'] = 'multiple';
594		}
595		$l = $this->_options($list, $selected);
596
597		$name = $id;
598		if(isset($options['multiple']))
599		{
600			$name .= '[]';
601		}
602		$a = $options + array(
603			'name' => $name,
604			'id' => $id
605		);
606		return $this->_render_node('select', $a, $l);
607	}
608
609	// used to render an options list.
610	function _options($list, $sel)
611	{
612		$l = '';
613		foreach($list as $opt => $label)
614		{
615			if(is_array($label))
616			{
617				$l .= '<optgroup label="' . htmlspecialchars($key) . '">';
618				$l .= $this->_options($label, $sel);
619				$l .= '</optgroup>';
620			}
621			else
622			{
623				$a = array('value' => $opt);
624				if(in_array($opt, $sel))
625				{
626					$a['selected'] = 'selected';
627				}
628				$l .= $this->_render_node('option', $a, htmlspecialchars($label));
629			}
630		}
631		return $l;
632	}
633
634	// render a checkbox or series of checkboxes
635	function _input_checkbox($object, $id, $value, $options)
636	{
637		return $this->_checkbox('checkbox', $id, $value, $options);
638	}
639
640	// render a series of radio buttons
641	function _input_radio($object, $id, $value, $options)
642	{
643		return $this->_checkbox('radio', $id, $value, $options);
644	}
645
646	// renders one or more checkboxes or radio buttons
647	function _checkbox($type, $id, $value, $options, $sub_id = '', $label = '')
648	{
649		if(isset($options['value']))
650		{
651			$value = $options['value'];
652			unset($options['value']);
653		}
654		// if there is a list in options, render our multiple checkboxes.
655		if(isset($options['list']))
656		{
657			$list = $options['list'];
658			unset($options['list']);
659			$ret = '';
660			if( ! is_array($value))
661			{
662				if(is_null($value) || $value === FALSE || $value === '')
663				{
664					$value = array();
665				}
666				else
667				{
668					$value = array($value);
669				}
670			}
671			$sep = '<br/>';
672			if(isset($options['input_separator']))
673			{
674				$sep = $options['input_separator'];
675				unset($options['input_separator']);
676			}
677			foreach($list as $k => $v)
678			{
679				if( ! empty($ret))
680				{
681					// put each node on one line.
682					$ret .= $sep;
683				}
684				$ret .= $this->_checkbox($type, $id, $value, $options, $k, $v);
685			}
686			return $ret;
687		}
688		else
689		{
690			// just render the single checkbox.
691			$node_id = $id;
692			if( ! empty($sub_id))
693			{
694				// there are multiple nodes with this id, append the sub_id
695				$node_id .= '_' . $sub_id;
696				$field_value = $sub_id;
697			}
698			else
699			{
700				// sub_id is the same as the node's id
701				$sub_id = $id;
702				$field_value = '1';
703			}
704			$name = $id;
705			if(is_array($value))
706			{
707				// allow for multiple results
708				$name .= '[]';
709			}
710			// node attributes
711			$a = $options + array(
712				'type' => $type,
713				'id' => $node_id,
714				'name' => $name,
715				'value' => $field_value
716			);
717			// if checked wasn't overridden
718			if( ! isset($a['checked']))
719			{
720				// determine if this is a multiple checkbox or not.
721				$checked = $value;
722				if(is_array($checked))
723				{
724					$checked = in_array($sub_id, $value);
725				}
726				if($checked)
727				{
728					$a['checked'] = 'checked';
729				}
730			}
731			$ret = $this->_render_node('input', $a);
732			if( ! empty($label))
733			{
734				$ret .= ' ' . $this->_render_node('label', array('for' => $node_id), $label);
735			}
736			return $ret;
737		}
738	}
739
740	// render a file upload input
741    function _input_file($object, $id, $value, $options)
742    {
743        $a = $options + array(
744			'type' => 'file',
745			'name' => $id,
746			'id' => $id
747		);
748		return $this->_render_node('input', $a);
749    }
750
751	// Utility method to render a normal <input>
752	function _render_simple_input($type, $id, $value, $options)
753	{
754		$a = $options + array(
755			'type' => $type,
756			'name' => $id,
757			'id' => $id,
758			'value' => $value
759		);
760		return $this->_render_node('input', $a);
761	}
762
763	// Utility method to render a node.
764	function _render_node($type, $attributes, $content = FALSE)
765	{
766		// generate node
767		$res = '<' . $type;
768		foreach($attributes as $att => $v)
769		{
770			// the special attribute '_' is rendered directly.
771			if($att == '_')
772			{
773				$res .= ' ' . $v;
774			}
775			else
776			{
777				if($att != 'label')
778				{
779					$res .= ' ' . $att . '="' . htmlspecialchars((string)$v) . '"';
780				}
781			}
782		}
783		// allow for content-containing nodes
784		if($content !== FALSE)
785		{
786			$res .= '>' . $content . '</' . $type .'>';
787		}
788		else
789		{
790			$res .= ' />';
791		}
792		return $res;
793	}
794
795}
796
797/* End of file htmlform.php */
798/* Location: ./application/datamapper/htmlform.php */