Hoa central
Form.php
Go to the documentation of this file.
1 <?php
2 
37 namespace Hoa\Xyl\Interpreter\Html;
38 
39 use Hoa\Http;
40 use Hoa\Praspel;
41 use Hoa\Stream;
42 use Hoa\Xyl;
43 
52 class Form extends Generic implements Xyl\Element\Executable
53 {
59  protected static $_attributes = [
60  'accept-charset' => parent::ATTRIBUTE_TYPE_NORMAL,
61  'action' => parent::ATTRIBUTE_TYPE_LINK,
62  'autocomplete' => parent::ATTRIBUTE_TYPE_NORMAL,
63  'async' => parent::ATTRIBUTE_TYPE_NORMAL,
64  'enctype' => parent::ATTRIBUTE_TYPE_NORMAL,
65  'error' => parent::ATTRIBUTE_TYPE_LIST,
66  'method' => parent::ATTRIBUTE_TYPE_NORMAL,
67  'name' => parent::ATTRIBUTE_TYPE_NORMAL,
68  // client, security, all (=true)
69  'novalidate' => parent::ATTRIBUTE_TYPE_NORMAL,
70  'target' => parent::ATTRIBUTE_TYPE_NORMAL
71  ];
72 
78  protected static $_attributesMapping = [
79  'accept-charset',
80  'action',
81  'autocomplete',
82  'async' => 'data-formasync',
83  'enctype',
84  'method',
85  'name',
86  'novalidate',
87  'target'
88  ];
89 
95  protected $_formData = null;
96 
102  protected $_validity = null;
103 
104 
105 
112  protected function paint(Stream\IStream\Out $out)
113  {
114  $name = $this->getName();
115 
116  $out->writeAll('<' . $name . $this->readAttributesAsString() . '>');
117  // Force Internet Explorer <= 8 to use UTF-8.
118  $out->writeAll('<input type="hidden" name="__utf8" value="✓" />');
119  $this->computeValue($out);
120  $out->writeAll('</' . $name . '>');
121 
122  return;
123  }
124 
138  public function preExecute()
139  {
140  if ('true' === $this->abstract->readAttribute('async')) {
141  if (false === $this->attributeExists('aria-atomic')) {
142  $this->writeAttribute('aria-atomic', 'true');
143  }
144 
145  if (false === $this->attributeExists('aria-busy')) {
146  $this->writeAttribute('aria-busy', 'false');
147  }
148 
149  if (false === $this->attributeExists('aria-live')) {
150  $this->writeAttribute('aria-live', 'polite');
151  }
152 
153  if (false === $this->attributeExists('aria-relevant')) {
154  $this->writeAttribute('aria-relevant', 'all');
155  }
156  }
157 
158  if (false === $this->attributeExists('action')) {
159  $this->writeAttribute('action', '#');
160  }
161 
162  if (false === $this->attributeExists('method')) {
163  $this->writeAttribute('method', 'post');
164  }
165 
166  if (true === $this->attributeExists('novalidate')) {
167  $this->writeAttribute('novalidate', 'true');
168  }
169 
170  return;
171  }
172 
178  public function postExecute()
179  {
180  return;
181  }
182 
190  public function getElements()
191  {
192 
193  // Form elements.
194  $out = array_merge(
195  $this->xpath('.//__current_ns:input[@name]'),
196  $this->xpath('.//__current_ns:button[@name]'),
197  $this->xpath('.//__current_ns:select[@name]'),
198  $this->xpath('.//__current_ns:textarea[@name]'),
199  $this->xpath('.//__current_ns:keygen[@name]'),
200  $this->xpath('.//__current_ns:output[@name]')
201  );
202 
203  if (null !== $id = $this->readAttribute('id')) {
204  // Form-associated elements.
205  $out = array_merge(
206  $out,
207  $this->xpath('//__current_ns:input[@name and @form="' . $id . '"]'),
208  $this->xpath('//__current_ns:button[@name and @form="' . $id . '"]'),
209  $this->xpath('//__current_ns:select[@name and @form="' . $id . '"]'),
210  $this->xpath('//__current_ns:textarea[@name and @form="' . $id . '"]'),
211  $this->xpath('//__current_ns:keygen[@name and @form="' . $id . '"]'),
212  $this->xpath('//__current_ns:output[@name and @form="' . $id . '"]')
213  );
214  }
215 
216  return $out;
217  }
218 
224  public function getSubmitElements()
225  {
226  $out = array_merge(
227  $this->xpath('.//__current_ns:input[@type="submit"]'),
228  $this->xpath('.//__current_ns:input[@type="image"]')
229  );
230 
231  if (null !== $id = $this->readAttribute('id')) {
232  // Form-associated elements.
233  $out = array_merge(
234  $this->xpath('//__current_ns:input[@type="submit" and @form="' . $id . '"]'),
235  $this->xpath('//__current_ns:input[@type="image" and @form="' . $id . '"]')
236  );
237  }
238 
239  return $out;
240  }
241 
247  public function hasBeenSent()
248  {
249  $novalidate = $this->abstract->readAttributeAsList('novalidate');
250 
251  if (false === $this->abstract->attributeExists('novalidate') ||
252  (false === in_array('security', $novalidate) &&
253  false === in_array('all', $novalidate) &&
254  false === in_array('true', $novalidate))) {
255  $method = strtolower($this->readAttribute('method')) ?: 'post';
256 
257  if ($method !== Http\Runtime::getMethod()) {
258  return false;
259  }
260 
261  $enctype = $this->readAttribute('enctype')
262  ?: 'application/x-www-form-urlencoded';
263 
264  if ($enctype !== Http\Runtime::getHeader('Content-Type')) {
265  return false;
266  }
267 
268  // add verifications if:
269  // <input type="submit" formaction="…" form*="…" />
270  }
271 
272  return \Hoa\Http\Runtime::hasData();
273  }
274 
281  public function isValid($revalid = false)
282  {
283  if (false === $revalid && null !== $this->_validity) {
284  return $this->_validity;
285  }
286 
287  $novalidate = $this->abstract->readAttributeAsList('novalidate');
288  // what about @formnovalidate on submitable element?
289 
290  if (true === in_array('all', $novalidate) ||
291  true === in_array('true', $novalidate)) {
292  return $this->_validity = true;
293  }
294 
295  $this->_validity = true;
296  $data = Http\Runtime::getData();
297  unset($data['__utf8']);
298  $this->flat($data, $flat);
299 
300  if (false === is_array($data) || empty($data)) {
301  return $this->_validity = false;
302  }
303 
304  $elements = $this->getElements();
305  $names = [];
306  $validation = [];
307 
308  foreach ($elements as &$_element) {
309  $_element = $this->getConcreteElement($_element);
310  $name = $_element->readAttribute('name');
311 
312  if (!isset($names[$name])) {
313  $names[$name] = [];
314  }
315 
316  $names[$name][] = $_element;
317  }
318 
319  foreach ($data as $index => $datum) {
320  if (!is_array($datum)) {
321  if (!isset($names[$index])) {
322  $validation[$index] = false;
323 
324  continue;
325  }
326 
327  if (1 < count($names[$index])) {
328  $validation[$index] = false;
329 
330  foreach ($names[$index] as $element) {
331  $handle = $element->isValid($revalid, $datum);
332 
333  if (true === $handle) {
334  $element->setValue($datum);
335  }
336 
337  $validation[$index] = $validation[$index] || $handle;
338  }
339 
340  unset($names[$index]);
341  unset($flat[$index]);
342 
343  continue;
344  }
345 
346  $element = $names[$index][0];
347  $validation[$index] = $element->isValid($revalid, $datum);
348  $element->setValue($datum);
349  unset($names[$index]);
350  unset($flat[$index]);
351 
352  continue;
353  }
354 
355  $validation[$index] = false;
356 
357  /*
358  print_r($flat);
359  print_r($validation);
360 
361  $remainder = array();
362 
363  foreach($datum as $key => &$value) {
364 
365  $key = key($flat);
366 
367  if(!isset($names[$key])) {
368 
369  $remainder[] = $key;
370  next($flat);
371 
372  continue;
373  }
374 
375  $validation[$key] = $names[$key][0]->isValid($revalid, $value);
376  unset($flat[$key]);
377  unset($names[$key]);
378  }
379 
380  print_r($remainder);
381  */
382  }
383 
384  foreach ($names as $name => $element) {
385  foreach ($element as $el) {
386  if (($el instanceof Input ||
387  $el instanceof Textarea ||
388  $el instanceof Select) &&
389  true === $el->attributeExists('required')) {
390  $validation[$name] = false;
391  }
392  }
393  }
394 
395  $handle = &$this->_validity;
396  array_walk($validation, function ($verdict) use (&$handle) {
397  $handle = $handle && $verdict;
398  });
399 
400  self::postVerification($this->_validity, $this);
401 
402  if (true === $this->_validity) {
403  $this->_formData = $data;
404  }
405 
406  return $this->_validity;
407  }
408 
417  protected function flat(&$value, &$out, $key = null)
418  {
419  if (!is_array($value)) {
420  $out[$key] = &$value;
421 
422  return;
423  }
424 
425  foreach ($value as $k => &$v) {
426  if (null === $key) {
427  $this->flat($v, $out, $k);
428  } else {
429  $this->flat($v, $out, $key . '[' . $k . ']');
430  }
431  }
432 
433  return;
434  }
435 
447  public static function postValidation(
448  $verdict,
449  &$value,
450  Concrete $element,
451  $postVerification = true
452  ) {
453  // Order is important.
454  $validates = [];
455 
456  if (true === $element->abstract->attributeExists('validate')) {
457  $validates['@'] = $element->abstract->readAttribute('validate');
458  }
459 
460  $validates = array_merge(
461  $validates,
462  $element->abstract->readCustomAttributes('validate')
463  );
464 
465  if (empty($validates)) {
466  if (true === $postVerification) {
467  static::postVerification($verdict, $element);
468  }
469 
470  return $verdict;
471  }
472 
473  // Order is not important.
474  $errors = $element->abstract->readCustomAttributesAsList('error');
475 
476  if (true === $element->abstract->attributeExists('error')) {
477  $errors['@'] = $element->abstract->readAttributeAsList('error');
478  }
479 
480  if (ctype_digit($value)) {
481  $value = (int) $value;
482  } elseif (is_numeric($value)) {
483  $value = (float) $value;
484  }
485 
486  $decision = true;
487 
488  foreach ($validates as $name => $realdom) {
489  $praspel = Praspel::interpret(
490  '@requires i: ' . $realdom . ';'
491  );
492  $clause = $praspel->getClause('requires');
493  $variable = $clause['i'];
494  $decision = $variable->predicate($value);
495 
496  if ('@' === $name) {
497  $decision = $verdict && $decision;
498  }
499 
500  if (true === $decision) {
501  unset($errors[$name]);
502 
503  continue;
504  }
505 
506  if (!isset($errors[$name])) {
507  continue;
508  }
509 
510  $handle = $element->xpath(
511  '//__current_ns:error[@id="' .
512  implode('" or @id="', $errors[$name]) .
513  '"]'
514  );
515 
516  foreach ($handle as $error) {
517  $element->getConcreteElement($error)->setVisibility(true);
518  }
519 
520  unset($errors[$name]);
521 
522  break;
523  }
524 
525  $verdict = $decision;
526 
527  if (true === $postVerification) {
528  static::postVerification($verdict, $element, isset($errors['@']));
529  }
530 
531  return $verdict;
532  }
533 
542  public static function postVerification(
543  $verdict,
544  Concrete $element,
545  $raise = true
546  ) {
547  if (true === $verdict) {
548  return;
549  }
550 
551  if (true !== $raise) {
552  return;
553  }
554 
555  $onerror = $element->abstract->readAttributeAsList('error');
556  $errors = $element->xpath(
557  '//__current_ns:error[@id="' . implode('" or @id="', $onerror) . '"]'
558  );
559 
560  foreach ($errors as $error) {
561  $element->getConcreteElement($error)->setVisibility(true);
562  }
563 
564  return;
565  }
566 
573  public function getData($index = null)
574  {
575  if (null === $index) {
576  return $this->_formData;
577  }
578 
579  if (!isset($this->_formData[$index])) {
580  return null;
581  }
582 
583  return $this->_formData[$index];
584  }
585 
592  public static function getMe(Concrete $element)
593  {
594  if (true === $element->attributeExists('form')) {
595  $form = $element->xpath(
596  '//__current_ns:form[@id="' . $element->readAttribute('form') . '"]'
597  );
598  } else {
599  $form = $element->xpath('.//ancestor::__current_ns:form');
600  }
601 
602  if (empty($form)) {
603  return null;
604  }
605 
606  return $form[0];
607  }
608 }
isValid($revalid=false)
Definition: Form.php:281
static getData($extended=true)
Definition: Runtime.php:80
static postVerification($verdict, Concrete $element, $raise=true)
Definition: Form.php:542
flat(&$value, &$out, $key=null)
Definition: Form.php:417
static getHeader($header)
Definition: Runtime.php:181
static getMe(Concrete $element)
Definition: Form.php:592
paint(Stream\IStream\Out $out)
Definition: Form.php:112
static getConcreteElement(Element $element)
Definition: Concrete.php:212
static postValidation($verdict, &$value, Concrete $element, $postVerification=true)
Definition: Form.php:447
static getMethod()
Definition: Runtime.php:54
static interpret($praspel, $bindToClass=null)
Definition: Praspel.php:69