Deps needed to be stripped before exploding
[AOOS.git] / AOOSModel.php
blob50aa15fca87b0ba1c70c30d2dd461784b135f894
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;
201 if (!$correct) {
202 throw new AOOSTypeException($this->core(), "", "When inserting ".$value);
203 return false;
205 return true;
208 private function _fixDataProps($col, $value, $props, $ignore = 0) {
209 // Only data props
210 if ($props & AOOSMODEL_PROP_DATA_NOHTML && !(AOOSMODEL_PROP_DATA_NOHTML & $ignore)) {
211 $value = htmlspecialchars($value);
214 if ($props & AOOSMODEL_PROP_DATA_ESCAPE && !(AOOSMODEL_PROP_DATA_ESCAPE & $ignore)) {
215 $value = mysql_real_escape_string($value);
218 if ($props & AOOSMODEL_PROP_DATA_STRIP && !(AOOSMODEL_PROP_DATA_STRIP & $ignore)) {
219 $value = rtrim(trim($value));
222 if ($props & AOOSMODEL_PROP_DATA_HASH && !(AOOSMODEL_PROP_DATA_HASH & $ignore)) {
223 $value = hash("sha256", $value);
225 if ($props & AOOSMODEL_PROP_DATA_QUOTES && !(AOOSMODEL_PROP_DATA_QUOTES & $ignore)) {
226 $first = substr($value, 0,1);
227 if ($first != "'" && $first != '"' && $first != substr($value, -1)) {
228 $value = "'".$value."'";
231 if ($props & AOOSMODEL_PROP_UNIQUE && !(AOOSMODEL_PROP_UNIQUE & $ignore)) {
232 $cols = $this->getColumn($col);
233 if (in_array($value, $cols)) {
234 throw new AOOSException($this->core(), $this->tr("not_unique"));
235 return false;
238 if ($props & AOOSMODEL_PROP_DATA_INCREASING && !(AOOSMODEL_PROP_DATA_INCREASING & $ignore)) {
239 if ($this->rows() == 1) {
240 $value = 0;
242 else {
243 $r = $this->getRow(-2);
244 $value = $r->$col + 1;
248 return $value;
252 * Returns a to-dimensional array containing the data.
253 * @return array
255 public function data() {
256 return $this->_data;
260 * Returns the column with the name $column
261 * @param $column The name of the column
262 * @param $offset Column values starts at this row
263 * @param $length The array of column values has this length
264 * @return array
266 public function getColumn($column, $start = 0, $length = null) {
267 if (!$this->inColumnIndex($column)) {
268 return false;
270 if ($length === null) {
271 $length = $this->rows();
273 if ($start < 0) {
274 $start = $this->rows() + $start;
276 $a = array();
277 for ($i= $start; $length > 0; $length--) {
278 $r = $this->getRow($i);
279 $a[] = $r[$column];
280 $i++;
282 return $a;
286 * Returns an AOOSModelRow object
287 * @param $row The row number.
288 * @return AOOSModel
290 public function getRow($row) {
291 if ($row < 0) {
292 $row = $this->rows() + $row;
294 return $this->_data[$row];
298 * Returns the row number
299 * @param $row The AOOSModelRow object
300 * @return integer
302 /* public function getRowNumber($row) {
303 foreach*/
306 * Returns the number of rows
307 * @return integer
309 public function rows() {
310 return $this->_rows;
314 * Returns only columns which match the given properties
315 * @param $props Properties
317 public function filter($props = 0, $offset = 0, $length = null) {
318 if ($length === null) {
319 $length = $this->rows();
321 $a = array();
322 for ($i = $offset; $length > 0; $length--) {
323 $r = $this->getRow($i);
324 $ra = array();
325 foreach ($this->_props as $col => $colprops) {
326 if ($colprops & $props) {
327 $ra[$col] = $r[$col];
330 $a[] = $ra;
331 $i++;
333 return $a;
337 * Returns the first row from $start which matches $match
338 * @param $match An associative array in which keys should match column
339 * indices and values should match values. Values of the match array can be
340 * regular expressions.
341 * @param $start An integer representing the row from which search should.
342 * Defaults to 0.
343 * start
344 * @return Integer
345 * @sa findAll
347 public function find($match, $start = 0) {
348 if ($match && !($match = $this->_fixWhere($match))) {
349 return false;
351 for ($i = $start; $i < $this->rows(); $i++) {
352 $r = $this->getRow($i);
353 if ($r->match($match)) {
354 $this->_lastMatchRow = $i;
355 return $r;
358 return false;
362 * Returns all rows which match $match, starting from $start
363 * @param $match The row to match against.
364 * @param $start An integer representing the row from which to start from.
365 * Defaults to 0.
366 * @return An array containing integers.
367 * @sa find
369 public function findAll($match, $start = 0, $length = null) {
370 if ($length === null) {
371 $length = $this->rows();
373 $i = $start;
374 $a = array();
375 while (true) {
376 if (false === ($r = $this->find($match, $i))) {
377 break;
379 $a[] = $r;
380 $i = $this->_lastMatchRow+1;
382 $a = array_slice($a, 0, $length);
383 return count($a) ? $a : false;
387 * Sets a columns properties to be $property
388 * @param $column Column index name
389 * @param $type The type of the column
390 * @param $property An integer representing the property
391 * @return true
393 public function setProperty($column, $type, $property) {
394 if (!$this->inColumnIndex($column)) {
395 // Exception?
396 return false;
398 if ($property & AOOSMODEL_PROP_FROM_DATABASE && !($type & AOOSMODEL_TYPE_INTEGER)) {
399 $property |= AOOSMODEL_PROP_DATA_QUOTES;
401 $this->_types[$column] = $type;
402 $this->_props[$column] = $property;
403 return true;
407 * Returns the properties of the given $column
408 * @param $column Name of the column
409 * @return integer
411 public function getProperty($column) {
412 if (!$this->inColumnIndex($column)) {
413 return false;
415 return $this->_props[$column];
419 * Toggles a given property using the ... operation.
420 * @param $column Column index name
421 * @param $property An integer representing the property to toggle
422 * @return true
424 public function toggleProperty($column, $property) {
425 if (!$this->inColumnIndex($column)) {
426 // Exception?
427 return false;
429 if ($property & AOOSMODEL_PROP_FROM_DATABASE) {
430 $property |= AOOSMODEL_PROP_DATA_QUOTES;
432 $this->_props[$column] ^= $property;
433 return true;
437 * Returns the type of the given $column
438 * @param $column Name of the column
439 * @return string
441 public function getType($column) {
442 if (!$this->inColumnIndex($column)) {
443 // Exception?
444 return false;
446 $type = $this->_types[$column];
447 $typename = 0;
448 $continue = true;
449 if ($type & AOOSMODEL_TYPE_UNKNOWN) {
450 $typename = "UNKNOWN";
451 $continue = false;
453 elseif($continue && $type & AOOSMODEL_TYPE_STRING) {
454 $typename = "STRING";
455 $continue = false;
457 elseif($continue && $type & AOOSMODEL_TYPE_INTEGER) {
458 $typename = "INTEGER";
459 $continue = false;
461 elseif($continue && $type & AOOSMODEL_TYPE_ARRAY) {
462 $typename = "ARRAY";
463 $continue = false;
465 elseif($continue && $type & AOOSMODEL_TYPE_AOOSMODEL) {
466 $typename = "AOOSModel";
467 $continue = false;
470 return $typename;
474 * Returns true if the given $property is set, otherwise return false
475 * @param $column Column index name
476 * @param $property An integer representing the property
477 * @return boolean
479 public function checkProperty($column, $property) {
480 if (!$this->inColumnIndex($column)) {
481 // Exception?
482 return false;
484 if ($this->_props[$column] & $property) {
485 return true;
487 return false;
491 * Sets the column index to be $index. Note: this resets the properties AND
492 * data
493 * If $index isn't an array an AOOSException is thrown.
494 * @param $index An array containing the column indices
495 * @return true
497 public function setColumnIndex($index) {
498 if (!is_array($index)) {
499 throw new AOOSTypeException($this->core(), $this->tr("not_array"));
500 return false;
502 $this->_index = $index;
503 $this->_props = array_fill_keys($index, 0);
504 return true;
508 * Returns the column index
509 * @return array
511 public function columnIndex() { return $this->_index; }
514 * Returns true if the given $index is in the column index. Otherwise returns
515 * false.
516 * @param $index Name of the index
517 * @return bool
519 public function inColumnIndex($index) {
520 return in_array($index, $this->_index);
525 * Sets the table from in which data is contained
526 * @param $table Table name
527 * @return true
529 public function setTable($table) {
530 $this->_table = $this->core()->getSetting("DBPrefix").$table;
531 return true;
535 * Returns the table name
536 * @return string
538 public function table() {
539 return $this->_table;
543 * Sets the type of source from which data is fetched, inserted and deleted.
544 * @param $source The type of source, e.g. "mysql".
545 * @return true
547 public function setSource($source) {
548 $this->_source = strtolower($source);
549 return true;
553 * Returns the type of source in a string
554 * @return string
556 public function source() {
557 return $this->_source;
561 * Populates the model. If the model is successfully populated $this is
562 * returned. Otherwise false is returned.
563 * @param $where An associative array containing the where-keys and values.
564 * This means that array("FOO" => "BAR") will expand to WHERE FOO = 'BAR'
565 * @param $order An array with two rows. First row defines the row to order
566 * by, the second row defines the order. This means that array("BAZ",
567 * "DESC") will expand to ORDER BY BAZ DESC.
568 * @param $limit An array with two rows, where the first row is the start
569 * limit and the second row is the end limit. This means that array(0,2)
570 * will expand into LIMIT 0,2
571 * @return
573 public function populate($where = null, $order = null, $limit = null) {
574 $this->_reset();
575 if ($where && !($where = $this->_fixWhere($where))) {
576 return false;
578 $fields = array();
579 foreach ($this->_props as $col => $prop) {
580 if ($prop & AOOSMODEL_PROP_FROM_DATABASE) {
581 $fields[] = $col;
584 if (false === ($values = $this->_connection()->select($fields, $where, $order, $limit))) {
585 throw new AOOSException($this->core(), $this->tr("couldnt_select_values"));
586 return false;
588 foreach ($values as $row) {
589 $this->appendRow($row, AOOSMODEL_PROP_DATA_HASH);
591 return $this;
595 * Inserts the given array into the model. Note that data isn't saved to
596 * database before calling AOOSModel::save()
597 * @param $values An associative array, in which the array keys corresponds
598 * to column indices and the array values are the value that should be
599 * inserted.
601 public function insert($values) {
602 $this->appendRow($values);
603 $values = $this->filter(AOOSMODEL_PROP_FROM_DATABASE, -1, 1);
604 $this->_intern[] = array(AOOSModel::INTERN_DATA_INSERTED, $values);
605 return true;
609 * Deletes the row which matches $where. As with AOOSModel::insert() and
610 * AOOSModel::update(), changes aren't saved to database before AOOSModel::save() is
611 * called. An AOOSException is thrown if no rows match $where.
612 * @param $where Either an associative array containing the where-keys and values.
613 * This means that array("FOO" => "BAR") will expand to WHERE FOO = 'BAR' or
614 * an integer representing a row number.
615 * @param $limit An array with two rows, where the first row is the start
616 * limit and the second row is the end limit. This means that array(0,2)
617 * will expand into LIMIT 0,2 or a single integer
619 public function remove($where, $limit = null) {
620 $limit = $this->_fixLimit($limit);
621 if (is_array($where)) {
622 if (!($w = $this->findAll($where, $limit[0], $limit[1]))) {
623 throw new AOOSException($this->core(), $this->tr("no_rows_found"), "", true, 2);
624 return false;
627 elseif (is_integer($where)) {
628 $w = array($where);
629 $where = $this->getRow($where);
631 else {
632 return false;
634 foreach($w as $row) {
635 unset($row);//$this->_data[$nr]);
636 $this->_rows--;
638 $where = $this->_fixWhere($where);
639 $this->_intern[] = array(AOOSModel::INTERN_DATA_REMOVED, $where, $limit);
640 return true;
644 * Updates the row that matches $where with the values, $values. Changes
645 * aren't saved to database until AOOSModel::save() is called..
646 * @param $values An associative array, in which the array keys matches
647 * column indices and the array values are the values to use to update the
648 * selected row
649 * @param $where Either an associative array containing the where-keys and values.
650 * This means that array("FOO" => "BAR") will expand to WHERE FOO = 'BAR' or
651 * an integer representing a row number
652 * @param $limit An array with two rows, where the first row is the start
653 * limit and the second row is the end limit. This means that array(0,2)
654 * will expand into LIMIT 0,2
656 public function update($values, $where, $limit = null) {
657 $limit = $this->_fixLimit($limit);
658 $w = $this->findAll($where, $limit[0], $limit[1]);
659 foreach ($w as $row) {
660 $this->setDataRow($row, $values);
662 $where = $this->_fixWhere($where);
663 $values = $this->_fixWhere($values);
664 $this->_intern[] = array(AOOSModel::INTERN_DATA_UPDATED, $values, $where, $limit);
665 return 0;
669 * Creates the model as a new table in the database.
670 * @return bool
672 public function create() {
673 if (!$this->_connection()) {
674 return false;
676 return $this->_connection()->create($this->_types, $this->_props);
680 * Creates the model as a new table in the database.
681 * @return bool
683 public function drop() {
684 if (!$this->_connection()) {
685 return false;
687 return $this->_connection()->drop();
691 * Saves the changes caused by AOOSModel::insert(), AOOSModel::remove() or
692 * AOOSModel::update()
694 public function save() {
695 if (!$this->_connection()) {
696 return false;
698 foreach ($this->_intern as $key => $row) {
699 switch($row[0]) {
700 case(AOOSModel::INTERN_DATA_INSERTED):
701 if (!$this->_connection()->insert($row[1])) {
702 return false;
704 break;
705 case(AOOSModel::INTERN_DATA_REMOVED):
706 if (!$this->_connection()->remove($row[1], $row[2])) {
707 return false;
709 case(AOOSModel::INTERN_DATA_UPDATED):
710 if (!$this->_connection()->update($row[1], $row[2], $row[3])) {
711 return false;
714 unset($this->_intern[$key]);
716 return true;
719 private function _connect() {
720 if ($this->_connection) {
721 return $this->_connection;
723 if (!$this->source() || !$this->table()) {
724 throw new AOOSException($this->core(), $this->tr("no_source_or_table_set"));
725 return false;
727 switch ($this->source()) {
728 case("mysql"):
729 require_once("lib/AOOSMysqlInterface.php");
730 $this->_connection = new AOOSMysqlInterface($this->core());
731 break;
733 $this->_connection->setTable($this->table());
736 private function _connection() {
737 if (!$this->_connection) {
738 try {
739 $this->_connect();
741 catch (AOOSException $e) {
742 throw $e;
743 return false;
746 return $this->_connection;
750 * \internal Fixes where clauses so that the given values are correctly
751 * formatted according to the properties for their column
753 private function _fixWhere($where) {
754 $ci = $this->columnIndex();
756 if (!is_array($where)) {
757 return false;
760 foreach ($where as $key => $value) {
761 if (!in_array($key, $ci)) {
762 unset($where[$ci]);
763 continue;
765 $where[$key] = $this->_fixDataProps($key, $value, $this->getProperty($key));
767 return $where;
771 * \internal Fixes a limit
773 private function _fixLimit($l) {
774 $limit = array(0,$this->rows());
775 if (is_integer($l)) {
776 $limit[1] = $l;
778 elseif(is_array($l)) {
779 $limit = $l;
781 return $limit;
784 private function _reset() {
785 $this->_data = array();
786 $this->_connect();
787 $this->_rows = 0;
791 * Dummy function
793 public function dataModelDefinition() { return 0; }
795 static public function stripQuotes($string) {
796 if (substr($string,0,1) == "'") {
797 $string = substr($string,1,strlen($string)-2);
799 return $string;
804 class AOOSModelRow extends AOOSModule implements ArrayAccess {
805 private $_data = array();
806 private $_columnIndex = array();
808 public function __construct($parent, $columnIndex) {
809 parent::__construct($parent);
810 $this->_columnIndex = $columnIndex;
811 $this->_data = array_fill_keys($columnIndex, 0);
814 public function dataModelDefinition() {
815 return 0;
818 public function __set($name, $value) {
819 return $this->parent()->setDataRow($this, $name, $value);
822 public function __get($name) {
823 return $this->_data[$name];
826 public function __toString() {
827 $str = "Row:\n";
828 foreach ($this->_data as $key => $value) {
829 $str .= "\t[".$key."] => ".$value."\n";
831 return $str;
834 public function columnIndex() {
835 return $this->_columnIndex;
838 // Array Access
839 public function offsetExists($offset) {
840 return in_array($offset, array_keys($_data));
843 public function offsetSet($offset, $value) {
844 $this->$offset = $value;
847 public function offsetGet($offset) {
848 return $this->$offset;
851 public function offsetUnset($offset) {
852 unset($this->_data[$offset]);
856 * Simply inserts the data into the row. This should NOT be used since it
857 * ignores column flags completely
859 public function setData($name, $value) {
860 $this->_data[$name] = $value;
861 return true;
864 public function save() {
865 return $this->parent()->save();
868 public function match($match) {
869 foreach ($match as $key => $value) {
870 if (!preg_match($this->_data[$key], "/".$value."/")) {
871 return false;
874 return true;
877 // vim: textwidth=80