settings.php+tmp/stylesheet.css:
[AOOS.git] / AOOSModel.php
blob765189263c785f864c2b0095a0ea286bb2ca1a97
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);
7 define("AOOSMODEL_TYPE_EMAIL" , 32);
9 define("AOOSMODEL_PROP_DATA_INCREASING" , 1);
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);
14 define("AOOSMODEL_PROP_DATA_STRIP" , 64);
16 define("AOOSMODEL_PROP_GUI_NOTEDITABLE" , 1<<8);
17 define("AOOSMODEL_PROP_GUI_PRIVATE" , 2<<8);
18 define("AOOSMODEL_PROP_FROM_DATABASE" , 4<<8);
19 define("AOOSMODEL_PROP_UNIQUE" , 8<<8);
21 /**
22 * Second-generation AOOSModel
23 * On the good side:
24 * - The addition of properties allowing easy per-column settings
25 * @author Sebastian Skejø
27 class AOOSModel extends AOOSModule {
28 const INTERN_DATA_UPDATED = 1;
29 const INTERN_DATA_REMOVED = 2;
30 const INTERN_DATA_INSERTED = 3;
32 private $_data = array();
33 private $_index = array();
34 private $_props = array();
35 private $_types = array();
36 private $_intern = array();
38 private $_source = null;
39 private $_table = null;
40 private $_connection = null;
43 private $_rows = 0;
44 private $_curRow = 0;
45 private $_curCol = 0;
47 /**
48 * Standard destructor
50 public function __destruct() {
53 /**
54 * Transforms to a printable string
56 public function __toString() {
57 return nl2br(str_replace(" ", "&nbsp;", print_r($this->data(), true)));
60 /**
61 * Appends a row to the end of the model with 0 as values unless $values is
62 * provided. Appended rows will NOT be inserted into database when AOOSModel::save() is called.
63 * @param $values If given null an empty row will be added. Otherwise an
64 * associative array is assumed.
65 * @param $ignore Flags to ignores when inserting data.
66 * @see insert
67 * @return true
69 public function appendRow($values = null, $ignore = 0) {
70 $this->_data[] = new AOOSModelRow($this, $this->columnIndex()); //array_fill_keys($this->_index, 0);
71 $this->_rows++;
72 if (is_array($values)) {
73 $index = $this->columnIndex();
74 $last = $this->rows()-1;
75 foreach ($index as $i) {
76 if (in_array($i, array_keys($values))) {
77 $this->setData($last, $i, $values[$i], $ignore);
79 else {
80 $this->setData($last, $i, null, $ignore);
84 return true;
87 /**
88 * Sets the data of a single field. Fields updated this way will NOT be
89 * updated in the database when AOOSModel::save() is called. If the type of
90 * the inserted data doesn't match the column type, an AOOSTypeException is
91 * thrown. If $row is less than 0 or greater that the row count, an
92 * AOOSException is throw.
93 * @param $row An integer representing the row
94 * @param $column A column index
95 * @param $value Value
96 * @param $ignore Flags to ignore when inserting data
97 * @return bool
99 public function setData($row, $column, $value, $ignore = 0) {
100 if (!$this->inColumnIndex($column)) {
101 // Exception?
102 return false;
104 $props = $this->_props[$column];
105 $type = $this->_types[$column];
107 try {
108 $this->_checkType($value, $type);
110 catch (AOOSTypeException $e) {
111 throw $e;
112 return false;
115 try {
116 $value = $this->_fixDataProps($column, $value, $props, $ignore);
118 catch (AOOSException $e) {
119 throw $e;
120 return false;
123 // XXX
124 if ($row < 0 || $row > $this->rows()) {
125 throw new AOOSException($this->core(), $this->tr("row_out_of_bounds"));
126 return false;
128 $this->_data[$row]->setData($column, $value);
129 return true;
134 public function setDataRowSingle($row, $column, $value, $ignore = 0) {
135 if (!$this->inColumnIndex($column)) {
136 // Exception?
137 return false;
139 $props = $this->_props[$column];
140 $type = $this->_types[$column];
142 try {
143 $this->_checkType($value, $type);
145 catch (AOOSTypeException $e) {
146 throw $e;
147 return false;
150 try {
151 $value = $this->_fixDataProps($column, $value, $props, $ignore);
153 catch (AOOSException $e) {
154 throw $e;
155 return false;
158 $row->setData($column, $value);
159 return true;
162 public function setDataRow($row, $values, $ignore = 0) {
163 if (is_int($row)) {
164 $row = $this->getRow($row);
167 foreach ($values as $key => $value) {
168 $this->setDataRowSingle($row, $key, $value, $ignore);
172 private function _checkType($value, $type) {
173 // Only type-props
174 $correct = false;
175 $continue = true;
176 if ($type & AOOSMODEL_TYPE_UNKNOWN) {
177 $correct = true;
178 $continue = false;
180 elseif($continue && $type & AOOSMODEL_TYPE_STRING) {
181 $correct = is_string($value);
182 $continue = false;
184 elseif($continue && $type & AOOSMODEL_TYPE_INTEGER) {
185 $correct = is_integer((int)$value);
186 $continue = false;
188 elseif($continue && $type & AOOSMODEL_TYPE_ARRAY) {
189 $correct = is_array($value);
190 $continue = false;
192 elseif($continue && $type & AOOSMODEL_TYPE_AOOSMODEL) {
193 $correct = $value instanceof AOOSModel;
194 $continue = false;
196 elseif($continue && $type & AOOSMODEL_TYPE_EMAIL) {
197 $correct = preg_match("^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,3})$", $value);
198 $continue = false;
202 if (!$correct) {
203 throw new AOOSTypeException($this->core(), "", "When inserting ".$value);
204 return false;
206 return true;
209 private function _fixDataProps($col, $value, $props, $ignore = 0) {
210 // Only data props
211 if ($props & AOOSMODEL_PROP_DATA_NOHTML && !(AOOSMODEL_PROP_DATA_NOHTML & $ignore)) {
212 $value = htmlspecialchars($value);
215 if ($props & AOOSMODEL_PROP_DATA_ESCAPE && !(AOOSMODEL_PROP_DATA_ESCAPE & $ignore)) {
216 $value = mysql_real_escape_string($value);
219 if ($props & AOOSMODEL_PROP_DATA_STRIP && !(AOOSMODEL_PROP_DATA_STRIP & $ignore)) {
220 $value = rtrim(trim($value));
223 if ($props & AOOSMODEL_PROP_DATA_HASH && !(AOOSMODEL_PROP_DATA_HASH & $ignore)) {
224 $value = hash("sha256", $value);
226 if ($props & AOOSMODEL_PROP_DATA_QUOTES && !(AOOSMODEL_PROP_DATA_QUOTES & $ignore)) {
227 $first = substr($value, 0,1);
228 if (($first != "'" && $first != '"' && $first != substr($value, -1)) || $value == "") {
229 $value = "'".$value."'";
232 if ($props & AOOSMODEL_PROP_DATA_INCREASING && !(AOOSMODEL_PROP_DATA_INCREASING & $ignore)) {
233 if ($this->rows() == 1 && !$this->getRow(0)->$col) {
234 $value = $value;
236 elseif ($this->rows() == 1) {
237 $value = 0;
239 else {
240 $r = $this->getRow(-2);
241 $value = $r->$col + 1;
244 if ($props & AOOSMODEL_PROP_UNIQUE && !(AOOSMODEL_PROP_UNIQUE & $ignore)) {
245 $cols = $this->getColumn($col);
246 if (in_array($value, $cols)) {
247 throw new AOOSException($this->core(), $this->tr("not_unique"));
248 return false;
252 return $value;
256 * Returns a to-dimensional array containing the data.
257 * @return array
259 public function data() {
260 return $this->_data;
264 * Returns the column with the name $column
265 * @param $column The name of the column
266 * @param $offset Column values starts at this row
267 * @param $length The array of column values has this length
268 * @return array
270 public function getColumn($column, $start = 0, $length = null) {
271 if (!$this->inColumnIndex($column)) {
272 return false;
274 if ($length === null) {
275 $length = $this->rows();
277 if ($start < 0) {
278 $start = $this->rows() + $start;
280 $a = array();
281 for ($i= $start; $length > 0; $length--) {
282 $r = $this->getRow($i);
283 $a[] = $r[$column];
284 $i++;
286 return $a;
290 * Returns an AOOSModelRow object
291 * @param $row The row number.
292 * @return AOOSModel
294 public function getRow($row) {
295 if ($row < 0) {
296 $row = $this->rows() + $row;
298 return $this->_data[$row];
302 * Returns the row number
303 * @param $row The AOOSModelRow object
304 * @return integer
306 /* public function getRowNumber($row) {
307 foreach*/
310 * Returns the number of rows
311 * @return integer
313 public function rows() {
314 return $this->_rows;
318 * Returns only columns which match the given properties
319 * @param $props Properties
321 public function filter($props = 0, $offset = 0, $length = null) {
322 if ($length === null) {
323 $length = $this->rows();
325 $a = array();
326 for ($i = $offset; $length > 0; $length--) {
327 $r = $this->getRow($i);
328 $ra = array();
329 foreach ($this->_props as $col => $colprops) {
330 if ($colprops & $props) {
331 $ra[$col] = $r[$col];
334 $a[] = $ra;
335 $i++;
337 return $a;
341 * Returns the first row from $start which matches $match
342 * @param $match An associative array in which keys should match column
343 * indices and values should match values. Values of the match array can be
344 * regular expressions.
345 * @param $start An integer representing the row from which search should.
346 * Defaults to 0.
347 * start
348 * @return Integer
349 * @sa findAll
351 public function find($match, $start = 0) {
352 if ($match && !($match = $this->_fixWhere($match))) {
353 return false;
355 for ($i = $start; $i < $this->rows(); $i++) {
356 $r = $this->getRow($i);
357 if ($r->match($match)) {
358 $this->_lastMatchRow = $i;
359 return $r;
362 return false;
366 * Returns all rows which match $match, starting from $start
367 * @param $match The row to match against.
368 * @param $start An integer representing the row from which to start from.
369 * Defaults to 0.
370 * @return An array containing integers.
371 * @sa find
373 public function findAll($match, $start = 0, $length = null) {
374 if ($length === null) {
375 $length = $this->rows();
377 $i = $start;
378 $a = array();
379 while (true) {
380 if (false === ($r = $this->find($match, $i))) {
381 break;
383 $a[] = $r;
384 $i = $this->_lastMatchRow+1;
386 $a = array_slice($a, 0, $length);
387 return count($a) ? $a : false;
391 * Sets a columns properties to be $property
392 * @param $column Column index name
393 * @param $type The type of the column
394 * @param $property An integer representing the property
395 * @return true
397 public function setProperty($column, $type, $property) {
398 if (!$this->inColumnIndex($column)) {
399 // Exception?
400 return false;
402 if ($property & AOOSMODEL_PROP_FROM_DATABASE && !($type & AOOSMODEL_TYPE_INTEGER)) {
403 $property |= AOOSMODEL_PROP_DATA_QUOTES;
405 $this->_types[$column] = $type;
406 $this->_props[$column] = $property;
407 return true;
411 * Returns the properties of the given $column
412 * @param $column Name of the column
413 * @return integer
415 public function getProperty($column) {
416 if (!$this->inColumnIndex($column)) {
417 return false;
419 return $this->_props[$column];
423 * Toggles a given property using the ... operation.
424 * @param $column Column index name
425 * @param $property An integer representing the property to toggle
426 * @return true
428 public function toggleProperty($column, $property) {
429 if (!$this->inColumnIndex($column)) {
430 // Exception?
431 return false;
433 if ($property & AOOSMODEL_PROP_FROM_DATABASE) {
434 $property |= AOOSMODEL_PROP_DATA_QUOTES;
436 $this->_props[$column] ^= $property;
437 return true;
441 * Returns the type of the given $column
442 * @param $column Name of the column
443 * @return string
445 public function getType($column) {
446 if (!$this->inColumnIndex($column)) {
447 // Exception?
448 return false;
450 $type = $this->_types[$column];
451 $typename = 0;
452 $continue = true;
453 if ($type & AOOSMODEL_TYPE_UNKNOWN) {
454 $typename = "UNKNOWN";
455 $continue = false;
457 elseif($continue && $type & AOOSMODEL_TYPE_STRING) {
458 $typename = "STRING";
459 $continue = false;
461 elseif($continue && $type & AOOSMODEL_TYPE_INTEGER) {
462 $typename = "INTEGER";
463 $continue = false;
465 elseif($continue && $type & AOOSMODEL_TYPE_ARRAY) {
466 $typename = "ARRAY";
467 $continue = false;
469 elseif($continue && $type & AOOSMODEL_TYPE_AOOSMODEL) {
470 $typename = "AOOSModel";
471 $continue = false;
474 return $typename;
478 * Returns true if the given $property is set, otherwise return false
479 * @param $column Column index name
480 * @param $property An integer representing the property
481 * @return boolean
483 public function checkProperty($column, $property) {
484 if (!$this->inColumnIndex($column)) {
485 // Exception?
486 return false;
488 if ($this->_props[$column] & $property) {
489 return true;
491 return false;
495 * Sets the column index to be $index. Note: this resets the properties AND
496 * data
497 * If $index isn't an array an AOOSException is thrown.
498 * @param $index An array containing the column indices
499 * @return true
501 public function setColumnIndex($index) {
502 if (!is_array($index)) {
503 throw new AOOSTypeException($this->core(), $this->tr("not_array"));
504 return false;
506 $this->_index = $index;
507 $this->_props = array_fill_keys($index, 0);
508 return true;
512 * Returns the column index
513 * @return array
515 public function columnIndex() { return $this->_index; }
518 * Returns true if the given $index is in the column index. Otherwise returns
519 * false.
520 * @param $index Name of the index
521 * @return bool
523 public function inColumnIndex($index) {
524 return in_array($index, $this->_index);
529 * Sets the table from in which data is contained
530 * @param $table Table name
531 * @return true
533 public function setTable($table) {
534 $this->_table = $this->core()->getSetting("DBPrefix").$table;
535 return true;
539 * Returns the table name
540 * @return string
542 public function table() {
543 return $this->_table;
547 * Sets the type of source from which data is fetched, inserted and deleted.
548 * @param $source The type of source, e.g. "mysql".
549 * @return true
551 public function setSource($source) {
552 $this->_source = strtolower($source);
553 return true;
557 * Returns the type of source in a string
558 * @return string
560 public function source() {
561 return $this->_source;
565 * Populates the model. If the model is successfully populated $this is
566 * returned. Otherwise false is returned.
567 * @param $where An associative array containing the where-keys and values.
568 * This means that array("FOO" => "BAR") will expand to WHERE FOO = 'BAR'
569 * @param $order An array with two rows. First row defines the row to order
570 * by, the second row defines the order. This means that array("BAZ",
571 * "DESC") will expand to ORDER BY BAZ DESC.
572 * @param $limit An array with two rows, where the first row is the start
573 * limit and the second row is the end limit. This means that array(0,2)
574 * will expand into LIMIT 0,2
575 * @return
577 public function populate($where = null, $order = null, $limit = null) {
578 $this->_reset();
579 if ($where && !($where = $this->_fixWhere($where))) {
580 return false;
582 $fields = array();
583 foreach ($this->_props as $col => $prop) {
584 if ($prop & AOOSMODEL_PROP_FROM_DATABASE) {
585 $fields[] = $col;
588 if (false === ($values = $this->_connection()->select($fields, $where, $order, $limit))) {
589 throw new AOOSException($this->core(), $this->tr("couldnt_select_values"));
590 return false;
592 foreach ($values as $row) {
593 $this->appendRow($row, AOOSMODEL_PROP_DATA_HASH);
595 return $this;
599 * Inserts the given array into the model. Note that data isn't saved to
600 * database before calling AOOSModel::save()
601 * @param $values An associative array, in which the array keys corresponds
602 * to column indices and the array values are the value that should be
603 * inserted.
605 public function insert($values) {
606 $this->appendRow($values);
607 $values = $this->filter(AOOSMODEL_PROP_FROM_DATABASE, -1, 1);
608 $this->_intern[] = array(AOOSModel::INTERN_DATA_INSERTED, $values);
609 return true;
613 * Deletes the row which matches $where. As with AOOSModel::insert() and
614 * AOOSModel::update(), changes aren't saved to database before AOOSModel::save() is
615 * called. An AOOSException is thrown if no rows match $where.
616 * @param $where Either an associative array containing the where-keys and values.
617 * This means that array("FOO" => "BAR") will expand to WHERE FOO = 'BAR' or
618 * an integer representing a row number.
619 * @param $limit An array with two rows, where the first row is the start
620 * limit and the second row is the end limit. This means that array(0,2)
621 * will expand into LIMIT 0,2 or a single integer
623 public function remove($where, $limit = null) {
624 $limit = $this->_fixLimit($limit);
625 if (is_array($where)) {
626 if (!($w = $this->findAll($where, $limit[0], $limit[1]))) {
627 throw new AOOSException($this->core(), $this->tr("no_rows_found"), "", true, 2);
628 return false;
631 elseif (is_integer($where)) {
632 $w = array($where);
633 $where = $this->getRow($where);
635 else {
636 return false;
638 foreach($w as $row) {
639 unset($row);//$this->_data[$nr]);
640 $this->_rows--;
642 $where = $this->_fixWhere($where);
643 $this->_intern[] = array(AOOSModel::INTERN_DATA_REMOVED, $where, $limit);
644 return true;
648 * Updates the row that matches $where with the values, $values. Changes
649 * aren't saved to database until AOOSModel::save() is called..
650 * @param $values An associative array, in which the array keys matches
651 * column indices and the array values are the values to use to update the
652 * selected row
653 * @param $where Either an associative array containing the where-keys and values.
654 * This means that array("FOO" => "BAR") will expand to WHERE FOO = 'BAR' or
655 * an integer representing a row number
656 * @param $limit An array with two rows, where the first row is the start
657 * limit and the second row is the end limit. This means that array(0,2)
658 * will expand into LIMIT 0,2
660 public function update($values, $where, $limit = null) {
661 $limit = $this->_fixLimit($limit);
662 $w = $this->findAll($where, $limit[0], $limit[1]);
663 foreach ($w as $row) {
664 $this->setDataRow($row, $values);
666 $where = $this->_fixWhere($where);
667 $values = $this->_fixWhere($values);
668 $this->_intern[] = array(AOOSModel::INTERN_DATA_UPDATED, $values, $where, $limit);
669 return 0;
673 * Creates the model as a new table in the database.
674 * @return bool
676 public function create() {
677 if (!$this->_connection()) {
678 return false;
680 return $this->_connection()->create($this->_types, $this->_props);
684 * Creates the model as a new table in the database.
685 * @return bool
687 public function drop() {
688 if (!$this->_connection()) {
689 return false;
691 return $this->_connection()->drop();
695 * Saves the changes caused by AOOSModel::insert(), AOOSModel::remove() or
696 * AOOSModel::update()
698 public function save() {
699 if (!$this->_connection()) {
700 return false;
702 foreach ($this->_intern as $key => $row) {
703 switch($row[0]) {
704 case(AOOSModel::INTERN_DATA_INSERTED):
705 if (!$this->_connection()->insert($row[1])) {
706 return false;
708 break;
709 case(AOOSModel::INTERN_DATA_REMOVED):
710 if (!$this->_connection()->remove($row[1], $row[2])) {
711 return false;
713 case(AOOSModel::INTERN_DATA_UPDATED):
714 if (!$this->_connection()->update($row[1], $row[2], $row[3])) {
715 return false;
718 unset($this->_intern[$key]);
720 return true;
723 private function _connect() {
724 if ($this->_connection) {
725 return $this->_connection;
727 if (!$this->source() || !$this->table()) {
728 throw new AOOSException($this->core(), $this->tr("no_source_or_table_set"));
729 return false;
731 switch ($this->source()) {
732 case("mysql"):
733 require_once("lib/AOOSMysqlInterface.php");
734 $this->_connection = new AOOSMysqlInterface($this->core());
735 break;
737 $this->_connection->setTable($this->table());
740 private function _connection() {
741 if (!$this->_connection) {
742 try {
743 $this->_connect();
745 catch (AOOSException $e) {
746 throw $e;
747 return false;
750 return $this->_connection;
754 * \internal Fixes where clauses so that the given values are correctly
755 * formatted according to the properties for their column
757 private function _fixWhere($where) {
758 $ci = $this->columnIndex();
760 if (!is_array($where)) {
761 return false;
764 foreach ($where as $key => $value) {
765 if (!in_array($key, $ci)) {
766 unset($where[$ci]);
767 continue;
769 $where[$key] = $this->_fixDataProps($key, $value, $this->getProperty($key));
771 return $where;
775 * \internal Fixes a limit
777 private function _fixLimit($l) {
778 $limit = array(0,$this->rows());
779 if (is_integer($l)) {
780 $limit[1] = $l;
782 elseif(is_array($l)) {
783 $limit = $l;
785 return $limit;
788 private function _reset() {
789 $this->_data = array();
790 $this->_connect();
791 $this->_rows = 0;
795 * Dummy function
797 public function dataModelDefinition() { return 0; }
799 static public function stripQuotes($string) {
800 if (substr($string,0,1) == "'") {
801 $string = substr($string,1,strlen($string)-2);
803 return $string;
808 class AOOSModelRow extends AOOSModule implements ArrayAccess {
809 private $_data = array();
810 private $_columnIndex = array();
812 public function __construct($parent, $columnIndex) {
813 parent::__construct($parent);
814 $this->_columnIndex = $columnIndex;
815 $this->_data = array_fill_keys($columnIndex, null);
818 public function dataModelDefinition() {
819 return 0;
822 public function __set($name, $value) {
823 return $this->parent()->setDataRow($this, $name, $value);
826 public function __get($name) {
827 return $this->_data[$name];
830 public function __toString() {
831 $str = "Row:\n";
832 foreach ($this->_data as $key => $value) {
833 $str .= "\t[".$key."] => ".$value."\n";
835 return $str;
838 public function columnIndex() {
839 return $this->_columnIndex;
842 // Array Access
843 public function offsetExists($offset) {
844 return in_array($offset, array_keys($_data));
847 public function offsetSet($offset, $value) {
848 $this->$offset = $value;
851 public function offsetGet($offset) {
852 return $this->$offset;
855 public function offsetUnset($offset) {
856 unset($this->_data[$offset]);
860 * Simply inserts the data into the row. This should NOT be used since it
861 * ignores column flags completely
863 public function setData($name, $value) {
864 $this->_data[$name] = $value;
865 return true;
868 public function save() {
869 return $this->parent()->save();
872 public function match($match) {
873 foreach ($match as $key => $value) {
874 if (!preg_match($this->_data[$key], "/".$value."/")) {
875 return false;
878 return true;
881 // vim: textwidth=80