AOOSModel.php: Various bugfixes
[AOOS.git] / AOOSModel.php
blobe6b6f1b0937b6ac78bbf5483e19ef9899f5fbece
1 <?php
2 require_once("lib/AOOSModelConstraints.php");
4 define('AOOSMODEL_TYPE_STRING', 1);
5 define('AOOSMODEL_TYPE_TEXT', 2);
6 define('AOOSMODEL_TYPE_INTEGER', 3);
7 define('AOOSMODEL_TYPE_BOOLEAN', 4);
8 define('AOOSMODEL_TYPE_UNKNOWN', 5);
10 define('AOOSMODEL_PROP_HASH', 1);
11 define('AOOSMODEL_PROP_NOHTML', 2);
12 define('AOOSMODEL_PROP_STRIP', 4);
13 define('AOOSMODEL_PROP_ESCAPE', 8);
14 define('AOOSMODEL_PROP_TIME', 16);
16 define('AOOSMODEL_FLAG_FROM_DATABASE', 1);
17 define('AOOSMODEL_FLAG_UNIQUE', 2);
18 define('AOOSMODEL_FLAG_PRIMARY_KEY', 4);
19 define('AOOSMODEL_FLAG_GUI_PRIVATE', 8);
20 define('AOOSMODEL_FLAG_GUI_NOTEDITABLE',16);
22 define('AOOSMODELROW_PRIVATE_INSERT', 1);
23 define('AOOSMODELROW_PRIVATE_UPDATE', 2);
24 define('AOOSMODELROW_PRIVATE_REMOVE', 3);
26 class AOOSModel extends AOOSModule {
27 private $_data = array();
28 private $_columnIndex = array();
29 private $_properties = array();
30 private $_constraints = array();
31 private $_types = array();
32 private $_flags = array();
33 private $_storage = null;
34 private $_rows = 0;
35 private $_lastMatch = 0;
37 private $_table = null;
39 /* ------ Data functions ------ */
40 public function appendRow($values = null) {
41 if ($values instanceof AOOSModelRow) {
42 $values = $values->toArray();
44 $row = new AOOSModelRow($this, $values, AOOSMODELROW_PRIVATE_INSERT);
45 $this->_data[] = $row;
46 $this->_rows++;
47 return null;
50 public function setData($row, $column, $value) {
51 try {
52 $r = $this->getRow($row);
53 } catch (AOOSException $e) {
54 throw $e;
56 if (!$this->inColumnIndex($column)) {
57 throw new AOOSException($this->core(), $this->tr("index_error"));
59 return $r->$column = $value;
62 public function data() {
63 $rows = array();
64 foreach ($this->_data as $row) {
65 if ($row->flag() != AOOSMODELROW_PRIVATE_REMOVE) {
66 $rows[] = $row;
69 return $rows;
72 public function getRow($index) {
73 if ($index < 0) {
74 $index = $this->rows()+$index;
76 if($index >= $this->rows() || $index < 0) {
77 throw new AOOSException($this->core(), $this->tr("bound_error"), $index);
79 return $this->_data[$index];
82 public function getRows($start, $length) {
83 $rows = array();
84 if ($start < 0) {
85 $start = $this->rows()+$start;
87 elseif ($start > $this->rows() || $start+$length > $this->rows()) {
88 throw new AOOSException($this->core(), $this->tr("bound_error"));
90 for ($i=0; $i<$length; ++$i) {
91 $rows[$start] = $this->_data[$start];
92 $start++;
94 return $rows;
97 public function rows() {
98 return $this->_rows;
101 public function remove($where, $limit) {
102 if (!is_array($where) || !is_array($limit)) {
103 throw new AOOSException($this->core(), $this->tr("not_array"));
105 $rows = $this->findAll($where);
106 $rows = array_slice($rows, $limit[0], $limit[1]);
107 foreach ($rows as $row) {
108 $row->setFlag(AOOSMODELROW_PRIVATE_REMOVE);
110 return true;
113 public function getColumn($column, $start = 0, $length = null) {
114 $columnA = array();
115 if (!$this->inColumnIndex($column)) {
116 throw new AOOSException($this->core(), $this->tr("index_error"));
118 if ($length === null) {
119 $length = $this->rows();
121 $rows = $this->getRows($start, $length);
122 foreach ($rows as $index => $row) {
123 $columnA[$index] = $row[$column];
125 return $columnA;
128 public function find($match, $offset = 0) {
129 $m = false;
130 for($i = $offset; $i < $this->rows(); ++$i) {
131 try {
132 $row = $this->getRow($i);
133 if ($row->match($match) !== false) {
134 $m = $row;
135 $this->_lastMatch = $i;
136 break;
138 } catch (AOOSException $e) {
139 throw $e;
142 return $m;
145 public function findAll($match, $offset = 0) {
146 $m = array();
147 $i = 0;
148 while (true) {
149 $row = $this->find($match, $i);
150 if ($row === false) {
151 break;
153 $m[] = $row;
154 $i = $this->_lastMatch+1;
156 return $m;
159 public function reset() {
160 $this->_data = array();
161 $this->_properties = array();
162 $this->_constraints = array();
163 $this->_flags = array();
164 $this->_rows = 0;
165 return true;
168 public function resetData() {
169 $this->_data = array();
170 $this->_rows = 0;
171 return true;
175 /* ----- Column index ------ */
176 public function setColumnIndex($columnindex) {
177 if (!is_array($columnindex)) {
178 throw new AOOSException($this->core(), $this->tr("not_array"));
180 $this->_columnIndex = $columnindex;
181 $this->reset();
182 return true;
185 public function columnIndex() {
186 return $this->_columnIndex;
189 public function inColumnIndex($column) {
190 return in_array($column, $this->columnIndex());
194 /* ------ Properties, types, flags and constraints ------ */
195 public function setProperties($column, $type, $properties = 0, $flag = 0) {
196 if (!$this->inColumnIndex($column)) {
197 throw new AOOSException($this->core(), $this->tr("index_error"));
199 if (!is_integer($properties) || !is_integer($type) || !is_integer($flag)) {
200 throw new AOOSException($this->core(), $this->tr("not_integer"));
202 $this->_properties[$column] = $properties;
203 $this->_types[$column] = $type;
204 $this->_flags[$column] = $flag;
205 return null;
208 public function getProperties($column) {
209 if (!$this->inColumnIndex($column)) {
210 throw new AOOSException($this->core(), $this->tr("index_error"));
212 if (!in_array($column, array_keys($this->_properties))) {
213 return false;
215 return $this->_properties[$column];
218 public function addConstraint($column, $constraint, $args) {
219 if (!$this->inColumnIndex($column)) {
220 throw new AOOSException($this->core(), $this->tr("index_error"));
222 $name = $constraint."Constraint";
223 if (!class_exists($name)) {
224 return false;
226 if (!is_array($args)) {
227 $args = array($args);
229 $numArgs = call_user_func(array($name, "numArgs"));
230 if (count($args) != $numArgs) {
231 return false;
233 if (!in_array($column,array_keys($this->_constraints))) {
234 $this->_constraints[$column] = array();
236 $this->_constraints[$column][] = array($name, $args);
237 return true;
240 public function getConstraints($column) {
241 if (!$this->inColumnIndex($column)) {
242 throw new AOOSException($this->core(), $this->tr("index_error"));
244 if (!in_array($column, array_keys($this->_constraints))) {
245 return false;
247 return $this->_constraints[$column];
251 * Returns the type of $column.
252 * @param Column name.
253 * @return integer
255 public function getType($column) {
256 if (!$this->inColumnIndex($column)) {
257 throw new AOOSException($this->core(), $this->tr("index_error"));
259 return $this->_types[$column];
262 public function setFlags($column, $flag) {
263 if (!$this->inColumnIndex($column)) {
264 throw new AOOSException($this->core(), $this->tr("index_error"));
266 if (!is_integer($flag)) {
267 throw new AOOSException($this->core(), $this->tr("not_integer"));
270 $this->_flags[$column] = $flag;
271 return true;
274 public function getFlags($column) {
275 if (!$this->inColumnIndex($column)) {
276 throw new AOOSException($this->core(), $this->tr("index_error"));
278 if (!in_array($column, array_keys($this->_flags))) {
279 return false;
281 return $this->_flags[$column];
285 /* ----- Storage ----- */
287 * Sets the type of the database.
288 * This must be called before AOOSModel::setTable().
289 * @param $source A string containing the type. Implemented: mysql
290 * @return true
292 public function setSource($source) {
293 if (!is_string($source)) {
294 throw new AOOSException($this->core(), $this->tr("not_string"));
297 switch (strtolower($source)) {
298 case("mysql"):
299 require_once("lib/AOOSMysqlInterface.php");
300 $this->_storage = new AOOSMysqlInterface($this->core());
301 break;
302 default:
303 exit("Not valid database");
305 return true;
309 * Sets the table on which AOOSModel::populate() and AOOSModel::save() operates on.
310 * @param $table The table name. Must be a string.
311 * @return boolean
313 public function setTable($table) {
314 if (!is_object($this->_storage)) {
315 throw new AOOSException($this->core(), $this->tr("database_not_initialized"));
317 if (!is_string($table)) {
318 throw new AOOSException($this->core(), $this->tr("not_string"));
320 $table = $this->core()->getSetting("DBPrefix").$table;
321 $this->_table = $table;
323 return $this->_storage->setTable($table);
326 public function table() {
327 return $this->_table;
330 public function populate($where = null, $order = null, $limit = null) {
331 $fields = array();
332 if (is_array($where)) {
333 $where = new AOOSModelRow($this, $where);
334 $where = $where->toDatabaseArray();
336 elseif ($where instanceof AOOSModelRow) {
337 $where = $where->toDatabaseArray();
339 elseif ($where !== null) {
340 throw new AOOSException($this->core(), $this->tr("wrong_type"));
343 foreach ($this->_flags as $column => $flag) {
344 if ($flag & AOOSMODEL_FLAG_FROM_DATABASE) {
345 $fields[] = $column;
349 try {
350 $rows = $this->_storage->select($fields, $where, $order, $limit);
351 } catch (AOOSException $e) {
352 throw $e;
354 foreach ($rows as $row) {
355 $r = new AOOSModelRow($this);
356 $r->fromArray($row);
357 $this->_data[] = $r;
358 $this->_rows++;
360 return true;
363 public function create() {
364 $flags = array();
365 foreach ($this->_flags as $column => $flag) {
366 if ($flag & AOOSMODEL_FLAG_FROM_DATABASE) {
367 $flags[$column] = $flag;
370 $types = array_intersect_key($this->_types, $flags);
371 return $this->_storage->create($types, $flags);
374 public function drop() {
375 return $this->_storage->drop();
378 public function save() {
379 foreach ($this->data() as $row) {
380 switch ($row->flag()) {
381 case(AOOSMODELROW_PRIVATE_INSERT):
382 $this->_storage->insert($row->toDatabaseArray());
383 break;
384 case(AOOSMODELROW_PRIVATE_UPDATE):
385 $this->_storage->update($row->toDatabaseArray(), $row->old(), array(0,1));
386 break;
387 case(AOOSMODELROW_PRIVATE_REMOVE):
388 $this->_storage->remove($row->toDatabaseArray(), array(0,1));
389 break;
391 $row->setFlag(0);
393 return true;
397 class AOOSModelRow extends AOOSModule implements ArrayAccess{
398 private $_data = array();
399 private $_flag = 0;
400 private $_old = null;
402 public function __construct($parent, $value = null, $flag = 0) {
403 parent::__construct($parent);
404 $this->setFlag($flag);
405 $this->_data = array_fill_keys($this->parent()->columnIndex(), null);
406 if (is_array($value)) {
407 $values = array_intersect_key($value, $this->_data);
408 foreach ($values as $index => $val) {
409 $this->_setData($index, $value[$index]);
414 public function __set($name, $value) {
415 return $this->_setData($name, $value);
418 public function __get($name) {
419 if (!$this->parent()->inColumnIndex($name)) {
420 throw new AOOSException($this->core(), $this->tr("index_error"));
422 return $this->_data[$name];
425 public function __toString() {
426 return print_r($this->_data, true);
429 public function offsetSet($name, $value) {
430 return $this->$name = $value;
433 public function offsetGet($name) {
434 return $this->$name;
437 public function offsetExists($name) {
438 return $this->parent()->inColumnIndex($name);
441 public function offsetUnset($name) {
442 if (!$this->parent()->inColumnIndex($name)) {
443 throw new AOOSException($this->core(), $this->tr("index_error"));
445 $this->$name = null;
446 return true;
449 public function match($match) {
450 $matches = array_intersect_key($match, $this->_data);
451 foreach ($matches as $key => $val) {
452 if (!preg_match("/".$val."/", $this->$key)) {
453 return false;
456 return true;
459 public function toArray() {
460 $data = $this->_data;
461 return $data;
464 public function toDatabaseArray($data = null) {
465 if ($data == null) {
466 $data = $this->_data;
468 foreach ($data as $key => $value) {
469 $flags =$this->parent()->getFlags($key);
470 if (!($flags & AOOSMODEL_FLAG_FROM_DATABASE) || ($flags & AOOSMODEL_FLAG_PRIMARY_KEY)) {
471 unset($data[$key]);
472 continue;
474 $type = $this->parent()->getType($key);
475 if ($type == AOOSMODEL_TYPE_STRING || $type == AOOSMODEL_TYPE_TEXT) {
476 $data[$key] = "'".$value."'";
479 return $data;
483 public function fromArray($value) {
484 $this->_data = array_fill_keys($this->parent()->columnIndex(), null);
485 $values = array_intersect_key($value, $this->_data);
486 foreach ($values as $key => $value) {
487 $this->_data[$key] = $value;
489 return true;
492 public function old($db = true) {
493 return $db ? $this->toDatabaseArray($this->_old) : $this->_old;
496 public function save() {
497 return $this->parent()->save();
500 public function setFlag($flag) {
501 if (!is_integer($flag)) {
502 throw new AOOSException($this->core(), $this->tr("not_integer"));
504 $this->_flag = $flag;
505 return true;
508 public function flag() {
509 return $this->_flag;
512 /* ------ Private functions ----- */
513 private function _checkType($name, $value) {
514 $type = $this->parent()->getType($name);
515 switch($type) {
516 case(AOOSMODEL_TYPE_STRING):
517 case(AOOSMODEL_TYPE_TEXT):
518 return is_string($value);
519 break;
520 case(AOOSMODEL_TYPE_INTEGER):
521 $value = (int) $value;
522 return is_integer($value);
523 break;
524 case(AOOSMODEL_TYPE_BOOLEAN):
525 return is_bool($value);
526 break;
527 case(AOOSMODEL_TYPE_UNKNOWN):
528 return true;
529 break;
530 default:
531 return false;
532 break;
536 private function _checkFlags($name, $value) {
537 $flags = $this->parent()->getFlags($name);
538 if ($flags === false) {
539 return true;
541 if ($flags & AOOSMODEL_FLAG_UNIQUE) {
542 $column = $this->parent()->getColumn($name);
543 if (in_array($value, $column)) {
544 return false;
547 return true;
550 private function _doProperties($name, $value) {
551 $properties = $this->parent()->getProperties($name);
552 if ($properties === false) {
553 return $value;
555 if ($properties & AOOSMODEL_PROP_TIME) {
556 $value = time();
557 return $value;
559 if ($properties & AOOSMODEL_PROP_HASH) {
560 $value = hash("sha256", $value);
562 if ($properties & AOOSMODEL_PROP_NOHTML) {
563 $value = htmlspecialchars($value);
565 if ($properties & AOOSMODEL_PROP_ESCAPE) {
566 $value = mysql_real_escape_string($value);
568 if ($properties & AOOSMODEL_PROP_STRIP) {
569 $value = rtrim(trim($value));
571 return $value;
574 private function _doConstraints($name, $value) {
575 $constraints = $this->parent()->getConstraints($name);
576 if ($constraints === false) {
577 return $value;
579 foreach ($constraints as $constraint) {
580 if (!call_user_func(array($constraint[0], "check"), $constraint[1], $value)) {
581 continue;
583 $value = call_user_func(array($constraint[0], "execute"), $constraint[1], $value);
584 if (false === $value) {
585 throw new AOOSException($this->core(), $this->tr("constraint_fail"), $name);
586 return false;
589 return $value;
592 private function _setData($name, $value) {
593 if ($this->flag() == AOOSMODELROW_PRIVATE_REMOVE) {
594 return false;
596 if (!$this->parent()->inColumnIndex($name)) {
597 throw new AOOSException($this->core(), $this->tr("index_error"));
599 if (!$this->_checkType($name, $value)) {
600 throw new AOOSException($this->core(), $this->tr("wrong_type"), $name);
602 try {
603 $value = $this->_doProperties($name, $value);
604 $value = $this->_doConstraints($name, $value);
605 } catch (AOOSException $e) {
606 throw $e;
608 if (!$this->_checkFlags($name, $value)) {
609 throw new AOOSException($this->core(), $this->tr("flags_failed"), $name);
611 if ($this->flag() == 0) {
612 $this->setFlag(AOOSMODELROW_PRIVATE_UPDATE);
613 $this->_old = $this->_data;
615 $this->_data[$name] = $value;
616 return true;