PageRenderTime 59ms CodeModel.GetById 32ms RepoModel.GetById 0ms app.codeStats 0ms

/forms/Form/Element.php

https://github.com/robtuley/knotwerk
PHP | 601 lines | 252 code | 52 blank | 297 comment | 25 complexity | 14fe691041ce90b968f3047c8e384c7e MD5 | raw file
  1. <?php
  2. /**
  3. * Defines the T_Form_Element class.
  4. *
  5. * @package forms
  6. * @author Rob Tuley
  7. * @version SVN: $Id$
  8. * @license http://knotwerk.com/licence MIT
  9. */
  10. /**
  11. * Encapsulates a user input element.
  12. *
  13. * @package forms
  14. */
  15. abstract class T_Form_Element implements T_Form_Input
  16. {
  17. /**
  18. * Element alias.
  19. *
  20. * @var string
  21. */
  22. protected $alias;
  23. /**
  24. * Element label.
  25. *
  26. * @var string
  27. */
  28. protected $label;
  29. /**
  30. * Default value.
  31. *
  32. * @var string
  33. */
  34. protected $default = null;
  35. /**
  36. * Clean user input value.
  37. *
  38. * @var mixed
  39. */
  40. protected $clean;
  41. /**
  42. * Array of element filters.
  43. *
  44. * @var array
  45. */
  46. protected $filters = array();
  47. /**
  48. * Error exception.
  49. *
  50. * @var T_Form_Error
  51. */
  52. protected $error;
  53. /**
  54. * Whether we are expecting a scalar (alternative is an array).
  55. *
  56. * This will depend on the type of input we are dealing with. A textbox
  57. * input will expect a scalar (the default), whereas a checkbox set will
  58. * expect an array.
  59. *
  60. * @var bool
  61. */
  62. protected $as_scalar = true;
  63. /**
  64. * Whether element is present or not.
  65. *
  66. * @var bool
  67. */
  68. protected $is_present;
  69. /**
  70. * Whether input is required.
  71. *
  72. * @var bool
  73. */
  74. protected $is_required = true;
  75. /**
  76. * Whether to redisplay an invalid submission.
  77. *
  78. * @var bool
  79. */
  80. protected $redisplay_invalid = true;
  81. /**
  82. * Whether to redisplay a valid submission.
  83. *
  84. * @var bool
  85. */
  86. protected $redisplay_valid = true;
  87. /**
  88. * Fieldname salt.
  89. *
  90. * @var string
  91. */
  92. protected $salt = false;
  93. /**
  94. * Fieldname hashing function.
  95. *
  96. * @var T_Filter_RepeatableHash
  97. */
  98. protected $hash = false;
  99. /**
  100. * A text note to help explain the field.
  101. *
  102. * @var string|T_Template_File
  103. */
  104. protected $help = null;
  105. /**
  106. * Array of attributes.
  107. *
  108. * @var array
  109. */
  110. protected $attributes = array();
  111. /**
  112. * Create form element.
  113. *
  114. * @param string $alias element alias
  115. * @param string $label element label
  116. */
  117. function __construct($alias,$label)
  118. {
  119. $this->alias = (string) $alias;
  120. $this->label = (string) $label;
  121. if (strlen($alias)==0 || strlen($label)==0) {
  122. throw new InvalidArgumentException('zero length alias or label');
  123. }
  124. $this->reset(); /* reset validation affected parameters */
  125. }
  126. /**
  127. * Gets the clean user input or null if none.
  128. *
  129. * @param T_Filter optional filter
  130. * @return mixed filtered user input
  131. */
  132. function getValue($filter=null)
  133. {
  134. return _transform($this->clean,$filter);
  135. }
  136. /**
  137. * Gets the default input.
  138. *
  139. * @param function $f optional filter to apply
  140. * @return mixed default input
  141. */
  142. function getDefault($f=null)
  143. {
  144. if ($this->as_scalar) {
  145. $default = null;
  146. } else {
  147. $default = array();
  148. }
  149. if ($this->redisplay_invalid && !$this->isValid()) {
  150. $default = $this->default;
  151. } elseif ($this->redisplay_valid && $this->isValid()) {
  152. $default = $this->default;
  153. }
  154. return _transform($default,$f);
  155. }
  156. /**
  157. * Set default.
  158. *
  159. * @param mixed $value default value.
  160. * @return OKT_FormElement fluent interface
  161. */
  162. function setDefault($value)
  163. {
  164. $this->default = $value;
  165. return $this;
  166. }
  167. /**
  168. * Add a filter to apply.
  169. *
  170. * @param function $filter
  171. * @return T_Form_Element fluent interface
  172. */
  173. function attachFilter($filter)
  174. {
  175. $this->filters[] = $filter;
  176. return $this;
  177. }
  178. /**
  179. * Removes a filter.
  180. *
  181. * @param function $filter filter to remove
  182. * @return T_Form_Element fluent interface
  183. */
  184. function removeFilter($filter)
  185. {
  186. foreach ($this->filters as $key => $f) {
  187. if ($f === $filter) {
  188. unset($this->filters[$key]);
  189. return $this;
  190. }
  191. }
  192. throw new InvalidArgumentException('filter not attached');
  193. }
  194. /**
  195. * Whether the field is submitted in a particular array cage.
  196. *
  197. * @param T_Cage_Array $source source array to check
  198. * @return bool whether a non-zero length value has been submitted
  199. */
  200. function isSubmitted(T_Cage_Array $source)
  201. {
  202. $submitted = $source->exists($this->getFieldname());
  203. if ($submitted) {
  204. if ($this->as_scalar) {
  205. $cage = $source->asScalar($this->getFieldname());
  206. $submitted = strlen($cage->uncage()) > 0;
  207. } else {
  208. $cage = $source->asArray($this->getFieldname());
  209. $submitted = count($cage->uncage())>0;
  210. }
  211. }
  212. return $submitted;
  213. }
  214. /**
  215. * Whether to redisplay invalid submissions (true by default).
  216. *
  217. * @param bool $yesno whether to redisplay invalid submissions
  218. * @return T_Form_Element fluent interface
  219. */
  220. function redisplayInvalid($yesno=true)
  221. {
  222. $this->redisplay_invalid = (bool) $yesno;
  223. return $this;
  224. }
  225. /**
  226. * Whether to redisplay valid submissions (true by default).
  227. *
  228. * @param bool $yesno whether to redisplay valid submissions
  229. * @return T_Form_Element fluent interface
  230. */
  231. function redisplayValid($yesno=true)
  232. {
  233. $this->redisplay_valid = (bool) $yesno;
  234. return $this;
  235. }
  236. /**
  237. * Whether the field data is redisplayed after a valid submission.
  238. *
  239. * @return bool
  240. */
  241. function isRedisplayValid()
  242. {
  243. return $this->redisplay_valid;
  244. }
  245. /**
  246. * Validate element user input source.
  247. *
  248. * @param T_Cage_Array $source source array to validate
  249. * @return T_Form_Element fluent interface
  250. */
  251. function validate(T_Cage_Array $source)
  252. {
  253. $this->reset();
  254. $this->is_present = $this->isSubmitted($source);
  255. /* quit if not present, checking that is present if required */
  256. if (!$this->is_present) {
  257. if ($this->is_required) {
  258. $this->error = new T_Form_Error('is missing');
  259. } else {
  260. /* if it is NOT present, and permitted to be not present,
  261. * we need to clear any default value from before. */
  262. $this->clearDefault();
  263. }
  264. return $this;
  265. }
  266. /* retrieve caged value */
  267. if ($this->as_scalar) {
  268. $cage = $source->asScalar($this->getFieldname());
  269. } else {
  270. $cage = $source->asArray($this->getFieldname());
  271. }
  272. /* if there is a possibility that the default might be needed,
  273. it needs to be stored. */
  274. if ($this->redisplay_invalid || $this->redisplay_valid) {
  275. try {
  276. $this->setDefault($cage->uncage());
  277. // using setDefault() means any attempt to submit values not
  278. // in options array for radio or checkbox bubbles to a
  279. // InvalidArgumentException error.
  280. } catch (InvalidArgumentException $e) {
  281. $this->clearDefault();
  282. }
  283. }
  284. /* now validate input by filtering */
  285. try {
  286. foreach ($this->filters as $filter) {
  287. $cage = $cage->filter($filter);
  288. }
  289. } catch (T_Exception_Filter $e) {
  290. $this->error = new T_Form_Error($e->getMessage());
  291. return $this;
  292. }
  293. /* value has been filtered clean, and is valid */
  294. $this->clean = $cage->uncage();
  295. return $this;
  296. }
  297. /**
  298. * Resets result of validation.
  299. *
  300. * @return void
  301. */
  302. protected function reset()
  303. {
  304. $this->error = false;
  305. $this->clean = null;
  306. $this->is_present = false;
  307. }
  308. /**
  309. * Clears the default value.
  310. */
  311. protected function clearDefault()
  312. {
  313. if ($this->as_scalar) {
  314. $this->default = null;
  315. } else {
  316. $this->default = array();
  317. }
  318. }
  319. /**
  320. * Whether the element is valid.
  321. *
  322. * @return bool whether the element is valid
  323. */
  324. function isValid()
  325. {
  326. return $this->error === false;
  327. }
  328. /**
  329. * Gets the error that has occurred.
  330. *
  331. * @return bool|T_Form_Error error experienced, or false if no error
  332. */
  333. function getError()
  334. {
  335. return $this->error;
  336. }
  337. /**
  338. * Set the error.
  339. *
  340. * @param $error T_Form_Error error experienced
  341. * @return T_Form_Element fluent interface
  342. */
  343. function setError(T_Form_Error $error)
  344. {
  345. $this->error = $error;
  346. return $this;
  347. }
  348. /**
  349. * Clears any errors.
  350. *
  351. * @return T_Form_Element fluent interface
  352. */
  353. function clearError()
  354. {
  355. $this->error = false;
  356. return $this;
  357. }
  358. /**
  359. * Whether the element has been submitted.
  360. *
  361. * @return bool if the element is submitted
  362. */
  363. function isPresent()
  364. {
  365. return $this->is_present;
  366. }
  367. /**
  368. * Sets the submission as optional.
  369. *
  370. * @return T_Form_Element fluent interface
  371. */
  372. function setOptional()
  373. {
  374. $this->is_required = false;
  375. return $this;
  376. }
  377. /**
  378. * Sets the submission as required.
  379. *
  380. * @return T_Form_Element fluent interface
  381. */
  382. function setRequired()
  383. {
  384. $this->is_required = true;
  385. return $this;
  386. }
  387. /**
  388. * Whether the element is required or not.
  389. *
  390. * @return bool whether the element is required or not.
  391. */
  392. function isRequired()
  393. {
  394. return $this->is_required;
  395. }
  396. /**
  397. * Sets the fieldname salt.
  398. *
  399. * @param string $salt salt to use for this field
  400. * @param T_Filter_RepeatableHash $hash repeatable hash function
  401. * @return T_Form_Element fluent interface
  402. */
  403. function setFieldnameSalt($salt,T_Filter_RepeatableHash $hash)
  404. {
  405. $this->salt = (string) $salt;
  406. $this->hash = $hash;
  407. return $this;
  408. }
  409. /**
  410. * Gets the fieldname of this element.
  411. *
  412. * @return string fieldname
  413. */
  414. function getFieldname()
  415. {
  416. if ($this->salt && $this->hash) {
  417. return 'c'._transform($this->alias.$this->salt,$this->hash);
  418. } else {
  419. return $this->alias;
  420. }
  421. }
  422. /**
  423. * Gets the alias.
  424. *
  425. * @return string alias
  426. */
  427. function getAlias()
  428. {
  429. return $this->alias;
  430. }
  431. /**
  432. * Sets the alias.
  433. *
  434. * @return T_Form_Input fluent interface
  435. */
  436. function setAlias($alias)
  437. {
  438. $this->alias = $alias;
  439. return $this;
  440. }
  441. /**
  442. * Get element label.
  443. *
  444. * @param function $f filter to apply
  445. * @return string element label
  446. */
  447. function getLabel($f=null)
  448. {
  449. return _transform($this->label,$f);
  450. }
  451. /**
  452. * Set element label.
  453. *
  454. * @param string $label label
  455. * @return T_Form_Element fluent interface
  456. */
  457. function setLabel($label)
  458. {
  459. $this->label = $label;
  460. return $this;
  461. }
  462. /**
  463. * Search for element with a particular name.
  464. *
  465. * @param string $alias alias to search for
  466. * @return bool|T_Form_Element element required or false if not found
  467. */
  468. function search($alias)
  469. {
  470. if (strcmp($alias,$this->alias)===0) {
  471. return $this;
  472. } else {
  473. return false;
  474. }
  475. }
  476. /**
  477. * Gets the available composite object (null in this case).
  478. *
  479. * @return null no composite available
  480. */
  481. function getComposite()
  482. {
  483. return null;
  484. }
  485. /**
  486. * Accept a visitor.
  487. *
  488. * @param T_Visitor $visitor visitor object
  489. */
  490. function accept(T_Visitor $visitor)
  491. {
  492. $name = explode('_',get_class($this));
  493. array_shift($name);
  494. $method = 'visit'.implode('',$name);
  495. $visitor->$method($this);
  496. }
  497. /**
  498. * Get element help text.
  499. *
  500. * @param function $f filter to apply
  501. * @return string element label
  502. */
  503. function getHelp($f=null)
  504. {
  505. return _transform($this->help,$f);
  506. }
  507. /**
  508. * Set element help text.
  509. *
  510. * @param string $help help
  511. * @return T_Form_Element fluent interface
  512. */
  513. function setHelp($help)
  514. {
  515. $this->help = $help;
  516. return $this;
  517. }
  518. /**
  519. * Sets an attribute on a particular member.
  520. *
  521. * @param string $name attribute name
  522. * @param mixed $value attribute value
  523. * @return T_Form_Element fluent
  524. */
  525. function setAttribute($name,$value)
  526. {
  527. $this->attributes[$name] = $value;
  528. return $this;
  529. }
  530. /**
  531. * Gets the value of an attribute.
  532. *
  533. * @param string $name attribute name
  534. * @return mixed
  535. */
  536. function getAttribute($name)
  537. {
  538. return array_key_exists($name,$this->attributes) ? $this->attributes[$name] : null;
  539. }
  540. /**
  541. * Gets the values of all set attributes.
  542. *
  543. * @return array attribute name=>value pairs
  544. */
  545. function getAllAttributes()
  546. {
  547. return $this->attributes;
  548. }
  549. }