1
0
mirror of https://github.com/e107inc/e107.git synced 2025-01-18 05:09:05 +01:00
php-e107/e107_handlers/model_class.php

1069 lines
27 KiB
PHP

<?php
/*
* e107 website system
*
* Copyright (C) 2001-2008 e107 Inc (e107.org)
* Released under the terms and conditions of the
* GNU General Public License (http://www.gnu.org/licenses/gpl.txt)
*
* e107 Base Model
*
* $Source: /cvs_backup/e107_0.8/e107_handlers/model_class.php,v $
* $Revision: 1.9 $
* $Date: 2009-09-25 20:20:23 $
* $Author: secretr $
*/
if (!defined('e107_INIT')) { exit; }
/**
* Base e107 Model class
*
* @package e107
* @category e107_handlers
* @version 1.0
* @author SecretR
* @copyright Copyright (c) 2009, e107 Inc.
*/
class e_model
{
/**
* Object data
*
* @var array
*/
protected $_data = array();
/**
* Posted data
* Back-end related data
*
* @var array
*/
protected $_posted_data = array();
/**
* Runtime cache of parsed from {@link _getData()} keys
*
* @var array
*/
protected $_parsed_keys = array();
/**
* DB structure array
* Awaits implementation logic,
* should be consistent with db::_getTypes() and db::_getFieldValue()
*
* @var array
*/
protected $_FIELD_TYPES = array();
/**
* Avoid DB calls if data is not changed
*
* @see mergePostedData()
* @var boolean
*/
protected $data_has_changed = false;
/**
* Validation structure in format
* 'field_name' => rule (to be used with core validator handler)
* Awaits implementation logic, should be consistent with expected frmo the validator
* structure.
*
* @var array
*/
protected $_validation_rules = array();
/**
* Validator object
*
* @var validatorClass
*/
protected $_validator = null;
/**
* Validation error stack
* See also {@link validate()}, {@link setErrors()}, {@link getErrors()}
*
* @var array
*/
protected $_validation_errors = array();
/**
* Name of object id field
* Required for {@link getId()()} method
*
* @var string
*/
protected $_field_id;
/**
* Constructor - set data on initialization
*
* @param array $data
*/
function __construct($data = array())
{
$this->setData($data);
}
/**
* Set name of object's field id
*
* @see getId()
*
* @param string $name
* @return e_model
*/
public function setFieldIdName($name)
{
$this->_field_id = $name;
return $this;
}
/**
* Retrieve name of object's field id
*
* @see getId()
*
* @param string $name
* @return string
*/
public function getFieldIdName()
{
return $this->_field_id;
}
/**
* @return array
*/
public function getValidationRules()
{
return $this->_validation_rules;
}
/**
* Set object validation rules if $_validation_rules array is empty
*
* @param array $vrules
* @return e_model
*/
public function setValidationRules(array $vrules)
{
if(empty($this->_validation_rules))
{
$this->_validation_rules = $vrules;
}
return $this;
}
/**
* Retrieve object field id value
*
* @return mixed
*/
public function getId()
{
if ($this->getFieldIdName())
{
return $this->get($this->getFieldIdName(), 0);
}
return $this->get('id', 0);
}
/**
* Retrieves data from the object ($_data) without
* key parsing (performance wise, prefered when possible)
*
* @see _getDataSimple()
* @param string $key
* @param mixed $default
* @return mixed
*/
public function get($key, $default = null)
{
return $this->_getDataSimple((string) $key, $default);
}
/**
* Retrieves data from the object ($_data)
* If $key is empty, return all object data
*
* @see _getData()
* @param string $key
* @param mixed $default
* @param integer $index
* @return mixed
*/
public function getData($key = '', $default = null, $index = null)
{
return $this->_getData($key, $default, $index);
}
/**
* Retrieves data from the object ($_posted_data) without
* key parsing (performance wise, prefered when possible)
*
* @see _getDataSimple()
* @param string $key
* @param mixed $default
* @return mixed
*/
public function getPosted($key, $default = null)
{
return $this->_getDataSimple((string) $key, $default, '_posted_data');
}
/**
* Retrieves data from the object ($_posted_data)
* If $key is empty, return all object posted data
* @see _getData()
* @param string $key
* @param mixed $default
* @param integer $index
* @return mixed
*/
public function getPostedData($key = '', $default = null, $index = null)
{
return $this->_getData($key, $default, $index, '_posted_data');
}
/**
* Search for requested data from available sources in this order:
* - posted data
* - default object data
* - empty string
*
* Use this method inside forms
*
* @param string $key
* @param string $default
* @param integer $index
* @return string
*/
public function getIfPosted($key, $default = '', $index = null)
{
if(null !== $this->getPostedData((string) $key))
{
return e107::getParser()->post_toForm($this->getPostedData((string) $key, null, $index));
}
return e107::getParser()->toForm($this->getData((string) $key, $default, $index));
}
/**
* Overwrite data in the object for a single field. Key is not parsed.
* Public proxy of {@link _setDataSimple()}
* Data isn't sanitized so use this method only when data comes from trustable sources (e.g. DB)
*
*
* @see _setData()
* @param string $key
* @param mixed $value
* @param boolean $strict update only
* @return e_model
*/
public function set($key, $value = null, $strict = false)
{
return $this->_setDataSimple($key, $value, $strict);
}
/**
* Overwrite data in the object. Public proxy of {@link _setData()}
* Data isn't sanitized so use this method only when data comes from trustable sources (e.g. DB)
*
* @see _setData()
* @param string|array $key
* @param mixed $value
* @param boolean $strict update only
* @return e_model
*/
public function setData($key, $value = null, $strict = false)
{
return $this->_setData($key, $value, $strict);
}
/**
* Overwrite posted data in the object for a single field. Key is not parsed.
* Public proxy of {@link _setDataSimple()}
* Use this method to store data from non-trustable sources (e.g. _POST) - it doesn't overwrite
* the original object data
*
* @param string $key
* @param mixed $data
* @param boolean $strict update only
* @return e_model
*/
public function setPosted($key, $data = null, $strict = false)
{
return $this->_setDataSimple($key, $data, $strict, '_posted_data');
}
/**
* Overwrite posted data in the object. Key is parsed (multidmensional array support).
* Public proxy of {@link _setData()}
* Use this method to store data from non-trustable sources (e.g. _POST) - it doesn't overwrite
* the original object data
*
* @param string|array $key
* @param mixed $data
* @param boolean $strict update only
* @return e_model
*/
public function setPostedData($key = null, $data = null, $strict = false)
{
return $this->_setData($key, $data, $strict, '_posted_data');
}
/**
* Add data to the object.
* Retains existing data in the object.
* Public proxy of {@link _addData()}
*
* If $override is false, data will be updated only (check against existing data)
*
* @param string|array $key
* @param mixed $value
* @param boolean $override override existing data
* @return e_model
*/
public function addData($key, $value = null, $override = true)
{
return $this->_addData($key, $value, $override);
}
/**
* Add data to the object.
* Retains existing data in the object.
* Public proxy of {@link _addData()}
*
* If $override is false, data will be updated only (check against existing data)
*
* @param string|array $key
* @param mixed $value
* @param boolean $override override existing data
* @return e_model
*/
public function addPostedData($key, $value = null, $override = true)
{
return $this->_addData($key, $value, $override, '_posted_data');
}
/**
* Unset single field from the object.
* Public proxy of {@link _unsetDataSimple()}
*
* @param string $key
* @return e_model
*/
public function remove($key)
{
return $this->_unsetDataSimple($key);
}
/**
* Unset data from the object.
* $key can be a string only. Array will be ignored.
* '/' inside the key will be treated as array path
* if $key is null entire object will be reset
*
* Public proxy of {@link _unsetData()}
*
* @param string|null $key
* @return e_model
*/
public function removeData($key = null)
{
return $this->_unsetData($key);
}
/**
* Unset single posted data field from the object.
* Public proxy of {@link _unsetDataSimple()}
*
* @param string $key
* @return e_model
*/
public function removePosted($key)
{
return $this->_unsetDataSimple($key, '_posted_data');
}
/**
* Unset posted data from the object.
* $key can be a string only. Array will be ignored.
* '/' inside the key will be treated as array path
* if $key is null entire object will be reset
*
* Public proxy of {@link _unsetData()}
*
* @param string|null $key
* @return e_model
*/
public function removePostedData($key = null)
{
return $this->_unsetData($key, '_posted_data');
}
/**
* @param string $key
* @return boolean
*/
public function has($key)
{
return $this->_hasData($key);
}
/**
* @param string $key
* @return boolean
*/
public function hasData($key = '')
{
return $this->_hasData($key);
}
/**
* @param string $key
* @return boolean
*/
public function hasPosted($key)
{
return $this->_hasData($key, '_posted_data');
}
public function hasPostedData()
{
return $this->_hasData('', '_posted_data');
}
/**
* @param string $key
* @return boolean
*/
public function is($key)
{
return (isset($this->_data[$key]));
}
/**
* @param string $key
* @return boolean
*/
public function isData($key)
{
return $this->_isData($key);
}
/**
* @param string $key
* @return boolean
*/
public function isPosted($key)
{
return (isset($this->_posted_data[$key]));
}
/**
* @param string $key
* @return boolean
*/
public function isPostedData($key)
{
return $this->_isData($key, '_posted_data');
}
/**
* Compares posted data vs object data
*
* @param string $field
* @param boolean $strict compare variable type as well
* @return boolean
*/
public function dataHasChangedFor($field, $strict = false)
{
$newData = $this->getData($field);
$postedData = $this->getPostedData($field);
return ($strict ? $newData !== $postedData : $newData != $postedData);
}
/**
* @return boolean
*/
public function dataHasChanged()
{
return $this->data_has_changed;
}
/**
* Merge posted data with the object data
* Should be used on edit/update/create record (back-end)
* Copied posted data will be removed (no matter if copy is successfull or not)
*
* If $strict is true, only existing object data will be copied (update)
* TODO - move to admin e_model extension
*
* @param boolean $strict
* @param boolean $sanitize
* @param boolean $validate perform validation check
* @return e_model
*/
public function mergePostedData($strict = true, $sanitize = true, $validate = true)
{
if(!$this->getPostedData() || ($validate && !$this->validate()))
{
return $this;
}
$tp = e107::getParser();
//TODO - sanitize method based on validation rules OR _FIELD_TYPES array?
$data = $sanitize ? $tp->toDB($this->getPostedData()) : $this->getPostedData();
foreach ($data as $field => $dt)
{
$this->setData($field, $dt, $strict)
->removePostedData($field);
}
return $this;
}
/**
* Merge passed data array with the object data
* If $strict is true, only existing object data will be copied (update)
* TODO - move to admin e_model extension
*
* @param array $src_data
* @param boolean $sanitize
* @param boolean $validate perform validation check
* @return e_model
*/
public function mergeData(array $src_data, $strict = true, $sanitize = true, $validate = true)
{
//FIXME
if(!$src_data || ($validate && !$this->validate($src_data)))
{
return $this;
}
//TODO - sanitize method based on validation rules OR _FIELD_TYPES array?
if($sanitize)
{
$src_data = e107::getParser()->toDB($src_data);
}
foreach ($src_data as $key => $value)
{
$this->setData($key, $value, $strict);
}
return $this;
}
/**
* Retrieves data from the object
*
* If $key is empty will return all the data as an array
* Otherwise it will return value of the attribute specified by $key
* '/' inside the key will be treated as array path (x/y/z equals to [x][y][z]
*
* If $index is specified it will assume that attribute data is an array
* and retrieve corresponding member.
*
* @param string $key
* @param mixed $default
* @param integer $index
* @param boolean $posted data source
* @return mixed
*/
protected function _getData($key = '', $default = null, $index = null, $data_src = '_data')
{
$key = trim($key, '/');
if ('' === $key)
{
return $this->$data_src;
}
if (strpos($key, '/'))
{
if(isset($this->_parsed_keys[$data_src.'/'.$key]))
{
return $this->_parsed_keys[$data_src.'/'.$key];
}
$keyArr = explode('/', $key);
$data = $this->$data_src;
foreach ($keyArr as $k)
{
if ('' === $k)
{
return $default;
}
if (is_array($data))
{
if (!isset($data[$k]))
{
return $default;
}
$data = $data[$k];
}
else
{
return $default;
}
}
$this->_parsed_keys[$data_src.'/'.$key] = $data;
return $data;
}
//get $index
if (isset($this->{$data_src}[$key]))
{
if (null === $index)
{
return $this->{$data_src}[$key];
}
$value = $this->{$data_src}[$key];
if (is_array($value))
{
if (isset($value[$index]))
{
return $value[$index];
}
return $default;
}
elseif (is_string($value))
{
$arr = explode("\n", $value);
return (isset($arr[$index]) ? $arr[$index] : $default);
}
return $default;
}
return $default;
}
/**
* Get value from _data array without parsing the key
*
* @param string $key
* @param mixed $default
* @param string $posted data source
* @return mixed
*/
protected function _getDataSimple($key, $default = null, $data_src = '_data')
{
return isset($this->{$data_src}[$key]) ? $this->{$data_src}[$key] : $default;
}
/**
* Overwrite data in the object.
*
* $key can be string or array.
* If $key is string, the attribute value will be overwritten by $value
* '/' inside the key will be treated as array path
*
* If $key is an array and $strict is false, it will overwrite all the data in the object.
*
* If $strict is true and $data_src is '_data', data will be updated only (no new data will be added)
*
* @param string|array $key
* @param mixed $value
* @param boolean $strict
* @param string $data_src
* @return e_model
*/
protected function _setData($key, $value = null, $strict = false, $data_src = '_data')
{
if(is_array($key))
{
if($strict)
{
foreach(array_keys($key) as $k)
{
$this->_setData($k, $key[$k], true, $data_src);
}
return $this;
}
$this->$data_src = $key;
return $this;
}
//multidimensional array support - strict _setData for values of type array
if($strict && !empty($value) && is_array($value))
{
foreach($value as $k => $v)
{
$this->_setData($key.'/'.$k, $v, true, $data_src);
}
return $this;
}
//multidimensional array support - parse key
$key = trim($key, '/');
if(strpos($key,'/'))
{
//if strict - update only
if($strict && !$this->isData($key))
{
return $this;
}
$keyArr = explode('/', $key);
$data = &$this->$data_src;
for ($i = 0, $l = count($keyArr); $i < $l; $i++)
{
$k = $keyArr[$i];
if (!isset($data[$k]))
{
$data[$k] = array();
}
$data = &$data[$k];
}
//data has changed - optimized
if('_data' === $data_src && !$this->data_has_changed)
{
$this->data_has_changed = (!isset($this->_data[$key]) || $this->_data[$key] != $value);
}
$this->_parsed_keys[$data_src.'/'.$key] = $value;
$data = $value;
}
else
{
//if strict - update only
if($strict && !isset($this->_data[$key]))
{
return $this;
}
if('_data' === $data_src && !$this->data_has_changed)
{
$this->data_has_changed = (!isset($this->_data[$key]) || $this->{$data_src}[$key] != $value);
}
$this->{$data_src}[$key] = $value;
}
return $this;
}
/**
* Set data for the given source. More simple (and performance wise) version
* of {@link _setData()}
*
* @param string $key
* @param mixed $value
* @param boolean $strict
* @param string $data_src
* @return e_model
*/
protected function _setDataSimple($key, $value = null, $strict = false, $data_src = '_data')
{
$key = $key.'';//smart toString
if(!$strict)
{
//data has changed
if('_data' === $data_src && !$this->data_has_changed)
{
$this->data_has_changed = (!isset($this->_data[$key]) || $this->_data[$key] != $value);
}
$this->{$data_src}[$key] = $value;
return $this;
}
if($this->isData($key))
{
if('_data' === $data_src && !$this->data_has_changed)
{
$this->data_has_changed = (!isset($this->_data[$key]) || $this->_data[$key] != $value);
}
$this->{$data_src}[$key] = $value;
}
return $this;
}
/**
* Add data to the object.
* Retains existing data in the object.
*
* If $override is false, only new (non-existent) data will be added
*
* @param string|array $key
* @param mixed $value
* @param boolean $override allow override of existing data
* @param string $data_src data source
* @return e_model
*/
protected function _addData($key, $value = null, $override = true, $data_src = '_data')
{
if(is_array($key))
{
foreach($key as $k => $v)
{
$this->_addData($k, $v, $override, $data_src);
}
return $this;
}
if($override || !$this->_isData($key, $data_src))
{
if(is_array($value))
{
if(is_array($key))
{
foreach($key as $k => $v)
{
$this->_addData($key.'/'.$k, $v, $override, $data_src);
}
}
return $this;
}
$this->_setData($key, $value, false, $data_src);
}
return $this;
}
/**
* Unset data from the object from the given source.
* $key can be a string only. Array will be ignored.
* '/' inside the key will be treated as array path
* if $key is null entire object will be reset
*
* @param string|null $key
* @param string $data_src data source
* @return e_model
*/
protected function _unsetData($key = null, $data_src = '_data')
{
if (null === $key)
{
if('_data' === $data_src && !empty($this->_data))
{
$this->data_has_changed = true;
}
$this->$data_src = array();
return $this;
}
$key = trim($key, '/');
if(strpos($key,'/'))
{
$keyArr = explode('/', $key);
$data = &$this->$data_src;
$unskey = array_pop($data);
for ($i = 0, $l = count($keyArr); $i < $l; $i++)
{
$k = $keyArr[$i];
if (!isset($data[$k]))
{
return $this; //not found
}
$data = &$data[$k];
}
if(is_array($data))
{
if('_data' === $data_src && isset($data[$unskey]))
{
$this->data_has_changed = true;
}
unset($data[$unskey], $this->_parsed_keys[$data_src.'/'.$key]);
}
}
else
{
if('_data' === $data_src && isset($this->{$data_src}[$key]))
{
$this->data_has_changed = true;
}
unset($this->{$data_src}[$key]);
}
return $this;
}
/**
* Unset single field from the object from the given source. Key is not parsed
*
* @param string $key
* @param string $data_src data source
* @return e_model
*/
protected function _unsetDataSimple($key, $data_src = '_data')
{
if('_data' === $data_src && isset($this->{$data_src}[$key]))
{
$this->data_has_changed = true;
}
unset($this->{$data_src}[$key]);
return $this;
}
/**
* If $key is empty, checks whether there's any data in the object
* Otherwise checks if the specified key is empty/set.
*
* @param string $key
* @param string $data_src data source
* @return boolean
*/
protected function _hasData($key = '', $data_src = '_data')
{
if (empty($key))
{
return !empty($this->$data_src);
}
$value = $this->_getData($key, null, null, $data_src);
return !empty($value);
}
/**
* Checks if the specified key is set
*
* @param string $key
* @param string $data_src data source
* @return boolean
*/
protected function _isData($key, $data_src = '_data')
{
return (null !== $this->_getData($key, null, null, $data_src));
}
/**
* Validate posted data:
* 1. validate posted data against object validation rules
* 2. add validation errors to the object if any
* 3. return true for valid and false for non-valid data
*
* @param array $data optional - data for validation, defaults to posted data
* @return boolean
*/
public function validate(array $data = array())
{
$this->_validation_errors = array();
if(!$this->getValidationRules())
{
return true;
}
$result = $this->getValidator()->validateFields(($data ? $data : $this->getPostedData()), $this->getValidationRules());
if(!empty($result['errors']))
{
$this->_validation_errors = $result['errors'];
return false;
}
return true;
}
/**
* @return boolean
*/
public function isError()
{
return !empty($this->_validation_errors);
}
/**
* Under construction
* Add human readable errors to eMessage stack
*
* @param boolean $reset reset errors
* @param boolean $session store messages to session
* @return e_model
*/
public function setErrors($reset = true, $session = false)
{
//$emessage = eMessage::getInstance();
return $this;
}
/**
* Load data from DB
* Awaiting for child class implementation
*
*/
public function load()
{
}
/**
* Save data to DB
* Awaiting for child class implementation
*
*/
public function save()
{
}
/**
* Insert data to DB
* Awaiting for child class implementation
*
*/
public function dbInsert()
{
}
/**
* Update DB data
* Awaiting for child class implementation
*
*/
public function dbUpdate()
{
}
/**
* @return validatorClass
*/
public function getValidator()
{
if(null === $this->_validator)
{
$this->_validator = e107::getObject('validatorClass', null, e_HANDLER.'validator_class.php');
}
return $this->_validator;
}
/**
* Convert object data to a string
*
* @param boolean $AddSlashes
* @param string $key optional, if set method will return corresponding value as a string
* @return string
*/
public function toString($AddSlashes = true, $key = null)
{
if (null !== $key)
{
$value = $this->getData($key);
if(is_array($value))
{
return e107::getArrayStorage()->WriteArray($value, $AddSlashes);
}
return (string) $value;
}
return (string) e107::getArrayStorage()->WriteArray($this->getData(), $AddSlashes);
}
/**
* Magic method - convert object data to a string
* NOTE: before PHP 5.2.0 the __toString method was only
* called when it was directly combined with echo() or print()
*
* NOTE: PHP 5.3+ is throwing parse error if __toString has optional arguments.
*
* @param boolean $AddSlashes
* @return string
*/
public function __toString()
{
return $this->toString((@func_get_arg(0) === true));
}
}