Transaction references extended with parametrized patterns, added check_reference...
[fa-stable.git] / includes / ui / class.crud_view.inc
diff --git a/includes/ui/class.crud_view.inc b/includes/ui/class.crud_view.inc
new file mode 100644 (file)
index 0000000..8572fdb
--- /dev/null
@@ -0,0 +1,756 @@
+<?php
+/**********************************************************************
+    Copyright (C) FrontAccounting, LLC.
+       Released under the terms of the GNU General Public License, GPL, 
+       as published by the Free Software Foundation, either version 3 
+       of the License, or (at your option) any later version.
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
+    See the License here <http://www.gnu.org/licenses/gpl-3.0.html>.
+***********************************************************************/
+
+include_once $path_to_root.'/includes/db/class.data_set.inc';
+
+class UI {
+       var $ui_mode;
+
+       // formatters
+       function text($value, $name='', $opts=array())
+       {
+               $text = array('label', 'size'=>"", 'max'=>"", 'title'=>false,   'labparams'=>"", 'post_label'=>"", 'inparams'=>"");
+               $opts = array_merge($text, $opts);
+
+               if (!$name)
+                       return $value;
+
+               call_user_func_array('text_cells', $opts);
+       }
+}
+/*
+       TODO: for php5:
+       . use __construct in base class to avoid need for implicit call to parent constructor.
+       . use private instead _* function name convention
+*/
+//
+//     User input-output conversions.
+//
+class user_view {
+
+       var     $data;          // data in php format
+       var $fields;    // input fields format descriptions
+       var $errors = array();
+       var $dec;
+       var $name;
+
+       function user_view($name)
+       {
+               $this->name = $name;
+       }
+
+       function error($msg, $context=null)
+       {
+               // save error message
+               if (!isset($context))
+                       $context = count($this->errors);
+               $this->errors[$context] = $msg;
+
+               return false;
+       }
+       /*
+               Input/output formatters - convert values between php/user domains.
+       */
+       function _format_input($value, $fmt)
+       {
+               switch($fmt) {
+                       case 'stock':
+                               $this->dec = get_qty_dec($value);
+                               return $value;
+                       case 'price':
+                       case 'qty':
+                       case 'number':
+                                   return user_numeric($value);
+                       case 'percent':
+                                       return user_numeric($value)/100;
+                       case 'text':
+                       case 'date':
+                       default:
+                               return $value;
+               }
+       }
+       //
+       // Returns formatted value
+       //
+       function _format_output($value, $fmt)
+       {
+               switch($fmt) {
+                       case 'price':
+                               return price_format($value);
+                       case 'qty':
+                               return number_format2($value, $this->dec);
+                       case 'number':
+                               return number_format2($value);
+                       case 'percent':
+                               return percent_format($value*100);
+                       case 'stock':
+                               $this->dec = get_qty_dec($value); // retrieve dec for use in following qty fields
+                       case 'text':
+                       case 'date':
+                       default:
+                               return $value;
+               }
+       }
+       /**
+       *       Returns html element for given field
+       *       @mode - true for edit, false for read-only
+       **/
+       function _format_cells($value, $fmt, $mode)
+       {
+               $value = $this->_format_output($fmt);
+
+               // available formatters with parameters
+               $formatters = array(
+                       'email_cell' => array('label', 'params'=>'', 'id'=>null),
+                       'amount_cell' => array('label'=>null, 'bold'=>false, 'params'=>'', 'id'=>null),
+                       'text_cells' => array('label', 'name', 'value'=>null, 'size'=>"", 'max'=>"", 'title'=>false,
+                               'labparams'=>"", 'post_label'=>"", 'inparams'=>"")
+               );
+               // format functions used in various modes
+               $formats = array(
+                       // field format => (view [,edit])
+                       '' => array('label_cell', 'text_cells'), // default
+                       'price' => array('label_cell', 'amount_cell'),
+                       'qty',
+                       'number',
+                       'percent',
+                       'stock',
+                       'text',
+                       'date',
+               );
+       }
+
+       /**
+       *
+       *       PHP->user format values convertion.
+       *
+       **/
+       function set_output($data=null, &$output=null)
+       {
+               if (isset($data))
+                       $this->data = $data;
+
+               if (!isset($output)) {
+                       $output = &$_POST;
+                       $prefix = $this->name;
+               } else
+                       $prefix = '';
+
+               foreach($this->fields as $name => $fmt) {
+
+                       if (is_int($name)) {
+                               $name = $fmt;
+                               $fmt = array();
+                       } elseif (!is_array($fmt))
+                               $fmt = array('fmt' => $fmt);
+
+                       $post = $prefix.(isset($fmt['post']) ? $fmt['post'] : $name);
+
+                       $fld = isset($fmt['fld']) ? $fmt['fld'] : $name;
+
+                       if (is_object($this->data))
+                               $value = isset($this->data->$fld) ?     $this->data->$fld : @$fmt['dflt'];
+                       else
+                               $value = isset($this->data[$fld]) ?     $this->data[$fld] : @$fmt['dflt'];
+                       if(isset($value))
+                               $output[$post] = $this->_format_output($value, @$fmt['fmt']);
+               }
+       }
+
+       /**
+       *
+       *       User->php format values convertion.
+       *       $input - data in user format
+       *       $all - return also null values for non-existing input field.
+       **/
+       function get_input($input=null, $all=false)
+       {
+               if (!isset($input))
+                       $input = $_POST;
+
+               if ($this->name)        // strip view name prefix
+                   foreach($input as $postkey=>$postval )
+               {
+                               if (strpos($postkey, $this->name) === 0)
+                               {
+                                       $id = substr($postkey, strlen($this->name));
+                                       $input[$id] = $postval;
+                               }
+                               unset($input[$postkey]);
+               }
+
+       $data = array();
+               foreach ($this->fields as $name => $fmt) {
+                       if (is_int($name)) {    // direct string passed: this is name of field
+                               $name = $fmt;
+                               $fmt = array(); // default format
+                       } elseif (!is_array($fmt))
+                               $fmt = array('fmt' => $fmt);
+                       $post = isset($fmt['post']) ? $fmt['post'] : $name; // input name (default to field name)
+                       $fld = isset($fmt['fld']) ? $fmt['fld'] : $name;        // input value (default to field name)
+
+//                     if ($all || array_key_exists($post, $input))
+//                     {
+                               $value = $this->_format_input(@$input[$post], @$fmt['fmt']);
+
+//                             if (is_array($data))
+                               if ($all || isset($value))
+                                       $data[$fld] = $value;
+//                             else
+//                                     $data->$fld = $value;
+//                     }
+               }
+
+               return $data;
+       }
+
+       //--------------------------------------------------------
+       //
+       //      Return data formatted according to field descriptions.
+       //
+       function get_fields_views($input=null, $mode=null)
+       {
+
+               if (!isset($input))
+                       $input = $_POST;
+
+               $view = array();
+               foreach ($this->fields as $name => $fmt) {
+                       if (is_int($name)) {
+                               $name = $fmt;
+                               $fmt = array();
+                       }
+                       $post = isset($fmt['post']) ? $fmt['post'] : $name;
+                       $fld = isset($fmt['fld']) ? $fmt['fld'] : $name;
+
+                       $value = $this->_format_cells(@$input[$post], @$fmt['fmt'], $mode);
+
+                       $view[$fld] = $value;
+               }
+               return $view;
+       }
+
+}
+
+//
+//     Template for simple table editors
+//
+
+class simple_crud_view extends user_view {
+       var $name;
+               // object status:
+       var $Mode = 'RESET';
+       var $selected_id;
+       var $prev_id;
+
+       var $_none = ''; // selector value when no item is selected
+       var $pre_handlers; // control buttons and related methods called before view display
+       var $views;
+       var $data = array();
+       var $fields;
+       var $tool_buttons;
+       var $options = array(
+               'delete' => true,               // true or message for successfull action.
+               'update' => true,
+               'insert' => true,
+               'clone' => true,
+       );
+       var $dec;
+       var $data_set;
+       var $display_both = false; //when set to true both list and editor are displayed all the time (eventually set in sub classes)
+       //
+       //
+       function simple_crud_view($name, $data_set = null, $options=array())
+       {
+               $this->user_view($name);
+
+               $this->options = array_merge($this->options, $options);
+
+               $this->views[''] = 'list_view';                 // default view
+
+               if ($this->options['update'])
+                       $this->_add_action('Edit', '_edit', _('Edit'), _('Edit document line'), ICON_EDIT, '',
+                               'editor_view');
+
+               if ($this->options['delete'])
+                       $this->_add_action('Delete', '_delete', _('Delete'), _('Remove line from document'), ICON_DELETE, '',
+                               'list_view');
+
+               if ($this->options['update'])
+                       $this->_add_action('UPDATE', '_update', _('Update'), _('Submit changes'), ICON_UPDATE, 'default',
+                               'editor_view');
+
+               $this->_add_action('RESET', '_cancel', _('Cancel'), _('Cancel changes'), ICON_ESCAPE, 'cancel',
+                       'list_view');
+
+               if ($this->options['insert'])
+                       $this->_add_action('ADD', '_add', _('Add'), _('Add new'), ICON_ADD, 'default',
+                               'editor_view');
+
+               if ($this->options['insert'])
+                       $this->_add_action('NEW', '_add', _('New'), _('Add new'), ICON_ADD, 'default',
+                               'editor_view');
+
+               if ($this->options['clone'])
+                       $this->_add_action('CLONE', '_cloning', _('Clone'), _('Clone'), ICON_ADD, '',
+                               'editor_view');
+
+               $this->data_set = $data_set;
+               $this->fields = $data_set->fields;
+
+//             $this->_prev_status();
+       }
+
+       function _add_action($name, $handler, $but_value=null, $but_title=false, $but_icon=false, $aspect='', $view=null)
+       {
+               $this->pre_handlers[$name] = $handler;
+
+               if ($but_value)
+                       $this->tool_buttons[$name] = array($but_value, $but_title, $but_icon, $aspect);
+
+               if ($view)
+                       $this->_add_mode($name, $view);
+       }
+
+       function _add_mode($name, $view)
+       {
+               $this->views[$name] = $view;
+       }
+
+       function _prev_status()
+       {
+               // Restore previous mode/key (obsolete for views stored in session)
+
+               $mod = get_post($this->name.'Mode', $this->Mode);
+               if ($mod) {
+                       if (is_array($mod)) {
+                               $val = key($mod);
+                               $this->selected_id = $val!==null ? @quoted_printable_decode($val) : $this->_none;
+                               $mod = $mod[$val];
+                       } else {
+                               $val = $mod;
+                               $this->selected_id = $this->_none;
+                       }
+               }
+               $this->Mode = $mod;
+       }
+
+       function _check_mode()
+       {
+               global $Ajax;
+
+               $mod = '';//$this->Mode;
+               // Detect action (mode change)
+               foreach (array_keys($this->pre_handlers) as $m) { // check button controls
+
+                       if (isset($_POST[$this->name.$m])) {
+                               unset($_POST['_focus']); // focus on first form entry
+                               $Ajax->activate($this->name.'_div');
+                               $Ajax->activate($this->name.'_controls');
+                               $val = is_array($_POST[$this->name.$m]) ? key($_POST[$this->name.$m]) : null;
+                               $this->prev_id = $this->selected_id;
+                               $this->selected_id = $val!==null ? @quoted_printable_decode($val) : $this->_none;
+                               $mod = $m; break;
+                       }
+               }
+               if (!$mod && $_SERVER['REQUEST_METHOD'] == 'GET') // initialize on every GET
+                       $mod = 'RESET';
+
+               return $mod;
+       }
+
+       function display_error()
+       {
+               $this->errors = array_merge($this->data_set->errors, $this->errors);
+               $firsterr = reset($this->errors);
+               $field = key($this->errors);
+
+               if (!is_numeric($field))
+                       set_focus($this->name.$field);
+               display_error($firsterr);
+
+               $this->errors = array(); // clean up to prevent false errors on object reuse
+       }
+       //
+       //      Set record for edition
+       //
+       function _edit($mode)
+       {
+               if ($this->selected_id != $this->prev_id || $mode != $this->Mode) { // get record for edition
+                       $this->data = $this->data_set->get($this->selected_id !== $this->_none ? $this->selected_id : null);
+                       $this->set_output();
+               }
+//             if ($this->Mode != $mode) {
+//             }
+
+//             else
+//                     $this->display_error();
+
+               $this->Mode = $mode;
+       }
+       //
+       //      Update record after edition
+       //
+       function _update($mode)
+       {
+               if (!$this->options['update'])
+                       return;
+
+               $this->data = $this->get_input();
+               if ($this->data_set->update_check($this->selected_id, $this->data) && $this->data_set->update($this->selected_id, $this->data)) {
+                               $this->selected_id = $this->_none;
+                               $this->Mode = 'RESET';
+                               $this->_cancel();
+                               if (is_string($this->options['update']))
+                                       display_notification($this->options['update']);
+                               return;
+               }
+               else
+                       $this->display_error();
+
+               $this->Mode = $mode;
+       }
+       //
+       //      Add new record
+       //
+       function _add($mode)
+       {
+               if (!$this->options['insert'])
+                       return;
+
+               if ($mode == 'ADD') {
+                       $this->data = $this->get_input();
+                       if ($this->data_set->insert_check($this->data) && ($this->data_set->insert($this->data) !== false)) {
+                               $this->_cancel();
+                               if (is_string($this->options['insert']))
+                                       display_notification($this->options['insert']);
+                               $this->Mode = 'RESET';
+                               return;
+                       }
+                       else
+                               $this->display_error();
+               } else {// mode==NEW
+                               $this->data = $this->data_set->get();
+               }
+               $this->Mode = 'ADD';
+       }
+       //
+       //      Delete selected  record
+       //
+       function _delete()
+       {
+               if (!$this->options['delete'])
+                       return;
+
+               if ($this->data_set->delete_check($this->selected_id) && $this->data_set->delete($this->selected_id))
+               {
+                       if (is_string($this->options['delete']))
+                               display_notification($this->options['delete']);
+               } else
+                       $this->display_error();
+               $this->_cancel();
+       }
+       //
+       //      Return to listing view
+       //
+       function _cancel()
+       {
+               global $Ajax;
+               $this->selected_id = $this->_none;
+               if ($this->display_both)
+               {
+                       $this->data = $this->data_set->get();
+                       $this->set_output();
+               }
+               $this->cancel();
+               if (!$this->display_both)
+                       $Ajax->activate($this->name.'_div');
+               $this->Mode = 'RESET';
+       }
+       //
+       // Clone record for new edition
+       //
+       function _cloning()
+       {
+               if (!$this->options['clone'])
+                       return;
+               $this->Mode = 'ADD';
+               $this->_edit('Edit');
+               $this->selected_id = $this->_none;
+       }
+       /*
+               Generate form controls
+       */
+
+       function _record_controls($list_view = false)
+       {
+               $clone = $this->options['clone'] && $this->selected_id != $this->_none;
+
+               div_start($this->name.'_controls');
+               echo "<center>";
+
+               if ($list_view)
+                       $this->action_button('NEW');
+               else {
+                       if ($this->Mode == 'NEW' || $this->selected_id==$this->_none)
+                       {
+                               $this->action_button('ADD');
+                               $this->action_button('RESET');
+                       } else {
+                               $this->action_button('UPDATE', $this->selected_id);
+                               if ($clone && $this->display_both) 
+                                       $this->action_button('CLONE', $this->selected_id);
+                               $this->action_button('RESET');
+                       }
+               }
+
+               echo "</center>";
+               div_end();
+       }
+
+       //===========================================================================
+       // Public functions
+       //
+
+       //
+       //      Submit buttons for form actions
+       //
+       function action_button($action, $selected_id=null)
+       {
+               list($value, $title, $icon, $aspect) = $this->tool_buttons[$action];
+               submit($this->name.$action.(isset($selected_id) ? "[$selected_id]" : ''), $value, true, $title, $aspect, $icon);
+       }
+
+       //
+       //      Tool button for grid line actions.
+       //
+       function tool_button($name, $selected_id=null, $params='')
+       {
+               $b = $this->tool_buttons[$name];
+
+               return "<td align='center' $params>"
+                       .button( "{$this->name}$name"
+                               .($selected_id === null || $selected_id === $this->_none ? '': "[$selected_id]"),
+                               $b[0], $b[1], $b[2], $b[3])."</td>";
+       }
+       //
+       //      Main function - display current CRUD editor content
+       //
+       function show($Mode=null)
+       {
+               if (!isset($Mode))
+                       $Mode = $this->_check_mode(true);
+
+               div_start($this->name.'_div');
+
+               if (array_key_exists($Mode, $this->pre_handlers)) {
+                       $fun = $this->pre_handlers[$Mode];
+                       if (is_array($fun))
+                               call_user_func($fun, $Mode);
+                       else
+                               $this->$fun($Mode);
+               }
+
+               if (isset($this->views[$this->Mode]))
+                       $this->{$this->views[$this->Mode]}($Mode);
+               else
+                       $this->{$this->views['']}($Mode); // default view
+
+               // this is needed only when we use temporary crud object together with ajax screen updates
+               hidden($this->name.'Mode'.'['.$this->selected_id.']', $this->Mode);
+               div_end();
+       }
+       
+       //
+       //      Optional things like focus set performed on edition cancel.
+       //
+       function cancel()
+       {
+       }
+
+       //
+       //      Show database content in pager/table
+       //      parameter $Mode contains new mode on change, or '' otherwise
+       //
+       function list_view($Mode) {
+               display_notification(__FUNCTION__. ' is not defined...');
+       }
+
+       //
+       //      Show record editor screen content
+       //      parameter $Mode contains new mode on change, or '' otherwise
+       //
+       function editor_view($Mode) {
+               display_notification(__FUNCTION__. ' is not defined...');
+       }
+};
+
+class selector_crud_view extends simple_crud_view {
+
+       function selector_crud_view($name, $data_set = null, $options=array())
+       {
+               $this->display_both = true;
+               $this->simple_crud_view($name, $data_set, $options);
+       }
+
+       function _check_mode()
+       {
+               global $Ajax;
+
+               // list controls lookup
+               $prev_mode = $this->Mode;
+               $mod = '';
+
+               $list = $this->name.'_id';
+               $this->prev_id = $this->selected_id;
+               $this->selected_id = get_post($list);
+               if (list_updated($list)) {
+                       $Ajax->activate($this->name);
+                       $Ajax->activate($this->name.'_controls');
+                       $mod = $this->selected_id == $this->_none ? 'RESET' : 'Edit';
+               } else {
+                       $mod = simple_crud_view::_check_mode();
+               }
+
+               if ($mod != $prev_mode) {
+                       $Ajax->activate($this->name.'_controls');
+               }
+               if (get_post('_show_inactive_update'))
+               {
+                       $Ajax->activate($this->name.'_id');
+               }
+
+               $_POST[$list] = $this->selected_id;
+               return $mod;
+       }
+
+       function cancel()
+       {
+               global $Ajax;
+
+               $_POST[$this->name.'_id'] = $this->selected_id = $this->_none;
+               $Ajax->activate($this->name.'_id');
+       }
+}
+
+//
+//     Template for line table editors
+//
+
+class table_crud_view extends simple_crud_view {
+
+       var $title;
+
+       function table_crud_view($name, &$data_set = null, $options=array())
+       {
+               $this->display_both = true;
+               $this->simple_crud_view($name, $data_set, $options);
+       }
+       /**
+       *
+       *       Returns selector string from data or current selected_id
+       *
+       **/
+       function curr_id($data=null)
+       {
+               if ($data)
+                       return $data ? implode('_', array_intersect_key($data, array_fill_keys($this->key, true))) : null; // php 5.2.0
+               else
+                       return $this->selected_id;
+       }
+       /**
+       *
+       *       Show single line from record table
+       *
+       **/
+       function show_record_row($line_no, $record=array())
+       {
+/* TBD
+               $id = $this->curr_id($record); // extract key
+
+               $view = array();
+               $this->set_output($line, $view);
+
+               $editables = array();
+               if ($this->options['edit'] && $this->Mode=='Edit' && $this->selected_id==$id)
+               {
+                       $editables = $this->data_set->edit_status($key);
+               }
+               $ctrls = $this->get_field_views($record, $editables);
+               alt_table_row_color($k);
+               foreach($ctrls as $key => $fld)
+               {
+                       echo '<td>';
+                       echo $ctrls[$key];
+                       echo '</td>';
+               }
+
+               if ($this->options['edit']) {
+                       echo '<td>';
+                       $this->tool_button($this->selected_id==$id ? 'UPDATE' : 'Edit', $id);
+                       echo '</td>';
+               }
+               if ($this->options['delete']$this->selected_id==$id) {
+                       echo '<td>';
+                       $this->tool_button( ? 'Cancel' : 'Delete', $id);
+                       echo '</td>';
+               }
+               end_row();
+
+               if ($this->options['insert'])
+               {
+                       alt_table_row_color($k);
+                       $this->new_line($line_no, $line);
+                       end_row();
+               }
+*/
+       }
+       function show_list_headers()
+       {
+       }
+       //
+       //      Main function - display current CRUD table content
+       //
+       function show($Mode=null)
+       {
+               if (!isset($Mode))
+                       $Mode = $this->_check_mode(true);
+               div_start($this->name.'_div');
+
+               if (array_key_exists($Mode, $this->pre_handlers))
+               {
+                       $fun = $this->pre_handlers[$Mode];
+                       if (is_array($fun))
+                       {
+                               if (is_object($fun[0]))
+                                       $fun[0]->$fun[1]($Mode);
+                       } else
+                       $this->$fun($Mode);
+               }
+
+               if ($this->title)
+                       display_heading($this->title);
+
+               start_table(TABLESTYLE, "width=95%");
+               $this->show_list_headers();
+
+               foreach($this->data_set->get_all() as $line_no => $line) {
+                       $this->show_record_row($line_no, $line);
+               }
+               $this->show_record_row();
+
+               end_table();
+               hidden($this->name.'Mode'.'['.$this->selected_id.']', $this->Mode);
+               div_end();
+       }
+
+};