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 ***********************************************************************/
13 // array contains optional/required params for all supported validator types
15 $validator_types = array(
16 'required' => array(),
17 'range' => array('min'=>0, 'max' =>null, 'lt', 'gt', 'le', 'ge')
21 Generic record set object
23 abstract class record_set {
24 var $key; // unique key fields
25 var $fields; // other fields
26 var $name; // table name (without prefix)
27 var $checks = array(); // checks on data before insert/update/delete
29 var $selected_id; // current key
30 var $data; // current record data
31 var $errors = array();
33 var $subset; // optional where clause for record subset
37 $this->errors = array();
40 function __construct($name, $fields, $key, $subset=null)
43 $this->fields = $fields;
45 $this->subset = $subset;
48 function error($msg, $field=null) {
53 $field = count($this->errors);
54 $this->errors[$field] = $msg;
56 // save db errors for debugging purposes
57 if ($db && db_error_no()) $this->debug[] = db_error_msg($db);
61 //==============================================================================================
66 * Set validator for insert/update/delete
68 * @check - validator description string in format: 'field_name:uid_modes:check_type[[:param1][:param2]...]'
69 * parameters can be expressed with name in form: name=value
70 * @msg - message stored on error
73 function set_validator($check, $msg=null)
75 $params = explode(':', $check);
77 display_error(_('Invalid validator string'));
79 $fieldname = array_shift($params);
80 $mode = array_shift($params);
81 $type = array_shift($params);
85 foreach ($params as $par) {
86 if (($n = strpos($par, '=')) !== false) {
87 $options[substr($par, 0, $n)] = substr($par, $n+1);
92 $this->checks[] = array('fld'=>$fieldname, 'type' => $type, 'msg' => $msg, 'opts' => $options, 'mode'=>$mode);
97 * @key - record key (u/d)
98 * @data - data to be validated (i/u)
100 function _validate($key=null, $data)
102 $mode = isset($data) ? (isset($key) ? 'u' : 'i') : 'd';
104 foreach($this->checks as $check) {
106 if (strpos($check['mode'], $mode) !== false) {
107 $msg = $check['msg'];
108 $fld = $check['fld'];
109 $opts = @$check['opts'];
111 // precheck for existing
112 if ($mode == 'i' && $fld && !isset($data[$fld])) {
113 $msg = sprintf(_("Input parameter '%s' have to be set."), $check['fld']);
114 return $this->error($msg, $check['fld']);
117 switch($check['type']) {
120 if ($data[$fld]==='') {
121 if (!$msg) $msg = sprintf(_("Parameter '%s' cannot be empty.", $check['fld']));
122 return $this->error($msg, $check['fld']);
126 case 'clean_file_name':
127 if (isset($data[$fld]) && ($data[$fld] != clean_file_name($data[$fld]))) {
128 if (!$msg) $msg = sprintf(_("Parameter '%s' contains invalid characters.", $check['fld']));
129 return $this->error($msg, $check['fld']);
134 if (!$msg) $msg = sprintf(_("Parameter '%s' has invalid value.", $check['fld']));
135 // TODO: check at least one named parameter is passed
136 if (isset($opts['lt']) && !($data[$fld] < $opts['lt']) ||
137 (isset($opts['gt']) && !($data[$fld] > $opts['gt'])) ||
138 (isset($opts['min']) && !($data[$fld] < $opts['min'])) ||
139 (isset($opts['max']) && !($data[$fld] > $opts['max'])) )
140 return $this->error($msg, $check['fld']);
145 // user defined checks
147 $func = $check['type'];
148 if (method_exists($this, $func)) {
149 if (!$this->$func($data, $check['opts'], $key))
150 return $this->error($msg, $check['fld']);
151 } else if (function_exists($func)) {
152 if (!$func($data, $key, $check['opts'], $key))
153 return $this->error($msg, $check['fld']);
163 * Returns editable status for selected record.
164 * Array contains true for editable, false for readonly fields.
165 * This looks redundant ('forbidden' update_check could be used)
166 * but in fact the constraints are related to exact record, so changes with key.
168 function edit_status($key)
170 $editables = array();
172 // default: all but key fields editable
173 foreach ($this->fields as $fld=>$val)
174 $editables[$fld] = !in_array($fld, (array)$this->key);
180 function delete_check($key)
182 return $this->_validate($key, null);
185 function insert_check($data)
187 return $this->_validate(null, $data);
190 function update_check($key, $data)
192 // Note: this does not allow change of key
193 return $this->_validate( $key, $data);
196 //===========================================================================
197 // Database functions placeholders
200 // Generic read record routine
202 function get($key=null)
205 // return all defined default values
206 foreach ($this->fields as $name => $def)
208 if(!is_numeric($name)) {
210 $defaults[$name] = $def;
211 elseif (isset($def['dflt']))
212 $defaults[$name] = $def['dflt'];
218 // Generic list record routine
220 abstract function get_all();
222 // Generic update record routine
224 function update($key, $data)
226 if (!$this->update_check($key, $data))
232 // Generic delete record routine
234 function delete($key)
236 if (!$this->delete_check($key))
244 function insert($data)
246 if (!$this->insert_check($data))
254 class data_set extends record_set {
256 function __construct($name, $fields, $key, $subset=null)
258 parent::__construct($name, $fields, $key, $subset);
262 // Generic read record routine
264 function get($key=null)
267 return parent::get();
269 $sql = "SELECT * FROM ".TB_PREF.$this->name." WHERE ";
270 $where = $this->subset ? (array)$this->subset : array();
272 if (is_array($this->key)) {
273 foreach($this->key as $fld)
274 if (isset($key[$fld]))
275 $where[$fld] = "`$fld`=".db_escape($key[$fld]);
277 return $this->error(sprintf(_("Invalid key passed reading '%s'"), $this->name));
279 $where = array($this->key => "`".$this->key."`=".db_escape($key));
282 $sql .= implode(' AND ', $where);
283 $result = db_query($sql);
285 return $this->error("Cannot get record from ".$this->name);
287 return $rec = db_num_rows($result) ? db_fetch_assoc($result) : null;
290 // Generic list record routine
292 function get_all($where=null, $order_by=null)
295 foreach($this->fields as $fld)
296 $fields[] = '`'.$fld.'`';
297 $sql = "SELECT ".implode(',', $fields)." FROM ".TB_PREF.$this->name;
300 $sql .= " WHERE ".($this->subset ? '('.$this->subset . ') AND ' : ''). $where;
302 $order_by = (array)$order_by;
303 foreach($order_by as $i => $fld)
304 $order_by[$i] = '`'.$fld.'`';
305 $sql .= " ORDER BY ".implode(',', (array)$order_by);
307 $result = db_query($sql);
309 return $this->error("Cannot get record from ".$this->name);
314 // Generic update record routine
316 function update($key, $data)
318 if (!parent::update($key, $data)) // validate data
321 $sql = "UPDATE ".TB_PREF.$this->name." SET ";
324 foreach($data as $fld => $value) { // select only data relevant for this model
325 if (in_array($fld, $this->fields))
326 $updates[$fld] = "`$fld`=".db_escape($value);
328 if (count($updates) == 0)
329 return $this->error(_("Empty update data for table ").$this->name);
331 $sql .= implode(',', $updates)." WHERE ";
332 $where = $this->subset ? (array)$this->subset : array();
334 if(is_array($this->key)) { // construct key phrase
335 foreach($this->key as $fld)
336 if (isset($key[$fld]))
337 $where[$fld] = "`$fld`=".db_escape($key[$fld]);
339 return $this->error(sprintf(_("Invalid key for update '%s'"), $this->name));
341 $where = array("`".$this->key."`=".db_escape($key));
344 $sql .= implode(' AND ', $where);
345 $result = db_query($sql);
348 return $this->error("cannot update record in ".$this->name);
353 // Generic delete record routine
355 function delete($key)
357 if (!parent::delete_check($key))
360 $sql = "DELETE FROM ".TB_PREF.$this->name;
361 $where = $this->subset ? (array)$this->subset : array();
363 if(is_array($this->key)) {
364 foreach($this->key as $fld)
365 if (isset($key[$fld]))
366 $where[$fld] = "`$fld`=".db_escape($key[$fld]);
368 return $this->error(sprintf(_("Invalid key for update '%s'"), $this->name));
370 $where = array("`".$this->key."`=".db_escape($key));
373 $sql .= " WHERE ".implode(' AND ', $where);
374 $result = db_query($sql);
376 return $this->error(_("Cannot update record in ").$this->name);
383 function insert($data)
385 if (!parent::insert_check($data))
388 $sql = "INSERT INTO ".TB_PREF.$this->name. ' (';
390 foreach($data as $fld => $value) {
391 if (in_array($fld, $this->fields) || (is_array($this->key) ? in_array($this->key) : $fld==$this->key))
392 $fields["`$fld`"] = db_escape($value);
395 return $this->error(_("Empty data set for insertion into ".$this->name));
397 $sql .= implode(',', array_keys($fields)) .') VALUES ('. implode(',', $fields).')';
399 $result = db_query($sql);
401 return $this->error(_("Cannot insert record into ").$this->name);
410 * Data set as array of arrays/objects
412 * TODO: default to: fields = ReflectionClass->getProperties
414 class array_set extends record_set {
416 var $array = array();
418 var $object_class; // name of record object class or null for arrays
420 function __construct($name, $fields, $key=null, &$arr=array(), $class = null)
422 $this->array = &$arr;
423 $this->object_class = $class;
424 parent::__construct($name, $fields, $key);
427 //===========================================================================
428 // Database functions placeholders
433 function get($key=null)
436 return parent::get();
438 return @$this->array[$key];
441 // Generic list record routine
448 function _set_record($data, $record = null)
450 if (!isset($record)) {
451 if ($this->object_class) {
452 $record = new $this->object_class;
457 foreach(array_merge($this->fields, (array)$this->key) as $n => $fld)
461 if (array_key_exists($fld, $data))
463 if ($this->object_class)
464 $record->$fld = $data[$fld];
466 $record[$fld] = $data[$fld];
470 return $updates ? $record : null;
473 // Generic update record routine
475 function update($key, $data)
477 if (parent::update($key, $data) === false)
480 $record = $this->_set_record($data, $this->array[$key]);
482 return $this->error(_("Empty update data for array ").$this->name);
484 $this->array[$key] = $record;
491 function delete($key)
493 if (!parent::delete($key))
496 unset($this->array[$key]);
503 function insert($data)
505 if (parent::insert($data) === false)
508 $record = $this->_set_record($data);
510 return $this->error(_("Empty data for array ").$this->name);
512 $this->array[] = $record;
514 $ret = array_keys($this->array);