index.php
[AOOS.git] / AOOSModel.php
blobc4a8f79c5d8370d37e1417b5582c6227028af33b
1 <?php
2 define("AOOSMODEL_TYPE_STRING" , 1);
3 define("AOOSMODEL_TYPE_INTEGER" , 2);
4 define("AOOSMODEL_TYPE_ARRAY" , 4);
5 define("AOOSMODEL_TYPE_AOOSMODEL" , 8);
6 define("AOOSMODEL_TYPE_UNKNOWN" , 16);
8 define("AOOSMODEL_PROP_DATA_INCREASING" , 1);
9 define("AOOSMODEL_PROP_DATA_DECREASING" , 2);
10 define("AOOSMODEL_PROP_DATA_QUOTES" , 4);
11 define("AOOSMODEL_PROP_DATA_ESCAPE" , 8);
12 define("AOOSMODEL_PROP_DATA_NOHTML" , 16);
13 define("AOOSMODEL_PROP_DATA_HASH" , 32);
15 define("AOOSMODEL_PROP_GUI_NOTEDITABLE" , 1<<8);
16 define("AOOSMODEL_PROP_GUI_PRIVATE" , 2<<8);
17 define("AOOSMODEL_PROP_FROM_DATABASE" , 4<<8);
19 /**
20 * Second-generation AOOSModel
21 * On the good side:
22 * - The addition of properties allowing easy per-column settings
23 * @author Sebastian Skejø
25 class AOOSModel extends AOOSModule {
26 const INTERN_DATA_UPDATED = 1;
27 const INTERN_DATA_REMOVED = 2;
28 const INTERN_DATA_INSERTED = 3;
30 private $_data = array();
31 private $_index = array();
32 private $_props = array();
33 private $_types = array();
34 private $_intern = array();
36 private $_source = null;
37 private $_table = null;
38 private $_connection = null;
41 private $_rows = 0;
42 private $_curRow = 0;
43 private $_curCol = 0;
45 /**
46 * Standard destructor
48 public function __destruct() {
51 /**
52 * Transforms to a printable string
54 public function __toString() {
55 return nl2br(str_replace(" ", "&nbsp;", print_r($this->data(), true)));
58 /**
59 * Appends a row to the end of the model with 0 as values unless $values is
60 * provided. Appended rows will NOT be inserted into database when AOOSModel::save() is called.
61 * @param $values If given null an empty row will be added. Otherwise an
62 * associative array is assumed.
63 * @param $ignore Flags to ignores when inserting data.
64 * @see insert
65 * @return true
67 public function appendRow($values = null, $ignore = 0) {
68 $this->_data[] = array_fill_keys($this->_index, 0);
69 $this->_rows++;
70 if (is_array($values)) {
71 $index = $this->columnIndex();
72 $last = $this->rows()-1;
73 foreach ($values as $key => $value) {
74 if (in_array($key, $index)) {
75 $this->setData($last, $key, $value, $ignore);
79 return true;
82 /**
83 * Sets the data of a single field. Fields updated this way will NOT be
84 * updated in the database when AOOSModel::save() is called. If the type of
85 * the inserted data doesn't match the column type, an AOOSTypeException is
86 * thrown. If $row is less than 0 or greater that the row count, an
87 * AOOSException is throw.
88 * @param $row An integer representing the row
89 * @param $column A column index
90 * @param $value Value
91 * @param $ignore Flags to ignore when inserting data
92 * @return bool
94 public function setData($row, $column, $value, $ignore = 0) {
95 if (!$this->inColumnIndex($column)) {
96 // Exception?
97 return false;
99 $props = $this->_props[$column];
100 $type = $this->_types[$column];
102 try {
103 $this->_checkType($value, $type);
105 catch (AOOSTypeException $e) {
106 throw $e;
107 return false;
110 $value = $this->_fixDataProps($value, $props, $ignore);
112 // XXX
113 if ($row < 0 || $row > $this->rows()) {
114 throw new AOOSException($this->core(), $this->tr("row_out_of_bounds"));
115 return false;
117 $this->_data[$row][$column] = $value;
118 return true;
122 * Sets a row of data
123 * @param $row The row number
124 * @param $values An associative array with column indices as keys and new values as
125 * array values
126 * @param $ignore Flags to ignore when inserting data
127 * @return bool
128 * @sa setData
130 public function setDataRow($row, $values, $ignore = 0) {
131 foreach ($values as $index => $value) {
132 if (!$this->setData($row, $index, $value, $ignore)) {
133 return false;
136 return true;
139 private function _checkType($value, $type) {
140 // Only type-props
141 $correct = false;
142 $continue = true;
143 if ($type & AOOSMODEL_TYPE_UNKNOWN) {
144 $correct = true;
145 $continue = false;
147 elseif($continue && $type & AOOSMODEL_TYPE_STRING) {
148 $correct = is_string($value);
149 $continue = false;
151 elseif($continue && $type & AOOSMODEL_TYPE_INTEGER) {
152 $correct = is_integer((int)$value);
153 $continue = false;
155 elseif($continue && $type & AOOSMODEL_TYPE_ARRAY) {
156 $correct = is_array($value);
157 $continue = false;
159 elseif($continue && $type & AOOSMODEL_TYPE_AOOSMODEL) {
160 $corrent = $value instanceof AOOSModel;
161 $continue = false;
164 if (!$correct) {
165 throw new AOOSTypeException($this->core(), "", "When inserting ".$value);
166 return false;
168 return true;
171 private function _fixDataProps($value, $props, $ignore = 0) {
172 // Only data props
173 if ($props & AOOSMODEL_PROP_DATA_NOHTML && !(AOOSMODEL_PROP_DATA_NOHTML & $ignore)) {
174 $value = htmlspecialchars($value);
177 if ($props & AOOSMODEL_PROP_DATA_ESCAPE && !(AOOSMODEL_PROP_DATA_ESCAPE & $ignore)) {
178 $value = mysql_real_escape_string($value);
181 if ($props & AOOSMODEL_PROP_DATA_HASH && !(AOOSMODEL_PROP_DATA_HASH & $ignore)) {
182 $value = hash("sha256", $value);
184 if ($props & AOOSMODEL_PROP_DATA_QUOTES && !(AOOSMODEL_PROP_DATA_QUOTES & $ignore)) {
185 $first = substr($value, 0,1);
186 if ($first != "'" && $first != '"' && $first != substr($value, -1)) {
187 $value = "'".$value."'";
191 return $value;
195 * Returns a to-dimensional array containing the data.
196 * @return array
198 public function data() {
199 return $this->_data;
203 * Returns the column with the name $column
204 * @param $column The name of the column
205 * @param $offset Column values starts at this row
206 * @param $length The array of column values has this length
207 * @return array
209 public function getColumn($column, $start = 0, $length = null) {
210 if (!$this->inColumnIndex($column)) {
211 return false;
213 if ($length === null) {
214 $length = $this->rows();
216 if ($start < 0) {
217 $start = $this->rows() + $start;
219 $a = array();
220 for ($i= $start; $length > 0; $length--) {
221 $r = $this->getRow($i);
222 $a[] = $r[$column];
223 $i++;
225 return $a;
229 * Returns an array containing the columns of the given $row
230 * @param $row The row number. If this is negative the row selected is the
231 * $row'th number from the end.
232 * @return array
234 public function getRow($row) {
235 return array_pop(array_slice($this->data(), $row, 1));
239 * Returns the number of rows
240 * @return integer
242 public function rows() {
243 return $this->_rows;
247 * Returns only columns which match the given properties
248 * @param $props Properties
250 public function filter($props = 0, $offset = 0, $length = null) {
251 if ($length === null) {
252 $length = $this->rows();
254 $a = array();
255 for ($i = $offset; $length > 0; $length--) {
256 $r = $this->getRow($i);
257 $ra = array();
258 foreach ($this->_props as $col => $colprops) {
259 if ($colprops & $props) {
260 $ra[$col] = $r[$col];
263 $a[] = $ra;
264 $i++;
266 return $a;
270 * Returns the first row from $start which matches $match
271 * @param $match An associative array in which keys should match column
272 * indices and values should match values
273 * @param $start An integer representing the row from which search should.
274 * Defaults to 0.
275 * start
276 * @return Integer
277 * @sa findAll
279 public function find($match, $start = 0) {
280 if ($match && !($match = $this->_fixWhere($match))) {
281 return false;
283 for ($i = $start; $i < $this->rows(); $i++) {
284 $r = $this->getRow($i);
285 $fkey = key($match);
286 if ($r[$fkey] == $match[$fkey]) {
287 foreach ($match as $key => $val) {
288 if ($r[$key] != $val) {
289 continue 2;
292 return $i;
295 return false;
299 * Returns all rows which match $match, starting from $start
300 * @param $match The row to match against.
301 * @param $start An integer representing the row from which to start from.
302 * Defaults to 0.
303 * @return An array containing integers.
304 * @sa find
306 public function findAll($match, $start = 0, $length = null) {
307 if ($length === null) {
308 $length = $this->rows();
310 $i = $start;
311 $a = array();
312 while (true) {
313 if (!($f = $this->find($match, $i))) {
314 break;
316 $a[] = $f;
317 $i = ++$f;
319 $a = array_slice($a, 0, $length);
320 return count($a) ? $a : false;
324 * Sets a columns properties to be $property
325 * @param $column Column index name
326 * @param $type The type of the column
327 * @param $property An integer representing the property
328 * @return true
330 public function setProperty($column, $type, $property) {
331 if (!$this->inColumnIndex($column)) {
332 // Exception?
333 return false;
335 $this->_types[$column] = $type;
336 $this->_props[$column] = $property;
337 return true;
341 * Returns the properties of the given $column
342 * @param $column Name of the column
343 * @return integer
345 public function getProperty($column) {
346 if (!$this->inColumnIndex($column)) {
347 return false;
349 return $this->_props[$column];
353 * Toggles a given property using the bitwise OR operation.
354 * @param $column Column index name
355 * @param $property An integer representing the property to toggle
356 * @return true
358 public function toggleProperty($column, $property) {
359 if (!$this->inColumnIndex($column)) {
360 // Exception?
361 return false;
363 $this->_props[$column] |= $property;
364 return true;
368 * Returns the type of the given $column
369 * @param $column Name of the column
370 * @return string
372 public function getType($column) {
373 if (!$this->inColumnIndex($column)) {
374 // Exception?
375 return false;
377 $type = $this->_types[$column];
378 $typename = 0;
379 $continue = true;
380 if ($type & AOOSMODEL_TYPE_UNKNOWN) {
381 $typename = "UNKNOWN";
382 $continue = false;
384 elseif($continue && $type & AOOSMODEL_TYPE_STRING) {
385 $typename = "STRING";
386 $continue = false;
388 elseif($continue && $type & AOOSMODEL_TYPE_INTEGER) {
389 $typename = "INTEGER";
390 $continue = false;
392 elseif($continue && $type & AOOSMODEL_TYPE_ARRAY) {
393 $typename = "ARRAY";
394 $continue = false;
396 elseif($continue && $type & AOOSMODEL_TYPE_AOOSMODEL) {
397 $typename = "AOOSModel";
398 $continue = false;
401 return $typename;
405 * Returns true if the given $property is set, otherwise return false
406 * @param $column Column index name
407 * @param $property An integer representing the property
408 * @return boolean
410 public function checkProperty($column, $property) {
411 if (!$this->inColumnIndex($column)) {
412 // Exception?
413 return false;
415 if ($this->_props[$column] & $property) {
416 return true;
418 return false;
422 * Sets the column index to be $index. Note: this resets the properties AND
423 * data
424 * If $index isn't an array an AOOSException is thrown.
425 * @param $index An array containing the column indices
426 * @return true
428 public function setColumnIndex($index) {
429 if (!is_array($index)) {
430 throw new AOOSTypeException($this->core(), $this->tr("not_array"));
431 return false;
433 $this->_index = $index;
434 $this->_props = array_fill_keys($index, 0);
435 return true;
439 * Returns the column index
440 * @return array
442 public function columnIndex() { return $this->_index; }
445 * Returns true if the given $index is in the column index. Otherwise returns
446 * false.
447 * @param $index Name of the index
448 * @return bool
450 public function inColumnIndex($index) {
451 return in_array($index, $this->_index);
456 * Sets the table from in which data is contained
457 * @param $table Table name
458 * @return true
460 public function setTable($table) {
461 $this->_table = $this->core()->getSetting("DBPrefix").$table;
462 return true;
466 * Returns the table name
467 * @return string
469 public function table() {
470 return $this->_table;
474 * Sets the type of source from which data is fetched, inserted and deleted.
475 * @param $source The type of source, e.g. "mysql".
476 * @return true
478 public function setSource($source) {
479 $this->_source = strtolower($source);
480 return true;
484 * Returns the type of source in a string
485 * @return string
487 public function source() {
488 return $this->_source;
492 * Populates the model. If the model is successfully populated $this is
493 * returned. Otherwise false is returned.
494 * @param $where An associative array containing the where-keys and values.
495 * This means that array("FOO" => "BAR") will expand to WHERE FOO = 'BAR'
496 * @param $order An array with two rows. First row defines the row to order
497 * by, the second row defines the order. This means that array("BAZ",
498 * "DESC") will expand to ORDER BY BAZ DESC.
499 * @param $limit An array with two rows, where the first row is the start
500 * limit and the second row is the end limit. This means that array(0,2)
501 * will expand into LIMIT 0,2
502 * @return
504 public function populate($where = null, $order = null, $limit = null) {
505 $this->_reset();
506 if ($where && !($where = $this->_fixWhere($where))) {
507 return false;
509 $fields = array();
510 foreach ($this->_props as $col => $prop) {
511 if ($prop & AOOSMODEL_PROP_FROM_DATABASE) {
512 $fields[] = $col;
515 if (!($values = $this->_connection()->select($fields, $where, $order, $limit))) {
516 throw new AOOSException($this->core(), $this->tr("couldnt_select_values"));
517 return false;
519 foreach ($values as $row) {
520 $this->appendRow($row, AOOSMODEL_PROP_DATA_HASH);
522 return $this;
526 * Inserts the given array into the model. Note that data isn't saved to
527 * database before calling AOOSModel::save()
528 * @param $values An associative array, in which the array keys corresponds
529 * to column indices and the array values are the value that should be
530 * inserted.
532 public function insert($values) {
533 $this->appendRow($values);
534 $values = $this->filter(AOOSMODEL_PROP_FROM_DATABASE, -1, 1);
535 $this->_intern[] = array(AOOSModel::INTERN_DATA_INSERTED, $values);
536 return true;
540 * Deletes the row which matches $where. As with AOOSModel::insert() and
541 * AOOSModel::update(), changes aren't saved to database before AOOSModel::save() is
542 * called. An AOOSException is thrown if no rows match $where.
543 * @param $where Either an associative array containing the where-keys and values.
544 * This means that array("FOO" => "BAR") will expand to WHERE FOO = 'BAR' or
545 * an integer representing a row number.
546 * @param $limit An array with two rows, where the first row is the start
547 * limit and the second row is the end limit. This means that array(0,2)
548 * will expand into LIMIT 0,2 or a single integer
550 public function remove($where, $limit = null) {
551 $limit = $this->_fixLimit($limit);
552 if (is_array($where)) {
553 if (!($w = $this->findAll($where, $limit[0], $limit[1]))) {
554 throw new AOOSException($this->core(), $this->tr("no_rows_found"), "", true, 2);
555 return false;
558 elseif (is_integer($where)) {
559 $w = array($where);
560 $where = $this->getRow($where);
562 else {
563 return false;
565 foreach($w as $row) {
566 unset($this->_data[$row]);
567 $this->_rows--;
569 $where = $this->_fixWhere($where);
570 $this->_intern[] = array(AOOSModel::INTERN_DATA_REMOVED, $where, $limit);
571 return 0;
575 * Updates the row that matches $where with the values, $values. Changes
576 * aren't saved to database until AOOSModel::save() is called..
577 * @param $values An associative array, in which the array keys matches
578 * column indices and the array values are the values to use to update the
579 * selected row
580 * @param $where Either an associative array containing the where-keys and values.
581 * This means that array("FOO" => "BAR") will expand to WHERE FOO = 'BAR' or
582 * an integer representing a row number
583 * @param $limit An array with two rows, where the first row is the start
584 * limit and the second row is the end limit. This means that array(0,2)
585 * will expand into LIMIT 0,2
587 public function update($values, $where, $limit = null) {
588 $limit = $this->_fixLimit($limit);
589 $w = $this->findAll($where, $limit[0], $limit[1]);
590 foreach ($w as $row) {
591 $this->setDataRow($row, $values);
593 $where = $this->_fixWhere($where);
594 $values = $this->_fixWhere($values);
595 $this->_intern[] = array(AOOSModel::INTERN_DATA_UPDATED, $values, $where, $limit);
596 return 0;
600 * Saves the changes caused by AOOSModel::insert(), AOOSModel::remove() or
601 * AOOSModel::update()
603 public function save() {
604 if (!$this->_connection()) {
605 return false;
607 foreach ($this->_intern as $key => $row) {
608 switch($row[0]) {
609 case(AOOSModel::INTERN_DATA_INSERTED):
610 if (!$this->_connection()->insert($row[1])) {
611 return false;
613 break;
614 case(AOOSModel::INTERN_DATA_REMOVED):
615 if (!$this->_connection()->remove($row[1], $row[2])) {
616 return false;
618 case(AOOSModel::INTERN_DATA_UPDATED):
619 if (!$this->_connection()->update($row[1], $row[2], $row[3])) {
620 return false;
623 unset($this->_intern[$key]);
625 return true;
628 private function _connect() {
629 if ($this->_connection) {
630 return $this->_connection;
632 if (!$this->source() || !$this->table()) {
633 throw new AOOSException($this->core(), $this->tr("no_source_or_table_set"));
634 return false;
636 switch ($this->source()) {
637 case("mysql"):
638 require_once("lib/AOOSMysqlInterface.php");
639 $this->_connection = new AOOSMysqlInterface($this->core());
640 break;
642 $this->_connection->setTable($this->table());
645 private function _connection() {
646 if (!$this->_connection) {
647 try {
648 $this->_connect();
650 catch (AOOSException $e) {
651 throw $e;
652 return false;
655 return $this->_connection;
659 * \internal Fixes where clauses so that the given values are correctly
660 * formatted according to the properties for their column
662 private function _fixWhere($where) {
663 $ci = $this->columnIndex();
665 if (!is_array($where)) {
666 return false;
669 foreach ($where as $key => $value) {
670 if (!in_array($key, $ci)) {
671 unset($where[$ci]);
672 continue;
674 $where[$key] = $this->_fixDataProps($value, $this->getProperty($key));
676 return $where;
680 * \internal Fixes a limit
682 private function _fixLimit($l) {
683 $limit = array(0,$this->rows());
684 if (is_integer($l)) {
685 $limit[1] = $l;
687 elseif(is_array($l)) {
688 $limit = $l;
690 return $limit;
693 private function _reset() {
694 $this->_data = array();
695 $this->_connect();
696 $this->_rows = 0;
700 * Dummy function
702 public function dataModelDefinition() { return 0; }
704 // vim: textwidth=80