User: Prepared module for porting to new AOOSModel
[AOOS.git] / AOOSModel.php
blobf521e09df2cc54e8bc5b488baba50ad8be1d32f5
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 define('AOOSMODEL_CACHE_STATIC', 1); // Only populate if empty
27 define('AOOSMODEL_CACHE_DYNAMIC', 2); // Clear and populate always
29 class AOOSModel extends AOOSModule {
30 private $_data = array();
31 private $_columnIndex = array();
32 private $_properties = array();
33 private $_constraints = array();
34 private $_types = array();
35 private $_flags = array();
36 private $_storage = null;
37 private $_rows = 0;
38 private $_lastMatch = 0;
39 private $_cache = AOOSMODEL_CACHE_STATIC;
41 private $_table = null;
43 /* ------ Data functions ------ */
44 /**
45 * Appends a row to the end of the model. If given an array, the function creates the row with the values specified
46 * in the array. Otherwise the row is empty.
47 * @param $values An array with the values to fill the row with. Array keys are column indeces and array values are
48 * the values used to fill the array.
49 * @return true
51 public function appendRow($values = null) {
52 if ($values instanceof AOOSModelRow) {
53 $values = $values->toArray();
55 $row = new AOOSModelRow($this, $values, AOOSMODELROW_PRIVATE_INSERT);
56 $this->_data[] = $row;
57 $this->_rows++;
58 return true;
61 /**
62 * Sets value of $column in row $row to be $value. Throws an AOOSException if $row isn't valid, if the specified
63 * column isn't a valid column index or setting the value for some reason failed.
64 * @param $row An integer representing the row
65 * @param $column A valid column index.
66 * @param $value The value
67 * @return boolean
69 public function setData($row, $column, $value) {
70 try {
71 $r = $this->getRow($row);
72 $ret = $r->$column = $value;
73 } catch (AOOSException $e) {
74 throw $e;
76 return $ret;
79 /**
80 * Returns all rows that haven't been removed
81 * @return array
83 public function data() {
84 $rows = array();
85 foreach ($this->_data as $row) {
86 if ($row->flag() != AOOSMODELROW_PRIVATE_REMOVE) {
87 $rows[] = $row;
90 return $rows;
93 /**
94 * Returns the row at $index position. If a negative value is given it is counted from the end. Throws an AOOSException
95 * if $index isn't within bounds.
96 * @param $index An integer representing the row
97 * @sa getRows
98 * @return AOOSModelRow
100 public function getRow($index) {
101 if ($index < 0) {
102 $index = $this->rows()+$index;
104 if($index >= $this->rows() || $index < 0) {
105 throw new AOOSException($this->core(), $this->tr("bound_error"), $index);
107 return $this->_data[$index];
111 * Returns all rows from $start until $length rows is found. If $start is negative, it is counted from the end. Throws an AOOSException if $start or $start+$length aren't within bounds.
112 * @param $start The start row. An integer.
113 * @param $length Get $length number of rows. An integer.
114 * @sa getRows
115 * @return array
117 public function getRows($start, $length) {
118 $rows = array();
119 if ($start < 0) {
120 $start = $this->rows()+$start;
122 elseif ($start > $this->rows() || $start+$length > $this->rows()) {
123 throw new AOOSException($this->core(), $this->tr("bound_error"));
125 for ($i=0; $i<$length; ++$i) {
126 $rows[$start] = $this->_data[$start];
127 $start++;
129 return $rows;
133 * Returns the number of rows.
134 * @return integer
136 public function rows() {
137 return $this->_rows;
141 * Removes rows that match $where. $limit defines the number of rows to remove. Both $where and $limit are arrays.
142 * $limit consists of two entries, $limit[0] which defines the start of which rows to remove and $limit[1] which
143 * defines the number of rows to remove. Throws an AOOSException if $where or $limit aren't arrays.
144 * @param $where An array against which all rows are matched.
145 * @param $limit The limit array.
146 * @return true.
148 public function remove($where, $limit) {
149 if (!is_array($where) || !is_array($limit)) {
150 throw new AOOSException($this->core(), $this->tr("not_array"));
152 $rows = $this->findAll($where);
153 $rows = array_slice($rows, $limit[0], $limit[1]);
154 foreach ($rows as $row) {
155 $row->setFlag(AOOSMODELROW_PRIVATE_REMOVE);
157 return true;
161 * Returns the column $column. $start defines the start row and $length defines the number of rows. $start and
162 * $length are optional. Throws an AOOSException if $column isn't a valid column index.
163 * @param $column The column name.
164 * @param $start An integer representing the start row. Defaults to 0.
165 * @param $length The number or rows to use. Defaults to all rows.
166 * @return array
168 public function getColumn($column, $start = 0, $length = null) {
169 $columnA = array();
170 if (!$this->inColumnIndex($column)) {
171 throw new AOOSException($this->core(), $this->tr("index_error"));
173 if ($length === null) {
174 $length = $this->rows();
176 $rows = $this->getRows($start, $length);
177 foreach ($rows as $index => $row) {
178 $columnA[$index] = $row[$column];
180 return $columnA;
184 * Returns the first row that matches $match from $offset and forwards. The rows are matched using preg_match and
185 * delimiters should NOT be included in the array values. Throws an AOOSException if $offset isn't within bounds.
186 * @param $match An array in which keys are column names and values are the values to match against.
187 * @param $offset The row to start searching in.
188 * @sa findAll
189 * @return AOOSModelRow or false if no rows are found.
191 public function find($match, $offset = 0) {
192 $m = false;
193 for($i = $offset; $i < $this->rows(); ++$i) {
194 try {
195 $row = $this->getRow($i);
196 if ($row->match($match) !== false) {
197 $m = $row;
198 $this->_lastMatch = $i;
199 break;
201 } catch (AOOSException $e) {
202 throw $e;
205 return $m;
209 * Returns all rows that match $match from $offset and forwards. Throws an AOOSException if $offset isn't within
210 * bounds.
211 * @param $match An array in which keys are column names and values are the values to match against.
212 * @param $offset The row to start searching in.
213 * @sa find
214 * @return array
216 public function findAll($match, $offset = 0) {
217 $m = array();
218 $i = 0;
219 while (true) {
220 try {
221 $row = $this->find($match, $i);
222 } catch (AOOSException $e) {
223 throw $e;
225 if ($row === false) {
226 break;
228 $m[] = $row;
229 $i = $this->_lastMatch+1;
231 return $m;
235 * Empties the model and removes all propeties, constraints and flags. Only column indeces are kept.
236 * @sa resetData
237 * @return true
239 public function reset() {
240 $this->_data = array();
241 $this->_properties = array();
242 $this->_constraints = array();
243 $this->_flags = array();
244 $this->_rows = 0;
245 return true;
249 * Empties the model.
250 * @sa reset
251 * @return true;
253 public function resetData() {
254 $this->_data = array();
255 $this->_rows = 0;
256 return true;
259 /* ----- Cache mode ------ */
260 public function setCacheMode($cacheMode) {
261 if ($cacheMode != AOOSMODEL_CACHE_STATIC && $cacheMode != AOOSMODEL_CACHE_DYNAMIC) {
262 throw new AOOSExcpetion($this->core(), $this->tr("not_valid_cache_mode"));
264 $this->_cache = $cacheMode;
265 return true;
268 public function cacheMode() {
269 return $this->_cache;
273 /* ----- Column index ------ */
274 public function setColumnIndex($columnindex) {
275 if (!is_array($columnindex)) {
276 throw new AOOSException($this->core(), $this->tr("not_array"));
278 $this->_columnIndex = $columnindex;
279 $this->reset();
280 return true;
283 public function columnIndex() {
284 return $this->_columnIndex;
287 public function inColumnIndex($column) {
288 return in_array($column, $this->columnIndex());
292 /* ------ Properties, types, flags and constraints ------ */
293 public function setProperties($column, $type, $properties = 0, $flag = 0) {
294 if (!$this->inColumnIndex($column)) {
295 throw new AOOSException($this->core(), $this->tr("index_error"));
297 if (!is_integer($properties) || !is_integer($type) || !is_integer($flag)) {
298 throw new AOOSException($this->core(), $this->tr("not_integer"));
300 $this->_properties[$column] = $properties;
301 $this->_types[$column] = $type;
302 $this->_flags[$column] = $flag;
303 return null;
306 public function getProperties($column) {
307 if (!$this->inColumnIndex($column)) {
308 throw new AOOSException($this->core(), $this->tr("index_error"));
310 if (!in_array($column, array_keys($this->_properties))) {
311 return false;
313 return $this->_properties[$column];
316 public function addConstraint($column, $constraint, $args) {
317 if (!$this->inColumnIndex($column)) {
318 throw new AOOSException($this->core(), $this->tr("index_error"));
320 $name = $constraint."Constraint";
321 if (!class_exists($name)) {
322 return false;
324 if (!is_array($args)) {
325 $args = array($args);
327 $numArgs = call_user_func(array($name, "numArgs"));
328 if (count($args) != $numArgs) {
329 return false;
331 if (!in_array($column,array_keys($this->_constraints))) {
332 $this->_constraints[$column] = array();
334 $this->_constraints[$column][] = array($name, $args);
335 return true;
338 public function getConstraints($column) {
339 if (!$this->inColumnIndex($column)) {
340 throw new AOOSException($this->core(), $this->tr("index_error"));
342 if (!in_array($column, array_keys($this->_constraints))) {
343 return false;
345 return $this->_constraints[$column];
349 * Returns the type of $column.
350 * @param Column name.
351 * @return integer
353 public function getType($column) {
354 if (!$this->inColumnIndex($column)) {
355 throw new AOOSException($this->core(), $this->tr("index_error"));
357 return $this->_types[$column];
360 public function setFlags($column, $flag) {
361 if (!$this->inColumnIndex($column)) {
362 throw new AOOSException($this->core(), $this->tr("index_error"));
364 if (!is_integer($flag)) {
365 throw new AOOSException($this->core(), $this->tr("not_integer"));
368 $this->_flags[$column] = $flag;
369 return true;
372 public function getFlags($column) {
373 if (!$this->inColumnIndex($column)) {
374 throw new AOOSException($this->core(), $this->tr("index_error"));
376 if (!in_array($column, array_keys($this->_flags))) {
377 return false;
379 return $this->_flags[$column];
383 /* ----- Storage ----- */
385 * Sets the type of the database.
386 * This must be called before AOOSModel::setTable().
387 * @param $source A string containing the type. Implemented: mysql
388 * @return true
390 public function setSource($source) {
391 if (!is_string($source)) {
392 throw new AOOSException($this->core(), $this->tr("not_string"));
395 switch (strtolower($source)) {
396 case("mysql"):
397 require_once("lib/AOOSMysqlInterface.php");
398 $this->_storage = new AOOSMysqlInterface($this->core());
399 break;
400 default:
401 exit("Not valid database");
403 return true;
407 * Sets the table on which AOOSModel::populate() and AOOSModel::save() operates on.
408 * @param $table The table name. Must be a string.
409 * @return boolean
411 public function setTable($table) {
412 if (!is_object($this->_storage)) {
413 throw new AOOSException($this->core(), $this->tr("database_not_initialized"));
415 if (!is_string($table)) {
416 throw new AOOSException($this->core(), $this->tr("not_string"));
418 $table = $this->core()->getSetting("DBPrefix").$table;
419 $this->_table = $table;
421 return $this->_storage->setTable($table);
424 public function table() {
425 return $this->_table;
428 public function populate($where = null, $order = null, $limit = null) {
429 switch($this->_cache) {
430 case(AOOSMODEL_CACHE_STATIC):
431 if ($this->rows() != 0) {
432 return false;
434 break;
435 case(AOOSMODEL_CACHE_DYNAMIC):
436 $this->resetData();
437 break;
439 $fields = array();
440 if (is_array($where)) {
441 $where = new AOOSModelRow($this, $where);
442 $where = $where->toDatabaseArray();
444 elseif ($where instanceof AOOSModelRow) {
445 $where = $where->toDatabaseArray();
447 elseif ($where !== null) {
448 throw new AOOSException($this->core(), $this->tr("wrong_type"));
451 foreach ($this->_flags as $column => $flag) {
452 if ($flag & AOOSMODEL_FLAG_FROM_DATABASE) {
453 $fields[] = $column;
457 try {
458 $rows = $this->_storage->select($fields, $where, $order, $limit);
459 } catch (AOOSException $e) {
460 throw $e;
462 foreach ($rows as $row) {
463 $r = new AOOSModelRow($this);
464 $r->fromArray($row);
465 $this->_data[] = $r;
466 $this->_rows++;
468 return true;
471 public function create() {
472 $flags = array();
473 foreach ($this->_flags as $column => $flag) {
474 if ($flag & AOOSMODEL_FLAG_FROM_DATABASE) {
475 $flags[$column] = $flag;
478 $types = array_intersect_key($this->_types, $flags);
479 return $this->_storage->create($types, $flags);
482 public function drop() {
483 return $this->_storage->drop();
486 public function save() {
487 foreach ($this->data() as $row) {
488 switch ($row->flag()) {
489 case(AOOSMODELROW_PRIVATE_INSERT):
490 $this->_storage->insert($row->toDatabaseArray());
491 break;
492 case(AOOSMODELROW_PRIVATE_UPDATE):
493 $this->_storage->update($row->toDatabaseArray(), $row->old(), array(0,1));
494 break;
495 case(AOOSMODELROW_PRIVATE_REMOVE):
496 $this->_storage->remove($row->toDatabaseArray(), array(0,1));
497 break;
499 $row->setFlag(0);
501 return true;
505 class AOOSModelRow extends AOOSModule implements ArrayAccess{
506 private $_data = array();
507 private $_flag = 0;
508 private $_old = null;
510 public function __construct($parent, $value = null, $flag = 0) {
511 parent::__construct($parent);
512 $this->setFlag($flag);
513 $this->_data = array_fill_keys($this->parent()->columnIndex(), null);
514 if (is_array($value)) {
515 $values = array_intersect_key($value, $this->_data);
516 foreach ($values as $index => $val) {
517 $this->_setData($index, $value[$index]);
522 public function __set($name, $value) {
523 try {
524 $ret = $this->_setData($name, $value);
525 } catch (AOOSException $e) {
526 throw $e;
528 return $ret;
531 public function __get($name) {
532 if (!$this->parent()->inColumnIndex($name)) {
533 throw new AOOSException($this->core(), $this->tr("index_error"));
535 return $this->_data[$name];
538 public function __toString() {
539 return print_r($this->_data, true);
542 public function offsetSet($name, $value) {
543 return $this->$name = $value;
546 public function offsetGet($name) {
547 return $this->$name;
550 public function offsetExists($name) {
551 return $this->parent()->inColumnIndex($name);
554 public function offsetUnset($name) {
555 if (!$this->parent()->inColumnIndex($name)) {
556 throw new AOOSException($this->core(), $this->tr("index_error"));
558 $this->$name = null;
559 return true;
562 public function match($match) {
563 $matches = array_intersect_key($match, $this->_data);
564 foreach ($matches as $key => $val) {
565 if (!preg_match("/".$val."/", $this->$key)) {
566 return false;
569 return true;
572 public function toArray() {
573 $data = $this->_data;
574 return $data;
577 public function toDatabaseArray($data = null) {
578 if ($data == null) {
579 $data = $this->_data;
581 foreach ($data as $key => $value) {
582 $flags =$this->parent()->getFlags($key);
583 if (!($flags & AOOSMODEL_FLAG_FROM_DATABASE) || ($flags & AOOSMODEL_FLAG_PRIMARY_KEY)) {
584 unset($data[$key]);
585 continue;
587 $type = $this->parent()->getType($key);
588 if ($type == AOOSMODEL_TYPE_STRING || $type == AOOSMODEL_TYPE_TEXT) {
589 $data[$key] = "'".$value."'";
592 return $data;
596 public function fromArray($value) {
597 $this->_data = array_fill_keys($this->parent()->columnIndex(), null);
598 $values = array_intersect_key($value, $this->_data);
599 foreach ($values as $key => $value) {
600 $this->_data[$key] = $value;
602 return true;
605 public function old($db = true) {
606 return $db ? $this->toDatabaseArray($this->_old) : $this->_old;
609 public function save() {
610 return $this->parent()->save();
613 public function setFlag($flag) {
614 if (!is_integer($flag)) {
615 throw new AOOSException($this->core(), $this->tr("not_integer"));
617 $this->_flag = $flag;
618 return true;
621 public function flag() {
622 return $this->_flag;
625 /* ------ Private functions ----- */
626 private function _checkType($name, $value) {
627 $type = $this->parent()->getType($name);
628 switch($type) {
629 case(AOOSMODEL_TYPE_STRING):
630 case(AOOSMODEL_TYPE_TEXT):
631 return is_string($value);
632 break;
633 case(AOOSMODEL_TYPE_INTEGER):
634 $value = (int) $value;
635 return is_integer($value);
636 break;
637 case(AOOSMODEL_TYPE_BOOLEAN):
638 return is_bool($value);
639 break;
640 case(AOOSMODEL_TYPE_UNKNOWN):
641 return true;
642 break;
643 default:
644 return false;
645 break;
649 private function _checkFlags($name, $value) {
650 $flags = $this->parent()->getFlags($name);
651 if ($flags === false) {
652 return true;
654 if ($flags & AOOSMODEL_FLAG_UNIQUE) {
655 $column = $this->parent()->getColumn($name);
656 if (in_array($value, $column)) {
657 return false;
660 return true;
663 private function _doProperties($name, $value) {
664 $properties = $this->parent()->getProperties($name);
665 if ($properties === false) {
666 return $value;
668 if ($properties & AOOSMODEL_PROP_TIME) {
669 $value = time();
670 return $value;
672 if ($properties & AOOSMODEL_PROP_HASH) {
673 $value = hash("sha256", $value);
675 if ($properties & AOOSMODEL_PROP_NOHTML) {
676 $value = htmlspecialchars($value);
678 if ($properties & AOOSMODEL_PROP_ESCAPE) {
679 $value = mysql_real_escape_string($value);
681 if ($properties & AOOSMODEL_PROP_STRIP) {
682 $value = rtrim(trim($value));
684 return $value;
687 private function _doConstraints($name, $value) {
688 $constraints = $this->parent()->getConstraints($name);
689 if ($constraints === false) {
690 return $value;
692 foreach ($constraints as $constraint) {
693 if (!call_user_func(array($constraint[0], "check"), $constraint[1], $value)) {
694 continue;
696 $value = call_user_func(array($constraint[0], "execute"), $constraint[1], $value);
697 if (false === $value) {
698 throw new AOOSException($this->core(), $this->tr("constraint_fail"), $name);
699 return false;
702 return $value;
705 private function _setData($name, $value) {
706 if ($this->flag() == AOOSMODELROW_PRIVATE_REMOVE) {
707 return false;
709 if (!$this->parent()->inColumnIndex($name)) {
710 throw new AOOSException($this->core(), $this->tr("index_error"));
712 if (!$this->_checkType($name, $value)) {
713 throw new AOOSException($this->core(), $this->tr("wrong_type"), $name);
715 try {
716 $value = $this->_doProperties($name, $value);
717 $value = $this->_doConstraints($name, $value);
718 } catch (AOOSException $e) {
719 throw $e;
721 if (!$this->_checkFlags($name, $value)) {
722 throw new AOOSException($this->core(), $this->tr("flags_failed"), $name);
724 if ($this->flag() == 0) {
725 $this->setFlag(AOOSMODELROW_PRIVATE_UPDATE);
726 $this->_old = $this->_data;
728 $this->_data[$name] = $value;
729 return true;