8572fdbe867d6f3c527cc3fcb330b0e2e4763d48
[fa-stable.git] / includes / ui / class.crud_view.inc
1 <?php
2 /**********************************************************************
3     Copyright (C) FrontAccounting, LLC.
4         Released under the terms of the GNU General Public License, GPL, 
5         as published by the Free Software Foundation, either version 3 
6         of the License, or (at your option) any later version.
7     This program is distributed in the hope that it will be useful,
8     but WITHOUT ANY WARRANTY; without even the implied warranty of
9     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
10     See the License here <http://www.gnu.org/licenses/gpl-3.0.html>.
11 ***********************************************************************/
12
13 include_once $path_to_root.'/includes/db/class.data_set.inc';
14
15 class UI {
16         var $ui_mode;
17
18         // formatters
19         function text($value, $name='', $opts=array())
20         {
21                 $text = array('label', 'size'=>"", 'max'=>"", 'title'=>false,   'labparams'=>"", 'post_label'=>"", 'inparams'=>"");
22                 $opts = array_merge($text, $opts);
23
24                 if (!$name)
25                         return $value;
26
27                 call_user_func_array('text_cells', $opts);
28         }
29 }
30 /*
31         TODO: for php5:
32         . use __construct in base class to avoid need for implicit call to parent constructor.
33         . use private instead _* function name convention
34 */
35 //
36 //      User input-output conversions.
37 //
38 class user_view {
39
40         var     $data;          // data in php format
41         var $fields;    // input fields format descriptions
42         var $errors = array();
43         var $dec;
44         var $name;
45
46         function user_view($name)
47         {
48                 $this->name = $name;
49         }
50
51         function error($msg, $context=null)
52         {
53                 // save error message
54                 if (!isset($context))
55                         $context = count($this->errors);
56                 $this->errors[$context] = $msg;
57
58                 return false;
59         }
60         /*
61                 Input/output formatters - convert values between php/user domains.
62         */
63         function _format_input($value, $fmt)
64         {
65                 switch($fmt) {
66                         case 'stock':
67                                 $this->dec = get_qty_dec($value);
68                                 return $value;
69                         case 'price':
70                         case 'qty':
71                         case 'number':
72                                     return user_numeric($value);
73                         case 'percent':
74                                         return user_numeric($value)/100;
75                         case 'text':
76                         case 'date':
77                         default:
78                                 return $value;
79                 }
80         }
81         //
82         // Returns formatted value
83         //
84         function _format_output($value, $fmt)
85         {
86                 switch($fmt) {
87                         case 'price':
88                                 return price_format($value);
89                         case 'qty':
90                                 return number_format2($value, $this->dec);
91                         case 'number':
92                                 return number_format2($value);
93                         case 'percent':
94                                 return percent_format($value*100);
95                         case 'stock':
96                                 $this->dec = get_qty_dec($value); // retrieve dec for use in following qty fields
97                         case 'text':
98                         case 'date':
99                         default:
100                                 return $value;
101                 }
102         }
103         /**
104         *       Returns html element for given field
105         *       @mode - true for edit, false for read-only
106         **/
107         function _format_cells($value, $fmt, $mode)
108         {
109                 $value = $this->_format_output($fmt);
110
111                 // available formatters with parameters
112                 $formatters = array(
113                         'email_cell' => array('label', 'params'=>'', 'id'=>null),
114                         'amount_cell' => array('label'=>null, 'bold'=>false, 'params'=>'', 'id'=>null),
115                         'text_cells' => array('label', 'name', 'value'=>null, 'size'=>"", 'max'=>"", 'title'=>false,
116                                 'labparams'=>"", 'post_label'=>"", 'inparams'=>"")
117                 );
118                 // format functions used in various modes
119                 $formats = array(
120                         // field format => (view [,edit])
121                         '' => array('label_cell', 'text_cells'), // default
122                         'price' => array('label_cell', 'amount_cell'),
123                         'qty',
124                         'number',
125                         'percent',
126                         'stock',
127                         'text',
128                         'date',
129                 );
130         }
131
132         /**
133         *
134         *       PHP->user format values convertion.
135         *
136         **/
137         function set_output($data=null, &$output=null)
138         {
139                 if (isset($data))
140                         $this->data = $data;
141
142                 if (!isset($output)) {
143                         $output = &$_POST;
144                         $prefix = $this->name;
145                 } else
146                         $prefix = '';
147
148                 foreach($this->fields as $name => $fmt) {
149
150                         if (is_int($name)) {
151                                 $name = $fmt;
152                                 $fmt = array();
153                         } elseif (!is_array($fmt))
154                                 $fmt = array('fmt' => $fmt);
155
156                         $post = $prefix.(isset($fmt['post']) ? $fmt['post'] : $name);
157
158                         $fld = isset($fmt['fld']) ? $fmt['fld'] : $name;
159
160                         if (is_object($this->data))
161                                 $value = isset($this->data->$fld) ?     $this->data->$fld : @$fmt['dflt'];
162                         else
163                                 $value = isset($this->data[$fld]) ?     $this->data[$fld] : @$fmt['dflt'];
164                         if(isset($value))
165                                 $output[$post] = $this->_format_output($value, @$fmt['fmt']);
166                 }
167         }
168
169         /**
170         *
171         *       User->php format values convertion.
172         *       $input - data in user format
173         *       $all - return also null values for non-existing input field.
174         **/
175         function get_input($input=null, $all=false)
176         {
177                 if (!isset($input))
178                         $input = $_POST;
179
180                 if ($this->name)        // strip view name prefix
181                     foreach($input as $postkey=>$postval )
182                 {
183                                 if (strpos($postkey, $this->name) === 0)
184                                 {
185                                         $id = substr($postkey, strlen($this->name));
186                                         $input[$id] = $postval;
187                                 }
188                                 unset($input[$postkey]);
189                 }
190
191         $data = array();
192                 foreach ($this->fields as $name => $fmt) {
193                         if (is_int($name)) {    // direct string passed: this is name of field
194                                 $name = $fmt;
195                                 $fmt = array(); // default format
196                         } elseif (!is_array($fmt))
197                                 $fmt = array('fmt' => $fmt);
198                         $post = isset($fmt['post']) ? $fmt['post'] : $name; // input name (default to field name)
199                         $fld = isset($fmt['fld']) ? $fmt['fld'] : $name;        // input value (default to field name)
200
201 //                      if ($all || array_key_exists($post, $input))
202 //                      {
203                                 $value = $this->_format_input(@$input[$post], @$fmt['fmt']);
204
205 //                              if (is_array($data))
206                                 if ($all || isset($value))
207                                         $data[$fld] = $value;
208 //                              else
209 //                                      $data->$fld = $value;
210 //                      }
211                 }
212
213                 return $data;
214         }
215
216         //--------------------------------------------------------
217         //
218         //      Return data formatted according to field descriptions.
219         //
220         function get_fields_views($input=null, $mode=null)
221         {
222
223                 if (!isset($input))
224                         $input = $_POST;
225
226                 $view = array();
227                 foreach ($this->fields as $name => $fmt) {
228                         if (is_int($name)) {
229                                 $name = $fmt;
230                                 $fmt = array();
231                         }
232                         $post = isset($fmt['post']) ? $fmt['post'] : $name;
233                         $fld = isset($fmt['fld']) ? $fmt['fld'] : $name;
234
235                         $value = $this->_format_cells(@$input[$post], @$fmt['fmt'], $mode);
236
237                         $view[$fld] = $value;
238                 }
239                 return $view;
240         }
241
242 }
243
244 //
245 //      Template for simple table editors
246 //
247
248 class simple_crud_view extends user_view {
249         var $name;
250                 // object status:
251         var $Mode = 'RESET';
252         var $selected_id;
253         var $prev_id;
254
255         var $_none = ''; // selector value when no item is selected
256         var $pre_handlers; // control buttons and related methods called before view display
257         var $views;
258         var $data = array();
259         var $fields;
260         var $tool_buttons;
261         var $options = array(
262                 'delete' => true,               // true or message for successfull action.
263                 'update' => true,
264                 'insert' => true,
265                 'clone' => true,
266         );
267         var $dec;
268         var $data_set;
269         var $display_both = false; //when set to true both list and editor are displayed all the time (eventually set in sub classes)
270         //
271         //
272         function simple_crud_view($name, $data_set = null, $options=array())
273         {
274                 $this->user_view($name);
275
276                 $this->options = array_merge($this->options, $options);
277
278                 $this->views[''] = 'list_view';                 // default view
279
280                 if ($this->options['update'])
281                         $this->_add_action('Edit', '_edit', _('Edit'), _('Edit document line'), ICON_EDIT, '',
282                                 'editor_view');
283
284                 if ($this->options['delete'])
285                         $this->_add_action('Delete', '_delete', _('Delete'), _('Remove line from document'), ICON_DELETE, '',
286                                 'list_view');
287
288                 if ($this->options['update'])
289                         $this->_add_action('UPDATE', '_update', _('Update'), _('Submit changes'), ICON_UPDATE, 'default',
290                                 'editor_view');
291
292                 $this->_add_action('RESET', '_cancel', _('Cancel'), _('Cancel changes'), ICON_ESCAPE, 'cancel',
293                         'list_view');
294
295                 if ($this->options['insert'])
296                         $this->_add_action('ADD', '_add', _('Add'), _('Add new'), ICON_ADD, 'default',
297                                 'editor_view');
298
299                 if ($this->options['insert'])
300                         $this->_add_action('NEW', '_add', _('New'), _('Add new'), ICON_ADD, 'default',
301                                 'editor_view');
302
303                 if ($this->options['clone'])
304                         $this->_add_action('CLONE', '_cloning', _('Clone'), _('Clone'), ICON_ADD, '',
305                                 'editor_view');
306
307                 $this->data_set = $data_set;
308                 $this->fields = $data_set->fields;
309
310 //              $this->_prev_status();
311         }
312
313         function _add_action($name, $handler, $but_value=null, $but_title=false, $but_icon=false, $aspect='', $view=null)
314         {
315                 $this->pre_handlers[$name] = $handler;
316
317                 if ($but_value)
318                         $this->tool_buttons[$name] = array($but_value, $but_title, $but_icon, $aspect);
319
320                 if ($view)
321                         $this->_add_mode($name, $view);
322         }
323
324         function _add_mode($name, $view)
325         {
326                 $this->views[$name] = $view;
327         }
328
329         function _prev_status()
330         {
331                 // Restore previous mode/key (obsolete for views stored in session)
332
333                 $mod = get_post($this->name.'Mode', $this->Mode);
334                 if ($mod) {
335                         if (is_array($mod)) {
336                                 $val = key($mod);
337                                 $this->selected_id = $val!==null ? @quoted_printable_decode($val) : $this->_none;
338                                 $mod = $mod[$val];
339                         } else {
340                                 $val = $mod;
341                                 $this->selected_id = $this->_none;
342                         }
343                 }
344                 $this->Mode = $mod;
345         }
346
347         function _check_mode()
348         {
349                 global $Ajax;
350
351                 $mod = '';//$this->Mode;
352                 // Detect action (mode change)
353                 foreach (array_keys($this->pre_handlers) as $m) { // check button controls
354
355                         if (isset($_POST[$this->name.$m])) {
356                                 unset($_POST['_focus']); // focus on first form entry
357                                 $Ajax->activate($this->name.'_div');
358                                 $Ajax->activate($this->name.'_controls');
359                                 $val = is_array($_POST[$this->name.$m]) ? key($_POST[$this->name.$m]) : null;
360                                 $this->prev_id = $this->selected_id;
361                                 $this->selected_id = $val!==null ? @quoted_printable_decode($val) : $this->_none;
362                                 $mod = $m; break;
363                         }
364                 }
365                 if (!$mod && $_SERVER['REQUEST_METHOD'] == 'GET') // initialize on every GET
366                         $mod = 'RESET';
367
368                 return $mod;
369         }
370
371         function display_error()
372         {
373                 $this->errors = array_merge($this->data_set->errors, $this->errors);
374                 $firsterr = reset($this->errors);
375                 $field = key($this->errors);
376
377                 if (!is_numeric($field))
378                         set_focus($this->name.$field);
379                 display_error($firsterr);
380
381                 $this->errors = array(); // clean up to prevent false errors on object reuse
382         }
383         //
384         //      Set record for edition
385         //
386         function _edit($mode)
387         {
388                 if ($this->selected_id != $this->prev_id || $mode != $this->Mode) { // get record for edition
389                         $this->data = $this->data_set->get($this->selected_id !== $this->_none ? $this->selected_id : null);
390                         $this->set_output();
391                 }
392 //              if ($this->Mode != $mode) {
393 //              }
394
395 //              else
396 //                      $this->display_error();
397
398                 $this->Mode = $mode;
399         }
400         //
401         //      Update record after edition
402         //
403         function _update($mode)
404         {
405                 if (!$this->options['update'])
406                         return;
407
408                 $this->data = $this->get_input();
409                 if ($this->data_set->update_check($this->selected_id, $this->data) && $this->data_set->update($this->selected_id, $this->data)) {
410                                 $this->selected_id = $this->_none;
411                                 $this->Mode = 'RESET';
412                                 $this->_cancel();
413                                 if (is_string($this->options['update']))
414                                         display_notification($this->options['update']);
415                                 return;
416                 }
417                 else
418                         $this->display_error();
419
420                 $this->Mode = $mode;
421         }
422         //
423         //      Add new record
424         //
425         function _add($mode)
426         {
427                 if (!$this->options['insert'])
428                         return;
429
430                 if ($mode == 'ADD') {
431                         $this->data = $this->get_input();
432                         if ($this->data_set->insert_check($this->data) && ($this->data_set->insert($this->data) !== false)) {
433                                 $this->_cancel();
434                                 if (is_string($this->options['insert']))
435                                         display_notification($this->options['insert']);
436                                 $this->Mode = 'RESET';
437                                 return;
438                         }
439                         else
440                                 $this->display_error();
441                 } else {// mode==NEW
442                                 $this->data = $this->data_set->get();
443                 }
444                 $this->Mode = 'ADD';
445         }
446         //
447         //      Delete selected  record
448         //
449         function _delete()
450         {
451                 if (!$this->options['delete'])
452                         return;
453
454                 if ($this->data_set->delete_check($this->selected_id) && $this->data_set->delete($this->selected_id))
455                 {
456                         if (is_string($this->options['delete']))
457                                 display_notification($this->options['delete']);
458                 } else
459                         $this->display_error();
460                 $this->_cancel();
461         }
462         //
463         //      Return to listing view
464         //
465         function _cancel()
466         {
467                 global $Ajax;
468                 $this->selected_id = $this->_none;
469                 if ($this->display_both)
470                 {
471                         $this->data = $this->data_set->get();
472                         $this->set_output();
473                 }
474                 $this->cancel();
475                 if (!$this->display_both)
476                         $Ajax->activate($this->name.'_div');
477                 $this->Mode = 'RESET';
478         }
479         //
480         // Clone record for new edition
481         //
482         function _cloning()
483         {
484                 if (!$this->options['clone'])
485                         return;
486                 $this->Mode = 'ADD';
487                 $this->_edit('Edit');
488                 $this->selected_id = $this->_none;
489         }
490         /*
491                 Generate form controls
492         */
493
494         function _record_controls($list_view = false)
495         {
496                 $clone = $this->options['clone'] && $this->selected_id != $this->_none;
497
498                 div_start($this->name.'_controls');
499                 echo "<center>";
500
501                 if ($list_view)
502                         $this->action_button('NEW');
503                 else {
504                         if ($this->Mode == 'NEW' || $this->selected_id==$this->_none)
505                         {
506                                 $this->action_button('ADD');
507                                 $this->action_button('RESET');
508                         } else {
509                                 $this->action_button('UPDATE', $this->selected_id);
510                                 if ($clone && $this->display_both) 
511                                         $this->action_button('CLONE', $this->selected_id);
512                                 $this->action_button('RESET');
513                         }
514                 }
515
516                 echo "</center>";
517                 div_end();
518         }
519
520         //===========================================================================
521         // Public functions
522         //
523
524         //
525         //      Submit buttons for form actions
526         //
527         function action_button($action, $selected_id=null)
528         {
529                 list($value, $title, $icon, $aspect) = $this->tool_buttons[$action];
530                 submit($this->name.$action.(isset($selected_id) ? "[$selected_id]" : ''), $value, true, $title, $aspect, $icon);
531         }
532
533         //
534         //      Tool button for grid line actions.
535         //
536         function tool_button($name, $selected_id=null, $params='')
537         {
538                 $b = $this->tool_buttons[$name];
539
540                 return "<td align='center' $params>"
541                         .button( "{$this->name}$name"
542                                 .($selected_id === null || $selected_id === $this->_none ? '': "[$selected_id]"),
543                                 $b[0], $b[1], $b[2], $b[3])."</td>";
544         }
545         //
546         //      Main function - display current CRUD editor content
547         //
548         function show($Mode=null)
549         {
550                 if (!isset($Mode))
551                         $Mode = $this->_check_mode(true);
552
553                 div_start($this->name.'_div');
554
555                 if (array_key_exists($Mode, $this->pre_handlers)) {
556                         $fun = $this->pre_handlers[$Mode];
557                         if (is_array($fun))
558                                 call_user_func($fun, $Mode);
559                         else
560                                 $this->$fun($Mode);
561                 }
562
563                 if (isset($this->views[$this->Mode]))
564                         $this->{$this->views[$this->Mode]}($Mode);
565                 else
566                         $this->{$this->views['']}($Mode); // default view
567
568                 // this is needed only when we use temporary crud object together with ajax screen updates
569                 hidden($this->name.'Mode'.'['.$this->selected_id.']', $this->Mode);
570                 div_end();
571         }
572         
573         //
574         //      Optional things like focus set performed on edition cancel.
575         //
576         function cancel()
577         {
578         }
579
580         //
581         //      Show database content in pager/table
582         //      parameter $Mode contains new mode on change, or '' otherwise
583         //
584         function list_view($Mode) {
585                 display_notification(__FUNCTION__. ' is not defined...');
586         }
587
588         //
589         //      Show record editor screen content
590         //      parameter $Mode contains new mode on change, or '' otherwise
591         //
592         function editor_view($Mode) {
593                 display_notification(__FUNCTION__. ' is not defined...');
594         }
595 };
596
597 class selector_crud_view extends simple_crud_view {
598
599         function selector_crud_view($name, $data_set = null, $options=array())
600         {
601                 $this->display_both = true;
602                 $this->simple_crud_view($name, $data_set, $options);
603         }
604
605         function _check_mode()
606         {
607                 global $Ajax;
608
609                 // list controls lookup
610                 $prev_mode = $this->Mode;
611                 $mod = '';
612
613                 $list = $this->name.'_id';
614                 $this->prev_id = $this->selected_id;
615                 $this->selected_id = get_post($list);
616                 if (list_updated($list)) {
617                         $Ajax->activate($this->name);
618                         $Ajax->activate($this->name.'_controls');
619                         $mod = $this->selected_id == $this->_none ? 'RESET' : 'Edit';
620                 } else {
621                         $mod = simple_crud_view::_check_mode();
622                 }
623
624                 if ($mod != $prev_mode) {
625                         $Ajax->activate($this->name.'_controls');
626                 }
627                 if (get_post('_show_inactive_update'))
628                 {
629                         $Ajax->activate($this->name.'_id');
630                 }
631
632                 $_POST[$list] = $this->selected_id;
633                 return $mod;
634         }
635
636         function cancel()
637         {
638                 global $Ajax;
639
640                 $_POST[$this->name.'_id'] = $this->selected_id = $this->_none;
641                 $Ajax->activate($this->name.'_id');
642         }
643 }
644
645 //
646 //      Template for line table editors
647 //
648
649 class table_crud_view extends simple_crud_view {
650
651         var $title;
652
653         function table_crud_view($name, &$data_set = null, $options=array())
654         {
655                 $this->display_both = true;
656                 $this->simple_crud_view($name, $data_set, $options);
657         }
658         /**
659         *
660         *       Returns selector string from data or current selected_id
661         *
662         **/
663         function curr_id($data=null)
664         {
665                 if ($data)
666                         return $data ? implode('_', array_intersect_key($data, array_fill_keys($this->key, true))) : null; // php 5.2.0
667                 else
668                         return $this->selected_id;
669         }
670         /**
671         *
672         *       Show single line from record table
673         *
674         **/
675         function show_record_row($line_no, $record=array())
676         {
677 /* TBD
678                 $id = $this->curr_id($record); // extract key
679
680                 $view = array();
681                 $this->set_output($line, $view);
682
683                 $editables = array();
684                 if ($this->options['edit'] && $this->Mode=='Edit' && $this->selected_id==$id)
685                 {
686                         $editables = $this->data_set->edit_status($key);
687                 }
688                 $ctrls = $this->get_field_views($record, $editables);
689                 alt_table_row_color($k);
690                 foreach($ctrls as $key => $fld)
691                 {
692                         echo '<td>';
693                         echo $ctrls[$key];
694                         echo '</td>';
695                 }
696
697                 if ($this->options['edit']) {
698                         echo '<td>';
699                         $this->tool_button($this->selected_id==$id ? 'UPDATE' : 'Edit', $id);
700                         echo '</td>';
701                 }
702                 if ($this->options['delete']$this->selected_id==$id) {
703                         echo '<td>';
704                         $this->tool_button( ? 'Cancel' : 'Delete', $id);
705                         echo '</td>';
706                 }
707                 end_row();
708
709                 if ($this->options['insert'])
710                 {
711                         alt_table_row_color($k);
712                         $this->new_line($line_no, $line);
713                         end_row();
714                 }
715 */
716         }
717         function show_list_headers()
718         {
719         }
720         //
721         //      Main function - display current CRUD table content
722         //
723         function show($Mode=null)
724         {
725                 if (!isset($Mode))
726                         $Mode = $this->_check_mode(true);
727                 div_start($this->name.'_div');
728
729                 if (array_key_exists($Mode, $this->pre_handlers))
730                 {
731                         $fun = $this->pre_handlers[$Mode];
732                         if (is_array($fun))
733                         {
734                                 if (is_object($fun[0]))
735                                         $fun[0]->$fun[1]($Mode);
736                         } else
737                         $this->$fun($Mode);
738                 }
739
740                 if ($this->title)
741                         display_heading($this->title);
742
743                 start_table(TABLESTYLE, "width=95%");
744                 $this->show_list_headers();
745
746                 foreach($this->data_set->get_all() as $line_no => $line) {
747                         $this->show_record_row($line_no, $line);
748                 }
749                 $this->show_record_row();
750
751                 end_table();
752                 hidden($this->name.'Mode'.'['.$this->selected_id.']', $this->Mode);
753                 div_end();
754         }
755
756 };