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_DECREASING" , 2);
11 define("AOOSMODEL_PROP_DATA_QUOTES" , 4);
12 define("AOOSMODEL_PROP_DATA_ESCAPE" , 8);
13 define("AOOSMODEL_PROP_DATA_NOHTML" , 16);
14 define("AOOSMODEL_PROP_DATA_HASH" , 32);
15 define("AOOSMODEL_PROP_DATA_STRIP" , 64);
17 define("AOOSMODEL_PROP_GUI_NOTEDITABLE" , 1<<8);
18 define("AOOSMODEL_PROP_GUI_PRIVATE" , 2<<8);
19 define("AOOSMODEL_PROP_FROM_DATABASE" , 4<<8);
20 define("AOOSMODEL_PROP_UNIQUE" , 8<<8);
23 * Second-generation AOOSModel
25 * - The addition of properties allowing easy per-column settings
26 * @author Sebastian Skejø
28 class AOOSModel
extends AOOSModule
{
29 const INTERN_DATA_UPDATED
= 1;
30 const INTERN_DATA_REMOVED
= 2;
31 const INTERN_DATA_INSERTED
= 3;
33 private $_data = array();
34 private $_index = array();
35 private $_props = array();
36 private $_types = array();
37 private $_intern = array();
39 private $_source = null;
40 private $_table = null;
41 private $_connection = null;
51 public function __destruct() {
55 * Transforms to a printable string
57 public function __toString() {
58 return nl2br(str_replace(" ", " ", print_r($this->data(), true)));
62 * Appends a row to the end of the model with 0 as values unless $values is
63 * provided. Appended rows will NOT be inserted into database when AOOSModel::save() is called.
64 * @param $values If given null an empty row will be added. Otherwise an
65 * associative array is assumed.
66 * @param $ignore Flags to ignores when inserting data.
70 public function appendRow($values = null, $ignore = 0) {
71 $this->_data
[] = array_fill_keys($this->_index
, 0);
73 if (is_array($values)) {
74 $index = $this->columnIndex();
75 $last = $this->rows()-1;
76 foreach ($values as $key => $value) {
77 if (in_array($key, $index)) {
78 $this->setData($last, $key, $value, $ignore);
86 * Sets the data of a single field. Fields updated this way will NOT be
87 * updated in the database when AOOSModel::save() is called. If the type of
88 * the inserted data doesn't match the column type, an AOOSTypeException is
89 * thrown. If $row is less than 0 or greater that the row count, an
90 * AOOSException is throw.
91 * @param $row An integer representing the row
92 * @param $column A column index
94 * @param $ignore Flags to ignore when inserting data
97 public function setData($row, $column, $value, $ignore = 0) {
98 if (!$this->inColumnIndex($column)) {
102 $props = $this->_props
[$column];
103 $type = $this->_types
[$column];
106 $this->_checkType($value, $type);
108 catch (AOOSTypeException
$e) {
113 $value = $this->_fixDataProps($value, $props, $ignore);
116 if ($row < 0 ||
$row > $this->rows()) {
117 throw new AOOSException($this->core(), $this->tr("row_out_of_bounds"));
120 $this->_data
[$row][$column] = $value;
126 * @param $row The row number
127 * @param $values An associative array with column indices as keys and new values as
129 * @param $ignore Flags to ignore when inserting data
133 public function setDataRow($row, $values, $ignore = 0) {
134 foreach ($values as $index => $value) {
135 if (!$this->setData($row, $index, $value, $ignore)) {
142 private function _checkType($value, $type) {
146 if ($type & AOOSMODEL_TYPE_UNKNOWN
) {
150 elseif($continue && $type & AOOSMODEL_TYPE_STRING
) {
151 $correct = is_string($value);
154 elseif($continue && $type & AOOSMODEL_TYPE_INTEGER
) {
155 $correct = is_integer((int)$value);
158 elseif($continue && $type & AOOSMODEL_TYPE_ARRAY
) {
159 $correct = is_array($value);
162 elseif($continue && $type & AOOSMODEL_TYPE_AOOSMODEL
) {
163 $correct = $value instanceof AOOSModel
;
166 elseif($continue && $type & AOOSMODEL_TYPE_EMAIL
) {
167 $correct = preg_match("^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,3})$", $value);
172 throw new AOOSTypeException($this->core(), "", "When inserting ".$value);
178 private function _fixDataProps($value, $props, $ignore = 0) {
180 if ($props & AOOSMODEL_PROP_DATA_NOHTML
&& !(AOOSMODEL_PROP_DATA_NOHTML
& $ignore)) {
181 $value = htmlspecialchars($value);
184 if ($props & AOOSMODEL_PROP_DATA_ESCAPE
&& !(AOOSMODEL_PROP_DATA_ESCAPE
& $ignore)) {
185 $value = mysql_real_escape_string($value);
188 if ($props & AOOSMODEL_PROP_DATA_STRIP
&& !(AOOSMODEL_PROP_DATA_STRIP
& $ignore)) {
189 $value = rtrim(trim($value));
192 if ($props & AOOSMODEL_PROP_DATA_HASH
&& !(AOOSMODEL_PROP_DATA_HASH
& $ignore)) {
193 $value = hash("sha256", $value);
195 if ($props & AOOSMODEL_PROP_DATA_QUOTES
&& !(AOOSMODEL_PROP_DATA_QUOTES
& $ignore)) {
196 $first = substr($value, 0,1);
197 if ($first != "'" && $first != '"' && $first != substr($value, -1)) {
198 $value = "'".$value."'";
206 * Returns a to-dimensional array containing the data.
209 public function data() {
214 * Returns the column with the name $column
215 * @param $column The name of the column
216 * @param $offset Column values starts at this row
217 * @param $length The array of column values has this length
220 public function getColumn($column, $start = 0, $length = null) {
221 if (!$this->inColumnIndex($column)) {
224 if ($length === null) {
225 $length = $this->rows();
228 $start = $this->rows() +
$start;
231 for ($i= $start; $length > 0; $length--) {
232 $r = $this->getRow($i);
240 * Returns an array containing the columns of the given $row
241 * @param $row The row number. If this is negative the row selected is the
242 * $row'th number from the end.
245 public function getRow($row) {
246 return array_pop(array_slice($this->data(), $row, 1));
250 * Returns the number of rows
253 public function rows() {
258 * Returns only columns which match the given properties
259 * @param $props Properties
261 public function filter($props = 0, $offset = 0, $length = null) {
262 if ($length === null) {
263 $length = $this->rows();
266 for ($i = $offset; $length > 0; $length--) {
267 $r = $this->getRow($i);
269 foreach ($this->_props
as $col => $colprops) {
270 if ($colprops & $props) {
271 $ra[$col] = $r[$col];
281 * Returns the first row from $start which matches $match
282 * @param $match An associative array in which keys should match column
283 * indices and values should match values
284 * @param $start An integer representing the row from which search should.
290 public function find($match, $start = 0) {
291 if ($match && !($match = $this->_fixWhere($match))) {
294 for ($i = $start; $i < $this->rows(); $i++
) {
295 $r = $this->getRow($i);
297 if ($r[$fkey] == $match[$fkey]) {
298 foreach ($match as $key => $val) {
299 if ($r[$key] != $val) {
310 * Returns all rows which match $match, starting from $start
311 * @param $match The row to match against.
312 * @param $start An integer representing the row from which to start from.
314 * @return An array containing integers.
317 public function findAll($match, $start = 0, $length = null) {
318 if ($length === null) {
319 $length = $this->rows();
324 if (!($f = $this->find($match, $i))) {
330 $a = array_slice($a, 0, $length);
331 return count($a) ?
$a : false;
335 * Sets a columns properties to be $property
336 * @param $column Column index name
337 * @param $type The type of the column
338 * @param $property An integer representing the property
341 public function setProperty($column, $type, $property) {
342 if (!$this->inColumnIndex($column)) {
346 $this->_types
[$column] = $type;
347 $this->_props
[$column] = $property;
352 * Returns the properties of the given $column
353 * @param $column Name of the column
356 public function getProperty($column) {
357 if (!$this->inColumnIndex($column)) {
360 return $this->_props
[$column];
364 * Toggles a given property using the bitwise OR operation.
365 * @param $column Column index name
366 * @param $property An integer representing the property to toggle
369 public function toggleProperty($column, $property) {
370 if (!$this->inColumnIndex($column)) {
374 $this->_props
[$column] |
= $property;
379 * Returns the type of the given $column
380 * @param $column Name of the column
383 public function getType($column) {
384 if (!$this->inColumnIndex($column)) {
388 $type = $this->_types
[$column];
391 if ($type & AOOSMODEL_TYPE_UNKNOWN
) {
392 $typename = "UNKNOWN";
395 elseif($continue && $type & AOOSMODEL_TYPE_STRING
) {
396 $typename = "STRING";
399 elseif($continue && $type & AOOSMODEL_TYPE_INTEGER
) {
400 $typename = "INTEGER";
403 elseif($continue && $type & AOOSMODEL_TYPE_ARRAY
) {
407 elseif($continue && $type & AOOSMODEL_TYPE_AOOSMODEL
) {
408 $typename = "AOOSModel";
416 * Returns true if the given $property is set, otherwise return false
417 * @param $column Column index name
418 * @param $property An integer representing the property
421 public function checkProperty($column, $property) {
422 if (!$this->inColumnIndex($column)) {
426 if ($this->_props
[$column] & $property) {
433 * Sets the column index to be $index. Note: this resets the properties AND
435 * If $index isn't an array an AOOSException is thrown.
436 * @param $index An array containing the column indices
439 public function setColumnIndex($index) {
440 if (!is_array($index)) {
441 throw new AOOSTypeException($this->core(), $this->tr("not_array"));
444 $this->_index
= $index;
445 $this->_props
= array_fill_keys($index, 0);
450 * Returns the column index
453 public function columnIndex() { return $this->_index
; }
456 * Returns true if the given $index is in the column index. Otherwise returns
458 * @param $index Name of the index
461 public function inColumnIndex($index) {
462 return in_array($index, $this->_index
);
467 * Sets the table from in which data is contained
468 * @param $table Table name
471 public function setTable($table) {
472 $this->_table
= $this->core()->getSetting("DBPrefix").$table;
477 * Returns the table name
480 public function table() {
481 return $this->_table
;
485 * Sets the type of source from which data is fetched, inserted and deleted.
486 * @param $source The type of source, e.g. "mysql".
489 public function setSource($source) {
490 $this->_source
= strtolower($source);
495 * Returns the type of source in a string
498 public function source() {
499 return $this->_source
;
503 * Populates the model. If the model is successfully populated $this is
504 * returned. Otherwise false is returned.
505 * @param $where An associative array containing the where-keys and values.
506 * This means that array("FOO" => "BAR") will expand to WHERE FOO = 'BAR'
507 * @param $order An array with two rows. First row defines the row to order
508 * by, the second row defines the order. This means that array("BAZ",
509 * "DESC") will expand to ORDER BY BAZ DESC.
510 * @param $limit An array with two rows, where the first row is the start
511 * limit and the second row is the end limit. This means that array(0,2)
512 * will expand into LIMIT 0,2
515 public function populate($where = null, $order = null, $limit = null) {
517 if ($where && !($where = $this->_fixWhere($where))) {
521 foreach ($this->_props
as $col => $prop) {
522 if ($prop & AOOSMODEL_PROP_FROM_DATABASE
) {
526 if (!($values = $this->_connection()->select($fields, $where, $order, $limit))) {
527 throw new AOOSException($this->core(), $this->tr("couldnt_select_values"));
530 foreach ($values as $row) {
531 $this->appendRow($row, AOOSMODEL_PROP_DATA_HASH
);
537 * Inserts the given array into the model. Note that data isn't saved to
538 * database before calling AOOSModel::save()
539 * @param $values An associative array, in which the array keys corresponds
540 * to column indices and the array values are the value that should be
543 public function insert($values) {
544 $this->appendRow($values);
545 $values = $this->filter(AOOSMODEL_PROP_FROM_DATABASE
, -1, 1);
546 $this->_intern
[] = array(AOOSModel
::INTERN_DATA_INSERTED
, $values);
551 * Deletes the row which matches $where. As with AOOSModel::insert() and
552 * AOOSModel::update(), changes aren't saved to database before AOOSModel::save() is
553 * called. An AOOSException is thrown if no rows match $where.
554 * @param $where Either an associative array containing the where-keys and values.
555 * This means that array("FOO" => "BAR") will expand to WHERE FOO = 'BAR' or
556 * an integer representing a row number.
557 * @param $limit An array with two rows, where the first row is the start
558 * limit and the second row is the end limit. This means that array(0,2)
559 * will expand into LIMIT 0,2 or a single integer
561 public function remove($where, $limit = null) {
562 $limit = $this->_fixLimit($limit);
563 if (is_array($where)) {
564 if (!($w = $this->findAll($where, $limit[0], $limit[1]))) {
565 throw new AOOSException($this->core(), $this->tr("no_rows_found"), "", true, 2);
569 elseif (is_integer($where)) {
571 $where = $this->getRow($where);
576 foreach($w as $row) {
577 unset($this->_data
[$row]);
580 $where = $this->_fixWhere($where);
581 $this->_intern
[] = array(AOOSModel
::INTERN_DATA_REMOVED
, $where, $limit);
586 * Updates the row that matches $where with the values, $values. Changes
587 * aren't saved to database until AOOSModel::save() is called..
588 * @param $values An associative array, in which the array keys matches
589 * column indices and the array values are the values to use to update the
591 * @param $where Either an associative array containing the where-keys and values.
592 * This means that array("FOO" => "BAR") will expand to WHERE FOO = 'BAR' or
593 * an integer representing a row number
594 * @param $limit An array with two rows, where the first row is the start
595 * limit and the second row is the end limit. This means that array(0,2)
596 * will expand into LIMIT 0,2
598 public function update($values, $where, $limit = null) {
599 $limit = $this->_fixLimit($limit);
600 $w = $this->findAll($where, $limit[0], $limit[1]);
601 foreach ($w as $row) {
602 $this->setDataRow($row, $values);
604 $where = $this->_fixWhere($where);
605 $values = $this->_fixWhere($values);
606 $this->_intern
[] = array(AOOSModel
::INTERN_DATA_UPDATED
, $values, $where, $limit);
611 * Creates the model as a new table in the database.
614 public function create() {
615 if (!$this->_connection()) {
618 return $this->_connection()->create($this->_types
, $this->_props
);
622 * Saves the changes caused by AOOSModel::insert(), AOOSModel::remove() or
623 * AOOSModel::update()
625 public function save() {
626 if (!$this->_connection()) {
629 foreach ($this->_intern
as $key => $row) {
631 case(AOOSModel
::INTERN_DATA_INSERTED
):
632 if (!$this->_connection()->insert($row[1])) {
636 case(AOOSModel
::INTERN_DATA_REMOVED
):
637 if (!$this->_connection()->remove($row[1], $row[2])) {
640 case(AOOSModel
::INTERN_DATA_UPDATED
):
641 if (!$this->_connection()->update($row[1], $row[2], $row[3])) {
645 unset($this->_intern
[$key]);
650 private function _connect() {
651 if ($this->_connection
) {
652 return $this->_connection
;
654 if (!$this->source() ||
!$this->table()) {
655 throw new AOOSException($this->core(), $this->tr("no_source_or_table_set"));
658 switch ($this->source()) {
660 require_once("lib/AOOSMysqlInterface.php");
661 $this->_connection
= new AOOSMysqlInterface($this->core());
664 $this->_connection
->setTable($this->table());
667 private function _connection() {
668 if (!$this->_connection
) {
672 catch (AOOSException
$e) {
677 return $this->_connection
;
681 * \internal Fixes where clauses so that the given values are correctly
682 * formatted according to the properties for their column
684 private function _fixWhere($where) {
685 $ci = $this->columnIndex();
687 if (!is_array($where)) {
691 foreach ($where as $key => $value) {
692 if (!in_array($key, $ci)) {
696 $where[$key] = $this->_fixDataProps($value, $this->getProperty($key));
702 * \internal Fixes a limit
704 private function _fixLimit($l) {
705 $limit = array(0,$this->rows());
706 if (is_integer($l)) {
709 elseif(is_array($l)) {
715 private function _reset() {
716 $this->_data
= array();
724 public function dataModelDefinition() { return 0; }