Form.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812
  1. <?php
  2. /**
  3. *
  4. * Cube Framework $Id$ RIf1kofs62tvouScqjMgb/bPsILDv1YlnM0seyFY/N8=
  5. *
  6. * @link http://codecu.be/framework
  7. * @copyright Copyright (c) 2017 CodeCube SRL
  8. * @license http://codecu.be/framework/license Commercial License
  9. *
  10. * @version 1.10 [rev.1.10.01]
  11. */
  12. /**
  13. * form generator class
  14. */
  15. namespace Cube;
  16. use Cube\Form\Element,
  17. Cube\Validate,
  18. Cube\Controller\Front,
  19. Cube\Translate\Adapter\AbstractAdapter as TranslateAdapter;
  20. class Form
  21. {
  22. const METHOD_GET = 'GET';
  23. const METHOD_POST = 'POST';
  24. /**
  25. *
  26. * the action of the form
  27. *
  28. * @var string
  29. */
  30. protected $_action;
  31. /**
  32. *
  33. * the method of the form
  34. *
  35. * @var string method - get|post
  36. */
  37. protected $_method;
  38. /**
  39. *
  40. * form elements
  41. *
  42. * @var array
  43. */
  44. protected $_elements = array();
  45. /**
  46. *
  47. * data resulted from a previous form submit, used to pre-fill the elements
  48. *
  49. * @var array
  50. */
  51. protected $_data = array();
  52. /**
  53. *
  54. * an array of validator messages resulted from the form validation method
  55. *
  56. * @var array
  57. */
  58. protected $_messages = array();
  59. /**
  60. *
  61. * the title of the form
  62. *
  63. * @var string
  64. */
  65. protected $_title;
  66. /**
  67. *
  68. * a description for the form
  69. *
  70. * @var string
  71. */
  72. protected $_description;
  73. /**
  74. *
  75. * view object
  76. *
  77. * @var \Cube\View
  78. */
  79. protected $_view;
  80. /**
  81. *
  82. * the view partial used to display the form
  83. *
  84. * @var string
  85. */
  86. protected $_partial;
  87. /**
  88. *
  89. * translate adapter
  90. *
  91. * @var \Cube\Translate\Adapter\AbstractAdapter
  92. */
  93. protected $_translate;
  94. /**
  95. *
  96. * class constructor
  97. *
  98. * @param string $action the form's action
  99. * @param bool $csrf set whether to add csrf validation to the form
  100. */
  101. public function __construct($action = null, $csrf = true)
  102. {
  103. $this->setAction($action);
  104. if ($csrf === true) {
  105. $this->addElement(new Element\Csrf());
  106. }
  107. }
  108. /**
  109. *
  110. * get form action
  111. *
  112. * @return string
  113. */
  114. public function getAction()
  115. {
  116. return $this->_action;
  117. }
  118. /**
  119. *
  120. * set the action of the form
  121. *
  122. * @param string $action
  123. *
  124. * @return $this
  125. */
  126. public function setAction($action)
  127. {
  128. $this->_action = $action;
  129. return $this;
  130. }
  131. /**
  132. *
  133. * get form method
  134. *
  135. * @return string
  136. */
  137. public function getMethod()
  138. {
  139. return $this->_method;
  140. }
  141. /**
  142. *
  143. * set the method of the form
  144. *
  145. * @param string $method get|post
  146. *
  147. * @return $this
  148. * @throws \InvalidArgumentException
  149. */
  150. public function setMethod($method)
  151. {
  152. $method = strtoupper($method);
  153. if ($method !== self::METHOD_GET && $method !== self::METHOD_POST) {
  154. throw new \InvalidArgumentException(
  155. sprintf("Invalid form method provided, '%s'.", $method));
  156. }
  157. $this->_method = $method;
  158. return $this;
  159. }
  160. /**
  161. *
  162. * get form elements
  163. *
  164. * @return array
  165. */
  166. public function getElements()
  167. {
  168. return $this->_elements;
  169. }
  170. /**
  171. *
  172. * return true if the form has elements, false otherwise
  173. *
  174. * @return bool
  175. */
  176. public function hasElements()
  177. {
  178. return (count($this->_elements) > 0) ? true : false;
  179. }
  180. /**
  181. *
  182. * get a single form element
  183. *
  184. * @param string $name
  185. *
  186. * @return \Cube\Form\Element
  187. * @throws \InvalidArgumentException
  188. */
  189. public function getElement($name)
  190. {
  191. if (isset($this->_elements[$name])) {
  192. return $this->_elements[$name];
  193. }
  194. else {
  195. throw new \InvalidArgumentException(
  196. sprintf("The element with the name '%s' does not exist in the form.", $name));
  197. }
  198. }
  199. /**
  200. *
  201. * check if an element exists in the form
  202. *
  203. * @param string $name
  204. *
  205. * @return bool
  206. */
  207. public function hasElement($name)
  208. {
  209. return (isset($this->_elements[$name])) ? true : false;
  210. }
  211. /**
  212. *
  213. * remove an element from the form
  214. *
  215. * @param string $name
  216. *
  217. * @return $this
  218. */
  219. public function removeElement($name)
  220. {
  221. if (isset($this->_elements[$name])) {
  222. unset($this->_elements[$name]);
  223. }
  224. return $this;
  225. }
  226. /**
  227. *
  228. * remove all elements from the form
  229. *
  230. * @return $this
  231. */
  232. public function clearElements()
  233. {
  234. $this->_elements = array();
  235. return $this;
  236. }
  237. /**
  238. *
  239. * add elements to the form
  240. *
  241. * @param array $elements
  242. *
  243. * @return $this
  244. */
  245. public function addElements(array $elements)
  246. {
  247. foreach ($elements as $element) {
  248. $this->addElement($element);
  249. }
  250. return $this;
  251. }
  252. /**
  253. *
  254. * add a string element to the form
  255. * overwrites an element with the same name
  256. *
  257. * @param \Cube\Form\Element $element
  258. *
  259. * @return $this
  260. * @throws \InvalidArgumentException
  261. */
  262. public function addElement(Element $element)
  263. {
  264. $this->_elements[(string)$element->getName()] = $element;
  265. return $this;
  266. }
  267. /**
  268. *
  269. * method to create a new form element
  270. *
  271. * @param string $element the element type
  272. * @param string $name the name of the element
  273. *
  274. * @return \Cube\Form\Element returns a form element object
  275. */
  276. public function createElement($element, $name)
  277. {
  278. $elementClass = '\\Cube\\Form\\Element\\' . ucfirst($element);
  279. if (class_exists($element)) {
  280. return new $element($name);
  281. }
  282. else if (class_exists($elementClass)) {
  283. return new $elementClass($name);
  284. }
  285. else {
  286. return new Element($element, $name);
  287. }
  288. }
  289. /**
  290. *
  291. * get data
  292. *
  293. * @param string $key
  294. *
  295. * @return mixed
  296. */
  297. public function getData($key = null)
  298. {
  299. if ($key !== null) {
  300. if (!empty($this->_data[$key])) {
  301. return $this->_data[$key];
  302. }
  303. return null;
  304. }
  305. return $this->_data;
  306. }
  307. /**
  308. *
  309. * set the data of the submitted form,
  310. * plus set the submit data for each element in the form
  311. * the data array is filter on a per element basis
  312. *
  313. * @param array $data form data
  314. *
  315. * @return $this
  316. */
  317. public function setData(array $data = null)
  318. {
  319. $this->_data = $data;
  320. /* @var \Cube\Form\Element $element */
  321. foreach ($this->_elements as $element) {
  322. $elementName = $element->getName();
  323. preg_match_all("/\[([^\]]*)\]/", $elementName, $matches);
  324. $matches = array_filter($matches);
  325. $betweenBrackets = null;
  326. if (count($matches) > 0) {
  327. $betweenBrackets = reset($matches[1]);
  328. $elementName = str_replace(reset($matches[0]), '', $elementName);
  329. }
  330. if (array_key_exists($elementName, $this->_data)) {
  331. if (!empty($betweenBrackets)) {
  332. $elementData = isset($this->_data[$elementName][$betweenBrackets]) ?
  333. $this->_data[$elementName][$betweenBrackets] : null;
  334. $element->setData($elementData);
  335. $this->_data[$elementName][$betweenBrackets] = $element->getValue();
  336. }
  337. else {
  338. $element->setData($this->_data[$elementName]);
  339. $this->_data[$elementName] = $element->getValue();
  340. }
  341. }
  342. }
  343. return $this;
  344. }
  345. /**
  346. *
  347. * get the title set for the form
  348. *
  349. * @return string
  350. */
  351. public function getTitle()
  352. {
  353. $translate = $this->getTranslate();
  354. if (null !== $translate) {
  355. return $translate->_($this->_title);
  356. }
  357. return $this->_title;
  358. }
  359. /**
  360. *
  361. * set a title for the form
  362. *
  363. * @param string $title
  364. *
  365. * @return $this
  366. */
  367. public function setTitle($title)
  368. {
  369. $this->_title = (string)$title;
  370. return $this;
  371. }
  372. /**
  373. *
  374. * get the description set for the form
  375. *
  376. * @return string
  377. */
  378. public function getDescription()
  379. {
  380. $translate = $this->getTranslate();
  381. if (null !== $translate) {
  382. return $translate->_($this->_description);
  383. }
  384. return $this->_description;
  385. }
  386. /**
  387. *
  388. * set a description for the form
  389. *
  390. * @param string $description
  391. *
  392. * @return $this
  393. */
  394. public function setDescription($description)
  395. {
  396. $this->_description = (string)$description;
  397. return $this;
  398. }
  399. /**
  400. *
  401. * get the messages resulted from an isValid function
  402. *
  403. * @return array
  404. */
  405. public function getMessages()
  406. {
  407. return $this->_messages;
  408. }
  409. /**
  410. *
  411. * set multiple validation messages
  412. *
  413. * @param array $messages
  414. *
  415. * @return $this
  416. */
  417. public function setMessages(array $messages = null)
  418. {
  419. foreach ($messages as $message) {
  420. $this->setMessage($message);
  421. }
  422. return $this;
  423. }
  424. /**
  425. *
  426. * clear form validator messages
  427. *
  428. * @return $this
  429. */
  430. public function clearMessages()
  431. {
  432. $this->_messages = array();
  433. return $this;
  434. }
  435. /**
  436. *
  437. * add a new validation message, but only if the message is not empty
  438. * also translate the message if a translate adapter is available
  439. *
  440. * @param string $message
  441. */
  442. public function setMessage($message)
  443. {
  444. if (!empty($message)) {
  445. $translate = $this->getTranslate();
  446. if (null !== $translate) {
  447. $message = $translate->_($message);
  448. }
  449. $this->_messages[] = $message;
  450. }
  451. }
  452. /**
  453. *
  454. * get the view object
  455. *
  456. * @return \Cube\View
  457. */
  458. public function getView()
  459. {
  460. if ($this->_view === null) {
  461. $this->setView();
  462. }
  463. return $this->_view;
  464. }
  465. /**
  466. * set the view object
  467. *
  468. * @param \Cube\View $view
  469. *
  470. * @return $this
  471. */
  472. public function setView(View $view = null)
  473. {
  474. if (!$view instanceof View) {
  475. $bootstrap = Front::getInstance()->getBootstrap();
  476. if ($bootstrap->hasResource('view')) {
  477. $view = $bootstrap->getResource('view');
  478. }
  479. else {
  480. $view = new View();
  481. }
  482. }
  483. $this->_view = $view;
  484. return $this;
  485. }
  486. /**
  487. *
  488. * get the view file
  489. *
  490. * @return string
  491. */
  492. public function getPartial()
  493. {
  494. return $this->_partial;
  495. }
  496. /**
  497. *
  498. * set the view partial
  499. *
  500. * @param string $partial
  501. *
  502. * @return $this
  503. */
  504. public function setPartial($partial)
  505. {
  506. $this->_partial = $partial;
  507. return $this;
  508. }
  509. /**
  510. *
  511. * set translate adapter
  512. *
  513. * @param \Cube\Translate\Adapter\AbstractAdapter $translate
  514. *
  515. * @return $this
  516. */
  517. public function setTranslate(TranslateAdapter $translate)
  518. {
  519. $this->_translate = $translate;
  520. return $this;
  521. }
  522. /**
  523. *
  524. * get translate adapter
  525. *
  526. * @return \Cube\Translate\Adapter\AbstractAdapter
  527. */
  528. public function getTranslate()
  529. {
  530. if (!$this->_translate instanceof TranslateAdapter) {
  531. $translate = Front::getInstance()->getBootstrap()->getResource('translate');
  532. if ($translate instanceof Translate) {
  533. $this->setTranslate(
  534. $translate->getAdapter());
  535. }
  536. }
  537. return $this->_translate;
  538. }
  539. /**
  540. *
  541. * checks if the form is valid based on the validators entered for each element
  542. *
  543. * @return bool
  544. */
  545. public function isValid()
  546. {
  547. $valid = true;
  548. /* @var \Cube\Form\Element $element */
  549. foreach ($this->_elements as $element) {
  550. $required = $element->getRequired();
  551. /**
  552. * in case we have an array in the required field, we will check
  553. * for the dependency first to see whether the field is required or not.
  554. */
  555. if (is_array($required)) {
  556. $required = ($this->_data[$required[0]] == $required[1]) ? $required[2] : !$required[2];
  557. }
  558. if ($required === true) {
  559. $element->addValidator(
  560. new Validate\NotEmpty());
  561. }
  562. $valid = ($elementValid = $element->isValid()) ? $valid : false;
  563. if (!$elementValid) {
  564. $this->setMessages(
  565. $element->getMessages());
  566. }
  567. }
  568. return (bool)$valid;
  569. }
  570. /**
  571. *
  572. * renders all hidden elements for the form
  573. * called in the view partial file, usable through the __get() magic method
  574. * Important: by default, multiple hidden elements are not rendered
  575. *
  576. * @param bool $multiple
  577. *
  578. * @return string
  579. */
  580. public function getHiddenElements($multiple = false)
  581. {
  582. $elements = null;
  583. /* @var \Cube\Form\Element $element */
  584. foreach ($this->_elements as $element) {
  585. if ($element->isHidden() && (!$element->getMultiple() || $multiple)) {
  586. $elements .= $element->render() . "\n";
  587. }
  588. }
  589. return $elements;
  590. }
  591. /**
  592. *
  593. * renders the form
  594. * if no action is set, use the request uri
  595. *
  596. * @return string the formatted html
  597. */
  598. public function render()
  599. {
  600. $this->renderHeaderCode();
  601. $this->renderBodyCode();
  602. if (!$this->getAction()) {
  603. $request = Front::getInstance()->getRequest();
  604. $action = $request->getBaseUrl() . $request->getRequestUri();
  605. $this->setAction($action);
  606. }
  607. $view = $this->getView();
  608. $view->set('form', $this);
  609. return $view->process(
  610. $this->getPartial(), true);
  611. }
  612. /**
  613. *
  614. * when called, it will get the header code from all elements
  615. * and append it to the Script view helper
  616. *
  617. * @return string
  618. */
  619. public function renderHeaderCode()
  620. {
  621. /* @var \Cube\View\Helper\Script $helper */
  622. $helper = $this->getView()->getHelper('script');
  623. /* @var \Cube\Form\Element $element */
  624. foreach ($this->_elements as $element) {
  625. $elementHeaderCode = $element->getHeaderCode();
  626. foreach ($elementHeaderCode as $code) {
  627. $helper->addHeaderCode($code);
  628. }
  629. }
  630. return $this;
  631. }
  632. /**
  633. *
  634. * when called, it will get the body code from all elements
  635. * and append it to the Script view helper
  636. *
  637. * @return string
  638. */
  639. public function renderBodyCode()
  640. {
  641. /* @var \Cube\View\Helper\Script $helper */
  642. $helper = $this->getView()->getHelper('script');
  643. /* @var \Cube\Form\Element $element */
  644. foreach ($this->_elements as $element) {
  645. $elementBodyCode = $element->getBodyCode();
  646. foreach ($elementBodyCode as $code) {
  647. $helper->addBodyCode($code);
  648. }
  649. }
  650. return $this;
  651. }
  652. /**
  653. *
  654. * get magic method, enables <code> echo $form->name </code>
  655. * name will be the name of an element, and when called, it will render it
  656. *
  657. * @param string $name
  658. *
  659. * @return mixed|string the rendered element
  660. * @throws \InvalidArgumentException an error is thrown if the element doesnt exist
  661. */
  662. public function get($name)
  663. {
  664. $method = 'get' . ucfirst($name);
  665. if (method_exists($this, $method)) {
  666. return $this->$method();
  667. }
  668. else if (isset($this->_elements[$name])) {
  669. return $this->_elements[$name]->render();
  670. }
  671. else {
  672. throw new \InvalidArgumentException(
  673. sprintf("The element with the name '%s' does not exist in the form.", $name));
  674. }
  675. }
  676. /**
  677. *
  678. * get magic method, proxy to $this->get($name)
  679. *
  680. * @param string $name
  681. *
  682. * @return string|null
  683. */
  684. public function __get($name)
  685. {
  686. return $this->get($name);
  687. }
  688. /**
  689. *
  690. * toString magic method, enabled <code> echo $form </code>
  691. *
  692. * @return string
  693. */
  694. public function __toString()
  695. {
  696. try {
  697. $render = $this->render();
  698. } catch (\Exception $e) {
  699. $render = 'Form rendering error: ' . $e->getMessage();
  700. }
  701. return $render;
  702. }
  703. }