1 <?php
2 3 4 5 6 7
8
9 namespace Hawk;
10
11 12 13 14 15
16 class Form{
17 use Utils;
18
19 const NO_EXIT = false;
20 const EXIT_JSON = true;
21
22 const VIEWS_DIR = 'form/';
23
24
25 const STATUS_SUCCESS = 'success';
26 const STATUS_ERROR = 'error';
27 const STATUS_CHECK_ERROR = 'check-error';
28
29
30 const HTTP_CODE_SUCCESS = 200;
31 const HTTP_CODE_CHECK_ERROR = 412;
32 const HTTP_CODE_ERROR = 424;
33
34
35 const ACTION_REGISTER = 'register';
36 const ACTION_DELETE = 'delete';
37
38
39 const DEFAULT_MODEL = "GenericModel";
40
41
42 43 44
45 public $method = 'post',
46
47 48 49 50 51
52 $name = '',
53
54 55 56 57 58
59 $id = '',
60
61 62 63 64 65
66 $model = self::DEFAULT_MODEL,
67
68 69 70 71 72
73 $object,
74
75 76 77 78 79
80 $labelWidth = '',
81
82 83 84 85 86 87
88 $status = null,
89
90 91 92 93 94 95 96
97 $columns = 1,
98
99
100 101 102 103 104
105 $class = '',
106
107
108 109 110 111 112
113 $autocomplete = true,
114
115
116 117 118 119 120
121 $fieldsets = array(),
122
123 124 125 126 127
128 $inputs = array(),
129
130 131 132 133 134
135 $upload = false,
136
137 138 139 140 141
142 $nomessage = false,
143
144
145 146 147 148 149
150 $action = '',
151
152 153 154 155 156
157 $returns = array(),
158
159 160 161 162 163
164 $errors = array(),
165
166 167 168 169 170 171 172 173 174
175 $reference = array();
176
177 178 179 180 181
182 private $example = null,
183
184 185 186 187 188
189 $dbaction = self::ACTION_REGISTER;
190
191 192 193 194 195
196 private static $instances = array();
197
198
199 200 201 202 203
204 public function __construct($param = array()){
205 206 207
208 $this->action = App::request()->getUri();
209
210
211 $data = $param;
212 unset($data['fieldsets']);
213 $this->setParam($data);
214
215 if(!$this->name) {
216 $this->name = $this->id;
217 }
218
219 if(!in_array($this->columns, array(1,2,3,4,6,12))) {
220 $this->columns = 1;
221 }
222
223 if(!class_exists($this->model)) {
224 $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
225 $reflection = new \ReflectionClass($trace[1]['class']);
226 $this->model = $reflection->getNamespaceName() . '\\' . $this->model;
227 }
228
229 if(!isset($data['object'])) {
230 if(isset($this->model) && !empty($this->reference)) {
231 $model = $this->model;
232 $this->example = new DBExample($this->reference);
233 $this->object = $model::getByExample($this->example);
234 }
235 else{
236 $this->object = null;
237 }
238 }
239 else{
240 $this->model = get_class($this->object);
241 $model = $this->model;
242 $id = $model::getPrimaryColumn();
243 $this->reference = array($id => $this->object->$id);
244 }
245
246 $this->new = $this->object === null;
247
248
249
250 $this->fieldsets = array();
251 if(!empty($param['fieldsets'])) {
252 foreach($param['fieldsets'] as $name => &$fieldset){
253 $inputs = array();
254 $params = array();
255
256 $this->addFieldset(new FormFieldset($this, $name));
257
258 foreach($fieldset as $key => &$field){
259 if($field instanceof FormInput) {
260 $this->addInput($field, $name);
261 if($field instanceof FileInput) {
262 $this->upload = true;
263 }
264 }
265 else {
266 $this->fieldsets[$name]->setParam($key, $field);
267 }
268 }
269 }
270 }
271 else{
272 $this->addFieldset(new FormFieldset($this, 'form'));
273 foreach($this->inputs as &$field){
274 if($field instanceof FormInput) {
275 $this->addInput($field, 'form');
276
277 if($field instanceof FileInput) {
278 $this->upload = true;
279 }
280 }
281 }
282 }
283
284
285 $this->reload();
286
287 self::$instances[$this->id] = $this;
288
289
290 $event = new Event(
291 'form.' . $this->id . '.instanciated', array(
292 'form' => $this
293 )
294 );
295 $event->trigger();
296 }
297
298
299 300 301 302 303 304 305 306 307
308 public static function getInstance($id){
309 if(isset(self::$instances[$id])) {
310 return self::$instances[$id];
311 }
312 else{
313 return null;
314 }
315 }
316
317 318 319 320 321 322 323 324
325 public function setParam($param, $value = null){
326 if(is_array($param)) {
327 $this->map($param);
328 }
329 else{
330 $this->$param = $value;
331 }
332 }
333
334
335 336 337
338 public function reload(){
339
340 $data = array();
341 foreach($this->inputs as $name => $field){
342 if(isset($field->default)) {
343 $data[$name] = $field->default;
344 }
345
346 if(!$this->submitted() && isset($this->object->$name)) {
347 $data[$name] = $this->object->$name;
348 }
349 }
350
351 if($this->submitted()) {
352 $data = strtolower($this->method) == 'get' ? App::request()->getParams() : App::request()->getBody();
353 }
354
355
356 $this->setData($data);
357 }
358
359
360 361 362 363 364 365 366 367
368 public function setData($data, $prefix = '') {
369 foreach($data as $key => $value) {
370 $field = $prefix ? $prefix."[$key]" : $key;
371 if(isset($this->inputs[$field])) {
372 $this->inputs[$field]->setValue($value);
373 }
374 elseif(is_array($value)) {
375 $this->setData($value, $field);
376 }
377
378 }
379 }
380
381
382 383 384 385 386 387 388 389 390
391 public function getData($name = null){
392 if($name) {
393 return $this->inputs[$name]->value;
394 }
395 else{
396 $result = array();
397 foreach($this->inputs as $name => $field){
398 $result[$name] = $field->value;
399 }
400
401 return $result;
402 }
403 }
404
405
406 407 408 409 410
411 public function addFieldset(FormFieldset $fieldset){
412 $this->fieldsets[$fieldset->name] = $fieldset;
413 }
414
415 416 417 418 419 420 421
422 public function addInput(FormInput $input, $fieldset = ''){
423 if($input::INDEPENDANT) {
424
425 $input->independant = true;
426 }
427
428 $labelWidth = $this->labelWidth;
429 if(isset($this->fieldsets[$fieldset]->labelWidth)) {
430 $labelWidth = $this->fieldsets[$fieldset]->labelWidth;
431 }
432 if(isset($input->labelWidth)) {
433 $labelWidth = $input->labelWidth;
434 }
435 $input->labelWidth = $labelWidth;
436
437 $this->inputs[$input->name] = &$input;
438
439 if($fieldset) {
440 $this->fieldsets[$fieldset]->inputs[$input->name] = $input;
441 }
442 }
443
444
445 446 447 448 449 450
451 public function submitted(){
452 if(App::request()->getMethod() == "delete") {
453 return self::ACTION_DELETE;
454 }
455
456 $action = $this->method == 'get' ? App::request()->getParams('_submittedForm') : App::request()->getBody('_submittedForm');
457 return $action ? $action : false;
458 }
459
460
461 462 463 464 465 466 467 468
469 public function wrap($content){
470 App::logger()->info('display form ' . $this->id);
471
472
473 $clientVars = array(
474 'id',
475 'type',
476 'name',
477 'required',
478 'emptyValue',
479 'pattern',
480 'minimum',
481 'maximum',
482 'compare',
483 'errorAt'
484 );
485 $clientInputs = array();
486 foreach($this->inputs as $field){
487 $clientInputs[$field->name] = array_filter(
488 get_object_vars($field),
489 function ($key) use ($clientVars) {
490 return in_array($key, $clientVars);
491 },
492 ARRAY_FILTER_USE_KEY
493 );
494
495 $clientInputs[$field->name]['type'] = $field::TYPE;
496 }
497
498
499 $inputs = json_encode($clientInputs, JSON_HEX_APOS | JSON_HEX_QUOT | JSON_NUMERIC_CHECK);
500 $errors = json_encode($this->errors, JSON_HEX_APOS | JSON_HEX_QUOT | JSON_NUMERIC_CHECK);
501
502 return
503 View::make(Theme::getSelected()->getView(Form::VIEWS_DIR . 'form.tpl'), array(
504 'form' => $this,
505 'content' => $content,
506 )) .
507
508 View::make(Plugin::get('main')->getView('form.js.tpl'), array(
509 'form' => $this,
510 'inputs' => $inputs,
511 'errors' => $errors
512 ));
513 }
514
515 516 517 518 519
520 public function __toString(){
521 return $this->display();
522 }
523
524 525 526 527 528
529 public function display(){
530 try{
531 if(empty($this->fieldsets)) {
532
533 $this->addFieldset(new FormFieldset($this, ''));
534 foreach ($this->inputs as $name => $input) {
535 $this->fieldsets['']->inputs[$name] = &$input;
536 }
537 }
538
539
540 $content = View::make(Theme::getSelected()->getView(Form::VIEWS_DIR . 'form-content.tpl'), array(
541 'form' => $this,
542 'column' => 0
543 ));
544
545
546 return $this->wrap($content);
547 }
548 catch(\Exception $e){
549 App::errorHandler()->exception($e);
550 }
551 }
552
553
554 555 556 557 558 559 560 561
562 public function check($exit = self::EXIT_JSON){
563 if(empty($this->errors))
564 $this->errors = array();
565
566 foreach($this->inputs as $name => $field){
567 $field->check($this);
568 }
569
570 if(!empty($this->errors)) {
571 $this->status = self::STATUS_ERROR;
572 App::logger()->warning(App::session()->getUser()->username . ' has badly completed the form ' . $this->id);
573 if($exit) {
574
575 App::response()->setBody($this->response(self::STATUS_CHECK_ERROR, Lang::get('form.error-fill')));
576 throw new AppStopException();
577 }
578 else{
579 $this->addReturn('message', Lang::get('form.error-fill'));
580 return false;
581 }
582 }
583
584
585 return true;
586 }
587
588 589 590 591 592 593 594 595 596
597 public function register($exit = self::EXIT_JSON, $success = "", $error = ""){
598 try{
599 $this->dbaction = self::ACTION_REGISTER;
600
601 if($this->model == self::DEFAULT_MODEL || !$this->reference) {
602 throw new \Exception("The method register of the class Form can be called only if model and reference properties are set");
603 }
604 if(!$this->object) {
605 $model = $this->model;
606 $this->object = new $model($this->getData());
607 }
608 else{
609 $this->object->set($this->reference);
610 }
611
612
613 foreach($this->inputs as $name => $field){
614 615 616 617 618 619
620 if(!$field->independant && $field->insert !== false && !$field->disabled) {
621
622 $this->object->set($name, $field->dbvalue());
623 }
624 }
625
626 if(!$this->new) {
627 $this->object->update();
628 }
629 else{
630 $this->object->save();
631 }
632
633
634 $id = $this->object->getPrimaryColumn();
635
636 $this->addReturn(
637 array(
638 'primary' => $this->object->$id,
639 'action' => self::ACTION_REGISTER,
640 'new' => $this->new
641 )
642 );
643 $this->status = self::STATUS_SUCCESS;
644
645 App::logger()->info(App::session()->getUser()->username . ' has updated the data on the form ' . $this->id);
646 if($exit) {
647
648 App::response()->setBody($this->response(self::STATUS_SUCCESS, $success ? $success : Lang::get('form.success-register')));
649 throw new AppStopException();
650 }
651 return $this->object->$id;
652 }
653 catch(DBException $e){
654 $this->status = self::STATUS_ERROR;
655 App::logger()->error('An error occured while registering data on the form ' . $this->id . ' : ' . $e->getMessage());
656 if($exit) {
657 return $this->response(self::STATUS_ERROR, DEBUG_MODE ? $e->getMessage() : ($error ? $error : Lang::get('form.error-register')));
658 }
659 throw $e;
660 }
661 }
662
663
664 665 666 667 668 669 670 671 672
673 public function delete($exit = self::EXIT_JSON, $success = "", $error = ""){
674 try{
675 $this->dbaction = self::ACTION_DELETE;
676
677 if($this->model == self::DEFAULT_MODEL || !$this->reference) {
678 throw new \Exception("The method delete of the class Form can be called only if model and reference properties are set");
679 }
680
681 if(!$this->object) {
682 throw new \Exception("This object instance cannot be removed : No such object");
683 }
684
685 $id = $this->object->getPrimaryColumn();
686 $this->object->delete();
687
688 $this->addReturn(
689 array(
690 'primary' => $this->object->$id,
691 'action' => self::ACTION_DELETE
692 )
693 );
694 $this->status = self::STATUS_SUCCESS;
695
696 App::logger()->info('The delete action on the form ' . $this->id . ' was successflully completed');
697 if($exit) {
698 App::response()->setBody($this->response(self::STATUS_SUCCESS, $success ? $success : Lang::get('form.success-delete')));
699 throw new AppStopException();
700 }
701 return $this->object->$id;
702 }
703 catch(DBException $e){
704 $this->status = self::STATUS_ERROR;
705 App::logger()->error('An error occured while deleting the element of the form ' . $this->id . ' : ' . $e->getMessage());
706
707 if($exit) {
708 return $this->response(self::STATUS_ERROR, DEBUG_MODE ? $e->getMessage() : ($error ? $error : Lang::get('form.error-delete')));
709 }
710 throw $e;
711 }
712 }
713
714 715 716 717 718 719
720 public function error($name, $error){
721 $this->errors[$name] = $error;
722 }
723
724
725 726 727 728 729 730
731 public function addReturn($name, $message= ""){
732 if(is_array($name)) {
733 foreach($name as $key => $value)
734 $this->addReturn($key, $value);
735 }
736 else{
737 $this->returns[$name] = $message;
738 }
739 }
740
741
742 743 744 745 746 747 748 749 750
751 public function response($status, $message = ''){
752 $response = array();
753 switch($status){
754 case self::STATUS_SUCCESS :
755
756 App::response()->setStatus(self::HTTP_CODE_SUCCESS);
757 if(! $this->nomessage) {
758 $response['message'] = $message ? $message : Lang::get('form.'.$status.'-'.$this->dbaction);
759 }
760 $response['data'] = $this->returns;
761 break;
762
763 case self::STATUS_CHECK_ERROR :
764
765 App::response()->setStatus(self::HTTP_CODE_CHECK_ERROR);
766 $response['message'] = $message ? $message : Lang::get('form.error-fill');
767 $response['errors'] = $this->errors;
768 break;
769
770 case self::STATUS_ERROR :
771 default :
772 App::response()->setStatus(self::HTTP_CODE_ERROR);
773 $response['message'] = $message ? $message : Lang::get('form.'.$status.'-'.$this->dbaction);
774 $response['errors'] = $this->errors;
775 break;
776 }
777
778 App::response()->setContentType('json');
779 return $response;
780 }
781
782
783
784 785 786 787 788 789 790
791 public function treat($exit = self::EXIT_JSON){
792 if($this->submitted() == self::ACTION_DELETE) {
793 return $this->delete($exit);
794 }
795 else{
796 if($this->check($exit)) {
797 return $this->register($exit);
798 }
799 else{
800 return false;
801 }
802 }
803 }
804 }
805