diff --git a/application/config/datamapper.php b/application/config/datamapper.php index 71b8b89..974fa8c 100644 --- a/application/config/datamapper.php +++ b/application/config/datamapper.php @@ -1,33 +1,22 @@ -'; -$config['error_suffix'] = '

'; -$config['created_field'] = 'created'; -$config['updated_field'] = 'updated'; -$config['local_time'] = FALSE; -$config['unix_timestamp'] = FALSE; -$config['timestamp_format'] = ''; -$config['lang_file_format'] = 'model_${model}'; -$config['field_label_lang_format'] = '${model}_${field}'; -$config['auto_transaction'] = FALSE; -$config['auto_populate_has_many'] = FALSE; -$config['auto_populate_has_one'] = FALSE; -$config['all_array_uses_ids'] = FALSE; -// set to FALSE to use the same DB instance across the board (breaks subqueries) -// Set to any acceptable parameters to $CI->database() to override the default. -$config['db_params'] = ''; -// Uncomment to enable the production cache -// $config['production_cache'] = 'datamapper/cache'; -$config['extensions_path'] = 'datamapper'; -$config['extensions'] = array(); - -/* End of file datamapper.php */ +'; +$config['error_suffix'] = '

'; +$config['created_field'] = 'created'; +$config['updated_field'] = 'updated'; +$config['local_time'] = FALSE; +$config['unix_timestamp'] = FALSE; +$config['auto_transaction'] = FALSE; +$config['auto_populate_has_many'] = FALSE; +$config['auto_populate_has_one'] = FALSE; + +/* End of file datamapper.php */ /* Location: ./application/config/datamapper.php */ \ No newline at end of file diff --git a/application/helpers/inflector_helper.php b/application/helpers/inflector_helper.php index 25d093e..9eb04e2 100644 --- a/application/helpers/inflector_helper.php +++ b/application/helpers/inflector_helper.php @@ -1,236 +1,215 @@ - 4 OR in_array($end3, array('ses', 'hes', 'oes'))) - { - $str = substr($str, 0, -2); - } - elseif (in_array($end2, array('da', 'ia', 'la'))) - { - $str = substr($str, 0, -1).'um'; - } - elseif (in_array($end2, array('bi', 'ei', 'gi', 'li', 'mi', 'pi'))) - { - $str = substr($str, 0, -1).'us'; - } - else - { - if ($end1 == 's' && $end2 != 'us' && $end2 != 'ss') - { - $str = substr($str, 0, -1); - } - } - - return $str; - } -} - -// -------------------------------------------------------------------- - -/** -* Plural -* -* Takes a singular word and makes it plural (improved by stensi) -* -* @access public -* @param string -* @param bool -* @return str -*/ -if ( ! function_exists('plural')) -{ - function plural($str, $force = FALSE) - { - $str = strtolower(trim($str)); - $end3 = substr($str, -3); - $end2 = substr($str, -2); - $end1 = substr($str, -1); - - if ($end3 == 'eau') - { - $str .= 'x'; - } - elseif ($end3 == 'man') - { - $str = substr($str, 0, -2).'en'; - } - elseif (in_array($end3, array('dum', 'ium', 'lum'))) - { - $str = substr($str, 0, -2).'a'; - } - elseif (strlen($str) > 4 && in_array($end3, array('bus', 'eus', 'gus', 'lus', 'mus', 'pus'))) - { - $str = substr($str, 0, -2).'i'; - } - elseif ($end3 == 'ife') - { - $str = substr($str, 0, -2).'ves'; - } - elseif ($end1 == 'f') - { - $str = substr($str, 0, -1).'ves'; - } - elseif ($end1 == 'y') - { - if(preg_match('#[aeiou]y#i', $end2)) - { - // ays, oys, etc. - $str = $str . 's'; - } - else - { - $str = substr($str, 0, -1).'ies'; - } - } - elseif ($end1 == 'o') - { - if(preg_match('#[aeiou]o#i', $end2)) - { - // oos, etc. - $str = $str . 's'; - } - else - { - $str .= 'es'; - } - } - elseif ($end1 == 'x' || in_array($end2, array('ss', 'ch', 'sh')) ) - { - $str .= 'es'; - } - elseif ($end1 == 's') - { - if ($force == TRUE) - { - $str .= 'es'; - } - } - else - { - $str .= 's'; - } - - return $str; - } -} - -// -------------------------------------------------------------------- - -/** - * Camelize - * - * Takes multiple words separated by spaces or underscores and camelizes them - * - * @access public - * @param string - * @return str - */ -if ( ! function_exists('camelize')) -{ - function camelize($str) - { - $str = 'x'.strtolower(trim($str)); - $str = ucwords(preg_replace('/[\s_]+/', ' ', $str)); - return substr(str_replace(' ', '', $str), 1); - } -} - -// -------------------------------------------------------------------- - -/** - * Underscore - * - * Takes multiple words separated by spaces and underscores them - * - * @access public - * @param string - * @return str - */ -if ( ! function_exists('underscore')) -{ - function underscore($str) - { - return preg_replace('/[\s]+/', '_', strtolower(trim($str))); - } -} - -// -------------------------------------------------------------------- - -/** - * Humanize - * - * Takes multiple words separated by underscores and changes them to spaces - * - * @access public - * @param string - * @return str - */ -if ( ! function_exists('humanize')) -{ - function humanize($str) - { - return ucwords(preg_replace('/[_]+/', ' ', strtolower(trim($str)))); - } -} - -/* End of file inflector_helper.php */ + 4 OR in_array($end3, array('ses', 'hes', 'oes'))) + { + $str = substr($str, 0, -2); + } + elseif (in_array($end2, array('da', 'ia', 'la'))) + { + $str = substr($str, 0, -1).'um'; + } + elseif (in_array($end2, array('bi', 'ei', 'gi', 'li', 'mi', 'pi'))) + { + $str = substr($str, 0, -1).'us'; + } + else + { + if ($end1 == 's') + { + $str = substr($str, 0, -1); + } + } + + return $str; + } +} + +// -------------------------------------------------------------------- + +/** +* Plural +* +* Takes a singular word and makes it plural (improved by stensi) +* +* @access public +* @param string +* @param bool +* @return str +*/ +if ( ! function_exists('plural')) +{ + function plural($str, $force = FALSE) + { + $str = strtolower(trim($str)); + $end3 = substr($str, -3); + $end1 = substr($str, -1); + + if ($end3 == 'eau') + { + $str .= 'x'; + } + elseif ($end3 == 'man') + { + $str = substr($str, 0, -2).'en'; + } + elseif (in_array($end3, array('dum', 'ium', 'lum'))) + { + $str = substr($str, 0, -2).'a'; + } + elseif (strlen($str) > 4 && in_array($end3, array('bus', 'eus', 'gus', 'lus', 'mus', 'pus'))) + { + $str = substr($str, 0, -2).'i'; + } + elseif ($end3 == 'ife') + { + $str = substr($str, 0, -2).'ves'; + } + elseif ($end1 == 'f') + { + $str = substr($str, 0, -1).'ves'; + } + elseif ($end1 == 'y') + { + $str = substr($str, 0, -1).'ies'; + } + elseif (in_array($end1, array('h', 'o', 'x'))) + { + $str .= 'es'; + } + elseif ($end1 == 's') + { + if ($force == TRUE) + { + $str .= 'es'; + } + } + else + { + $str .= 's'; + } + + return $str; + } +} + +// -------------------------------------------------------------------- + +/** + * Camelize + * + * Takes multiple words separated by spaces or underscores and camelizes them + * + * @access public + * @param string + * @return str + */ +if ( ! function_exists('camelize')) +{ + function camelize($str) + { + $str = 'x'.strtolower(trim($str)); + $str = ucwords(preg_replace('/[\s_]+/', ' ', $str)); + return substr(str_replace(' ', '', $str), 1); + } +} + +// -------------------------------------------------------------------- + +/** + * Underscore + * + * Takes multiple words separated by spaces and underscores them + * + * @access public + * @param string + * @return str + */ +if ( ! function_exists('underscore')) +{ + function underscore($str) + { + return preg_replace('/[\s]+/', '_', strtolower(trim($str))); + } +} + +// -------------------------------------------------------------------- + +/** + * Humanize + * + * Takes multiple words separated by underscores and changes them to spaces + * + * @access public + * @param string + * @return str + */ +if ( ! function_exists('humanize')) +{ + function humanize($str) + { + return ucwords(preg_replace('/[_]+/', ' ', strtolower(trim($str)))); + } +} + +/* End of file inflector_helper.php */ /* Location: ./application/helpers/inflector_helper.php */ \ No newline at end of file diff --git a/application/language/english/datamapper_lang.php b/application/language/english/datamapper_lang.php index e89103e..563695d 100644 --- a/application/language/english/datamapper_lang.php +++ b/application/language/english/datamapper_lang.php @@ -1,25 +1,21 @@ - SQL functions: - * @method DataMapper where_field_field_func() where_field_func($field, string $function_name, mixed $args,...) Limits results based on a SQL function. - * @method DataMapper or_where_field_field_func() or_where_field_func($field, string $function_name, mixed $args,...) Limits results based on a SQL function, via OR. - * @method DataMapper where_in_field_field_func() where_in_field_func($field, string $function_name, mixed $args,...) Limits results by comparing a SQL function to a range of values. - * @method DataMapper or_where_in_field_field_func() or_where_in_field_func($field, string $function_name, mixed $args,...) Limits results by comparing a SQL function to a range of values. - * @method DataMapper where_not_in_field_field_func() where_not_in_field_func($field, string $function_name, string $field) Limits results by comparing a SQL function to a range of values. - * @method DataMapper or_where_not_in_field_field_func() or_where_not_in_field_func($field, string $function_name, mixed $args,...) Limits results by comparing a SQL function to a range of values. - * @method DataMapper like_field_field_func() like_field_func($field, string $function_name, mixed $args,...) Limits results by matching a SQL function to a value. - * @method DataMapper or_like_field_field_func() like_field_func($field, string $function_name, mixed $args,...) Limits results by matching a SQL function to a value. - * @method DataMapper not_like_field_field_func() like_field_func($field, string $function_name, mixed $args,...) Limits results by matching a SQL function to a value. - * @method DataMapper or_not_like_field_field_func() like_field_func($field, string $function_name, mixed $args,...) Limits results by matching a SQL function to a value. - * @method DataMapper ilike_field_field_func() like_field_func($field, string $function_name, mixed $args,...) Limits results by matching a SQL function to a value (case insensitive). - * @method DataMapper or_ilike_field_field_func() like_field_func($field, string $function_name, mixed $args,...) Limits results by matching a SQL function to a value (case insensitive). - * @method DataMapper not_ilike_field_field_func() like_field_func($field, string $function_name, mixed $args,...) Limits results by matching a SQL function to a value (case insensitive). - * @method DataMapper or_not_ilike_field_field_func() like_field_func($field, string $function_name, mixed $args,...) Limits results by matching a SQL function to a value (case insensitive). - * @method DataMapper group_by_field_field_func() group_by_field_func($field, string $function_name, mixed $args,...) Groups the query by a SQL function. - * @method DataMapper having_field_field_func() having_field_func($field, string $function_name, mixed $args,...) Groups the querying using a HAVING clause. - * @method DataMapper or_having_field_field_func() having_field_func($field, string $function_name, mixed $args,...) Groups the querying using a HAVING clause, via OR. - * @method DataMapper order_by_field_field_func() order_by_field_func($field, string $function_name, mixed $args,...) Orders the query based on a SQL function. - * - * Subqueries: - * @method DataMapper select_subquery() select_subquery(DataMapper $subquery, string $alias) Selects the result of a function. Alias is required. - * @method DataMapper where_subquery() where_subquery(mixed $subquery_or_field, mixed $value_or_subquery) Limits results based on a subquery. - * @method DataMapper or_where_subquery() or_where_subquery(mixed $subquery_or_field, mixed $value_or_subquery) Limits results based on a subquery, via OR. - * @method DataMapper where_in_subquery() where_in_subquery(mixed $subquery_or_field, mixed $values_or_subquery) Limits results by comparing a subquery to a range of values. - * @method DataMapper or_where_in_subquery() or_where_in_subquery(mixed $subquery_or_field, mixed $values_or_subquery) Limits results by comparing a subquery to a range of values. - * @method DataMapper where_not_in_subquery() where_not_in_subquery(mixed $subquery_or_field, string $field, mixed $values_or_subquery) Limits results by comparing a subquery to a range of values. - * @method DataMapper or_where_not_in_subquery() or_where_not_in_subquery(mixed $subquery_or_field, mixed $values_or_subquery) Limits results by comparing a subquery to a range of values. - * @method DataMapper like_subquery() like_subquery(DataMapper $subquery, string $value, string $match = 'both') Limits results by matching a subquery to a value. - * @method DataMapper or_like_subquery() like_subquery(DataMapper $subquery, string $value, string $match = 'both') Limits results by matching a subquery to a value. - * @method DataMapper not_like_subquery() like_subquery(DataMapper $subquery, string $value, string $match = 'both') Limits results by matching a subquery to a value. - * @method DataMapper or_not_like_subquery() like_subquery(DataMapper $subquery, string $value, string $match = 'both') Limits results by matching a subquery to a value. - * @method DataMapper ilike_subquery() like_subquery(DataMapper $subquery, string $value, string $match = 'both') Limits results by matching a subquery to a value (case insensitive). - * @method DataMapper or_ilike_subquery() like_subquery(DataMapper $subquery, string $value, string $match = 'both') Limits results by matching a subquery to a value (case insensitive). - * @method DataMapper not_ilike_subquery() like_subquery(DataMapper $subquery, string $value, string $match = 'both') Limits results by matching a subquery to a value (case insensitive). - * @method DataMapper or_not_ilike_subquery() like_subquery(DataMapper $subquery, string $value, string $match = 'both') Limits results by matching a subquery to a value (case insensitive). - * @method DataMapper having_subquery() having_subquery(string $field, DataMapper $subquery) Groups the querying using a HAVING clause. - * @method DataMapper or_having_subquery() having_subquery(string $field, DataMapper $subquery) Groups the querying using a HAVING clause, via OR. - * @method DataMapper order_by_subquery() order_by_subquery(DataMapper $subquery, string $direction) Orders the query based on a subquery. - * - * Related Subqueries: - * @method DataMapper where_related_subquery() where_related_subquery(mixed $related_model, string $related_field, DataMapper $subquery) Limits results based on a subquery. - * @method DataMapper or_where_related_subquery() or_where_related_subquery(mixed $related_model, string $related_field, DataMapper $subquery) Limits results based on a subquery, via OR. - * @method DataMapper where_in_related_subquery() where_in_related_subquery(mixed $related_model, string $related_field, DataMapper $subquery) Limits results by comparing a subquery to a range of values. - * @method DataMapper or_where_in_related_subquery() or_where_in_related_subquery(mixed $related_model, string $related_field, DataMapper $subquery) Limits results by comparing a subquery to a range of values. - * @method DataMapper where_not_in_related_subquery() where_not_in_related_subquery(mixed $related_model, string $related_field, DataMapper $subquery) Limits results by comparing a subquery to a range of values. - * @method DataMapper or_where_not_in_related_subquery() or_where_not_in_related_subquery(mixed $related_model, string $related_field, DataMapper $subquery) Limits results by comparing a subquery to a range of values. - * @method DataMapper having_related_subquery() having_related_subquery(mixed $related_model, string $related_field, DataMapper $subquery) Groups the querying using a HAVING clause. - * @method DataMapper or_having_related_subquery() having_related_subquery(mixed $related_model, string $related_field, DataMapper $subquery) Groups the querying using a HAVING clause, via OR. - * - * Array Extension: - * @method array to_array() to_array($fields = '') NEEDS ARRAY EXTENSION. Converts this object into an associative array. @link DMZ_Array::to_array - * @method array all_to_array() all_to_array($fields = '') NEEDS ARRAY EXTENSION. Converts the all array into an associative array. @link DMZ_Array::all_to_array - * @method array|bool from_array() from_array($data, $fields = '', $save = FALSE) NEEDS ARRAY EXTENSION. Converts $this->all into an associative array. @link DMZ_Array::all_to_array - * - * CSV Extension - * @method bool csv_export() csv_export($filename, $fields = '', $include_header = TRUE) NEEDS CSV EXTENSION. Exports this object as a CSV file. - * @method array csv_import() csv_import($filename, $fields = '', $header_row = TRUE, $callback = NULL) NEEDS CSV EXTENSION. Imports a CSV file into this object. - * - * JSON Extension: - * @method string to_json() to_json($fields = '', $pretty_print = FALSE) NEEDS JSON EXTENSION. Converts this object into a JSON string. - * @method string all_to_json() all_to_json($fields = '', $pretty_print = FALSE) NEEDS JSON EXTENSION. Converts the all array into a JSON string. - * @method bool from_json() from_json($json, $fields = '') NEEDS JSON EXTENSION. Imports the values from a JSON string into this object. - * @method void set_json_content_type() set_json_content_type() NEEDS JSON EXTENSION. Sets the content type header to Content-Type: application/json. - * - * SimpleCache Extension: - * @method DataMapper get_cached() get_cached($limit = '', $offset = '') NEEDS SIMPLECACHE EXTENSION. Enables cacheable queries. - * @method DataMapper clear_cache() get_cached($segment,...) NEEDS SIMPLECACHE EXTENSION. Clears a cache for the specfied segment. - * - * Translate Extension: - * - * Nestedsets Extension: - * - */ -class DataMapper implements IteratorAggregate { - - /** - * Stores the shared configuration - * @var array - */ - static $config = array(); - /** - * Stores settings that are common across a specific Model - * @var array - */ - static $common = array(DMZ_CLASSNAMES_KEY => array()); - /** - * Stores global extensions - * @var array - */ - static $global_extensions = array(); - /** - * Used to override unset default properties. - * @var array - */ - static $_dmz_config_defaults = array( - 'timestamp_format' => 'Y-m-d H:i:s O', - 'created_field' => 'created', - 'updated_field' => 'updated', - 'extensions_path' => 'datamapper', - 'field_label_lang_format' => '${model}_${field}', - ); - - /** - * Contains any errors that occur during validation, saving, or other - * database access. - * @var DM_Error_Object - */ - public $error; - /** - * Used to keep track of the original values from the database, to - * prevent unecessarily changing fields. - * @var object - */ - public $stored; - /** - * DB Table Prefix - * @var string - */ - public $prefix = ''; - /** - * DB Join Table Prefix - * @var string - */ - public $join_prefix = ''; - /** - * The name of the table for this model (may be automatically generated - * from the classname). - * @var string - */ - public $table = ''; - /** - * The singular name for this model (may be automatically generated from - * the classname). - * @var string - */ - public $model = ''; - /** - * Can be used to override the default database behavior. - * @var mixed - */ - public $db_params = ''; - /** - * Prefix string used when reporting errors. - * @var string - */ - public $error_prefix = ''; - /** - * Suffic string used when reporting errors. - * @var string - */ - public $error_suffix = ''; - /** - * Custom name for the automatic timestamp saved with new objects. - * Defaults to 'created'. - * @var string - */ - public $created_field = ''; - /** - * Custom name for the automatic timestamp saved when an object changes. - * Defaults to 'updated'. - * @var string - */ - public $updated_field = ''; - /** - * If TRUE, automatically wrap every save and delete in a transaction. - * @var bool - */ - public $auto_transaction = FALSE; - /** - * If TRUE, has_many relationships are automatically loaded when accessed. - * Not recommended in most situations. - * @var bool - */ - public $auto_populate_has_many = FALSE; - /** - * If TRUE, has_one relationships are automatically loaded when accessed. - * Not recommended in some situations. - * @var bool - */ - public $auto_populate_has_one = FALSE; - /** - * Enables the old method of storing the all array using an object's ID. - * @var bool - */ - public $all_array_uses_ids = FALSE; - /** - * The result of validate is stored here. - * @var bool - */ - public $valid = FALSE; - /** - * If TRUE, the created/updated fields are stored using local time. - * If FALSE (the default), they are stored using UTC - * @var bool - */ - public $local_time = FALSE; - /** - * If TRUE, the created/updated fields are stored as a unix timestamp, - * as opposed to a formatted string. - * Defaults to FALSE. - * @var bool - */ - public $unix_timestamp = FALSE; - /** - * Set to a date format to override the default format of - * 'Y-m-d H:i:s O' - * @var string - */ - public $timestamp_format = ''; - /** - * Contains the database fields for this object. - * ** Automatically configured ** - * @var array - */ - public $fields = array(); - /** - * Set to a string to use when autoloading lang files. - * Can contain two magic values: ${model} and ${table}. - * These are automatically - * replaced when looking up the language file. - * Defaults to model_${model} - * @var string - */ - public $lang_file_format = ''; - /** - * Set to a string to use when looking up field labels. Can contain three - * magic values: ${model}, ${table}, and ${field}. These are automatically - * replaced when looking up the language file. - * Defaults to ${model}_${field} - * @var string - */ - public $field_label_lang_format = ''; - /** - * Contains the result of the last query. - * @var array - */ - public $all = array(); - /** - * Semi-private field used to track the parent model/id if there is one. - * @var array - */ - public $parent = array(); - /** - * Contains the validation rules, label, and get_rules for each field. - * @var array - */ - public $validation = array(); - /** - * Contains any related objects of which this model is related one or more times. - * @var array - */ - public $has_many = array(); - /** - * Contains any related objects of which this model is singularly related. - * @var array - */ - public $has_one = array(); - /** - * Used to enable or disable the production cache. - * This should really only be set in the global configuration. - * @var bool - */ - public $production_cache = FALSE; - /** - * Used to determine where to look for extensions. - * This should really only be set in the global configuration. - * @var string - */ - public $extensions_path = ''; - /** - * If set to an array of names, this will automatically load the - * specified extensions for this model. - * @var mixed - */ - public $extensions = NULL; - /** - * If a query returns more than the number of rows specified here, - * then it will be automatically freed after a get. - * @var int - */ - public $free_result_threshold = 100; - /** - * This can be specified as an array of fields to sort by if no other - * sorting or selection has occurred. - * @var mixed - */ - public $default_order_by = NULL; - - // tracks whether or not the object has already been validated - protected $_validated = FALSE; - // Tracks the columns that need to be instantiated after a GET - protected $_instantiations = NULL; - // Tracks get_rules, matches, and intval rules, to spped up _to_object - protected $_field_tracking = NULL; - // used to track related queries in deep relationships. - protected $_query_related = array(); - // If true before a related get(), any extra fields on the join table will be added. - protected $_include_join_fields = FALSE; - // If true before a save, this will force the next save to be new. - protected $_force_save_as_new = FALSE; - // If true, the next where statement will not be prefixed with an AND or OR. - protected $_where_group_started = FALSE; - - /** - * Constructors (both PHP4 and PHP5 style, to stay compatible) - * - * Initialize DataMapper. - * @param int $id if provided, load in the object specified by that ID. - */ - public function __construct($id = NULL) - { - return $this->DataMapper($id); - } - - public function DataMapper($id = NULL) - { - $this->_dmz_assign_libraries(); - - $this_class = strtolower(get_class($this)); - $is_dmz = $this_class == 'datamapper'; - - if($is_dmz) - { - $this->_load_languages(); - - $this->_load_helpers(); - } - - // this is to ensure that singular is only called once per model - if(isset(DataMapper::$common[DMZ_CLASSNAMES_KEY][$this_class])) { - $common_key = DataMapper::$common[DMZ_CLASSNAMES_KEY][$this_class]; - } else { - DataMapper::$common[DMZ_CLASSNAMES_KEY][$this_class] = $common_key = singular($this_class); - } - - // Determine model name - if (empty($this->model)) - { - $this->model = $common_key; - } - - // Load stored config settings by reference - foreach (DataMapper::$config as $config_key => &$config_value) - { - // Only if they're not already set - if (empty($this->{$config_key})) - { - $this->{$config_key} =& $config_value; - } - } - - // Load model settings if not in common storage - if ( ! isset(DataMapper::$common[$common_key])) - { - // If model is 'datamapper' then this is the initial autoload by CodeIgniter - if ($is_dmz) - { - // Load config settings - $this->config->load('datamapper', TRUE, TRUE); - - // Get and store config settings - DataMapper::$config = $this->config->item('datamapper'); - - // now double check that all required config values were set - foreach(DataMapper::$_dmz_config_defaults as $config_key => $config_value) - { - if(empty(DataMapper::$config[$config_key])) - { - DataMapper::$config[$config_key] = $config_value; - } - } - - DataMapper::_load_extensions(DataMapper::$global_extensions, DataMapper::$config['extensions']); - unset(DataMapper::$config['extensions']); - - return; - } - - // load language file, if requested and it exists - if(!empty($this->lang_file_format)) - { - $lang_file = str_replace(array('${model}', '${table}'), array($this->model, $this->table), $this->lang_file_format); - $deft_lang = $this->config->item('language'); - $idiom = ($deft_lang == '') ? 'english' : $deft_lang; - if(file_exists(APPPATH.'language/'.$idiom.'/'.$lang_file.'_lang'.EXT)) - { - $this->lang->load($lang_file, $idiom); - } - } - - $loaded_from_cache = FALSE; - - // Load in the production cache for this model, if it exists - if( ! empty(DataMapper::$config['production_cache'])) - { - // check if it's a fully qualified path first - if (!is_dir($cache_folder = DataMapper::$config['production_cache'])) - { - // if not, it's relative to the application path - $cache_folder = APPPATH . DataMapper::$config['production_cache']; - } - if(file_exists($cache_folder) && is_dir($cache_folder) && is_writeable($cache_folder)) - { - $cache_file = $cache_folder . '/' . $common_key . EXT; - if(file_exists($cache_file)) - { - include($cache_file); - if(isset($cache)) - { - DataMapper::$common[$common_key] =& $cache; - unset($cache); - - // allow subclasses to add initializations - if(method_exists($this, 'post_model_init')) - { - $this->post_model_init(TRUE); - } - - // Load extensions (they are not cacheable) - $this->_initiate_local_extensions($common_key); - - $loaded_from_cache = TRUE; - } - } - } - } - - if(! $loaded_from_cache) - { - - // Determine table name - if (empty($this->table)) - { - $this->table = plural(get_class($this)); - } - - // Add prefix to table - $this->table = $this->prefix . $this->table; - - $this->_field_tracking = array( - 'get_rules' => array(), - 'matches' => array(), - 'intval' => array('id') - ); - - // Convert validation into associative array by field name - $associative_validation = array(); - - foreach ($this->validation as $name => $validation) - { - if(is_string($name)) { - $validation['field'] = $name; - } else { - $name = $validation['field']; - } - - // clean up possibly missing fields - if( ! isset($validation['rules'])) - { - $validation['rules'] = array(); - } - - // Populate associative validation array - $associative_validation[$name] = $validation; - - if (!empty($validation['get_rules'])) - { - $this->_field_tracking['get_rules'][] = $name; - } - - // Check if there is a "matches" validation rule - if (isset($validation['rules']['matches'])) - { - $this->_field_tracking['matches'][$name] = $validation['rules']['matches']; - } - } - - // set up id column, if not set - if(!isset($associative_validation['id'])) - { - // label is set below, to prevent caching language-based labels - $associative_validation['id'] = array( - 'field' => 'id', - 'rules' => array('integer') - ); - } - - $this->validation = $associative_validation; - - // Force all other has_one ITFKs to integers on get - foreach($this->has_one as $related => $rel_props) - { - $field = $related . '_id'; - if( in_array($field, $this->fields) && - ( ! isset($this->validation[$field]) || // does not have a validation key or... - ! isset($this->validation[$field]['get_rules'])) && // a get_rules key... - ( ! isset($this->validation[$related]) || // nor does the related have a validation key or... - ! isset($this->validation[$related]['get_rules'])) ) // a get_rules key - { - // assume an int - $this->_field_tracking['intval'][] = $field; - } - } - - // Get and store the table's field names and meta data - $fields = $this->db->field_data($this->table); - - // Store only the field names and ensure validation list includes all fields - foreach ($fields as $field) - { - // Populate fields array - $this->fields[] = $field->name; - - // Add validation if current field has none - if ( ! isset($this->validation[$field->name])) - { - // label is set below, to prevent caching language-based labels - $this->validation[$field->name] = array('field' => $field->name, 'rules' => array()); - } - } - - // convert simple has_one and has_many arrays into more advanced ones - foreach(array('has_one', 'has_many') as $arr) - { - foreach ($this->{$arr} as $related_field => $rel_props) - { - // process the relationship - $this->_relationship($arr, $rel_props, $related_field); - } - } - - // allow subclasses to add initializations - if(method_exists($this, 'post_model_init')) - { - $this->post_model_init(FALSE); - } - - // Store common model settings - foreach (array('table', 'fields', 'validation', - 'has_one', 'has_many', '_field_tracking') as $item) - { - DataMapper::$common[$common_key][$item] = $this->{$item}; - } - - // store the item to the production cache - $this->production_cache(); - - // Load extensions last, so they aren't cached. - $this->_initiate_local_extensions($common_key); - } - - // Finally, localize the labels here (because they shouldn't be cached - // This also sets any missing labels. - $validation =& DataMapper::$common[$common_key]['validation']; - foreach($validation as $field => &$val) - { - // Localize label if necessary - $val['label'] = $this->localize_label($field, - isset($val['label']) ? - $val['label'] : - FALSE); - } - unset($validation); - } - - // Load stored common model settings by reference - foreach(DataMapper::$common[$common_key] as $key => &$value) - { - $this->{$key} =& $value; - } - - // Clear object properties to set at default values - $this->clear(); - - if( ! empty($id) && is_numeric($id)) - { - $this->get_by_id(intval($id)); - } - } - - // -------------------------------------------------------------------- - - /** - * Reloads in the configuration data for a model. This is mainly - * used to handle language changes. Only this instance and new instances - * will see the changes. - */ - public function reinitialize_model() - { - // this is to ensure that singular is only called once per model - if(isset(DataMapper::$common[DMZ_CLASSNAMES_KEY][$this_class])) { - $common_key = DataMapper::$common[DMZ_CLASSNAMES_KEY][$this_class]; - } else { - DataMapper::$common[DMZ_CLASSNAMES_KEY][$this_class] = $common_key = singular($this_class); - } - unset(DataMapper::$common[$common_key]); - $model = get_class($this); - new $model(); // re-initialze - - // Load stored common model settings by reference - foreach(DataMapper::$common[$common_key] as $key => &$value) - { - $this->{$key} =& $value; - } - } - - // -------------------------------------------------------------------- - - /** - * Autoload - * - * Autoloads object classes that are used with DataMapper. - * This method will look in any model directories available to CI. - * - * Note: - * It is important that they are autoloaded as loading them manually with - * CodeIgniter's loader class will cause DataMapper's __get and __set functions - * to not function. - * - * @param string $class Name of class to load. - */ - public static function autoload($class) - { - $CI =& get_instance(); - - // Don't attempt to autoload CI_ , EE_, or custom prefixed classes - if (in_array(substr($class, 0, 3), array('CI_', 'EE_')) OR strpos($class, $CI->config->item('subclass_prefix')) === 0) - { - return; - } - - // Prepare class - $class = strtolower($class); - - // Prepare path - if (isset($CI->load->_ci_model_paths) && is_array($CI->load->_ci_model_paths)) - { - // use CI 2.0 loader's model paths - $paths = $CI->load->_ci_model_paths; - } - else - { - // search only the applications models folder - $paths[] = APPPATH; - } - - foreach ($paths as $path) - { - // Prepare file - $file = $path . 'models/' . $class . EXT; - - // Check if file exists, require_once if it does - if (file_exists($file)) - { - require_once($file); - break; - } - } - - // if class not loaded, do a recursive search of model paths for the class - if (! class_exists($class)) - { - foreach($paths as $path) - { - $found = DataMapper::recursive_require_once($class, $path . 'models'); - if($found) - { - break; - } - } - } - } - - // -------------------------------------------------------------------- - - /** - * Recursive Require Once - * - * Recursively searches the path for the class, require_once if found. - * - * @param string $class Name of class to look for - * @param string $path Current path to search - */ - protected static function recursive_require_once($class, $path) - { - $found = FALSE; - if(is_dir($path)) - { - $handle = opendir($path); - if ($handle) - { - while (FALSE !== ($dir = readdir($handle))) - { - // If dir does not contain a dot - if (strpos($dir, '.') === FALSE) - { - // Prepare recursive path - $recursive_path = $path . '/' . $dir; - - // Prepare file - $file = $recursive_path . '/' . $class . EXT; - - // Check if file exists, require_once if it does - if (file_exists($file)) - { - require_once($file); - $found = TRUE; - - break; - } - else if (is_dir($recursive_path)) - { - // Do a recursive search of the path for the class - DataMapper::recursive_require_once($class, $recursive_path); - } - } - } - - closedir($handle); - } - } - return $found; - } - - // -------------------------------------------------------------------- - - /** - * Loads in any extensions used by this class or globally. - * - * @param array $extensions List of extensions to add to. - * @param array $name List of new extensions to load. - */ - protected static function _load_extensions(&$extensions, $names) - { - $CI =& get_instance(); - $class_prefixes = array( - 0 => 'DMZ_', - 1 => 'DataMapper_', - 2 => $CI->config->item('subclass_prefix'), - 3 => 'CI_' - ); - foreach($names as $name => $options) - { - if( ! is_string($name)) - { - $name = $options; - $options = NULL; - } - // only load an extension if it wasn't already loaded in this context - if(isset($extensions[$name])) - { - return; - } - - if( ! isset($extensions['_methods'])) - { - $extensions['_methods'] = array(); - } - - // determine the file name and class name - if(strpos($name, '/') === FALSE) - { - $file = APPPATH . DataMapper::$config['extensions_path'] . '/' . $name . EXT; - $ext = $name; - } - else - { - $file = APPPATH . $name . EXT; - $ext = array_pop(explode('/', $name)); - } - - if(!file_exists($file)) - { - show_error('DataMapper Error: loading extension ' . $name . ': File not found.'); - } - - // load class - include_once($file); - - // Allow for DMZ_Extension, DataMapper_Extension, etc. - foreach($class_prefixes as $index => $prefix) - { - if(class_exists($prefix.$ext)) - { - if($index == 2) // "MY_" - { - // Load in the library this class is based on - $CI->load->library($ext); - } - $ext = $prefix.$ext; - break; - } - } - if(!class_exists($ext)) - { - show_error("DataMapper Error: Unable to find a class for extension $name."); - } - // create class - if(is_null($options)) - { - $o = new $ext(); - } - else - { - $o = new $ext($options); - } - $extensions[$name] = $o; - - // figure out which methods can be called on this class. - $methods = get_class_methods($ext); - foreach($methods as $m) - { - // do not load private methods or methods already loaded. - if($m[0] !== '_' && - is_callable(array($o, $m)) && - ! isset($extensions['_methods'][$m]) - ) { - // store this method. - $extensions['_methods'][$m] = $name; - } - } - } - } - - // -------------------------------------------------------------------- - - /** - * Loads the extensions that are local to this model. - * @param string $common_key Shared key to save extenions to. - */ - private function _initiate_local_extensions($common_key) - { - if(!empty($this->extensions)) - { - $extensions = $this->extensions; - $this->extensions = array(); - DataMapper::_load_extensions($this->extensions, $extensions); - } - else - { - // ensure an empty array - $this->extensions = array('_methods' => array()); - } - // bind to the shared key, for dynamic loading - DataMapper::$common[$common_key]['extensions'] =& $this->extensions; - } - - // -------------------------------------------------------------------- - - /** - * Dynamically load an extension when needed. - * @param object $name Name of the extension (or array of extensions). - * @param array $options Options for the extension - * @param boolean $local If TRUE, only loads the extension into this object - */ - public function load_extension($name, $options = NULL, $local = FALSE) - { - if( ! is_array($name)) - { - if( ! is_null($options)) - { - $name = array($name => $options); - } - else - { - $name = array($name); - } - } - // called individually to ensure that the array is modified directly - // (and not copied instead) - if($local) - { - DataMapper::_load_extensions($this->extensions, $name); - } - else - { - DataMapper::_load_extensions(DataMapper::$global_extensions, $name); - } - - } - - // -------------------------------------------------------------------- - - - /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * * - * Magic methods * - * * - * The following are methods to override the default PHP behaviour. * - * * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - - // -------------------------------------------------------------------- - - /** - * Magic Get - * - * Returns the value of the named property. - * If named property is a related item, instantiate it first. - * - * This method also instantiates the DB object and the form_validation - * objects as necessary - * - * @ignore - * @param string $name Name of property to look for - * @return mixed - */ - public function __get($name) - { - // We dynamically get DB when needed, and create a copy. - // This allows multiple queries to be generated at the same time. - if($name == 'db') - { - $CI =& get_instance(); - if($this->db_params === FALSE) - { - $this->db =& $CI->db; - } - else - { - if($this->db_params == '' || $this->db_params === TRUE) - { - // ensure the shared DB is disconnected, even if the app exits uncleanly - if(!isset($CI->db->_has_shutdown_hook)) - { - register_shutdown_function(array($CI->db, 'close')); - $CI->db->_has_shutdown_hook = TRUE; - } - // clone, so we don't create additional connections to the DB - $this->db = clone($CI->db); - $this->db->_reset_select(); - } - else - { - // connecting to a different database, so we *must* create additional copies. - // It is up to the developer to close the connection! - $this->db = $CI->load->database($this->db_params, TRUE, TRUE); - } - // these items are shared (for debugging) - if(isset($CI->db)) - { - $this->db->queries =& $CI->db->queries; - $this->db->query_times =& $CI->db->query_times; - } - } - // ensure the created DB is disconnected, even if the app exits uncleanly - if(!isset($this->db->_has_shutdown_hook)) - { - register_shutdown_function(array($this->db, 'close')); - $this->db->_has_shutdown_hook = TRUE; - } - return $this->db; - } - - // Special case to get form_validation when first accessed - if($name == 'form_validation') - { - if ( ! isset($this->form_validation) ) - { - $CI =& get_instance(); - if( ! isset($CI->form_validation)) - { - $CI->load->library('form_validation'); - $this->lang->load('form_validation'); - unset($CI->load->_ci_classes['form_validation']); - } - $this->form_validation = $CI->form_validation; - } - return $this->form_validation; - } - - $has_many = isset($this->has_many[$name]); - $has_one = isset($this->has_one[$name]); - - // If named property is a "has many" or "has one" related item - if ($has_many || $has_one) - { - $related_properties = $has_many ? $this->has_many[$name] : $this->has_one[$name]; - // Instantiate it before accessing - $class = $related_properties['class']; - $this->{$name} = new $class(); - - // Store parent data - $this->{$name}->parent = array('model' => $related_properties['other_field'], 'id' => $this->id); - - // Check if Auto Populate for "has many" or "has one" is on - // (but only if this object exists in the DB, and we aren't instantiating) - if ($this->exists() && - ($has_many && $this->auto_populate_has_many) || ($has_one && $this->auto_populate_has_one)) - { - $this->{$name}->get(); - } - - return $this->{$name}; - } - - $name_single = singular($name); - if($name_single !== $name) { - // possibly return single form of name - $test = $this->{$name_single}; - if(is_object($test)) { - return $test; - } - } - - return NULL; - } - - // -------------------------------------------------------------------- - - /** - * Used several places to temporarily override the auto_populate setting - * @ignore - * @param string $related Related Name - * @return DataMapper|NULL - */ - private function &_get_without_auto_populating($related) - { - $b_many = $this->auto_populate_has_many; - $b_one = $this->auto_populate_has_one; - $this->auto_populate_has_many = FALSE; - $this->auto_populate_has_one = FALSE; - $ret =& $this->{$related}; - $this->auto_populate_has_many = $b_many; - $this->auto_populate_has_one = $b_one; - return $ret; - } - - // -------------------------------------------------------------------- - - /** - * Magic Call - * - * Calls special methods, or extension methods. - * - * @ignore - * @param string $method Method name - * @param array $arguments Arguments to method - * @return mixed - */ - public function __call($method, $arguments) - { - - // List of watched method names - // NOTE: order matters: make sure more specific items are listed before - // less specific items - $watched_methods = array( - 'save_', 'delete_', - 'get_by_related_', 'get_by_related', 'get_by_', - '_related_subquery', '_subquery', - '_related_', '_related', - '_join_field', - '_field_func', '_func' - ); - - foreach ($watched_methods as $watched_method) - { - // See if called method is a watched method - if (strpos($method, $watched_method) !== FALSE) - { - $pieces = explode($watched_method, $method); - if ( ! empty($pieces[0]) && ! empty($pieces[1])) - { - // Watched method is in the middle - return $this->{'_' . trim($watched_method, '_')}($pieces[0], array_merge(array($pieces[1]), $arguments)); - } - else - { - // Watched method is a prefix or suffix - return $this->{'_' . trim($watched_method, '_')}(str_replace($watched_method, '', $method), $arguments); - } - } - } - - // attempt to call an extension - $ext = NULL; - if($this->_extension_method_exists($method, 'local')) - { - $name = $this->extensions['_methods'][$method]; - $ext = $this->extensions[$name]; - } - else if($this->_extension_method_exists($method, 'global')) - { - $name = DataMapper::$global_extensions['_methods'][$method]; - $ext = DataMapper::$global_extensions[$name]; - } - if( ! is_null($ext)) - { - array_unshift($arguments, $this); - return call_user_func_array(array($ext, $method), $arguments); - } - - // show an error, for debugging's sake. - throw new Exception("Unable to call the method \"$method\" on the class " . get_class($this)); - } - - // -------------------------------------------------------------------- - - /** - * Returns TRUE or FALSE if the method exists in the extensions. - * - * @param object $method Method to look for. - * @param object $which One of 'both', 'local', or 'global' - * @return bool TRUE if the method can be called. - */ - private function _extension_method_exists($method, $which = 'both') { - $found = FALSE; - if($which != 'global') { - $found = ! empty($this->extensions) && isset($this->extensions['_methods'][$method]); - } - if( ! $found && $which != 'local' ) { - $found = ! empty(DataMapper::$global_extensions) && isset(DataMapper::$global_extensions['_methods'][$method]); - } - return $found; - } - - // -------------------------------------------------------------------- - - /** - * Magic Clone - * - * Allows for a less shallow clone than the default PHP clone. - * - * @ignore - */ - public function __clone() - { - foreach ($this as $key => $value) - { - if (is_object($value) && $key != 'db') - { - $this->{$key} = clone($value); - } - } - } - - // -------------------------------------------------------------------- - - /** - * To String - * - * Converts the current object into a string. - * Should be overridden by extended objects. - * - * @return string - */ - public function __toString() - { - return ucfirst($this->model); - } - - // -------------------------------------------------------------------- - - /** - * Allows the all array to be iterated over without - * having to specify it. - * - * @return Iterator An iterator for the all array - */ - public function getIterator() { - if(isset($this->_dm_dataset_iterator)) { - return $this->_dm_dataset_iterator; - } else { - return new ArrayIterator($this->all); - } - } - - // -------------------------------------------------------------------- - - - /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * * - * Main methods * - * * - * The following are methods that form the main * - * functionality of DataMapper. * - * * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - - - // -------------------------------------------------------------------- - - /** - * Get - * - * Get objects from the database. - * - * @param integer|NULL $limit Limit the number of results. - * @param integer|NULL $offset Offset the results when limiting. - * @return DataMapper Returns self for method chaining. - */ - public function get($limit = NULL, $offset = NULL) - { - // Check if this is a related object and if so, perform a related get - if (! $this->_handle_related()) - { - // invalid get request, return this for chaining. - return $this; - } // Else fall through to a normal get - - $query = FALSE; - - // Check if object has been validated (skipped for related items) - if ($this->_validated && empty($this->parent)) - { - // Reset validated - $this->_validated = FALSE; - - // Use this objects properties - $data = $this->_to_array(TRUE); - - if ( ! empty($data)) - { - // Clear this object to make way for new data - $this->clear(); - - // Set up default order by (if available) - $this->_handle_default_order_by(); - - // Get by objects properties - $query = $this->db->get_where($this->table, $data, $limit, $offset); - } // FIXME: notify user if nothing was set? - } - else - { - // Clear this object to make way for new data - $this->clear(); - - // Set up default order by (if available) - $this->_handle_default_order_by(); - - // Get by built up query - $query = $this->db->get($this->table, $limit, $offset); - } - - // Convert the query result into DataMapper objects - if($query) - { - $this->_process_query($query); - } - - // For method chaining - return $this; - } - - // -------------------------------------------------------------------- - - /** - * Returns the SQL string of the current query (SELECTs ONLY). - * NOTE: This also _clears_ the current query info. - * - * This can be used to generate subqueries. - * - * @param integer|NULL $limit Limit the number of results. - * @param integer|NULL $offset Offset the results when limiting. - * @return string SQL as a string. - */ - public function get_sql($limit = NULL, $offset = NULL, $handle_related = FALSE) - { - if($handle_related) { - $this->_handle_related(); - } - - $this->db->_track_aliases($this->table); - $this->db->from($this->table); - - $this->_handle_default_order_by(); - - if ( ! is_null($limit)) - { - $this->limit($limit, $offset); - } - - $sql = $this->db->_compile_select(); - $this->_clear_after_query(); - return $sql; - } - - // -------------------------------------------------------------------- - - /** - * Runs the query, but returns the raw CodeIgniter results - * NOTE: This also _clears_ the current query info. - * - * @param integer|NULL $limit Limit the number of results. - * @param integer|NULL $offset Offset the results when limiting. - * @return CI_DB_result Result Object - */ - public function get_raw($limit = NULL, $offset = NULL, $handle_related = TRUE) - { - if($handle_related) { - $this->_handle_related(); - } - - $this->_handle_default_order_by(); - - $query = $this->db->get($this->table, $limit, $offset); - $this->_clear_after_query(); - return $query; - } - - // -------------------------------------------------------------------- - - /** - * Returns a streamable result set for large queries. - * Usage: - * $rs = $object->get_iterated(); - * $size = $rs->count; - * foreach($rs as $o) { - * // handle $o - * } - * $rs can be looped through more than once. - * - * @param integer|NULL $limit Limit the number of results. - * @param integer|NULL $offset Offset the results when limiting. - * @return DataMapper Returns self for method chaining. - */ - public function get_iterated($limit = NULL, $offset = NULL) - { - // clone $this, so we keep track of instantiations, etc. - // because these are cleared after the call to get_raw - $object = $this->get_clone(); - // need to clear query from the clone - $object->db->_reset_select(); - // Clear the query related list from the clone - $object->_query_related = array(); - - // Build iterator - $this->_dm_dataset_iterator = new DM_DatasetIterator($object, $this->get_raw($limit, $offset, TRUE)); - return $this; - } - - // -------------------------------------------------------------------- - - /** - * Convenience method that runs a query based on pages. - * This object will have two new values, $query_total_pages and - * $query_total_rows, which can be used to determine how many pages and - * how many rows are available in total, respectively. - * - * @param int $page Page (1-based) to start on, or row (0-based) to start on - * @param int $page_size Number of rows in a page - * @param bool $page_num_by_rows When TRUE, $page is the starting row, not the starting page - * @param bool $iterated Internal Use Only - * @return DataMapper Returns self for method chaining. - */ - public function get_paged($page = 1, $page_size = 50, $page_num_by_rows = FALSE, $info_object = 'paged', $iterated = FALSE) - { - // first, duplicate this query, so we have a copy for the query - $count_query = $this->get_clone(TRUE); - - if($page_num_by_rows) - { - $page = 1 + floor(intval($page) / $page_size); - } - - // never less than 1 - $page = max(1, intval($page)); - $offset = $page_size * ($page - 1); - - // for performance, we clear out the select AND the order by statements, - // since they aren't necessary and might slow down the query. - $count_query->db->ar_select = NULL; - $count_query->db->ar_orderby = NULL; - $total = $count_query->db->ar_distinct ? $count_query->count_distinct() : $count_query->count(); - - // common vars - $last_row = $page_size * floor($total / $page_size); - $total_pages = ceil($total / $page_size); - - if($offset >= $last_row) - { - // too far! - $offset = $last_row; - $page = $total_pages; - } - - // now query this object - if($iterated) - { - $this->get_iterated($page_size, $offset); - } - else - { - $this->get($page_size, $offset); - } - - $this->{$info_object} = new stdClass(); - - $this->{$info_object}->page_size = $page_size; - $this->{$info_object}->items_on_page = $this->result_count(); - $this->{$info_object}->current_page = $page; - $this->{$info_object}->current_row = $offset; - $this->{$info_object}->total_rows = $total; - $this->{$info_object}->last_row = $last_row; - $this->{$info_object}->total_pages = $total_pages; - $this->{$info_object}->has_previous = $offset > 0; - $this->{$info_object}->previous_page = max(1, $page-1); - $this->{$info_object}->previous_row = max(0, $offset-$page_size); - $this->{$info_object}->has_next = $page < $total_pages; - $this->{$info_object}->next_page = min($total_pages, $page+1); - $this->{$info_object}->next_row = min($last_row, $offset+$page_size); - - return $this; - } - - // -------------------------------------------------------------------- - - /** - * Runs get_paged, but as an Iterable. - * - * @see get_paged - * @param int $page Page (1-based) to start on, or row (0-based) to start on - * @param int $page_size Number of rows in a page - * @param bool $page_num_by_rows When TRUE, $page is the starting row, not the starting page - * @param bool $iterated Internal Use Only - * @return DataMapper Returns self for method chaining. - */ - public function get_paged_iterated($page = 1, $page_size = 50, $page_num_by_rows = FALSE, $info_object = 'paged') - { - return $this->get_paged($page, $page_size, $page_num_by_rows, $info_object, TRUE); - } - - // -------------------------------------------------------------------- - - /** - * Forces this object to be INSERTed, even if it has an ID. - * - * @param mixed $object See save. - * @param string $related_field See save. - * @return bool Result of the save. - */ - public function save_as_new($object = '', $related_field = '') - { - $this->_force_save_as_new = TRUE; - return $this->save($object, $related_field); - } - - // -------------------------------------------------------------------- - - /** - * Save - * - * Saves the current record, if it validates. - * If object is supplied, saves relations between this object and the supplied object(s). - * - * @param mixed $object Optional object to save or array of objects to save. - * @param string $related_field Optional string to save the object as a specific relationship. - * @return bool Success or Failure of the validation and save. - */ - public function save($object = '', $related_field = '') - { - // Temporarily store the success/failure - $result = array(); - - // Validate this objects properties - $this->validate($object, $related_field); - - // If validation passed - if ($this->valid) - { - - // Begin auto transaction - $this->_auto_trans_begin(); - - $trans_complete_label = array(); - - // Get current timestamp - $timestamp = $this->_get_generated_timestamp(); - - // Check if object has a 'created' field, and it is not already set - if (in_array($this->created_field, $this->fields) && empty($this->{$this->created_field})) - { - $this->{$this->created_field} = $timestamp; - } - - // SmartSave: if there are objects being saved, and they are stored - // as in-table foreign keys, we can save them at this step. - if( ! empty($object)) - { - if( ! is_array($object)) - { - $object = array($object); - } - $this->_save_itfk($object, $related_field); - } - - // Convert this object to array - $data = $this->_to_array(); - - if ( ! empty($data)) - { - if ( ! $this->_force_save_as_new && ! empty($data['id'])) - { - // Prepare data to send only changed fields - foreach ($data as $field => $value) - { - // Unset field from data if it hasn't been changed - if ($this->{$field} === $this->stored->{$field}) - { - unset($data[$field]); - } - } - - // if there are changes, check if we need to update the update timestamp - if (count($data) && in_array($this->updated_field, $this->fields) && ! isset($data[$this->updated_field])) - { - // update it now - $data[$this->updated_field] = $this->{$this->updated_field} = $timestamp; - } - - // Only go ahead with save if there is still data - if ( ! empty($data)) - { - // Update existing record - $this->db->where('id', $this->id); - $this->db->update($this->table, $data); - - $trans_complete_label[] = 'update'; - } - - // Reset validated - $this->_validated = FALSE; - - $result[] = TRUE; - } - else - { - // Prepare data to send only populated fields - foreach ($data as $field => $value) - { - // Unset field from data - if ( ! isset($value)) - { - unset($data[$field]); - } - } - - // Create new record - $this->db->insert($this->table, $data); - - if( ! $this->_force_save_as_new) - { - // Assign new ID - $this->id = $this->db->insert_id(); - } - - $trans_complete_label[] = 'insert'; - - // Reset validated - $this->_validated = FALSE; - - $result[] = TRUE; - } - } - - $this->_refresh_stored_values(); - - // Check if a relationship is being saved - if ( ! empty($object)) - { - // save recursively - $this->_save_related_recursive($object, $related_field); - - $trans_complete_label[] = 'relationships'; - } - - if(!empty($trans_complete_label)) - { - $trans_complete_label = 'save (' . implode(', ', $trans_complete_label) . ')'; - } - else - { - $trans_complete_label = '-nothing done-'; - } - - $this->_auto_trans_complete($trans_complete_label); - - } - - $this->_force_save_as_new = FALSE; - - // If no failure was recorded, return TRUE - return ( ! empty($result) && ! in_array(FALSE, $result)); - } - - // -------------------------------------------------------------------- - - /** - * Recursively saves arrays of objects if they are In-Table Foreign Keys. - * @ignore - * @param object $objects Objects to save. This array may be modified. - * @param object $related_field Related Field name (empty is OK) - */ - protected function _save_itfk( &$objects, $related_field) - { - foreach($objects as $index => $o) - { - if(is_int($index)) - { - $rf = $related_field; - } - else - { - $rf = $index; - } - if(is_array($o)) - { - $this->_save_itfk($o, $rf); - if(empty($o)) - { - unset($objects[$index]); - } - } - else - { - if(empty($rf)) { - $rf = $o->model; - } - $related_properties = $this->_get_related_properties($rf); - $other_column = $related_properties['join_other_as'] . '_id'; - if(isset($this->has_one[$rf]) && in_array($other_column, $this->fields)) - { - // unset, so that it doesn't get re-saved later. - unset($objects[$index]); - - if($this->{$other_column} != $o->id) - { - // ITFK: store on the table - $this->{$other_column} = $o->id; - - // Remove reverse relationships for one-to-ones - $this->_remove_other_one_to_one($rf, $o); - } - } - } - } - } - - // -------------------------------------------------------------------- - - /** - * Recursively saves arrays of objects. - * - * @ignore - * @param object $object Array of objects to save, or single object - * @param object $related_field Default related field name (empty is OK) - * @return bool TRUE or FALSE if an error occurred. - */ - protected function _save_related_recursive($object, $related_field) - { - if(is_array($object)) - { - $success = TRUE; - foreach($object as $rk => $o) - { - if(is_int($rk)) - { - $rk = $related_field; - } - $rec_success = $this->_save_related_recursive($o, $rk); - $success = $success && $rec_success; - } - return $success; - } - else - { - return $this->_save_relation($object, $related_field); - } - } - - // -------------------------------------------------------------------- - - /** - * _Save - * - * Used by __call to process related saves. - * - * @ignore - * @param mixed $related_field - * @param array $arguments - * @return bool - */ - private function _save($related_field, $arguments) - { - return $this->save($arguments[0], $related_field); - } - - // -------------------------------------------------------------------- - - /** - * Update - * - * Allows updating of more than one row at once. - * - * @param object $field A field to update, or an array of fields => values - * @param object $value The new value - * @param object $escape_values If false, don't escape the values - * @return bool TRUE or FALSE on success or failure - */ - public function update($field, $value = NULL, $escape_values = TRUE) - { - if( ! is_array($field)) - { - $field = array($field => $value); - } - else if($value === FALSE) - { - $escape_values = FALSE; - } - if(empty($field)) - { - show_error("Nothing was provided to update."); - } - - // Check if object has an 'updated' field - if (in_array($this->updated_field, $this->fields)) - { - $timestamp = $this->_get_generated_timestamp(); - if( ! $escape_values) - { - $timestamp = $this->db->escape($timestamp); - } - // Update updated datetime - $field[$this->updated_field] = $timestamp; - } - - foreach($field as $k => $v) - { - if( ! $escape_values) - { - // attempt to add the table name - $v = $this->add_table_name($v); - } - $this->db->set($k, $v, $escape_values); - } - return $this->db->update($this->table); - } - - // -------------------------------------------------------------------- - - /** - * Update All - * - * Updates all items that are in the all array. - * - * @param object $field A field to update, or an array of fields => values - * @param object $value The new value - * @param object $escape_values If false, don't escape the values - * @return bool TRUE or FALSE on success or failure - */ - public function update_all($field, $value = NULL, $escape_values = TRUE) - { - $ids = array(); - foreach($this->all as $object) - { - $ids[] = $object->id; - } - if(empty($ids)) - { - return FALSE; - } - - $this->where_in('id', $ids); - return $this->update($field, $value, $escape_values); - } - - // -------------------------------------------------------------------- - - /** - * Gets a timestamp to use when saving. - * @return mixed - */ - private function _get_generated_timestamp() - { - // Get current timestamp - $timestamp = ($this->local_time) ? date($this->timestamp_format) : gmdate($this->timestamp_format); - - // Check if unix timestamp - return ($this->unix_timestamp) ? strtotime($timestamp) : $timestamp; - } - - // -------------------------------------------------------------------- - - /** - * Delete - * - * Deletes the current record. - * If object is supplied, deletes relations between this object and the supplied object(s). - * - * @param mixed $object If specified, delete the relationship to the object or array of objects. - * @param string $related_field Can be used to specify which relationship to delete. - * @return bool Success or Failure of the delete. - */ - public function delete($object = '', $related_field = '') - { - if (empty($object) && ! is_array($object)) - { - if ( ! empty($this->id)) - { - // Begin auto transaction - $this->_auto_trans_begin(); - - // Delete all "has many" and "has one" relations for this object first - foreach (array('has_many', 'has_one') as $type) - { - foreach ($this->{$type} as $model => $properties) - { - // Prepare model - $class = $properties['class']; - $object = new $class(); - - $this_model = $properties['join_self_as']; - $other_model = $properties['join_other_as']; - - // Determine relationship table name - $relationship_table = $this->_get_relationship_table($object, $model); - - // We have to just set NULL for in-table foreign keys that - // are pointing at this object - if($relationship_table == $object->table && // ITFK - // NOT ITFKs that point at the other object - ! ($object->table == $this->table && // self-referencing has_one join - in_array($other_model . '_id', $this->fields)) // where the ITFK is for the other object - ) - { - $data = array($this_model . '_id' => NULL); - - // Update table to remove relationships - $this->db->where($this_model . '_id', $this->id); - $this->db->update($object->table, $data); - } - else if ($relationship_table != $this->table) - { - - $data = array($this_model . '_id' => $this->id); - - // Delete relation - $this->db->delete($relationship_table, $data); - } - // Else, no reason to delete the relationships on this table - } - } - - // Delete the object itself - $this->db->where('id', $this->id); - $this->db->delete($this->table); - - // Complete auto transaction - $this->_auto_trans_complete('delete'); - - // Clear this object - $this->clear(); - - return TRUE; - } - } - else if (is_array($object)) - { - // Begin auto transaction - $this->_auto_trans_begin(); - - // Temporarily store the success/failure - $result = array(); - - foreach ($object as $rel_field => $obj) - { - if (is_int($rel_field)) - { - $rel_field = $related_field; - } - if (is_array($obj)) - { - foreach ($obj as $r_f => $o) - { - if (is_int($r_f)) - { - $r_f = $rel_field; - } - $result[] = $this->_delete_relation($o, $r_f); - } - } - else - { - $result[] = $this->_delete_relation($obj, $rel_field); - } - } - - // Complete auto transaction - $this->_auto_trans_complete('delete (relationship)'); - - // If no failure was recorded, return TRUE - if ( ! in_array(FALSE, $result)) - { - return TRUE; - } - } - else - { - // Begin auto transaction - $this->_auto_trans_begin(); - - // Temporarily store the success/failure - $result = $this->_delete_relation($object, $related_field); - - // Complete auto transaction - $this->_auto_trans_complete('delete (relationship)'); - - return $result; - } - - return FALSE; - } - - // -------------------------------------------------------------------- - - /** - * _Delete - * - * Used by __call to process related deletes. - * - * @ignore - * @param string $related_field - * @param array $arguments - * @return bool - */ - private function _delete($related_field, $arguments) - { - return $this->delete($arguments[0], $related_field); - } - - // -------------------------------------------------------------------- - - /** - * Delete All - * - * Deletes all records in this objects all list. - * - * @return bool Success or Failure of the delete - */ - public function delete_all() - { - $success = TRUE; - foreach($this as $item) - { - if ( ! empty($item->id)) - { - $success_temp = $item->delete(); - $success = $success && $success_temp; - } - } - $this->clear(); - return $success; - } - - // -------------------------------------------------------------------- - - /** - * Refresh All - * - * Removes any empty objects in this objects all list. - * Only needs to be used if you are looping through the all list - * a second time and you have deleted a record the first time through. - * - * @return bool FALSE if the $all array was already empty. - */ - public function refresh_all() - { - if ( ! empty($this->all)) - { - $all = array(); - - foreach ($this->all as $item) - { - if ( ! empty($item->id)) - { - $all[] = $item; - } - } - - $this->all = $all; - - return TRUE; - } - - return FALSE; - } - - // -------------------------------------------------------------------- - - /** - * Validate - * - * Validates the value of each property against the assigned validation rules. - * - * @param mixed $object Objects included with the validation [from save()]. - * @param string $related_field See save. - * @return DataMapper Returns $this for method chanining. - */ - public function validate($object = '', $related_field = '') - { - // Return if validation has already been run - if ($this->_validated) - { - // For method chaining - return $this; - } - - // Set validated as having been run - $this->_validated = TRUE; - - // Clear errors - $this->error = new DM_Error_Object(); - - // Loop through each property to be validated - foreach ($this->validation as $field => $validation) - { - if(empty($validation['rules'])) - { - continue; - } - - // Get validation settings - $rules = $validation['rules']; - - // Will validate differently if this is for a related item - $related = (isset($this->has_many[$field]) || isset($this->has_one[$field])); - - // Check if property has changed since validate last ran - if ($related || ! isset($this->stored->{$field}) || $this->{$field} !== $this->stored->{$field}) - { - // Only validate if field is related or required or has a value - if ( ! $related && ! in_array('required', $rules) && ! in_array('always_validate', $rules)) - { - if ( ! isset($this->{$field}) || $this->{$field} === '') - { - continue; - } - } - - $label = ( ! empty($validation['label'])) ? $validation['label'] : $field; - - // Loop through each rule to validate this property against - foreach ($rules as $rule => $param) - { - // Check for parameter - if (is_numeric($rule)) - { - $rule = $param; - $param = ''; - } - - // Clear result - $result = ''; - // Clear message - $line = FALSE; - - // Check rule exists - if ($related) - { - // Prepare rule to use different language file lines - $rule = 'related_' . $rule; - - $arg = $object; - if( ! empty($related_field)) { - $arg = array($related_field => $object); - } - - if (method_exists($this, '_' . $rule)) - { - // Run related rule from DataMapper or the class extending DataMapper - $line = $result = $this->{'_' . $rule}($arg, $field, $param); - } - else if($this->_extension_method_exists('rule_' . $rule)) - { - $line = $result = $this->{'rule_' . $rule}($arg, $field, $param); - } - } - else if (method_exists($this, '_' . $rule)) - { - // Run rule from DataMapper or the class extending DataMapper - $line = $result = $this->{'_' . $rule}($field, $param); - } - else if($this->_extension_method_exists('rule_' . $rule)) - { - // Run an extension-based rule. - $line = $result = $this->{'rule_' . $rule}($field, $param); - } - else if (method_exists($this->form_validation, $rule)) - { - // Run rule from CI Form Validation - $result = $this->form_validation->{$rule}($this->{$field}, $param); - } - else if (function_exists($rule)) - { - // Run rule from PHP - $this->{$field} = $rule($this->{$field}); - } - - // Add an error message if the rule returned FALSE - if (is_string($line) || $result === FALSE) - { - if(!is_string($line)) - { - if (FALSE === ($line = $this->lang->line($rule))) - { - // Get corresponding error from language file - $line = 'Unable to access an error message corresponding to your rule name: '.$rule.'.'; - } - } - - // Check if param is an array - if (is_array($param)) - { - // Convert into a string so it can be used in the error message - $param = implode(', ', $param); - - // Replace last ", " with " or " - if (FALSE !== ($pos = strrpos($param, ', '))) - { - $param = substr_replace($param, ' or ', $pos, 2); - } - } - - // Check if param is a validation field - if (isset($this->validation[$param])) - { - // Change it to the label value - $param = $this->validation[$param]['label']; - } - - // Add error message - $this->error_message($field, sprintf($line, $label, $param)); - - // Escape to prevent further error checks - break; - } - } - } - } - - // Set whether validation passed - $this->valid = empty($this->error->all); - - // For method chaining - return $this; - } - - // -------------------------------------------------------------------- - - /** - * Skips validation for the next call to save. - * Note that this also prevents the validation routine from running until the next get. - * - * @param object $skip If FALSE, re-enables validation. - * @return DataMapper Returns self for method chaining. - */ - public function skip_validation($skip = TRUE) - { - $this->_validated = $skip; - $this->valid = $skip; - return $this; - } - - // -------------------------------------------------------------------- - - /** - * Clear - * - * Clears the current object. - */ - public function clear() - { - // Clear the all list - $this->all = array(); - - // Clear errors - $this->error = new DM_Error_Object(); - - // Clear this objects properties and set blank error messages in case they are accessed - foreach ($this->fields as $field) - { - $this->{$field} = NULL; - } - - // Clear this objects "has many" related objects - foreach ($this->has_many as $related => $properties) - { - unset($this->{$related}); - } - - // Clear this objects "has one" related objects - foreach ($this->has_one as $related => $properties) - { - unset($this->{$related}); - } - - // Clear the query related list - $this->_query_related = array(); - - // Clear and refresh stored values - $this->stored = new stdClass(); - - // Clear the saved iterator - unset($this->_dm_dataset_iterator); - - $this->_refresh_stored_values(); - } - - // -------------------------------------------------------------------- - - /** - * Clears the db object after processing a query, or returning the - * SQL for a query. - * - * @ignore - */ - protected function _clear_after_query() - { - // clear the query as if it was run - $this->db->_reset_select(); - - // in case some include_related instantiations were set up, clear them - $this->_instantiations = NULL; - - // Clear the query related list (Thanks to TheJim) - $this->_query_related = array(); - - // Clear the saved iterator - unset($this->_dm_dataset_iterator); - } - - // -------------------------------------------------------------------- - - /** - * Count - * - * Returns the total count of the object records from the database. - * If on a related object, returns the total count of related objects records. - * - * @param array $exclude_ids A list of ids to exlcude from the count - * @return int Number of rows in query. - */ - public function count($exclude_ids = NULL, $column = NULL, $related_id = NULL) - { - // Check if related object - if ( ! empty($this->parent)) - { - // Prepare model - $related_field = $this->parent['model']; - $related_properties = $this->_get_related_properties($related_field); - $class = $related_properties['class']; - $other_model = $related_properties['join_other_as']; - $this_model = $related_properties['join_self_as']; - $object = new $class(); - - // Determine relationship table name - $relationship_table = $this->_get_relationship_table($object, $related_field); - - // To ensure result integrity, group all previous queries - if( ! empty($this->db->ar_where)) - { - // if the relationship table is different from our table, include our table in the count query - if ($relationship_table != $this->table) - { - $this->db->join($this->table, $this->table . '.id = ' . $relationship_table . '.' . $this_model.'_id', 'LEFT OUTER'); - } - - array_unshift($this->db->ar_where, '( '); - $this->db->ar_where[] = ' )'; - } - - // We have to query special for in-table foreign keys that - // are pointing at this object - if($relationship_table == $object->table && // ITFK - // NOT ITFKs that point at the other object - ! ($object->table == $this->table && // self-referencing has_one join - in_array($other_model . '_id', $this->fields)) // where the ITFK is for the other object - ) - { - // ITFK on the other object's table - $this->db->where('id', $this->parent['id'])->where($this_model . '_id IS NOT NULL'); - } - else - { - // All other cases - $this->db->where($relationship_table . '.' . $other_model . '_id', $this->parent['id']); - } - if(!empty($exclude_ids)) - { - $this->db->where_not_in($relationship_table . '.' . $this_model . '_id', $exclude_ids); - } - if($column == 'id') - { - $column = $relationship_table . '.' . $this_model . '_id'; - } - if(!empty($related_id)) - { - $this->db->where($this_model . '_id', $related_id); - } - $this->db->from($relationship_table); - } - else - { - $this->db->from($this->table); - if(!empty($exclude_ids)) - { - $this->db->where_not_in('id', $exclude_ids); - } - if(!empty($related_id)) - { - $this->db->where('id', $related_id); - } - $column = $this->add_table_name($column); - } - - // Manually overridden to allow for COUNT(DISTINCT COLUMN) - $select = $this->db->_count_string; - if(!empty($column)) - { - // COUNT DISTINCT - $select = 'SELECT COUNT(DISTINCT ' . $this->db->_protect_identifiers($column) . ') AS '; - } - $sql = $this->db->_compile_select($select . $this->db->_protect_identifiers('numrows')); - - $query = $this->db->query($sql); - $this->db->_reset_select(); - - if ($query->num_rows() == 0) - { - return 0; - } - - $row = $query->row(); - return intval($row->numrows); - } - - // -------------------------------------------------------------------- - - /** - * Count Distinct - * - * Returns the total count of distinct object records from the database. - * If on a related object, returns the total count of related objects records. - * - * @param array $exclude_ids A list of ids to exlcude from the count - * @param string $column If provided, use this column for the DISTINCT instead of 'id' - * @return int Number of rows in query. - */ - public function count_distinct($exclude_ids = NULL, $column = 'id') - { - return $this->count($exclude_ids, $column); - } - - // -------------------------------------------------------------------- - - /** - * Convenience method to return the number of items from - * the last call to get. - * - * @return int - */ - public function result_count() { - if(isset($this->_dm_dataset_iterator)) { - return $this->_dm_dataset_iterator->result_count(); - } else { - return count($this->all); - } - } - - // -------------------------------------------------------------------- - - /** - * Exists - * - * Returns TRUE if the current object has a database record. - * - * @return bool - */ - public function exists() - { - // returns TRUE if the id of this object is set and not empty, OR - // there are items in the ALL array. - return isset($this->id) ? !empty($this->id) : ($this->result_count() > 0); - } - - // -------------------------------------------------------------------- - - /** - * Query - * - * Runs the specified query and populates the current object with the results. - * - * Warning: Use at your own risk. This will only be as reliable as your query. - * - * @param string $sql The query to process - * @param array|bool $binds Array of values to bind (see CodeIgniter) - * @return DataMapper Returns self for method chaining. - */ - public function query($sql, $binds = FALSE) - { - // Get by objects properties - $query = $this->db->query($sql, $binds); - - $this->_process_query($query); - - // For method chaining - return $this; - } - - // -------------------------------------------------------------------- - - /** - * Check Last Query - * Renders the last DB query performed. - * - * @param array $delims Delimiters for the SQL string. - * @param bool $return_as_string If TRUE, don't output automatically. - * @return string Last db query formatted as a string. - */ - public function check_last_query($delims = array('
', '
'), $return_as_string = FALSE) { - $q = wordwrap($this->db->last_query(), 100, "\n\t"); - if(!empty($delims)) { - $q = implode($q, $delims); - } - if($return_as_string === FALSE) { - echo $q; - } - return $q; - } - - // -------------------------------------------------------------------- - - /** - * Error Message - * - * Adds an error message to this objects error object. - * - * @param string $field Field to set the error on. - * @param string $error Error message. - */ - public function error_message($field, $error) - { - if ( ! empty($field) && ! empty($error)) - { - // Set field specific error - $this->error->{$field} = $this->error_prefix . $error . $this->error_suffix; - - // Add field error to errors all list - $this->error->all[$field] = $this->error->{$field}; - - // Append field error to error message string - $this->error->string .= $this->error->{$field}; - } - } - - // -------------------------------------------------------------------- - - /** - * Get Clone - * - * Returns a clone of the current object. - * - * @return DataMapper Cloned copy of this object. - */ - public function get_clone($force_db = FALSE) - { - $temp = clone($this); - - // This must be left in place, even with the __clone method, - // or else the DB will not be copied over correctly. - if($force_db || - (($this->db_params !== FALSE) && isset($this->db)) ) - { - // create a copy of $this->db - $temp->db = clone($this->db); - } - return $temp; - } - - // -------------------------------------------------------------------- - - /** - * Get Copy - * - * Returns an unsaved copy of the current object. - * - * @return DataMapper Cloned copy of this object with an empty ID for saving as new. - */ - public function get_copy($force_db = FALSE) - { - $copy = $this->get_clone($force_db); - - $copy->id = NULL; - - return $copy; - } - - // -------------------------------------------------------------------- - - /** - * Get By - * - * Gets objects by specified field name and value. - * - * @ignore - * @param string $field Field to look at. - * @param array $value Arguments to this method. - * @return DataMapper Returns self for method chaining. - */ - private function _get_by($field, $value = array()) - { - if (isset($value[0])) - { - $this->where($field, $value[0]); - } - - return $this->get(); - } - - // -------------------------------------------------------------------- - - /** - * Get By Related - * - * Gets objects by specified related object and optionally by field name and value. - * - * @ignore - * @param mixed $model Related Model or Object - * @param array $arguments Arguments to the where method - * @return DataMapper Returns self for method chaining. - */ - private function _get_by_related($model, $arguments = array()) - { - if ( ! empty($model)) - { - // Add model to start of arguments - $arguments = array_merge(array($model), $arguments); - } - - $this->_related('where', $arguments); - - return $this->get(); - } - - // -------------------------------------------------------------------- - - /** - * Handles the adding the related part of a query if $parent is set - * - * @ignore - * @return bool Success or failure - */ - protected function _handle_related() - { - if ( ! empty($this->parent)) - { - $has_many = array_key_exists($this->parent['model'], $this->has_many); - $has_one = array_key_exists($this->parent['model'], $this->has_one); - - // If this is a "has many" or "has one" related item - if ($has_many || $has_one) - { - if( ! $this->_get_relation($this->parent['model'], $this->parent['id'])) - { - return FALSE; - } - } - else - { - // provide feedback on errors - $parent = $this->parent['model']; - $this_model = get_class($this); - show_error("DataMapper Error: '$parent' is not a valid parent relationship for $this_model. Are your relationships configured correctly?"); - } - } - - return TRUE; - } - - // -------------------------------------------------------------------- - - - /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * * - * Active Record methods * - * * - * The following are methods used to provide Active Record * - * functionality for data retrieval. * - * * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - - - // -------------------------------------------------------------------- - - /** - * Add Table Name - * - * Adds the table name to a field if necessary - * - * @param string $field Field to add the table name to. - * @return string Possibly modified field name. - */ - public function add_table_name($field) - { - // only add table if the field doesn't contain a dot (.) or open parentheses - if (preg_match('/[\.\(]/', $field) == 0) - { - // split string into parts, add field - $field_parts = explode(',', $field); - $field = ''; - foreach ($field_parts as $part) - { - if ( ! empty($field)) - { - $field .= ', '; - } - $part = ltrim($part); - // handle comparison operators on where - $subparts = explode(' ', $part, 2); - if ($subparts[0] == '*' || in_array($subparts[0], $this->fields)) - { - $field .= $this->table . '.' . $part; - } - else - { - $field .= $part; - } - } - } - return $field; - } - - // -------------------------------------------------------------------- - - /** - * Creates a SQL-function with the given (optional) arguments. - * - * Each argument can be one of several forms: - * 1) An un escaped string value, which will be automatically escaped: "hello" - * 2) An escaped value or non-string, which is copied directly: "'hello'" 123, etc - * 3) An operator, *, or a non-escaped string is copied directly: "[non-escaped]" ">", etc - * 4) A field on this model: "@property" (Also, "@" will be copied directly - * 5) A field on a related or deeply related model: "@model/property" "@model/other_model/property" - * 6) An array, which is processed recursively as a forumla. - * - * @param string $function_name Function name. - * @param mixed $args,... (Optional) Any commands that need to be passed to the function. - * @return string The new SQL function string. - */ - public function func($function_name) - { - $ret = $function_name . '('; - $args = func_get_args(); - // pop the function name - array_shift($args); - $comma = ''; - foreach($args as $arg) - { - $ret .= $comma . $this->_process_function_arg($arg); - if(empty($comma)) - { - $comma = ', '; - } - } - $ret .= ')'; - return $ret; - } - - // private method to convert function arguments into SQL - private function _process_function_arg($arg, $is_formula = FALSE) - { - $ret = ''; - if(is_array($arg)) { - // formula - foreach($arg as $func => $formula_arg) { - if(!empty($ret)) { - $ret .= ' '; - } - if(is_numeric($func)) { - // process non-functions - $ret .= $this->_process_function_arg($formula_arg, TRUE); - } else { - // recursively process functions within functions - $func_args = array_merge(array($func), (array)$formula_arg); - $ret .= call_user_func_array(array($this, 'func'), $func_args); - } - } - return $ret; - } - - $operators = array( - 'AND', 'OR', 'NOT', // binary logic - '<', '>', '<=', '>=', '=', '<>', '!=', // comparators - '+', '-', '*', '/', '%', '^', // basic maths - '|/', '||/', '!', '!!', '@', '&', '|', '#', '~', // advanced maths - '<<', '>>'); // binary operators - - if(is_string($arg)) - { - if( ($is_formula && in_array($arg, $operators)) || - $arg == '*' || - ($arg[0] == "'" && $arg[strlen($arg)-1] == "'") || - ($arg[0] == "[" && $arg[strlen($arg)-1] == "]") ) - { - // simply add already-escaped strings, the special * value, or operators in formulas - if($arg[0] == "[" && $arg[strlen($arg)-1] == "]") { - // Arguments surrounded by square brackets are added directly, minus the brackets - $arg = substr($arg, 1, -1); - } - $ret .= $arg; - } - else if($arg[0] == '@') - { - // model or sub-model property - $arg = substr($arg, 1); - if(strpos($arg, '/') !== FALSE) - { - // related property - if(strpos($arg, 'parent/') === 0) - { - // special parent property for subqueries - $ret .= str_replace('parent/', '${parent}.', $arg); - } - else - { - $rel_elements = explode('/', $arg); - $property = array_pop($rel_elements); - $table = $this->_add_related_table(implode('/', $rel_elements)); - $ret .= $this->db->protect_identifiers($table . '.' . $property); - } - } - else - { - $ret .= $this->db->protect_identifiers($this->add_table_name($arg)); - } - } - else - { - $ret .= $this->db->escape($arg); - } - } - else - { - $ret .= $arg; - } - return $ret; - } - - // -------------------------------------------------------------------- - - /** - * Used by the magic method for select_func, {where}_func, etc - * - * @ignore - * @param object $query Name of query function - * @param array $args Arguments for func() - * @return DataMapper Returns self for method chaining. - */ - private function _func($query, $args) - { - if(count($args) < 2) - { - throw new Exception("Invalid number of arguments to {$query}_func: must be at least 2 arguments."); - } - if($query == 'select') - { - $alias = array_pop($args); - $value = call_user_func_array(array($this, 'func'), $args); - $value .= " AS $alias"; - - // we can't use the normal select method, because CI likes to breaky - $this->_add_to_select_directly($value); - - return $this; - } - else - { - $param = array_pop($args); - $value = call_user_func_array(array($this, 'func'), $args); - return $this->{$query}($value, $param); - } - } - - // -------------------------------------------------------------------- - - /** - * Used by the magic method for {where}_field_func, etc. - * - * @ignore - * @param string $query Name of query function - * @param array $args Arguments for func() - * @return DataMapper Returns self for method chaining. - */ - private function _field_func($query, $args) - { - if(count($args) < 2) - { - throw new Exception("Invalid number of arguments to {$query}_field_func: must be at least 2 arguments."); - } - $field = array_shift($args); - $func = call_user_func_array(array($this, 'func'), $args); - return $this->_process_special_query_clause($query, $field, $func); - } - - // -------------------------------------------------------------------- - - /** - * Used by the magic method for select_subquery {where}_subquery, etc - * - * @ignore - * @param string $query Name of query function - * @param array $args Arguments for subquery - * @return DataMapper Returns self for method chaining. - */ - private function _subquery($query, $args) - { - if(count($args) < 1) - { - throw new Exception("Invalid arguments on {$query}_subquery: must be at least one argument."); - } - if($query == 'select') - { - if(count($args) < 2) - { - throw new Exception('Invalid number of arguments to select_subquery: must be exactly 2 arguments.'); - } - $sql = $this->_parse_subquery_object($args[0]); - $alias = $args[1]; - // we can't use the normal select method, because CI likes to breaky - $this->_add_to_select_directly("$sql AS $alias"); - return $this; - } - else - { - $object = $field = $value = NULL; - if(is_object($args[0]) || - (is_string($args[0]) && !isset($args[1])) ) - { - $field = $this->_parse_subquery_object($args[0]); - if(isset($args[1])) { - $value = $this->db->protect_identifiers($this->add_table_name($args[1])); - } - } - else - { - $field = $this->add_table_name($args[0]); - $value = $args[1]; - if(is_object($value)) - { - $value = $this->_parse_subquery_object($value); - } - } - $extra = NULL; - if(isset($args[2])) { - $extra = $args[2]; - } - return $this->_process_special_query_clause($query, $field, $value, $extra); - } - } - - // -------------------------------------------------------------------- - - /** - * Parses and protects a subquery. - * Automatically replaces the special ${parent} argument with a reference to - * this table. - * - * Also replaces all table references that would overlap with this object. - * - * @ignore - * @param object $sql SQL string to process - * @return string Processed SQL string. - */ - protected function _parse_subquery_object($sql) - { - if(is_object($sql)) - { - $sql = '(' . $sql->get_sql() . ')'; - } - - // Table Name pattern should be - $tablename = $this->db->_escape_identifiers($this->table); - $table_pattern = '(?:' . preg_quote($this->table) . '|' . preg_quote($tablename) . ')'; - - $fieldname = $this->db->_escape_identifiers('__field__'); - $field_pattern = '([-\w]+|' . str_replace('__field__', '[-\w]+', preg_quote($fieldname)) . ')'; - - // replace all table.field references - // pattern ends up being [^_](table|`table`).(field|`field`) - // the NOT _ at the beginning is to prevent replacing of advanced relationship table references. - $pattern = '/([^_])' . $table_pattern . '\.' . $field_pattern . '/i'; - // replacement ends up being `table_subquery`.`$1` - $replacement = '$1' . $this->db->_escape_identifiers($this->table . '_subquery') . '.$2'; - $sql = preg_replace($pattern, $replacement, $sql); - - // now replace all "table table" aliases - // important: the space at the end is required - $pattern = "/$table_pattern $table_pattern /i"; - $replacement = $tablename . ' ' . $this->db->_escape_identifiers($this->table . '_subquery') . ' '; - $sql = preg_replace($pattern, $replacement, $sql); - - // now replace "FROM table" for self relationships - $pattern = "/FROM $table_pattern([,\\s])/i"; - $replacement = "FROM $tablename " . $this->db->_escape_identifiers($this->table . '_subquery') . '$1'; - $sql = preg_replace($pattern, $replacement, $sql); - - $sql = str_replace("\n", "\n\t", $sql); - - return str_replace('${parent}', $this->table, $sql); - } - - // -------------------------------------------------------------------- - - /** - * Manually adds an item to the SELECT column, to prevent it from - * being broken by AR->select - * - * @ignore - * @param string $value New SELECT value - */ - protected function _add_to_select_directly($value) - { - // copied from system/database/DB_activerecord.php - $this->db->ar_select[] = $value; - - if ($this->db->ar_caching === TRUE) - { - $this->ar_cache_select[] = $value; - $this->ar_cache_exists[] = 'select'; - } - } - - // -------------------------------------------------------------------- - - /** - * Handles specialized where clauses, like subqueries and functions - * - * @ignore - * @param string $query Query function - * @param string $field Field for Query function - * @param mixed $value Value for Query function - * @param mixed $extra If included, overrides the default assumption of FALSE for the third parameter to $query - * @return DataMapper Returns self for method chaining. - */ - private function _process_special_query_clause($query, $field, $value, $extra = NULL) { - if(strpos($query, 'where_in') !== FALSE) { - $query = str_replace('_in', '', $query); - $field .= ' IN '; - } else if(strpos($query, 'where_not_in') !== FALSE) { - $query = str_replace('_not_in', '', $query); - $field .= ' NOT IN '; - } - if(is_null($extra)) { - $extra = FALSE; - } - return $this->{$query}($field, $value, $extra); - } - - // -------------------------------------------------------------------- - - /** - * Select - * - * Sets the SELECT portion of the query. - * - * @param mixed $select Field(s) to select, array or comma separated string - * @param bool $escape If FALSE, don't escape this field (Probably won't work) - * @return DataMapper Returns self for method chaining. - */ - public function select($select = '*', $escape = NULL) - { - if ($escape !== FALSE) { - if (!is_array($select)) { - $select = $this->add_table_name($select); - } else { - $updated = array(); - foreach ($select as $sel) { - $updated = $this->add_table_name($sel); - } - $select = $updated; - } - } - $this->db->select($select, $escape); - - // For method chaining - return $this; - } - - // -------------------------------------------------------------------- - - /** - * Select Max - * - * Sets the SELECT MAX(field) portion of a query. - * - * @param string $select Field to look at. - * @param string $alias Alias of the MAX value. - * @return DataMapper Returns self for method chaining. - */ - public function select_max($select = '', $alias = '') - { - // Check if this is a related object - if ( ! empty($this->parent)) - { - $alias = ($alias != '') ? $alias : $select; - } - $this->db->select_max($this->add_table_name($select), $alias); - - // For method chaining - return $this; - } - - // -------------------------------------------------------------------- - - /** - * Select Min - * - * Sets the SELECT MIN(field) portion of a query. - * - * @param string $select Field to look at. - * @param string $alias Alias of the MIN value. - * @return DataMapper Returns self for method chaining. - */ - public function select_min($select = '', $alias = '') - { - // Check if this is a related object - if ( ! empty($this->parent)) - { - $alias = ($alias != '') ? $alias : $select; - } - $this->db->select_min($this->add_table_name($select), $alias); - - // For method chaining - return $this; - } - - // -------------------------------------------------------------------- - - /** - * Select Avg - * - * Sets the SELECT AVG(field) portion of a query. - * - * @param string $select Field to look at. - * @param string $alias Alias of the AVG value. - * @return DataMapper Returns self for method chaining. - */ - public function select_avg($select = '', $alias = '') - { - // Check if this is a related object - if ( ! empty($this->parent)) - { - $alias = ($alias != '') ? $alias : $select; - } - $this->db->select_avg($this->add_table_name($select), $alias); - - // For method chaining - return $this; - } - - // -------------------------------------------------------------------- - - /** - * Select Sum - * - * Sets the SELECT SUM(field) portion of a query. - * - * @param string $select Field to look at. - * @param string $alias Alias of the SUM value. - * @return DataMapper Returns self for method chaining. - */ - public function select_sum($select = '', $alias = '') - { - // Check if this is a related object - if ( ! empty($this->parent)) - { - $alias = ($alias != '') ? $alias : $select; - } - $this->db->select_sum($this->add_table_name($select), $alias); - - // For method chaining - return $this; - } - - // -------------------------------------------------------------------- - - /** - * Distinct - * - * Sets the flag to add DISTINCT to the query. - * - * @param bool $value Set to FALSE to turn back off DISTINCT - * @return DataMapper Returns self for method chaining. - */ - public function distinct($value = TRUE) - { - $this->db->distinct($value); - - // For method chaining - return $this; - } - - // -------------------------------------------------------------------- - - /** - * Get Where - * - * Get items matching the where clause. - * - * @param mixed $where See where() - * @param integer|NULL $limit Limit the number of results. - * @param integer|NULL $offset Offset the results when limiting. - * @return DataMapper Returns self for method chaining. - */ - public function get_where($where = array(), $limit = NULL, $offset = NULL) - { - $this->where($where); - - return $this->get($limit, $offset); - } - - // -------------------------------------------------------------------- - - /** - * Starts a query group. - * - * @param string $not (Internal use only) - * @param string $type (Internal use only) - * @return DataMapper Returns self for method chaining. - */ - public function group_start($not = '', $type = 'AND ') - { - // in case groups are being nested - $type = $this->_get_prepend_type($type); - - $prefix = (count($this->db->ar_where) == 0 AND count($this->db->ar_cache_where) == 0) ? '' : $type; - $this->db->ar_where[] = $prefix . $not . ' ('; - $this->_where_group_started = TRUE; - return $this; - } - - // -------------------------------------------------------------------- - - /** - * Starts a query group, but ORs the group - * @return DataMapper Returns self for method chaining. - */ - public function or_group_start() - { - return $this->group_start('', 'OR '); - } - - // -------------------------------------------------------------------- - - /** - * Starts a query group, but NOTs the group - * @return DataMapper Returns self for method chaining. - */ - public function not_group_start() - { - return $this->group_start('NOT ', 'OR '); - } - - // -------------------------------------------------------------------- - - /** - * Starts a query group, but OR NOTs the group - * @return DataMapper Returns self for method chaining. - */ - public function or_not_group_start() - { - return $this->group_start('NOT ', 'OR '); - } - - // -------------------------------------------------------------------- - - /** - * Ends a query group. - * @return DataMapper Returns self for method chaining. - */ - public function group_end() - { - $this->db->ar_where[] = ')'; - $this->_where_group_started = FALSE; - return $this; - } - - // -------------------------------------------------------------------- - - /** - * protected function to convert the AND or OR prefix to '' when starting - * a group. - * - * @ignore - * @param object $type Current type value - * @return New type value - */ - protected function _get_prepend_type($type) - { - if($this->_where_group_started) - { - $type = ''; - $this->_where_group_started = FALSE; - } - return $type; - } - - // -------------------------------------------------------------------- - - /** - * Where - * - * Sets the WHERE portion of the query. - * Separates multiple calls with AND. - * - * Called by get_where() - * - * @param mixed $key A field or array of fields to check. - * @param mixed $value For a single field, the value to compare to. - * @param bool $escape If FALSE, the field is not escaped. - * @return DataMapper Returns self for method chaining. - */ - public function where($key, $value = NULL, $escape = TRUE) - { - return $this->_where($key, $value, 'AND ', $escape); - } - - // -------------------------------------------------------------------- - - /** - * Or Where - * - * Sets the WHERE portion of the query. - * Separates multiple calls with OR. - * - * @param mixed $key A field or array of fields to check. - * @param mixed $value For a single field, the value to compare to. - * @param bool $escape If FALSE, the field is not escaped. - * @return DataMapper Returns self for method chaining. - */ - public function or_where($key, $value = NULL, $escape = TRUE) - { - return $this->_where($key, $value, 'OR ', $escape); - } - - // -------------------------------------------------------------------- - - /** - * Where - * - * Called by where() or or_where(). - * - * @ignore - * @param mixed $key A field or array of fields to check. - * @param mixed $value For a single field, the value to compare to. - * @param string $type Type of addition (AND or OR) - * @param bool $escape If FALSE, the field is not escaped. - * @return DataMapper Returns self for method chaining. - */ - protected function _where($key, $value = NULL, $type = 'AND ', $escape = NULL) - { - if ( ! is_array($key)) - { - $key = array($key => $value); - } - foreach ($key as $k => $v) - { - $new_k = $this->add_table_name($k); - if ($new_k != $k) - { - $key[$new_k] = $v; - unset($key[$k]); - } - } - - $type = $this->_get_prepend_type($type); - - $this->db->_where($key, $value, $type, $escape); - - // For method chaining - return $this; - } - - // -------------------------------------------------------------------- - - /** - * Where In - * - * Sets the WHERE field IN ('item', 'item') SQL query joined with - * AND if appropriate. - * - * @param string $key A field to check. - * @param array $values An array of values to compare against - * @return DataMapper Returns self for method chaining. - */ - public function where_in($key = NULL, $values = NULL) - { - return $this->_where_in($key, $values); - } - - // -------------------------------------------------------------------- - - /** - * Or Where In - * - * Sets the WHERE field IN ('item', 'item') SQL query joined with - * OR if appropriate. - * - * @param string $key A field to check. - * @param array $values An array of values to compare against - * @return DataMapper Returns self for method chaining. - */ - public function or_where_in($key = NULL, $values = NULL) - { - return $this->_where_in($key, $values, FALSE, 'OR '); - } - - // -------------------------------------------------------------------- - - /** - * Where Not In - * - * Sets the WHERE field NOT IN ('item', 'item') SQL query joined with - * AND if appropriate. - * - * @param string $key A field to check. - * @param array $values An array of values to compare against - * @return DataMapper Returns self for method chaining. - */ - public function where_not_in($key = NULL, $values = NULL) - { - return $this->_where_in($key, $values, TRUE); - } - - // -------------------------------------------------------------------- - - /** - * Or Where Not In - * - * Sets the WHERE field NOT IN ('item', 'item') SQL query joined wuth - * OR if appropriate. - * - * @param string $key A field to check. - * @param array $values An array of values to compare against - * @return DataMapper Returns self for method chaining. - */ - public function or_where_not_in($key = NULL, $values = NULL) - { - return $this->_where_in($key, $values, TRUE, 'OR '); - } - - // -------------------------------------------------------------------- - - /** - * Where In - * - * Called by where_in(), or_where_in(), where_not_in(), or or_where_not_in(). - * - * @ignore - * @param string $key A field to check. - * @param array $values An array of values to compare against - * @param bool $not If TRUE, use NOT IN instead of IN. - * @param string $type The type of connection (AND or OR) - * @return DataMapper Returns self for method chaining. - */ - protected function _where_in($key = NULL, $values = NULL, $not = FALSE, $type = 'AND ') - { - $type = $this->_get_prepend_type($type); - - if ($values instanceOf DataMapper) - { - $arr = array(); - foreach ($values as $value) - { - $arr[] = $value->id; - } - $values = $arr; - } - $this->db->_where_in($this->add_table_name($key), $values, $not, $type); - - // For method chaining - return $this; - } - - // -------------------------------------------------------------------- - - /** - * Like - * - * Sets the %LIKE% portion of the query. - * Separates multiple calls with AND. - * - * @param mixed $field A field or array of fields to check. - * @param mixed $match For a single field, the value to compare to. - * @param string $side One of 'both', 'before', or 'after' - * @return DataMapper Returns self for method chaining. - */ - public function like($field, $match = '', $side = 'both') - { - return $this->_like($field, $match, 'AND ', $side); - } - - // -------------------------------------------------------------------- - - /** - * Not Like - * - * Sets the NOT LIKE portion of the query. - * Separates multiple calls with AND. - * - * @param mixed $field A field or array of fields to check. - * @param mixed $match For a single field, the value to compare to. - * @param string $side One of 'both', 'before', or 'after' - * @return DataMapper Returns self for method chaining. - */ - public function not_like($field, $match = '', $side = 'both') - { - return $this->_like($field, $match, 'AND ', $side, 'NOT'); - } - - // -------------------------------------------------------------------- - - /** - * Or Like - * - * Sets the %LIKE% portion of the query. - * Separates multiple calls with OR. - * - * @param mixed $field A field or array of fields to check. - * @param mixed $match For a single field, the value to compare to. - * @param string $side One of 'both', 'before', or 'after' - * @return DataMapper Returns self for method chaining. - */ - public function or_like($field, $match = '', $side = 'both') - { - return $this->_like($field, $match, 'OR ', $side); - } - - // -------------------------------------------------------------------- - - /** - * Or Not Like - * - * Sets the NOT LIKE portion of the query. - * Separates multiple calls with OR. - * - * @param mixed $field A field or array of fields to check. - * @param mixed $match For a single field, the value to compare to. - * @param string $side One of 'both', 'before', or 'after' - * @return DataMapper Returns self for method chaining. - */ - public function or_not_like($field, $match = '', $side = 'both') - { - return $this->_like($field, $match, 'OR ', $side, 'NOT'); - } - - // -------------------------------------------------------------------- - - /** - * ILike - * - * Sets the case-insensitive %LIKE% portion of the query. - * - * @param mixed $field A field or array of fields to check. - * @param mixed $match For a single field, the value to compare to. - * @param string $side One of 'both', 'before', or 'after' - * @return DataMapper Returns self for method chaining. - */ - public function ilike($field, $match = '', $side = 'both') - { - return $this->_like($field, $match, 'AND ', $side, '', TRUE); - } - - // -------------------------------------------------------------------- - - /** - * Not ILike - * - * Sets the case-insensitive NOT LIKE portion of the query. - * Separates multiple calls with AND. - * - * @param mixed $field A field or array of fields to check. - * @param mixed $match For a single field, the value to compare to. - * @param string $side One of 'both', 'before', or 'after' - * @return DataMapper Returns self for method chaining. - */ - public function not_ilike($field, $match = '', $side = 'both') - { - return $this->_like($field, $match, 'AND ', $side, 'NOT', TRUE); - } - - // -------------------------------------------------------------------- - - /** - * Or Like - * - * Sets the case-insensitive %LIKE% portion of the query. - * Separates multiple calls with OR. - * - * @param mixed $field A field or array of fields to check. - * @param mixed $match For a single field, the value to compare to. - * @param string $side One of 'both', 'before', or 'after' - * @return DataMapper Returns self for method chaining. - */ - public function or_ilike($field, $match = '', $side = 'both') - { - return $this->_like($field, $match, 'OR ', $side, '', TRUE); - } - - // -------------------------------------------------------------------- - - /** - * Or Not Like - * - * Sets the case-insensitive NOT LIKE portion of the query. - * Separates multiple calls with OR. - * - * @param mixed $field A field or array of fields to check. - * @param mixed $match For a single field, the value to compare to. - * @param string $side One of 'both', 'before', or 'after' - * @return DataMapper Returns self for method chaining. - */ - public function or_not_ilike($field, $match = '', $side = 'both') - { - return $this->_like($field, $match, 'OR ', $side, 'NOT', TRUE); - } - - // -------------------------------------------------------------------- - - /** - * _Like - * - * Private function to do actual work. - * NOTE: this does NOT use the built-in ActiveRecord LIKE function. - * - * @ignore - * @param mixed $field A field or array of fields to check. - * @param mixed $match For a single field, the value to compare to. - * @param string $type The type of connection (AND or OR) - * @param string $side One of 'both', 'before', or 'after' - * @param string $not 'NOT' or '' - * @param bool $no_case If TRUE, configure to ignore case. - * @return DataMapper Returns self for method chaining. - */ - protected function _like($field, $match = '', $type = 'AND ', $side = 'both', $not = '', $no_case = FALSE) - { - if ( ! is_array($field)) - { - $field = array($field => $match); - } - - foreach ($field as $k => $v) - { - $new_k = $this->add_table_name($k); - if ($new_k != $k) - { - $field[$new_k] = $v; - unset($field[$k]); - } - } - - // Taken from CodeIgniter's Active Record because (for some reason) - // it is stored separately that normal where statements. - - foreach ($field as $k => $v) - { - if($no_case) - { - $k = 'UPPER(' . $this->db->protect_identifiers($k) .')'; - $v = strtoupper($v); - } - $f = "$k $not LIKE"; - - if ($side == 'before') - { - $m = "%{$v}"; - } - elseif ($side == 'after') - { - $m = "{$v}%"; - } - else - { - $m = "%{$v}%"; - } - - $this->_where($f, $m, $type); - } - - // For method chaining - return $this; - } - - // -------------------------------------------------------------------- - - /** - * Group By - * - * Sets the GROUP BY portion of the query. - * - * @param string $by Field to group by - * @return DataMapper Returns self for method chaining. - */ - public function group_by($by) - { - $this->db->group_by($this->add_table_name($by)); - - // For method chaining - return $this; - } - - // -------------------------------------------------------------------- - - /** - * Having - * - * Sets the HAVING portion of the query. - * Separates multiple calls with AND. - * - * @param string $key Field to compare. - * @param string $value value to compare to. - * @param bool $escape If FALSE, don't escape the value. - * @return DataMapper Returns self for method chaining. - */ - public function having($key, $value = '', $escape = TRUE) - { - return $this->_having($key, $value, 'AND ', $escape); - } - - // -------------------------------------------------------------------- - - /** - * Or Having - * - * Sets the OR HAVING portion of the query. - * Separates multiple calls with OR. - * - * @param string $key Field to compare. - * @param string $value value to compare to. - * @param bool $escape If FALSE, don't escape the value. - * @return DataMapper Returns self for method chaining. - */ - public function or_having($key, $value = '', $escape = TRUE) - { - return $this->_having($key, $value, 'OR ', $escape); - } - - // -------------------------------------------------------------------- - - /** - * Having - * - * Sets the HAVING portion of the query. - * Separates multiple calls with AND. - * - * @ignore - * @param string $key Field to compare. - * @param string $value value to compare to. - * @param string $type Type of connection (AND or OR) - * @param bool $escape If FALSE, don't escape the value. - * @return DataMapper Returns self for method chaining. - */ - protected function _having($key, $value = '', $type = 'AND ', $escape = TRUE) - { - $this->db->_having($this->add_table_name($key), $value, $type, $escape); - - // For method chaining - return $this; - } - - // -------------------------------------------------------------------- - - /** - * Order By - * - * Sets the ORDER BY portion of the query. - * - * @param string $orderby Field to order by - * @param string $direction One of 'ASC' or 'DESC' Defaults to 'ASC' - * @return DataMapper Returns self for method chaining. - */ - public function order_by($orderby, $direction = '') - { - $this->db->order_by($this->add_table_name($orderby), $direction); - - // For method chaining - return $this; - } - - // -------------------------------------------------------------------- - - /** - * Adds in the defaut order_by items, if there are any, and - * order_by hasn't been overridden. - * @ignore - */ - protected function _handle_default_order_by() - { - if(empty($this->default_order_by)) - { - return; - } - $sel = $this->table . '.' . '*'; - $sel_protect = $this->db->protect_identifiers($sel); - // only add the items if there isn't an existing order_by, - // AND the select statement is empty or includes * or table.* or `table`.* - if(empty($this->db->ar_orderby) && - ( - empty($this->db->ar_select) || - in_array('*', $this->db->ar_select) || - in_array($sel_protect, $this->db->ar_select) || - in_array($sel, $this->db->ar_select) - - )) - { - foreach($this->default_order_by as $k => $v) { - if(is_int($k)) { - $k = $v; - $v = ''; - } - $k = $this->add_table_name($k); - $this->order_by($k, $v); - } - } - } - - // -------------------------------------------------------------------- - - /** - * Limit - * - * Sets the LIMIT portion of the query. - * - * @param integer $limit Limit the number of results. - * @param integer|NULL $offset Offset the results when limiting. - * @return DataMapper Returns self for method chaining. - */ - public function limit($value, $offset = '') - { - $this->db->limit($value, $offset); - - // For method chaining - return $this; - } - - // -------------------------------------------------------------------- - - /** - * Offset - * - * Sets the OFFSET portion of the query. - * - * @param integer $offset Offset the results when limiting. - * @return DataMapper Returns self for method chaining. - */ - public function offset($offset) - { - $this->db->offset($offset); - - // For method chaining - return $this; - } - - // -------------------------------------------------------------------- - - /** - * Start Cache - * - * Starts AR caching. - */ - public function start_cache() - { - $this->db->start_cache(); - } - - // -------------------------------------------------------------------- - - /** - * Stop Cache - * - * Stops AR caching. - */ - public function stop_cache() - { - $this->db->stop_cache(); - } - - // -------------------------------------------------------------------- - - /** - * Flush Cache - * - * Empties the AR cache. - */ - public function flush_cache() - { - $this->db->flush_cache(); - } - - // -------------------------------------------------------------------- - - - /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * * - * Transaction methods * - * * - * The following are methods used for transaction handling. * - * * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - - - // -------------------------------------------------------------------- - - /** - * Trans Off - * - * This permits transactions to be disabled at run-time. - * - */ - public function trans_off() - { - $this->db->trans_enabled = FALSE; - } - - // -------------------------------------------------------------------- - - /** - * Trans Strict - * - * When strict mode is enabled, if you are running multiple groups of - * transactions, if one group fails all groups will be rolled back. - * If strict mode is disabled, each group is treated autonomously, meaning - * a failure of one group will not affect any others. - * - * @param bool $mode Set to false to disable strict mode. - */ - public function trans_strict($mode = TRUE) - { - $this->db->trans_strict($mode); - } - - // -------------------------------------------------------------------- - - /** - * Trans Start - * - * Start a transaction. - * - * @param bool $test_mode Set to TRUE to only run a test (and not commit) - */ - public function trans_start($test_mode = FALSE) - { - $this->db->trans_start($test_mode); - } - - // -------------------------------------------------------------------- - - /** - * Trans Complete - * - * Complete a transaction. - * - * @return bool Success or Failure - */ - public function trans_complete() - { - return $this->db->trans_complete(); - } - - // -------------------------------------------------------------------- - - /** - * Trans Begin - * - * Begin a transaction. - * - * @param bool $test_mode Set to TRUE to only run a test (and not commit) - * @return bool Success or Failure - */ - public function trans_begin($test_mode = FALSE) - { - return $this->db->trans_begin($test_mode); - } - - // -------------------------------------------------------------------- - - /** - * Trans Status - * - * Lets you retrieve the transaction flag to determine if it has failed. - * - * @return bool Returns FALSE if the transaction has failed. - */ - public function trans_status() - { - return $this->_trans_status; - } - - // -------------------------------------------------------------------- - - /** - * Trans Commit - * - * Commit a transaction. - * - * @return bool Success or Failure - */ - public function trans_commit() - { - return $this->db->trans_commit(); - } - - // -------------------------------------------------------------------- - - /** - * Trans Rollback - * - * Rollback a transaction. - * - * @return bool Success or Failure - */ - public function trans_rollback() - { - return $this->db->trans_rollback(); - } - - // -------------------------------------------------------------------- - - /** - * Auto Trans Begin - * - * Begin an auto transaction if enabled. - * - */ - protected function _auto_trans_begin() - { - // Begin auto transaction - if ($this->auto_transaction) - { - $this->trans_begin(); - } - } - - // -------------------------------------------------------------------- - - /** - * Auto Trans Complete - * - * Complete an auto transaction if enabled. - * - * @param string $label Name for this transaction. - */ - protected function _auto_trans_complete($label = 'complete') - { - // Complete auto transaction - if ($this->auto_transaction) - { - // Check if successful - if (!$this->trans_complete()) - { - $rule = 'transaction'; - - // Get corresponding error from language file - if (FALSE === ($line = $this->lang->line($rule))) - { - $line = 'Unable to access the ' . $rule .' error message.'; - } - - // Add transaction error message - $this->error_message($rule, sprintf($line, $label)); - - // Set validation as failed - $this->valid = FALSE; - } - } - } - - // -------------------------------------------------------------------- - - - /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * * - * Related methods * - * * - * The following are methods used for managing related records. * - * * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - - // -------------------------------------------------------------------- - - /** - * get_related_properties - * - * Located the relationship properties for a given field or model - * Can also optionally attempt to convert the $related_field to - * singular, and look up on that. It will modify the $related_field if - * the conversion to singular returns a result. - * - * $related_field can also be a deep relationship, such as - * 'post/editor/group', in which case the $related_field will be processed - * recursively, and the return value will be $user->has_NN['group']; - * - * @ignore - * @param mixed $related_field Name of related field or related object. - * @param bool $try_singular If TRUE, automatically tries to look for a singular name if not found. - * @return array Associative array of related properties. - */ - public function _get_related_properties(&$related_field, $try_singular = FALSE) - { - // Handle deep relationships - if(strpos($related_field, '/') !== FALSE) - { - $rfs = explode('/', $related_field); - $last = $this; - $prop = NULL; - foreach($rfs as &$rf) - { - $prop = $last->_get_related_properties($rf, $try_singular); - if(is_null($prop)) - { - break; - } - $last =& $last->_get_without_auto_populating($rf); - } - if( ! is_null($prop)) - { - // update in case any items were converted to singular. - $related_field = implode('/', $rfs); - } - return $prop; - } - else - { - if (isset($this->has_many[$related_field])) - { - return $this->has_many[$related_field]; - } - else if (isset($this->has_one[$related_field])) - { - return $this->has_one[$related_field]; - } - else - { - if($try_singular) - { - $rf = singular($related_field); - $ret = $this->_get_related_properties($rf); - if( is_null($ret)) - { - show_error("Unable to relate {$this->model} with $related_field."); - } - else - { - $related_field = $rf; - return $ret; - } - } - else - { - // not related - return NULL; - } - } - } - } - - // -------------------------------------------------------------------- - - /** - * Add Related Table - * - * Adds the table of a related item, and joins it to this class. - * Returns the name of that table for further queries. - * - * If $related_field is deep, then this adds all necessary relationships - * to the query. - * - * @ignore - * @param mixed $object The object (or related field) to look up. - * @param string $related_field Related field name for object - * @param string $id_only Private, do not use. - * @param object $db Private, do not use. - * @param array $query_related Private, do not use. - * @param string $name_prepend Private, do not use. - * @param string $this_table Private, do not use. - * @return string Name of the related table, or table.field if ID_Only - */ - public function _add_related_table($object, $related_field = '', $id_only = FALSE, $db = NULL, &$query_related = NULL, $name_prepend = '', $this_table = NULL) - { - if ( is_string($object)) - { - // only a model was passed in, not an object - $related_field = $object; - $object = NULL; - } - else if (empty($related_field)) - { - // model was not passed, so get the Object's native model - $related_field = $object->model; - } - - $related_field = strtolower($related_field); - - // Handle deep relationships - if(strpos($related_field, '/') !== FALSE) - { - $rfs = explode('/', $related_field); - $last = $this; - $prepend = ''; - $object_as = NULL; - foreach($rfs as $index => $rf) - { - // if this is the last item added, we can use the $id_only - // shortcut to prevent unnecessarily adding the last table. - $temp_id_only = $id_only; - if($temp_id_only) { - if($index < count($rfs)-1) { - $temp_id_only = FALSE; - } - } - $object_as = $last->_add_related_table($rf, '', $temp_id_only, $this->db, $this->_query_related, $prepend, $object_as); - $prepend .= $rf . '_'; - $last =& $last->_get_without_auto_populating($rf); - } - return $object_as; - } - - $related_properties = $this->_get_related_properties($related_field); - $class = $related_properties['class']; - $this_model = $related_properties['join_self_as']; - $other_model = $related_properties['join_other_as']; - - if (empty($object)) - { - // no object was passed in, so create one - $object = new $class(); - } - - if(is_null($query_related)) - { - $query_related =& $this->_query_related; - } - - if(is_null($this_table)) - { - $this_table = $this->table; - } - - // Determine relationship table name - $relationship_table = $this->_get_relationship_table($object, $related_field); - - // only add $related_field to the table name if the 'class' and 'related_field' aren't equal - // and the related object is in a different table - if ( ($class == $related_field) && ($this->table != $object->table) ) - { - $object_as = $name_prepend . $object->table; - $relationship_as = $name_prepend . $relationship_table; - } - else - { - $object_as = $name_prepend . $related_field . '_' . $object->table; - $relationship_as = $name_prepend . $related_field . '_' . $relationship_table; - } - - $other_column = $other_model . '_id'; - $this_column = $this_model . '_id' ; - - - if(is_null($db)) { - $db = $this->db; - } - - // Force the selection of the current object's columns - if (empty($db->ar_select)) - { - $db->select($this->table . '.*'); - } - - // the extra in_array column check is for has_one self references - if ($relationship_table == $this->table && in_array($other_column, $this->fields)) - { - // has_one relationship without a join table - if($id_only) - { - // nothing to join, just return the correct data - $object_as = $this_table . '.' . $other_column; - } - else if ( ! in_array($object_as, $query_related)) - { - $db->join($object->table . ' ' .$object_as, $object_as . '.id = ' . $this_table . '.' . $other_column, 'LEFT OUTER'); - $query_related[] = $object_as; - } - } - // the extra in_array column check is for has_one self references - else if ($relationship_table == $object->table && in_array($this_column, $object->fields)) - { - // has_one relationship without a join table - if ( ! in_array($object_as, $query_related)) - { - $db->join($object->table . ' ' .$object_as, $this_table . '.id = ' . $object_as . '.' . $this_column, 'LEFT OUTER'); - $query_related[] = $object_as; - } - if($id_only) - { - // include the column name - $object_as .= '.id'; - } - } - else - { - // has_one or has_many with a normal join table - - // Add join if not already included - if ( ! in_array($relationship_as, $query_related)) - { - $db->join($relationship_table . ' ' . $relationship_as, $this_table . '.id = ' . $relationship_as . '.' . $this_column, 'LEFT OUTER'); - - if($this->_include_join_fields) { - $fields = $db->field_data($relationship_table); - foreach($fields as $key => $f) - { - if($f->name == $this_column || $f->name == $other_column) - { - unset($fields[$key]); - } - } - // add all other fields - $selection = ''; - foreach ($fields as $field) - { - $new_field = 'join_'.$field->name; - if (!empty($selection)) - { - $selection .= ', '; - } - $selection .= $relationship_as.'.'.$field->name.' AS '.$new_field; - } - $db->select($selection); - - // now reset the flag - $this->_include_join_fields = FALSE; - } - - $query_related[] = $relationship_as; - } - - if($id_only) - { - // no need to add the whole table - $object_as = $relationship_as . '.' . $other_column; - } - else if ( ! in_array($object_as, $query_related)) - { - // Add join if not already included - $db->join($object->table . ' ' . $object_as, $object_as . '.id = ' . $relationship_as . '.' . $other_column, 'LEFT OUTER'); - - $query_related[] = $object_as; - } - } - - return $object_as; - } - - // -------------------------------------------------------------------- - - /** - * Related - * - * Sets the specified related query. - * - * @ignore - * @param string $query Query String - * @param array $arguments Arguments to process - * @param mixed $extra Used to prevent escaping in special circumstances. - * @return DataMapper Returns self for method chaining. - */ - private function _related($query, $arguments = array(), $extra = NULL) - { - if ( ! empty($query) && ! empty($arguments)) - { - $object = $field = $value = NULL; - - $next_arg = 1; - - // Prepare model - if (is_object($arguments[0])) - { - $object = $arguments[0]; - $related_field = $object->model; - - // Prepare field and value - $field = (isset($arguments[1])) ? $arguments[1] : 'id'; - $value = (isset($arguments[2])) ? $arguments[2] : $object->id; - $next_arg = 3; - } - else - { - $related_field = $arguments[0]; - // the TRUE allows conversion to singular - $related_properties = $this->_get_related_properties($related_field, TRUE); - $class = $related_properties['class']; - // enables where_related_{model}($object) - if(isset($arguments[1]) && is_object($arguments[1])) - { - $object = $arguments[1]; - // Prepare field and value - $field = (isset($arguments[2])) ? $arguments[2] : 'id'; - $value = (isset($arguments[3])) ? $arguments[3] : $object->id; - $next_arg = 4; - } - else - { - $object = new $class(); - // Prepare field and value - $field = (isset($arguments[1])) ? $arguments[1] : 'id'; - $value = (isset($arguments[2])) ? $arguments[2] : NULL; - $next_arg = 3; - } - } - - if($field == 'id') - { - // special case to prevent joining unecessary tables - $field = $this->_add_related_table($object, $related_field, TRUE); - } - else - { - // Determine relationship table name, and join the tables - $object_table = $this->_add_related_table($object, $related_field); - $field = $object_table . '.' . $field; - } - - if(is_string($value) && strpos($value, '${parent}') !== FALSE) { - $extra = FALSE; - } - - // allow special arguments to be passed into query methods - if(is_null($extra)) { - if(isset($arguments[$next_arg])) { - $extra = $arguments[$next_arg]; - } - } - - // Add query clause - if(is_null($extra)) - { - // convert where to where_in if the value is an array or a DM object - if ($query == 'where') - { - if ( is_array($value) ) - { - switch(count($value)) - { - case 0: - $value = NULL; - break; - case 1: - $value = reset($value); - break; - default: - $query = 'where_in'; - break; - } - } - elseif ( $value instanceOf DataMapper ) - { - switch($value->result_count()) - { - case 0: - $value = NULL; - break; - case 1: - $value = $value->id; - break; - default: - $query = 'where_in'; - break; - } - } - } - - $this->{$query}($field, $value); - } - else - { - $this->{$query}($field, $value, $extra); - } - } - - // For method chaining - return $this; - } - - // -------------------------------------------------------------------- - - /** - * Magic method to process a subquery for a related object. - * The format for this should be - * $object->{where}_related_subquery($related_item, $related_field, $subquery) - * related_field is optional - * - * @ignore - * @param string $query Query Method - * @param object $args Arguments for the query - * @return DataMapper Returns self for method chaining. - */ - private function _related_subquery($query, $args) - { - $rel_object = $args[0]; - $field = $value = NULL; - if(isset($args[2])) { - $field = $args[1]; - $value = $args[2]; - } else { - $field = 'id'; - $value = $args[1]; - } - if(is_object($value)) - { - // see 25_activerecord.php - $value = $this->_parse_subquery_object($value); - } - if(strpos($query, 'where_in') !== FALSE) { - $query = str_replace('_in', '', $query); - $field .= ' IN '; - } - return $this->_related($query, array($rel_object, $field, $value), FALSE); - } - - // -------------------------------------------------------------------- - - /** - * Is Related To - * If this object is related to the provided object, returns TRUE. - * Otherwise returns FALSE. - * Optionally can be provided a related field and ID. - * - * @param mixed $related_field The related object or field name - * @param int $id ID to compare to if $related_field is a string - * @return bool TRUE or FALSE if this object is related to $related_field - */ - public function is_related_to($related_field, $id = NULL) - { - if(is_object($related_field)) - { - $id = $related_field->id; - $related_field = $related_field->model; - } - return ($this->{$related_field}->count(NULL, NULL, $id) > 0); - } - - // -------------------------------------------------------------------- - - /** - * Include Related - * - * Joins specified values of a has_one object into the current query - * If $fields is NULL or '*', then all columns are joined (may require instantiation of the other object) - * If $fields is a single string, then just that column is joined. - * Otherwise, $fields should be an array of column names. - * - * $append_name can be used to override the default name to append, or set it to FALSE to prevent appending. - * - * @param mixed $related_field The related object or field name - * @param array $fields The fields to join (NULL or '*' means all fields, or use a single field or array of fields) - * @param bool $append_name The name to use for joining (with '_'), or FALSE to disable. - * @param bool $instantiate If TRUE, the results are instantiated into objects - * @return DataMapper Returns self for method chaining. - */ - public function include_related($related_field, $fields = NULL, $append_name = TRUE, $instantiate = FALSE) - { - if (is_object($related_field)) - { - $object = $related_field; - $related_field = $object->model; - $related_properties = $this->_get_related_properties($related_field); - } - else - { - // the TRUE allows conversion to singular - $related_properties = $this->_get_related_properties($related_field, TRUE); - $class = $related_properties['class']; - $object = new $class(); - } - - if(is_null($fields) || $fields == '*') - { - $fields = $object->fields; - } - else if ( ! is_array($fields)) - { - $fields = array((string)$fields); - } - - $rfs = explode('/', $related_field); - $last = $this; - foreach($rfs as $rf) - { - if ( ! isset($last->has_one[$rf]) ) - { - show_error("Invalid request to include_related: $rf is not a has_one relationship to {$last->model}."); - } - // prevent populating the related items. - $last =& $last->_get_without_auto_populating($rf); - } - - $table = $this->_add_related_table($object, $related_field); - - $append = ''; - if($append_name !== FALSE) - { - if($append_name === TRUE) - { - $append = str_replace('/', '_', $related_field); - } - else - { - $append = $append_name; - } - $append .= '_'; - } - - // now add fields - $selection = ''; - $property_map = array(); - foreach ($fields as $field) - { - $new_field = $append . $field; - // prevent collisions - if(in_array($new_field, $this->fields)) { - if($instantiate && $field == 'id' && $new_field != 'id') { - $property_map[$new_field] = $field; - } - continue; - } - if (!empty($selection)) - { - $selection .= ', '; - } - $selection .= $table.'.'.$field.' AS '.$new_field; - if($instantiate) { - $property_map[$new_field] = $field; - } - } - if(empty($selection)) - { - log_message('debug', "DataMapper Warning (include_related): No fields were selected for {$this->model} on $related_field."); - } - else - { - if($instantiate) - { - if(is_null($this->_instantiations)) - { - $this->_instantiations = array(); - } - $this->_instantiations[$related_field] = $property_map; - } - $this->db->select($selection); - } - - // For method chaining - return $this; - } - - /** - * Legacy version of include_related - * DEPRECATED: Will be removed by 2.0 - * @deprecated Please use include_related - */ - public function join_related($related_field, $fields = NULL, $append_name = TRUE) - { - return $this->include_related($related_field, $fields, $append_name); - } - - // -------------------------------------------------------------------- - - /** - * Includes the number of related items using a subquery. - * - * Default alias is {$related_field}_count - * - * @param mixed $related_field Field to count - * @param string $alias Alternative alias. - * @return DataMapper Returns self for method chaining. - */ - public function include_related_count($related_field, $alias = NULL) - { - if (is_object($related_field)) - { - $object = $related_field; - $related_field = $object->model; - $related_properties = $this->_get_related_properties($related_field); - } - else - { - // the TRUE allows conversion to singular - $related_properties = $this->_get_related_properties($related_field, TRUE); - $class = $related_properties['class']; - $object = new $class(); - } - - if(is_null($alias)) - { - $alias = $related_field . '_count'; - } - - // Force the selection of the current object's columns - if (empty($this->db->ar_select)) - { - $this->db->select($this->table . '.*'); - } - - // now generate a subquery for counting the related objects - $object->select_func('COUNT', '*', 'count'); - $this_rel = $related_properties['other_field']; - $tablename = $object->_add_related_table($this, $this_rel); - $object->where($tablename . '.id = ', $this->db->_escape_identifiers('${parent}.id'), FALSE); - $this->select_subquery($object, $alias); - return $this; - } - - // -------------------------------------------------------------------- - - /** - * Get Relation - * - * Finds all related records of this objects current record. - * - * @ignore - * @param mixed $related_field Related field or object - * @param int $id ID of related field or object - * @return bool Sucess or Failure - */ - private function _get_relation($related_field, $id) - { - // No related items - if (empty($related_field) || empty($id)) - { - // Reset query - $this->db->_reset_select(); - - return FALSE; - } - - // To ensure result integrity, group all previous queries - if( ! empty($this->db->ar_where)) - { - array_unshift($this->db->ar_where, '( '); - $this->db->ar_where[] = ' )'; - } - - // query all items related to the given model - $this->where_related($related_field, 'id', $id); - - return TRUE; - } - - // -------------------------------------------------------------------- - - /** - * Save Relation - * - * Saves the relation between this and the other object. - * - * @ignore - * @param DataMapper DataMapper Object to related to this object - * @param string Specific related field if necessary. - * @return bool Success or Failure - */ - protected function _save_relation($object, $related_field = '') - { - if (empty($related_field)) - { - $related_field = $object->model; - } - - // the TRUE allows conversion to singular - $related_properties = $this->_get_related_properties($related_field, TRUE); - - if ( ! empty($related_properties) && $this->exists() && $object->exists()) - { - $this_model = $related_properties['join_self_as']; - $other_model = $related_properties['join_other_as']; - $other_field = $related_properties['other_field']; - - // Determine relationship table name - $relationship_table = $this->_get_relationship_table($object, $related_field); - - if($relationship_table == $this->table && - // catch for self relationships. - in_array($other_model . '_id', $this->fields)) - { - $this->{$other_model . '_id'} = $object->id; - $ret = $this->save(); - // remove any one-to-one relationships with the other object - $this->_remove_other_one_to_one($related_field, $object); - return $ret; - } - else if($relationship_table == $object->table) - { - $object->{$this_model . '_id'} = $this->id; - $ret = $object->save(); - // remove any one-to-one relationships with this object - $object->_remove_other_one_to_one($other_field, $this); - return $ret; - } - else - { - $data = array($this_model . '_id' => $this->id, $other_model . '_id' => $object->id); - - // Check if relation already exists - $query = $this->db->get_where($relationship_table, $data, NULL, NULL); - - if ($query->num_rows() == 0) - { - // If this object has a "has many" relationship with the other object - if (isset($this->has_many[$related_field])) - { - // If the other object has a "has one" relationship with this object - if (isset($object->has_one[$other_field])) - { - // And it has an existing relation - $query = $this->db->get_where($relationship_table, array($other_model . '_id' => $object->id), 1, 0); - - if ($query->num_rows() > 0) - { - // Find and update the other objects existing relation to relate with this object - $this->db->where($other_model . '_id', $object->id); - $this->db->update($relationship_table, $data); - } - else - { - // Add the relation since one doesn't exist - $this->db->insert($relationship_table, $data); - } - - return TRUE; - } - else if (isset($object->has_many[$other_field])) - { - // We can add the relation since this specific relation doesn't exist, and a "has many" to "has many" relationship exists between the objects - $this->db->insert($relationship_table, $data); - - // Self relationships can be defined as reciprocal -- save the reverse relationship at the same time - if ($related_properties['reciprocal']) - { - $data = array($this_model . '_id' => $object->id, $other_model . '_id' => $this->id); - $this->db->insert($relationship_table, $data); - } - - return TRUE; - } - } - // If this object has a "has one" relationship with the other object - else if (isset($this->has_one[$related_field])) - { - // And it has an existing relation - $query = $this->db->get_where($relationship_table, array($this_model . '_id' => $this->id), 1, 0); - - if ($query->num_rows() > 0) - { - // Find and update the other objects existing relation to relate with this object - $this->db->where($this_model . '_id', $this->id); - $this->db->update($relationship_table, $data); - } - else - { - // Add the relation since one doesn't exist - $this->db->insert($relationship_table, $data); - } - - return TRUE; - } - } - else - { - // Relationship already exists - return TRUE; - } - } - } - else - { - if( ! $object->exists()) - { - $msg = 'dm_save_rel_noobj'; - } - else if( ! $this->exists()) - { - $msg = 'dm_save_rel_nothis'; - } - else - { - $msg = 'dm_save_rel_failed'; - } - $msg = $this->lang->line($msg); - $this->error_message($related_field, sprintf($msg, $related_field)); - } - - return FALSE; - } - - // -------------------------------------------------------------------- - - /** - * Remove Other One-to-One - * Removes other relationships on a one-to-one ITFK relationship - * - * @ignore - * @param string $rf Related field to look at. - * @param DataMapper $object Object to look at. - */ - private function _remove_other_one_to_one($rf, $object) - { - if( ! $object->exists()) - { - return; - } - $related_properties = $this->_get_related_properties($rf, TRUE); - if( ! array_key_exists($related_properties['other_field'], $object->has_one)) - { - return; - } - // This should be a one-to-one relationship with an ITFK if we got this far. - $other_column = $related_properties['join_other_as'] . '_id'; - $c = get_class($this); - $update = new $c(); - - $update->where($other_column, $object->id); - if($this->exists()) - { - $update->where('id <>', $this->id); - } - $update->update($other_column, NULL); - } - - // -------------------------------------------------------------------- - - /** - * Delete Relation - * - * Deletes the relation between this and the other object. - * - * @ignore - * @param DataMapper $object Object to remove the relationship to. - * @param string $related_field Optional specific related field - * @return bool Success or Failure - */ - protected function _delete_relation($object, $related_field = '') - { - if (empty($related_field)) - { - $related_field = $object->model; - } - - // the TRUE allows conversion to singular - $related_properties = $this->_get_related_properties($related_field, TRUE); - - if ( ! empty($related_properties) && ! empty($this->id) && ! empty($object->id)) - { - $this_model = $related_properties['join_self_as']; - $other_model = $related_properties['join_other_as']; - - // Determine relationship table name - $relationship_table = $this->_get_relationship_table($object, $related_field); - - if ($relationship_table == $this->table && - // catch for self relationships. - in_array($other_model . '_id', $this->fields)) - { - $this->{$other_model . '_id'} = NULL; - $this->save(); - } - else if ($relationship_table == $object->table) - { - $object->{$this_model . '_id'} = NULL; - $object->save(); - } - else - { - $data = array($this_model . '_id' => $this->id, $other_model . '_id' => $object->id); - - // Delete relation - $this->db->delete($relationship_table, $data); - - // Delete reverse direction if a reciprocal self relationship - if ($related_properties['reciprocal']) - { - $data = array($this_model . '_id' => $object->id, $other_model . '_id' => $this->id); - $this->db->delete($relationship_table, $data); - } - } - - // Clear related object so it is refreshed on next access - unset($this->{$related_field}); - - return TRUE; - } - - return FALSE; - } - - // -------------------------------------------------------------------- - - /** - * Get Relationship Table - * - * Determines the relationship table between this object and $object. - * - * @ignore - * @param DataMapper $object Object that we are interested in. - * @param string $related_field Optional specific related field. - * @return string The name of the table this relationship is stored on. - */ - public function _get_relationship_table($object, $related_field = '') - { - $prefix = $object->prefix; - $table = $object->table; - - if (empty($related_field)) - { - $related_field = $object->model; - } - - $related_properties = $this->_get_related_properties($related_field); - $this_model = $related_properties['join_self_as']; - $other_model = $related_properties['join_other_as']; - $other_field = $related_properties['other_field']; - - if (isset($this->has_one[$related_field])) - { - // see if the relationship is in this table - if (in_array($other_model . '_id', $this->fields)) - { - return $this->table; - } - } - - if (isset($object->has_one[$other_field])) - { - // see if the relationship is in this table - if (in_array($this_model . '_id', $object->fields)) - { - return $object->table; - } - } - - // was a join table defined for this relation? - if ( ! empty($related_properties['join_table']) ) - { - $relationship_table = $related_properties['join_table']; - } - else - { - $relationship_table = ''; - - // Check if self referencing - if ($this->table == $table) - { - // use the model names from related_properties - $p_this_model = plural($this_model); - $p_other_model = plural($other_model); - $relationship_table = ($p_this_model < $p_other_model) ? $p_this_model . '_' . $p_other_model : $p_other_model . '_' . $p_this_model; - } - else - { - $relationship_table = ($this->table < $table) ? $this->table . '_' . $table : $table . '_' . $this->table; - } - - // Remove all occurances of the prefix from the relationship table - $relationship_table = str_replace($prefix, '', str_replace($this->prefix, '', $relationship_table)); - - // So we can prefix the beginning, using the join prefix instead, if it is set - $relationship_table = (empty($this->join_prefix)) ? $this->prefix . $relationship_table : $this->join_prefix . $relationship_table; - } - - return $relationship_table; - } - - // -------------------------------------------------------------------- - - /** - * Count Related - * - * Returns the number of related items in the database and in the related object. - * Used by the _related_(required|min|max) validation rules. - * - * @ignore - * @param string $related_field The related field. - * @param mixed $object Object or array to include in the count. - * @return int Number of related items. - */ - protected function _count_related($related_field, $object = '') - { - $count = 0; - - // lookup relationship info - // the TRUE allows conversion to singular - $rel_properties = $this->_get_related_properties($related_field, TRUE); - $class = $rel_properties['class']; - - $ids = array(); - - if ( ! empty($object)) - { - $count = $this->_count_related_objects($related_field, $object, '', $ids); - $ids = array_unique($ids); - } - - if ( ! empty($related_field) && ! empty($this->id)) - { - $one = isset($this->has_one[$related_field]); - - // don't bother looking up relationships if this is a $has_one and we already have one. - if( (!$one) || empty($ids)) - { - // Prepare model - $object = new $class(); - - // Store parent data - $object->parent = array('model' => $rel_properties['other_field'], 'id' => $this->id); - - // pass in IDs to exclude from the count - - $count += $object->count($ids); - } - } - - return $count; - } - - // -------------------------------------------------------------------- - - /** - * Private recursive function to count the number of objects - * in a passed in array (or a single object) - * - * @ignore - * @param string $compare related field (model) to compare to - * @param mixed $object Object or array to count - * @param string $related_field related field of $object - * @param array $ids list of IDs we've already found. - * @return int Number of items found. - */ - private function _count_related_objects($compare, $object, $related_field, &$ids) - { - $count = 0; - if (is_array($object)) - { - // loop through array to check for objects - foreach ($object as $rel_field => $obj) - { - if ( ! is_string($rel_field)) - { - // if this object doesn't have a related field, use the parent related field - $rel_field = $related_field; - } - $count += $this->_count_related_objects($compare, $obj, $rel_field, $ids); - } - } - else - { - // if this object doesn't have a related field, use the model - if (empty($related_field)) - { - $related_field = $object->model; - } - // if this object is the same relationship type, it counts - if ($related_field == $compare && $object->exists()) - { - $ids[] = $object->id; - $count++; - } - } - return $count; - } - - // -------------------------------------------------------------------- - - /** - * Include Join Fields - * - * If TRUE, the any extra fields on the join table will be included - * - * @param bool $include If FALSE, turns back off the directive. - * @return DataMapper Returns self for method chaining. - */ - public function include_join_fields($include = TRUE) - { - $this->_include_join_fields = $include; - return $this; - } - - // -------------------------------------------------------------------- - - /** - * Set Join Field - * - * Sets the value on a join table based on the related field - * If $related_field is an array, then the array should be - * in the form $related_field => $object or array($object) - * - * @param mixed $related_field An object or array. - * @param mixed $field Field or array of fields to set. - * @param mixed $value Value for a single field to set. - * @param mixed $object Private for recursion, do not use. - * @return DataMapper Returns self for method chaining. - */ - public function set_join_field($related_field, $field, $value = NULL, $object = NULL) - { - $related_ids = array(); - - if (is_array($related_field)) - { - // recursively call this on the array passed in. - foreach ($related_field as $key => $object) - { - $this->set_join_field($key, $field, $value, $object); - } - return; - } - else if (is_object($related_field)) - { - $object = $related_field; - $related_field = $object->model; - $related_ids[] = $object->id; - $related_properties = $this->_get_related_properties($related_field); - } - else - { - // the TRUE allows conversion to singular - $related_properties = $this->_get_related_properties($related_field, TRUE); - if (is_null($object)) - { - $class = $related_properties['class']; - $object = new $class(); - } - } - - // Determine relationship table name - $relationship_table = $this->_get_relationship_table($object, $related_field); - - if (empty($object)) - { - // no object was passed in, so create one - $class = $related_properties['class']; - $object = new $class(); - } - - $this_model = $related_properties['join_self_as']; - $other_model = $related_properties['join_other_as']; - - if (! is_array($field)) - { - $field = array( $field => $value ); - } - - if ( ! is_array($object)) - { - $object = array($object); - } - - if (empty($object)) - { - $this->db->where($this_model . '_id', $this->id); - $this->db->update($relationship_table, $field); - } - else - { - foreach ($object as $obj) - { - $this->db->where($this_model . '_id', $this->id); - $this->db->where($other_model . '_id', $obj->id); - $this->db->update($relationship_table, $field); - } - } - - // For method chaining - return $this; - } - - // -------------------------------------------------------------------- - - /** - * Join Field - * - * Adds a query of a join table's extra field - * Accessed via __call - * - * @ignore - * @param string $query Query method. - * @param array $arguments Arguments for query. - * @return DataMapper Returns self for method chaining. - */ - private function _join_field($query, $arguments) - { - if ( ! empty($query) && count($arguments) >= 3) - { - $object = $field = $value = NULL; - - // Prepare model - if (is_object($arguments[0])) - { - $object = $arguments[0]; - $related_field = $object->model; - } - else - { - $related_field = $arguments[0]; - // the TRUE allows conversion to singular - $related_properties = $this->_get_related_properties($related_field, TRUE); - $class = $related_properties['class']; - $object = new $class(); - } - - - // Prepare field and value - $field = $arguments[1]; - $value = $arguments[2]; - - // Determine relationship table name, and join the tables - $rel_table = $this->_get_relationship_table($object, $related_field); - - // Add query clause - $extra = NULL; - if(count($arguments) > 3) { - $extra = $arguments[3]; - } - if(is_null($extra)) { - $this->{$query}($rel_table . '.' . $field, $value); - } else { - $this->{$query}($rel_table . '.' . $field, $value, $extra); - } - } - - // For method chaining - return $this; - } - - // -------------------------------------------------------------------- - - - /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * * - * Related Validation methods * - * * - * The following are methods used to validate the * - * relationships of this object. * - * * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - - - // -------------------------------------------------------------------- - - /** - * Related Required (pre-process) - * - * Checks if the related object has the required related item - * or if the required relation already exists. - * - * @ignore - */ - protected function _related_required($object, $model) - { - return ($this->_count_related($model, $object) == 0) ? FALSE : TRUE; - } - - // -------------------------------------------------------------------- - - /** - * Related Min Size (pre-process) - * - * Checks if the value of a property is at most the minimum size. - * - * @ignore - */ - protected function _related_min_size($object, $model, $size = 0) - { - return ($this->_count_related($model, $object) < $size) ? FALSE : TRUE; - } - - // -------------------------------------------------------------------- - - /** - * Related Max Size (pre-process) - * - * Checks if the value of a property is at most the maximum size. - * - * @ignore - */ - protected function _related_max_size($object, $model, $size = 0) - { - return ($this->_count_related($model, $object) > $size) ? FALSE : TRUE; - } - - // -------------------------------------------------------------------- - - - /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * * - * Validation methods * - * * - * The following are methods used to validate the * - * values of this objects properties. * - * * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - - - // -------------------------------------------------------------------- - - /** - * Always Validate - * - * Does nothing, but forces a validation even if empty (for non-required fields) - * - * @ignore - */ - protected function _always_validate() - { - } - - // -------------------------------------------------------------------- - - /** - * Alpha Dash Dot (pre-process) - * - * Alpha-numeric with underscores, dashes and full stops. - * - * @ignore - */ - protected function _alpha_dash_dot($field) - { - return ( ! preg_match('/^([\.-a-z0-9_-])+$/i', $this->{$field})) ? FALSE : TRUE; - } - - // -------------------------------------------------------------------- - - /** - * Alpha Slash Dot (pre-process) - * - * Alpha-numeric with underscores, dashes, forward slashes and full stops. - * - * @ignore - */ - protected function _alpha_slash_dot($field) - { - return ( ! preg_match('/^([\.\/-a-z0-9_-])+$/i', $this->{$field})) ? FALSE : TRUE; - } - - // -------------------------------------------------------------------- - - /** - * Matches (pre-process) - * - * Match one field to another. - * This replaces the version in CI_Form_validation. - * - * @ignore - */ - protected function _matches($field, $other_field) - { - return ($this->{$field} !== $this->{$other_field}) ? FALSE : TRUE; - } - - // -------------------------------------------------------------------- - - /** - * Min Date (pre-process) - * - * Checks if the value of a property is at least the minimum date. - * - * @ignore - */ - protected function _min_date($field, $date) - { - return (strtotime($this->{$field}) < strtotime($date)) ? FALSE : TRUE; - } - - // -------------------------------------------------------------------- - - /** - * Max Date (pre-process) - * - * Checks if the value of a property is at most the maximum date. - * - * @ignore - */ - protected function _max_date($field, $date) - { - return (strtotime($this->{$field}) > strtotime($date)) ? FALSE : TRUE; - } - - // -------------------------------------------------------------------- - - /** - * Min Size (pre-process) - * - * Checks if the value of a property is at least the minimum size. - * - * @ignore - */ - protected function _min_size($field, $size) - { - return ($this->{$field} < $size) ? FALSE : TRUE; - } - - // -------------------------------------------------------------------- - - /** - * Max Size (pre-process) - * - * Checks if the value of a property is at most the maximum size. - * - * @ignore - */ - protected function _max_size($field, $size) - { - return ($this->{$field} > $size) ? FALSE : TRUE; - } - - // -------------------------------------------------------------------- - - /** - * Unique (pre-process) - * - * Checks if the value of a property is unique. - * If the property belongs to this object, we can ignore it. - * - * @ignore - */ - protected function _unique($field) - { - if ( ! empty($this->{$field})) - { - $query = $this->db->get_where($this->table, array($field => $this->{$field}), 1, 0); - - if ($query->num_rows() > 0) - { - $row = $query->row(); - - // If unique value does not belong to this object - if ($this->id != $row->id) - { - // Then it is not unique - return FALSE; - } - } - } - - // No matches found so is unique - return TRUE; - } - - // -------------------------------------------------------------------- - - /** - * Unique Pair (pre-process) - * - * Checks if the value of a property, paired with another, is unique. - * If the properties belongs to this object, we can ignore it. - * - * @ignore - */ - protected function _unique_pair($field, $other_field = '') - { - if ( ! empty($this->{$field}) && ! empty($this->{$other_field})) - { - $query = $this->db->get_where($this->table, array($field => $this->{$field}, $other_field => $this->{$other_field}), 1, 0); - - if ($query->num_rows() > 0) - { - $row = $query->row(); - - // If unique pair value does not belong to this object - if ($this->id != $row->id) - { - // Then it is not a unique pair - return FALSE; - } - } - } - - // No matches found so is unique - return TRUE; - } - - // -------------------------------------------------------------------- - - /** - * Valid Date (pre-process) - * - * Checks whether the field value is a valid DateTime. - * - * @ignore - */ - protected function _valid_date($field) - { - // Ignore if empty - if (empty($this->{$field})) - { - return TRUE; - } - - $date = date_parse($this->{$field}); - - return checkdate($date['month'], $date['day'],$date['year']); - } - - // -------------------------------------------------------------------- - - /** - * Valid Date Group (pre-process) - * - * Checks whether the field value, grouped with other field values, is a valid DateTime. - * - * @ignore - */ - protected function _valid_date_group($field, $fields = array()) - { - // Ignore if empty - if (empty($this->{$field})) - { - return TRUE; - } - - $date = date_parse($this->{$fields['year']} . '-' . $this->{$fields['month']} . '-' . $this->{$fields['day']}); - - return checkdate($date['month'], $date['day'],$date['year']); - } - - // -------------------------------------------------------------------- - - /** - * Valid Match (pre-process) - * - * Checks whether the field value matches one of the specified array values. - * - * @ignore - */ - protected function _valid_match($field, $param = array()) - { - return in_array($this->{$field}, $param); - } - - // -------------------------------------------------------------------- - - /** - * Boolean (pre-process) - * - * Forces a field to be either TRUE or FALSE. - * Uses PHP's built-in boolean conversion. - * - * @ignore - */ - protected function _boolean($field) - { - $this->{$field} = (boolean)$this->{$field}; - } - - // -------------------------------------------------------------------- - - /** - * Encode PHP Tags (prep) - * - * Convert PHP tags to entities. - * This replaces the version in CI_Form_validation. - * - * @ignore - */ - protected function _encode_php_tags($field) - { - $this->{$field} = encode_php_tags($this->{$field}); - } - - // -------------------------------------------------------------------- - - /** - * Prep for Form (prep) - * - * Converts special characters to allow HTML to be safely shown in a form. - * This replaces the version in CI_Form_validation. - * - * @ignore - */ - protected function _prep_for_form($field) - { - $this->{$field} = $this->form_validation->prep_for_form($this->{$field}); - } - - // -------------------------------------------------------------------- - - /** - * Prep URL (prep) - * - * Adds "http://" to URLs if missing. - * This replaces the version in CI_Form_validation. - * - * @ignore - */ - protected function _prep_url($field) - { - $this->{$field} = $this->form_validation->prep_url($this->{$field}); - } - - // -------------------------------------------------------------------- - - /** - * Strip Image Tags (prep) - * - * Strips the HTML from image tags leaving the raw URL. - * This replaces the version in CI_Form_validation. - * - * @ignore - */ - protected function _strip_image_tags($field) - { - $this->{$field} = strip_image_tags($this->{$field}); - } - - // -------------------------------------------------------------------- - - /** - * XSS Clean (prep) - * - * Runs the data through the XSS filtering function, described in the Input Class page. - * This replaces the version in CI_Form_validation. - * - * @ignore - */ - protected function _xss_clean($field, $is_image = FALSE) - { - $this->{$field} = xss_clean($this->{$field}, $is_image); - } - - - // -------------------------------------------------------------------- - - /** - * Trim - * Custom trim rule that ignores NULL values - * - * @ignore - */ - protected function _trim($field) { - if( ! empty($this->{$field})) { - $this->{$field} = trim($this->{$field}); - } - } - - // -------------------------------------------------------------------- - - - /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * * - * Common methods * - * * - * The following are common methods used by other methods. * - * * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - - // -------------------------------------------------------------------- - - /** - * A specialized language lookup function that will automatically - * insert the model, table, and (optional) field into a key, and return the - * language result for the replaced key. - * - * @param string $key Basic key to use - * @param string $field Optional field value - * @return string|bool - */ - public function localize_by_model($key, $field = NULL) - { - $s = array('${model}', '${table}'); - $r = array($this->model, $this->table); - if(!is_null($field)) - { - $s[] = '${field}'; - $r[] = $field; - } - $key = str_replace($s, $r, $key); - return $this->lang->line($key); - } - - // -------------------------------------------------------------------- - - /** - * Variant that handles looking up a field labels - * @param string $field Name of field - * @param string|bool $label If not FALSE overrides default label. - * @return string|bool - */ - public function localize_label($field, $label = FALSE) - { - if($label === FALSE) - { - $label = $field; - if(!empty($this->field_label_lang_format)) - { - $label = $this->localize_by_model($this->field_label_lang_format, $field); - if($label === FALSE) - { - $label = $field; - } - } - } - else if(strpos($label, 'lang:') === 0) - { - $label = $this->localize_by_model(substr($label, 5), $field); - } - return $label; - } - - // -------------------------------------------------------------------- - - /** - * Allows you to define has_one relations at runtime - * @param string name of the model to make a relation with - * @param array optional, array with advanced relationship definitions - * @return bool - */ - public function has_one( $parm1 = NULL, $parm2 = NULL ) - { - if ( is_null($parm1) && is_null($parm2) ) - { - return FALSE; - } - elseif ( is_array($parm2) ) - { - return $this->_relationship('has_one', $parm2, $parm1); - } - else - { - return $this->_relationship('has_one', $parm1, 0); - } - } - - // -------------------------------------------------------------------- - - /** - * Allows you to define has_many relations at runtime - * @param string name of the model to make a relation with - * @param array optional, array with advanced relationship definitions - * @return bool - */ - public function has_many( $parm1 = NULL, $parm2 = NULL ) - { - if ( is_null($parm1) && is_null($parm2) ) - { - return FALSE; - } - elseif ( is_array($parm2) ) - { - return $this->_relationship('has_many', $parm2, $parm1); - } - else - { - return $this->_relationship('has_many', $parm1, 0); - } - } - - // -------------------------------------------------------------------- - - /** - * Creates or updates the production schema cache file for this model - * @param void - * @return void - */ - public function production_cache() - { - // if requested, store the item to the production cache - if( ! empty(DataMapper::$config['production_cache'])) - { - // check if it's a fully qualified path first - if (!is_dir($cache_folder = DataMapper::$config['production_cache'])) - { - // if not, it's relative to the application path - $cache_folder = APPPATH . DataMapper::$config['production_cache']; - } - if(file_exists($cache_folder) && is_dir($cache_folder) && is_writeable($cache_folder)) - { - $common_key = DataMapper::$common[DMZ_CLASSNAMES_KEY][strtolower(get_class($this))]; - $cache_file = $cache_folder . '/' . $common_key . EXT; - $cache = "<"."?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); \n"; - - $cache .= '$cache = ' . var_export(DataMapper::$common[$common_key], TRUE) . ';'; - - if ( ! $fp = @fopen($cache_file, 'w')) - { - show_error('Error creating production cache file: ' . $cache_file); - } - - flock($fp, LOCK_EX); - fwrite($fp, $cache); - flock($fp, LOCK_UN); - fclose($fp); - - @chmod($cache_file, FILE_WRITE_MODE); - } - } - } - - // -------------------------------------------------------------------- - - /** - * Define a new relationship for the current model - */ - protected function _relationship($type = '', $definition = array(), $name = 0) - { - // check the parameters - if (empty($type) OR ! in_array($type, array('has_one','has_many'))) - { - return FALSE; - } - - // allow for simple (old-style) associations - if (is_int($name)) - { - // delete the old style entry, we're going to convert it - if (isset($this->{$type}[$name])) - { - unset($this->{$type}[$name]); - } - $name = $definition; - } - - // get the current relationships - $new = (array) $this->{$type}; - - // convert value into array if necessary - if ( ! is_array($definition)) - { - $definition = array('class' => $definition); - } else if ( ! isset($definition['class'])) - { - // if already an array, ensure that the class attribute is set - $definition['class'] = $name; - } - if( ! isset($definition['other_field'])) - { - // add this model as the model to use in queries if not set - $definition['other_field'] = $this->model; - } - if( ! isset($definition['join_self_as'])) - { - // add this model as the model to use in queries if not set - $definition['join_self_as'] = $definition['other_field']; - } - if( ! isset($definition['join_other_as'])) - { - // add the key as the model to use in queries if not set - $definition['join_other_as'] = $name; - } - if( ! isset($definition['join_table'])) - { - // by default, automagically determine the join table name - $definition['join_table'] = ''; - } - if(isset($definition['reciprocal'])) - { - // only allow a reciprocal relationship to be defined if this is a has_many self relationship - $definition['reciprocal'] = ($definition['reciprocal'] && $type == 'has_many' && $definition['class'] == strtolower(get_class($this))); - } - else - { - $definition['reciprocal'] = FALSE; - } - $new[$name] = $definition; - - // load in labels for each not-already-set field - if(!isset($this->validation[$name])) - { - $label = $this->localize_label($name); - if(!empty($label)) - { - // label is re-set below, to prevent caching language-based labels - $this->validation[$name] = array('field' => $name, 'rules' => array()); - } - } - - // replace the old array - $this->{$type} = $new; - } - - // -------------------------------------------------------------------- - - /** - * To Array - * - * Converts this objects current record into an array for database queries. - * If validate is TRUE (getting by objects properties) empty objects are ignored. - * - * @ignore - * @param bool $validate - * @return array - */ - protected function _to_array($validate = FALSE) - { - $data = array(); - - foreach ($this->fields as $field) - { - if ($validate && ! isset($this->{$field})) - { - continue; - } - - $data[$field] = $this->{$field}; - } - - return $data; - } - - // -------------------------------------------------------------------- - - /** - * Process Query - * - * Converts a query result into an array of objects. - * Also updates this object - * - * @ignore - * @param CI_DB_result $query - */ - protected function _process_query($query) - { - if ($query->num_rows() > 0) - { - // Populate all with records as objects - $this->all = array(); - - $this->_to_object($this, $query->row()); - - // don't bother recreating the first item. - $index = ($this->all_array_uses_ids && isset($this->id)) ? $this->id : 0; - $this->all[$index] = $this->get_clone(); - - if($query->num_rows() > 1) - { - $model = get_class($this); - - $first = TRUE; - - foreach ($query->result() as $row) - { - if($first) - { - $first = FALSE; - continue; - } - - $item = new $model(); - - $this->_to_object($item, $row); - - if($this->all_array_uses_ids && isset($item->id)) - { - $this->all[$item->id] = $item; - } - else - { - $this->all[] = $item; - } - } - } - - // remove instantiations - $this->_instantiations = NULL; - - // free large queries - if($query->num_rows() > $this->free_result_threshold) - { - $query->free_result(); - } - } - else - { - // Refresh stored values is called by _to_object normally - $this->_refresh_stored_values(); - } - } - - // -------------------------------------------------------------------- - - /** - * To Object - * Copies the values from a query result row to an object. - * Also initializes that object by running get rules, and - * refreshing stored values on the object. - * - * Finally, if any "instantiations" are requested, those related objects - * are created off of query results - * - * This is only public so that the iterator can access it. - * - * @ignore - * @param DataMapper $item Item to configure - * @param object $row Query results - */ - public function _to_object($item, $row) - { - // Populate this object with values from first record - foreach ($row as $key => $value) - { - $item->{$key} = $value; - } - - foreach ($this->fields as $field) - { - if (! isset($row->{$field})) - { - $item->{$field} = NULL; - } - } - - // Force IDs to integers - foreach($this->_field_tracking['intval'] as $field) - { - if(isset($item->{$field})) - { - $item->{$field} = intval($item->{$field}); - } - } - - if (!empty($this->_field_tracking['get_rules'])) - { - $item->_run_get_rules(); - } - - $item->_refresh_stored_values(); - - if($this->_instantiations) { - foreach($this->_instantiations as $related_field => $field_map) { - // convert fields to a 'row' object - $row = new stdClass(); - foreach($field_map as $item_field => $c_field) { - $row->{$c_field} = $item->{$item_field}; - } - - // get the related item - $c =& $item->_get_without_auto_populating($related_field); - // set the values - $c->_to_object($c, $row); - - // also set up the ->all array - $c->all = array(); - $c->all[0] = $c->get_clone(); - } - } - } - - // -------------------------------------------------------------------- - - /** - * Run Get Rules - * - * Processes values loaded from the database - * - * @ignore - */ - protected function _run_get_rules() - { - // Loop through each property to be validated - foreach ($this->_field_tracking['get_rules'] as $field) - { - // Get validation settings - $rules = $this->validation[$field]['get_rules']; - - // only process non-empty keys that are not specifically - // set to be null - if( ! isset($this->{$field}) && ! in_array('allow_null', $rules)) - { - if(isset($this->has_one[$field])) - { - // automatically process $item_id values - $field = $field . '_id'; - if( ! isset($this->{$field}) && ! in_array('allow_null', $rules)) - { - continue; - } - } else { - continue; - } - } - - // Loop through each rule to validate this property against - foreach ($rules as $rule => $param) - { - // Check for parameter - if (is_numeric($rule)) - { - $rule = $param; - $param = ''; - } - if($rule == 'allow_null') - { - continue; - } - - if (method_exists($this, '_' . $rule)) - { - // Run rule from DataMapper or the class extending DataMapper - $result = $this->{'_' . $rule}($field, $param); - } - else if($this->_extension_method_exists('rule_' . $rule)) - { - // Run an extension-based rule. - $result = $this->{'rule_' . $rule}($field, $param); - } - else if (method_exists($this->form_validation, $rule)) - { - // Run rule from CI Form Validation - $result = $this->form_validation->{$rule}($this->{$field}, $param); - } - else if (function_exists($rule)) - { - // Run rule from PHP - $this->{$field} = $rule($this->{$field}); - } - } - } - } - - // -------------------------------------------------------------------- - - /** - * Refresh Stored Values - * - * Refreshes the stored values with the current values. - * - * @ignore - */ - protected function _refresh_stored_values() - { - // Update stored values - foreach ($this->fields as $field) - { - $this->stored->{$field} = $this->{$field}; - } - - // If there is a "matches" validation rule, match the field value with the other field value - foreach ($this->_field_tracking['matches'] as $field_name => $match_name) - { - $this->{$field_name} = $this->stored->{$field_name} = $this->{$match_name}; - } - } - - // -------------------------------------------------------------------- - - /** - * Assign Libraries - * - * Originally used by CodeIgniter, now just logs a warning. - * - * @ignore - */ - public function _assign_libraries() - { - log_message('debug', "Warning: A DMZ model ({$this->model}) was either loaded via autoload, or manually. DMZ automatically loads models, so this is unnecessary."); - } - - // -------------------------------------------------------------------- - - /** - * Assign Libraries - * - * Assigns required CodeIgniter libraries to DataMapper. - * - * @ignore - */ - protected function _dmz_assign_libraries() - { - $CI =& get_instance(); - if ($CI) - { - $this->lang = $CI->lang; - $this->load = $CI->load; - $this->config = $CI->config; - } - } - - // -------------------------------------------------------------------- - - /** - * Load Languages - * - * Loads required language files. - * - * @ignore - */ - protected function _load_languages() - { - - // Load the DataMapper language file - $this->lang->load('datamapper'); - } - - // -------------------------------------------------------------------- - - /** - * Load Helpers - * - * Loads required CodeIgniter helpers. - * - * @ignore - */ - protected function _load_helpers() - { - // Load inflector helper for singular and plural functions - $this->load->helper('inflector'); - - // Load security helper for prepping functions - $this->load->helper('security'); - } -} - -/** - * Simple class to prevent errors with unset fields. - * @package DMZ - * - * @param string $FIELD Get the error message for a given field or custom error - * @param string $RELATED Get the error message for a given relationship - * @param string $transaction Get the transaction error. - */ -class DM_Error_Object { - /** - * Array of all error messages. - * @var array - */ - public $all = array(); - - /** - * String containing entire error message. - * @var string - */ - public $string = ''; - - /** - * All unset fields are returned as empty strings by default. - * @ignore - * @param string $field - * @return string Empty string - */ - public function __get($field) { - return ''; - } -} - - - -/** - * Iterator for get_iterated - * - * @package DMZ - */ -class DM_DatasetIterator implements Iterator, Countable -{ - /** - * The parent DataMapper object that contains important info. - * @var DataMapper - */ - protected $parent; - /** - * The temporary DM object used in the loops. - * @var DataMapper - */ - protected $object; - /** - * Results array - * @var array - */ - protected $result; - /** - * Number of results - * @var int - */ - protected $count; - /** - * Current position - * @var int - */ - protected $pos; - - /** - * @param DataMapper $object Should be cloned ahead of time - * @param DB_result $query result from a CI DB query - */ - function __construct($object, $query) - { - // store the object as a main object - $this->parent = $object; - // clone the parent object, so it can be manipulated safely. - $this->object = $object->get_clone(); - - // Now get the information on the current query object - $this->result = $query->result(); - $this->count = count($this->result); - $this->pos = 0; - } - - /** - * Gets the item at the current index $pos - * @return DataMapper - */ - function current() - { - return $this->get($this->pos); - } - - function key() - { - return $this->pos; - } - - /** - * Gets the item at index $index - * @param int $index - * @return DataMapper - */ - function get($index) { - // clear to ensure that the item is not duplicating data - $this->object->clear(); - // set the current values on the object - $this->parent->_to_object($this->object, $this->result[$index]); - return $this->object; - } - - function next() - { - $this->pos++; - } - - function rewind() - { - $this->pos = 0; - } - - function valid() - { - return ($this->pos < $this->count); - } - - /** - * Returns the number of results - * @return int - */ - function count() - { - return $this->count; - } - - // Alias for count(); - function result_count() { - return $this->count; - } -} - - -// -------------------------------------------------------------------------- - -/** - * Autoload - * - * Autoloads object classes that are used with DataMapper. - * Must be at end due to implements IteratorAggregate... - */ -spl_autoload_register('DataMapper::autoload'); - -/* End of file datamapper.php */ -/* Location: ./application/models/datamapper.php */ +_assign_libraries(); + + $this->_load_languages(); + + $this->_load_helpers(); + + // Determine model name + if (empty($this->model)) + { + $this->model = singular(get_class($this)); + } + + // Load stored config settings by reference + foreach (array_keys(DataMapper::$config) as $key) + { + // Only if they're not already set + if (empty($this->{$key})) + { + $this->{$key} =& DataMapper::$config[$key]; + } + } + + // Load model settings if not in common storage + if ( ! array_key_exists($this->model, DataMapper::$common)) + { + // If model is 'datamapper' then this is the initial autoload by CodeIgniter + if ($this->model == 'datamapper') + { + // Load config settings + $this->config->load('datamapper', TRUE, TRUE); + + // Get and store config settings + DataMapper::$config = $this->config->item('datamapper'); + + return; + } + + // Determine table name + if (empty($this->table)) + { + $this->table = plural(get_class($this)); + } + + // Add prefix to table + $this->table = $this->prefix . $this->table; + + // Convert validation into associative array by field name + $associative_validation = array(); + + foreach ($this->validation as $validation) + { + // Populate associative validation array + $associative_validation[$validation['field']] = $validation; + } + + $this->validation = $associative_validation; + + // Get and store the table's field names and meta data + $fields = $this->db->field_data($this->table); + + // Store only the field names and ensure validation list includes all fields + foreach ($fields as $field) + { + // Populate fields array + $this->fields[] = $field->name; + + // Add validation if current field has none + if ( ! array_key_exists($field->name, $this->validation)) + { + $this->validation[$field->name] = array('field' => $field->name, 'label' => '', 'rules' => array()); + } + } + + // Store common model settings + DataMapper::$common[$this->model]['table'] = $this->table; + DataMapper::$common[$this->model]['fields'] = $this->fields; + DataMapper::$common[$this->model]['validation'] = $this->validation; + } + + // Load stored common model settings by reference + foreach (array_keys(DataMapper::$common[$this->model]) as $key) + { + $this->{$key} =& DataMapper::$common[$this->model][$key]; + } + + // Clear object properties to set at default values + $this->clear(); + } + + // -------------------------------------------------------------------- + + /** + * Autoload + * + * Autoloads object classes that are used with DataMapper. + * + * Note: + * It is important that they are autoloaded as loading them manually with + * CodeIgniter's loader class will cause DataMapper's __get and __set functions + * to not function. + * + * @access public + * @param string + * @return void + */ + static function autoload($class) + { + // Don't attempt to autoload CI_ or MY_ prefixed classes + if (in_array(substr($class, 0, 3), array('CI_', 'MY_'))) + { + return; + } + + // Prepare class + $class = strtolower($class); + + // Prepare path + $path = APPPATH . 'models'; + + // Prepare file + $file = $path . '/' . $class . EXT; + + // Check if file exists, require_once if it does + if (file_exists($file)) + { + require_once($file); + } + else + { + // Do a recursive search of the path for the class + DataMapper::recursive_require_once($class, $path); + } + } + + // -------------------------------------------------------------------- + + /** + * Recursive Require Once + * + * Recursively searches the path for the class, require_once if found. + * + * @access public + * @param string + * @param string + * @return void + */ + static function recursive_require_once($class, $path) + { + if ($handle = opendir($path)) + { + while (FALSE !== ($dir = readdir($handle))) + { + // If dir does not contain a dot + if (strpos($dir, '.') === FALSE) + { + // Prepare recursive path + $recursive_path = $path . '/' . $dir; + + // Prepare file + $file = $recursive_path . '/' . $class . EXT; + + // Check if file exists, require_once if it does + if (file_exists($file)) + { + require_once($file); + + break; + } + else if (is_dir($recursive_path)) + { + // Do a recursive search of the path for the class + DataMapper::recursive_require_once($class, $recursive_path); + } + } + } + + closedir($handle); + } + } + + // -------------------------------------------------------------------- + + + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Magic methods * + * * + * The following are methods to override the default PHP behaviour. * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + + + // -------------------------------------------------------------------- + + /** + * Set + * + * Sets the value of the named property. + * + * @access overload + * @param string + * @param string + * @return void + */ + function __set($name, $value) + { + $this->{$name} = $value; + } + + // -------------------------------------------------------------------- + + /** + * Get + * + * Returns the value of the named property. + * If named property is a related item, instantiate it first. + * + * @access overload + * @param string + * @return object + */ + function __get($name) + { + // Return value of the named property + if (isset($this->{$name})) + { + return $this->{$name}; + } + + $has_many = in_array($name, $this->has_many); + $has_one = in_array($name, $this->has_one); + + // If named property is a "has many" or "has one" related item + if ($has_many OR $has_one) + { + // Instantiate it before accessing + $model = ucfirst($name); + $this->{$name} = new $model(); + + // Store parent data + $this->{$name}->parent = array('model' => $this->model, 'id' => $this->id); + + // Check if Auto Populate for "has many" or "has one" is on + if (($has_many && $this->auto_populate_has_many) OR ($has_one && $this->auto_populate_has_one)) + { + $this->{$name}->get(); + } + + return $this->{$name}; + } + + return NULL; + } + + // -------------------------------------------------------------------- + + /** + * Call + * + * Calls the watched method. + * + * @access overload + * @param string + * @param string + * @return void + */ + function __call($method, $arguments) + { + // List of watched method names + $watched_methods = array('get_by_related_', 'get_by_related', 'get_by_', '_related_', '_related'); + + foreach ($watched_methods as $watched_method) + { + // See if called method is a watched method + if (strpos($method, $watched_method) !== FALSE) + { + $pieces = explode($watched_method, $method); + + if ( ! empty($pieces[0]) && ! empty($pieces[1])) + { + // Watched method is in the middle + return $this->{'_' . trim($watched_method, '_')}($pieces[0], array_merge(array($pieces[1]), $arguments)); + } + else + { + // Watched method is a prefix or suffix + return $this->{'_' . trim($watched_method, '_')}(str_replace($watched_method, '', $method), $arguments); + } + } + } + } + + // -------------------------------------------------------------------- + + /** + * Clone + * + * Allows for a less shallow clone than the default PHP clone. + * + * @access overload + * @return void + */ + function __clone() + { + foreach ($this as $key => $value) + { + if (is_object($value)) + { + $this->{$key} = clone($value); + } + } + } + + // -------------------------------------------------------------------- + + /** + * To String + * + * Converts the current object into a string. + * + * @access overload + * @return void + */ + function __toString() + { + return ucfirst($this->model); + } + + // -------------------------------------------------------------------- + + + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Main methods * + * * + * The following are methods that form the main * + * functionality of DataMapper. * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + + + // -------------------------------------------------------------------- + + /** + * Get + * + * Get objects. + * + * @access public + * @param integer + * @param integer + * @return object + */ + function get($limit = NULL, $offset = NULL) + { + // Check if this is a related object and if so, perform a related get + if ( ! empty($this->parent)) + { + // Set limit and offset + $this->limit($limit, $offset); + + // If this is a "has many" related item + if (in_array($this->parent['model'], $this->has_many)) + { + $this->_get_relation($this->parent['model'], $this->parent['id']); + } + + // If this is a "has one" related item + if (in_array($this->parent['model'], $this->has_one)) + { + $this->_get_relation($this->parent['model'], $this->parent['id']); + } + + // For method chaining + return $this; + } + + // Check if object has been validated + if ($this->validated) + { + // Reset validated + $this->validated = FALSE; + + // Use this objects properties + $data = $this->_to_array(TRUE); + + if ( ! empty($data)) + { + // Clear this object to make way for new data + $this->clear(); + + // Get by objects properties + $query = $this->db->get_where($this->table, $data, $limit, $offset); + + if ($query->num_rows() > 0) + { + // Populate all with records as objects + $this->all = $this->_to_object($query->result(), $this->model); + + // Populate this object with values from first record + foreach ($query->row() as $key => $value) + { + $this->{$key} = $value; + } + } + } + } + else + { + // Clear this object to make way for new data + $this->clear(); + + // Get by built up query + $query = $this->db->get($this->table, $limit, $offset); + + if ($query->num_rows() > 0) + { + // Populate all with records as objects + $this->all = $this->_to_object($query->result(), $this->model); + + // Populate this object with values from first record + foreach ($query->row() as $key => $value) + { + $this->{$key} = $value; + } + } + } + + $this->_refresh_stored_values(); + + // For method chaining + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Save + * + * Saves the current record. + * If object is supplied, saves relations between this object and the supplied object(s). + * + * @access public + * @param mixed + * @return bool + */ + function save($object = '') + { + // Temporarily store the success/failure + $result = array(); + + // Validate this objects properties + $this->validate($object); + + // If validation passed + if ($this->valid) + { + // Get current timestamp + $timestamp = ($this->local_time) ? date('Y-m-d H:i:s') : gmdate('Y-m-d H:i:s'); + + // Check if unix timestamp + $timestamp = ($this->unix_timestamp) ? strtotime($timestamp) : $timestamp; + + // Check if object has a 'created' field + if (in_array($this->created_field, $this->fields)) + { + // If created datetime is empty, set it + if (empty($this->{$this->created_field})) + { + $this->{$this->created_field} = $timestamp; + } + } + + // Check if object has an 'updated' field + if (in_array($this->updated_field, $this->fields)) + { + // Update updated datetime + $this->{$this->updated_field} = $timestamp; + } + + // Convert this object to array + $data = $this->_to_array(); + + if ( ! empty($data)) + { + if ( ! empty($data['id'])) + { + // Prepare data to send only changed fields + foreach ($data as $field => $value) + { + // Unset field from data if it hasn't been changed + if ($this->{$field} === $this->stored->{$field}) + { + unset($data[$field]); + } + } + + // Check if only the 'updated' field has changed, and if so, revert it + if (count($data) == 1 && isset($data[$this->updated_field])) + { + // Revert updated + $this->{$this->updated_field} = $this->stored->{$this->updated_field}; + + // Unset it + unset($data[$this->updated_field]); + } + + // Only go ahead with save if there is still data + if ( ! empty($data)) + { + // Begin auto transaction + $this->_auto_trans_begin(); + + // Update existing record + $this->db->where('id', $this->id); + $this->db->update($this->table, $data); + + // Complete auto transaction + $this->_auto_trans_complete('save (update)'); + } + + // Reset validated + $this->validated = FALSE; + + $result[] = TRUE; + } + else + { + // Prepare data to send only populated fields + foreach ($data as $field => $value) + { + // Unset field from data + if ( ! isset($value)) + { + unset($data[$field]); + } + } + + // Begin auto transaction + $this->_auto_trans_begin(); + + // Create new record + $this->db->insert($this->table, $data); + + // Complete auto transaction + $this->_auto_trans_complete('save (insert)'); + + // Assign new ID + $this->id = $this->db->insert_id(); + + // Reset validated + $this->validated = FALSE; + + $result[] = TRUE; + } + } + + $this->_refresh_stored_values(); + + // Check if a relationship is being saved + if ( ! empty($object)) + { + // Check if it is an array of relationships + if (is_array($object)) + { + // Begin auto transaction + $this->_auto_trans_begin(); + + foreach ($object as $obj) + { + if (is_array($obj)) + { + foreach ($obj as $o) + { + $result[] = $this->_save_relation($o); + } + } + else + { + $result[] = $this->_save_relation($obj); + } + } + + // Complete auto transaction + $this->_auto_trans_complete('save (relationship)'); + } + else + { + // Begin auto transaction + $this->_auto_trans_begin(); + + // Temporarily store the success/failure + $result[] = $this->_save_relation($object); + + // Complete auto transaction + $this->_auto_trans_complete('save (relationship)'); + } + } + } + + // If no failure was recorded, return TRUE + return ( ! empty($result) && ! in_array(FALSE, $result)); + } + + // -------------------------------------------------------------------- + + /** + * Delete + * + * Deletes the current record. + * If object is supplied, deletes relations between this object and the supplied object(s). + * + * @access public + * @param mixed + * @return bool + */ + function delete($object = '') + { + if (empty($object)) + { + if ( ! empty($this->id)) + { + // Begin auto transaction + $this->_auto_trans_begin(); + + // Delete this object + $this->db->where('id', $this->id); + $this->db->delete($this->table); + + // Delete all "has many" relations for this object + foreach ($this->has_many as $model) + { + // Prepare model + $model = ucfirst($model); + $object = new $model(); + + // Determine relationship table name + $relationship_table = $this->_get_relationship_table($object->prefix, $object->table, $object->model); + + $data = array($this->model . '_id' => $this->id); + + // Delete relation + $this->db->delete($relationship_table, $data); + } + + // Delete all "has one" relations for this object + foreach ($this->has_one as $model) + { + // Prepare model + $model = ucfirst($model); + $object = new $model(); + + // Determine relationship table name + $relationship_table = $this->_get_relationship_table($object->prefix, $object->table, $object->model); + + $data = array($this->model . '_id' => $this->id); + + // Delete relation + $this->db->delete($relationship_table, $data); + } + + // Complete auto transaction + $this->_auto_trans_complete('delete'); + + // Clear this object + $this->clear(); + + return TRUE; + } + } + else if (is_array($object)) + { + // Begin auto transaction + $this->_auto_trans_begin(); + + // Temporarily store the success/failure + $result = array(); + + foreach ($object as $obj) + { + if (is_array($obj)) + { + foreach ($obj as $o) + { + $result[] = $this->_delete_relation($o); + } + } + else + { + $result[] = $this->_delete_relation($obj); + } + } + + // Complete auto transaction + $this->_auto_trans_complete('delete (relationship)'); + + // If no failure was recorded, return TRUE + if ( ! in_array(FALSE, $result)) + { + return TRUE; + } + } + else + { + // Begin auto transaction + $this->_auto_trans_begin(); + + // Temporarily store the success/failure + $result = $this->_delete_relation($object); + + // Complete auto transaction + $this->_auto_trans_complete('delete (relationship)'); + + return $result; + } + + return FALSE; + } + + // -------------------------------------------------------------------- + + /** + * Delete All + * + * Deletes all records in this objects all list. + * + * @access public + * @return bool + */ + function delete_all() + { + if ( ! empty($this->all)) + { + foreach ($this->all as $item) + { + if ( ! empty($item->id)) + { + $item->delete(); + } + } + + $this->clear(); + + return TRUE; + } + + return FALSE; + } + + // -------------------------------------------------------------------- + + /** + * Refresh All + * + * Removes any empty objects in this objects all list. + * Only needs to be used if you are looping through the all list + * a second time and you have deleted a record the first time through. + * + * @access public + * @return bool + */ + function refresh_all() + { + if ( ! empty($this->all)) + { + $all = array(); + + foreach ($this->all as $item) + { + if ( ! empty($item->id)) + { + $all[] = $item; + } + } + + $this->all = $all; + + return TRUE; + } + + return FALSE; + } + + // -------------------------------------------------------------------- + + /** + * Validate + * + * Validates the value of each property against the assigned validation rules. + * + * @access public + * @param mixed + * @return object + */ + function validate($object = '') + { + // Return if validation has already been run + if ($this->validated) + { + // For method chaining + return $this; + } + + // Set validated as having been run + $this->validated = TRUE; + + // Clear errors + $this->error = new stdClass(); + $this->error->all = array(); + $this->error->string = ''; + + foreach ($this->fields as $field) + { + $this->error->{$field} = ''; + } + + // Loop through each property to be validated + foreach ($this->validation as $validation) + { + // Get validation settings + $field = $validation['field']; + $label = ( ! empty($validation['label'])) ? $validation['label'] : $field; + $rules = $validation['rules']; + + // Will validate differently if this is for a related item + $related = (in_array($field, $this->has_many) OR in_array($field, $this->has_one)); + + // Check if property has changed since validate last ran + if ($related OR ! isset($this->stored->{$field}) OR $this->{$field} !== $this->stored->{$field}) + { + // Only validate if field is related or required or has a value + if ( ! $related && ! in_array('required', $rules)) + { + if ( ! isset($this->{$field}) OR $this->{$field} === '') + { + continue; + } + } + + // Loop through each rule to validate this property against + foreach ($rules as $rule => $param) + { + // Check for parameter + if (is_numeric($rule)) + { + $rule = $param; + $param = ''; + } + + // Clear result + $result = ''; + + // Check rule exists + if ($related) + { + // Prepare rule to use different language file lines + $rule = 'related_' . $rule; + + if (method_exists($this->model, '_' . $rule)) + { + // Run related rule from DataMapper or the class extending DataMapper + $result = $this->{'_' . $rule}($object, $field, $param); + } + } + else if (method_exists($this->model, '_' . $rule)) + { + // Run rule from DataMapper or the class extending DataMapper + $result = $this->{'_' . $rule}($field, $param); + } + else if (method_exists($this->form_validation, $rule)) + { + // Run rule from CI Form Validation + $result = $this->form_validation->{$rule}($this->{$field}, $param); + } + else if (function_exists($rule)) + { + // Run rule from PHP + $this->{$field} = $rule($this->{$field}); + } + + // Add an error message if the rule returned FALSE + if ($result === FALSE) + { + // Get corresponding error from language file + if (FALSE === ($line = $this->lang->line($rule))) + { + $line = 'Unable to access an error message corresponding to your field name.'; + } + + // Check if param is an array + if (is_array($param)) + { + // Convert into a string so it can be used in the error message + $param = implode(', ', $param); + + // Replace last ", " with " or " + if (FALSE !== ($pos = strrpos($param, ', '))) + { + $param = substr_replace($param, ' or ', $pos, 2); + } + } + + // Check if param is a validation field + if (array_key_exists($param, $this->validation)) + { + // Change it to the label value + $param = $this->validation[$param]['label']; + } + + // Add error message + $this->error_message($field, sprintf($line, $label, $param)); + } + } + } + } + + // Set whether validation passed + $this->valid = empty($this->error->all); + + // For method chaining + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Clear + * + * Clears the current object. + * + * @access public + * @return void + */ + function clear() + { + // Clear the all list + $this->all = array(); + + // Clear errors + $this->error = new stdClass(); + $this->error->all = array(); + $this->error->string = ''; + + // Clear this objects properties and set blank error messages in case they are accessed + foreach ($this->fields as $field) + { + $this->{$field} = NULL; + $this->error->{$field} = ''; + } + + // Clear this objects "has many" related objects + foreach ($this->has_many as $related) + { + unset($this->{$related}); + } + + // Clear this objects "has one" related objects + foreach ($this->has_one as $related) + { + unset($this->{$related}); + } + + // Clear the query related list + $this->query_related = array(); + + // Clear and refresh stored values + $this->stored = new stdClass(); + + $this->_refresh_stored_values(); + } + + // -------------------------------------------------------------------- + + /** + * Count + * + * Returns the total count of the objects records. + * If on a related object, returns the total count of related objects records. + * + * @access public + * @return integer + */ + function count() + { + // Check if related object + if ( ! empty($this->parent)) + { + // Prepare model + $model = ucfirst($this->parent['model']); + $object = new $model(); + + // Determine relationship table name + $relationship_table = $this->_get_relationship_table($object->prefix, $object->table, $object->model); + + $this->db->where($this->parent['model'] . '_id', $this->parent['id']); + $this->db->from($relationship_table); + + // Return count + return $this->db->count_all_results(); + } + else + { + $this->db->from($this->table); + + // Return count + return $this->db->count_all_results(); + } + } + + // -------------------------------------------------------------------- + + /** + * Exists + * + * Returns TRUE if the current object has a database record. + * + * @access public + * @return bool + */ + function exists() + { + return ( ! empty($this->id)); + } + + // -------------------------------------------------------------------- + + /** + * Query + * + * Runs the specified query and populates the current object with the results. + * + * Warning: Use at your own risk. This will only be as reliable as your query. + * + * @access public + * @access string + * @access array + * @return void + */ + function query($sql, $binds = FALSE) + { + // Get by objects properties + $query = $this->db->query($sql, $binds); + + if ($query->num_rows() > 0) + { + // Populate all with records as objects + $this->all = $this->_to_object($query->result(), $this->model); + + // Populate this object with values from first record + foreach ($query->row() as $key => $value) + { + $this->{$key} = $value; + } + } + + $this->_refresh_stored_values(); + + // For method chaining + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Error Message + * + * Adds an error message to this objects error object. + * + * @access public + * @access string + * @access string + * @return void + */ + function error_message($field, $error) + { + if ( ! empty($field) && ! empty($error)) + { + // Set field specific error + $this->error->{$field} = $this->error_prefix . $error . $this->error_suffix; + + // Add field error to errors all list + $this->error->all[] = $this->error->{$field}; + + // Append field error to error message string + $this->error->string .= $this->error->{$field}; + } + } + + // -------------------------------------------------------------------- + + /** + * Get Clone + * + * Returns a clone of the current object. + * + * @access public + * @return object + */ + function get_clone() + { + return clone($this); + } + + // -------------------------------------------------------------------- + + /** + * Get Copy + * + * Returns an unsaved copy of the current object. + * + * @access public + * @return object + */ + function get_copy() + { + $copy = clone($this); + + $copy->id = NULL; + + return $copy; + } + + // -------------------------------------------------------------------- + + /** + * Get By + * + * Gets objects by specified field name and value. + * + * @access private + * @param string + * @param string + * @return object + */ + function _get_by($field, $value = array()) + { + if (isset($value[0])) + { + $this->where($field, $value[0]); + } + + return $this->get(); + } + + // -------------------------------------------------------------------- + + /** + * Get By Related + * + * Gets objects by specified related object and optionally by field name and value. + * + * @access private + * @param string + * @param mixed + * @return object + */ + function _get_by_related($model, $arguments = array()) + { + if ( ! empty($model)) + { + // Add model to start of arguments + $arguments = array_merge(array($model), $arguments); + } + + $this->_related('where', $arguments); + + return $this->get(); + } + + // -------------------------------------------------------------------- + + + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Active Record methods * + * * + * The following are methods used to provide Active Record * + * functionality for data retrieval. * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + + + // -------------------------------------------------------------------- + + /** + * Select + * + * Sets the SELECT portion of the query. + * + * @access public + * @param string + * @param bool + * @return object + */ + function select($select = '*', $escape = NULL) + { + // Check if this is a related object + if ( ! empty($this->parent)) + { + $this->db->select($this->table . '.' . $select, $escape); + } + else + { + $this->db->select($select, $escape); + } + + // For method chaining + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Select Max + * + * Sets the SELECT MAX(field) portion of a query. + * + * @access public + * @param string + * @param string + * @return object + */ + function select_max($select = '', $alias = '') + { + // Check if this is a related object + if ( ! empty($this->parent)) + { + $alias = ($alias != '') ? $alias : $select; + + $this->db->select_max($this->table . '.' . $select, $alias); + } + else + { + $this->db->select_max($select, $alias); + } + + // For method chaining + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Select Min + * + * Sets the SELECT MIN(field) portion of a query. + * + * @access public + * @param string + * @param string + * @return object + */ + function select_min($select = '', $alias = '') + { + // Check if this is a related object + if ( ! empty($this->parent)) + { + $alias = ($alias != '') ? $alias : $select; + + $this->db->select_min($this->table . '.' . $select, $alias); + } + else + { + $this->db->select_min($select, $alias); + } + + // For method chaining + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Select Avg + * + * Sets the SELECT AVG(field) portion of a query. + * + * @access public + * @param string + * @param string + * @return object + */ + function select_avg($select = '', $alias = '') + { + // Check if this is a related object + if ( ! empty($this->parent)) + { + $alias = ($alias != '') ? $alias : $select; + + $this->db->select_avg($this->table . '.' . $select, $alias); + } + else + { + $this->db->select_avg($select, $alias); + } + + // For method chaining + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Select Sum + * + * Sets the SELECT SUM(field) portion of a query. + * + * @access public + * @param string + * @param string + * @return object + */ + function select_sum($select = '', $alias = '') + { + // Check if this is a related object + if ( ! empty($this->parent)) + { + $alias = ($alias != '') ? $alias : $select; + + $this->db->select_sum($this->table . '.' . $select, $alias); + } + else + { + $this->db->select_sum($select, $alias); + } + + // For method chaining + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Distinct + * + * Sets the flag to add DISTINCT to the query. + * + * @access public + * @param bool + * @return object + */ + function distinct($value = TRUE) + { + $this->db->distinct($value); + + // For method chaining + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Get Where + * + * Get items matching the where clause. + * + * @access public + * @param mixed + * @param string + * @param string + * @return bool + */ + function get_where($where = array(), $limit = NULL, $offset = NULL) + { + $this->where($where); + + return $this->get($limit, $offset); + } + + // -------------------------------------------------------------------- + + /** + * Where + * + * Sets the WHERE portion of the query. + * Separates multiple calls with AND. + * + * Called by get_where() + * + * @access public + * @param mixed + * @param mixed + * @return object + */ + function where($key, $value = NULL, $escape = TRUE) + { + return $this->_where($key, $value, 'AND ', $escape); + } + + // -------------------------------------------------------------------- + + /** + * Or Where + * + * Sets the WHERE portion of the query. + * Separates multiple calls with OR. + * + * @access public + * @param mixed + * @param mixed + * @return object + */ + function or_where($key, $value = NULL, $escape = TRUE) + { + return $this->_where($key, $value, 'OR ', $escape); + } + + // -------------------------------------------------------------------- + + /** + * Where + * + * Called by where() or or_where(). + * + * @access private + * @param mixed + * @param mixed + * @param string + * @param bool + * @return object + */ + function _where($key, $value = NULL, $type = 'AND ', $escape = NULL) + { + if ( ! is_array($key)) + { + $key = array($key => $value); + } + + // Check if this is a related object + if ( ! empty($this->parent)) + { + foreach ($key as $k => $v) + { + $key[$this->table . '.' . $k] = $v; + unset($key[$k]); + } + } + + $this->db->_where($key, $value, $type, $escape); + + // For method chaining + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Where In + * + * Sets the WHERE field IN ('item', 'item') SQL query joined with + * AND if appropriate. + * + * @access public + * @param string + * @param array + * @return object + */ + function where_in($key = NULL, $values = NULL) + { + return $this->_where_in($key, $values); + } + + // -------------------------------------------------------------------- + + /** + * Or Where In + * + * Sets the WHERE field IN ('item', 'item') SQL query joined with + * OR if appropriate. + * + * @access public + * @param string + * @param array + * @return object + */ + function or_where_in($key = NULL, $values = NULL) + { + return $this->_where_in($key, $values, FALSE, 'OR '); + } + + // -------------------------------------------------------------------- + + /** + * Where Not In + * + * Sets the WHERE field NOT IN ('item', 'item') SQL query joined with + * AND if appropriate. + * + * @access public + * @param string + * @param array + * @return object + */ + function where_not_in($key = NULL, $values = NULL) + { + return $this->_where_in($key, $values, TRUE); + } + + // -------------------------------------------------------------------- + + /** + * Or Where Not In + * + * Sets the WHERE field NOT IN ('item', 'item') SQL query joined wuth + * OR if appropriate. + * + * @access public + * @param string + * @param array + * @return object + */ + function or_where_not_in($key = NULL, $values = NULL) + { + return $this->_where_in($key, $values, TRUE, 'OR '); + } + + // -------------------------------------------------------------------- + + /** + * Where In + * + * Called by where_in(), or_where_in(), where_not_in(), or or_where_not_in(). + * + * @access private + * @param string + * @param array + * @param bool + * @param string + * @return object + */ + function _where_in($key = NULL, $values = NULL, $not = FALSE, $type = 'AND ') + { + // Check if this is a related object + if ( ! empty($this->parent)) + { + $this->db->_where_in($this->table . '.' . $key, $values, $not, $type); + } + else + { + $this->db->_where_in($key, $values, $not, $type); + } + + // For method chaining + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Like + * + * Sets the %LIKE% portion of the query. + * Separates multiple calls with AND. + * + * @access public + * @param mixed + * @param mixed + * @param string + * @return object + */ + function like($field, $match = '', $side = 'both') + { + return $this->_like($field, $match, 'AND ', $side); + } + + // -------------------------------------------------------------------- + + /** + * Not Like + * + * Sets the NOT LIKE portion of the query. + * Separates multiple calls with AND. + * + * @access public + * @param mixed + * @param mixed + * @param string + * @return object + */ + function not_like($field, $match = '', $side = 'both') + { + return $this->_like($field, $match, 'AND ', $side, 'NOT'); + } + + // -------------------------------------------------------------------- + + /** + * Or Like + * + * Sets the %LIKE% portion of the query. + * Separates multiple calls with OR. + * + * @access public + * @param mixed + * @param mixed + * @param string + * @return object + */ + function or_like($field, $match = '', $side = 'both') + { + return $this->_like($field, $match, 'OR ', $side); + } + + // -------------------------------------------------------------------- + + /** + * Or Not Like + * + * Sets the NOT LIKE portion of the query. + * Separates multiple calls with OR. + * + * @access public + * @param mixed + * @param mixed + * @param string + * @return object + */ + function or_not_like($field, $match = '', $side = 'both') + { + return $this->_like($field, $match, 'OR ', $side, 'NOT'); + } + + // -------------------------------------------------------------------- + + /** + * Like + * + * Sets the %LIKE% portion of the query. + * Separates multiple calls with AND. + * + * @access private + * @param mixed + * @param mixed + * @param string + * @param string + * @param string + * @return object + */ + function _like($field, $match = '', $type = 'AND ', $side = 'both', $not = '') + { + if ( ! is_array($field)) + { + $field = array($field => $match); + } + + // Check if this is a related object + if ( ! empty($this->parent)) + { + foreach ($field as $k => $v) + { + $field[$this->table . '.' . $k] = $v; + unset($field[$k]); + } + } + + $this->db->_like($field, $match, $type, $side, $not); + + // For method chaining + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Group By + * + * Sets the GROUP BY portion of the query. + * + * @access public + * @param string + * @return object + */ + function group_by($by) + { + // Check if this is a related object + if ( ! empty($this->parent)) + { + $this->db->group_by($this->table . '.' . $by); + } + else + { + $this->db->group_by($by); + } + + // For method chaining + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Having + * + * Sets the HAVING portion of the query. + * Separates multiple calls with AND. + * + * @access public + * @param string + * @param string + * @param bool + * @return object + */ + function having($key, $value = '', $escape = TRUE) + { + return $this->_having($key, $value, 'AND ', $escape); + } + + // -------------------------------------------------------------------- + + /** + * Or Having + * + * Sets the OR HAVING portion of the query. + * Separates multiple calls with OR. + * + * @access public + * @param string + * @param string + * @param bool + * @return object + */ + function or_having($key, $value = '', $escape = TRUE) + { + return $this->_having($key, $value, 'OR ', $escape); + } + + // -------------------------------------------------------------------- + + /** + * Having + * + * Sets the HAVING portion of the query. + * Separates multiple calls with AND. + * + * @access private + * @param string + * @param string + * @param string + * @param bool + * @return object + */ + function _having($key, $value = '', $type = 'AND ', $escape = TRUE) + { + // Check if this is a related object + if ( ! empty($this->parent)) + { + $this->db->_having($this->table . '.' . $key, $value, $type, $escape); + } + else + { + $this->db->_having($key, $value, $type, $escape); + } + + // For method chaining + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Order By + * + * Sets the ORDER BY portion of the query. + * + * @access public + * @param string + * @param string + * @return object + */ + function order_by($orderby, $direction = '') + { + // Check if this is a related object + if ( ! empty($this->parent)) + { + $this->db->order_by($this->table . '.' . $orderby, $direction); + } + else + { + $this->db->order_by($orderby, $direction); + } + + // For method chaining + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Limit + * + * Sets the LIMIT portion of the query. + * + * @access public + * @param integer + * @param integer + * @return object + */ + function limit($value, $offset = '') + { + $this->db->limit($value, $offset); + + // For method chaining + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Offset + * + * Sets the OFFSET portion of the query. + * + * @access public + * @param integer + * @return object + */ + function offset($offset) + { + $this->db->offset($offset); + + // For method chaining + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Start Cache + * + * Starts AR caching. + * + * @access public + * @return void + */ + function start_cache() + { + $this->db->start_cache(); + } + + // -------------------------------------------------------------------- + + /** + * Stop Cache + * + * Stops AR caching. + * + * @access public + * @return void + */ + function stop_cache() + { + $this->db->stop_cache(); + } + + // -------------------------------------------------------------------- + + /** + * Flush Cache + * + * Empties the AR cache. + * + * @access public + * @return void + */ + function flush_cache() + { + $this->db->flush_cache(); + } + + // -------------------------------------------------------------------- + + + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Transaction methods * + * * + * The following are methods used for transaction handling. * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + + + // -------------------------------------------------------------------- + + /** + * Trans Off + * + * This permits transactions to be disabled at run-time. + * + * @access public + * @return void + */ + function trans_off() + { + $this->db->trans_enabled = FALSE; + } + + // -------------------------------------------------------------------- + + /** + * Trans Strict + * + * When strict mode is enabled, if you are running multiple groups of + * transactions, if one group fails all groups will be rolled back. + * If strict mode is disabled, each group is treated autonomously, meaning + * a failure of one group will not affect any others. + * + * @access public + * @param bool + * @return void + */ + function trans_strict($mode = TRUE) + { + $this->db->trans_strict($mode); + } + + // -------------------------------------------------------------------- + + /** + * Trans Start + * + * Start a transaction. + * + * @access public + * @param bool + * @return void + */ + function trans_start($test_mode = FALSE) + { + $this->db->trans_start($test_mode); + } + + // -------------------------------------------------------------------- + + /** + * Trans Complete + * + * Complete a transaction. + * + * @access public + * @return bool + */ + function trans_complete() + { + return $this->db->trans_complete(); + } + + // -------------------------------------------------------------------- + + /** + * Trans Begin + * + * Begin a transaction. + * + * @access public + * @param bool + * @return bool + */ + function trans_begin($test_mode = FALSE) + { + return $this->db->trans_begin($test_mode); + } + + // -------------------------------------------------------------------- + + /** + * Trans Status + * + * Lets you retrieve the transaction flag to determine if it has failed. + * + * @access public + * @return bool + */ + function trans_status() + { + return $this->_trans_status; + } + + // -------------------------------------------------------------------- + + /** + * Trans Commit + * + * Commit a transaction. + * + * @access public + * @return bool + */ + function trans_commit() + { + return $this->db->trans_commit(); + } + + // -------------------------------------------------------------------- + + /** + * Trans Rollback + * + * Rollback a transaction. + * + * @access public + * @return bool + */ + function trans_rollback() + { + return $this->db->trans_rollback(); + } + + // -------------------------------------------------------------------- + + /** + * Auto Trans Begin + * + * Begin an auto transaction if enabled. + * + * @access public + * @param bool + * @return bool + */ + function _auto_trans_begin() + { + // Begin auto transaction + if ($this->auto_transaction) + { + $this->trans_begin(); + } + } + + // -------------------------------------------------------------------- + + /** + * Auto Trans Complete + * + * Complete an auto transaction if enabled. + * + * @access public + * @param string + * @return bool + */ + function _auto_trans_complete($label = 'complete') + { + // Complete auto transaction + if ($this->auto_transaction) + { + // Check if successful + if (!$this->trans_complete()) + { + $rule = 'transaction'; + + // Get corresponding error from language file + if (FALSE === ($line = $this->lang->line($rule))) + { + $line = 'Unable to access the ' . $rule .' error message.'; + } + + // Add transaction error message + $this->error_message($rule, sprintf($line, $label)); + + // Set validation as failed + $this->valid = FALSE; + } + } + } + + // -------------------------------------------------------------------- + + + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Related methods * + * * + * The following are methods used for managing related records. * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + + // -------------------------------------------------------------------- + + /** + * Related + * + * Sets the specified related query. + * + * @access private + * @param string + * @param mixed + * @return object + */ + function _related($query, $arguments = array()) + { + if ( ! empty($query) && ! empty($arguments)) + { + $object = $field = $value = $option = NULL; + + // Prepare model + if (is_object($arguments[0])) + { + $object = $arguments[0]; + + // Prepare field and value + $field = (isset($arguments[1])) ? $arguments[1] : 'id'; + $value = (isset($arguments[2])) ? $arguments[2] : $object->id; + } + else + { + $model = ucfirst($arguments[0]); + $object = new $model(); + + // Prepare field and value + $field = (isset($arguments[1])) ? $arguments[1] : 'id'; + $value = (isset($arguments[2])) ? $arguments[2] : NULL; + } + + // Ignore if field and value is invalid + if ($field == 'id' && empty($value) OR $field != 'id' && $this->table == $object->table) + { + // For method chaining + return $this; + } + + // Determine relationship table name + $relationship_table = $this->_get_relationship_table($object->prefix, $object->table, $object->model); + + // Retrieve related records + if (empty($this->db->ar_select)) + { + $this->db->select($this->table . '.*'); + } + + // Check if self referencing + if ($this->table == $object->table) + { + // Add join if not already included + if ( ! in_array($object->model, $this->query_related)) + { + $this->db->join($relationship_table, $object->table . '.id = ' . $relationship_table . '.' . $this->model . '_id', 'left'); + + $this->query_related[] = $object->model; + } + + // Add query clause + $this->db->{$query}($relationship_table . '.' . $object->model . '_id', $value); + } + else + { + // Add join if not already included + if ( ! in_array($object->model, $this->query_related)) + { + $this->db->join($relationship_table, $this->table . '.id = ' . $relationship_table . '.' . $this->model . '_id', 'left'); + + $this->query_related[] = $object->model; + } + + // Add join if not already included + if ( ! in_array($object->table, $this->query_related)) + { + $this->db->join($object->table, $object->table . '.id = ' . $relationship_table . '.' . $object->model . '_id', 'left'); + + $this->query_related[] = $object->table; + } + + // Add query clause + $this->db->{$query}($object->table . '.' . $field, $value); + } + } + + // For method chaining + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Get Relation + * + * Finds all related records of this objects current record. + * + * @access private + * @param string + * @param integer + * @return void + */ + function _get_relation($model, $id) + { + // No related items + if (empty($model) OR empty($id)) + { + // Reset query + $this->db->_reset_select(); + + return; + } + + // Prepare model + $model = ucfirst($model); + $object = new $model(); + + // Determine relationship table name + $relationship_table = $this->_get_relationship_table($object->prefix, $object->table, $object->model); + + // Retrieve related records + if (empty($this->db->ar_select)) + { + $this->db->select($this->table . '.*'); + } + + // Check if self referencing + if ($this->table == $object->table) + { + $this->db->from($this->table); + $this->db->join($relationship_table, $object->table . '.id = ' . $relationship_table . '.' . $this->model . '_id', 'left'); + $this->db->where($relationship_table . '.' . $object->model . '_id = ' . $id); + } + else + { + $this->db->from($this->table); + $this->db->join($relationship_table, $this->table . '.id = ' . $relationship_table . '.' . $this->model . '_id', 'left'); + $this->db->join($object->table, $object->table . '.id = ' . $relationship_table . '.' . $object->model . '_id', 'left'); + $this->db->where($object->table . '.id = ' . $id); + } + + $query = $this->db->get(); + + // Clear this object to make way for new data + $this->clear(); + + if ($query->num_rows() > 0) + { + // Populate all with records as objects + $this->all = $this->_to_object($query->result(), $this->model); + + // Populate this object with values from first record + foreach ($query->row() as $key => $value) + { + $this->{$key} = $value; + } + } + + $this->_refresh_stored_values(); + } + + // -------------------------------------------------------------------- + + /** + * Save Relation + * + * Saves the relation between this and the other object. + * + * @access private + * @param object + * @return bool + */ + function _save_relation($object) + { + if ( ! empty($object->model) && ! empty($this->id) && ! empty($object->id)) + { + // Determine relationship table name + $relationship_table = $this->_get_relationship_table($object->prefix, $object->table, $object->model); + + $data = array($this->model . '_id' => $this->id, $object->model . '_id' => $object->id); + + // Check if relation already exists + $query = $this->db->get_where($relationship_table, $data, NULL, NULL); + + if ($query->num_rows() == 0) + { + // If this object has a "has many" relationship with the other object + if (in_array($object->model, $this->has_many)) + { + // If the other object has a "has one" relationship with this object + if (in_array($this->model, $object->has_one)) + { + // And it has an existing relation + $query = $this->db->get_where($relationship_table, array($object->model . '_id' => $object->id), 1, 0); + + if ($query->num_rows() > 0) + { + // Find and update the other objects existing relation to relate with this object + $this->db->where($object->model . '_id', $object->id); + $this->db->update($relationship_table, $data); + } + else + { + // Add the relation since one doesn't exist + $this->db->insert($relationship_table, $data); + } + + return TRUE; + } + else if (in_array($this->model, $object->has_many)) + { + // We can add the relation since this specific relation doesn't exist, and a "has many" to "has many" relationship exists between the objects + $this->db->insert($relationship_table, $data); + + return TRUE; + } + } + // If this object has a "has one" relationship with the other object + else if (in_array($object->model, $this->has_one)) + { + // And it has an existing relation + $query = $this->db->get_where($relationship_table, array($this->model . '_id' => $this->id), 1, 0); + + if ($query->num_rows() > 0) + { + // Find and update the other objects existing relation to relate with this object + $this->db->where($this->model . '_id', $this->id); + $this->db->update($relationship_table, $data); + } + else + { + // Add the relation since one doesn't exist + $this->db->insert($relationship_table, $data); + } + + return TRUE; + } + } + else + { + // Relationship already exists + return TRUE; + } + } + + return FALSE; + } + + // -------------------------------------------------------------------- + + /** + * Delete Relation + * + * Deletes the relation between this and the other object. + * + * @access private + * @param object + * @return bool + */ + function _delete_relation($object) + { + if ( ! empty($object->model) && ! empty($this->id) && ! empty($object->id)) + { + // Determine relationship table name + $relationship_table = $this->_get_relationship_table($object->prefix, $object->table, $object->model); + + $data = array($this->model . '_id' => $this->id, $object->model . '_id' => $object->id); + + // Delete relation + $this->db->delete($relationship_table, $data); + + // Clear related object so it is refreshed on next access + $this->{$object->model} = NULL; + + return TRUE; + } + + return FALSE; + } + + // -------------------------------------------------------------------- + + /** + * Get Relationship Table + * + * Determines the relationship table. + * + * @access private + * @param string + * @param string + * @param string + * @return string + */ + function _get_relationship_table($prefix, $table, $model) + { + $relationship_table = ''; + + // Check if self referencing + if ($this->table == $table) + { + $relationship_table = (plural($this->model) < plural($model)) ? plural($this->model) . '_' . plural($model) : plural($model) . '_' . plural($this->model); + } + else + { + $relationship_table = ($this->table < $table) ? $this->table . '_' . $table : $table . '_' . $this->table; + } + + // Remove all occurances of the prefix from the relationship table + $relationship_table = str_replace($prefix, '', str_replace($this->prefix, '', $relationship_table)); + + // So we can prefix the beginning, using the join prefix instead, if it is set + $relationship_table = (empty($this->join_prefix)) ? $this->prefix . $relationship_table : $this->join_prefix . $relationship_table; + + return $relationship_table; + } + + // -------------------------------------------------------------------- + + /** + * Count Related + * + * Returns the number of related items in the database and in the related object. + * + * @access private + * @param string + * @param mixed + * @return integer + */ + function _count_related($model, $object = '') + { + $count = 0; + + if ( ! empty($object)) + { + if (is_array($object)) + { + foreach ($object as $obj) + { + if (is_array($obj)) + { + foreach ($obj as $o) + { + if ($o->model == $model) + { + $count++; + } + } + } + else + { + if ($obj->model == $model) + { + $count++; + } + } + } + } + else + { + if ($object->model == $model) + { + $count++; + } + } + } + + if ( ! empty($model) && ! empty($this->id)) + { + // Prepare model + $model = ucfirst($model); + $object = new $model(); + + // Store parent data + $object->parent = array('model' => $this->model, 'id' => $this->id); + + $count += $object->count(); + } + + return $count; + } + + // -------------------------------------------------------------------- + + + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Related Validation methods * + * * + * The following are methods used to validate the * + * relationships of this object. * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + + + // -------------------------------------------------------------------- + + /** + * Related Required (pre-process) + * + * Checks if the related object has the required related item + * or if the required relation already exists. + * + * @access private + * @param mixed + * @param string + * @return bool + */ + function _related_required($object, $model) + { + return ($this->_count_related($model, $object) == 0) ? FALSE : TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Related Min Size (pre-process) + * + * Checks if the value of a property is at most the minimum size. + * + * @access private + * @param mixed + * @param string + * @param integer + * @return bool + */ + function _related_min_size($object, $model, $size = 0) + { + return ($this->_count_related($model, $object) < $size) ? FALSE : TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Related Max Size (pre-process) + * + * Checks if the value of a property is at most the maximum size. + * + * @access private + * @param mixed + * @param string + * @param integer + * @return bool + */ + function _related_max_size($object, $model, $size = 0) + { + return ($this->_count_related($model, $object) > $size) ? FALSE : TRUE; + } + + // -------------------------------------------------------------------- + + + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Validation methods * + * * + * The following are methods used to validate the * + * values of this objects properties. * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + + + // -------------------------------------------------------------------- + + /** + * Alpha Dash Dot (pre-process) + * + * Alpha-numeric with underscores, dashes and full stops. + * + * @access private + * @param string + * @return bool + */ + function _alpha_dash_dot($field) + { + return ( ! preg_match('/^([\.-a-z0-9_-])+$/i', $this->{$field})) ? FALSE : TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Alpha Slash Dot (pre-process) + * + * Alpha-numeric with underscores, dashes, forward slashes and full stops. + * + * @access private + * @param string + * @return bool + */ + function _alpha_slash_dot($field) + { + return ( ! preg_match('/^([\.\/-a-z0-9_-])+$/i', $this->{$field})) ? FALSE : TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Matches (pre-process) + * + * Match one field to another. + * This replaces the version in CI_Form_validation. + * + * @access private + * @param string + * @param string + * @return bool + */ + function _matches($field, $other_field) + { + return ($this->{$field} !== $this->{$other_field}) ? FALSE : TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Min Date (pre-process) + * + * Checks if the value of a property is at least the minimum date. + * + * @access private + * @param string + * @param string + * @return bool + */ + function _min_date($field, $date) + { + return (strtotime($this->{$field}) < strtotime($date)) ? FALSE : TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Max Date (pre-process) + * + * Checks if the value of a property is at most the maximum date. + * + * @access private + * @param string + * @param string + * @return bool + */ + function _max_date($field, $date) + { + return (strtotime($this->{$field}) > strtotime($date)) ? FALSE : TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Min Size (pre-process) + * + * Checks if the value of a property is at least the minimum size. + * + * @access private + * @param string + * @param integer + * @return bool + */ + function _min_size($field, $size) + { + return ($this->{$field} < $size) ? FALSE : TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Max Size (pre-process) + * + * Checks if the value of a property is at most the maximum size. + * + * @access private + * @param string + * @param integer + * @return bool + */ + function _max_size($field, $size) + { + return ($this->{$field} > $size) ? FALSE : TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Unique (pre-process) + * + * Checks if the value of a property is unique. + * If the property belongs to this object, we can ignore it. + * + * @access private + * @param string + * @return bool + */ + function _unique($field) + { + if ( ! empty($this->{$field})) + { + $query = $this->db->get_where($this->table, array($field => $this->{$field}), 1, 0); + + if ($query->num_rows() > 0) + { + $row = $query->row(); + + // If unique value does not belong to this object + if ($this->id != $row->id) + { + // Then it is not unique + return FALSE; + } + } + } + + // No matches found so is unique + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Unique Pair (pre-process) + * + * Checks if the value of a property, paired with another, is unique. + * If the properties belongs to this object, we can ignore it. + * + * @access private + * @param string + * @param string + * @return bool + */ + function _unique_pair($field, $other_field = '') + { + if ( ! empty($this->{$field}) && ! empty($this->{$other_field})) + { + $query = $this->db->get_where($this->table, array($field => $this->{$field}, $other_field => $this->{$other_field}), 1, 0); + + if ($query->num_rows() > 0) + { + $row = $query->row(); + + // If unique pair value does not belong to this object + if ($this->id != $row->id) + { + // Then it is not a unique pair + return FALSE; + } + } + } + + // No matches found so is unique + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Valid Date (pre-process) + * + * Checks whether the field value is a valid DateTime. + * + * @access private + * @param string + * @return bool + */ + function _valid_date($field) + { + // Ignore if empty + if (empty($this->{$field})) + { + return TRUE; + } + + $date = date_parse($this->{$field}); + + return checkdate($date['month'], $date['day'],$date['year']); + } + + // -------------------------------------------------------------------- + + /** + * Valid Date Group (pre-process) + * + * Checks whether the field value, grouped with other field values, is a valid DateTime. + * + * @access private + * @param string + * @param array + * @return bool + */ + function _valid_date_group($field, $fields = array()) + { + // Ignore if empty + if (empty($this->{$field})) + { + return TRUE; + } + + $date = date_parse($this->{$fields['year']} . '-' . $this->{$fields['month']} . '-' . $this->{$fields['day']}); + + return checkdate($date['month'], $date['day'],$date['year']); + } + + // -------------------------------------------------------------------- + + /** + * Valid Match (pre-process) + * + * Checks whether the field value matches one of the specified array values. + * + * @access private + * @param string + * @param array + * @return bool + */ + function _valid_match($field, $param = array()) + { + return in_array($this->{$field}, $param); + } + + // -------------------------------------------------------------------- + + /** + * Encode PHP Tags (prep) + * + * Convert PHP tags to entities. + * This replaces the version in CI_Form_validation. + * + * @access private + * @param string + * @return void + */ + function _encode_php_tags($field) + { + $this->{$field} = encode_php_tags($this->{$field}); + } + + // -------------------------------------------------------------------- + + /** + * Prep for Form (prep) + * + * Converts special characters to allow HTML to be safely shown in a form. + * This replaces the version in CI_Form_validation. + * + * @access private + * @param string + * @return void + */ + function _prep_for_form($field) + { + $this->{$field} = $this->form_validation->prep_for_form($this->{$field}); + } + + // -------------------------------------------------------------------- + + /** + * Prep URL (prep) + * + * Adds "http://" to URLs if missing. + * This replaces the version in CI_Form_validation. + * + * @access private + * @param string + * @return void + */ + function _prep_url($field) + { + $this->{$field} = $this->form_validation->prep_url($this->{$field}); + } + + // -------------------------------------------------------------------- + + /** + * Strip Image Tags (prep) + * + * Strips the HTML from image tags leaving the raw URL. + * This replaces the version in CI_Form_validation. + * + * @access private + * @param string + * @return void + */ + function _strip_image_tags($field) + { + $this->{$field} = strip_image_tags($this->{$field}); + } + + // -------------------------------------------------------------------- + + /** + * XSS Clean (prep) + * + * Runs the data through the XSS filtering function, described in the Input Class page. + * This replaces the version in CI_Form_validation. + * + * @access private + * @param string + * @param bool + * @return void + */ + function _xss_clean($field, $is_image = FALSE) + { + $this->{$field} = xss_clean($this->{$field}, $is_image); + } + + // -------------------------------------------------------------------- + + + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Common methods * + * * + * The following are common methods used by other methods. * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + + + // -------------------------------------------------------------------- + + /** + * To Array + * + * Converts this objects current record into an array for database queries. + * If validate is TRUE (getting by objects properties) empty objects are ignored. + * + * @access private + * @param bool + * @return array + */ + function _to_array($validate = FALSE) + { + $data = array(); + + foreach ($this->fields as $field) + { + if (empty($this->{$field}) && $validate) + { + continue; + } + + $data[$field] = $this->{$field}; + } + + return $data; + } + + // -------------------------------------------------------------------- + + /** + * To Object + * + * Converts the query result into an array of objects. + * + * @access private + * @param array + * @param string + * @return array + */ + function _to_object($result, $model) + { + $items = array(); + + foreach ($result as $row) + { + $item = new $model(); + + foreach ($this->fields as $field) + { + if (isset($row->{$field})) + { + $item->{$field} = $row->{$field}; + } + else + { + $item->{$field} = NULL; + } + } + + $item->_refresh_stored_values(); + + $items[$item->id] = $item; + } + + return $items; + } + + // -------------------------------------------------------------------- + + /** + * Refresh Stored Values + * + * Refreshes the stored values with the current values. + * + * @access private + * @return void + */ + function _refresh_stored_values() + { + // Update stored values + foreach ($this->fields as $field) + { + $this->stored->{$field} = $this->{$field}; + } + + // Check if there is a "matches" validation rule + foreach ($this->validation as $validation) + { + // If there is, match the field value with the other field value + if (array_key_exists('matches', $validation['rules'])) + { + $this->{$validation['field']} = $this->stored->{$validation['field']} = $this->{$validation['rules']['matches']}; + } + } + } + + // -------------------------------------------------------------------- + + /** + * Assign Libraries + * + * Assigns required CodeIgniter libraries to DataMapper. + * + * @access private + * @return void + */ + function _assign_libraries() + { + if ($CI =& get_instance()) + { + // Load CodeIgniters form validation if not already loaded + if ( ! isset($CI->form_validation)) + { + $CI->load->library('form_validation'); + } + + $this->form_validation = $CI->form_validation; + $this->lang = $CI->lang; + $this->load = $CI->load; + $this->db = $CI->db; + $this->config = $CI->config; + } + } + + // -------------------------------------------------------------------- + + /** + * Load Languages + * + * Loads required language files. + * + * @access private + * @return void + */ + function _load_languages() + { + // Load the form validation language file + $this->lang->load('form_validation'); + + // Load the DataMapper language file + $this->lang->load('datamapper'); + } + + // -------------------------------------------------------------------- + + /** + * Load Helpers + * + * Loads required CodeIgniter helpers. + * + * @access private + * @return void + */ + function _load_helpers() + { + // Load inflector helper for singular and plural functions + $this->load->helper('inflector'); + + // Load security helper for prepping functions + $this->load->helper('security'); + } +} + +/* End of file datamapper.php */ +/* Location: ./application/models/datamapper.php */ \ No newline at end of file