%PDF- %PDF-
Direktori : /home1/lightco1/www/lightingrepublic.com.au/libraries/cegcore/libs/ |
Current File : //home1/lightco1/www/lightingrepublic.com.au/libraries/cegcore/libs/model.php |
<?php /** * ChronoCMS version 1.0 * Copyright (c) 2012 ChronoCMS.com, All rights reserved. * Author: (ChronoCMS.com Team) * license: Please read LICENSE.txt * Visit http://www.ChronoCMS.com for regular updates and information. **/ namespace GCore\Libs; /* @copyright:ChronoEngine.com @license:GPLv2 */defined('_JEXEC') or die('Restricted access'); defined("GCORE_SITE") or die; class Model { var $name; var $alias; var $tablename; var $table_fields = array(); var $pkey; var $dbo = null; var $dbo_config = array(); var $id = 0; var $ids = array(); var $validate = array(); var $errors = array(); var $hasOne = array(); var $belongsTo = array(); var $hasMany = array(); var $conditions = array(); var $order_by = array(); var $group = array(); var $limit = null; var $offset = null; var $page = null; var $contain = array(); var $recursive = 1; var $page_limit = 0; var $data = array(); var $created = null; var $_alias_used = array(); var $associated_models = array(); static $__parents_models = array(); var $_parents_models = array(); var $_late_hasMany = array(); var $chain_models = array(); static $_chain_models = array(); var $allowed_models = array(); static $_generated_models = array(); function start(){ } function __construct($settings = array()){ $this->start(); if(empty($settings['dbo'])){ $dbo = Database::getInstance($this->dbo_config); }else{ $dbo = $settings['dbo']; } $this->dbo = &$dbo; if(empty($this->name)){ $this->name = empty($settings['name']) ? Base::getClassName(get_class($this)) : $settings['name']; } if(empty($this->tablename) AND !empty($settings['tablename'])){ $this->tablename = $settings['tablename']; } //set table name after replacing prefix $this->tablename = $this->dbo->_prefixTable($this->tablename); //set table fields if not set if(empty($this->table_fields)){ $this->loadFields(); } //set alias if(empty($this->alias)){ $this->alias = $this->name; } //get primary key if not set if(empty($this->pkey)){ $this->loadPKey(); } //set default ordering if(empty($this->order_by) AND !empty($this->pkey)){ //$this->order_by = array($this->alias.'.'.$this->pkey); } //set default page limit $this->page_limit = empty($this->page_limit) ? Base::getConfig('list_limit', 30) : $this->page_limit; /*if(!empty(self::$__parents_models)){ $this->_parents_models = self::$__parents_models; self::$__parents_models = array(); }*/ if(!empty($settings['allowed_models'])){ $this->allowed_models = $settings['allowed_models']; } if(!empty(self::$_chain_models)){ $this->chain_models = self::$_chain_models; //$this->chain_models[] = $this->alias; self::$_chain_models = array(); }else{ $this->chain_models[] = $this->alias; } if(empty($settings['no_relations'])){ foreach(array('hasOne', 'belongsTo', 'hasMany') as $type){ $this->_bind_models($type); } } $this->initialize(); //reset the parents models list $this->_parents_models = array(); } public static function getInstance($settings = array()){ static $instances; if(!isset($instances)){ $instances = array(); } $name = get_called_class(); $key = md5(serialize($settings)); if(empty($instances[$name.'.'.$key])){ $instances[$name.'.'.$key] = new $name($settings); return $instances[$name.'.'.$key]; }else{ return $instances[$name.'.'.$key]; } } function initialize(){ } public static function generateModel($model_name, $model_settings = array()){ if(!in_array($model_name, self::$_generated_models)){ self::$_generated_models[] = $model_name; $class_code = ' namespace GCore\Models; if(!class_exists("\GCore\Models\\'.$model_name.'", false)){ class '.$model_name.' extends \\'.get_called_class().' {'; foreach($model_settings as $setting => $value){ if(is_object($value)){ //$class_code .= 'var $'.$setting.' = '.unserialize(serialize($value)).';'."\n"; }else{ $class_code .= 'var $'.$setting.' = '.var_export($value, true).';'."\n"; } } $class_code .= ' } } '; eval($class_code); } } function setFields($fields = array()){ $this->table_fields = $fields; } function loadPKey(){ $cached = false; if(Base::getConfig('cache') >= 1 AND Base::getConfig('cache_dbinfo') >= 1){ $cache = Cache::getInstance('db_tables_info', array('expiration' => Base::getConfig('dbinfo_cache_expiry', 43200)), 'file'); $this->pkey = $cache->get($this->tablename.'.pkey'); if($this->pkey !== false){ $cached = true; } } if(!$cached){ $this->pkey = $this->dbo->getTablePrimary($this->tablename); } if(!$cached AND Base::getConfig('cache') >= 1 AND Base::getConfig('cache_dbinfo') >= 1){ $cache = Cache::getInstance('db_tables_info', array('expiration' => Base::getConfig('dbinfo_cache_expiry', 43200)), 'file'); $cache->set($this->tablename.'.pkey', $this->pkey); } } function loadFields(){ $cached = false; if(Base::getConfig('cache') >= 1 AND Base::getConfig('cache_dbinfo') >= 1){ $cache = Cache::getInstance('db_tables_info', array('expiration' => Base::getConfig('dbinfo_cache_expiry', 43200)), 'file'); $this->table_fields = $cache->get($this->tablename.'.columns'); if(!empty($this->table_fields)){ $cached = true; } } if(empty($this->table_fields)){ $this->table_fields = $this->dbo->getTableColumns($this->tablename); } if(!$cached AND Base::getConfig('cache') >= 1 AND Base::getConfig('cache_dbinfo') >= 1){ $cache = Cache::getInstance('db_tables_info', array('expiration' => Base::getConfig('dbinfo_cache_expiry', 43200)), 'file'); $cache->set($this->tablename.'.columns', $this->table_fields); } } function load($id = null){ $id = empty($id) ? $this->id : $id; $data = $this->find('first', array('conditions' => array($this->pkey => $id), 'recursive' => -1)); if(!empty($data)){ return $data; } return false; } private function _bind_models($type){ if(!empty($this->{$type})){ $this->{$type} = (array)$this->{$type}; //fix models relations foreach($this->{$type} as $k => $model){ //if this model should not be associated then continue if(!empty($this->allowed_models) AND !in_array($k, $this->allowed_models)){ continue; } $this->_fix_relation($type, $k, $model); } } } private function _fix_relation($type, $k, $model){ //only model name provided if(is_string($model)){ $alias = Base::getClassName($model); }else{ $alias = $k; } //make sure we don't have an endless loop of properties /*if(in_array($alias, $this->_parents_models)){ return; }*/ /*foreach($this->_parents_models as $_p_model){ if($_p_model->alias == $alias){ $this->{$alias} = &$_p_model; return; } }*/ if(in_array($alias, $this->chain_models)){ return; } //fix any quick relations if(is_numeric($k)){ $this->{$type}[$alias] = array('className' => $model); unset($this->{$type}[$k]); }elseif(!is_numeric($k) AND !is_array($model)){ $this->{$type}[$alias] = array('className' => $model); unset($this->{$type}[$k]); }elseif(!is_numeric($k) AND is_array($model) AND !empty($model['className'])){ //good to go $this->{$type}[$alias] = $model; }else{ unset($this->{$type}[$k]); } $this->associated_models[$alias] = $type; //bind models //make sure we don't have an endless loop of properties $a = $this->{$type}[$alias]['className']; //$a::$__parents_models[] = $this;//array_merge($this->_parents_models, (array)$this->alias); $a::$_chain_models = $this->chain_models; $a::$_chain_models[] = $alias; //add associated model as property $this->{$alias} = new $a(array('allowed_models' => $this->allowed_models)); $this->{$alias}->alias = $alias; //add foreign key value if(empty($this->{$type}[$alias]['foreignKey'])){ if($type == 'hasOne' OR $type == 'hasMany'){ $this->{$type}[$alias]['foreignKey'] = Str::uncamilize($this->name).'_'.$this->pkey; }else{ $this->{$type}[$alias]['foreignKey'] = Str::uncamilize($this->{$alias}->name).'_'.$this->{$alias}->pkey; } } } function bindModels($type, $models){ foreach($models as $k => $model){ $this->_fix_relation($type, $k, $model); } } function unbindModel($type, $model){ unset($this->{$type}[$model]); unset($this->{$model}); } private function _prepare_tablename($tablename, $as = false, $quote = true){ $tablename = $this->dbo->_prefixTable($tablename); if($quote){ $tablename = $this->dbo->quoteName($tablename); } //add the alias if($as !== false){ $alias = $this->dbo->quoteName($this->alias); $tablename .= ' AS '.$alias; } return $tablename; } private function _prepare_field($field, $as = false, $attach_alias = true, $quote = true){ $original_field = $field = trim($field); if(strpos($field, ':') === 0){ $field = substr($field, 1); $new_field = $field; goto _prepare_field_process_as; } if(strpos($field, '(') !== false){ //there is a function used, extract the field name inside //preg_match('/\(([^)]+)\)/', $field, $field_name); preg_match('/[(](\w+|\*)[)]/', $field, $field_name); if(!empty($field_name[1])){ $field = $field_name[1]; } } //sepcial field name, treat differently if($field == '*'){ $attach_alias = false; $quote = false; } $extracted_field = $field; if($attach_alias AND strpos($field, '.') === false){ //no model name used, add this model name $field = $this->alias.'.'.$field; }elseif(!$attach_alias AND strpos($field, '.') !== false){ $field = substr($field, strpos($field, '.') + 1); } $new_field = $field; //check if we have to quote the field name if($quote){ $new_field = $this->dbo->quoteName(str_replace('.', $this->dbo->quoteName('.'), $field)); } $new_field = str_replace($extracted_field, $new_field, $original_field); //add the alias _prepare_field_process_as: if($as !== false){ $new_field .= ' AS '; if(!empty($as)){ if(strpos($as, ':') === 0){ $as = substr($as, 1); $new_field .= $as; return $new_field; } if(strpos($as, '.') === false){ //no model name used, add this model name $as = $this->alias.'.'.$as; } $new_field .= $this->dbo->quoteName($as); }else{ $new_field .= $this->dbo->quoteName($field); } } return $new_field; } private function _prepare_field_string($string = ''){ if(strpos($string, ':') === 0){ $string = substr($string, 1); return $string; } preg_match_all('/[a-zA-Z][a-zA-Z0-9_.]*/', $string, $fields_names); if(!empty($fields_names[0])){ $fields_names = $fields_names[0]; $reserved = $this->dbo->get_reserved_words(); foreach($fields_names as $field_name){ if(!in_array(strtoupper($field_name), $reserved)){ $string = str_replace($field_name, $this->_prepare_field($field_name, false), $string); } } } return $string; } protected function _get_associated_values(&$fieldname, &$value, $newQuery = false, &$parent = null){ //check if we have an alias and there is no function if(!is_numeric($fieldname) AND strpos($fieldname, '(') === false AND strpos($fieldname, '.') !== false){ $chunks = explode('.', $fieldname); $alias = $chunks[0]; if($alias != $this-> alias){ //check if this alias is directly associated to the current model if(isset($this->associated_models[$alias]) AND $this->associated_models[$alias] == 'hasMany'){ //retrieve the foreign keys in associated hasMany models $this_pkey_values = $this->{$alias}->find('list', array('fields' => array($alias.'.'.$this->hasMany[$alias]['foreignKey']), 'conditions' => array($fieldname => $value))); $fieldname = $this->alias.'.'.$this->pkey; $value = $this_pkey_values; return true; }elseif(isset($this->associated_models[$alias]) AND $this->associated_models[$alias] != 'hasMany'){ //direct first level relationship, do nothing if($newQuery){ $fields = $this->alias.'.'.$parent->{$parent->associated_models[$this->alias]}[$this->alias]['foreignKey'];//($this->associated_models[$alias] == 'hasOne') ? array($alias.'.'.$this->{$this->associated_models[$alias]}[$alias]['foreignKey']) : array($this->alias.'.'.$this->{$this->associated_models[$alias]}[$alias]['foreignKey']); $this_pkey_values = $this->find('list', array('fields' => $fields, 'conditions' => array($fieldname => $value))); $fieldname = $parent->alias.'.'.$parent->pkey;//$this->alias.'.'.$this->{$this->associated_models[$alias]}[$alias]['foreignKey']; $value = $this_pkey_values; } return true; }else{ //check if its indirectly associated foreach($this->associated_models as $model_alias => $relation_type){ if(isset($this->{$model_alias})){ $found = $this->{$model_alias}->_get_associated_values($fieldname, $value, true, $this); if($found){ return true;//$this->_get_associated_values($fieldname, $value); } } } } } } return false; } function processConditions($conditions, $op = 'AND', $attach_alias = true){ $operators = array('AND', 'OR', 'NOT'); $parts = array(); $conditions = (array)$conditions; foreach($conditions as $k => $v){ $k = trim($k); //check the key type if(in_array($k, $operators)){ //this is a logical operator if(!is_array($v)){ $v = (array)$v; } if(!Arr::is_assoc($v) AND is_array($v[0])){ $inner_op_parts = array(); //e.g: 'OR' => array(array('title' => 'x'), array('title' => 'y')) foreach($v as $same_field_condition){ $inner_op_parts[] = '('.$this->processConditions($same_field_condition, $k, $attach_alias).')'; } $parts[] = '('.implode(" $k ", $inner_op_parts).')'; }else{ $parts[] = '('.$this->processConditions($v, $k, $attach_alias).')'; } }else{ $this->_get_associated_values($k, $v); if(is_array($v)){ //IN operation if(empty($v)){ $v = array(null); } $parts[] = $this->_prepare_field($k, false, $attach_alias).' IN ('.implode(', ', array_map(array($this->dbo, 'quote'), $v)).')'; }else if(is_null($v)){ $parts[] = $this->_prepare_field($k, false, $attach_alias).' IS NULL'; }else{ if(is_numeric($k)){ //custom query part //escape fields names $parts[] = $this->_prepare_field_string($v); }elseif(strpos($k, ' ') !== false){ //this is a field name with an operator $k_chunks = explode(' ', $k); $field_name = $k_chunks[0]; $k = str_replace($field_name, $this->_prepare_field($field_name, false, $attach_alias), $k); $parts[] = $k.' '.$this->dbo->quote($v); }else{ //this is a plain field name $parts[] = $this->_prepare_field($k, false, $attach_alias).' = '.$this->dbo->quote($v); } } } } if($op == 'NOT'){ return 'NOT ('.implode(' ', $parts).')'; }else{ return implode(" $op ", $parts); } } function processOrder($orders){ $orders = (array)$orders; $parts = array(); foreach($orders as $order){ $parts[] = $this->_prepare_field_string($order); } if(!empty($parts)){ return ' ORDER BY '.implode(', ', $parts); } } function processGroup($groups){ $groups = (array)$groups; $parts = array(); foreach($groups as $group){ $parts[] = $this->_prepare_field_string($group); } return ' GROUP BY '.implode(', ', $parts); } function processLimit($limit){ if(!empty($limit) AND is_numeric($limit)){ return ' LIMIT '.$limit;//$this->dbo->processor->limit($limit); } return ''; } function processOffset($offset){ if(!empty($offset) AND is_numeric($offset)){ return ' OFFSET '.$offset; } return ''; } function processPage($page){ $return = ''; if(!empty($page) AND is_numeric($page)){ $return .= ' LIMIT '.$this->page_limit; if(($page - 1) * $this->page_limit){ $return .= ' OFFSET '.(($page - 1) * $this->page_limit); } } return $return; } function processJoins($joins = array()){ if(!empty($joins) AND is_array($joins)){ $parts = array(); //loop through every join rule foreach($joins as $join){ if(!empty($join) AND is_array($join)){ if(empty($join['type'])){ $join['type'] = 'left'; } if(!empty($join['table']) AND !empty($join['alias']) AND !empty($join['conditions'])){ $_class_name = $join['className']; $className = new $_class_name(array('no_relations' => true)); $className->alias = $join['alias']; $parts[] = strtoupper($join['type']).' JOIN '.$className->_prepare_tablename($join['table'], true).' ON '.$this->processConditions($join['conditions']); } } } return implode(' ', $parts); } return false; } function processJoinsFields($joins = array()){ if(!empty($joins) AND is_array($joins)){ $joins_fields = array(); //loop through every join rule foreach($joins as $join){ $fields = array(); if(!empty($join) AND is_array($join)){ if(!empty($join['table']) AND !empty($join['alias']) AND !empty($join['conditions'])){ $_class_name = $join['className']; $className = new $_class_name(array('no_relations' => true)); $className->alias = $join['alias']; if(empty($join['fields'])){ $join['fields'] = array(); } $fields = $className->processFields($join['fields']); } } $joins_fields = array_merge($joins_fields, $fields); } return $joins_fields; } return array(); } function findModelFields($alias){ if($this->alias == $alias){ //return $this->table_fields; $alias_fields = $this->table_fields; foreach($alias_fields as $k => $alias_field){ $alias_fields[$k] = $alias.'.'.$alias_field; } return $alias_fields; }else if(in_array($alias, array_keys($this->associated_models))){ $alias_fields = $this->{$alias}->table_fields; foreach($alias_fields as $k => $alias_field){ $alias_fields[$k] = $alias.'.'.$alias_field; } return $alias_fields; }else{ foreach(array_keys($this->associated_models) as $associated_model){ $deep_fields = $this->{$associated_model}->findModelFields($alias); if(!empty($deep_fields)){ return $deep_fields; } } } return array(); } function processFields($fields = array()){ $returns = array(); $fields = (array)$fields; if(empty($fields)){ $fields = $this->table_fields; }else{ //check if we have a * field and extract its model's fields if(in_array('*', array_values($fields))){ $index = array_search('*', $fields, true); unset($fields[$index]); $fields = array_merge($this->table_fields, $fields); }else{ foreach(array_values($fields) as $field){ if(substr($field, -2, 2) == '.*'){ $index = array_search($field, $fields, true); $model_fields = $this->findModelFields(str_replace('.*', '', $field)); unset($fields[$index]); $fields = array_merge($fields, $model_fields); } } } /* if(in_array('*', array_values($fields))){ $index = array_search('*', $fields, true); unset($fields[$index]); $fields = array_merge($this->table_fields, $fields); }elseif(in_array($this->alias.'.*', array_values($fields))){ $index = array_search($this->alias.'.*', $fields, true); unset($fields[$index]); $fields = array_merge($this->table_fields, $fields); } foreach($this->associated_models as $alias => $type){ if(in_array($alias.'.*', array_values($fields))){ $index = array_search($alias.'.*', $fields, true); unset($fields[$index]); $alias_fields = $this->{$alias}->table_fields; foreach($alias_fields as $k => $alias_field){ $alias_fields[$k] = $alias.'.'.$alias_field; } $fields = array_merge($fields, $alias_fields); } } */ } foreach($fields as $k => $field){ if(!empty($field)){ if(!is_numeric($k)){ //we have an alias set $returns[] = $this->_prepare_field($k, $field); }else{ //no alias, pass the field name only $returns[] = $this->_prepare_field($field, ''); } } } return $returns; } function _find_hasOne(&$params){ if(!empty($this->hasOne)){ foreach($this->hasOne as $alias => $model_info){ if(in_array($alias, $params['_alias_used']) OR !isset($this->{$alias})){ continue; } //check contained models list if(empty($params['contain'])){ break; }else if(!in_array('*', $params['contain']) AND !in_array($alias, $params['contain'], true)){ continue; }else{ $current_contained_key = array_search($alias, $params['contain']); if(!empty($current_contained_key)){ unset($params['contain'][$current_contained_key]); } } $params['_alias_used'][] = $alias; //if the model class is loaded, inject new join $className = &$this->{$alias}; $new_join = array( 'table' => $className->tablename, 'alias' => $alias, 'type' => !empty($model_info['type']) ? $model_info['type'] : 'left', ); $new_join['className'] = $model_info['className']; $foreignKey = $model_info['foreignKey']; $new_join['conditions'] = !empty($model_info['join_conditions']) ? $model_info['join_conditions'] : array($this->alias.'.'.$this->pkey.' = '.$className->alias.'.'.$foreignKey); if(!empty($model_info['conditions'])){ $params['conditions'] = !isset($params['conditions']) ? array() : $params['conditions']; $params['conditions'] = array_merge((array)$params['conditions'], (array)$model_info['conditions']); } if(!empty($model_info['fields'])){ $new_join['fields'] = $model_info['fields']; } if(!empty($model_info['order'])){ $params['order'] = !isset($params['order']) ? array() : $params['order']; $params['order'] = array_merge((array)$params['order'], (array)$model_info['order']); } $params['group'] = !isset($params['group']) ? array() : $params['group']; //make sure that there is 1 record loaded for each primary model record and avoid duplicates in case the secondary model has more than 1 record (hasMany attached as hasOne) if(empty($params['group']) AND (bool)array_search($this->alias, $this->chain_models) === false){ $params['group'] = array_merge(array($this->alias.'.'.$this->pkey), (array)$params['group']); } $params['joins'][] = $new_join; $className->processRelations('find', array('hasOne', 'belongsTo'), $params); $this->_late_hasMany[] = $alias; } } } function _find_belongsTo(&$params){ if(!empty($this->belongsTo)){ foreach($this->belongsTo as $alias => $model_info){ if(in_array($alias, $params['_alias_used']) OR !isset($this->{$alias})){ continue; } //check contained models list if(empty($params['contain'])){ break; }else if(!in_array('*', $params['contain']) AND !in_array($alias, $params['contain'], true)){ continue; }else{ $current_contained_key = array_search($alias, $params['contain']); if(!empty($current_contained_key)){ unset($params['contain'][$current_contained_key]); } } $params['_alias_used'][] = $alias; //if the model class is loaded, inject new join $className = &$this->{$alias}; $new_join = array( 'table' => $className->tablename, 'alias' => $alias, 'type' => !empty($model_info['type']) ? $model_info['type'] : 'left', ); $new_join['className'] = $model_info['className']; $foreignKey = $model_info['foreignKey']; $new_join['conditions'] = !empty($model_info['join_conditions']) ? $model_info['join_conditions'] : array($this->alias.'.'.$foreignKey.' = '.$className->alias.'.'.$className->pkey); if(!empty($model_info['conditions'])){ $params['conditions'] = !isset($params['conditions']) ? array() : $params['conditions']; $params['conditions'] = array_merge((array)$params['conditions'], (array)$model_info['conditions']); } if(!empty($model_info['fields'])){ $new_join['fields'] = $model_info['fields']; } if(!empty($model_info['order'])){ $params['order'] = !isset($params['order']) ? array() : $params['order']; $params['order'] = array_merge((array)$params['order'], (array)$model_info['order']); } $params['joins'][] = $new_join; $className->processRelations('find', array('hasOne', 'belongsTo'), $params); $this->_late_hasMany[] = $alias; } } } function _find_hasMany(&$data, $params = array()){ if(!empty($this->hasMany)){ foreach($this->hasMany as $alias => $model_info){ if(in_array($alias, $params['_alias_used']) OR !isset($this->{$alias})){ continue; } //check contained models list if(empty($params['contain'])){ break; }else if(!in_array('*', $params['contain']) AND !in_array($alias, $params['contain'], true)){ continue; }else{ $current_contained_key = array_search($alias, $params['contain']); if(!empty($current_contained_key)){ unset($params['contain'][$current_contained_key]); } } $params['_alias_used'][] = $alias; //if the model class is loaded, inject new join $className = &$this->{$alias}; $foreignKey = $model_info['foreignKey']; //build the find $f_params = array(); $f_params['conditions'] = !empty($model_info['conditions']) ? (array)$model_info['conditions'] : array(); $f_params['fields'] = !empty($model_info['fields']) ? (array)$model_info['fields'] : array(); $f_params['order'] = !empty($model_info['order']) ? (array)$model_info['order'] : array(); if(!empty($model_info['limit'])){ $f_params['limit'] = (int)$model_info['limit']; } if(!empty($model_info['offset'])){ $f_params['offset'] = (int)$model_info['offset']; } if(!empty($model_info['group'])){ $f_params['group'] = $model_info['group']; } //extract the primary values of the data array $main_model_p_values = Arr::getVal($data, array('[n]', $this->alias, $this->pkey)); $f_params['conditions'] = array_merge(array($className->alias.'.'.$foreignKey => $main_model_p_values), (array)$f_params['conditions']); $f_params['contain'] = $params['contain']; //find all associated records $records = $className->find('all', $f_params); //loop through main model data and inject associated records foreach($data as $k => $row){ //get primary key value of this row $p_value = Arr::getVal($row, array($this->alias, $this->pkey)); foreach($records as $rk => $record){ //get the foreign key value of associated record and compare to primary value of this row $f_value = Arr::getVal($record, array($className->alias, $foreignKey)); if($f_value == $p_value){ if(!empty($model_info['sub_group'])){ $data[$k][$className->alias][] = $record; }else{ //inject the record of he associated record foreach($record as $sub_model => $sub_model_data){ if(empty($data[$k][$sub_model]) OR !is_array($data[$k][$sub_model])){ $data[$k][$sub_model] = array(); } if(is_array($sub_model_data) AND !Arr::is_assoc($sub_model_data)){ $data[$k][$sub_model] = array_merge($data[$k][$sub_model], $sub_model_data); }else{ //if we are grouping by the foreign key field only then the data should be inserted directly if(!empty($model_info['single']) OR (!empty($model_info['group']) AND (($foreignKey == $model_info['group']) OR (is_array($model_info['group']) AND count($model_info['group']) == 1 AND array_pop($model_info['group']) == $foreignKey)))){ $data[$k][$sub_model] = $sub_model_data; }else{ $data[$k][$sub_model][] = $sub_model_data; } } } } } } if(!empty($model_info['single'])){ $data[$k][$alias][$foreignKey] = $p_value; } } } } } function processRelations($calltype = 'find', $types, &$params = array(), $o_params = array()){ //find and fix any setup relations foreach($types as $type){ if(!empty($this->{$type})){ //$this->_fix_relation_type($type); $fn = '_'.$calltype.'_'.$type; switch($calltype){ case 'find': $this->$fn($params, $o_params); break; case 'save': $this->$fn($params); break; case 'delete': $ids = $this->get_ids_of_conditions($params); $this->$fn($ids); break; } } } } function get_ids_of_conditions($conditions){ static $results; if(!isset($results)){ $results = array(); } $data_key = md5(serialize($conditions)); if(array_key_exists($data_key, $results)){ return $results[$data_key]; }else{ $data = $this->find('all', array( 'fields' => $this->alias.'.'.$this->pkey, 'conditions' => $conditions, 'recursive' => -1, )); if(!empty($data)){ $results[$data_key] = Arr::getVal($data, array('[n]', $this->alias, $this->pkey)); }else{ $results[$data_key] = array(); } return $results[$data_key]; } } function initializeFind($type, &$params){ if(!empty($params['initialized'])){ return; } if(!isset($params['_alias_used'])){ $params['_alias_used'] = array(); } $params['_alias_used'][] = $this->alias; //reset fields for count type if($type == 'count'){ $params['fields'] = array('COUNT('.(!empty($this->pkey) ? $this->pkey : '*').')' => 'count'); $params['page'] = $this->page = 0; } if($type == 'first'){ $params['limit'] = 1; } //fix fields list if(empty($params['fields'])){ $params['fields'] = !empty($params['fields']) ? $params['fields'] : array(); } //fix recursive $un_recursive = 0; if(isset($params['recursive']) AND ($params['recursive'] == -1)){ $un_recursive = 1; }else if(isset($this->recursive) AND ($this->recursive == -1)){ $un_recursive = 1; } $params['recursive'] = $un_recursive ? -1 : 1; //fix conditions $params['conditions'] = empty($params['conditions']) ? array() : $params['conditions']; $params['conditions'] = array_merge($this->conditions, $params['conditions']); //fix order $params['order'] = empty($params['order']) ? array() : (array)$params['order']; if(!empty($this->order_by)){ $params['order'] = array_merge((array)$params['order'], (array)$this->order_by); } //fix group $params['group'] = empty($params['group']) ? array() : (array)$params['group']; if(!empty($this->group)){ $params['group'] = array_merge((array)$params['group'], (array)$this->group); } $params['page'] = !empty($params['page']) ? $params['page'] : $this->page; $params['limit'] = !empty($params['limit']) ? $params['limit'] : $this->limit; $params['offset'] = !empty($params['offset']) ? $params['offset'] : $this->offset; if(!empty($this->contain)){ $params['contain'] = empty($params['contain']) ? $this->contain : array_merge($this->contain, (array)$params['contain']); }else{ $params['contain'] = empty($params['contain']) ? array('*') : (array)$params['contain']; } $params['initialized'] = true; } function beforeFind($type, &$params){ } function find($type = 'all', $params = array()){ $this->initializeFind($type, $params); $this->beforeFind($type, $params); $sql = 'SELECT '; //get the fields list of this model $fields = $this->processFields($params['fields']); if(!empty($params['extra_fields'])){ $fields = array_merge($fields, $this->processFields($params['extra_fields'])); } //check relationships and modify $params if($params['recursive'] != -1){ $this->processRelations('find', array('hasOne', 'belongsTo'), $params); if($type == 'count'){ $this->_late_hasMany = array(); } foreach($this->_late_hasMany as $alias){ $this->{$alias}->beforeFind($type, $params); } } //check if there are joins to get their fields, we will not load any joins fields if we have a fields list, you must ensure that the necessary fields from joins tables are loaded. if(empty($params['fields']) AND !empty($params['joins'])){ $joins_fields = $this->processJoinsFields($params['joins']); $fields = array_merge($fields, $joins_fields); } //build select fields list $sql .= implode(', ', $fields); if(!empty($params['from'])){ $sql .= ' FROM ('.$params['from']['query'].') AS '.(!empty($params['from']['alias']) ? $params['from']['alias'] : $this->alias); }else{ $sql .= ' FROM '.$this->_prepare_tablename($this->tablename, true); } $sql_extensions = array(); //get joins if any set if(!empty($params['joins'])){ $sql_extensions['joins'] = ' '.$this->processJoins($params['joins']); } //get conditions if there are any if(!empty($params['conditions'])){ $sql_extensions['where'] = ' WHERE '.$this->processConditions($params['conditions']); } //get group if its set if(!empty($params['group'])){ $sql_extensions['group'] = $this->processGroup($params['group']); } //get having if there are any if(!empty($params['having'])){ $sql_extensions['having'] = ' HAVING '.$this->processConditions($params['having']); } //get order if its set $sql_extensions['order'] = $this->processOrder(array_unique($params['order'])); //get limit if its set $sql_extensions['limit'] = $this->processLimit($params['limit']); //get offset if its set $sql_extensions['offset'] = $this->processOffset($params['offset']); //get page if its set $sql_extensions['page'] = $this->processPage($params['page']); if(!empty($sql_extensions['page'])){ unset($sql_extensions['limit']); unset($sql_extensions['offset']); } $this->fixTypeSql($type, $sql_extensions); //append the extensions to the main SQL $sql .= implode('', $sql_extensions); if($type == 'query'){ return $sql; } //run the query and return the results $qdata = $this->dbo->loadAssocList($sql); //fix dots in aliases $qdata = $this->fix_columns_aliases($qdata); //check relationships and modify $params if($params['recursive'] != -1 AND !empty($qdata) AND is_array($qdata) AND $type != 'count' AND !empty($params['contain'])){ $this->processRelations('find', array('hasMany'), $qdata, $params); foreach($this->_late_hasMany as $alias){ $this->{$alias}->processRelations('find', array('hasMany'), $qdata, $params); } } //process the afterFind callback $this->afterFind($type, $qdata); //do some find types specific final processing $qdata = $this->process_find_type($type, $qdata, $params); //finalize $this->finalizeFind($type, $qdata); //return data return $qdata; } function afterFind($type, &$qdata){ } function finalizeFind($type, &$qdata){ //reset the used aliases counter for the next find $this->_alias_used = array(); } function fixTypeSql($type, &$sql){ switch ($type){ case 'count': if(isset($sql['order'])){ unset($sql['order']); } if(isset($sql['limit'])){ unset($sql['limit']); } if(isset($sql['offset'])){ unset($sql['offset']); } break; case 'first': $sql['limit'] = $this->processLimit(array('limit' => 1)); break; } } function fix_columns_aliases($data){ foreach($data as $k => $assoc){ $data[$k] = $this->_extract_model_data($assoc); } return $data; } function process_find_type($type = 'all', $data = array(), $params = array()){ switch ($type){ case 'all': //$data = $this->fix_columns_aliases($data); break; case 'list': $new = array(); //$data = $this->fix_columns_aliases($data); foreach($data as $k => $assoc){ $assoc = array_values($assoc[$this->alias]); $count = count($assoc); if($count == 1){ $new[$assoc[0]] = $assoc[0]; }elseif($count > 1){ $new[$assoc[0]] = $assoc[1]; } } $data = $new; break; case 'first': //$data = $this->fix_columns_aliases($data); $data = !empty($data) ? array_shift($data) : array(); break; case 'threaded': //$data = $this->fix_columns_aliases($data); if(!empty($data) AND !empty($this->parent_id)){ $data = $this->build_threaded_list($data); break; } break; case 'flat': //$data = $this->fix_columns_aliases($data); if(!empty($data) AND !empty($this->parent_id)){ $data = $this->build_flat_list($data); break; } break; case 'count': //$data = $this->fix_columns_aliases($data); if(empty($params['group'])){ $data = !empty($data) ? array_shift($data) : array(); if(!empty($data)){ $data = $data[$this->alias]['count']; break; } }else{ $data = count($data); break; } $data = 0; break; } return $data; } function build_threaded_list(array &$elements, $parentId = 0, $_depth = 0){ $branch = array(); foreach($elements as $k => $element){ if($element[$this->alias][$this->parent_id] == $parentId){ $element[$this->alias]['_depth'] = $_depth; $children = $this->build_threaded_list($elements, $element[$this->alias][$this->pkey], ($_depth + 1)); if($children){ $element[$this->alias]['children'] = $children; } $branch[$k] = $element; unset($elements[$k]); } } return $branch; } function build_flat_list(array &$elements, $parentId = 0, $_depth = 0){ $branch = array(); foreach($elements as $k => $element){ if($element[$this->alias][$this->parent_id] == $parentId){ $element[$this->alias]['_depth'] = $_depth; $branch[] = $element; $children = $this->build_flat_list($elements, $element[$this->alias][$this->pkey], ($_depth + 1)); if($children){ $branch = array_merge($branch, $children); } } } return $branch; } private function _extract_model_data($assoc){ if(!empty($assoc)){ $new_assoc = array(); $models = array(); foreach($assoc as $k => $v){ if(strpos($k, '.') !== false){ $k_chunks = explode('.', $k); $model_name = $k_chunks[0]; $field_name = $k_chunks[1]; $new_assoc[$model_name][$field_name] = $v; }else{ $new_assoc[$k] = $v; } } return $new_assoc; } return $assoc; } function delete($id = null, $params = array()){ if(!empty($id)){ return $this->deleteAll(array($this->pkey => $id), $params); } return false; } function beforeDelete(&$conditions, $params = array()){ } function deleteAll($conditions, $params = array()){ if(!empty($conditions)){ if(!array_key_exists('callbacks', $params) OR ($params['callbacks'] !== false)){ $continue = $this->beforeDelete($conditions, $params); if($continue === false){ return null; } } //check relationships $deleted_ids = array(); if(!isset($params['recursive']) OR $params['recursive'] != -1){ $this->processRelations('delete', array('hasOne', 'belongsTo', 'hasMany'), $conditions); } //create main query $sql = 'DELETE FROM '.$this->_prepare_tablename($this->tablename).' WHERE '.$this->processConditions($conditions, 'AND', false); //close $sql = $this->dbo->_close($sql); //run query $result = $this->dbo->exec($sql); $this->dbo->_log($sql); if($result === false){ return false; } $this->afterDelete($conditions); return $result; } return false; } function afterDelete($conditions = array()){ } private function _delete_hasOne($ids){ if(!empty($this->hasOne)){ foreach($this->hasOne as $alias => $model_info){ if(!empty($model_info['delete_on_delete']) AND (bool)$model_info['delete_on_delete'] === true AND isset($this->{$alias})){ //if no ids provided and no conditions then don't run this empty delete statement if(empty($model_info['conditions']) AND empty($ids)){ continue; } //if the model class is loaded, inject new join $className = &$this->{$alias}; $foreignKey = $model_info['foreignKey']; $delete_conditions = empty($model_info['conditions']) ? array($foreignKey => $ids) : array_merge(array($foreignKey => $ids), $model_info['conditions']); $className->deleteAll($delete_conditions); } } } } private function _delete_belongsTo($ids){ if(!empty($this->belongsTo)){ foreach($this->belongsTo as $alias => $model_info){ if(isset($this->{$alias})){ //if the model class is loaded, inject new join $className = &$this->{$alias}; $foreignKey = $model_info['foreignKey']; //check counter Cache if(!empty($model_info['counterCache']) AND !empty($ids)){ $counter_field = $model_info['counterCache']; $parents = $this->find('list', array('recursive' => -1, 'fields' => array($this->pkey, $foreignKey), 'conditions' => array($this->pkey => $ids))); $parents = array_values(array_unique(array_filter($parents))); foreach($parents as $parent){ $className->id = $parent; $className->updateField($counter_field, '- 1'); } } //check cache fields if(!empty($model_info['cache']) AND !empty($ids)){ //get affected parents $parents = $this->find('list', array('recursive' => -1, 'fields' => array($this->pkey, $foreignKey), 'conditions' => array($this->pkey => $ids))); $parents = array_values(array_unique(array_filter($parents))); foreach($model_info['cache'] as $field => $info){ if(in_array($field, $className->table_fields)){ if(!empty($parents)){ foreach($parents as $parent){ //select records not matching the delete condition and update the cache $default_conditions = array($foreignKey => $parent, 'NOT' => array($this->pkey => $ids)); $info['conditions'] = !empty($info['conditions']) ? array_merge($info['conditions'], $default_conditions) : $default_conditions; $cache_result = $this->find('first', $info); if(!empty($cache_result)){ $className->updateAll(array($field => $cache_result[$this->alias][$field]), array($className->pkey => $parent), array_merge(array('modified' => false, 'cleanlist' => array($field), 'validate' => false, 'callbacks' => false, 'recursive' => -1))); } } } } } } } } } } private function _delete_hasMany($ids){ if(!empty($this->hasMany)){ foreach($this->hasMany as $alias => $model_info){ if(!empty($model_info['delete_on_delete']) AND (bool)$model_info['delete_on_delete'] === true AND isset($this->{$alias})){ //if no ids provided and no conditions then don't run this empty delete statement if(empty($model_info['conditions']) AND empty($ids)){ continue; } //if the model class is loaded, inject new join $className = &$this->{$alias}; $foreignKey = $model_info['foreignKey']; $delete_conditions = empty($model_info['conditions']) ? array($foreignKey => $ids) : array_merge(array($foreignKey => $ids), $model_info['conditions']); $className->deleteAll($delete_conditions); } } } } function saveAll($data = array(), $params = array()){ if(!empty($data) AND is_array($data) AND !Arr::is_assoc($data)){//array_values($data) === $data){ //numerically indexed list of records $this->ids = array(); foreach($data as $k => $record){ $this->save($record, $params); $this->ids[] = $this->id; } return true; } return false; } function updateAll($data = array(), $conditions = array(), $params = array()){ if(!empty($data) AND !empty($conditions)){ return $this->save($data, array_merge($params, array( 'conditions' => $conditions ))); } return false; } function field($fieldname, $conditions = array()){ if(!empty($fieldname)){ $fieldname = strpos($fieldname, '.') !== false ? $fieldname : $this->alias.'.'.$fieldname; $data = $this->find('first', array('fields' => array($fieldname), 'conditions' => $conditions, 'recursive' => -1)); $result = Arr::getVal($data, explode('.', $fieldname)); if(!empty($result)){ return $result; } return false; } return false; } function saveField($fieldname, $fieldvalue){ if(!empty($fieldname) AND !empty($this->pkey) AND !empty($this->id) AND in_array($fieldname, $this->table_fields)){ return $this->save(array( $this->pkey => $this->id, $fieldname => $fieldvalue ), array('whitelist' => array($this->pkey, $fieldname), 'modified' => false, 'validate' => false, 'callbacks' => false, 'recursive' => -1)); } return false; } function updateField($fieldname, $updateValue = '+ 1', $params = array()){ if(!empty($fieldname) AND !empty($this->id)){ return $this->updateAll(array($fieldname => $this->dbo->quoteName($fieldname).' '.$updateValue), array($this->pkey => $this->id), array_merge(array('modified' => false, 'cleanlist' => array($fieldname), 'validate' => false, 'callbacks' => false), $params)); } return false; } function validate($data = array(), $mode = 'create'){ $data = empty($data) ? $this->data : $data; $return = true; if(!empty($data) AND !empty($this->validate)){ foreach($this->validate as $fld => $rules){ foreach($rules as $rule => $params){ if($rule == 'message'){ continue; } if(!empty($params['on']) AND $params['on'] != $mode){ continue; } if($rule == 'function' AND !empty($params['name']) AND is_string($params['name'])){ $valid = $this->{$params['name']}(); goto check_valid; } //check other rules $value = Arr::getVal($data, array($fld)); if(!empty($params[$rule])){ //rule with extra param, example: regex $arg = $params[$rule]; if(!empty($params['data'])){ $arg = Arr::getVal($data, array($params[$rule])); } $valid = Validate::$rule($value, $arg); }else{ $valid = Validate::$rule($value); } check_valid: if(!$valid){ $this->errors[$fld][] = !empty($params['message']) ? $params['message'] : (!empty($rules['message']) ? $rules['message'] : ''); $return = false; } } if(array_key_exists($fld, $this->errors)){ $this->errors[$fld] = array_unique($this->errors[$fld]); } } } $this->errors = array_filter($this->errors); return $return; } function initializeSave(&$data, &$params){ } function beforeSave(&$data, &$params, $mode){ } /* params: new: force create new conditions: conditions for updating cleanlist: escaped/quoted fields values, don't quote again whitelist: limit save or update to those fields only blacklist: don't save or update the fields here callbacks: if false then it will disable the callbacks: beforeSave, afterSave..etc */ function save($data = array(), $params = array()){ $insert = false; $update = false; $this->initializeSave($data, $params); //check the data format $models_data = array(); if($data !== array_values($data) AND !empty($data[$this->alias]) AND is_array($data[$this->alias])){ $models_data = $data; $data = $data[$this->alias]; } $this->data = $data; //check new or update record if((!empty($this->pkey) AND !empty($data[$this->pkey]) AND empty($params['new'])) OR (!empty($params['conditions']) AND is_array($params['conditions']))){ $mode = 'update'; }else{ $mode = 'create'; } //if validation is not disabled then validate the data if(!empty($params['validate']) AND !empty($this->validate)){ if(!$this->validate($data, $mode)){ return false; } } //process the beforeSave() if(!array_key_exists('callbacks', $params) OR ($params['callbacks'] !== false)){ $keep_on = $this->beforeSave($data, $params, $mode); if($keep_on === false){ return $keep_on; } } //set the query type if($mode == 'update'){ $tablename = $this->_prepare_tablename($this->tablename, true); $sql = 'UPDATE '.$tablename.' '; $update = true; if(empty($data['modified']) AND (!isset($params['modified']) OR $params['modified'] !== false)){ $data['modified'] = date('Y-m-d H:i:s', time()); if(!empty($params['whitelist'])){ $params['whitelist'][] = 'modified'; } } }else{ $tablename = $this->_prepare_tablename($this->tablename, false); $sql = 'INSERT INTO '.$tablename.' '; $insert = true; if(empty($data['created']) AND (!isset($params['created']) OR $params['created'] !== false)){ $data['created'] = date('Y-m-d H:i:s', time()); if(!empty($params['whitelist'])){ $params['whitelist'][] = 'created'; } } } //get the fields to use in the query if(empty($params['cleanlist'])){ $cleanlist = array(); }else{ $cleanlist = (array)$params['cleanlist']; } $query_fields = array(); foreach($data as $k => $v){ if(!in_array($k, $this->table_fields)){ unset($data[$k]); continue; } //check if we have a black fields list to check if(!empty($params['blacklist']) AND is_array($params['blacklist'])){ if(in_array($k, $params['blacklist'])){ unset($data[$k]); continue; } } //check if we have a white fields list to check if(!empty($params['whitelist']) AND is_array($params['whitelist'])){ if(!in_array($k, $params['whitelist'])){ unset($data[$k]); continue; } } $query_fields[] = $k; } //if there are no eligible fields, exit if(empty($query_fields)){ return false; } //quote columns names for security $query_fields_q = array_map(array($this->dbo, 'quoteName'), $query_fields); //build the fields section in an insert query if($insert){ $sql .= '('.implode(', ', $query_fields_q).')'; $sql .= ' values '; $sql .= '(:'.implode(', :', $query_fields).')'; $this->id = !empty($data[$this->pkey]) ? $data[$this->pkey] : $this->id; } //build the fields section in an update query if($update){ $sql .= 'SET '; $chunks = array(); foreach($query_fields as $k => $query_field){ if($query_field != $this->pkey){ //check if we have a clean fields list to check if(in_array($query_field, $cleanlist)){ $chunks[] = $query_fields_q[$k].' = '.$data[$query_field]; unset($data[$query_field]); }else{ $chunks[] = $query_fields_q[$k].' = :'.$query_field; } } } $sql .= implode(', ', $chunks); if(!empty($params['conditions']) AND is_array($params['conditions'])){ $sql .= ' WHERE '.$this->processConditions($params['conditions']); }else{ $sql .= ' WHERE '.$this->dbo->quoteName($this->pkey).' = :'.$this->pkey; $this->id = $data[$this->pkey]; } } //close $sql = $this->dbo->_close($sql); //replace table prefix //$sql = $this->dbo->_prefixTable($sql); //run query //$query = $this->dbo->prepare($sql); if(!$this->dbo->execute_query($sql, $data)){ return false; } $this->dbo->_log($sql, $data); if($insert){ $last_insert = $this->dbo->lastInsertId(); $this->id = !empty($last_insert) ? $last_insert : $this->id; $this->created = true; }else{ $this->created = false; } $this->data[$this->pkey] = $this->id; //check relationships if(!isset($params['recursive']) OR $params['recursive'] != -1){ $this->processRelations('save', array('hasOne', 'belongsTo', 'hasMany'), $models_data); } //after save if(!array_key_exists('callbacks', $params) OR ($params['callbacks'] !== false)){ $this->afterSave(); } return true; } function afterSave(){ } private function _save_hasOne($data){ if(!empty($this->hasOne)){ foreach($this->hasOne as $alias => $model_info){ if(!empty($model_info['save_on_save']) AND (bool)$model_info['save_on_save'] === true AND isset($this->{$alias})){ //if the model class is loaded, inject new join $className = &$this->{$alias}; $foreignKey = $model_info['foreignKey']; //if there is no data for this model and force save is disabled then don't try to save if(empty($data[$alias]) AND isset($model_info['force_save']) AND (bool)$model_info['force_save'] === false){ continue; } //check if this is a new record or an existing one if($this->created === true){ //new record, save a new associated record $data[$alias][$foreignKey] = $this->id; //if the pkey is the same as fkey then make sure it doesn't make an UPDATE statement $save_params = array(); if($className->pkey == $foreignKey){ $save_params = array('new' => true); } $className->save($data[$alias], $save_params); }else{ //update existing associated record if(!empty($data[$alias])){ //unset the associated record pkey if its set, we should run the update based on the foreign key value only //NO, if the primary key exists then update, if not then insert a new record, same method used in saveMany $exists = false; if(!empty($data[$alias][$className->pkey])){ //unset($data[$alias][$className->pkey]); $fkey = $className->field($foreignKey, array($className->pkey => $data[$alias][$className->pkey])); $exists = ($fkey == $this->id) ? true : false; }else{ //$exists = $className->field($foreignKey, array($foreignKey => $this->id)); } if($exists){ //$className->updateAll($data[$alias], array($foreignKey => $this->id)); $className->updateAll($data[$alias], array($className->pkey => $data[$alias][$className->pkey])); }else{ $data[$alias][$foreignKey] = $this->id; $className->save($data[$alias]); } } } } } } } private function _save_belongsTo($data){ if(!empty($this->belongsTo)){ foreach($this->belongsTo as $alias => $model_info){ if(isset($this->{$alias})){ //if the model class is loaded, inject new join $className = &$this->{$alias}; $foreignKey = $model_info['foreignKey']; if($this->created === true){ //check counter Cache if(!empty($model_info['counterCache'])){ $counter_field = $model_info['counterCache']; $className->updateAll(array($counter_field => $this->dbo->quoteName($counter_field).' + 1'), array($className->pkey => $this->data[$foreignKey]), array('cleanlist' => array($counter_field), 'modified' => false)); } } //check cache fields if(!empty($model_info['cache'])){ foreach($model_info['cache'] as $field => $info){ if(in_array($field, $className->table_fields)){ $info['conditions'] = !empty($info['conditions']) ? array_merge($info['conditions'], array($foreignKey => $this->data[$foreignKey])) : array($foreignKey => $this->data[$foreignKey]); $cache_result = $this->find('first', $info); if(!empty($cache_result)){ if(!empty($this->id)){ $className->updateAll(array($field => $cache_result[$this->alias][$field]), array($className->pkey => $this->data[$foreignKey]), array_merge(array('modified' => false, 'cleanlist' => array($field), 'validate' => false, 'callbacks' => false, 'recursive' => -1))); } } //check if this is an updated record and lets do cache processing for the old parent which this model belongs to if(!$this->created AND !empty($info['xforeignKey']) AND !empty($this->data[$info['xforeignKey']])){ $xforeignKey = $info['xforeignKey']; if($this->data[$xforeignKey] != $this->data[$foreignKey]){ $info['conditions'] = !empty($info['conditions']) ? array_merge($info['conditions'], array($foreignKey => $this->data[$xforeignKey])) : array($foreignKey => $this->data[$xforeignKey]); $cache_result = $this->find('first', $info); if(!empty($cache_result)){ if(!empty($this->id)){ $className->updateAll(array($field => $cache_result[$this->alias][$field]), array($className->pkey => $this->data[$xforeignKey]), array_merge(array('modified' => false, 'cleanlist' => array($field), 'validate' => false, 'callbacks' => false, 'recursive' => -1))); } } } } } } } } } } } private function _save_hasMany($data){ if(!empty($this->hasMany)){ foreach($this->hasMany as $alias => $model_info){ if(!empty($model_info['save_on_save']) AND (bool)$model_info['save_on_save'] === true AND isset($this->{$alias})){ //if the model class is loaded, inject new join $className = &$this->{$alias}; $foreignKey = $model_info['foreignKey']; //check if this is a new record or an existing one if($this->created === true){ //new record, update and save associated data if(!empty($data) AND !empty($data[$alias])){ foreach($data[$alias] as $k => $_d){ $data[$alias][$k][$foreignKey] = $this->id; } $className->saveAll($data[$alias]); } }else{ //update existing associated record if(!empty($data) AND isset($data[$alias])){ //delete non existent records based on p key values if(!empty($model_info['delete_non_existent']) AND (bool)$model_info['delete_non_existent'] === true){ $existing_keys = Arr::getVal($data, array($alias, '[n]', $className->pkey)); $existing_keys = array_unique($existing_keys); $existing_keys = array_filter($existing_keys); $delete_conditions = empty($model_info['conditions']) ? array($foreignKey => $this->id, 'NOT' => array($className->pkey => $existing_keys)) : array_merge(array($foreignKey => $this->id, 'NOT' => array($className->pkey => $existing_keys)), $model_info['conditions']); $className->deleteAll($delete_conditions); } if(!empty($data[$alias])){ //fix any foreign key issues and save foreach($data[$alias] as $k => $_d){ $data[$alias][$k][$foreignKey] = $this->id; } $className->saveAll($data[$alias]); } } } } } } } }