PageRenderTime 68ms CodeModel.GetById 36ms RepoModel.GetById 0ms app.codeStats 0ms

/application/datamapper/htmlform.php

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