diff --git a/e107_handlers/pref_class.php b/e107_handlers/pref_class.php index b9eee5ffb..901c2198d 100644 --- a/e107_handlers/pref_class.php +++ b/e107_handlers/pref_class.php @@ -1,25 +1,1589 @@ 'SitePrefs', + 'core_backup' => 'SitePrefs_Backup', + 'emote' => 'emote', + 'menu' => 'menu_pref', + 'search' => 'search_prefs', + 'notify' => 'notify_prefs' + ); + + /** + * Backward compatibility - list of prefid's which operate wit serialized data + * + * @var array + */ + protected $serial_bc_array = array('emote', 'menu', 'search'); + + /** + * Constructor + * + * @param string $alias + * @param boolean $load load DB data on startup + */ + function __construct($alias, $load = true) + { + if($this->getAlias($alias)) + { + $pref_alias = $this->getAlias($alias); + $pref_id = $alias; + } + elseif($this->getConfigId($alias)) + { + $pref_id = $this->getConfigId($alias); + $pref_alias = $alias; + } + else + { + $pref_id = $pref_alias = ''; + trigger_error('Core config ID '.$alias.' not found!', E_USER_WARNING); + return; + } + + if(in_array($pref_alias, $this->serial_bc_array)) + { + $this->setOptionSerialize(true); + } + + if('core' === $pref_alias) + { + $this->setOptionBackup(true); + } + + parent::__construct($pref_id, $pref_alias); + if($load && $pref_id) + { + $this->load(); + } + } + + /** + * Get config ID + * Allowed values: key or value from $alias array + * If id not found this method returns false + * + * @param string $alias + * @return string + */ + public function getConfigId($alias) + { + $alias = trim($alias); + if(isset($this->alliases[$alias])) + { + return $this->allias[$alias]; + } + return array_search($alias, $this->alliases); + } + + /** + * Get config ID + * Allowed values: key or value from $alias array + * If id not found this method returns false + * + * @param string $alias + * @return string + */ + public function getAlias($prefid) + { + $prefid = trim($prefid); + return array_search($prefid, $this->alliases); + } +} + +/** + * Handle plugin preferences + * + * @package e107 + * @category e107_handlers + * @version 1.0 + * @author SecretR + * @copyright Copyright (c) 2009, e107 Inc. + */ +class e_plugin_pref extends e_pref +{ + /** + * Unique plugin name + * + * @var string + */ + protected $plugin_id; + + /** + * Constructor + * Note: object data will be loaded only if the plugin is installed (no matter of the passed + * $load value) + * + * @param string $plugin_id unique plugin name + * @param string $multi_row additional field identifier appended to the $prefid + * @param boolean $load load on startup + */ + function __construct($plugin_id, $multi_row = '', $load = true) + { + $this->plugin_id = $plugin_id; + if($multi_row) + { + $plugin_id = $plugin_id.'_'.$multi_row; + } + parent::__construct($plugin_id, 'plugin_'.$plugin_id); + if($load && e107::findPref('plug_installed/'.$this->plugin_id)) + { + $this->load(); + } + } + + /** + * Retrive unique plugin name + * + * @return string + */ + public function getPluginId() + { + return $this->plugin_id; + } +} + +/** + * Base preference object - shouldn't be used direct, + * used internal by {@link e_plugin_pref} and {@link e_core_pref classes} + * + * @package e107 + * @category e107_handlers + * @version 1.0 + * @author SecretR + * @copyright Copyright (c) 2009, e107 Inc. + */ +abstract class e_pref extends e_model +{ + /** + * Preference ID - DB row value + * + * @var string + */ + protected $prefid; + + /** + * Preference ID alias e.g. 'core' is an alias of prefid 'SitePrefs' + * + * @var string + */ + protected $alias; + + /** + * Set on first data load + * + * @var string + */ + protected $pref_cache = ''; + + /** + * Backward compatibility - serialized preferences + * Note: serialized preference storage is deprecated + * + * @var boolean + */ + protected $serial_bc = false; + + /** + * If true, $prefid.'_Backup' row will be created/updated + * on every {@link save()} call + * + * @var boolean + */ + protected $set_backup = false; + + /** + * Constructor + * + * @param string $prefid + * @param string $alias + * @param array $data + */ + function __construct($prefid, $alias = '', $data = array()) + { + $this->prefid = $prefid; + if(empty($alias)) + { + $alias = $prefid; + } + $this->alias = $alias; + parent::__construct($data); + if($data) $this->setStructure(); + } + + /** + * Advanced getter - $pref_name is parsed (multidimensional arrays support), alias of {@link e_model::getData()} + * + * @param string $pref_name + * @param mixed $default + * @param integer $index + * @return mixed + */ + public function getPref($pref_name, $default = null, $index = null) + { + return $this->getData($pref_name, $default, $index); + } + + /** + * Simple getter - $pref_name is not parsed (no multidimensional arrays support), alias of {@link e_model::get()} + * + * @param string $pref_name + * @param mixed $default + * @return mixed + */ + public function get($pref_name, $default = null) + { + return $this->get($pref_name, $default); + } + + /** + * Advanced setter - $pref_name is parsed (multidimensional arrays support), alias of {@link setData()} + * Object data reseting is not allowed, adding new pref is not allowed + * @param string|array $key + * @param mixed $value + * @return e_pref + */ + public function setPref($pref_name, $value) + { + return $this->setData($pref_name, $value, true); + } + + /** + * Simple setter - $pref_name is not parsed (no multidimensional arrays support) + * Adding new pref is not allowed + * + * @param string|array $key + * @param mixed $value + * @return e_pref + */ + public function set($pref_name, $value) + { + if(empty($pref_name) || false !== strpos($pref_name, '/') || !$this->isData($pref_name)) + { + return $this; + } + return $this->setData($pref_name, $value, true); + } + + /** + * Add new (single) preference (ONLY if doesn't exist) + * + * @see addData() + * @param string $pref_name + * @param mixed $value + * @return e_pref + */ + public function add($pref_name, $value = null) + { + if(!is_string($pref_name) || strpos($pref_name, '/')) + { + return $this; + } + + $this->addData($pref_name, $value, false); + return $this; + } + + /** + * Add new preference or preference array (ONLY if it/they doesn't exist) + * + * @see addData() + * @param string|array $pref_name + * @param mixed $value + * @return e_pref + */ + public function addPref($pref_name, $value = null) + { + return $this->addData($pref_name, $value, false); + } + + /** + * Remove single preference + * + * @see e_model::remove() + * @param string $pref_name + * @return e_pref + */ + public function remove($pref_name) + { + parent::remove($pref_name); + + $struct = $this->getStructure(); + unset($struct[$pref_name]); + $this->setStructure($struct); + + return $this; + } + + /** + * Remove single preference (parse $pref_name) + * + * @see removeData() + * @param string $pref_name + * @return e_pref + */ + public function removePref($pref_name) + { + $this->removeData($pref_name); + return $this; + } + + /** + * Disallow public use of addData() + * Disallow preference override + * + * @param string|array $pref_name + * @param boolean $strict + */ + final protected function addData($pref_name, $value = null, $strict = false) + { + //XXX - Move most of this to the base class? + if(is_array($pref_name)) + { + parent::addData($pref_name, $strict, false); + $this->setStructure($this->getStructure() + array_keys($pref_name)); + return $this; + } + + if(!$this->isData($pref_name)) + { + $pref_name = trim($pref_name, '/'); + parent::setData($pref_name, $value, $strict); + if(!strpos($pref_name, '/')) + { + $this->setStructure($this->getStructure() + array($pref_name)); + } + } + + return $this; + } + + /** + * Disallow public use of setData() + * Object data reseting is not allowed + * Adding new preference key is not allowed + * + * @see e_model::setData() + * @param string|array $key + * @param mixed $value + * @return e_pref + */ + final protected function setData($pref_name, $value, $strict) + { + //object reset not allowed, adding new pref is not allowed + if(empty($pref_name) || !$this->isData($pref_name)) + { + return $this; + } + parent::setData($pref_name, $value, $strict); + + return $this; + } + + /** + * Disallow public use of removeData() + * Object data reseting is not allowed + * + * @return e_pref + */ + final protected function removeData($pref_name) + { + if(is_null($pref_name)) + { + return $this; //object reseting not allowed + } + parent::removeData($pref_name); + + $pref_name = trim($pref_name, '/'); + if(!strpos($pref_name, '/')) + { + $struct = $this->getStructure(); + unset($struct[$pref_name]); + $this->setStructure($struct); + } + return $this; + } + + /** + * Load object data - public + * + * @see _load() + * @param boolean $force + * @return e_pref + */ + public function load($force = false) + { + if($force || !$this->hasData()) + { + $this->data_has_changed = false; + $this->_load($force); + } + + return $this; + } + + /** + * Load object data + * + * @param boolean $force + * @return e_pref + */ + protected function _load($force = false) + { + $id = e107::getParser()->toDB($this->prefid); + $data = $force ? false : $this->getPrefCache(true); + + if(false !== $data) + { + $this->pref_cache = e107::getArrayStorage()->WriteArray($data, false); //runtime cache + return $this->setData($data); + } + + if ($this->getDb()->db_Select('core', 'e107_value', "e107_name='{$id}'")) + { + $row = $this->getDb()->db_Fetch(); + + if($this->serial_bc) + { + $data = unserialize($row['e107_value']); + $row['e107_value'] = e107::getArrayStorage()->WriteArray($row['e107_value'], false); + } + else + { + $data = e107::getArrayStorage()->ReadArray($row['e107_value']); + } + + $this->pref_cache = $row['e107_value']; //runtime cache + $this->setPrefCache($row['e107_value']); + } + + if(empty($data)) $data = array(); + + return $this->setData($data); + } + + /** + * Save object data to DB + * + * @param boolean $from_post merge post data + * @param boolean $session_messages use session messages + * @return unknown + */ + public function save($from_post = true, $session_messages = false) + { + if(!$this->prefid) + { + return $this; + } + + if($from_post) + { + $this->setStructure(); //set structure from current data + $this->mergePostedData(); //all posted data is sanitized and filtered vs structure array + } + + //TODO - LAN + require_once(e_HANDLER.'message_handler.php'); + $emessage = eMessage::getInstance(); + + //Save to DB + if($this->data_has_changed && !$this->isError()) + { + if(true === $this->set_backup) + { + if(e107::getDb()->db_Select_gen("REPLACE INTO `#core` (e107_name,e107_value) values ('{$this->prefid}_Backup', '".addslashes($this->pref_cache)."') ")) + { + $emessage->add('Backup successfully created.', E_MESSAGE_SUCCESS, $session_messages); + } + } + if(e107::getDb()->db_Select_gen("REPLACE INTO `#core` (e107_name,e107_value) values ('{$this->prefid}', '".$this->toString(true)."') ")) + { + $this->data_has_changed = false; //reset status + $this->pref_cache = $this->toString(false); //reset pref cache + $this->setPrefCache($this->pref_cache); //reset pref cache file + $emessage->add('Settings successfully saved.', E_MESSAGE_SUCCESS, $session_messages); + } + } + + if($this->isError()) + { + $this->setErrors(true, $session_messages); //add errors to the eMessage stack + $emessage->add('Settings not saved.', E_MESSAGE_ERROR, $session_messages); + } + else + { + $emessage->add('Settings not saved as no changes were made.', E_MESSAGE_INFO, $session_messages); + } + + return $this; + } + + /** + * Get cached data from server cache file + * + * @param boolean $toArray convert to array + * @return string|array|false + */ + protected function getPrefCache($toArray = true) + { + if(!$this->pref_cache) + { + $this->pref_cache = ecache::retrieve_sys('Config_'.$this->alias, 24 * 60, true); + } + return ($toArray && $this->pref_cache ? e107::getArrayStorage()->ReadArray($this->pref_cache) : $this->pref_cache); + } + + /** + * Store data to a server cache file + * If $cache_string is an array, it'll be converted to a string + * + * @param string|array $cache_string + * @return e_pref + */ + protected function setPrefCache($cache_string) + { + if(is_array($cache_string)) + { + $cache_string = e107::getArrayStorage()->WriteArray($cache_string, false); + } + ecache::set_sys('Config_'.$this->alias, $cache_string, true); + return $this; + } + + /** + * Set $set_backup option + * + * @param boolean $optval + * @return e_pref + * + */ + public function setOptionBackup($optval) + { + $this->set_backup = $optval; + return $this; + } + + /** + * Set $serial_bc option + * + * @param boolean $optval + * @return e_pref + * + */ + public function setOptionSerialize($optval) + { + $this->serial_bc = $optval; + return $this; + } + +} + +/** + * 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(); + + /** + * Data structure array + * Needs implementation by child classes + * + * @var array + */ + protected $_data_structure = 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) + * Needs implementation by child classes + * + * @var array + */ + protected $_validation_rules = array(); + + /** + * @var validatorClass + */ + protected $_validator = null; + + /** + * Validation error stack + * See also {@link validate()}, {@link setErrors()}, {@link getErrors()} + * + * @var array + */ + protected $_validation_errors = array(); + + /** + * Posted data + * + * @var array + */ + protected $_posted_data = array(); + + /** + * Name of object id field + * + * @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->_idFieldName = $name; + return $this; + } + + /** + * Retrieve name of object's field id + * + * @see getId() + * + * @param string $name + * @return string + */ + public function getFieldIdName() + { + return $this->_idFieldName; + } + + /** + * Get object data structure + * Used as a filter when copy/merge posted data to the object + * + * @return array + */ + public function getStructure() + { + return $this->_data_structure; + } + + /** + * Set object data structure if $_data_structure is empty + * + * @param array $struct + * @return e_model + */ + public function setStructure(array $struct = array()) + { + if(empty($this->_data_structure) || $struct) + { + $this->_data_structure = !empty($struct) ? array_unique($struct) : array_keys($this->getData()); + } + return $this; + } + + /** + * @return array + */ + public function getValidationRules() + { + return $this->_validation_rules; + } + + /** + * Set object validation rules if $_validation_rules 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->getIdFieldName()) + { + return $this->getData($this->getIdFieldName(), 0, null); + } + return $this->getData('id', 0, null); + } + + /** + * 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($key, $default); + } + + /** + * Retrieves data from the 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($key, $default, '_posted_data'); + } + + /** + * Retrieves data from the 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 + * + * Useful when operating with form GUI + * + * @param string $key + * @param string $default + * @param integer $index + * @return string + */ + public function getIfPosted($key, $default = '', $index = null) + { + if(null !== $this->getPostedData($key)) + { + return e107::getParser()->post_toForm($this->getPostedData($key, null, $index)); + } + return $this->getData($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 + * @return e_model + */ + public function set($key, $value = null, $strict = false) + { + $this->data_has_changed = true; + 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 + * @return e_model + */ + public function setData($key, $value = null, $strict = false) + { + $this->data_has_changed = true; + 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 + * @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 for a single field + * 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 $key + * @param mixed $data + * @param boolean $strict + * @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 $strict is true, data will be filtered by the + * object structure. NOTE - Multidimensional arrays are not filtered. + * + * @param array $arr + * @param boolean $strict + * @return e_model + */ + public function addData(array $arr, $strict = false, $override = true) + { + if($arr) $this->data_has_changed = true; //TODO - better status change check + return $this->_addData($arr, $strict, $override); + } + + /** + * Add data to the object. + * Retains existing data in the object. + * Public proxy of {@link _addData()} + * + * If $strict is true, data will be filtered by the + * object structure. NOTE - Multidimensional arrays are not filtered. + * + * @param array $arr + * @param boolean $strict + * @return e_model + */ + public function addPostedData(array $arr, $strict = false, $override = true) + { + return $this->_addData($arr, $strict, $override, '_posted_data'); + } + + /** + * Unset single field from the object. + * Public proxy of {@link _unsetDataSimple()} + * + * @param string $key + * @return e_model + */ + public function remove($key) + { + $this->data_has_changed = true; + 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) + { + $this->data_has_changed = true; + 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); + } + + /** + * @return boolean + */ + public function hasData() + { + return $this->_hasData(); + } + + /** + * @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 isData($key) + { + return $this->_isData($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); + } + + /** + * Merge posted data with the object data + * Should be used on edit/update record + * Copied posted data will be removed + * + * @param boolean $sanitize + * @return e_model + */ + public function mergePostedData($sanitize = true) + { + if(!$this->getPostedData() || !$this->getStructure() || !$this->validate()) + { + return $this; + } + + $tp = e107::getParser(); + foreach ($this->getStructure() as $field) + { + if(null === $this->getPostedData($field) || !$this->dataHasChangedFor($field)) + { + $this->removePostedData($field); + continue; + } + + $this->data_has_changed = true; + $data = $sanitize ? $tp->toDB($this->getPostedData($field)) : $this->getPostedData($field); + $this->setData($field, $data) + ->removePostedData($field); + } + return $this; + } + + /** + * Copy posted data with the object data + * Should be used on create/insert records + * Copied posted data will be removed + * + * @param boolean $validate + * @param boolean $sanitize + * @param boolean $strict filter by the object structure + * @return e_model + */ + public function copyPostedData($validate = true, $sanitize = true, $strict = true) + { + if($validate && !$this->validate()) + { + return $this; + } + + $tp = e107::getParser(); + if(!$strict || !$this->getStructure()) + { + $data = $sanitize ? $tp->toDB($this->getPostedData()) : $this->getPostedData(); + if(!empty($data)) + { + $this->data_has_changed = true; + $this->setPostedData(null) + ->setData($data); + } + return $this; + } + + foreach ($this->getStructure() as $field) + { + if(null === $this->getPostedData($field) || !$this->dataHasChangedFor($field)) + { + continue; + } + + $this->data_has_changed = true; + $data = $sanitize ? $tp->toDB($this->getPostedData($field)) : $this->getPostedData($field); + $this->setData($field, $data) + ->removePostedData($field); + } + 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, '/')) + { + $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; + } + } + 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, it will overwrite all the data in the object. + * + * If $strict is true and $key is an array, data will be filtered by the + * object structure + * + * @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 && $this->getStructure()) + { + $struct = $this->getStructure(); + foreach($struct as $k) + { + if(isset($key[$k])) + { + $this->_setData($k, $key[$k], $strict, $data_src); + } + } + return $this; + } + $this->$data_src = $key; + return $this; + } + + $key = trim($key, '/'); + if(strpos($key,'/')) + { + $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 = $value; + } + else + { + $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 unknown + */ + protected function _setDataSimple($key, $value = null, $strict = false, $data_src = '_data') + { + if(!$strict || in_array($key, $this->getStructure())) + { + $this->$data_src[$key] = $value; + } + return $this; + } + + /** + * Add data to the object. + * Retains existing data in the object. + * + * If $strict is true, data will be filtered by the + * object structure. NOTE - Multidimensional arrays are not filtered. + * + * @param array $arr + * @param boolean $strict + * @param boolean $override allow override of existing data + * @param string $data_src data source + * @return e_model + */ + protected function _addData(array $arr, $strict = true, $override = true, $data_src = '_data') + { + if($strict && $this->getStructure()) + { + $struct = $this->getStructure(); + foreach($struct as $key => $value) + { + if(!$override && $this->isData($key)) + { + continue; + } + if(isset($arr[$key])) + { + $this->_setData($key, $value, $strict, $data_src); + } + } + return $this; + } + + foreach($arr as $key => $value) + { + $this->_setData($key, $value, $strict, $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) + { + $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)) + { + unset($data[$unskey]); + } + } + else + { + 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') + { + 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 + * + * @return boolean + */ + public function validate() + { + $this->_validation_errors = array(); + + if(!$this->getValidationRules()) + { + return true; + } + + $result = $this->getValidator()->validateFields($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; + } + + /** + * Wrapper of {@link __toString()} + * + * @param boolean $AddSlashes + * @return string + */ + public function toString($AddSlashes = true) + { + return $this->__toString($AddSlashes); + } + + /** + * Magic method - convert object data to string + * NOTE: before PHP 5.2.0 the __toString method was only + * called when it was directly combined with echo() or print() + * + * @param boolean $AddSlashes + * @return string + */ + public function __toString($AddSlashes = true) + { + return e107::getArrayStorage()->WriteArray($this->getData(), $AddSlashes); + } +} + +/** + * DEPRECATED - see e107::getConfig(), e_core_pref and e_plugin_pref + * + */ // // Simple functionality: // Grab all prefs once, in one DB query. Reuse them throughout the session.