PageRenderTime 103ms CodeModel.GetById 37ms RepoModel.GetById 3ms app.codeStats 0ms

/application/datamapper/htmlform.php

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