Form.class.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879
  1. <?php
  2. /**
  3. * OO Forms. This class is used as a base for extending or a direct mechanism to create
  4. * object-based HTML forms.
  5. *
  6. * @author Ironpilot
  7. * @copyright Copywrite (c) 2011, STAPLE CODE
  8. *
  9. * This file is part of the STAPLE Framework.
  10. *
  11. * The STAPLE Framework is free software: you can redistribute it and/or modify
  12. * it under the terms of the GNU Lesser General Public License as published by the
  13. * Free Software Foundation, either version 3 of the License, or (at your option)
  14. * any later version.
  15. *
  16. * The STAPLE Framework is distributed in the hope that it will be useful,
  17. * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  18. * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
  19. * more details.
  20. *
  21. * You should have received a copy of the GNU Lesser General Public License
  22. * along with the STAPLE Framework. If not, see <http://www.gnu.org/licenses/>.
  23. */
  24. class Staple_Form
  25. {
  26. const METHOD_GET = 'GET';
  27. const METHOD_POST = 'POST';
  28. const ENC_APP = 'application/x-www-form-urlencoded';
  29. const ENC_FILE = 'multipart/form-data';
  30. const ENC_TEXT = 'text/plain';
  31. /**
  32. * The action (form submittal) location.
  33. * @var string
  34. */
  35. protected $action;
  36. /**
  37. * Holds the form's method.
  38. * @var string
  39. */
  40. protected $method = "POST";
  41. /**
  42. * The name of the form. This name identifies the form in both the session and on the
  43. * website's HTML. It needs to be unique to any other forms on the website.
  44. * @var string
  45. */
  46. protected $name;
  47. /**
  48. * Stores the EncType of the form.
  49. * @var string
  50. */
  51. protected $enctype;
  52. /**
  53. * Set the HTML target attribute.
  54. * @var string
  55. */
  56. protected $target;
  57. /**
  58. * An array that holds a list of callback functions to be called upon form validation.
  59. * @var array
  60. */
  61. protected $callbacks = array();
  62. /**
  63. * Contains an array of errors from form validation
  64. * @var array
  65. */
  66. protected $errors = array();
  67. /**
  68. * An array of Staple_Form_Element objects, that represent the form fields.
  69. * @var array[Staple_Form_Element]
  70. */
  71. public $fields = array();
  72. /**
  73. * A boolean value that signifys a valid submission of the form.
  74. * @var boolean
  75. */
  76. private $createIdent = true;
  77. /**
  78. * A long identifying value for the form. This is used to identify a valid form submission.
  79. * @var string
  80. */
  81. protected $identifier;
  82. /**
  83. * Boolean value that signifys a submittal of the form on the last HTTP request.
  84. * @var boolean
  85. */
  86. private $submitted = false;
  87. /**
  88. * Holds a list of the HTML classes to apply to the form tag.
  89. * @var array
  90. */
  91. protected $classes = array();
  92. /**
  93. * Holds a title for the form.
  94. * @var string
  95. */
  96. protected $title;
  97. /**
  98. * Holds the form layout name.
  99. * @var string
  100. */
  101. protected $layout;
  102. /**
  103. * Dynamic datastore.
  104. * @var array
  105. */
  106. protected $_store = array();
  107. /**
  108. * @param string $name
  109. * @param string $action
  110. */
  111. final public function __construct($name = NULL, $action = NULL)
  112. {
  113. $this->_start();
  114. if(isset($name))
  115. {
  116. $this->name = $name;
  117. }
  118. if(isset($action))
  119. {
  120. $this->action = $action;
  121. }
  122. if(isset($this->name))
  123. {
  124. //check that the form was submitted.
  125. if(array_key_exists('Staple', $_SESSION))
  126. {
  127. if(array_key_exists('Forms', $_SESSION['Staple']))
  128. {
  129. if(array_key_exists($this->name, $_SESSION['Staple']['Forms']))
  130. {
  131. if(array_key_exists('ident', $_SESSION['Staple']['Forms'][$this->name]))
  132. {
  133. if(array_key_exists('ident', $_REQUEST))
  134. {
  135. if($_SESSION['Staple']['Forms'][$this->name]['ident'] == $_REQUEST['ident'])
  136. {
  137. $this->submitted = true;
  138. }
  139. }
  140. }
  141. }
  142. }
  143. }
  144. }
  145. //Repopulate data from the session -- I might add this.....
  146. //if($this->wasSubmitted())
  147. //{
  148. //}
  149. //create the form's identity field.
  150. if($this->createIdent === true)
  151. {
  152. $this->createIdentifier();
  153. }
  154. }
  155. public function __destruct()
  156. {
  157. if(isset($this->name) && $this->createIdent === TRUE)
  158. {
  159. $_SESSION['Staple']['Forms'][$this->name]['ident'] = $this->identifier;
  160. }
  161. }
  162. /**
  163. * Overloaded __set allows for dynamic addition of properties.
  164. * @param string | int $key
  165. * @param mixed $value
  166. */
  167. public function __set($key,$value)
  168. {
  169. $this->_store[$key] = $value;
  170. }
  171. /**
  172. * Retrieves a stored property.
  173. * @param string | int $key
  174. */
  175. public function __get($key)
  176. {
  177. if(array_key_exists($key,$this->_store))
  178. {
  179. return $this->_store[$key];
  180. }
  181. else
  182. {
  183. return NULL;
  184. }
  185. }
  186. /**
  187. * Boot function for initialization of forms that extend this class.
  188. */
  189. public function _start()
  190. {
  191. }
  192. /**
  193. * Creates the ident field, adds it to the form and save the value in the session. This is used
  194. * to verify the form has been submitted and also aids in preventing CSRF form attacks.
  195. */
  196. protected function createIdentifier()
  197. {
  198. $this->identifier = Staple_Encrypt::genHex(32);
  199. $ident = new Staple_Form_HiddenElement('ident');
  200. $ident->setValue($this->identifier)
  201. ->setReadOnly();
  202. $this->addField($ident);
  203. }
  204. public function disableIdentifier()
  205. {
  206. unset($this->identifier);
  207. $this->createIdent = false;
  208. unset($this->fields['ident']);
  209. return $this;
  210. }
  211. /**
  212. * The toString magic method calls the forms build function to output the form to the website.
  213. */
  214. public function __toString()
  215. {
  216. try {
  217. return $this->build();
  218. }
  219. catch (Exception $e)
  220. {
  221. $msg = '<p class=\"formerror\">Form Error....</p>';
  222. if(Staple_Config::getValue('errors', 'devmode'))
  223. {
  224. $msg .= '<p>'.$e->getMessage().'</p>';
  225. }
  226. return $msg;
  227. }
  228. }
  229. /**
  230. * A factory function to encapsulate the creation of form objects.
  231. * @param string $name
  232. * @param string $action
  233. * @param string $method
  234. * @return Staple_Form
  235. */
  236. public static function Create($name, $action=NULL, $method="POST")
  237. {
  238. $inst = new self($name,$action);
  239. $inst->setMethod($method);
  240. return $inst;
  241. }
  242. /**
  243. * Adds a field to the form from an already instantiated form element.
  244. * @param Staple_Form_Element $field
  245. */
  246. public function addField(Staple_Form_Element $field)
  247. {
  248. $args = func_get_args();
  249. foreach($args as $newField)
  250. {
  251. if($newField instanceof Staple_Form_FileElement)
  252. {
  253. $this->setEnctype(self::ENC_FILE);
  254. }
  255. if($newField instanceof Staple_Form_Element)
  256. {
  257. $this->fields[$newField->getName()] = $newField;
  258. }
  259. }
  260. return $this;
  261. }
  262. /**
  263. * Accepts an associative array of fields=>values to apply to the form elements.
  264. * @param array $data
  265. */
  266. public function addData(array $data)
  267. {
  268. foreach($this->fields as $fieldname=>$obj)
  269. {
  270. if(array_key_exists($fieldname, $data))
  271. {
  272. $obj->setValue($data[$fieldname]);
  273. }
  274. elseif($obj instanceof Staple_Form_CheckboxGroup)
  275. {
  276. $boxes = $obj->getBoxes();
  277. foreach($boxes as $chk)
  278. {
  279. if(array_key_exists($chk->getName(), $data))
  280. {
  281. $chk->setValue($data[$chk->getName()]);
  282. }
  283. }
  284. }
  285. else
  286. {
  287. //Checkbox Fix
  288. if($obj->isDisabled() === false && $obj instanceof Staple_Form_CheckboxElement)
  289. {
  290. $obj->setValue(NULL);
  291. }
  292. }
  293. }
  294. return $this;
  295. }
  296. /**
  297. * Returns an associative array of the field values with the field names as the keys, including
  298. * the identity field.
  299. * @return array
  300. */
  301. public function exportFormData()
  302. {
  303. $data = array();
  304. foreach($this->fields as $name=>$field)
  305. {
  306. $data[$field->getName()] = $field->getValue();
  307. }
  308. return $data;
  309. }
  310. /**
  311. * Returns the value of $this->submitted
  312. * @return bool
  313. */
  314. public function wasSubmitted()
  315. {
  316. if(isset($this->name) && isset($_SESSION['Staple']['Forms'][$this->name]['ident']) && isset($_REQUEST['ident']))
  317. {
  318. if($_SESSION['Staple']['Forms'][$this->name]['ident'] == $_REQUEST['ident'])
  319. {
  320. $this->submitted = true;
  321. return true;
  322. }
  323. }
  324. $this->submitted = false;
  325. return false;
  326. //return $this->submitted;
  327. }
  328. /**
  329. * Adds an HTML class to the form.
  330. * @param string $class
  331. */
  332. public function addClass($class)
  333. {
  334. if(!in_array($class,$this->classes))
  335. {
  336. $this->classes[] = $class;
  337. }
  338. return $this;
  339. }
  340. /**
  341. * Removes an HTML class from the form.
  342. * @param string $class
  343. */
  344. public function removeClass($class)
  345. {
  346. if(($key = array_search($class,$this->classes)) !== false)
  347. {
  348. unset($this->classes[$key]);
  349. }
  350. return $this;
  351. }
  352. /**
  353. * Checks that the specified field exists on the form and that it is instantiated.
  354. * @param string $field
  355. * @return boolean
  356. */
  357. public function fieldExists($field)
  358. {
  359. if(array_key_exists($field, $this->fields))
  360. {
  361. if($this->fields[$field] instanceof Staple_Form_Element)
  362. {
  363. return true;
  364. }
  365. }
  366. return false;
  367. }
  368. /**
  369. * @todo complete the javascript validators.....
  370. *
  371. */
  372. public function clientJS()
  373. {
  374. }
  375. public function clientJQuery()
  376. {
  377. $script = <<<JS
  378. var {$this->name}validated = false;
  379. $(function (){
  380. $('#{$this->name}_form').submit(function (){
  381. var errors = new Array();
  382. JS;
  383. foreach($this->fields as $field)
  384. {
  385. if($field instanceof Staple_Form_Element)
  386. {
  387. if($field->isRequired())
  388. {
  389. $script .= $field->clientJQuery();
  390. }
  391. }
  392. else
  393. {
  394. throw new Exception('Form Error', Staple_Error::FORM_ERROR);
  395. }
  396. }
  397. $script .= <<<JS
  398. if(errors.length > 0)
  399. {
  400. var msg = 'Please correct these form errors:\\n';
  401. var count = 0;
  402. for(var x in errors)
  403. {
  404. count++;
  405. if(count <= 10)
  406. {
  407. msg += '\\n'+errors[x];
  408. }
  409. }
  410. if(count > 10)
  411. {
  412. msg += '\\nAnd '+count+' more...';
  413. }
  414. alert(msg);
  415. //jQuery UI Dialog
  416. //$('<div class="form_validation_dialog" title="Form Errors">'+msg+'</div>').dialog({modal:true, buttons: {'Ok': function(){ $(this).dialog('close'); }}});
  417. {$this->name}validated = false;
  418. return false;
  419. }
  420. else
  421. {
  422. {$this->name}validated = true;
  423. }
  424. })
  425. });
  426. JS;
  427. $script .= "";
  428. $script .= "";
  429. //$script = "$('#{$this->name}_form').submit(false);\n";
  430. return $script;
  431. }
  432. /**
  433. * Runs the validators on each field and checks for the completion of required fields.
  434. *
  435. * @throws Exception
  436. */
  437. public function validate()
  438. {
  439. $this->clearErrors();
  440. //Process validation callbacks.
  441. foreach($this->callbacks as $func)
  442. {
  443. try{
  444. $result = call_user_func_array($func['func'],$func['params']);
  445. }
  446. catch (Exception $e)
  447. {
  448. $result = false;
  449. }
  450. /*
  451. if(is_array($result))
  452. {
  453. $this->errors[] = array('label'=>'Additional Form Validation','errors'=>array($result));
  454. }
  455. else
  456. {
  457. $result = (bool)$result;
  458. if($result === false)
  459. {
  460. $this->errors[] = array('label'=>'Additional Form Validation','errors'=>array(array('Form Validation Returned False.')));
  461. }
  462. }
  463. */
  464. }
  465. //Process all validation fields.
  466. foreach($this->fields as $field)
  467. {
  468. if($field instanceof Staple_Form_Element)
  469. {
  470. if(!$field->isValid())
  471. {
  472. if($field->isRequired())
  473. {
  474. $this->errors[$field->getName()] = array('label'=>$field->getLabel(),'errors'=>$field->getErrors());
  475. }
  476. elseif($field->getValue() != '')
  477. {
  478. //A few extra steps to handle File Uploads
  479. if($field instanceof Staple_Form_FileElement)
  480. {
  481. if(is_array($field->getValue()))
  482. {
  483. $file = $field->getValue();
  484. if(isset($file['error']))
  485. {
  486. if($file['error'] != UPLOAD_ERR_NO_FILE)
  487. {
  488. $this->errors[$field->getName()] = array('label'=>$field->getLabel(),'errors'=>$field->getErrors());
  489. }
  490. }
  491. else
  492. {
  493. $this->errors[$field->getName()] = array('label'=>$field->getLabel(),'errors'=>$field->getErrors());
  494. }
  495. }
  496. else
  497. {
  498. $this->errors[$field->getName()] = array('label'=>$field->getLabel(),'errors'=>$field->getErrors());
  499. }
  500. }
  501. else
  502. {
  503. $this->errors[$field->getName()] = array('label'=>$field->getLabel(),'errors'=>$field->getErrors());
  504. }
  505. }
  506. }
  507. }
  508. else
  509. {
  510. throw new Exception('Form Error', Staple_Error::FORM_ERROR);
  511. }
  512. }
  513. //Check for errors.
  514. if(count($this->errors) > 0)
  515. {
  516. return false;
  517. }
  518. else
  519. {
  520. return true;
  521. }
  522. }
  523. /**
  524. * Add a single error to the form errors
  525. * @param string $label
  526. * @param string $msg
  527. */
  528. public function addError($label,$msg)
  529. {
  530. $this->errors[] = array('label'=>$label,'errors'=>array(array($msg)));
  531. }
  532. /**
  533. * @return the $errors
  534. */
  535. public function getErrors()
  536. {
  537. return $this->errors;
  538. }
  539. /**
  540. * Clear out the form errors before validating.
  541. */
  542. protected function clearErrors()
  543. {
  544. $this->errors = array();
  545. return $this;
  546. }
  547. /**
  548. * Adds a callback function to the validation stack. The function can be any standard callback, including an annonymous function.
  549. * A callback function must return a boolean true on success, and boolean false or an array of errors on failure.
  550. *
  551. * @param callback $func
  552. * @param array $params
  553. */
  554. public function addValidationCallback($func,$params = array())
  555. {
  556. array_push($this->callbacks, array('func'=>$func,'params'=>$params));
  557. return $this;
  558. }
  559. /*----------------------------------------Getters and Setters----------------------------------------*/
  560. /**
  561. * Sets the form action location
  562. * @param string $action
  563. * @return Staple_Form
  564. */
  565. public function setAction($action)
  566. {
  567. $this->action = $action;
  568. return $this;
  569. }
  570. /**
  571. * Returns the action location of the form.
  572. * @return string
  573. */
  574. public function getAction()
  575. {
  576. return $this->action;
  577. }
  578. /**
  579. * Sets the method for the form. Only accepts GET and POST. POST is the default.
  580. * @param string $method
  581. * @return Staple_Form
  582. */
  583. public function setMethod($method)
  584. {
  585. if(strtoupper($method) == "GET")
  586. {
  587. $this->method = "GET";
  588. }
  589. else
  590. {
  591. $this->method = "POST";
  592. }
  593. return $this;
  594. }
  595. /**
  596. * Returns the method, either GET or POST
  597. * @return string
  598. */
  599. public function getMethod()
  600. {
  601. return $this->method;
  602. }
  603. /**
  604. * Sets the name of the form
  605. * @param string $name
  606. * @return Staple_Form
  607. */
  608. public function setName($name)
  609. {
  610. $this->name = str_replace(' ','_',$name);
  611. return $this;
  612. }
  613. /**
  614. * Returns the name of the form.
  615. * @return string
  616. */
  617. public function getName()
  618. {
  619. return $this->name;
  620. }
  621. /**
  622. * @return the $enctype
  623. */
  624. public function getEnctype()
  625. {
  626. return $this->enctype;
  627. }
  628. /**
  629. * @param string $enctype
  630. */
  631. public function setEnctype($enctype)
  632. {
  633. switch($enctype)
  634. {
  635. case self::ENC_APP:
  636. case self::ENC_FILE:
  637. case self::ENC_TEXT:
  638. $this->enctype = $enctype;
  639. break;
  640. }
  641. return $this;
  642. }
  643. /**
  644. * @return the $title
  645. */
  646. public function getTitle()
  647. {
  648. return $this->title;
  649. }
  650. /**
  651. * @return the $layout
  652. */
  653. public function getLayout()
  654. {
  655. return $this->layout;
  656. }
  657. /**
  658. * @param string $layout
  659. */
  660. public function setLayout($layout)
  661. {
  662. $this->layout = $layout;
  663. return $this;
  664. }
  665. /**
  666. * @param string $title
  667. */
  668. public function setTitle($title)
  669. {
  670. $this->title = $title;
  671. return $this;
  672. }
  673. /**
  674. * @return the $target
  675. */
  676. public function getTarget()
  677. {
  678. return $this->target;
  679. }
  680. /**
  681. * @param string $target
  682. */
  683. public function setTarget($target)
  684. {
  685. $this->target = $target;
  686. return $this;
  687. }
  688. public function getFieldValue($fieldname)
  689. {
  690. if(array_key_exists($fieldname,$this->fields))
  691. {
  692. if($this->fields[$fieldname] instanceof Staple_Form_Element)
  693. {
  694. return $this->fields[$fieldname]->getValue();
  695. }
  696. }
  697. return NULL;
  698. }
  699. /*----------------------------------------Builders----------------------------------------*/
  700. public function formstart()
  701. {
  702. $buf = '';
  703. $buf .= "\n<form name=\"{$this->name}\" id=\"{$this->name}_form\" action=\"{$this->action}\" method=\"{$this->method}\"";
  704. if(isset($this->enctype))
  705. {
  706. $buf .= ' enctype="'.$this->enctype.'"';
  707. }
  708. if(isset($this->target))
  709. {
  710. $buf .= ' target="'.$this->target.'"';
  711. }
  712. if(count($this->classes) > 0)
  713. {
  714. $buf .= ' class="';
  715. $cstring = '';
  716. foreach($this->classes as $class)
  717. {
  718. $cstring .= $class.' ';
  719. }
  720. $buf .= trim($cstring);
  721. $buf .= '"';
  722. }
  723. $buf .= ">\n";
  724. $buf .= "<div id=\"{$this->name}_div\"";
  725. if(count($this->classes) > 0)
  726. {
  727. $buf .= ' class="'.trim($cstring).'"';
  728. }
  729. $buf .= ">\n";
  730. return $buf;
  731. }
  732. public function formend()
  733. {
  734. $buf = "\n";
  735. if(array_key_exists('ident', $this->fields))
  736. {
  737. if($this->fields['ident'] instanceof Staple_Form_Element)
  738. {
  739. $buf .= $this->fields['ident']->build();
  740. }
  741. }
  742. $buf .= "\n</div>\n</form>\n";
  743. return $buf;
  744. }
  745. public function title()
  746. {
  747. return $this->title;
  748. }
  749. public function fields()
  750. {
  751. $buf = '';
  752. foreach($this->fields as $field)
  753. {
  754. if($field->getName() != 'ident')
  755. {
  756. $buf .= $field->build();
  757. }
  758. }
  759. return $buf;
  760. }
  761. /**
  762. * Constructs and echos the HTML for the form and all of its elements.
  763. */
  764. public function build()
  765. {
  766. $buf = '';
  767. if(isset($this->layout))
  768. {
  769. $layoutloc = FORMS_ROOT.'layouts/'.basename($this->layout).'.phtml';
  770. if(file_exists($layoutloc))
  771. {
  772. ob_start();
  773. include $layoutloc;
  774. $buf = ob_get_contents();
  775. ob_end_clean();
  776. }
  777. else
  778. {
  779. throw new Exception('Unable to load form layout.', Staple_Error::FORM_ERROR);
  780. }
  781. }
  782. else
  783. {
  784. $buf .= $this->formstart();
  785. $buf .= $this->title();
  786. $buf .= $this->fields();
  787. $buf .= $this->formend();
  788. }
  789. return $buf;
  790. }
  791. /*----------------------------------------Helpers----------------------------------------*/
  792. /**
  793. *
  794. * If an array is supplied, a link is created to a controller/action. If a string is
  795. * supplied, a file link is specified.
  796. * @param string | array $link
  797. * @param array $get
  798. */
  799. public function link($link,array $get = array())
  800. {
  801. return Staple_Link::get($link,$get);
  802. }
  803. /**
  804. * @see Staple_View::escape()
  805. * @param string $estring
  806. * @param boolean $strip
  807. */
  808. public static function escape($estring, $strip = false)
  809. {
  810. return Staple_View::escape($estring,$strip);
  811. }
  812. }