PageRenderTime 28ms CodeModel.GetById 29ms RepoModel.GetById 0ms app.codeStats 0ms

/fuel/core/classes/fieldset.php

https://bitbucket.org/codeyash/bootstrap
PHP | 855 lines | 485 code | 108 blank | 262 comment | 55 complexity | 9c2b04cffbb8e63b1f777ad9c7c069bf MD5 | raw file
Possible License(s): MIT, Apache-2.0
  1. <?php
  2. /**
  3. * Part of the Fuel framework.
  4. *
  5. * @package Fuel
  6. * @version 1.5
  7. * @author Fuel Development Team
  8. * @license MIT License
  9. * @copyright 2010 - 2013 Fuel Development Team
  10. * @link http://fuelphp.com
  11. */
  12. namespace Fuel\Core;
  13. // ------------------------------------------------------------------------
  14. /**
  15. * Fieldset Class
  16. *
  17. * Define a set of fields that can be used to generate a form or to validate input.
  18. *
  19. * @package Fuel
  20. * @category Core
  21. */
  22. class Fieldset
  23. {
  24. /**
  25. * @var Fieldset
  26. */
  27. protected static $_instance;
  28. /**
  29. * @var array contains references to all instantiations of Fieldset
  30. */
  31. protected static $_instances = array();
  32. /**
  33. * Create Fieldset object
  34. *
  35. * @param string Identifier for this fieldset
  36. * @param array Configuration array
  37. * @return Fieldset
  38. */
  39. public static function forge($name = 'default', array $config = array())
  40. {
  41. if ($exists = static::instance($name))
  42. {
  43. \Error::notice('Fieldset with this name exists already, cannot be overwritten.');
  44. return $exists;
  45. }
  46. static::$_instances[$name] = new static($name, $config);
  47. if ($name == 'default')
  48. {
  49. static::$_instance = static::$_instances[$name];
  50. }
  51. return static::$_instances[$name];
  52. }
  53. /**
  54. * Return a specific instance, or the default instance (is created if necessary)
  55. *
  56. * @param string driver id
  57. * @return Fieldset
  58. */
  59. public static function instance($instance = null)
  60. {
  61. if ($instance !== null)
  62. {
  63. if ( ! array_key_exists($instance, static::$_instances))
  64. {
  65. return false;
  66. }
  67. return static::$_instances[$instance];
  68. }
  69. if (static::$_instance === null)
  70. {
  71. static::$_instance = static::forge();
  72. }
  73. return static::$_instance;
  74. }
  75. /**
  76. * @var string instance id
  77. */
  78. protected $name;
  79. /**
  80. * @var string tag used to wrap this instance
  81. */
  82. protected $fieldset_tag = null;
  83. /**
  84. * @var Fieldset instance to which this instance belongs
  85. */
  86. protected $fieldset_parent = null;
  87. /**
  88. * @var array instances that belong to this one
  89. */
  90. protected $fieldset_children = array();
  91. /**
  92. * @var array array of Fieldset_Field objects
  93. */
  94. protected $fields = array();
  95. /**
  96. * @var Validation instance of validation
  97. */
  98. protected $validation;
  99. /**
  100. * @var Form instance of form
  101. */
  102. protected $form;
  103. /**
  104. * @var array configuration array
  105. */
  106. protected $config = array();
  107. /**
  108. * @var array disabled fields array
  109. */
  110. protected $disabled = array();
  111. /**
  112. * @var string name of class providing the tabular form
  113. */
  114. protected $tabular_form_model = null;
  115. /**
  116. * @var string name of the relation of the parent object this tabular form is modeled on
  117. */
  118. protected $tabular_form_relation = null;
  119. /**
  120. * Object constructor
  121. *
  122. * @param string
  123. * @param array
  124. */
  125. protected function __construct($name, array $config = array())
  126. {
  127. if (isset($config['validation_instance']))
  128. {
  129. $this->validation($config['validation_instance']);
  130. unset($config['validation_instance']);
  131. }
  132. if (isset($config['form_instance']))
  133. {
  134. $this->form($config['form_instance']);
  135. unset($config['form_instance']);
  136. }
  137. $this->name = (string) $name;
  138. $this->config = $config;
  139. }
  140. /**
  141. * Get related Validation instance or create it
  142. *
  143. * @param bool|Validation
  144. * @return Validation
  145. */
  146. public function validation($instance = true)
  147. {
  148. if ($instance instanceof Validation)
  149. {
  150. $this->validation = $instance;
  151. return $instance;
  152. }
  153. if (empty($this->validation) and $instance === true)
  154. {
  155. $this->validation = \Validation::forge($this);
  156. }
  157. return $this->validation;
  158. }
  159. /**
  160. * Get related Form instance or create it
  161. *
  162. * @param bool|Form
  163. * @return Form
  164. */
  165. public function form($instance = true)
  166. {
  167. if ($instance instanceof Form)
  168. {
  169. $this->form = $instance;
  170. return $instance;
  171. }
  172. if (empty($this->form) and $instance === true)
  173. {
  174. $this->form = \Form::forge($this);
  175. }
  176. return $this->form;
  177. }
  178. /**
  179. * Set the tag to be used for this fieldset
  180. *
  181. * @param string $tag
  182. * @return Fieldset this, to allow chaining
  183. */
  184. public function set_fieldset_tag($tag)
  185. {
  186. $this->fieldset_tag = $tag;
  187. return $this;
  188. }
  189. /**
  190. * Set the parent Fieldset instance
  191. *
  192. * @param Fieldset parent fieldset to which this belongs
  193. * @return Fieldset
  194. */
  195. public function set_parent(Fieldset $fieldset)
  196. {
  197. if ( ! empty($this->fieldset_parent))
  198. {
  199. throw new \RuntimeException('Fieldset already has a parent, belongs to "'.$this->parent()->name.'".');
  200. }
  201. $children = $fieldset->children();
  202. while ($child = array_shift($children))
  203. {
  204. if ($child === $this)
  205. {
  206. throw new \RuntimeException('Circular reference detected, adding a Fieldset that\'s already a child as a parent.');
  207. }
  208. $children = array_merge($child->children(), $children);
  209. }
  210. $this->fieldset_parent = $fieldset;
  211. $fieldset->add_child($this);
  212. return $this;
  213. }
  214. /**
  215. * Add a child Fieldset instance
  216. *
  217. * @param Fieldset $fieldset
  218. * @return Fieldset
  219. */
  220. protected function add_child(Fieldset $fieldset)
  221. {
  222. if (is_null($fieldset->fieldset_tag))
  223. {
  224. $fieldset->fieldset_tag = 'fieldset';
  225. }
  226. $this->fieldset_children[$fieldset->name] = $fieldset;
  227. return $this;
  228. }
  229. /**
  230. * Factory for Fieldset_Field objects
  231. *
  232. * @param string
  233. * @param string
  234. * @param array
  235. * @param array
  236. * @return Fieldset_Field
  237. */
  238. public function add($name, $label = '', array $attributes = array(), array $rules = array())
  239. {
  240. if ($name instanceof Fieldset_Field)
  241. {
  242. if ($name->name == '' or $this->field($name->name) !== false)
  243. {
  244. throw new \RuntimeException('Fieldname empty or already exists in this Fieldset: "'.$name->name.'".');
  245. }
  246. $name->set_fieldset($this);
  247. $this->fields[$name->name] = $name;
  248. return $name;
  249. }
  250. elseif ($name instanceof Fieldset)
  251. {
  252. if (empty($name->name) or $this->field($name->name) !== false)
  253. {
  254. throw new \RuntimeException('Fieldset name empty or already exists in this Fieldset: "'.$name->name.'".');
  255. }
  256. $name->set_parent($this);
  257. $this->fields[$name->name] = $name;
  258. return $name;
  259. }
  260. if (empty($name) || (is_array($name) and empty($name['name'])))
  261. {
  262. throw new \InvalidArgumentException('Cannot create field without name.');
  263. }
  264. // Allow passing the whole config in an array, will overwrite other values if that's the case
  265. if (is_array($name))
  266. {
  267. $attributes = $name;
  268. $label = isset($name['label']) ? $name['label'] : '';
  269. $rules = isset($name['rules']) ? $name['rules'] : array();
  270. $name = $name['name'];
  271. }
  272. // Check if it exists already, if so: return and give notice
  273. if ($field = $this->field($name))
  274. {
  275. \Error::notice('Field with this name exists already in this fieldset: "'.$name.'".');
  276. return $field;
  277. }
  278. $this->fields[$name] = new \Fieldset_Field($name, $label, $attributes, $rules, $this);
  279. return $this->fields[$name];
  280. }
  281. /**
  282. * Add a new Fieldset_Field before an existing field in a Fieldset
  283. *
  284. * @param string $name
  285. * @param string $label
  286. * @param array $attributes
  287. * @param array $rules
  288. * @param string $fieldname fieldname before which the new field is inserted in the fieldset
  289. * @return Fieldset_Field
  290. */
  291. public function add_before($name, $label = '', array $attributes = array(), array $rules = array(), $fieldname = null)
  292. {
  293. $field = $this->add($name, $label, $attributes, $rules);
  294. // Remove from tail and reinsert at correct location
  295. unset($this->fields[$field->name]);
  296. if ( ! \Arr::insert_before_key($this->fields, array($field->name => $field), $fieldname, true))
  297. {
  298. throw new \RuntimeException('Field "'.$fieldname.'" does not exist in this Fieldset. Field "'.$name.'" can not be added.');
  299. }
  300. return $field;
  301. }
  302. /**
  303. * Add a new Fieldset_Field after an existing field in a Fieldset
  304. *
  305. * @param string $name
  306. * @param string $label
  307. * @param array $attributes
  308. * @param array $rules
  309. * @param string $fieldname fieldname after which the new field is inserted in the fieldset
  310. * @return Fieldset_Field
  311. */
  312. public function add_after($name, $label = '', array $attributes = array(), array $rules = array(), $fieldname = null)
  313. {
  314. $field = $this->add($name, $label, $attributes, $rules);
  315. // Remove from tail and reinsert at correct location
  316. unset($this->fields[$field->name]);
  317. if ( ! \Arr::insert_after_key($this->fields, array($field->name => $field), $fieldname, true))
  318. {
  319. throw new \RuntimeException('Field "'.$fieldname.'" does not exist in this Fieldset. Field "'.$name.'" can not be added.');
  320. }
  321. return $field;
  322. }
  323. /**
  324. * Get Field instance
  325. *
  326. * @param string|null field name or null to fetch an array of all
  327. * @param bool whether to get the fields array or flattened array
  328. * @param bool whether to include tabular form fields in the flattened array
  329. * @return Fieldset_Field|false returns false when field wasn't found
  330. */
  331. public function field($name = null, $flatten = false, $tabular_form = true)
  332. {
  333. if ($name === null)
  334. {
  335. if ( ! $flatten)
  336. {
  337. return $this->fields;
  338. }
  339. $fields = $this->fields;
  340. foreach ($this->fieldset_children as $fs_name => $fieldset)
  341. {
  342. if ($tabular_form or ! $fieldset->get_tabular_form())
  343. {
  344. \Arr::insert_after_key($fields, $fieldset->field(null, true), $fs_name);
  345. }
  346. unset($fields[$fs_name]);
  347. }
  348. return $fields;
  349. }
  350. if ( ! array_key_exists($name, $this->fields))
  351. {
  352. foreach ($this->fieldset_children as $fieldset)
  353. {
  354. if (($field = $fieldset->field($name)) !== false)
  355. {
  356. return $field;
  357. }
  358. }
  359. return false;
  360. }
  361. return $this->fields[$name];
  362. }
  363. /**
  364. * Add a model's fields
  365. * The model must have a method "set_form_fields" that takes this Fieldset instance
  366. * and adds fields to it.
  367. *
  368. * @param string|Object either a full classname (including full namespace) or object instance
  369. * @param array|Object array or object that has the exactly same named properties to populate the fields
  370. * @param string method name to call on model for field fetching
  371. * @return Fieldset this, to allow chaining
  372. */
  373. public function add_model($class, $instance = null, $method = 'set_form_fields')
  374. {
  375. // Add model to validation callables for validation rules
  376. $this->validation()->add_callable($class);
  377. if ((is_string($class) and is_callable($callback = array('\\'.$class, $method)))
  378. || is_callable($callback = array($class, $method)))
  379. {
  380. $instance ? call_user_func($callback, $this, $instance) : call_user_func($callback, $this);
  381. }
  382. return $this;
  383. }
  384. /**
  385. * Sets a config value on the fieldset
  386. *
  387. * @param string
  388. * @param mixed
  389. * @return Fieldset this, to allow chaining
  390. */
  391. public function set_config($config, $value = null)
  392. {
  393. $config = is_array($config) ? $config : array($config => $value);
  394. foreach ($config as $key => $value)
  395. {
  396. if (strpos($key, '.') === false)
  397. {
  398. $this->config[$key] = $value;
  399. }
  400. else
  401. {
  402. \Arr::set($this->config, $key, $value);
  403. }
  404. }
  405. return $this;
  406. }
  407. /**
  408. * Get a single or multiple config values by key
  409. *
  410. * @param string|array a single key or multiple in an array, empty to fetch all
  411. * @param mixed default output when config wasn't set
  412. * @return mixed|array a single config value or multiple in an array when $key input was an array
  413. */
  414. public function get_config($key = null, $default = null)
  415. {
  416. if ($key === null)
  417. {
  418. return $this->config;
  419. }
  420. if (is_array($key))
  421. {
  422. $output = array();
  423. foreach ($key as $k)
  424. {
  425. $output[$k] = $this->get_config($k, $default);
  426. }
  427. return $output;
  428. }
  429. if (strpos($key, '.') === false)
  430. {
  431. return array_key_exists($key, $this->config) ? $this->config[$key] : $default;
  432. }
  433. else
  434. {
  435. return \Arr::get($this->config, $key, $default);
  436. }
  437. }
  438. /**
  439. * Populate the form's values using an input array or object
  440. *
  441. * @param array|object
  442. * @param bool
  443. * @return Fieldset this, to allow chaining
  444. */
  445. public function populate($input, $repopulate = false)
  446. {
  447. $fields = $this->field(null, true, false);
  448. foreach ($fields as $f)
  449. {
  450. if (is_array($input) or $input instanceof \ArrayAccess)
  451. {
  452. if (isset($input[$f->basename]))
  453. {
  454. $f->set_value($input[$f->basename], true);
  455. }
  456. }
  457. elseif (is_object($input) and property_exists($input, $f->basename))
  458. {
  459. $f->set_value($input->{$f->basename}, true);
  460. }
  461. }
  462. // Optionally overwrite values using post/get
  463. if ($repopulate)
  464. {
  465. $this->repopulate();
  466. }
  467. return $this;
  468. }
  469. /**
  470. * Set all fields to the input from get or post (depends on the form method attribute)
  471. *
  472. * @return Fieldset this, to allow chaining
  473. */
  474. public function repopulate()
  475. {
  476. $fields = $this->field(null, true);
  477. foreach ($fields as $f)
  478. {
  479. // Don't repopulate the CSRF field
  480. if ($f->name === \Config::get('security.csrf_token_key', 'fuel_csrf_token'))
  481. {
  482. continue;
  483. }
  484. if (($value = $f->input()) !== null)
  485. {
  486. $f->set_value($value, true);
  487. }
  488. }
  489. return $this;
  490. }
  491. /**
  492. * Build the fieldset HTML
  493. *
  494. * @return string
  495. */
  496. public function build($action = null)
  497. {
  498. $attributes = $this->get_config('form_attributes');
  499. if ($action and ($this->fieldset_tag == 'form' or empty($this->fieldset_tag)))
  500. {
  501. $attributes['action'] = $action;
  502. }
  503. $open = ($this->fieldset_tag == 'form' or empty($this->fieldset_tag))
  504. ? $this->form()->open($attributes).PHP_EOL
  505. : $this->form()->{$this->fieldset_tag.'_open'}($attributes);
  506. $fields_output = '';
  507. // construct the tabular form table header
  508. if ($this->tabular_form_relation)
  509. {
  510. $properties = call_user_func($this->tabular_form_model.'::properties');
  511. $primary_keys = call_user_func($this->tabular_form_model.'::primary_key');
  512. $fields_output .= '<thead><tr>'.PHP_EOL;
  513. foreach ($properties as $field => $settings)
  514. {
  515. if ((isset($settings['skip']) and $settings['skip']) or in_array($field, $primary_keys))
  516. {
  517. continue;
  518. }
  519. if (isset($settings['form']['type']) and ($settings['form']['type'] === false or $settings['form']['type'] === 'hidden'))
  520. {
  521. continue;
  522. }
  523. $fields_output .= "\t".'<th class="'.$this->tabular_form_relation.'_col_'.$field.'">'.(isset($settings['label'])?\Lang::get($settings['label']):'').'</th>'.PHP_EOL;
  524. }
  525. $fields_output .= "\t".'<th>'.\Config::get('form.tabular_delete_label', 'Delete?').'</th>'.PHP_EOL;
  526. $fields_output .= '</tr></thead>'.PHP_EOL;
  527. }
  528. foreach ($this->field() as $f)
  529. {
  530. in_array($f->name, $this->disabled) or $fields_output .= $f->build().PHP_EOL;
  531. }
  532. $close = ($this->fieldset_tag == 'form' or empty($this->fieldset_tag))
  533. ? $this->form()->close($attributes).PHP_EOL
  534. : $this->form()->{$this->fieldset_tag.'_close'}($attributes);
  535. $template = $this->form()->get_config((empty($this->fieldset_tag) ? 'form' : $this->fieldset_tag).'_template',
  536. "\n\t\t{open}\n\t\t<table>\n{fields}\n\t\t</table>\n\t\t{close}\n");
  537. $template = str_replace(array('{form_open}', '{open}', '{fields}', '{form_close}', '{close}'),
  538. array($open, $open, $fields_output, $close, $close),
  539. $template);
  540. return $template;
  541. }
  542. /**
  543. * Enable a disabled field from being build
  544. *
  545. * @return Fieldset this, to allow chaining
  546. */
  547. public function enable($name = null)
  548. {
  549. // Check if it exists. if not, bail out
  550. if ( ! $this->field($name))
  551. {
  552. throw new \RuntimeException('Field "'.$name.'" does not exist in this Fieldset.');
  553. }
  554. if (isset($this->disabled[$name]))
  555. {
  556. unset($this->disabled[$name]);
  557. }
  558. return $this;
  559. }
  560. /**
  561. * Disable a field from being build
  562. *
  563. * @return Fieldset this, to allow chaining
  564. */
  565. public function disable($name = null)
  566. {
  567. // Check if it exists. if not, bail out
  568. if ( ! $this->field($name))
  569. {
  570. throw new \RuntimeException('Field "'.$name.'" does not exist in this Fieldset.');
  571. }
  572. isset($this->disabled[$name]) or $this->disabled[$name] = $name;
  573. return $this;
  574. }
  575. /**
  576. * Magic method toString that will build this as a form
  577. *
  578. * @return string
  579. */
  580. public function __toString()
  581. {
  582. return $this->build();
  583. }
  584. /**
  585. * Return parent Fieldset
  586. *
  587. * @return Fieldset
  588. */
  589. public function parent()
  590. {
  591. return $this->fieldset_parent;
  592. }
  593. /**
  594. * Return the child fieldset instances
  595. *
  596. * @return array
  597. */
  598. public function children()
  599. {
  600. return $this->fieldset_children;
  601. }
  602. /**
  603. * Alias for $this->validation()->input()
  604. *
  605. * @return mixed
  606. */
  607. public function input($field = null)
  608. {
  609. return $this->validation()->input($field);
  610. }
  611. /**
  612. * Alias for $this->validation()->validated()
  613. *
  614. * @return mixed
  615. */
  616. public function validated($field = null)
  617. {
  618. return $this->validation()->validated($field);
  619. }
  620. /**
  621. * Alias for $this->validation()->error()
  622. *
  623. * @return Validation_Error|array
  624. */
  625. public function error($field = null)
  626. {
  627. return $this->validation()->error($field);
  628. }
  629. /**
  630. * Alias for $this->validation()->show_errors()
  631. *
  632. * @return string
  633. */
  634. public function show_errors(array $config = array())
  635. {
  636. return $this->validation()->show_errors($config);
  637. }
  638. /**
  639. * Get the fieldset name
  640. *
  641. * @return string
  642. */
  643. public function get_name()
  644. {
  645. return $this->name;
  646. }
  647. /**
  648. * Enable or disable the tabular form feature of this fieldset
  649. *
  650. * @param string Model on which to define the tabular form
  651. * @param string Relation of the Model on the tabular form is modeled
  652. * @param array Collection of Model objects from a many relation
  653. * @param int Number of empty rows to generate
  654. *
  655. * @return Fieldset this, to allow chaining
  656. */
  657. public function set_tabular_form($model, $relation, $parent, $blanks = 1)
  658. {
  659. // make sure our parent is an ORM model instance
  660. if ( ! $parent instanceOf \Orm\Model)
  661. {
  662. throw new \RuntimeException('Parent passed to set_tabular_form() is not an ORM model object.');
  663. }
  664. // validate the model and relation
  665. // fetch the relations of the parent model
  666. $relations = call_user_func(array($parent, 'relations'));
  667. if ( ! array_key_exists($relation, $relations))
  668. {
  669. throw new \RuntimeException('Relation passed to set_tabular_form() is not a valid relation of the ORM parent model object.');
  670. }
  671. // check for compound primary keys
  672. try
  673. {
  674. // fetch the relations of the parent model
  675. $primary_key = call_user_func($model.'::primary_key');
  676. // we don't support compound primary keys
  677. if (count($primary_key) !== 1)
  678. {
  679. throw new \RuntimeException('set_tabular_form() does not supports models with compound primary keys.');
  680. }
  681. // store the primary key name, we need that later
  682. $primary_key = reset($primary_key);
  683. }
  684. catch (\Exception $e)
  685. {
  686. throw new \RuntimeException('Unable to fetch the models primary key information.');
  687. }
  688. // store the tabular form class name
  689. $this->tabular_form_model = $model;
  690. // and the relation on which we model the rows
  691. $this->tabular_form_relation = $relation;
  692. // load the form config if not loaded yet
  693. \Config::load('form', true);
  694. // load the config for embedded forms
  695. $this->set_config(array(
  696. 'form_template' => \Config::get('form.tabular_form_template', "<table>{fields}</table>\n"),
  697. 'field_template' => \Config::get('form.tabular_field_template', "{field}")
  698. ));
  699. // add the rows to the tabular form fieldset
  700. foreach ($parent->{$relation} as $row)
  701. {
  702. // add the row fieldset to the tabular form fieldset
  703. $this->add($fieldset = \Fieldset::forge($this->tabular_form_relation.'_row_'.$row->{$primary_key}));
  704. // and add the model fields to the row fielset
  705. $fieldset->add_model($model, $row)->set_fieldset_tag(false);
  706. $fieldset->set_config(array(
  707. 'form_template' => \Config::get('form.tabular_row_template', "<table>{fields}</table>\n"),
  708. 'field_template' => \Config::get('form.tabular_row_field_template', "{field}")
  709. ));
  710. $fieldset->add($this->tabular_form_relation.'['.$row->{$primary_key}.'][_delete]', '', array('type' => 'checkbox', 'value' => 1));
  711. }
  712. // and finish with zero or more empty rows so we can add new data
  713. if ( ! is_numeric($blanks) or $blanks < 0)
  714. {
  715. $blanks = 1;
  716. }
  717. for ($i = 0; $i < $blanks; $i++)
  718. {
  719. $this->add($fieldset = \Fieldset::forge($this->tabular_form_relation.'_new_'.$i));
  720. $fieldset->add_model($model)->set_fieldset_tag(false);
  721. $fieldset->set_config(array(
  722. 'form_template' => \Config::get('form.tabular_row_template', "<tr>{fields}</tr>"),
  723. 'field_template' => \Config::get('form.tabular_row_field_template', "{field}")
  724. ));
  725. $fieldset->add($this->tabular_form_relation.'_new['.$i.'][_delete]', '', array('type' => 'checkbox', 'value' => 0, 'disabled' => 'disabled'));
  726. // no required rules on this row
  727. foreach ($fieldset->field() as $f)
  728. {
  729. $f->delete_rule('required', false)->delete_rule('required_with', false);
  730. }
  731. }
  732. return $this;
  733. }
  734. /**
  735. * return the tabular form relation of this fieldset
  736. *
  737. * @return bool
  738. */
  739. public function get_tabular_form()
  740. {
  741. return $this->tabular_form_relation;
  742. }
  743. }