PageRenderTime 87ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 1ms

/application/libraries/datamapper.php

https://bitbucket.org/alexdeoliveira/ignitercms
PHP | 6756 lines | 3363 code | 828 blank | 2565 comment | 587 complexity | 82bfbef425fc5df97a8f8c842a8e15a6 MD5 | raw file
Possible License(s): BSD-3-Clause, GPL-2.0
  1. <?php
  2. /**
  3. * Data Mapper ORM Class
  4. *
  5. * Transforms database tables into objects.
  6. *
  7. * @license MIT License
  8. * @package DataMapper ORM
  9. * @category DataMapper ORM
  10. * @author Harro Verton
  11. * @author Phil DeJarnett (up to v1.7.1)
  12. * @author Simon Stenhouse (up to v1.6.0)
  13. * @link http://datamapper.wanwizard.eu/
  14. * @version 1.8.3-dev
  15. */
  16. /**
  17. * Key for storing pre-converted classnames
  18. */
  19. define('DMZ_CLASSNAMES_KEY', '_dmz_classnames');
  20. /**
  21. * DMZ version
  22. */
  23. define('DMZ_VERSION', '1.8.3-dev');
  24. /**
  25. * Data Mapper Class
  26. *
  27. * Transforms database tables into objects.
  28. *
  29. * @package DataMapper ORM
  30. *
  31. * Properties (for code completion)
  32. * @property CI_DB_driver $db The CodeIgniter Database Library
  33. * @property CI_Loader $load The CodeIgnter Loader Library
  34. * @property CI_Language $lang The CodeIgniter Language Library
  35. * @property CI_Config $config The CodeIgniter Config Library
  36. * @property CI_Form_validation $form_validation The CodeIgniter Form Validation Library
  37. *
  38. *
  39. * Define some of the magic methods:
  40. *
  41. * Get By:
  42. * @method DataMapper get_by_id() get_by_id(int $value) Looks up an item by its ID.
  43. * @method DataMapper get_by_FIELD() get_by_FIELD(mixed $value) Looks up an item by a specific FIELD. Ex: get_by_name($user_name);
  44. * @method DataMapper get_by_related() get_by_related(mixed $related, string $field = NULL, string $value = NULL) Get results based on a related item.
  45. * @method DataMapper get_by_related_RELATEDFIELD() get_by_related_RELATEDFIELD(string $field = NULL, string $value = NULL) Get results based on a RELATEDFIELD. Ex: get_by_related_user('id', $userid);
  46. *
  47. * Save and Delete
  48. * @method DataMapper save_RELATEDFIELD() save_RELATEDFIELD(mixed $object) Saves relationship(s) using the specified RELATEDFIELD. Ex: save_user($user);
  49. * @method DataMapper delete_RELATEDFIELD() delete_RELATEDFIELD(mixed $object) Deletes relationship(s) using the specified RELATEDFIELD. Ex: delete_user($user);
  50. *
  51. * Related:
  52. * @method DataMapper where_related() where_related(mixed $related, string $field = NULL, string $value = NULL) Limits results based on a related field.
  53. * @method DataMapper where_between_related() where_related(mixed $related, string $field = NULL, string $value1 = NULL, string $value2 = NULL) Limits results based on a related field, via BETWEEN.
  54. * @method DataMapper or_where_related() or_where_related(mixed $related, string $field = NULL, string $value = NULL) Limits results based on a related field, via OR.
  55. * @method DataMapper where_in_related() where_in_related(mixed $related, string $field, array $values) Limits results by comparing a related field to a range of values.
  56. * @method DataMapper or_where_in_related() or_where_in_related(mixed $related, string $field, array $values) Limits results by comparing a related field to a range of values.
  57. * @method DataMapper where_not_in_related() where_not_in_related(mixed $related, string $field, array $values) Limits results by comparing a related field to a range of values.
  58. * @method DataMapper or_where_not_in_related() or_where_not_in_related(mixed $related, string $field, array $values) Limits results by comparing a related field to a range of values.
  59. * @method DataMapper like_related() like_related(mixed $related, string $field, string $value, string $match = 'both') Limits results by matching a related field to a value.
  60. * @method DataMapper or_like_related() like_related(mixed $related, string $field, string $value, string $match = 'both') Limits results by matching a related field to a value.
  61. * @method DataMapper not_like_related() like_related(mixed $related, string $field, string $value, string $match = 'both') Limits results by matching a related field to a value.
  62. * @method DataMapper or_not_like_related() like_related(mixed $related, string $field, string $value, string $match = 'both') Limits results by matching a related field to a value.
  63. * @method DataMapper ilike_related() like_related(mixed $related, string $field, string $value, string $match = 'both') Limits results by matching a related field to a value (case insensitive).
  64. * @method DataMapper or_ilike_related() like_related(mixed $related, string $field, string $value, string $match = 'both') Limits results by matching a related field to a value (case insensitive).
  65. * @method DataMapper not_ilike_related() like_related(mixed $related, string $field, string $value, string $match = 'both') Limits results by matching a related field to a value (case insensitive).
  66. * @method DataMapper or_not_ilike_related() like_related(mixed $related, string $field, string $value, string $match = 'both') Limits results by matching a related field to a value (case insensitive).
  67. * @method DataMapper group_by_related() group_by_related(mixed $related, string $field) Groups the query by a related field.
  68. * @method DataMapper having_related() having_related(mixed $related, string $field, string $value) Groups the querying using a HAVING clause.
  69. * @method DataMapper or_having_related() having_related(mixed $related, string $field, string $value) Groups the querying using a HAVING clause, via OR.
  70. * @method DataMapper order_by_related() order_by_related(mixed $related, string $field, string $direction) Orders the query based on a related field.
  71. *
  72. *
  73. * Join Fields:
  74. * @method DataMapper where_join_field() where_join_field(mixed $related, string $field = NULL, string $value = NULL) Limits results based on a join field.
  75. * @method DataMapper where_between_join_field() where_related(mixed $related, string $field = NULL, string $value1 = NULL, string $value2 = NULL) Limits results based on a join field, via BETWEEN.
  76. * @method DataMapper or_where_join_field() or_where_join_field(mixed $related, string $field = NULL, string $value = NULL) Limits results based on a join field, via OR.
  77. * @method DataMapper where_in_join_field() where_in_join_field(mixed $related, string $field, array $values) Limits results by comparing a join field to a range of values.
  78. * @method DataMapper or_where_in_join_field() or_where_in_join_field(mixed $related, string $field, array $values) Limits results by comparing a join field to a range of values.
  79. * @method DataMapper where_not_in_join_field() where_not_in_join_field(mixed $related, string $field, array $values) Limits results by comparing a join field to a range of values.
  80. * @method DataMapper or_where_not_in_join_field() or_where_not_in_join_field(mixed $related, string $field, array $values) Limits results by comparing a join field to a range of values.
  81. * @method DataMapper like_join_field() like_join_field(mixed $related, string $field, string $value, string $match = 'both') Limits results by matching a join field to a value.
  82. * @method DataMapper or_like_join_field() like_join_field(mixed $related, string $field, string $value, string $match = 'both') Limits results by matching a join field to a value.
  83. * @method DataMapper not_like_join_field() like_join_field(mixed $related, string $field, string $value, string $match = 'both') Limits results by matching a join field to a value.
  84. * @method DataMapper or_not_like_join_field() like_join_field(mixed $related, string $field, string $value, string $match = 'both') Limits results by matching a join field to a value.
  85. * @method DataMapper ilike_join_field() like_join_field(mixed $related, string $field, string $value, string $match = 'both') Limits results by matching a join field to a value (case insensitive).
  86. * @method DataMapper or_ilike_join_field() like_join_field(mixed $related, string $field, string $value, string $match = 'both') Limits results by matching a join field to a value (case insensitive).
  87. * @method DataMapper not_ilike_join_field() like_join_field(mixed $related, string $field, string $value, string $match = 'both') Limits results by matching a join field to a value (case insensitive).
  88. * @method DataMapper or_not_ilike_join_field() like_join_field(mixed $related, string $field, string $value, string $match = 'both') Limits results by matching a join field to a value (case insensitive).
  89. * @method DataMapper group_by_join_field() group_by_join_field(mixed $related, string $field) Groups the query by a join field.
  90. * @method DataMapper having_join_field() having_join_field(mixed $related, string $field, string $value) Groups the querying using a HAVING clause.
  91. * @method DataMapper or_having_join_field() having_join_field(mixed $related, string $field, string $value) Groups the querying using a HAVING clause, via OR.
  92. * @method DataMapper order_by_join_field() order_by_join_field(mixed $related, string $field, string $direction) Orders the query based on a join field.
  93. *
  94. * SQL Functions:
  95. * @method DataMapper select_func() select_func(string $function_name, mixed $args,..., string $alias) Selects the result of a SQL function. Alias is required.
  96. * @method DataMapper where_func() where_func(string $function_name, mixed $args,..., string $value) Limits results based on a SQL function.
  97. * @method DataMapper or_where_func() or_where_func(string $function_name, mixed $args,..., string $value) Limits results based on a SQL function, via OR.
  98. * @method DataMapper where_in_func() where_in_func(string $function_name, mixed $args,..., array $values) Limits results by comparing a SQL function to a range of values.
  99. * @method DataMapper or_where_in_func() or_where_in_func(string $function_name, mixed $args,..., array $values) Limits results by comparing a SQL function to a range of values.
  100. * @method DataMapper where_not_in_func() where_not_in_func(string $function_name, string $field, array $values) Limits results by comparing a SQL function to a range of values.
  101. * @method DataMapper or_where_not_in_func() or_where_not_in_func(string $function_name, mixed $args,..., array $values) Limits results by comparing a SQL function to a range of values.
  102. * @method DataMapper like_func() like_func(string $function_name, mixed $args,..., string $value) Limits results by matching a SQL function to a value.
  103. * @method DataMapper or_like_func() like_func(string $function_name, mixed $args,..., string $value) Limits results by matching a SQL function to a value.
  104. * @method DataMapper not_like_func() like_func(string $function_name, mixed $args,..., string $value) Limits results by matching a SQL function to a value.
  105. * @method DataMapper or_not_like_func() like_func(string $function_name, mixed $args,..., string $value) Limits results by matching a SQL function to a value.
  106. * @method DataMapper ilike_func() like_func(string $function_name, mixed $args,..., string $value) Limits results by matching a SQL function to a value (case insensitive).
  107. * @method DataMapper or_ilike_func() like_func(string $function_name, mixed $args,..., string $value) Limits results by matching a SQL function to a value (case insensitive).
  108. * @method DataMapper not_ilike_func() like_func(string $function_name, mixed $args,..., string $value) Limits results by matching a SQL function to a value (case insensitive).
  109. * @method DataMapper or_not_ilike_func() like_func(string $function_name, mixed $args,..., string $value) Limits results by matching a SQL function to a value (case insensitive).
  110. * @method DataMapper group_by_func() group_by_func(string $function_name, mixed $args,...) Groups the query by a SQL function.
  111. * @method DataMapper having_func() having_func(string $function_name, mixed $args,..., string $value) Groups the querying using a HAVING clause.
  112. * @method DataMapper or_having_func() having_func(string $function_name, mixed $args,..., string $value) Groups the querying using a HAVING clause, via OR.
  113. * @method DataMapper order_by_func() order_by_func(string $function_name, mixed $args,..., string $direction) Orders the query based on a SQL function.
  114. *
  115. * Field -> SQL functions:
  116. * @method DataMapper where_field_field_func() where_field_func($field, string $function_name, mixed $args,...) Limits results based on a SQL function.
  117. * @method DataMapper where_between_field_field_func() where_between_field_func($field, string $function_name, mixed $args,...) Limits results based on a SQL function, via BETWEEN.
  118. * @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.
  119. * @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.
  120. * @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.
  121. * @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.
  122. * @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.
  123. * @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.
  124. * @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.
  125. * @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.
  126. * @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.
  127. * @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).
  128. * @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).
  129. * @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).
  130. * @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).
  131. * @method DataMapper group_by_field_field_func() group_by_field_func($field, string $function_name, mixed $args,...) Groups the query by a SQL function.
  132. * @method DataMapper having_field_field_func() having_field_func($field, string $function_name, mixed $args,...) Groups the querying using a HAVING clause.
  133. * @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.
  134. * @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.
  135. *
  136. * Subqueries:
  137. * @method DataMapper select_subquery() select_subquery(DataMapper $subquery, string $alias) Selects the result of a function. Alias is required.
  138. * @method DataMapper where_subquery() where_subquery(mixed $subquery_or_field, mixed $value_or_subquery) Limits results based on a subquery.
  139. * @method DataMapper or_where_subquery() or_where_subquery(mixed $subquery_or_field, mixed $value_or_subquery) Limits results based on a subquery, via OR.
  140. * @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.
  141. * @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.
  142. * @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.
  143. * @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.
  144. * @method DataMapper like_subquery() like_subquery(DataMapper $subquery, string $value, string $match = 'both') Limits results by matching a subquery to a value.
  145. * @method DataMapper or_like_subquery() like_subquery(DataMapper $subquery, string $value, string $match = 'both') Limits results by matching a subquery to a value.
  146. * @method DataMapper not_like_subquery() like_subquery(DataMapper $subquery, string $value, string $match = 'both') Limits results by matching a subquery to a value.
  147. * @method DataMapper or_not_like_subquery() like_subquery(DataMapper $subquery, string $value, string $match = 'both') Limits results by matching a subquery to a value.
  148. * @method DataMapper ilike_subquery() like_subquery(DataMapper $subquery, string $value, string $match = 'both') Limits results by matching a subquery to a value (case insensitive).
  149. * @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).
  150. * @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).
  151. * @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).
  152. * @method DataMapper having_subquery() having_subquery(string $field, DataMapper $subquery) Groups the querying using a HAVING clause.
  153. * @method DataMapper or_having_subquery() having_subquery(string $field, DataMapper $subquery) Groups the querying using a HAVING clause, via OR.
  154. * @method DataMapper order_by_subquery() order_by_subquery(DataMapper $subquery, string $direction) Orders the query based on a subquery.
  155. *
  156. * Related Subqueries:
  157. * @method DataMapper where_related_subquery() where_related_subquery(mixed $related_model, string $related_field, DataMapper $subquery) Limits results based on a subquery.
  158. * @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.
  159. * @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.
  160. * @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.
  161. * @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.
  162. * @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.
  163. * @method DataMapper having_related_subquery() having_related_subquery(mixed $related_model, string $related_field, DataMapper $subquery) Groups the querying using a HAVING clause.
  164. * @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.
  165. *
  166. * Array Extension:
  167. * @method array to_array() to_array($fields = '') NEEDS ARRAY EXTENSION. Converts this object into an associative array. @link DMZ_Array::to_array
  168. * @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
  169. * @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
  170. *
  171. * CSV Extension
  172. * @method bool csv_export() csv_export($filename, $fields = '', $include_header = TRUE) NEEDS CSV EXTENSION. Exports this object as a CSV file.
  173. * @method array csv_import() csv_import($filename, $fields = '', $header_row = TRUE, $callback = NULL) NEEDS CSV EXTENSION. Imports a CSV file into this object.
  174. *
  175. * JSON Extension:
  176. * @method string to_json() to_json($fields = '', $pretty_print = FALSE) NEEDS JSON EXTENSION. Converts this object into a JSON string.
  177. * @method string all_to_json() all_to_json($fields = '', $pretty_print = FALSE) NEEDS JSON EXTENSION. Converts the all array into a JSON string.
  178. * @method bool from_json() from_json($json, $fields = '') NEEDS JSON EXTENSION. Imports the values from a JSON string into this object.
  179. * @method void set_json_content_type() set_json_content_type() NEEDS JSON EXTENSION. Sets the content type header to Content-Type: application/json.
  180. *
  181. * SimpleCache Extension:
  182. * @method DataMapper get_cached() get_cached($limit = '', $offset = '') NEEDS SIMPLECACHE EXTENSION. Enables cacheable queries.
  183. * @method DataMapper clear_cache() get_cached($segment,...) NEEDS SIMPLECACHE EXTENSION. Clears a cache for the specfied segment.
  184. *
  185. * Translate Extension:
  186. *
  187. * Nestedsets Extension:
  188. *
  189. */
  190. class DataMapper implements IteratorAggregate {
  191. /**
  192. * Stores the shared configuration
  193. * @var array
  194. */
  195. static $config = array();
  196. /**
  197. * Stores settings that are common across a specific Model
  198. * @var array
  199. */
  200. static $common = array(DMZ_CLASSNAMES_KEY => array());
  201. /**
  202. * Stores global extensions
  203. * @var array
  204. */
  205. static $global_extensions = array();
  206. /**
  207. * Used to override unset default properties.
  208. * @var array
  209. */
  210. static $_dmz_config_defaults = array(
  211. 'prefix' => '',
  212. 'join_prefix' => '',
  213. 'error_prefix' => '<span class="error">',
  214. 'error_suffix' => '</span>',
  215. 'created_field' => 'created',
  216. 'updated_field' => 'updated',
  217. 'local_time' => FALSE,
  218. 'unix_timestamp' => FALSE,
  219. 'timestamp_format' => 'Y-m-d H:i:s',
  220. 'lang_file_format' => 'model_${model}',
  221. 'field_label_lang_format' => '${model}_${field}',
  222. 'auto_transaction' => FALSE,
  223. 'auto_populate_has_many' => FALSE,
  224. 'auto_populate_has_one' => TRUE,
  225. 'all_array_uses_ids' => FALSE,
  226. 'db_params' => '',
  227. 'extensions' => array(),
  228. 'extensions_path' => 'datamapper',
  229. );
  230. /**
  231. * Contains any errors that occur during validation, saving, or other
  232. * database access.
  233. * @var DM_Error_Object
  234. */
  235. public $error;
  236. /**
  237. * Used to keep track of the original values from the database, to
  238. * prevent unecessarily changing fields.
  239. * @var object
  240. */
  241. public $stored;
  242. /**
  243. * The name of the table for this model (may be automatically generated
  244. * from the classname).
  245. * @var string
  246. */
  247. public $table = '';
  248. /**
  249. * The singular name for this model (may be automatically generated from
  250. * the classname).
  251. * @var string
  252. */
  253. public $model = '';
  254. /**
  255. * The primary key used for this models table
  256. * the classname).
  257. * @var string
  258. */
  259. public $primary_key = 'id';
  260. /**
  261. * The result of validate is stored here.
  262. * @var bool
  263. */
  264. public $valid = FALSE;
  265. /**
  266. * delete relations on delete of an object. Defaults to TRUE.
  267. * set to FALSE if you RDBMS takes care of this using constraints
  268. * @var bool
  269. */
  270. public $cascade_delete = TRUE;
  271. /**
  272. * Contains the database fields for this object.
  273. * ** Automatically configured **
  274. * @var array
  275. */
  276. public $fields = array();
  277. /**
  278. * Contains the result of the last query.
  279. * @var array
  280. */
  281. public $all = array();
  282. /**
  283. * Semi-private field used to track the parent model/id if there is one.
  284. * @var array
  285. */
  286. public $parent = array();
  287. /**
  288. * Contains the validation rules, label, and get_rules for each field.
  289. * @var array
  290. */
  291. public $validation = array();
  292. /**
  293. * Contains any related objects of which this model is related one or more times.
  294. * @var array
  295. */
  296. public $has_many = array();
  297. /**
  298. * Contains any related objects of which this model is singularly related.
  299. * @var array
  300. */
  301. public $has_one = array();
  302. /**
  303. * Used to enable or disable the production cache.
  304. * This should really only be set in the global configuration.
  305. * @var bool
  306. */
  307. public $production_cache = FALSE;
  308. /**
  309. * If a query returns more than the number of rows specified here,
  310. * then it will be automatically freed after a get.
  311. * @var int
  312. */
  313. public $free_result_threshold = 100;
  314. /**
  315. * This can be specified as an array of fields to sort by if no other
  316. * sorting or selection has occurred.
  317. * @var mixed
  318. */
  319. public $default_order_by = NULL;
  320. // tracks whether or not the object has already been validated
  321. protected $_validated = FALSE;
  322. // tracks whether validation needs to be forced before save
  323. protected $_force_validation = FALSE;
  324. // Tracks the columns that need to be instantiated after a GET
  325. protected $_instantiations = NULL;
  326. // Tracks get_rules, matches, and intval rules, to spped up _to_object
  327. protected $_field_tracking = NULL;
  328. // used to track related queries in deep relationships.
  329. protected $_query_related = array();
  330. // If true before a related get(), any extra fields on the join table will be added.
  331. protected $_include_join_fields = FALSE;
  332. // If true before a save, this will force the next save to be new.
  333. protected $_force_save_as_new = FALSE;
  334. // If true, the next where statement will not be prefixed with an AND or OR.
  335. protected $_where_group_started = FALSE;
  336. // Tracks total number of groups created
  337. protected $_group_count = 0;
  338. // storage for additional model paths for the autoloader
  339. protected static $model_paths = array();
  340. /**
  341. * Constructors (both PHP4 and PHP5 style, to stay compatible)
  342. *
  343. * Initialize DataMapper.
  344. * @param int $id if provided, load in the object specified by that ID.
  345. */
  346. public function __construct($id = NULL)
  347. {
  348. return $this->DataMapper($id);
  349. }
  350. public function DataMapper($id = NULL)
  351. {
  352. $this->_dmz_assign_libraries();
  353. $this_class = strtolower(get_class($this));
  354. $is_dmz = $this_class == 'datamapper';
  355. if($is_dmz)
  356. {
  357. $this->_load_languages();
  358. $this->_load_helpers();
  359. }
  360. // this is to ensure that singular is only called once per model
  361. if(isset(DataMapper::$common[DMZ_CLASSNAMES_KEY][$this_class])) {
  362. $common_key = DataMapper::$common[DMZ_CLASSNAMES_KEY][$this_class];
  363. } else {
  364. DataMapper::$common[DMZ_CLASSNAMES_KEY][$this_class] = $common_key = singular($this_class);
  365. }
  366. // Determine model name
  367. if (empty($this->model))
  368. {
  369. $this->model = $common_key;
  370. }
  371. // If model is 'datamapper' then this is the initial autoload by CodeIgniter
  372. if ($is_dmz)
  373. {
  374. // Load config settings
  375. $this->ci_config->load('datamapper', TRUE, TRUE);
  376. // Get and store config settings
  377. DataMapper::$config = $this->ci_config->item('datamapper');
  378. // now double check that all required config values were set
  379. foreach(DataMapper::$_dmz_config_defaults as $config_key => $config_value)
  380. {
  381. if( ! array_key_exists($config_key, DataMapper::$config))
  382. {
  383. DataMapper::$config[$config_key] = $config_value;
  384. }
  385. }
  386. DataMapper::_load_extensions(DataMapper::$global_extensions, DataMapper::$config['extensions']);
  387. unset(DataMapper::$config['extensions']);
  388. return;
  389. }
  390. // Load stored config settings by reference
  391. foreach (DataMapper::$config as $config_key => &$config_value)
  392. {
  393. // Only if they're not already set
  394. if ( ! property_exists($this, $config_key))
  395. {
  396. $this->{$config_key} = $config_value;
  397. }
  398. }
  399. // Load model settings if not in common storage
  400. if ( ! isset(DataMapper::$common[$common_key]))
  401. {
  402. // load language file, if requested and it exists
  403. if(!empty($this->lang_file_format))
  404. {
  405. $lang_file = str_replace(array('${model}', '${table}'), array($this->model, $this->table), $this->lang_file_format);
  406. $deft_lang = $this->ci_config->item('language');
  407. $idiom = ($deft_lang == '') ? 'english' : $deft_lang;
  408. if(file_exists(APPPATH.'language/'.$idiom.'/'.$lang_file.'_lang' . '.php'))
  409. {
  410. $this->ci_lang->load($lang_file, $idiom);
  411. }
  412. }
  413. $loaded_from_cache = FALSE;
  414. // Load in the production cache for this model, if it exists
  415. if( ! empty(DataMapper::$config['production_cache']))
  416. {
  417. // check if it's a fully qualified path first
  418. if (!is_dir($cache_folder = DataMapper::$config['production_cache']))
  419. {
  420. // if not, it's relative to the application path
  421. $cache_folder = APPPATH . DataMapper::$config['production_cache'];
  422. }
  423. if(file_exists($cache_folder) && is_dir($cache_folder) && is_writeable($cache_folder))
  424. {
  425. $cache_file = $cache_folder . '/' . $common_key . '.php';
  426. if(file_exists($cache_file))
  427. {
  428. include($cache_file);
  429. if(isset($cache))
  430. {
  431. DataMapper::$common[$common_key] =& $cache;
  432. unset($cache);
  433. // allow subclasses to add initializations
  434. if(method_exists($this, 'post_model_init'))
  435. {
  436. $this->post_model_init(TRUE);
  437. }
  438. // Load extensions (they are not cacheable)
  439. $this->_initiate_local_extensions($common_key);
  440. $loaded_from_cache = TRUE;
  441. }
  442. }
  443. }
  444. }
  445. if(! $loaded_from_cache)
  446. {
  447. // Determine table name
  448. if (empty($this->table))
  449. {
  450. $this->table = strtolower(plural(get_class($this)));
  451. }
  452. // Add prefix to table
  453. $this->table = $this->prefix . $this->table;
  454. $this->_field_tracking = array(
  455. 'get_rules' => array(),
  456. 'matches' => array(),
  457. 'intval' => array('id')
  458. );
  459. // Convert validation into associative array by field name
  460. $associative_validation = array();
  461. foreach ($this->validation as $name => $validation)
  462. {
  463. if(is_string($name)) {
  464. $validation['field'] = $name;
  465. } else {
  466. $name = $validation['field'];
  467. }
  468. // clean up possibly missing fields
  469. if( ! isset($validation['rules']))
  470. {
  471. $validation['rules'] = array();
  472. }
  473. // Populate associative validation array
  474. $associative_validation[$name] = $validation;
  475. if (!empty($validation['get_rules']))
  476. {
  477. $this->_field_tracking['get_rules'][] = $name;
  478. }
  479. // Check if there is a "matches" validation rule
  480. if (isset($validation['rules']['matches']))
  481. {
  482. $this->_field_tracking['matches'][$name] = $validation['rules']['matches'];
  483. }
  484. }
  485. // set up id column, if not set
  486. if(!isset($associative_validation['id']))
  487. {
  488. // label is set below, to prevent caching language-based labels
  489. $associative_validation['id'] = array(
  490. 'field' => 'id',
  491. 'rules' => array('integer')
  492. );
  493. }
  494. $this->validation = $associative_validation;
  495. // Force all other has_one ITFKs to integers on get
  496. foreach($this->has_one as $related => $rel_props)
  497. {
  498. $field = $related . '_id';
  499. if( in_array($field, $this->fields) &&
  500. ( ! isset($this->validation[$field]) || // does not have a validation key or...
  501. ! isset($this->validation[$field]['get_rules'])) && // a get_rules key...
  502. ( ! isset($this->validation[$related]) || // nor does the related have a validation key or...
  503. ! isset($this->validation[$related]['get_rules'])) ) // a get_rules key
  504. {
  505. // assume an int
  506. $this->_field_tracking['intval'][] = $field;
  507. }
  508. }
  509. // Get and store the table's field names and meta data
  510. $fields = $this->db->field_data($this->table);
  511. // Store only the field names and ensure validation list includes all fields
  512. foreach ($fields as $field)
  513. {
  514. // Populate fields array
  515. $this->fields[] = $field->name;
  516. // Add validation if current field has none
  517. if ( ! isset($this->validation[$field->name]))
  518. {
  519. // label is set below, to prevent caching language-based labels
  520. $this->validation[$field->name] = array('field' => $field->name, 'rules' => array());
  521. }
  522. }
  523. // convert simple has_one and has_many arrays into more advanced ones
  524. foreach(array('has_one', 'has_many') as $arr)
  525. {
  526. foreach ($this->{$arr} as $related_field => $rel_props)
  527. {
  528. // process the relationship
  529. $this->_relationship($arr, $rel_props, $related_field);
  530. }
  531. }
  532. // allow subclasses to add initializations
  533. if(method_exists($this, 'post_model_init'))
  534. {
  535. $this->post_model_init(FALSE);
  536. }
  537. // Store common model settings
  538. foreach (array('table', 'fields', 'validation',
  539. 'has_one', 'has_many', '_field_tracking') as $item)
  540. {
  541. DataMapper::$common[$common_key][$item] = $this->{$item};
  542. }
  543. // store the item to the production cache
  544. $this->production_cache();
  545. // Load extensions last, so they aren't cached.
  546. $this->_initiate_local_extensions($common_key);
  547. }
  548. // define any custom module path present so we can find the model
  549. foreach(array('has_one', 'has_many') as $arr)
  550. {
  551. foreach ($this->{$arr} as $related_field => $rel_props)
  552. {
  553. // process the model custom paths if present
  554. if( isset($rel_props['model_path']))
  555. {
  556. $rel_props['model_path'] = rtrim($rel_props['model_path'], '/') . '/';
  557. if ( is_dir($rel_props['model_path'].'models') && ! in_array($rel_props['model_path'], self::$model_paths))
  558. {
  559. self::$model_paths[] = $rel_props['model_path'];
  560. }
  561. }
  562. }
  563. }
  564. // Finally, localize the labels here (because they shouldn't be cached
  565. // This also sets any missing labels.
  566. $validation =& DataMapper::$common[$common_key]['validation'];
  567. foreach($validation as $field => &$val)
  568. {
  569. // Localize label if necessary
  570. $val['label'] = $this->localize_label($field,
  571. isset($val['label']) ?
  572. $val['label'] :
  573. FALSE);
  574. }
  575. unset($validation);
  576. }
  577. // Load stored common model settings by reference
  578. foreach(DataMapper::$common[$common_key] as $key => &$value)
  579. {
  580. $this->{$key} = $value;
  581. }
  582. // Clear object properties to set at default values
  583. $this->clear();
  584. if( ! empty($id) && is_numeric($id))
  585. {
  586. $this->get_by_id(intval($id));
  587. }
  588. }
  589. // --------------------------------------------------------------------
  590. /**
  591. * Reloads in the configuration data for a model. This is mainly
  592. * used to handle language changes. Only this instance and new instances
  593. * will see the changes.
  594. */
  595. public function reinitialize_model()
  596. {
  597. // determine the classname
  598. $this_class = strtolower(get_class($this));
  599. // this is to ensure that singular is only called once per model
  600. if(isset(DataMapper::$common[DMZ_CLASSNAMES_KEY][$this_class])) {
  601. $common_key = DataMapper::$common[DMZ_CLASSNAMES_KEY][$this_class];
  602. } else {
  603. DataMapper::$common[DMZ_CLASSNAMES_KEY][$this_class] = $common_key = singular($this_class);
  604. }
  605. unset(DataMapper::$common[$common_key]);
  606. $model = get_class($this);
  607. new $model(); // re-initialze
  608. // Load stored common model settings by reference
  609. foreach(DataMapper::$common[$common_key] as $key => &$value)
  610. {
  611. $this->{$key} =& $value;
  612. }
  613. }
  614. // --------------------------------------------------------------------
  615. /**
  616. * Autoload
  617. *
  618. * Autoloads object classes that are used with DataMapper.
  619. * This method will look in any model directories available to CI.
  620. *
  621. * Note:
  622. * It is important that they are autoloaded as loading them manually with
  623. * CodeIgniter's loader class will cause DataMapper's __get and __set functions
  624. * to not function.
  625. *
  626. * @param string $class Name of class to load.
  627. */
  628. public static function autoload($class)
  629. {
  630. static $CI = NULL;
  631. // get the CI instance
  632. is_null($CI) AND $CI =& get_instance();
  633. // Don't attempt to autoload CI_ , EE_, or custom prefixed classes
  634. if (in_array(substr($class, 0, 3), array('CI_', 'EE_')) OR strpos($class, $CI->config->item('subclass_prefix')) === 0)
  635. {
  636. return;
  637. }
  638. // Prepare class
  639. $class = strtolower($class);
  640. // Prepare path
  641. $paths = array();
  642. if (method_exists($CI->load, 'get_package_paths'))
  643. {
  644. // use CI 2.0 loader's model paths
  645. $paths = $CI->load->get_package_paths(false);
  646. }
  647. foreach (array_merge(array(APPPATH),$paths, self::$model_paths) as $path)
  648. {
  649. // Prepare file
  650. $file = $path . 'models/' . $class . '.php';
  651. // Check if file exists, require_once if it does
  652. if (file_exists($file))
  653. {
  654. require_once($file);
  655. break;
  656. }
  657. }
  658. // if class not loaded, do a recursive search of model paths for the class
  659. if (! class_exists($class))
  660. {
  661. foreach($paths as $path)
  662. {
  663. $found = DataMapper::recursive_require_once($class, $path . 'models');
  664. if($found)
  665. {
  666. break;
  667. }
  668. }
  669. }
  670. }
  671. // --------------------------------------------------------------------
  672. /**
  673. * Add Model Path
  674. *
  675. * Manually add paths for the model autoloader
  676. *
  677. * @param mixed $paths path or array of paths to search
  678. */
  679. public static function add_model_path($paths)
  680. {
  681. // make sure paths is an array
  682. is_array($paths) OR $paths = array($paths);
  683. foreach($paths as $path)
  684. {
  685. $path = rtrim($path, '/') . '/';
  686. if ( is_dir($path.'models') && ! in_array($path, self::$model_paths))
  687. {
  688. self::$model_paths[] = $path;
  689. }
  690. }
  691. }
  692. // --------------------------------------------------------------------
  693. /**
  694. * Recursive Require Once
  695. *
  696. * Recursively searches the path for the class, require_once if found.
  697. *
  698. * @param string $class Name of class to look for
  699. * @param string $path Current path to search
  700. */
  701. protected static function recursive_require_once($class, $path)
  702. {
  703. $found = FALSE;
  704. if(is_dir($path))
  705. {
  706. $handle = opendir($path);
  707. if ($handle)
  708. {
  709. while (FALSE !== ($dir = readdir($handle)))
  710. {
  711. // If dir does not contain a dot
  712. if (strpos($dir, '.') === FALSE)
  713. {
  714. // Prepare recursive path
  715. $recursive_path = $path . '/' . $dir;
  716. // Prepare file
  717. $file = $recursive_path . '/' . $class . '.php';
  718. // Check if file exists, require_once if it does
  719. if (file_exists($file))
  720. {
  721. require_once($file);
  722. $found = TRUE;
  723. break;
  724. }
  725. else if (is_dir($recursive_path))
  726. {
  727. // Do a recursive search of the path for the class
  728. DataMapper::recursive_require_once($class, $recursive_path);
  729. }
  730. }
  731. }
  732. closedir($handle);
  733. }
  734. }
  735. return $found;
  736. }
  737. // --------------------------------------------------------------------
  738. /**
  739. * Loads in any extensions used by this class or globally.
  740. *
  741. * @param array $extensions List of extensions to add to.
  742. * @param array $name List of new extensions to load.
  743. */
  744. protected static function _load_extensions(&$extensions, $names)
  745. {
  746. static $CI = NULL;
  747. // get the CI instance
  748. is_null($CI) AND $CI =& get_instance();
  749. $class_prefixes = array(
  750. 0 => 'DMZ_',
  751. 1 => 'DataMapper_',
  752. 2 => $CI->config->item('subclass_prefix'),
  753. 3 => 'CI_'
  754. );
  755. foreach($names as $name => $options)
  756. {
  757. if( ! is_string($name))
  758. {
  759. $name = $options;
  760. $options = NULL;
  761. }
  762. // only load an extension if it wasn't already loaded in this context
  763. if(isset($extensions[$name]))
  764. {
  765. return;
  766. }
  767. if( ! isset($extensions['_methods']))
  768. {
  769. $extensions['_methods'] = array();
  770. }
  771. // determine the file name and class name
  772. $file = DataMapper::$config['extensions_path'] . '/' . $name . '.php';
  773. if ( ! file_exists($file))
  774. {
  775. if(strpos($name, '/') === FALSE)
  776. {
  777. $file = APPPATH . DataMapper::$config['extensions_path'] . '/' . $name . '.php';
  778. $ext = $name;
  779. }
  780. else
  781. {
  782. $file = APPPATH . $name . '.php';
  783. $ext = array_pop(explode('/', $name));
  784. }
  785. if(!file_exists($file))
  786. {
  787. show_error('DataMapper Error: loading extension ' . $name . ': File not found.');
  788. }
  789. }
  790. else
  791. {
  792. $ext = $name;
  793. }
  794. // load class
  795. include_once($file);
  796. // Allow for DMZ_Extension, DataMapper_Extension, etc.
  797. foreach($class_prefixes as $index => $prefix)
  798. {
  799. if(class_exists($prefix.$ext))
  800. {
  801. if($index == 2) // "MY_"
  802. {
  803. // Load in the library this class is based on
  804. $CI->load->library($ext);
  805. }
  806. $ext = $prefix.$ext;
  807. break;
  808. }
  809. }
  810. if(!class_exists($ext))
  811. {
  812. show_error("DataMapper Error: Unable to find a class for extension $name.");
  813. }
  814. // create class
  815. if(is_null($options))
  816. {
  817. $o = new $ext(NULL, isset($this) ? $this : NULL);
  818. }
  819. else
  820. {
  821. $o = new $ext($options, isset($this) ? $this : NULL);
  822. }
  823. $extensions[$name] = $o;
  824. // figure out which methods can be called on this class.
  825. $methods = get_class_methods($ext);
  826. foreach($methods as $m)
  827. {
  828. // do not load private methods or methods already loaded.
  829. if($m[0] !== '_' &&
  830. is_callable(array($o, $m)) &&
  831. ! isset($extensions['_methods'][$m])
  832. ) {
  833. // store this method.
  834. $extensions['_methods'][$m] = $name;
  835. }
  836. }
  837. }
  838. }
  839. // --------------------------------------------------------------------
  840. /**
  841. * Loads the extensions that are local to this model.
  842. * @param string $common_key Shared key to save extenions to.
  843. */
  844. protected function _initiate_local_extensions($common_key)
  845. {
  846. if(!empty($this->extensions))
  847. {
  848. $extensions = $this->extensions;
  849. $this->extensions = array();
  850. DataMapper::_load_extensions($this->extensions, $extensions);
  851. }
  852. else
  853. {
  854. // ensure an empty array
  855. $this->extensions = array('_methods' => array());
  856. }
  857. // bind to the shared key, for dynamic loading
  858. DataMapper::$common[$common_key]['extensions'] =& $this->extensions;
  859. }
  860. // --------------------------------------------------------------------
  861. /**
  862. * Dynamically load an extension when needed.
  863. * @param object $name Name of the extension (or array of extensions).
  864. * @param array $options Options for the extension
  865. * @param boolean $local If TRUE, only loads the extension into this object
  866. */
  867. public function load_extension($name, $options = NULL, $local = FALSE)
  868. {
  869. if( ! is_array($name))
  870. {
  871. if( ! is_null($options))
  872. {
  873. $name = array($name => $options);
  874. }
  875. else
  876. {
  877. $name = array($name);
  878. }
  879. }
  880. // called individually to ensure that the array is modified directly
  881. // (and not copied instead)
  882. if($local)
  883. {
  884. DataMapper::_load_extensions($this->extensions, $name);
  885. }
  886. else
  887. {
  888. DataMapper::_load_extensions(DataMapper::$global_extensions, $name);
  889. }
  890. }
  891. // --------------------------------------------------------------------
  892. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  893. * *
  894. * Magic methods *
  895. * *
  896. * The following are methods to override the default PHP behaviour. *
  897. * *
  898. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
  899. // --------------------------------------------------------------------
  900. /**
  901. * Magic Get
  902. *
  903. * Returns the value of the named property.
  904. * If named property is a related item, instantiate it first.
  905. *
  906. * This method also instantiates the DB object and the form_validation
  907. * objects as necessary
  908. *
  909. * @ignore
  910. * @param string $name Name of property to look for
  911. * @return mixed
  912. */
  913. public function __get($name)
  914. {
  915. static $CI = NULL;
  916. // get the CI instance
  917. is_null($CI) AND $CI =& get_instance();
  918. // We dynamically get DB when needed, and create a copy.
  919. // This allows multiple queries to be generated at the same time.
  920. if($name == 'db')
  921. {
  922. if($this->db_params === FALSE)
  923. {
  924. if ( ! isset($CI->db) || ! is_object($CI->db) || ! isset($CI->db->dbdriver) )
  925. {
  926. show_error('DataMapper Error: CodeIgniter database library not loaded.');
  927. }
  928. $this->db =& $CI->db;
  929. }
  930. else
  931. {
  932. if($this->db_params == '' || $this->db_params === TRUE)
  933. {
  934. if ( ! isset($CI->db) || ! is_object($CI->db) || ! isset($CI->db->dbdriver) )
  935. {
  936. show_error('DataMapper Error: CodeIgniter database library not loaded.');
  937. }
  938. // ensure the shared DB is disconnected, even if the app exits uncleanly
  939. if(!isset($CI->db->_has_shutdown_hook))
  940. {
  941. register_shutdown_function(array($CI->db, 'close'));
  942. $CI->db->_has_shutdown_hook = TRUE;
  943. }
  944. // clone, so we don't create additional connections to the DB
  945. $this->db = clone($CI->db);
  946. $this->db->dm_call_method('_reset_select');
  947. }
  948. else
  949. {
  950. // connecting to a different database, so we *must* create additional copies.
  951. // It is up to the developer to close the connection!
  952. $this->db = $CI->load->database($this->db_params, TRUE, TRUE);
  953. }
  954. // these items are shared (for debugging)
  955. if(is_object($CI->db) && isset($CI->db->dbdriver))
  956. {
  957. $this->db->queries =& $CI->db->queries;
  958. $this->db->query_times =& $CI->db->query_times;
  959. }
  960. }
  961. // ensure the created DB is disconnected, even if the app exits uncleanly
  962. if(!isset($this->db->_has_shutdown_hook))
  963. {
  964. register_shutdown_function(array($this->db, 'close'));
  965. $this->db->_has_shutdown_hook = TRUE;
  966. }
  967. return $this->db;
  968. }
  969. // Special case to get form_validation when first accessed
  970. if($name == 'form_validation')
  971. {
  972. if ( ! isset($this->form_validation) )
  973. {
  974. isset($CI->form_validation) OR $CI->load->library('form_validation');
  975. $this->form_validation =& $CI->form_validation;
  976. $this->ci_lang->load('form_validation');
  977. }
  978. return $this->form_validation;
  979. }
  980. $has_many = isset($this->has_many[$name]);
  981. $has_one = isset($this->has_one[$name]);
  982. // If named property is a "has many" or "has one" related item
  983. if ($has_many || $has_one)
  984. {
  985. $related_properties = $has_many ? $this->has_many[$name] : $this->has_one[$name];
  986. // Instantiate it before accessing
  987. $class = $related_properties['class'];
  988. $this->{$name} = new $class();
  989. // Store parent data
  990. $this->{$name}->parent = array('model' => $related_properties['other_field'], 'id' => $this->id);
  991. // Check if Auto Populate for "has many" or "has one" is on
  992. // (but only if this object exists in the DB, and we aren't instantiating)
  993. if ($this->exists() &&
  994. ($has_many && ($this->auto_populate_has_many || $this->has_many[$name]['auto_populate'])) || ($has_one && ($this->auto_populate_has_one || $this->has_one[$name]['auto_populate'])))
  995. {
  996. $this->{$name}->get();
  997. }
  998. return $this->{$name};
  999. }
  1000. $name_single = singular($name);
  1001. if($name_single !== $name) {
  1002. // possibly return single form of name
  1003. $test = $this->{$name_single};
  1004. if(is_object($test)) {
  1005. return $test;
  1006. }
  1007. }
  1008. return NULL;
  1009. }
  1010. // --------------------------------------------------------------------
  1011. /**
  1012. * Used several places to temporarily override the auto_populate setting
  1013. * @ignore
  1014. * @param string $related Related Name
  1015. * @return DataMapper|NULL
  1016. */
  1017. protected function &_get_without_auto_populating($related)
  1018. {
  1019. $b_many = $this->auto_populate_has_many;
  1020. $b_one = $this->auto_populate_has_one;
  1021. $this->auto_populate_has_many = FALSE;
  1022. $this->auto_populate_has_one = FALSE;
  1023. $ret =& $this->{$related};
  1024. $this->auto_populate_has_many = $b_many;
  1025. $this->auto_populate_has_one = $b_one;
  1026. return $ret;
  1027. }
  1028. // --------------------------------------------------------------------
  1029. /**
  1030. * Magic Call
  1031. *
  1032. * Calls special methods, or extension methods.
  1033. *
  1034. * @ignore
  1035. * @param string $method Method name
  1036. * @param array $arguments Arguments to method
  1037. * @return mixed
  1038. */
  1039. public function __call($method, $arguments)
  1040. {
  1041. // List of watched method names
  1042. // NOTE: order matters: make sure more specific items are listed before
  1043. // less specific items
  1044. static $watched_methods = array(
  1045. 'save_', 'delete_',
  1046. 'get_by_related_', 'get_by_related', 'get_by_',
  1047. '_related_subquery', '_subquery',
  1048. '_related_', '_related',
  1049. '_join_field',
  1050. '_field_func', '_func'
  1051. );
  1052. $ext = NULL;
  1053. // attempt to call an extension first
  1054. if($this->_extension_method_exists($method, 'local'))
  1055. {
  1056. $name = $this->extensions['_methods'][$method];
  1057. $ext = $this->extensions[$name];
  1058. }
  1059. elseif($this->_extension_method_exists($method, 'global'))
  1060. {
  1061. $name = DataMapper::$global_extensions['_methods'][$method];
  1062. $ext = DataMapper::$global_extensions[$name];
  1063. }
  1064. else
  1065. {
  1066. foreach ($watched_methods as $watched_method)
  1067. {
  1068. // See if called method is a watched method
  1069. if (strpos($method, $watched_method) !== FALSE)
  1070. {
  1071. $pieces = explode($watched_method, $method);
  1072. if ( ! empty($pieces[0]) && ! empty($pieces[1]))
  1073. {
  1074. // Watched method is in the middle
  1075. return $this->{'_' . trim($watched_method, '_')}($pieces[0], array_merge(array($pieces[1]), $arguments));
  1076. }
  1077. else
  1078. {
  1079. // Watched method is a prefix or suffix
  1080. return $this->{'_' . trim($watched_method, '_')}(str_replace($watched_method, '', $method), $arguments);
  1081. }
  1082. }
  1083. }
  1084. }
  1085. if( ! is_null($ext))
  1086. {
  1087. array_unshift($arguments, $this);
  1088. return call_user_func_array(array($ext, $method), $arguments);
  1089. }
  1090. // show an error, for debugging's sake.
  1091. throw new Exception("Unable to call the method \"$method\" on the class " . get_class($this));
  1092. }
  1093. // --------------------------------------------------------------------
  1094. /**
  1095. * Returns TRUE or FALSE if the method exists in the extensions.
  1096. *
  1097. * @param object $method Method to look for.
  1098. * @param object $which One of 'both', 'local', or 'global'
  1099. * @return bool TRUE if the method can be called.
  1100. */
  1101. protected function _extension_method_exists($method, $which = 'both') {
  1102. $found = FALSE;
  1103. if($which != 'global') {
  1104. $found = ! empty($this->extensions) && isset($this->extensions['_methods'][$method]);
  1105. }
  1106. if( ! $found && $which != 'local' ) {
  1107. $found = ! empty(DataMapper::$global_extensions) && isset(DataMapper::$global_extensions['_methods'][$method]);
  1108. }
  1109. return $found;
  1110. }
  1111. // --------------------------------------------------------------------
  1112. /**
  1113. * Magic Clone
  1114. *
  1115. * Allows for a less shallow clone than the default PHP clone.
  1116. *
  1117. * @ignore
  1118. */
  1119. public function __clone()
  1120. {
  1121. foreach ($this as $key => $value)
  1122. {
  1123. if (is_object($value) && $key != 'db')
  1124. {
  1125. $this->{$key} = clone($value);
  1126. }
  1127. }
  1128. }
  1129. // --------------------------------------------------------------------
  1130. /**
  1131. * To String
  1132. *
  1133. * Converts the current object into a string.
  1134. * Should be overridden by extended objects.
  1135. *
  1136. * @return string
  1137. */
  1138. public function __toString()
  1139. {
  1140. return ucfirst($this->model);
  1141. }
  1142. // --------------------------------------------------------------------
  1143. /**
  1144. * Allows the all array to be iterated over without
  1145. * having to specify it.
  1146. *
  1147. * @return Iterator An iterator for the all array
  1148. */
  1149. public function getIterator() {
  1150. if(isset($this->_dm_dataset_iterator)) {
  1151. return $this->_dm_dataset_iterator;
  1152. } else {
  1153. return new ArrayIterator($this->all);
  1154. }
  1155. }
  1156. // --------------------------------------------------------------------
  1157. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1158. * *
  1159. * Main methods *
  1160. * *
  1161. * The following are methods that form the main *
  1162. * functionality of DataMapper. *
  1163. * *
  1164. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
  1165. // --------------------------------------------------------------------
  1166. /**
  1167. * Get
  1168. *
  1169. * Get objects from the database.
  1170. *
  1171. * @param integer|NULL $limit Limit the number of results.
  1172. * @param integer|NULL $offset Offset the results when limiting.
  1173. * @return DataMapper Returns self for method chaining.
  1174. */
  1175. public function get($limit = NULL, $offset = NULL)
  1176. {
  1177. // Check if this is a related object and if so, perform a related get
  1178. if (! $this->_handle_related())
  1179. {
  1180. // invalid get request, return this for chaining.
  1181. return $this;
  1182. } // Else fall through to a normal get
  1183. $query = FALSE;
  1184. // Check if object has been validated (skipped for related items)
  1185. if ($this->_validated && empty($this->parent))
  1186. {
  1187. // Reset validated
  1188. $this->_validated = FALSE;
  1189. // Use this objects properties
  1190. $data = $this->_to_array(TRUE);
  1191. if ( ! empty($data))
  1192. {
  1193. // Clear this object to make way for new data
  1194. $this->clear();
  1195. // Set up default order by (if available)
  1196. $this->_handle_default_order_by();
  1197. // Get by objects properties
  1198. $query = $this->db->get_where($this->table, $data, $limit, $offset);
  1199. } // FIXME: notify user if nothing was set?
  1200. }
  1201. else
  1202. {
  1203. // Clear this object to make way for new data
  1204. $this->clear();
  1205. // Set up default order by (if available)
  1206. $this->_handle_default_order_by();
  1207. // Get by built up query
  1208. $query = $this->db->get($this->table, $limit, $offset);
  1209. }
  1210. // Convert the query result into DataMapper objects
  1211. if($query)
  1212. {
  1213. $this->_process_query($query);
  1214. }
  1215. // For method chaining
  1216. return $this;
  1217. }
  1218. // --------------------------------------------------------------------
  1219. /**
  1220. * Returns the SQL string of the current query (SELECTs ONLY).
  1221. * NOTE: This also _clears_ the current query info.
  1222. *
  1223. * This can be used to generate subqueries.
  1224. *
  1225. * @param integer|NULL $limit Limit the number of results.
  1226. * @param integer|NULL $offset Offset the results when limiting.
  1227. * @return string SQL as a string.
  1228. */
  1229. public function get_sql($limit = NULL, $offset = NULL, $handle_related = FALSE)
  1230. {
  1231. if($handle_related) {
  1232. $this->_handle_related();
  1233. }
  1234. $this->db->dm_call_method('_track_aliases', $this->table);
  1235. $this->db->from($this->table);
  1236. $this->_handle_default_order_by();
  1237. if ( ! is_null($limit))
  1238. {
  1239. $this->limit($limit, $offset);
  1240. }
  1241. $sql = $this->db->dm_call_method('_compile_select');
  1242. $this->_clear_after_query();
  1243. return $sql;
  1244. }
  1245. // --------------------------------------------------------------------
  1246. /**
  1247. * Runs the query, but returns the raw CodeIgniter results
  1248. * NOTE: This also _clears_ the current query info.
  1249. *
  1250. * @param integer|NULL $limit Limit the number of results.
  1251. * @param integer|NULL $offset Offset the results when limiting.
  1252. * @return CI_DB_result Result Object
  1253. */
  1254. public function get_raw($limit = NULL, $offset = NULL, $handle_related = TRUE)
  1255. {
  1256. if($handle_related) {
  1257. $this->_handle_related();
  1258. }
  1259. $this->_handle_default_order_by();
  1260. $query = $this->db->get($this->table, $limit, $offset);
  1261. $this->_clear_after_query();
  1262. return $query;
  1263. }
  1264. // --------------------------------------------------------------------
  1265. /**
  1266. * Returns a streamable result set for large queries.
  1267. * Usage:
  1268. * $rs = $object->get_iterated();
  1269. * $size = $rs->count;
  1270. * foreach($rs as $o) {
  1271. * // handle $o
  1272. * }
  1273. * $rs can be looped through more than once.
  1274. *
  1275. * @param integer|NULL $limit Limit the number of results.
  1276. * @param integer|NULL $offset Offset the results when limiting.
  1277. * @return DataMapper Returns self for method chaining.
  1278. */
  1279. public function get_iterated($limit = NULL, $offset = NULL)
  1280. {
  1281. // clone $this, so we keep track of instantiations, etc.
  1282. // because these are cleared after the call to get_raw
  1283. $object = $this->get_clone();
  1284. // need to clear query from the clone
  1285. $object->db->dm_call_method('_reset_select');
  1286. // Clear the query related list from the clone
  1287. $object->_query_related = array();
  1288. // Build iterator
  1289. $this->_dm_dataset_iterator = new DM_DatasetIterator($object, $this->get_raw($limit, $offset, TRUE));
  1290. return $this;
  1291. }
  1292. // --------------------------------------------------------------------
  1293. /**
  1294. * Convenience method that runs a query based on pages.
  1295. * This object will have two new values, $query_total_pages and
  1296. * $query_total_rows, which can be used to determine how many pages and
  1297. * how many rows are available in total, respectively.
  1298. *
  1299. * @param int $page Page (1-based) to start on, or row (0-based) to start on
  1300. * @param int $page_size Number of rows in a page
  1301. * @param bool $page_num_by_rows When TRUE, $page is the starting row, not the starting page
  1302. * @param bool $iterated Internal Use Only
  1303. * @return DataMapper Returns self for method chaining.
  1304. */
  1305. public function get_paged($page = 1, $page_size = 50, $page_num_by_rows = FALSE, $info_object = 'paged', $iterated = FALSE)
  1306. {
  1307. // first, duplicate this query, so we have a copy for the query
  1308. $count_query = $this->get_clone(TRUE);
  1309. if($page_num_by_rows)
  1310. {
  1311. $page = 1 + floor(intval($page) / $page_size);
  1312. }
  1313. // never less than 1
  1314. $page = max(1, intval($page));
  1315. $offset = $page_size * ($page - 1);
  1316. // for performance, we clear out the select AND the order by statements,
  1317. // since they aren't necessary and might slow down the query.
  1318. $count_query->db->dm_set('ar_select', NULL);
  1319. $count_query->db->dm_set('ar_orderby', NULL);
  1320. $total = $count_query->db->dm_get('ar_distinct') ? $count_query->count_distinct() : $count_query->count();
  1321. // common vars
  1322. $last_row = $page_size * floor($total / $page_size);
  1323. $total_pages = ceil($total / $page_size);
  1324. if($offset >= $last_row)
  1325. {
  1326. // too far!
  1327. $offset = $last_row;
  1328. $page = $total_pages;
  1329. }
  1330. // now query this object
  1331. if($iterated)
  1332. {
  1333. $this->get_iterated($page_size, $offset);
  1334. }
  1335. else
  1336. {
  1337. $this->get($page_size, $offset);
  1338. }
  1339. $this->{$info_object} = new stdClass();
  1340. $this->{$info_object}->page_size = $page_size;
  1341. $this->{$info_object}->items_on_page = $this->result_count();
  1342. $this->{$info_object}->current_page = $page;
  1343. $this->{$info_object}->current_row = $offset;
  1344. $this->{$info_object}->total_rows = $total;
  1345. $this->{$info_object}->last_row = $last_row;
  1346. $this->{$info_object}->total_pages = $total_pages;
  1347. $this->{$info_object}->has_previous = $offset > 0;
  1348. $this->{$info_object}->previous_page = max(1, $page-1);
  1349. $this->{$info_object}->previous_row = max(0, $offset-$page_size);
  1350. $this->{$info_object}->has_next = $page < $total_pages;
  1351. $this->{$info_object}->next_page = min($total_pages, $page+1);
  1352. $this->{$info_object}->next_row = min($last_row, $offset+$page_size);
  1353. return $this;
  1354. }
  1355. // --------------------------------------------------------------------
  1356. /**
  1357. * Runs get_paged, but as an Iterable.
  1358. *
  1359. * @see get_paged
  1360. * @param int $page Page (1-based) to start on, or row (0-based) to start on
  1361. * @param int $page_size Number of rows in a page
  1362. * @param bool $page_num_by_rows When TRUE, $page is the starting row, not the starting page
  1363. * @param bool $iterated Internal Use Only
  1364. * @return DataMapper Returns self for method chaining.
  1365. */
  1366. public function get_paged_iterated($page = 1, $page_size = 50, $page_num_by_rows = FALSE, $info_object = 'paged')
  1367. {
  1368. return $this->get_paged($page, $page_size, $page_num_by_rows, $info_object, TRUE);
  1369. }
  1370. // --------------------------------------------------------------------
  1371. /**
  1372. * Forces this object to be INSERTed, even if it has an ID.
  1373. *
  1374. * @param mixed $object See save.
  1375. * @param string $related_field See save.
  1376. * @return bool Result of the save.
  1377. */
  1378. public function save_as_new($object = '', $related_field = '')
  1379. {
  1380. $this->_force_save_as_new = TRUE;
  1381. return $this->save($object, $related_field);
  1382. }
  1383. // --------------------------------------------------------------------
  1384. /**
  1385. * Save
  1386. *
  1387. * Saves the current record, if it validates.
  1388. * If object is supplied, saves relations between this object and the supplied object(s).
  1389. *
  1390. * @param mixed $object Optional object to save or array of objects to save.
  1391. * @param string $related_field Optional string to save the object as a specific relationship.
  1392. * @return bool Success or Failure of the validation and save.
  1393. */
  1394. public function save($object = '', $related_field = '')
  1395. {
  1396. // Temporarily store the success/failure
  1397. $result = array();
  1398. // Validate this objects properties
  1399. $this->validate($object, $related_field);
  1400. // If validation passed
  1401. if ($this->valid)
  1402. {
  1403. // Begin auto transaction
  1404. $this->_auto_trans_begin();
  1405. $trans_complete_label = array();
  1406. // Get current timestamp
  1407. $timestamp = $this->_get_generated_timestamp();
  1408. // Check if object has a 'created' field, and it is not already set
  1409. if (in_array($this->created_field, $this->fields) && empty($this->{$this->created_field}))
  1410. {
  1411. $this->{$this->created_field} = $timestamp;
  1412. }
  1413. // SmartSave: if there are objects being saved, and they are stored
  1414. // as in-table foreign keys, we can save them at this step.
  1415. if( ! empty($object))
  1416. {
  1417. if( ! is_array($object))
  1418. {
  1419. $object = array($object);
  1420. }
  1421. $this->_save_itfk($object, $related_field);
  1422. }
  1423. // Convert this object to array
  1424. $data = $this->_to_array();
  1425. if ( ! empty($data))
  1426. {
  1427. if ( ! $this->_force_save_as_new && ! empty($data['id']))
  1428. {
  1429. // Prepare data to send only changed fields
  1430. foreach ($data as $field => $value)
  1431. {
  1432. // Unset field from data if it hasn't been changed
  1433. if ($this->{$field} === $this->stored->{$field})
  1434. {
  1435. unset($data[$field]);
  1436. }
  1437. }
  1438. // if there are changes, check if we need to update the update timestamp
  1439. if (count($data) && in_array($this->updated_field, $this->fields) && ! isset($data[$this->updated_field]))
  1440. {
  1441. // update it now
  1442. $data[$this->updated_field] = $this->{$this->updated_field} = $timestamp;
  1443. }
  1444. // Only go ahead with save if there is still data
  1445. if ( ! empty($data))
  1446. {
  1447. // Update existing record
  1448. $this->db->where('id', $this->id);
  1449. $this->db->update($this->table, $data);
  1450. $trans_complete_label[] = 'update';
  1451. }
  1452. // Reset validated
  1453. $this->_validated = FALSE;
  1454. $result[] = TRUE;
  1455. }
  1456. else
  1457. {
  1458. // Prepare data to send only populated fields
  1459. foreach ($data as $field => $value)
  1460. {
  1461. // Unset field from data
  1462. if ( ! isset($value))
  1463. {
  1464. unset($data[$field]);
  1465. }
  1466. }
  1467. // Create new record
  1468. $this->db->insert($this->table, $data);
  1469. if( ! $this->_force_save_as_new)
  1470. {
  1471. // Assign new ID
  1472. $this->id = $this->db->insert_id();
  1473. }
  1474. $trans_complete_label[] = 'insert';
  1475. // Reset validated
  1476. $this->_validated = FALSE;
  1477. $result[] = TRUE;
  1478. }
  1479. }
  1480. $this->_refresh_stored_values();
  1481. if (!empty($this->_field_tracking['get_rules']))
  1482. {
  1483. $this->_run_get_rules();
  1484. }
  1485. // Check if a relationship is being saved
  1486. if ( ! empty($object))
  1487. {
  1488. // save recursively
  1489. $this->_save_related_recursive($object, $related_field);
  1490. $trans_complete_label[] = 'relationships';
  1491. }
  1492. if(!empty($trans_complete_label))
  1493. {
  1494. $trans_complete_label = 'save (' . implode(', ', $trans_complete_label) . ')';
  1495. }
  1496. else
  1497. {
  1498. $trans_complete_label = '-nothing done-';
  1499. }
  1500. $this->_auto_trans_complete($trans_complete_label);
  1501. }
  1502. $this->_force_save_as_new = FALSE;
  1503. // If no failure was recorded, return TRUE
  1504. return ( ! empty($result) && ! in_array(FALSE, $result));
  1505. }
  1506. // --------------------------------------------------------------------
  1507. /**
  1508. * Recursively saves arrays of objects if they are In-Table Foreign Keys.
  1509. * @ignore
  1510. * @param object $objects Objects to save. This array may be modified.
  1511. * @param object $related_field Related Field name (empty is OK)
  1512. */
  1513. protected function _save_itfk( &$objects, $related_field)
  1514. {
  1515. foreach($objects as $index => $o)
  1516. {
  1517. if(is_int($index))
  1518. {
  1519. $rf = $related_field;
  1520. }
  1521. else
  1522. {
  1523. $rf = $index;
  1524. }
  1525. if(is_array($o))
  1526. {
  1527. $this->_save_itfk($o, $rf);
  1528. if(empty($o))
  1529. {
  1530. unset($objects[$index]);
  1531. }
  1532. }
  1533. else
  1534. {
  1535. if(empty($rf)) {
  1536. $rf = $o->model;
  1537. }
  1538. $related_properties = $this->_get_related_properties($rf);
  1539. $other_column = $related_properties['join_other_as'] . '_id';
  1540. if(isset($this->has_one[$rf]) && in_array($other_column, $this->fields))
  1541. {
  1542. // unset, so that it doesn't get re-saved later.
  1543. unset($objects[$index]);
  1544. if($this->{$other_column} != $o->id)
  1545. {
  1546. // ITFK: store on the table
  1547. $this->{$other_column} = $o->id;
  1548. // Remove reverse relationships for one-to-ones
  1549. $this->_remove_other_one_to_one($rf, $o);
  1550. }
  1551. }
  1552. }
  1553. }
  1554. }
  1555. // --------------------------------------------------------------------
  1556. /**
  1557. * Recursively saves arrays of objects.
  1558. *
  1559. * @ignore
  1560. * @param object $object Array of objects to save, or single object
  1561. * @param object $related_field Default related field name (empty is OK)
  1562. * @return bool TRUE or FALSE if an error occurred.
  1563. */
  1564. protected function _save_related_recursive($object, $related_field)
  1565. {
  1566. if(is_array($object))
  1567. {
  1568. $success = TRUE;
  1569. foreach($object as $rk => $o)
  1570. {
  1571. if(is_int($rk))
  1572. {
  1573. $rk = $related_field;
  1574. }
  1575. $rec_success = $this->_save_related_recursive($o, $rk);
  1576. $success = $success && $rec_success;
  1577. }
  1578. return $success;
  1579. }
  1580. else
  1581. {
  1582. return $this->_save_relation($object, $related_field);
  1583. }
  1584. }
  1585. // --------------------------------------------------------------------
  1586. /**
  1587. * _Save
  1588. *
  1589. * Used by __call to process related saves.
  1590. *
  1591. * @ignore
  1592. * @param mixed $related_field
  1593. * @param array $arguments
  1594. * @return bool
  1595. */
  1596. protected function _save($related_field, $arguments)
  1597. {
  1598. return $this->save($arguments[0], $related_field);
  1599. }
  1600. // --------------------------------------------------------------------
  1601. /**
  1602. * Update
  1603. *
  1604. * Allows updating of more than one row at once.
  1605. *
  1606. * @param object $field A field to update, or an array of fields => values
  1607. * @param object $value The new value
  1608. * @param object $escape_values If false, don't escape the values
  1609. * @return bool TRUE or FALSE on success or failure
  1610. */
  1611. public function update($field, $value = NULL, $escape_values = TRUE)
  1612. {
  1613. if( ! is_array($field))
  1614. {
  1615. $field = array($field => $value);
  1616. }
  1617. else if($value === FALSE)
  1618. {
  1619. $escape_values = FALSE;
  1620. }
  1621. if(empty($field))
  1622. {
  1623. show_error("Nothing was provided to update.");
  1624. }
  1625. // Check if object has an 'updated' field
  1626. if (in_array($this->updated_field, $this->fields))
  1627. {
  1628. $timestamp = $this->_get_generated_timestamp();
  1629. if( ! $escape_values)
  1630. {
  1631. $timestamp = $this->db->escape($timestamp);
  1632. }
  1633. // Update updated datetime
  1634. $field[$this->updated_field] = $timestamp;
  1635. }
  1636. foreach($field as $k => $v)
  1637. {
  1638. if( ! $escape_values)
  1639. {
  1640. // attempt to add the table name
  1641. $v = $this->add_table_name($v);
  1642. }
  1643. $this->db->set($k, $v, $escape_values);
  1644. }
  1645. return $this->db->update($this->table);
  1646. }
  1647. // --------------------------------------------------------------------
  1648. /**
  1649. * Update All
  1650. *
  1651. * Updates all items that are in the all array.
  1652. *
  1653. * @param object $field A field to update, or an array of fields => values
  1654. * @param object $value The new value
  1655. * @param object $escape_values If false, don't escape the values
  1656. * @return bool TRUE or FALSE on success or failure
  1657. */
  1658. public function update_all($field, $value = NULL, $escape_values = TRUE)
  1659. {
  1660. $ids = array();
  1661. foreach($this->all as $object)
  1662. {
  1663. $ids[] = $object->id;
  1664. }
  1665. if(empty($ids))
  1666. {
  1667. return FALSE;
  1668. }
  1669. $this->where_in('id', $ids);
  1670. return $this->update($field, $value, $escape_values);
  1671. }
  1672. // --------------------------------------------------------------------
  1673. /**
  1674. * Gets a timestamp to use when saving.
  1675. * @return mixed
  1676. */
  1677. protected function _get_generated_timestamp()
  1678. {
  1679. // Get current timestamp
  1680. $timestamp = ($this->local_time) ? date($this->timestamp_format) : gmdate($this->timestamp_format);
  1681. // Check if unix timestamp
  1682. return ($this->unix_timestamp) ? strtotime($timestamp) : $timestamp;
  1683. }
  1684. // --------------------------------------------------------------------
  1685. /**
  1686. * Delete
  1687. *
  1688. * Deletes the current record.
  1689. * If object is supplied, deletes relations between this object and the supplied object(s).
  1690. *
  1691. * @param mixed $object If specified, delete the relationship to the object or array of objects.
  1692. * @param string $related_field Can be used to specify which relationship to delete.
  1693. * @return bool Success or Failure of the delete.
  1694. */
  1695. public function delete($object = '', $related_field = '')
  1696. {
  1697. if (empty($object) && ! is_array($object))
  1698. {
  1699. if ( ! empty($this->id))
  1700. {
  1701. // Begin auto transaction
  1702. $this->_auto_trans_begin();
  1703. // Delete all "has many" and "has one" relations for this object first
  1704. foreach (array('has_many', 'has_one') as $type)
  1705. {
  1706. foreach ($this->{$type} as $model => $properties)
  1707. {
  1708. // do we want cascading delete's?
  1709. if ($properties['cascade_delete'])
  1710. {
  1711. // Prepare model
  1712. $class = $properties['class'];
  1713. $object = new $class();
  1714. $this_model = $properties['join_self_as'];
  1715. $other_model = $properties['join_other_as'];
  1716. // Determine relationship table name
  1717. $relationship_table = $this->_get_relationship_table($object, $model);
  1718. // We have to just set NULL for in-table foreign keys that
  1719. // are pointing at this object
  1720. if($relationship_table == $object->table && // ITFK
  1721. // NOT ITFKs that point at the other object
  1722. ! ($object->table == $this->table && // self-referencing has_one join
  1723. in_array($other_model . '_id', $this->fields)) // where the ITFK is for the other object
  1724. )
  1725. {
  1726. $data = array($this_model . '_id' => NULL);
  1727. // Update table to remove relationships
  1728. $this->db->where($this_model . '_id', $this->id);
  1729. $this->db->update($object->table, $data);
  1730. }
  1731. else if ($relationship_table != $this->table)
  1732. {
  1733. $data = array($this_model . '_id' => $this->id);
  1734. // Delete relation
  1735. $this->db->delete($relationship_table, $data);
  1736. }
  1737. // Else, no reason to delete the relationships on this table
  1738. }
  1739. }
  1740. }
  1741. // Delete the object itself
  1742. $this->db->where('id', $this->id);
  1743. $this->db->delete($this->table);
  1744. // Complete auto transaction
  1745. $this->_auto_trans_complete('delete');
  1746. // Clear this object
  1747. $this->clear();
  1748. return TRUE;
  1749. }
  1750. }
  1751. else if (is_array($object))
  1752. {
  1753. // Begin auto transaction
  1754. $this->_auto_trans_begin();
  1755. // Temporarily store the success/failure
  1756. $result = array();
  1757. foreach ($object as $rel_field => $obj)
  1758. {
  1759. if (is_int($rel_field))
  1760. {
  1761. $rel_field = $related_field;
  1762. }
  1763. if (is_array($obj))
  1764. {
  1765. foreach ($obj as $r_f => $o)
  1766. {
  1767. if (is_int($r_f))
  1768. {
  1769. $r_f = $rel_field;
  1770. }
  1771. $result[] = $this->_delete_relation($o, $r_f);
  1772. }
  1773. }
  1774. else
  1775. {
  1776. $result[] = $this->_delete_relation($obj, $rel_field);
  1777. }
  1778. }
  1779. // Complete auto transaction
  1780. $this->_auto_trans_complete('delete (relationship)');
  1781. // If no failure was recorded, return TRUE
  1782. if ( ! in_array(FALSE, $result))
  1783. {
  1784. return TRUE;
  1785. }
  1786. }
  1787. else
  1788. {
  1789. // Begin auto transaction
  1790. $this->_auto_trans_begin();
  1791. // Temporarily store the success/failure
  1792. $result = $this->_delete_relation($object, $related_field);
  1793. // Complete auto transaction
  1794. $this->_auto_trans_complete('delete (relationship)');
  1795. return $result;
  1796. }
  1797. return FALSE;
  1798. }
  1799. // --------------------------------------------------------------------
  1800. /**
  1801. * _Delete
  1802. *
  1803. * Used by __call to process related deletes.
  1804. *
  1805. * @ignore
  1806. * @param string $related_field
  1807. * @param array $arguments
  1808. * @return bool
  1809. */
  1810. protected function _delete($related_field, $arguments)
  1811. {
  1812. return $this->delete($arguments[0], $related_field);
  1813. }
  1814. // --------------------------------------------------------------------
  1815. /**
  1816. * Delete All
  1817. *
  1818. * Deletes all records in this objects all list.
  1819. *
  1820. * @return bool Success or Failure of the delete
  1821. */
  1822. public function delete_all()
  1823. {
  1824. $success = TRUE;
  1825. foreach($this as $item)
  1826. {
  1827. if ( ! empty($item->id))
  1828. {
  1829. $success_temp = $item->delete();
  1830. $success = $success && $success_temp;
  1831. }
  1832. }
  1833. $this->clear();
  1834. return $success;
  1835. }
  1836. // --------------------------------------------------------------------
  1837. /**
  1838. * Truncate
  1839. *
  1840. * Deletes all records in this objects table.
  1841. *
  1842. * @return bool Success or Failure of the truncate
  1843. */
  1844. public function truncate()
  1845. {
  1846. // Begin auto transaction
  1847. $this->_auto_trans_begin();
  1848. // Delete all "has many" and "has one" relations for this object first
  1849. foreach (array('has_many', 'has_one') as $type)
  1850. {
  1851. foreach ($this->{$type} as $model => $properties)
  1852. {
  1853. // do we want cascading delete's?
  1854. if ($properties['cascade_delete'])
  1855. {
  1856. // Prepare model
  1857. $class = $properties['class'];
  1858. $object = new $class();
  1859. $this_model = $properties['join_self_as'];
  1860. $other_model = $properties['join_other_as'];
  1861. // Determine relationship table name
  1862. $relationship_table = $this->_get_relationship_table($object, $model);
  1863. // We have to just set NULL for in-table foreign keys that
  1864. // are pointing at this object
  1865. if($relationship_table == $object->table && // ITFK
  1866. // NOT ITFKs that point at the other object
  1867. ! ($object->table == $this->table && // self-referencing has_one join
  1868. in_array($other_model . '_id', $this->fields)) // where the ITFK is for the other object
  1869. )
  1870. {
  1871. $data = array($this_model . '_id' => NULL);
  1872. // Update table to remove all ITFK relations
  1873. $this->db->update($object->table, $data);
  1874. }
  1875. else if ($relationship_table != $this->table)
  1876. {
  1877. // Delete all relationship records
  1878. $this->db->truncate($relationship_table);
  1879. }
  1880. // Else, no reason to delete the relationships on this table
  1881. }
  1882. }
  1883. }
  1884. // Delete all records
  1885. $this->db->truncate($this->table);
  1886. // Complete auto transaction
  1887. $this->_auto_trans_complete('truncate');
  1888. // Clear this object
  1889. $this->clear();
  1890. return TRUE;
  1891. }
  1892. // --------------------------------------------------------------------
  1893. /**
  1894. * Refresh All
  1895. *
  1896. * Removes any empty objects in this objects all list.
  1897. * Only needs to be used if you are looping through the all list
  1898. * a second time and you have deleted a record the first time through.
  1899. *
  1900. * @return bool FALSE if the $all array was already empty.
  1901. */
  1902. public function refresh_all()
  1903. {
  1904. if ( ! empty($this->all))
  1905. {
  1906. $all = array();
  1907. foreach ($this->all as $item)
  1908. {
  1909. if ( ! empty($item->id))
  1910. {
  1911. $all[] = $item;
  1912. }
  1913. }
  1914. $this->all = $all;
  1915. return TRUE;
  1916. }
  1917. return FALSE;
  1918. }
  1919. // --------------------------------------------------------------------
  1920. /**
  1921. * Validate
  1922. *
  1923. * Validates the value of each property against the assigned validation rules.
  1924. *
  1925. * @param mixed $object Objects included with the validation [from save()].
  1926. * @param string $related_field See save.
  1927. * @return DataMapper Returns $this for method chanining.
  1928. */
  1929. public function validate($object = '', $related_field = '')
  1930. {
  1931. // Return if validation has already been run
  1932. if ($this->_validated)
  1933. {
  1934. // For method chaining
  1935. return $this;
  1936. }
  1937. // Set validated as having been run
  1938. $this->_validated = TRUE;
  1939. // Clear errors
  1940. $this->error = new DM_Error_Object();
  1941. // Loop through each property to be validated
  1942. foreach ($this->validation as $field => $validation)
  1943. {
  1944. if(empty($validation['rules']))
  1945. {
  1946. continue;
  1947. }
  1948. // Get validation settings
  1949. $rules = $validation['rules'];
  1950. // Will validate differently if this is for a related item
  1951. $related = (isset($this->has_many[$field]) || isset($this->has_one[$field]));
  1952. // Check if property has changed since validate last ran
  1953. if ($related || $this->_force_validation || ! isset($this->stored->{$field}) || $this->{$field} !== $this->stored->{$field})
  1954. {
  1955. // Only validate if field is related or required or has a value
  1956. if ( ! $related && ! in_array('required', $rules) && ! in_array('always_validate', $rules))
  1957. {
  1958. if ( ! isset($this->{$field}) || $this->{$field} === '')
  1959. {
  1960. continue;
  1961. }
  1962. }
  1963. $label = ( ! empty($validation['label'])) ? $validation['label'] : $field;
  1964. // Loop through each rule to validate this property against
  1965. foreach ($rules as $rule => $param)
  1966. {
  1967. // Check for parameter
  1968. if (is_numeric($rule))
  1969. {
  1970. $rule = $param;
  1971. $param = '';
  1972. }
  1973. // Clear result
  1974. $result = '';
  1975. // Clear message
  1976. $line = FALSE;
  1977. // Check rule exists
  1978. if ($related)
  1979. {
  1980. // Prepare rule to use different language file lines
  1981. $rule = 'related_' . $rule;
  1982. $arg = $object;
  1983. if( ! empty($related_field)) {
  1984. $arg = array($related_field => $object);
  1985. }
  1986. if (method_exists($this, '_' . $rule))
  1987. {
  1988. // Run related rule from DataMapper or the class extending DataMapper
  1989. $line = $result = $this->{'_' . $rule}($arg, $field, $param);
  1990. }
  1991. else if($this->_extension_method_exists('rule_' . $rule))
  1992. {
  1993. $line = $result = $this->{'rule_' . $rule}($arg, $field, $param);
  1994. }
  1995. }
  1996. else if (method_exists($this, '_' . $rule))
  1997. {
  1998. // Run rule from DataMapper or the class extending DataMapper
  1999. $line = $result = $this->{'_' . $rule}($field, $param);
  2000. }
  2001. else if($this->_extension_method_exists('rule_' . $rule))
  2002. {
  2003. // Run an extension-based rule.
  2004. $line = $result = $this->{'rule_' . $rule}($field, $param);
  2005. }
  2006. else if (method_exists($this->form_validation, $rule))
  2007. {
  2008. // Run rule from CI Form Validation
  2009. $result = $this->form_validation->{$rule}($this->{$field}, $param);
  2010. }
  2011. else if (function_exists($rule))
  2012. {
  2013. // Run rule from PHP
  2014. $this->{$field} = $rule($this->{$field});
  2015. }
  2016. // Add an error message if the rule returned FALSE
  2017. if (is_string($line) || $result === FALSE)
  2018. {
  2019. if(!is_string($line))
  2020. {
  2021. if (FALSE === ($line = $this->ci_lang->dm_line($rule)))
  2022. {
  2023. // Get corresponding error from language file
  2024. $line = 'Unable to access an error message corresponding to your rule name: '.$rule.'.';
  2025. }
  2026. }
  2027. // Check if param is an array
  2028. if (is_array($param))
  2029. {
  2030. // Convert into a string so it can be used in the error message
  2031. $param = implode(', ', $param);
  2032. // Replace last ", " with " or "
  2033. if (FALSE !== ($pos = strrpos($param, ', ')))
  2034. {
  2035. $param = substr_replace($param, ' or ', $pos, 2);
  2036. }
  2037. }
  2038. // Check if param is a validation field
  2039. if (isset($this->validation[$param]))
  2040. {
  2041. // Change it to the label value
  2042. $param = $this->validation[$param]['label'];
  2043. }
  2044. // Add error message
  2045. $this->error_message($field, sprintf($line, $label, $param));
  2046. // Escape to prevent further error checks
  2047. break;
  2048. }
  2049. }
  2050. }
  2051. }
  2052. // Set whether validation passed
  2053. $this->valid = empty($this->error->all);
  2054. // For method chaining
  2055. return $this;
  2056. }
  2057. // --------------------------------------------------------------------
  2058. /**
  2059. * Skips validation for the next call to save.
  2060. * Note that this also prevents the validation routine from running until the next get.
  2061. *
  2062. * @param object $skip If FALSE, re-enables validation.
  2063. * @return DataMapper Returns self for method chaining.
  2064. */
  2065. public function skip_validation($skip = TRUE)
  2066. {
  2067. $this->_validated = $skip;
  2068. $this->valid = $skip;
  2069. return $this;
  2070. }
  2071. // --------------------------------------------------------------------
  2072. /**
  2073. * Force revalidation for the next call to save.
  2074. * This allows you to run validation rules on fields that haven't been modified
  2075. *
  2076. * @param object $force If TRUE, forces validation on all fields.
  2077. * @return DataMapper Returns self for method chaining.
  2078. */
  2079. public function force_validation($force = TRUE)
  2080. {
  2081. $this->_force_validation = $force;
  2082. return $this;
  2083. }
  2084. // --------------------------------------------------------------------
  2085. /**
  2086. * Clear
  2087. *
  2088. * Clears the current object.
  2089. */
  2090. public function clear()
  2091. {
  2092. // Clear the all list
  2093. $this->all = array();
  2094. // Clear errors
  2095. $this->error = new DM_Error_Object();
  2096. // Clear this objects properties and set blank error messages in case they are accessed
  2097. foreach ($this->fields as $field)
  2098. {
  2099. $this->{$field} = NULL;
  2100. }
  2101. // Clear this objects "has many" related objects
  2102. foreach ($this->has_many as $related => $properties)
  2103. {
  2104. unset($this->{$related});
  2105. }
  2106. // Clear this objects "has one" related objects
  2107. foreach ($this->has_one as $related => $properties)
  2108. {
  2109. unset($this->{$related});
  2110. }
  2111. // Clear the query related list
  2112. $this->_query_related = array();
  2113. // Clear and refresh stored values
  2114. $this->stored = new stdClass();
  2115. // Clear the saved iterator
  2116. unset($this->_dm_dataset_iterator);
  2117. $this->_refresh_stored_values();
  2118. }
  2119. // --------------------------------------------------------------------
  2120. /**
  2121. * Clears the db object after processing a query, or returning the
  2122. * SQL for a query.
  2123. *
  2124. * @ignore
  2125. */
  2126. protected function _clear_after_query()
  2127. {
  2128. // clear the query as if it was run
  2129. $this->db->dm_call_method('_reset_select');
  2130. // in case some include_related instantiations were set up, clear them
  2131. $this->_instantiations = NULL;
  2132. // Clear the query related list (Thanks to TheJim)
  2133. $this->_query_related = array();
  2134. // Clear the saved iterator
  2135. unset($this->_dm_dataset_iterator);
  2136. }
  2137. // --------------------------------------------------------------------
  2138. /**
  2139. * Count
  2140. *
  2141. * Returns the total count of the object records from the database.
  2142. * If on a related object, returns the total count of related objects records.
  2143. *
  2144. * @param array $exclude_ids A list of ids to exlcude from the count
  2145. * @return int Number of rows in query.
  2146. */
  2147. public function count($exclude_ids = NULL, $column = NULL, $related_id = NULL)
  2148. {
  2149. // Check if related object
  2150. if ( ! empty($this->parent))
  2151. {
  2152. // Prepare model
  2153. $related_field = $this->parent['model'];
  2154. $related_properties = $this->_get_related_properties($related_field);
  2155. $class = $related_properties['class'];
  2156. $other_model = $related_properties['join_other_as'];
  2157. $this_model = $related_properties['join_self_as'];
  2158. $object = new $class();
  2159. // Determine relationship table name
  2160. $relationship_table = $this->_get_relationship_table($object, $related_field);
  2161. // To ensure result integrity, group all previous queries
  2162. $where = $this->db->dm_get('ar_where');
  2163. if( ! empty($where))
  2164. {
  2165. // if the relationship table is different from our table, include our table in the count query
  2166. if ($relationship_table != $this->table)
  2167. {
  2168. $this->db->join($this->table, $this->table . '.id = ' . $relationship_table . '.' . $this_model.'_id', 'LEFT OUTER');
  2169. }
  2170. $arwhere = $this->db->dm_get('ar_where');
  2171. array_unshift($arwhere, '( ');
  2172. $arwhere[] = ' )';
  2173. $this->db->dm_set('ar_where', $arwhere);
  2174. }
  2175. // We have to query special for in-table foreign keys that
  2176. // are pointing at this object
  2177. if($relationship_table == $object->table && // ITFK
  2178. // NOT ITFKs that point at the other object
  2179. ! ($object->table == $this->table && // self-referencing has_one join
  2180. in_array($other_model . '_id', $this->fields)) // where the ITFK is for the other object
  2181. )
  2182. {
  2183. // ITFK on the other object's table
  2184. $this->db->where('id', $this->parent['id'])->where($this_model . '_id IS NOT NULL');
  2185. }
  2186. else
  2187. {
  2188. // All other cases
  2189. $this->db->where($relationship_table . '.' . $other_model . '_id', $this->parent['id']);
  2190. }
  2191. if(!empty($exclude_ids))
  2192. {
  2193. $this->db->where_not_in($relationship_table . '.' . $this_model . '_id', $exclude_ids);
  2194. }
  2195. if($column == 'id')
  2196. {
  2197. $column = $relationship_table . '.' . $this_model . '_id';
  2198. }
  2199. if(!empty($related_id))
  2200. {
  2201. $this->db->where($this_model . '_id', $related_id);
  2202. }
  2203. $this->db->from($relationship_table);
  2204. }
  2205. else
  2206. {
  2207. $this->db->from($this->table);
  2208. if(!empty($exclude_ids))
  2209. {
  2210. $this->db->where_not_in('id', $exclude_ids);
  2211. }
  2212. if(!empty($related_id))
  2213. {
  2214. $this->db->where('id', $related_id);
  2215. }
  2216. $column = $this->add_table_name($column);
  2217. }
  2218. // Manually overridden to allow for COUNT(DISTINCT COLUMN)
  2219. $select = $this->db->_count_string;
  2220. if(!empty($column))
  2221. {
  2222. // COUNT DISTINCT
  2223. $select = 'SELECT COUNT(DISTINCT ' . $this->db->dm_call_method('_protect_identifiers', $column) . ') AS ';
  2224. }
  2225. $sql = $this->db->dm_call_method('_compile_select', $select . $this->db->dm_call_method('_protect_identifiers', 'numrows'));
  2226. $query = $this->db->query($sql);
  2227. $this->db->dm_call_method('_reset_select');
  2228. if ($query->num_rows() == 0)
  2229. {
  2230. return 0;
  2231. }
  2232. $row = $query->row();
  2233. return intval($row->numrows);
  2234. }
  2235. // --------------------------------------------------------------------
  2236. /**
  2237. * Count Distinct
  2238. *
  2239. * Returns the total count of distinct object records from the database.
  2240. * If on a related object, returns the total count of related objects records.
  2241. *
  2242. * @param array $exclude_ids A list of ids to exlcude from the count
  2243. * @param string $column If provided, use this column for the DISTINCT instead of 'id'
  2244. * @return int Number of rows in query.
  2245. */
  2246. public function count_distinct($exclude_ids = NULL, $column = 'id')
  2247. {
  2248. return $this->count($exclude_ids, $column);
  2249. }
  2250. // --------------------------------------------------------------------
  2251. /**
  2252. * Convenience method to return the number of items from
  2253. * the last call to get.
  2254. *
  2255. * @return int
  2256. */
  2257. public function result_count() {
  2258. if(isset($this->_dm_dataset_iterator)) {
  2259. return $this->_dm_dataset_iterator->result_count();
  2260. } else {
  2261. return count($this->all);
  2262. }
  2263. }
  2264. // --------------------------------------------------------------------
  2265. /**
  2266. * Exists
  2267. *
  2268. * Returns TRUE if the current object has a database record.
  2269. *
  2270. * @return bool
  2271. */
  2272. public function exists()
  2273. {
  2274. // returns TRUE if the id of this object is set and not empty, OR
  2275. // there are items in the ALL array.
  2276. return isset($this->id) ? !empty($this->id) : ($this->result_count() > 0);
  2277. }
  2278. // --------------------------------------------------------------------
  2279. /**
  2280. * Query
  2281. *
  2282. * Runs the specified query and populates the current object with the results.
  2283. *
  2284. * Warning: Use at your own risk. This will only be as reliable as your query.
  2285. *
  2286. * @param string $sql The query to process
  2287. * @param array|bool $binds Array of values to bind (see CodeIgniter)
  2288. * @return DataMapper Returns self for method chaining.
  2289. */
  2290. public function query($sql, $binds = FALSE)
  2291. {
  2292. // Get by objects properties
  2293. $query = $this->db->query($sql, $binds);
  2294. $this->_process_query($query);
  2295. // For method chaining
  2296. return $this;
  2297. }
  2298. // --------------------------------------------------------------------
  2299. /**
  2300. * Check Last Query
  2301. * Renders the last DB query performed.
  2302. *
  2303. * @param array $delims Delimiters for the SQL string.
  2304. * @param bool $return_as_string If TRUE, don't output automatically.
  2305. * @return string Last db query formatted as a string.
  2306. */
  2307. public function check_last_query($delims = array('<pre>', '</pre>'), $return_as_string = FALSE) {
  2308. $q = wordwrap($this->db->last_query(), 100, "\n\t");
  2309. if(!empty($delims)) {
  2310. $q = implode($q, $delims);
  2311. }
  2312. if($return_as_string === FALSE) {
  2313. echo $q;
  2314. }
  2315. return $q;
  2316. }
  2317. // --------------------------------------------------------------------
  2318. /**
  2319. * Error Message
  2320. *
  2321. * Adds an error message to this objects error object.
  2322. *
  2323. * @param string $field Field to set the error on.
  2324. * @param string $error Error message.
  2325. */
  2326. public function error_message($field, $error)
  2327. {
  2328. if ( ! empty($field) && ! empty($error))
  2329. {
  2330. // Set field specific error
  2331. $this->error->{$field} = $this->error_prefix . $error . $this->error_suffix;
  2332. // Add field error to errors all list
  2333. $this->error->all[$field] = $this->error->{$field};
  2334. // Append field error to error message string
  2335. $this->error->string .= $this->error->{$field};
  2336. }
  2337. }
  2338. // --------------------------------------------------------------------
  2339. /**
  2340. * Get Clone
  2341. *
  2342. * Returns a clone of the current object.
  2343. *
  2344. * @return DataMapper Cloned copy of this object.
  2345. */
  2346. public function get_clone($force_db = FALSE)
  2347. {
  2348. $temp = clone($this);
  2349. // This must be left in place, even with the __clone method,
  2350. // or else the DB will not be copied over correctly.
  2351. if($force_db ||
  2352. (($this->db_params !== FALSE) && isset($this->db)) )
  2353. {
  2354. // create a copy of $this->db
  2355. $temp->db = clone($this->db);
  2356. }
  2357. return $temp;
  2358. }
  2359. // --------------------------------------------------------------------
  2360. /**
  2361. * Get Copy
  2362. *
  2363. * Returns an unsaved copy of the current object.
  2364. *
  2365. * @return DataMapper Cloned copy of this object with an empty ID for saving as new.
  2366. */
  2367. public function get_copy($force_db = FALSE)
  2368. {
  2369. $copy = $this->get_clone($force_db);
  2370. $copy->id = NULL;
  2371. return $copy;
  2372. }
  2373. // --------------------------------------------------------------------
  2374. /**
  2375. * Get By
  2376. *
  2377. * Gets objects by specified field name and value.
  2378. *
  2379. * @ignore
  2380. * @param string $field Field to look at.
  2381. * @param array $value Arguments to this method.
  2382. * @return DataMapper Returns self for method chaining.
  2383. */
  2384. protected function _get_by($field, $value = array())
  2385. {
  2386. if (isset($value[0]))
  2387. {
  2388. $this->where($field, $value[0]);
  2389. }
  2390. return $this->get();
  2391. }
  2392. // --------------------------------------------------------------------
  2393. /**
  2394. * Get By Related
  2395. *
  2396. * Gets objects by specified related object and optionally by field name and value.
  2397. *
  2398. * @ignore
  2399. * @param mixed $model Related Model or Object
  2400. * @param array $arguments Arguments to the where method
  2401. * @return DataMapper Returns self for method chaining.
  2402. */
  2403. protected function _get_by_related($model, $arguments = array())
  2404. {
  2405. if ( ! empty($model))
  2406. {
  2407. // Add model to start of arguments
  2408. $arguments = array_merge(array($model), $arguments);
  2409. }
  2410. $this->_related('where', $arguments);
  2411. return $this->get();
  2412. }
  2413. // --------------------------------------------------------------------
  2414. /**
  2415. * Handles the adding the related part of a query if $parent is set
  2416. *
  2417. * @ignore
  2418. * @return bool Success or failure
  2419. */
  2420. protected function _handle_related()
  2421. {
  2422. if ( ! empty($this->parent))
  2423. {
  2424. $has_many = array_key_exists($this->parent['model'], $this->has_many);
  2425. $has_one = array_key_exists($this->parent['model'], $this->has_one);
  2426. // If this is a "has many" or "has one" related item
  2427. if ($has_many || $has_one)
  2428. {
  2429. if( ! $this->_get_relation($this->parent['model'], $this->parent['id']))
  2430. {
  2431. return FALSE;
  2432. }
  2433. }
  2434. else
  2435. {
  2436. // provide feedback on errors
  2437. $this_model = get_class($this);
  2438. show_error("DataMapper Error: '".$this->parent['model']."' is not a valid parent relationship for $this_model. Are your relationships configured correctly?");
  2439. }
  2440. }
  2441. return TRUE;
  2442. }
  2443. // --------------------------------------------------------------------
  2444. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  2445. * *
  2446. * Active Record methods *
  2447. * *
  2448. * The following are methods used to provide Active Record *
  2449. * functionality for data retrieval. *
  2450. * *
  2451. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
  2452. // --------------------------------------------------------------------
  2453. /**
  2454. * Add Table Name
  2455. *
  2456. * Adds the table name to a field if necessary
  2457. *
  2458. * @param string $field Field to add the table name to.
  2459. * @return string Possibly modified field name.
  2460. */
  2461. public function add_table_name($field)
  2462. {
  2463. // only add table if the field doesn't contain a dot (.) or open parentheses
  2464. if (preg_match('/[\.\(]/', $field) == 0)
  2465. {
  2466. // split string into parts, add field
  2467. $field_parts = explode(',', $field);
  2468. $field = '';
  2469. foreach ($field_parts as $part)
  2470. {
  2471. if ( ! empty($field))
  2472. {
  2473. $field .= ', ';
  2474. }
  2475. $part = ltrim($part);
  2476. // handle comparison operators on where
  2477. $subparts = explode(' ', $part, 2);
  2478. if ($subparts[0] == '*' || in_array($subparts[0], $this->fields))
  2479. {
  2480. $field .= $this->table . '.' . $part;
  2481. }
  2482. else
  2483. {
  2484. $field .= $part;
  2485. }
  2486. }
  2487. }
  2488. return $field;
  2489. }
  2490. // --------------------------------------------------------------------
  2491. /**
  2492. * Creates a SQL-function with the given (optional) arguments.
  2493. *
  2494. * Each argument can be one of several forms:
  2495. * 1) An un escaped string value, which will be automatically escaped: "hello"
  2496. * 2) An escaped value or non-string, which is copied directly: "'hello'" 123, etc
  2497. * 3) An operator, *, or a non-escaped string is copied directly: "[non-escaped]" ">", etc
  2498. * 4) A field on this model: "@property" (Also, "@<whatever>" will be copied directly
  2499. * 5) A field on a related or deeply related model: "@model/property" "@model/other_model/property"
  2500. * 6) An array, which is processed recursively as a forumla.
  2501. *
  2502. * @param string $function_name Function name.
  2503. * @param mixed $args,... (Optional) Any commands that need to be passed to the function.
  2504. * @return string The new SQL function string.
  2505. */
  2506. public function func($function_name)
  2507. {
  2508. $ret = $function_name . '(';
  2509. $args = func_get_args();
  2510. // pop the function name
  2511. array_shift($args);
  2512. $comma = '';
  2513. foreach($args as $arg)
  2514. {
  2515. $ret .= $comma . $this->_process_function_arg($arg);
  2516. if(empty($comma))
  2517. {
  2518. $comma = ', ';
  2519. }
  2520. }
  2521. $ret .= ')';
  2522. return $ret;
  2523. }
  2524. // private method to convert function arguments into SQL
  2525. protected function _process_function_arg($arg, $is_formula = FALSE)
  2526. {
  2527. $ret = '';
  2528. if(is_array($arg)) {
  2529. // formula
  2530. foreach($arg as $func => $formula_arg) {
  2531. if(!empty($ret)) {
  2532. $ret .= ' ';
  2533. }
  2534. if(is_numeric($func)) {
  2535. // process non-functions
  2536. $ret .= $this->_process_function_arg($formula_arg, TRUE);
  2537. } else {
  2538. // recursively process functions within functions
  2539. $func_args = array_merge(array($func), (array)$formula_arg);
  2540. $ret .= call_user_func_array(array($this, 'func'), $func_args);
  2541. }
  2542. }
  2543. return $ret;
  2544. }
  2545. $operators = array(
  2546. 'AND', 'OR', 'NOT', // binary logic
  2547. '<', '>', '<=', '>=', '=', '<>', '!=', // comparators
  2548. '+', '-', '*', '/', '%', '^', // basic maths
  2549. '|/', '||/', '!', '!!', '@', '&', '|', '#', '~', // advanced maths
  2550. '<<', '>>'); // binary operators
  2551. if(is_string($arg))
  2552. {
  2553. if( ($is_formula && in_array($arg, $operators)) ||
  2554. $arg == '*' ||
  2555. ($arg[0] == "'" && $arg[strlen($arg)-1] == "'") ||
  2556. ($arg[0] == "[" && $arg[strlen($arg)-1] == "]") )
  2557. {
  2558. // simply add already-escaped strings, the special * value, or operators in formulas
  2559. if($arg[0] == "[" && $arg[strlen($arg)-1] == "]") {
  2560. // Arguments surrounded by square brackets are added directly, minus the brackets
  2561. $arg = substr($arg, 1, -1);
  2562. }
  2563. $ret .= $arg;
  2564. }
  2565. else if($arg[0] == '@')
  2566. {
  2567. // model or sub-model property
  2568. $arg = substr($arg, 1);
  2569. if(strpos($arg, '/') !== FALSE)
  2570. {
  2571. // related property
  2572. if(strpos($arg, 'parent/') === 0)
  2573. {
  2574. // special parent property for subqueries
  2575. $ret .= str_replace('parent/', '${parent}.', $arg);
  2576. }
  2577. else
  2578. {
  2579. $rel_elements = explode('/', $arg);
  2580. $property = array_pop($rel_elements);
  2581. $table = $this->_add_related_table(implode('/', $rel_elements));
  2582. $ret .= $this->db->protect_identifiers($table . '.' . $property);
  2583. }
  2584. }
  2585. else
  2586. {
  2587. $ret .= $this->db->protect_identifiers($this->add_table_name($arg));
  2588. }
  2589. }
  2590. else
  2591. {
  2592. $ret .= $this->db->escape($arg);
  2593. }
  2594. }
  2595. else
  2596. {
  2597. $ret .= $arg;
  2598. }
  2599. return $ret;
  2600. }
  2601. // --------------------------------------------------------------------
  2602. /**
  2603. * Used by the magic method for select_func, {where}_func, etc
  2604. *
  2605. * @ignore
  2606. * @param object $query Name of query function
  2607. * @param array $args Arguments for func()
  2608. * @return DataMapper Returns self for method chaining.
  2609. */
  2610. protected function _func($query, $args)
  2611. {
  2612. if(count($args) < 2)
  2613. {
  2614. throw new Exception("Invalid number of arguments to {$query}_func: must be at least 2 arguments.");
  2615. }
  2616. if($query == 'select')
  2617. {
  2618. $alias = array_pop($args);
  2619. $value = call_user_func_array(array($this, 'func'), $args);
  2620. $value .= " AS $alias";
  2621. // we can't use the normal select method, because CI likes to breaky
  2622. $this->_add_to_select_directly($value);
  2623. return $this;
  2624. }
  2625. else
  2626. {
  2627. $param = array_pop($args);
  2628. $value = call_user_func_array(array($this, 'func'), $args);
  2629. return $this->{$query}($value, $param);
  2630. }
  2631. }
  2632. // --------------------------------------------------------------------
  2633. /**
  2634. * Used by the magic method for {where}_field_func, etc.
  2635. *
  2636. * @ignore
  2637. * @param string $query Name of query function
  2638. * @param array $args Arguments for func()
  2639. * @return DataMapper Returns self for method chaining.
  2640. */
  2641. protected function _field_func($query, $args)
  2642. {
  2643. if(count($args) < 2)
  2644. {
  2645. throw new Exception("Invalid number of arguments to {$query}_field_func: must be at least 2 arguments.");
  2646. }
  2647. $field = array_shift($args);
  2648. $func = call_user_func_array(array($this, 'func'), $args);
  2649. return $this->_process_special_query_clause($query, $field, $func);
  2650. }
  2651. // --------------------------------------------------------------------
  2652. /**
  2653. * Used by the magic method for select_subquery {where}_subquery, etc
  2654. *
  2655. * @ignore
  2656. * @param string $query Name of query function
  2657. * @param array $args Arguments for subquery
  2658. * @return DataMapper Returns self for method chaining.
  2659. */
  2660. protected function _subquery($query, $args)
  2661. {
  2662. if(count($args) < 1)
  2663. {
  2664. throw new Exception("Invalid arguments on {$query}_subquery: must be at least one argument.");
  2665. }
  2666. if($query == 'select')
  2667. {
  2668. if(count($args) < 2)
  2669. {
  2670. throw new Exception('Invalid number of arguments to select_subquery: must be exactly 2 arguments.');
  2671. }
  2672. $sql = $this->_parse_subquery_object($args[0]);
  2673. $alias = $args[1];
  2674. // we can't use the normal select method, because CI likes to breaky
  2675. $this->_add_to_select_directly("$sql AS $alias");
  2676. return $this;
  2677. }
  2678. else
  2679. {
  2680. $object = $field = $value = NULL;
  2681. if(is_object($args[0]) ||
  2682. (is_string($args[0]) && !isset($args[1])) )
  2683. {
  2684. $field = $this->_parse_subquery_object($args[0]);
  2685. if(isset($args[1])) {
  2686. $value = $this->db->protect_identifiers($this->add_table_name($args[1]));
  2687. }
  2688. }
  2689. else
  2690. {
  2691. $field = $this->add_table_name($args[0]);
  2692. $value = $args[1];
  2693. if(is_object($value))
  2694. {
  2695. $value = $this->_parse_subquery_object($value);
  2696. }
  2697. }
  2698. $extra = NULL;
  2699. if(isset($args[2])) {
  2700. $extra = $args[2];
  2701. }
  2702. return $this->_process_special_query_clause($query, $field, $value, $extra);
  2703. }
  2704. }
  2705. // --------------------------------------------------------------------
  2706. /**
  2707. * Parses and protects a subquery.
  2708. * Automatically replaces the special ${parent} argument with a reference to
  2709. * this table.
  2710. *
  2711. * Also replaces all table references that would overlap with this object.
  2712. *
  2713. * @ignore
  2714. * @param object $sql SQL string to process
  2715. * @return string Processed SQL string.
  2716. */
  2717. protected function _parse_subquery_object($sql)
  2718. {
  2719. if(is_object($sql))
  2720. {
  2721. $sql = '(' . $sql->get_sql() . ')';
  2722. }
  2723. // Table Name pattern should be
  2724. $tablename = $this->db->dm_call_method('_escape_identifiers', $this->table);
  2725. $table_pattern = '(?:' . preg_quote($this->table) . '|' . preg_quote($tablename) . '|\(' . preg_quote($tablename) . '\))';
  2726. $fieldname = $this->db->dm_call_method('_escape_identifiers', '__field__');
  2727. $field_pattern = '([-\w]+|' . str_replace('__field__', '[-\w]+', preg_quote($fieldname)) . ')';
  2728. // replace all table.field references
  2729. // pattern ends up being [^_](table|`table`).(field|`field`)
  2730. // the NOT _ at the beginning is to prevent replacing of advanced relationship table references.
  2731. $pattern = '/([^_])' . $table_pattern . '\.' . $field_pattern . '/i';
  2732. // replacement ends up being `table_subquery`.`$1`
  2733. $replacement = '$1' . $this->db->dm_call_method('_escape_identifiers', $this->table . '_subquery') . '.$2';
  2734. $sql = preg_replace($pattern, $replacement, $sql);
  2735. // now replace all "table table" aliases
  2736. // important: the space at the end is required
  2737. $pattern = "/$table_pattern $table_pattern /i";
  2738. $replacement = $tablename . ' ' . $this->db->dm_call_method('_escape_identifiers', $this->table . '_subquery') . ' ';
  2739. $sql = preg_replace($pattern, $replacement, $sql);
  2740. // now replace "FROM table" for self relationships
  2741. $pattern = "/FROM $table_pattern([,\\s])/i";
  2742. $replacement = "FROM $tablename " . $this->db->dm_call_method('_escape_identifiers', $this->table . '_subquery') . '$1';
  2743. $sql = preg_replace($pattern, $replacement, $sql);
  2744. $sql = str_replace("\n", "\n\t", $sql);
  2745. return str_replace('${parent}', $this->table, $sql);
  2746. }
  2747. // --------------------------------------------------------------------
  2748. /**
  2749. * Manually adds an item to the SELECT column, to prevent it from
  2750. * being broken by AR->select
  2751. *
  2752. * @ignore
  2753. * @param string $value New SELECT value
  2754. */
  2755. protected function _add_to_select_directly($value)
  2756. {
  2757. // copied from system/database/DB_activerecord.php
  2758. $var = $this->db->dm_get('ar_select');
  2759. $var[] = $value;
  2760. $this->db->dm_set('ar_select', $var);
  2761. if ($this->db->dm_get('ar_caching') === TRUE)
  2762. {
  2763. $var = $this->db->dm_get('ar_cache_select');
  2764. $var[] = $value;
  2765. $this->db->dm_set('ar_cache_select', $var);
  2766. $var = $this->db->dm_get('ar_cache_exists');
  2767. $var[] = 'select';
  2768. $this->db->dm_set('ar_cache_exists', $var);
  2769. }
  2770. }
  2771. // --------------------------------------------------------------------
  2772. /**
  2773. * Handles specialized where clauses, like subqueries and functions
  2774. *
  2775. * @ignore
  2776. * @param string $query Query function
  2777. * @param string $field Field for Query function
  2778. * @param mixed $value Value for Query function
  2779. * @param mixed $extra If included, overrides the default assumption of FALSE for the third parameter to $query
  2780. * @return DataMapper Returns self for method chaining.
  2781. */
  2782. protected function _process_special_query_clause($query, $field, $value, $extra = NULL) {
  2783. if(strpos($query, 'where_in') !== FALSE) {
  2784. $query = str_replace('_in', '', $query);
  2785. $field .= ' IN ';
  2786. } else if(strpos($query, 'where_not_in') !== FALSE) {
  2787. $query = str_replace('_not_in', '', $query);
  2788. $field .= ' NOT IN ';
  2789. }
  2790. if(is_null($extra)) {
  2791. $extra = FALSE;
  2792. }
  2793. return $this->{$query}($field, $value, $extra);
  2794. }
  2795. // --------------------------------------------------------------------
  2796. /**
  2797. * Select
  2798. *
  2799. * Sets the SELECT portion of the query.
  2800. *
  2801. * @param mixed $select Field(s) to select, array or comma separated string
  2802. * @param bool $escape If FALSE, don't escape this field (Probably won't work)
  2803. * @return DataMapper Returns self for method chaining.
  2804. */
  2805. public function select($select = '*', $escape = NULL)
  2806. {
  2807. if ($escape !== FALSE) {
  2808. if (!is_array($select)) {
  2809. $select = $this->add_table_name($select);
  2810. } else {
  2811. $updated = array();
  2812. foreach ($select as $sel) {
  2813. $updated = $this->add_table_name($sel);
  2814. }
  2815. $select = $updated;
  2816. }
  2817. }
  2818. $this->db->select($select, $escape);
  2819. // For method chaining
  2820. return $this;
  2821. }
  2822. // --------------------------------------------------------------------
  2823. /**
  2824. * Select Max
  2825. *
  2826. * Sets the SELECT MAX(field) portion of a query.
  2827. *
  2828. * @param string $select Field to look at.
  2829. * @param string $alias Alias of the MAX value.
  2830. * @return DataMapper Returns self for method chaining.
  2831. */
  2832. public function select_max($select = '', $alias = '')
  2833. {
  2834. // Check if this is a related object
  2835. if ( ! empty($this->parent))
  2836. {
  2837. $alias = ($alias != '') ? $alias : $select;
  2838. }
  2839. $this->db->select_max($this->add_table_name($select), $alias);
  2840. // For method chaining
  2841. return $this;
  2842. }
  2843. // --------------------------------------------------------------------
  2844. /**
  2845. * Select Min
  2846. *
  2847. * Sets the SELECT MIN(field) portion of a query.
  2848. *
  2849. * @param string $select Field to look at.
  2850. * @param string $alias Alias of the MIN value.
  2851. * @return DataMapper Returns self for method chaining.
  2852. */
  2853. public function select_min($select = '', $alias = '')
  2854. {
  2855. // Check if this is a related object
  2856. if ( ! empty($this->parent))
  2857. {
  2858. $alias = ($alias != '') ? $alias : $select;
  2859. }
  2860. $this->db->select_min($this->add_table_name($select), $alias);
  2861. // For method chaining
  2862. return $this;
  2863. }
  2864. // --------------------------------------------------------------------
  2865. /**
  2866. * Select Avg
  2867. *
  2868. * Sets the SELECT AVG(field) portion of a query.
  2869. *
  2870. * @param string $select Field to look at.
  2871. * @param string $alias Alias of the AVG value.
  2872. * @return DataMapper Returns self for method chaining.
  2873. */
  2874. public function select_avg($select = '', $alias = '')
  2875. {
  2876. // Check if this is a related object
  2877. if ( ! empty($this->parent))
  2878. {
  2879. $alias = ($alias != '') ? $alias : $select;
  2880. }
  2881. $this->db->select_avg($this->add_table_name($select), $alias);
  2882. // For method chaining
  2883. return $this;
  2884. }
  2885. // --------------------------------------------------------------------
  2886. /**
  2887. * Select Sum
  2888. *
  2889. * Sets the SELECT SUM(field) portion of a query.
  2890. *
  2891. * @param string $select Field to look at.
  2892. * @param string $alias Alias of the SUM value.
  2893. * @return DataMapper Returns self for method chaining.
  2894. */
  2895. public function select_sum($select = '', $alias = '')
  2896. {
  2897. // Check if this is a related object
  2898. if ( ! empty($this->parent))
  2899. {
  2900. $alias = ($alias != '') ? $alias : $select;
  2901. }
  2902. $this->db->select_sum($this->add_table_name($select), $alias);
  2903. // For method chaining
  2904. return $this;
  2905. }
  2906. // --------------------------------------------------------------------
  2907. /**
  2908. * Distinct
  2909. *
  2910. * Sets the flag to add DISTINCT to the query.
  2911. *
  2912. * @param bool $value Set to FALSE to turn back off DISTINCT
  2913. * @return DataMapper Returns self for method chaining.
  2914. */
  2915. public function distinct($value = TRUE)
  2916. {
  2917. $this->db->distinct($value);
  2918. // For method chaining
  2919. return $this;
  2920. }
  2921. // --------------------------------------------------------------------
  2922. /**
  2923. * Get Where
  2924. *
  2925. * Get items matching the where clause.
  2926. *
  2927. * @param mixed $where See where()
  2928. * @param integer|NULL $limit Limit the number of results.
  2929. * @param integer|NULL $offset Offset the results when limiting.
  2930. * @return DataMapper Returns self for method chaining.
  2931. */
  2932. public function get_where($where = array(), $limit = NULL, $offset = NULL)
  2933. {
  2934. $this->where($where);
  2935. return $this->get($limit, $offset);
  2936. }
  2937. // --------------------------------------------------------------------
  2938. /**
  2939. * Starts a query group.
  2940. *
  2941. * @param string $not (Internal use only)
  2942. * @param string $type (Internal use only)
  2943. * @return DataMapper Returns self for method chaining.
  2944. */
  2945. public function group_start($not = '', $type = 'AND ')
  2946. {
  2947. // Increment group count number to make them unique
  2948. $this->_group_count++;
  2949. // in case groups are being nested
  2950. $type = $this->_get_prepend_type($type);
  2951. $this->_where_group_started = TRUE;
  2952. $prefix = (count($this->db->dm_get('ar_where')) == 0 AND count($this->db->dm_get('ar_cache_where')) == 0) ? '' : $type;
  2953. $value = $prefix . $not . str_repeat(' ', $this->_group_count) . ' (';
  2954. $var = $this->db->dm_get('ar_where');
  2955. $var[] = $value;
  2956. $this->db->dm_set('ar_where', $var);
  2957. if ($this->db->dm_get('ar_caching'))
  2958. {
  2959. $var = $this->db->dm_get('ar_cache_where');
  2960. $var[] = $value;
  2961. $this->db->dm_set('ar_cache_where', $var);
  2962. }
  2963. return $this;
  2964. }
  2965. // --------------------------------------------------------------------
  2966. /**
  2967. * Starts a query group, but ORs the group
  2968. * @return DataMapper Returns self for method chaining.
  2969. */
  2970. public function or_group_start()
  2971. {
  2972. return $this->group_start('', 'OR ');
  2973. }
  2974. // --------------------------------------------------------------------
  2975. /**
  2976. * Starts a query group, but NOTs the group
  2977. * @return DataMapper Returns self for method chaining.
  2978. */
  2979. public function not_group_start()
  2980. {
  2981. return $this->group_start('NOT ', 'OR ');
  2982. }
  2983. // --------------------------------------------------------------------
  2984. /**
  2985. * Starts a query group, but OR NOTs the group
  2986. * @return DataMapper Returns self for method chaining.
  2987. */
  2988. public function or_not_group_start()
  2989. {
  2990. return $this->group_start('NOT ', 'OR ');
  2991. }
  2992. // --------------------------------------------------------------------
  2993. /**
  2994. * Ends a query group.
  2995. * @return DataMapper Returns self for method chaining.
  2996. */
  2997. public function group_end()
  2998. {
  2999. $value = str_repeat(' ', $this->_group_count) . ')';
  3000. $var = $this->db->dm_get('ar_where');
  3001. $var[] = $value;
  3002. $this->db->dm_set('ar_where', $var);
  3003. if($this->db->ar_caching) $this->db->ar_cache_where[] = $value;
  3004. $this->_where_group_started = FALSE;
  3005. return $this;
  3006. }
  3007. // --------------------------------------------------------------------
  3008. /**
  3009. * protected function to convert the AND or OR prefix to '' when starting
  3010. * a group.
  3011. *
  3012. * @ignore
  3013. * @param object $type Current type value
  3014. * @return New type value
  3015. */
  3016. protected function _get_prepend_type($type)
  3017. {
  3018. if($this->_where_group_started)
  3019. {
  3020. $type = '';
  3021. $this->_where_group_started = FALSE;
  3022. }
  3023. return $type;
  3024. }
  3025. // --------------------------------------------------------------------
  3026. /**
  3027. * Where
  3028. *
  3029. * Sets the WHERE portion of the query.
  3030. * Separates multiple calls with AND.
  3031. *
  3032. * Called by get_where()
  3033. *
  3034. * @param mixed $key A field or array of fields to check.
  3035. * @param mixed $value For a single field, the value to compare to.
  3036. * @param bool $escape If FALSE, the field is not escaped.
  3037. * @return DataMapper Returns self for method chaining.
  3038. */
  3039. public function where($key, $value = NULL, $escape = TRUE)
  3040. {
  3041. return $this->_where($key, $value, 'AND ', $escape);
  3042. }
  3043. // --------------------------------------------------------------------
  3044. /**
  3045. * Or Where
  3046. *
  3047. * Sets the WHERE portion of the query.
  3048. * Separates multiple calls with OR.
  3049. *
  3050. * @param mixed $key A field or array of fields to check.
  3051. * @param mixed $value For a single field, the value to compare to.
  3052. * @param bool $escape If FALSE, the field is not escaped.
  3053. * @return DataMapper Returns self for method chaining.
  3054. */
  3055. public function or_where($key, $value = NULL, $escape = TRUE)
  3056. {
  3057. return $this->_where($key, $value, 'OR ', $escape);
  3058. }
  3059. // --------------------------------------------------------------------
  3060. /**
  3061. * Where
  3062. *
  3063. * Called by where() or or_where().
  3064. *
  3065. * @ignore
  3066. * @param mixed $key A field or array of fields to check.
  3067. * @param mixed $value For a single field, the value to compare to.
  3068. * @param string $type Type of addition (AND or OR)
  3069. * @param bool $escape If FALSE, the field is not escaped.
  3070. * @return DataMapper Returns self for method chaining.
  3071. */
  3072. protected function _where($key, $value = NULL, $type = 'AND ', $escape = NULL)
  3073. {
  3074. if ( ! is_array($key))
  3075. {
  3076. $key = array($key => $value);
  3077. }
  3078. foreach ($key as $k => $v)
  3079. {
  3080. $new_k = $this->add_table_name($k);
  3081. $this->db->dm_call_method('_where', $new_k, $v, $this->_get_prepend_type($type), $escape);
  3082. }
  3083. // For method chaining
  3084. return $this;
  3085. }
  3086. // --------------------------------------------------------------------
  3087. /**
  3088. * Where Between
  3089. *
  3090. * Sets the WHERE field BETWEEN 'value1' AND 'value2' SQL query joined with
  3091. * AND if appropriate.
  3092. *
  3093. * @param string $key A field to check.
  3094. * @param mixed $value value to start with
  3095. * @param mixed $value value to end with
  3096. * @return DataMapper Returns self for method chaining.
  3097. */
  3098. public function where_between($key = NULL, $value1 = NULL, $value2 = NULL)
  3099. {
  3100. return $this->_where_between($key, $value1, $value2);
  3101. }
  3102. // --------------------------------------------------------------------
  3103. /**
  3104. * Where Between
  3105. *
  3106. * Sets the WHERE field BETWEEN 'value1' AND 'value2' SQL query joined with
  3107. * AND if appropriate.
  3108. *
  3109. * @param string $key A field to check.
  3110. * @param mixed $value value to start with
  3111. * @param mixed $value value to end with
  3112. * @return DataMapper Returns self for method chaining.
  3113. */
  3114. public function where_not_between($key = NULL, $value1 = NULL, $value2 = NULL)
  3115. {
  3116. return $this->_where_between($key, $value1, $value2, TRUE);
  3117. }
  3118. // --------------------------------------------------------------------
  3119. /**
  3120. * Where Between
  3121. *
  3122. * Sets the WHERE field BETWEEN 'value1' AND 'value2' SQL query joined with
  3123. * AND if appropriate.
  3124. *
  3125. * @param string $key A field to check.
  3126. * @param mixed $value value to start with
  3127. * @param mixed $value value to end with
  3128. * @return DataMapper Returns self for method chaining.
  3129. */
  3130. public function or_where_between($key = NULL, $value1 = NULL, $value2 = NULL)
  3131. {
  3132. return $this->_where_between($key, $value1, $value2, FALSE, 'OR ');
  3133. }
  3134. // --------------------------------------------------------------------
  3135. /**
  3136. * Where Between
  3137. *
  3138. * Sets the WHERE field BETWEEN 'value1' AND 'value2' SQL query joined with
  3139. * AND if appropriate.
  3140. *
  3141. * @param string $key A field to check.
  3142. * @param mixed $value value to start with
  3143. * @param mixed $value value to end with
  3144. * @return DataMapper Returns self for method chaining.
  3145. */
  3146. public function or_where_not_between($key = NULL, $value1 = NULL, $value2 = NULL)
  3147. {
  3148. return $this->_where_between($key, $value1, $value2, TRUE, 'OR ');
  3149. }
  3150. // --------------------------------------------------------------------
  3151. /**
  3152. * Where In
  3153. *
  3154. * Sets the WHERE field IN ('item', 'item') SQL query joined with
  3155. * AND if appropriate.
  3156. *
  3157. * @param string $key A field to check.
  3158. * @param array $values An array of values to compare against
  3159. * @return DataMapper Returns self for method chaining.
  3160. */
  3161. public function where_in($key = NULL, $values = NULL)
  3162. {
  3163. return $this->_where_in($key, $values);
  3164. }
  3165. // --------------------------------------------------------------------
  3166. /**
  3167. * Or Where In
  3168. *
  3169. * Sets the WHERE field IN ('item', 'item') SQL query joined with
  3170. * OR if appropriate.
  3171. *
  3172. * @param string $key A field to check.
  3173. * @param array $values An array of values to compare against
  3174. * @return DataMapper Returns self for method chaining.
  3175. */
  3176. public function or_where_in($key = NULL, $values = NULL)
  3177. {
  3178. return $this->_where_in($key, $values, FALSE, 'OR ');
  3179. }
  3180. // --------------------------------------------------------------------
  3181. /**
  3182. * Where Not In
  3183. *
  3184. * Sets the WHERE field NOT IN ('item', 'item') SQL query joined with
  3185. * AND if appropriate.
  3186. *
  3187. * @param string $key A field to check.
  3188. * @param array $values An array of values to compare against
  3189. * @return DataMapper Returns self for method chaining.
  3190. */
  3191. public function where_not_in($key = NULL, $values = NULL)
  3192. {
  3193. return $this->_where_in($key, $values, TRUE);
  3194. }
  3195. // --------------------------------------------------------------------
  3196. /**
  3197. * Or Where Not In
  3198. *
  3199. * Sets the WHERE field NOT IN ('item', 'item') SQL query joined wuth
  3200. * OR if appropriate.
  3201. *
  3202. * @param string $key A field to check.
  3203. * @param array $values An array of values to compare against
  3204. * @return DataMapper Returns self for method chaining.
  3205. */
  3206. public function or_where_not_in($key = NULL, $values = NULL)
  3207. {
  3208. return $this->_where_in($key, $values, TRUE, 'OR ');
  3209. }
  3210. // --------------------------------------------------------------------
  3211. /**
  3212. * Where In
  3213. *
  3214. * Called by where_in(), or_where_in(), where_not_in(), or or_where_not_in().
  3215. *
  3216. * @ignore
  3217. * @param string $key A field to check.
  3218. * @param array $values An array of values to compare against
  3219. * @param bool $not If TRUE, use NOT IN instead of IN.
  3220. * @param string $type The type of connection (AND or OR)
  3221. * @return DataMapper Returns self for method chaining.
  3222. */
  3223. protected function _where_in($key = NULL, $values = NULL, $not = FALSE, $type = 'AND ')
  3224. {
  3225. $type = $this->_get_prepend_type($type);
  3226. if ($values instanceOf DataMapper)
  3227. {
  3228. $arr = array();
  3229. foreach ($values as $value)
  3230. {
  3231. $arr[] = $value->id;
  3232. }
  3233. $values = $arr;
  3234. }
  3235. $this->db->dm_call_method('_where_in', $this->add_table_name($key), $values, $not, $type);
  3236. // For method chaining
  3237. return $this;
  3238. }
  3239. // --------------------------------------------------------------------
  3240. /**
  3241. * Where Between
  3242. *
  3243. * Called by where_between(), or_where_between(), where_not_between(), or or_where_not_between().
  3244. *
  3245. * @ignore
  3246. * @param string $key A field to check.
  3247. * @param mixed $value value to start with
  3248. * @param mixed $value value to end with
  3249. * @param bool $not If TRUE, use NOT IN instead of IN.
  3250. * @param string $type The type of connection (AND or OR)
  3251. * @return DataMapper Returns self for method chaining.
  3252. */
  3253. protected function _where_between($key = NULL, $value1 = NULL, $value2 = NULL, $not = FALSE, $type = 'AND ')
  3254. {
  3255. $type = $this->_get_prepend_type($type);
  3256. $this->db->dm_call_method('_where', $this->add_table_name($key)." ".($not?"NOT ":"")."BETWEEN ".$this->db->escape($value1)." AND ".$this->db->escape($value2), NULL, $type, NULL);
  3257. // For method chaining
  3258. return $this;
  3259. }
  3260. // --------------------------------------------------------------------
  3261. /**
  3262. * Like
  3263. *
  3264. * Sets the %LIKE% portion of the query.
  3265. * Separates multiple calls with AND.
  3266. *
  3267. * @param mixed $field A field or array of fields to check.
  3268. * @param mixed $match For a single field, the value to compare to.
  3269. * @param string $side One of 'both', 'before', or 'after'
  3270. * @return DataMapper Returns self for method chaining.
  3271. */
  3272. public function like($field, $match = '', $side = 'both')
  3273. {
  3274. return $this->_like($field, $match, 'AND ', $side);
  3275. }
  3276. // --------------------------------------------------------------------
  3277. /**
  3278. * Not Like
  3279. *
  3280. * Sets the NOT LIKE portion of the query.
  3281. * Separates multiple calls with AND.
  3282. *
  3283. * @param mixed $field A field or array of fields to check.
  3284. * @param mixed $match For a single field, the value to compare to.
  3285. * @param string $side One of 'both', 'before', or 'after'
  3286. * @return DataMapper Returns self for method chaining.
  3287. */
  3288. public function not_like($field, $match = '', $side = 'both')
  3289. {
  3290. return $this->_like($field, $match, 'AND ', $side, 'NOT');
  3291. }
  3292. // --------------------------------------------------------------------
  3293. /**
  3294. * Or Like
  3295. *
  3296. * Sets the %LIKE% portion of the query.
  3297. * Separates multiple calls with OR.
  3298. *
  3299. * @param mixed $field A field or array of fields to check.
  3300. * @param mixed $match For a single field, the value to compare to.
  3301. * @param string $side One of 'both', 'before', or 'after'
  3302. * @return DataMapper Returns self for method chaining.
  3303. */
  3304. public function or_like($field, $match = '', $side = 'both')
  3305. {
  3306. return $this->_like($field, $match, 'OR ', $side);
  3307. }
  3308. // --------------------------------------------------------------------
  3309. /**
  3310. * Or Not Like
  3311. *
  3312. * Sets the NOT LIKE portion of the query.
  3313. * Separates multiple calls with OR.
  3314. *
  3315. * @param mixed $field A field or array of fields to check.
  3316. * @param mixed $match For a single field, the value to compare to.
  3317. * @param string $side One of 'both', 'before', or 'after'
  3318. * @return DataMapper Returns self for method chaining.
  3319. */
  3320. public function or_not_like($field, $match = '', $side = 'both')
  3321. {
  3322. return $this->_like($field, $match, 'OR ', $side, 'NOT');
  3323. }
  3324. // --------------------------------------------------------------------
  3325. /**
  3326. * ILike
  3327. *
  3328. * Sets the case-insensitive %LIKE% portion of the query.
  3329. *
  3330. * @param mixed $field A field or array of fields to check.
  3331. * @param mixed $match For a single field, the value to compare to.
  3332. * @param string $side One of 'both', 'before', or 'after'
  3333. * @return DataMapper Returns self for method chaining.
  3334. */
  3335. public function ilike($field, $match = '', $side = 'both')
  3336. {
  3337. return $this->_like($field, $match, 'AND ', $side, '', TRUE);
  3338. }
  3339. // --------------------------------------------------------------------
  3340. /**
  3341. * Not ILike
  3342. *
  3343. * Sets the case-insensitive NOT LIKE portion of the query.
  3344. * Separates multiple calls with AND.
  3345. *
  3346. * @param mixed $field A field or array of fields to check.
  3347. * @param mixed $match For a single field, the value to compare to.
  3348. * @param string $side One of 'both', 'before', or 'after'
  3349. * @return DataMapper Returns self for method chaining.
  3350. */
  3351. public function not_ilike($field, $match = '', $side = 'both')
  3352. {
  3353. return $this->_like($field, $match, 'AND ', $side, 'NOT', TRUE);
  3354. }
  3355. // --------------------------------------------------------------------
  3356. /**
  3357. * Or Like
  3358. *
  3359. * Sets the case-insensitive %LIKE% portion of the query.
  3360. * Separates multiple calls with OR.
  3361. *
  3362. * @param mixed $field A field or array of fields to check.
  3363. * @param mixed $match For a single field, the value to compare to.
  3364. * @param string $side One of 'both', 'before', or 'after'
  3365. * @return DataMapper Returns self for method chaining.
  3366. */
  3367. public function or_ilike($field, $match = '', $side = 'both')
  3368. {
  3369. return $this->_like($field, $match, 'OR ', $side, '', TRUE);
  3370. }
  3371. // --------------------------------------------------------------------
  3372. /**
  3373. * Or Not Like
  3374. *
  3375. * Sets the case-insensitive NOT LIKE portion of the query.
  3376. * Separates multiple calls with OR.
  3377. *
  3378. * @param mixed $field A field or array of fields to check.
  3379. * @param mixed $match For a single field, the value to compare to.
  3380. * @param string $side One of 'both', 'before', or 'after'
  3381. * @return DataMapper Returns self for method chaining.
  3382. */
  3383. public function or_not_ilike($field, $match = '', $side = 'both')
  3384. {
  3385. return $this->_like($field, $match, 'OR ', $side, 'NOT', TRUE);
  3386. }
  3387. // --------------------------------------------------------------------
  3388. /**
  3389. * _Like
  3390. *
  3391. * Private function to do actual work.
  3392. * NOTE: this does NOT use the built-in ActiveRecord LIKE function.
  3393. *
  3394. * @ignore
  3395. * @param mixed $field A field or array of fields to check.
  3396. * @param mixed $match For a single field, the value to compare to.
  3397. * @param string $type The type of connection (AND or OR)
  3398. * @param string $side One of 'both', 'before', or 'after'
  3399. * @param string $not 'NOT' or ''
  3400. * @param bool $no_case If TRUE, configure to ignore case.
  3401. * @return DataMapper Returns self for method chaining.
  3402. */
  3403. protected function _like($field, $match = '', $type = 'AND ', $side = 'both', $not = '', $no_case = FALSE)
  3404. {
  3405. if ( ! is_array($field))
  3406. {
  3407. $field = array($field => $match);
  3408. }
  3409. foreach ($field as $k => $v)
  3410. {
  3411. $new_k = $this->add_table_name($k);
  3412. if ($new_k != $k)
  3413. {
  3414. $field[$new_k] = $v;
  3415. unset($field[$k]);
  3416. }
  3417. }
  3418. // Taken from CodeIgniter's Active Record because (for some reason)
  3419. // it is stored separately that normal where statements.
  3420. foreach ($field as $k => $v)
  3421. {
  3422. if($no_case)
  3423. {
  3424. $k = 'UPPER(' . $this->db->protect_identifiers($k) .')';
  3425. $v = strtoupper($v);
  3426. }
  3427. $f = "$k $not LIKE ";
  3428. if ($side == 'before')
  3429. {
  3430. $m = "%{$v}";
  3431. }
  3432. elseif ($side == 'after')
  3433. {
  3434. $m = "{$v}%";
  3435. }
  3436. else
  3437. {
  3438. $m = "%{$v}%";
  3439. }
  3440. $this->_where($f, $m, $type, TRUE);
  3441. }
  3442. // For method chaining
  3443. return $this;
  3444. }
  3445. // --------------------------------------------------------------------
  3446. /**
  3447. * Group By
  3448. *
  3449. * Sets the GROUP BY portion of the query.
  3450. *
  3451. * @param string $by Field to group by
  3452. * @return DataMapper Returns self for method chaining.
  3453. */
  3454. public function group_by($by)
  3455. {
  3456. $this->db->group_by($this->add_table_name($by));
  3457. // For method chaining
  3458. return $this;
  3459. }
  3460. // --------------------------------------------------------------------
  3461. /**
  3462. * Having
  3463. *
  3464. * Sets the HAVING portion of the query.
  3465. * Separates multiple calls with AND.
  3466. *
  3467. * @param string $key Field to compare.
  3468. * @param string $value value to compare to.
  3469. * @param bool $escape If FALSE, don't escape the value.
  3470. * @return DataMapper Returns self for method chaining.
  3471. */
  3472. public function having($key, $value = '', $escape = TRUE)
  3473. {
  3474. return $this->_having($key, $value, 'AND ', $escape);
  3475. }
  3476. // --------------------------------------------------------------------
  3477. /**
  3478. * Or Having
  3479. *
  3480. * Sets the OR HAVING portion of the query.
  3481. * Separates multiple calls with OR.
  3482. *
  3483. * @param string $key Field to compare.
  3484. * @param string $value value to compare to.
  3485. * @param bool $escape If FALSE, don't escape the value.
  3486. * @return DataMapper Returns self for method chaining.
  3487. */
  3488. public function or_having($key, $value = '', $escape = TRUE)
  3489. {
  3490. return $this->_having($key, $value, 'OR ', $escape);
  3491. }
  3492. // --------------------------------------------------------------------
  3493. /**
  3494. * Having
  3495. *
  3496. * Sets the HAVING portion of the query.
  3497. * Separates multiple calls with AND.
  3498. *
  3499. * @ignore
  3500. * @param string $key Field to compare.
  3501. * @param string $value value to compare to.
  3502. * @param string $type Type of connection (AND or OR)
  3503. * @param bool $escape If FALSE, don't escape the value.
  3504. * @return DataMapper Returns self for method chaining.
  3505. */
  3506. protected function _having($key, $value = '', $type = 'AND ', $escape = TRUE)
  3507. {
  3508. $this->db->dm_call_method('_having', $this->add_table_name($key), $value, $type, $escape);
  3509. // For method chaining
  3510. return $this;
  3511. }
  3512. // --------------------------------------------------------------------
  3513. /**
  3514. * Order By
  3515. *
  3516. * Sets the ORDER BY portion of the query.
  3517. *
  3518. * @param string $orderby Field to order by
  3519. * @param string $direction One of 'ASC' or 'DESC' Defaults to 'ASC'
  3520. * @return DataMapper Returns self for method chaining.
  3521. */
  3522. public function order_by($orderby, $direction = '')
  3523. {
  3524. // prefix the field with the table name if no prefix is given
  3525. strpos($orderby, '.') === FALSE AND $orderby = $this->add_table_name($orderby);
  3526. $this->db->order_by($orderby, $direction);
  3527. // For method chaining
  3528. return $this;
  3529. }
  3530. // --------------------------------------------------------------------
  3531. /**
  3532. * Adds in the defaut order_by items, if there are any, and
  3533. * order_by hasn't been overridden.
  3534. * @ignore
  3535. */
  3536. protected function _handle_default_order_by()
  3537. {
  3538. if(empty($this->default_order_by))
  3539. {
  3540. return;
  3541. }
  3542. $sel = $this->table . '.' . '*';
  3543. $sel_protect = $this->db->protect_identifiers($sel);
  3544. // only add the items if there isn't an existing order_by,
  3545. // AND the select statement is empty or includes * or table.* or `table`.*
  3546. if(empty($this->db->ar_orderby) &&
  3547. (
  3548. empty($this->db->ar_select) ||
  3549. in_array('*', $this->db->ar_select) ||
  3550. in_array($sel_protect, $this->db->ar_select) ||
  3551. in_array($sel, $this->db->ar_select)
  3552. ))
  3553. {
  3554. foreach($this->default_order_by as $k => $v) {
  3555. if(is_int($k)) {
  3556. $k = $v;
  3557. $v = '';
  3558. }
  3559. $k = $this->add_table_name($k);
  3560. $this->order_by($k, $v);
  3561. }
  3562. }
  3563. }
  3564. // --------------------------------------------------------------------
  3565. /**
  3566. * Limit
  3567. *
  3568. * Sets the LIMIT portion of the query.
  3569. *
  3570. * @param integer $limit Limit the number of results.
  3571. * @param integer|NULL $offset Offset the results when limiting.
  3572. * @return DataMapper Returns self for method chaining.
  3573. */
  3574. public function limit($value, $offset = '')
  3575. {
  3576. $this->db->limit($value, $offset);
  3577. // For method chaining
  3578. return $this;
  3579. }
  3580. // --------------------------------------------------------------------
  3581. /**
  3582. * Offset
  3583. *
  3584. * Sets the OFFSET portion of the query.
  3585. *
  3586. * @param integer $offset Offset the results when limiting.
  3587. * @return DataMapper Returns self for method chaining.
  3588. */
  3589. public function offset($offset)
  3590. {
  3591. $this->db->offset($offset);
  3592. // For method chaining
  3593. return $this;
  3594. }
  3595. // --------------------------------------------------------------------
  3596. /**
  3597. * Start Cache
  3598. *
  3599. * Starts AR caching.
  3600. */
  3601. public function start_cache()
  3602. {
  3603. $this->db->start_cache();
  3604. }
  3605. // --------------------------------------------------------------------
  3606. /**
  3607. * Stop Cache
  3608. *
  3609. * Stops AR caching.
  3610. */
  3611. public function stop_cache()
  3612. {
  3613. $this->db->stop_cache();
  3614. }
  3615. // --------------------------------------------------------------------
  3616. /**
  3617. * Flush Cache
  3618. *
  3619. * Empties the AR cache.
  3620. */
  3621. public function flush_cache()
  3622. {
  3623. $this->db->flush_cache();
  3624. }
  3625. // --------------------------------------------------------------------
  3626. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  3627. * *
  3628. * Transaction methods *
  3629. * *
  3630. * The following are methods used for transaction handling. *
  3631. * *
  3632. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
  3633. // --------------------------------------------------------------------
  3634. /**
  3635. * Trans Off
  3636. *
  3637. * This permits transactions to be disabled at run-time.
  3638. *
  3639. */
  3640. public function trans_off()
  3641. {
  3642. $this->db->trans_enabled = FALSE;
  3643. }
  3644. // --------------------------------------------------------------------
  3645. /**
  3646. * Trans Strict
  3647. *
  3648. * When strict mode is enabled, if you are running multiple groups of
  3649. * transactions, if one group fails all groups will be rolled back.
  3650. * If strict mode is disabled, each group is treated autonomously, meaning
  3651. * a failure of one group will not affect any others.
  3652. *
  3653. * @param bool $mode Set to false to disable strict mode.
  3654. */
  3655. public function trans_strict($mode = TRUE)
  3656. {
  3657. $this->db->trans_strict($mode);
  3658. }
  3659. // --------------------------------------------------------------------
  3660. /**
  3661. * Trans Start
  3662. *
  3663. * Start a transaction.
  3664. *
  3665. * @param bool $test_mode Set to TRUE to only run a test (and not commit)
  3666. */
  3667. public function trans_start($test_mode = FALSE)
  3668. {
  3669. $this->db->trans_start($test_mode);
  3670. }
  3671. // --------------------------------------------------------------------
  3672. /**
  3673. * Trans Complete
  3674. *
  3675. * Complete a transaction.
  3676. *
  3677. * @return bool Success or Failure
  3678. */
  3679. public function trans_complete()
  3680. {
  3681. return $this->db->trans_complete();
  3682. }
  3683. // --------------------------------------------------------------------
  3684. /**
  3685. * Trans Begin
  3686. *
  3687. * Begin a transaction.
  3688. *
  3689. * @param bool $test_mode Set to TRUE to only run a test (and not commit)
  3690. * @return bool Success or Failure
  3691. */
  3692. public function trans_begin($test_mode = FALSE)
  3693. {
  3694. return $this->db->trans_begin($test_mode);
  3695. }
  3696. // --------------------------------------------------------------------
  3697. /**
  3698. * Trans Status
  3699. *
  3700. * Lets you retrieve the transaction flag to determine if it has failed.
  3701. *
  3702. * @return bool Returns FALSE if the transaction has failed.
  3703. */
  3704. public function trans_status()
  3705. {
  3706. return $this->db->trans_status();
  3707. }
  3708. // --------------------------------------------------------------------
  3709. /**
  3710. * Trans Commit
  3711. *
  3712. * Commit a transaction.
  3713. *
  3714. * @return bool Success or Failure
  3715. */
  3716. public function trans_commit()
  3717. {
  3718. return $this->db->trans_commit();
  3719. }
  3720. // --------------------------------------------------------------------
  3721. /**
  3722. * Trans Rollback
  3723. *
  3724. * Rollback a transaction.
  3725. *
  3726. * @return bool Success or Failure
  3727. */
  3728. public function trans_rollback()
  3729. {
  3730. return $this->db->trans_rollback();
  3731. }
  3732. // --------------------------------------------------------------------
  3733. /**
  3734. * Auto Trans Begin
  3735. *
  3736. * Begin an auto transaction if enabled.
  3737. *
  3738. */
  3739. protected function _auto_trans_begin()
  3740. {
  3741. // Begin auto transaction
  3742. if ($this->auto_transaction)
  3743. {
  3744. $this->trans_begin();
  3745. }
  3746. }
  3747. // --------------------------------------------------------------------
  3748. /**
  3749. * Auto Trans Complete
  3750. *
  3751. * Complete an auto transaction if enabled.
  3752. *
  3753. * @param string $label Name for this transaction.
  3754. */
  3755. protected function _auto_trans_complete($label = 'complete')
  3756. {
  3757. // Complete auto transaction
  3758. if ($this->auto_transaction)
  3759. {
  3760. // Check if successful
  3761. if (!$this->trans_complete())
  3762. {
  3763. $rule = 'transaction';
  3764. // Get corresponding error from language file
  3765. if (FALSE === ($line = $this->ci_lang->dm_line($rule)))
  3766. {
  3767. $line = 'Unable to access the ' . $rule .' error message.';
  3768. }
  3769. // Add transaction error message
  3770. $this->error_message($rule, sprintf($line, $label));
  3771. // Set validation as failed
  3772. $this->valid = FALSE;
  3773. }
  3774. }
  3775. }
  3776. // --------------------------------------------------------------------
  3777. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  3778. * *
  3779. * Related methods *
  3780. * *
  3781. * The following are methods used for managing related records. *
  3782. * *
  3783. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
  3784. // --------------------------------------------------------------------
  3785. /**
  3786. * get_related_properties
  3787. *
  3788. * Located the relationship properties for a given field or model
  3789. * Can also optionally attempt to convert the $related_field to
  3790. * singular, and look up on that. It will modify the $related_field if
  3791. * the conversion to singular returns a result.
  3792. *
  3793. * $related_field can also be a deep relationship, such as
  3794. * 'post/editor/group', in which case the $related_field will be processed
  3795. * recursively, and the return value will be $user->has_NN['group'];
  3796. *
  3797. * @ignore
  3798. * @param mixed $related_field Name of related field or related object.
  3799. * @param bool $try_singular If TRUE, automatically tries to look for a singular name if not found.
  3800. * @return array Associative array of related properties.
  3801. */
  3802. public function _get_related_properties(&$related_field, $try_singular = FALSE)
  3803. {
  3804. // Handle deep relationships
  3805. if(strpos($related_field, '/') !== FALSE)
  3806. {
  3807. $rfs = explode('/', $related_field);
  3808. $last = $this;
  3809. $prop = NULL;
  3810. foreach($rfs as &$rf)
  3811. {
  3812. $prop = $last->_get_related_properties($rf, $try_singular);
  3813. if(is_null($prop))
  3814. {
  3815. break;
  3816. }
  3817. $last =& $last->_get_without_auto_populating($rf);
  3818. }
  3819. if( ! is_null($prop))
  3820. {
  3821. // update in case any items were converted to singular.
  3822. $related_field = implode('/', $rfs);
  3823. }
  3824. return $prop;
  3825. }
  3826. else
  3827. {
  3828. if (isset($this->has_many[$related_field]))
  3829. {
  3830. return $this->has_many[$related_field];
  3831. }
  3832. else if (isset($this->has_one[$related_field]))
  3833. {
  3834. return $this->has_one[$related_field];
  3835. }
  3836. else
  3837. {
  3838. if($try_singular)
  3839. {
  3840. $rf = singular($related_field);
  3841. $ret = $this->_get_related_properties($rf);
  3842. if( is_null($ret))
  3843. {
  3844. show_error("Unable to relate {$this->model} with $related_field.");
  3845. }
  3846. else
  3847. {
  3848. $related_field = $rf;
  3849. return $ret;
  3850. }
  3851. }
  3852. else
  3853. {
  3854. // not related
  3855. return NULL;
  3856. }
  3857. }
  3858. }
  3859. }
  3860. // --------------------------------------------------------------------
  3861. /**
  3862. * Add Related Table
  3863. *
  3864. * Adds the table of a related item, and joins it to this class.
  3865. * Returns the name of that table for further queries.
  3866. *
  3867. * If $related_field is deep, then this adds all necessary relationships
  3868. * to the query.
  3869. *
  3870. * @ignore
  3871. * @param mixed $object The object (or related field) to look up.
  3872. * @param string $related_field Related field name for object
  3873. * @param string $id_only Private, do not use.
  3874. * @param object $db Private, do not use.
  3875. * @param array $query_related Private, do not use.
  3876. * @param string $name_prepend Private, do not use.
  3877. * @param string $this_table Private, do not use.
  3878. * @return string Name of the related table, or table.field if ID_Only
  3879. */
  3880. public function _add_related_table($object, $related_field = '', $id_only = FALSE, $db = NULL, &$query_related = NULL, $name_prepend = '', $this_table = NULL)
  3881. {
  3882. if ( is_string($object))
  3883. {
  3884. // only a model was passed in, not an object
  3885. $related_field = $object;
  3886. $object = NULL;
  3887. }
  3888. else if (empty($related_field))
  3889. {
  3890. // model was not passed, so get the Object's native model
  3891. $related_field = $object->model;
  3892. }
  3893. $related_field = strtolower($related_field);
  3894. // Handle deep relationships
  3895. if(strpos($related_field, '/') !== FALSE)
  3896. {
  3897. $rfs = explode('/', $related_field);
  3898. $last = $this;
  3899. $prepend = '';
  3900. $object_as = NULL;
  3901. foreach($rfs as $index => $rf)
  3902. {
  3903. // if this is the last item added, we can use the $id_only
  3904. // shortcut to prevent unnecessarily adding the last table.
  3905. $temp_id_only = $id_only;
  3906. if($temp_id_only) {
  3907. if($index < count($rfs)-1) {
  3908. $temp_id_only = FALSE;
  3909. }
  3910. }
  3911. $object_as = $last->_add_related_table($rf, '', $temp_id_only, $this->db, $this->_query_related, $prepend, $object_as);
  3912. $prepend .= $rf . '_';
  3913. $last =& $last->_get_without_auto_populating($rf);
  3914. }
  3915. return $object_as;
  3916. }
  3917. $related_properties = $this->_get_related_properties($related_field);
  3918. $class = $related_properties['class'];
  3919. $this_model = $related_properties['join_self_as'];
  3920. $other_model = $related_properties['join_other_as'];
  3921. if (empty($object))
  3922. {
  3923. // no object was passed in, so create one
  3924. $object = new $class();
  3925. }
  3926. if(is_null($query_related))
  3927. {
  3928. $query_related =& $this->_query_related;
  3929. }
  3930. if(is_null($this_table))
  3931. {
  3932. $this_table = $this->table;
  3933. }
  3934. // Determine relationship table name
  3935. $relationship_table = $this->_get_relationship_table($object, $related_field);
  3936. // only add $related_field to the table name if the 'class' and 'related_field' aren't equal
  3937. // and the related object is in a different table
  3938. if ( ($class == $related_field) && ($this->table != $object->table) )
  3939. {
  3940. $object_as = $name_prepend . $object->table;
  3941. $relationship_as = $name_prepend . $relationship_table;
  3942. }
  3943. else
  3944. {
  3945. $object_as = $name_prepend . $related_field . '_' . $object->table;
  3946. $relationship_as = str_replace('.', '_', $name_prepend . $related_field . '_' . $relationship_table);
  3947. }
  3948. $other_column = $other_model . '_id';
  3949. $this_column = $this_model . '_id' ;
  3950. if(is_null($db)) {
  3951. $db = $this->db;
  3952. }
  3953. // Force the selection of the current object's columns
  3954. if (empty($db->ar_select))
  3955. {
  3956. $db->select($this->table . '.*');
  3957. }
  3958. // the extra in_array column check is for has_one self references
  3959. if ($relationship_table == $this->table && in_array($other_column, $this->fields))
  3960. {
  3961. // has_one relationship without a join table
  3962. if($id_only)
  3963. {
  3964. // nothing to join, just return the correct data
  3965. $object_as = $this_table . '.' . $other_column;
  3966. }
  3967. else if ( ! in_array($object_as, $query_related))
  3968. {
  3969. $db->join($object->table . ' ' .$object_as, $object_as . '.id = ' . $this_table . '.' . $other_column, 'LEFT OUTER');
  3970. $query_related[] = $object_as;
  3971. }
  3972. }
  3973. // the extra in_array column check is for has_one self references
  3974. else if ($relationship_table == $object->table && in_array($this_column, $object->fields))
  3975. {
  3976. // has_one relationship without a join table
  3977. if ( ! in_array($object_as, $query_related))
  3978. {
  3979. $db->join($object->table . ' ' .$object_as, $this_table . '.id = ' . $object_as . '.' . $this_column, 'LEFT OUTER');
  3980. $query_related[] = $object_as;
  3981. }
  3982. if($id_only)
  3983. {
  3984. // include the column name
  3985. $object_as .= '.id';
  3986. }
  3987. }
  3988. else
  3989. {
  3990. // has_one or has_many with a normal join table
  3991. // Add join if not already included
  3992. if ( ! in_array($relationship_as, $query_related))
  3993. {
  3994. $db->join($relationship_table . ' ' . $relationship_as, $this_table . '.id = ' . $relationship_as . '.' . $this_column, 'LEFT OUTER');
  3995. if($this->_include_join_fields) {
  3996. $fields = $db->field_data($relationship_table);
  3997. foreach($fields as $key => $f)
  3998. {
  3999. if($f->name == $this_column || $f->name == $other_column)
  4000. {
  4001. unset($fields[$key]);
  4002. }
  4003. }
  4004. // add all other fields
  4005. $selection = '';
  4006. foreach ($fields as $field)
  4007. {
  4008. $new_field = 'join_'.$field->name;
  4009. if (!empty($selection))
  4010. {
  4011. $selection .= ', ';
  4012. }
  4013. $selection .= $relationship_as.'.'.$field->name.' AS '.$new_field;
  4014. }
  4015. $db->select($selection);
  4016. // now reset the flag
  4017. $this->_include_join_fields = FALSE;
  4018. }
  4019. $query_related[] = $relationship_as;
  4020. }
  4021. if($id_only)
  4022. {
  4023. // no need to add the whole table
  4024. $object_as = $relationship_as . '.' . $other_column;
  4025. }
  4026. else if ( ! in_array($object_as, $query_related))
  4027. {
  4028. // Add join if not already included
  4029. $db->join($object->table . ' ' . $object_as, $object_as . '.id = ' . $relationship_as . '.' . $other_column, 'LEFT OUTER');
  4030. $query_related[] = $object_as;
  4031. }
  4032. }
  4033. return $object_as;
  4034. }
  4035. // --------------------------------------------------------------------
  4036. /**
  4037. * Related
  4038. *
  4039. * Sets the specified related query.
  4040. *
  4041. * @ignore
  4042. * @param string $query Query String
  4043. * @param array $arguments Arguments to process
  4044. * @param mixed $extra Used to prevent escaping in special circumstances.
  4045. * @return DataMapper Returns self for method chaining.
  4046. */
  4047. protected function _related($query, $arguments = array(), $extra = NULL)
  4048. {
  4049. if ( ! empty($query) && ! empty($arguments))
  4050. {
  4051. $object = $field = $value = NULL;
  4052. $next_arg = 1;
  4053. // Prepare model
  4054. if (is_object($arguments[0]))
  4055. {
  4056. $object = $arguments[0];
  4057. $related_field = $object->model;
  4058. // Prepare field and value
  4059. $field = (isset($arguments[1])) ? $arguments[1] : 'id';
  4060. $value = (isset($arguments[2])) ? $arguments[2] : $object->id;
  4061. $next_arg = 3;
  4062. }
  4063. else
  4064. {
  4065. $related_field = $arguments[0];
  4066. // the TRUE allows conversion to singular
  4067. $related_properties = $this->_get_related_properties($related_field, TRUE);
  4068. $class = $related_properties['class'];
  4069. // enables where_related_{model}($object)
  4070. if(isset($arguments[1]) && is_object($arguments[1]))
  4071. {
  4072. $object = $arguments[1];
  4073. // Prepare field and value
  4074. $field = (isset($arguments[2])) ? $arguments[2] : 'id';
  4075. $value = (isset($arguments[3])) ? $arguments[3] : $object->id;
  4076. $next_arg = 4;
  4077. }
  4078. else
  4079. {
  4080. $object = new $class();
  4081. // Prepare field and value
  4082. $field = (isset($arguments[1])) ? $arguments[1] : 'id';
  4083. $value = (isset($arguments[2])) ? $arguments[2] : NULL;
  4084. $next_arg = 3;
  4085. }
  4086. }
  4087. if(preg_replace('/[!=<> ]/ ', '', $field) == 'id')
  4088. {
  4089. // special case to prevent joining unecessary tables
  4090. $field = $this->_add_related_table($object, $related_field, TRUE);
  4091. }
  4092. else
  4093. {
  4094. // Determine relationship table name, and join the tables
  4095. $object_table = $this->_add_related_table($object, $related_field);
  4096. $field = $object_table . '.' . $field;
  4097. }
  4098. if(is_string($value) && strpos($value, '${parent}') !== FALSE) {
  4099. $extra = FALSE;
  4100. }
  4101. // allow special arguments to be passed into query methods
  4102. if(is_null($extra)) {
  4103. if(isset($arguments[$next_arg])) {
  4104. $extra = $arguments[$next_arg];
  4105. }
  4106. }
  4107. // Add query clause
  4108. if(is_null($extra))
  4109. {
  4110. // convert where to where_in if the value is an array or a DM object
  4111. if ($query == 'where')
  4112. {
  4113. if ( is_array($value) )
  4114. {
  4115. switch(count($value))
  4116. {
  4117. case 0:
  4118. $value = NULL;
  4119. break;
  4120. case 1:
  4121. $value = reset($value);
  4122. break;
  4123. default:
  4124. $query = 'where_in';
  4125. break;
  4126. }
  4127. }
  4128. elseif ( $value instanceOf DataMapper )
  4129. {
  4130. switch($value->result_count())
  4131. {
  4132. case 0:
  4133. $value = NULL;
  4134. break;
  4135. case 1:
  4136. $value = $value->id;
  4137. break;
  4138. default:
  4139. $query = 'where_in';
  4140. break;
  4141. }
  4142. }
  4143. }
  4144. $this->{$query}($field, $value);
  4145. }
  4146. else
  4147. {
  4148. $this->{$query}($field, $value, $extra);
  4149. }
  4150. }
  4151. // For method chaining
  4152. return $this;
  4153. }
  4154. // --------------------------------------------------------------------
  4155. /**
  4156. * Magic method to process a subquery for a related object.
  4157. * The format for this should be
  4158. * $object->{where}_related_subquery($related_item, $related_field, $subquery)
  4159. * related_field is optional
  4160. *
  4161. * @ignore
  4162. * @param string $query Query Method
  4163. * @param object $args Arguments for the query
  4164. * @return DataMapper Returns self for method chaining.
  4165. */
  4166. protected function _related_subquery($query, $args)
  4167. {
  4168. $rel_object = $args[0];
  4169. $field = $value = NULL;
  4170. if(isset($args[2])) {
  4171. $field = $args[1];
  4172. $value = $args[2];
  4173. } else {
  4174. $field = 'id';
  4175. $value = $args[1];
  4176. }
  4177. if(is_object($value))
  4178. {
  4179. // see 25_activerecord.php
  4180. $value = $this->_parse_subquery_object($value);
  4181. }
  4182. if(strpos($query, 'where_in') !== FALSE) {
  4183. $query = str_replace('_in', '', $query);
  4184. $field .= ' IN ';
  4185. }
  4186. return $this->_related($query, array($rel_object, $field, $value), FALSE);
  4187. }
  4188. // --------------------------------------------------------------------
  4189. /**
  4190. * Is Related To
  4191. * If this object is related to the provided object, returns TRUE.
  4192. * Otherwise returns FALSE.
  4193. * Optionally can be provided a related field and ID.
  4194. *
  4195. * @param mixed $related_field The related object or field name
  4196. * @param int $id ID to compare to if $related_field is a string
  4197. * @return bool TRUE or FALSE if this object is related to $related_field
  4198. */
  4199. public function is_related_to($related_field, $id = NULL)
  4200. {
  4201. if(is_object($related_field))
  4202. {
  4203. $id = $related_field->id;
  4204. $related_field = $related_field->model;
  4205. }
  4206. return ($this->{$related_field}->count(NULL, NULL, $id) > 0);
  4207. }
  4208. // --------------------------------------------------------------------
  4209. /**
  4210. * Include Related
  4211. *
  4212. * Joins specified values of a has_one object into the current query
  4213. * If $fields is NULL or '*', then all columns are joined (may require instantiation of the other object)
  4214. * If $fields is a single string, then just that column is joined.
  4215. * Otherwise, $fields should be an array of column names.
  4216. *
  4217. * $append_name can be used to override the default name to append, or set it to FALSE to prevent appending.
  4218. *
  4219. * @param mixed $related_field The related object or field name
  4220. * @param array $fields The fields to join (NULL or '*' means all fields, or use a single field or array of fields)
  4221. * @param bool $append_name The name to use for joining (with '_'), or FALSE to disable.
  4222. * @param bool $instantiate If TRUE, the results are instantiated into objects
  4223. * @return DataMapper Returns self for method chaining.
  4224. */
  4225. public function include_related($related_field, $fields = NULL, $append_name = TRUE, $instantiate = FALSE)
  4226. {
  4227. if (is_object($related_field))
  4228. {
  4229. $object = $related_field;
  4230. $related_field = $object->model;
  4231. $related_properties = $this->_get_related_properties($related_field);
  4232. }
  4233. else
  4234. {
  4235. // the TRUE allows conversion to singular
  4236. $related_properties = $this->_get_related_properties($related_field, TRUE);
  4237. $class = $related_properties['class'];
  4238. $object = new $class();
  4239. }
  4240. if(is_null($fields) || $fields == '*')
  4241. {
  4242. $fields = $object->fields;
  4243. }
  4244. else if ( ! is_array($fields))
  4245. {
  4246. $fields = array((string)$fields);
  4247. }
  4248. $rfs = explode('/', $related_field);
  4249. $last = $this;
  4250. foreach($rfs as $rf)
  4251. {
  4252. // prevent populating the related items.
  4253. $last =& $last->_get_without_auto_populating($rf);
  4254. }
  4255. $table = $this->_add_related_table($object, $related_field);
  4256. $append = '';
  4257. if($append_name !== FALSE)
  4258. {
  4259. if($append_name === TRUE)
  4260. {
  4261. $append = str_replace('/', '_', $related_field);
  4262. }
  4263. else
  4264. {
  4265. $append = $append_name;
  4266. }
  4267. $append .= '_';
  4268. }
  4269. // now add fields
  4270. $selection = '';
  4271. $property_map = array();
  4272. foreach ($fields as $field)
  4273. {
  4274. $new_field = $append . $field;
  4275. // prevent collisions
  4276. if(in_array($new_field, $this->fields)) {
  4277. if($instantiate && $field == 'id' && $new_field != 'id') {
  4278. $property_map[$new_field] = $field;
  4279. }
  4280. continue;
  4281. }
  4282. if (!empty($selection))
  4283. {
  4284. $selection .= ', ';
  4285. }
  4286. $selection .= $table.'.'.$field.' AS '.$new_field;
  4287. if($instantiate) {
  4288. $property_map[$new_field] = $field;
  4289. }
  4290. }
  4291. if(empty($selection))
  4292. {
  4293. log_message('debug', "DataMapper Warning (include_related): No fields were selected for {$this->model} on $related_field.");
  4294. }
  4295. else
  4296. {
  4297. if($instantiate)
  4298. {
  4299. if(is_null($this->_instantiations))
  4300. {
  4301. $this->_instantiations = array();
  4302. }
  4303. $this->_instantiations[$related_field] = $property_map;
  4304. }
  4305. $this->db->select($selection);
  4306. }
  4307. // For method chaining
  4308. return $this;
  4309. }
  4310. /**
  4311. * Legacy version of include_related
  4312. * DEPRECATED: Will be removed by 2.0
  4313. * @deprecated Please use include_related
  4314. */
  4315. public function join_related($related_field, $fields = NULL, $append_name = TRUE)
  4316. {
  4317. return $this->include_related($related_field, $fields, $append_name);
  4318. }
  4319. // --------------------------------------------------------------------
  4320. /**
  4321. * Includes the number of related items using a subquery.
  4322. *
  4323. * Default alias is {$related_field}_count
  4324. *
  4325. * @param mixed $related_field Field to count
  4326. * @param string $alias Alternative alias.
  4327. * @return DataMapper Returns self for method chaining.
  4328. */
  4329. public function include_related_count($related_field, $alias = NULL)
  4330. {
  4331. if (is_object($related_field))
  4332. {
  4333. $object = $related_field;
  4334. $related_field = $object->model;
  4335. $related_properties = $this->_get_related_properties($related_field);
  4336. }
  4337. else
  4338. {
  4339. // the TRUE allows conversion to singular
  4340. $related_properties = $this->_get_related_properties($related_field, TRUE);
  4341. $class = $related_properties['class'];
  4342. $object = new $class();
  4343. }
  4344. if(is_null($alias))
  4345. {
  4346. $alias = $related_field . '_count';
  4347. }
  4348. // Force the selection of the current object's columns
  4349. if (empty($this->db->ar_select))
  4350. {
  4351. $this->db->select($this->table . '.*');
  4352. }
  4353. // now generate a subquery for counting the related objects
  4354. $object->select_func('COUNT', '*', 'count');
  4355. $this_rel = $related_properties['other_field'];
  4356. $tablename = $object->_add_related_table($this, $this_rel);
  4357. $object->where($tablename . '.`id` = ', $this->db->dm_call_method('_escape_identifiers', '${parent}.id'), FALSE);
  4358. $this->select_subquery($object, $alias);
  4359. return $this;
  4360. }
  4361. // --------------------------------------------------------------------
  4362. /**
  4363. * Get Relation
  4364. *
  4365. * Finds all related records of this objects current record.
  4366. *
  4367. * @ignore
  4368. * @param mixed $related_field Related field or object
  4369. * @param int $id ID of related field or object
  4370. * @return bool Sucess or Failure
  4371. */
  4372. protected function _get_relation($related_field, $id)
  4373. {
  4374. // No related items
  4375. if (empty($related_field) || empty($id))
  4376. {
  4377. // Reset query
  4378. $this->db->dm_call_method('_reset_select');
  4379. return FALSE;
  4380. }
  4381. // To ensure result integrity, group all previous queries
  4382. if( ! empty($this->db->ar_where))
  4383. {
  4384. array_unshift($this->db->ar_where, '( ');
  4385. $this->db->ar_where[] = ' )';
  4386. }
  4387. // query all items related to the given model
  4388. $this->where_related($related_field, 'id', $id);
  4389. return TRUE;
  4390. }
  4391. // --------------------------------------------------------------------
  4392. /**
  4393. * Save Relation
  4394. *
  4395. * Saves the relation between this and the other object.
  4396. *
  4397. * @ignore
  4398. * @param DataMapper DataMapper Object to related to this object
  4399. * @param string Specific related field if necessary.
  4400. * @return bool Success or Failure
  4401. */
  4402. protected function _save_relation($object, $related_field = '')
  4403. {
  4404. if (empty($related_field))
  4405. {
  4406. $related_field = $object->model;
  4407. }
  4408. // the TRUE allows conversion to singular
  4409. $related_properties = $this->_get_related_properties($related_field, TRUE);
  4410. if ( ! empty($related_properties) && $this->exists() && $object->exists())
  4411. {
  4412. $this_model = $related_properties['join_self_as'];
  4413. $other_model = $related_properties['join_other_as'];
  4414. $other_field = $related_properties['other_field'];
  4415. // Determine relationship table name
  4416. $relationship_table = $this->_get_relationship_table($object, $related_field);
  4417. if($relationship_table == $this->table &&
  4418. // catch for self relationships.
  4419. in_array($other_model . '_id', $this->fields))
  4420. {
  4421. $this->{$other_model . '_id'} = $object->id;
  4422. $ret = $this->save();
  4423. // remove any one-to-one relationships with the other object
  4424. $this->_remove_other_one_to_one($related_field, $object);
  4425. return $ret;
  4426. }
  4427. else if($relationship_table == $object->table)
  4428. {
  4429. $object->{$this_model . '_id'} = $this->id;
  4430. $ret = $object->save();
  4431. // remove any one-to-one relationships with this object
  4432. $object->_remove_other_one_to_one($other_field, $this);
  4433. return $ret;
  4434. }
  4435. else
  4436. {
  4437. $data = array($this_model . '_id' => $this->id, $other_model . '_id' => $object->id);
  4438. // Check if relation already exists
  4439. $query = $this->db->get_where($relationship_table, $data, NULL, NULL);
  4440. if ($query->num_rows() == 0)
  4441. {
  4442. // If this object has a "has many" relationship with the other object
  4443. if (isset($this->has_many[$related_field]))
  4444. {
  4445. // If the other object has a "has one" relationship with this object
  4446. if (isset($object->has_one[$other_field]))
  4447. {
  4448. // And it has an existing relation
  4449. $query = $this->db->get_where($relationship_table, array($other_model . '_id' => $object->id), 1, 0);
  4450. if ($query->num_rows() > 0)
  4451. {
  4452. // Find and update the other objects existing relation to relate with this object
  4453. $this->db->where($other_model . '_id', $object->id);
  4454. $this->db->update($relationship_table, $data);
  4455. }
  4456. else
  4457. {
  4458. // Add the relation since one doesn't exist
  4459. $this->db->insert($relationship_table, $data);
  4460. }
  4461. return TRUE;
  4462. }
  4463. else if (isset($object->has_many[$other_field]))
  4464. {
  4465. // We can add the relation since this specific relation doesn't exist, and a "has many" to "has many" relationship exists between the objects
  4466. $this->db->insert($relationship_table, $data);
  4467. // Self relationships can be defined as reciprocal -- save the reverse relationship at the same time
  4468. if ($related_properties['reciprocal'])
  4469. {
  4470. $data = array($this_model . '_id' => $object->id, $other_model . '_id' => $this->id);
  4471. $this->db->insert($relationship_table, $data);
  4472. }
  4473. return TRUE;
  4474. }
  4475. }
  4476. // If this object has a "has one" relationship with the other object
  4477. else if (isset($this->has_one[$related_field]))
  4478. {
  4479. // And it has an existing relation
  4480. $query = $this->db->get_where($relationship_table, array($this_model . '_id' => $this->id), 1, 0);
  4481. if ($query->num_rows() > 0)
  4482. {
  4483. // Find and update the other objects existing relation to relate with this object
  4484. $this->db->where($this_model . '_id', $this->id);
  4485. $this->db->update($relationship_table, $data);
  4486. }
  4487. else
  4488. {
  4489. // Add the relation since one doesn't exist
  4490. $this->db->insert($relationship_table, $data);
  4491. }
  4492. return TRUE;
  4493. }
  4494. }
  4495. else
  4496. {
  4497. // Relationship already exists
  4498. return TRUE;
  4499. }
  4500. }
  4501. }
  4502. else
  4503. {
  4504. if( ! $object->exists())
  4505. {
  4506. $msg = 'dm_save_rel_noobj';
  4507. }
  4508. else if( ! $this->exists())
  4509. {
  4510. $msg = 'dm_save_rel_nothis';
  4511. }
  4512. else
  4513. {
  4514. $msg = 'dm_save_rel_failed';
  4515. }
  4516. $msg = $this->ci_lang->dm_line($msg);
  4517. $this->error_message($related_field, sprintf($msg, $related_field));
  4518. }
  4519. return FALSE;
  4520. }
  4521. // --------------------------------------------------------------------
  4522. /**
  4523. * Remove Other One-to-One
  4524. * Removes other relationships on a one-to-one ITFK relationship
  4525. *
  4526. * @ignore
  4527. * @param string $rf Related field to look at.
  4528. * @param DataMapper $object Object to look at.
  4529. */
  4530. protected function _remove_other_one_to_one($rf, $object)
  4531. {
  4532. if( ! $object->exists())
  4533. {
  4534. return;
  4535. }
  4536. $related_properties = $this->_get_related_properties($rf, TRUE);
  4537. if( ! array_key_exists($related_properties['other_field'], $object->has_one))
  4538. {
  4539. return;
  4540. }
  4541. // This should be a one-to-one relationship with an ITFK if we got this far.
  4542. $other_column = $related_properties['join_other_as'] . '_id';
  4543. $c = get_class($this);
  4544. $update = new $c();
  4545. $update->where($other_column, $object->id);
  4546. if($this->exists())
  4547. {
  4548. $update->where('id <>', $this->id);
  4549. }
  4550. $update->update($other_column, NULL);
  4551. }
  4552. // --------------------------------------------------------------------
  4553. /**
  4554. * Delete Relation
  4555. *
  4556. * Deletes the relation between this and the other object.
  4557. *
  4558. * @ignore
  4559. * @param DataMapper $object Object to remove the relationship to.
  4560. * @param string $related_field Optional specific related field
  4561. * @return bool Success or Failure
  4562. */
  4563. protected function _delete_relation($object, $related_field = '')
  4564. {
  4565. if (empty($related_field))
  4566. {
  4567. $related_field = $object->model;
  4568. }
  4569. // the TRUE allows conversion to singular
  4570. $related_properties = $this->_get_related_properties($related_field, TRUE);
  4571. if ( ! empty($related_properties) && ! empty($this->id) && ! empty($object->id))
  4572. {
  4573. $this_model = $related_properties['join_self_as'];
  4574. $other_model = $related_properties['join_other_as'];
  4575. // Determine relationship table name
  4576. $relationship_table = $this->_get_relationship_table($object, $related_field);
  4577. if ($relationship_table == $this->table &&
  4578. // catch for self relationships.
  4579. in_array($other_model . '_id', $this->fields))
  4580. {
  4581. $this->{$other_model . '_id'} = NULL;
  4582. $this->save();
  4583. }
  4584. else if ($relationship_table == $object->table)
  4585. {
  4586. $object->{$this_model . '_id'} = NULL;
  4587. $object->save();
  4588. }
  4589. else
  4590. {
  4591. $data = array($this_model . '_id' => $this->id, $other_model . '_id' => $object->id);
  4592. // Delete relation
  4593. $this->db->delete($relationship_table, $data);
  4594. // Delete reverse direction if a reciprocal self relationship
  4595. if ($related_properties['reciprocal'])
  4596. {
  4597. $data = array($this_model . '_id' => $object->id, $other_model . '_id' => $this->id);
  4598. $this->db->delete($relationship_table, $data);
  4599. }
  4600. }
  4601. // Clear related object so it is refreshed on next access
  4602. unset($this->{$related_field});
  4603. return TRUE;
  4604. }
  4605. return FALSE;
  4606. }
  4607. // --------------------------------------------------------------------
  4608. /**
  4609. * Get Relationship Table
  4610. *
  4611. * Determines the relationship table between this object and $object.
  4612. *
  4613. * @ignore
  4614. * @param DataMapper $object Object that we are interested in.
  4615. * @param string $related_field Optional specific related field.
  4616. * @return string The name of the table this relationship is stored on.
  4617. */
  4618. public function _get_relationship_table($object, $related_field = '')
  4619. {
  4620. $prefix = $object->prefix;
  4621. $table = $object->table;
  4622. if (empty($related_field))
  4623. {
  4624. $related_field = $object->model;
  4625. }
  4626. $related_properties = $this->_get_related_properties($related_field);
  4627. $this_model = $related_properties['join_self_as'];
  4628. $other_model = $related_properties['join_other_as'];
  4629. $other_field = $related_properties['other_field'];
  4630. if (isset($this->has_one[$related_field]))
  4631. {
  4632. // see if the relationship is in this table
  4633. if (in_array($other_model . '_id', $this->fields))
  4634. {
  4635. return $this->table;
  4636. }
  4637. }
  4638. if (isset($object->has_one[$other_field]))
  4639. {
  4640. // see if the relationship is in this table
  4641. if (in_array($this_model . '_id', $object->fields))
  4642. {
  4643. return $object->table;
  4644. }
  4645. }
  4646. // was a join table defined for this relation?
  4647. if ( ! empty($related_properties['join_table']) )
  4648. {
  4649. $relationship_table = $related_properties['join_table'];
  4650. }
  4651. else
  4652. {
  4653. $relationship_table = '';
  4654. // Check if self referencing
  4655. if ($this->table == $table)
  4656. {
  4657. // use the model names from related_properties
  4658. $p_this_model = plural($this_model);
  4659. $p_other_model = plural($other_model);
  4660. $relationship_table = ($p_this_model < $p_other_model) ? $p_this_model . '_' . $p_other_model : $p_other_model . '_' . $p_this_model;
  4661. }
  4662. else
  4663. {
  4664. $relationship_table = ($this->table < $table) ? $this->table . '_' . $table : $table . '_' . $this->table;
  4665. }
  4666. // Remove all occurances of the prefix from the relationship table
  4667. $relationship_table = str_replace($prefix, '', str_replace($this->prefix, '', $relationship_table));
  4668. // So we can prefix the beginning, using the join prefix instead, if it is set
  4669. $relationship_table = (empty($this->join_prefix)) ? $this->prefix . $relationship_table : $this->join_prefix . $relationship_table;
  4670. }
  4671. return $relationship_table;
  4672. }
  4673. // --------------------------------------------------------------------
  4674. /**
  4675. * Count Related
  4676. *
  4677. * Returns the number of related items in the database and in the related object.
  4678. * Used by the _related_(required|min|max) validation rules.
  4679. *
  4680. * @ignore
  4681. * @param string $related_field The related field.
  4682. * @param mixed $object Object or array to include in the count.
  4683. * @return int Number of related items.
  4684. */
  4685. protected function _count_related($related_field, $object = '')
  4686. {
  4687. $count = 0;
  4688. // lookup relationship info
  4689. // the TRUE allows conversion to singular
  4690. $rel_properties = $this->_get_related_properties($related_field, TRUE);
  4691. $class = $rel_properties['class'];
  4692. $ids = array();
  4693. if ( ! empty($object))
  4694. {
  4695. $count = $this->_count_related_objects($related_field, $object, '', $ids);
  4696. $ids = array_unique($ids);
  4697. }
  4698. if ( ! empty($related_field) && ! empty($this->id))
  4699. {
  4700. $one = isset($this->has_one[$related_field]);
  4701. // don't bother looking up relationships if this is a $has_one and we already have one.
  4702. if( (!$one) || empty($ids))
  4703. {
  4704. // Prepare model
  4705. $object = new $class();
  4706. // Store parent data
  4707. $object->parent = array('model' => $rel_properties['other_field'], 'id' => $this->id);
  4708. // pass in IDs to exclude from the count
  4709. $count += $object->count($ids);
  4710. }
  4711. }
  4712. return $count;
  4713. }
  4714. // --------------------------------------------------------------------
  4715. /**
  4716. * Private recursive function to count the number of objects
  4717. * in a passed in array (or a single object)
  4718. *
  4719. * @ignore
  4720. * @param string $compare related field (model) to compare to
  4721. * @param mixed $object Object or array to count
  4722. * @param string $related_field related field of $object
  4723. * @param array $ids list of IDs we've already found.
  4724. * @return int Number of items found.
  4725. */
  4726. protected function _count_related_objects($compare, $object, $related_field, &$ids)
  4727. {
  4728. $count = 0;
  4729. if (is_array($object))
  4730. {
  4731. // loop through array to check for objects
  4732. foreach ($object as $rel_field => $obj)
  4733. {
  4734. if ( ! is_string($rel_field))
  4735. {
  4736. // if this object doesn't have a related field, use the parent related field
  4737. $rel_field = $related_field;
  4738. }
  4739. $count += $this->_count_related_objects($compare, $obj, $rel_field, $ids);
  4740. }
  4741. }
  4742. else
  4743. {
  4744. // if this object doesn't have a related field, use the model
  4745. if (empty($related_field))
  4746. {
  4747. $related_field = $object->model;
  4748. }
  4749. // if this object is the same relationship type, it counts
  4750. if ($related_field == $compare && $object->exists())
  4751. {
  4752. $ids[] = $object->id;
  4753. $count++;
  4754. }
  4755. }
  4756. return $count;
  4757. }
  4758. // --------------------------------------------------------------------
  4759. /**
  4760. * Include Join Fields
  4761. *
  4762. * If TRUE, the any extra fields on the join table will be included
  4763. *
  4764. * @param bool $include If FALSE, turns back off the directive.
  4765. * @return DataMapper Returns self for method chaining.
  4766. */
  4767. public function include_join_fields($include = TRUE)
  4768. {
  4769. $this->_include_join_fields = $include;
  4770. return $this;
  4771. }
  4772. // --------------------------------------------------------------------
  4773. /**
  4774. * Set Join Field
  4775. *
  4776. * Sets the value on a join table based on the related field
  4777. * If $related_field is an array, then the array should be
  4778. * in the form $related_field => $object or array($object)
  4779. *
  4780. * @param mixed $related_field An object or array.
  4781. * @param mixed $field Field or array of fields to set.
  4782. * @param mixed $value Value for a single field to set.
  4783. * @param mixed $object Private for recursion, do not use.
  4784. * @return DataMapper Returns self for method chaining.
  4785. */
  4786. public function set_join_field($related_field, $field, $value = NULL, $object = NULL)
  4787. {
  4788. $related_ids = array();
  4789. if (is_array($related_field))
  4790. {
  4791. // recursively call this on the array passed in.
  4792. foreach ($related_field as $key => $object)
  4793. {
  4794. $this->set_join_field($key, $field, $value, $object);
  4795. }
  4796. return;
  4797. }
  4798. else if (is_object($related_field))
  4799. {
  4800. $object = $related_field;
  4801. $related_field = $object->model;
  4802. $related_ids[] = $object->id;
  4803. $related_properties = $this->_get_related_properties($related_field);
  4804. }
  4805. else
  4806. {
  4807. // the TRUE allows conversion to singular
  4808. $related_properties = $this->_get_related_properties($related_field, TRUE);
  4809. if (is_null($object))
  4810. {
  4811. $class = $related_properties['class'];
  4812. $object = new $class();
  4813. }
  4814. }
  4815. // Determine relationship table name
  4816. $relationship_table = $this->_get_relationship_table($object, $related_field);
  4817. if (empty($object))
  4818. {
  4819. // no object was passed in, so create one
  4820. $class = $related_properties['class'];
  4821. $object = new $class();
  4822. }
  4823. $this_model = $related_properties['join_self_as'];
  4824. $other_model = $related_properties['join_other_as'];
  4825. if (! is_array($field))
  4826. {
  4827. $field = array( $field => $value );
  4828. }
  4829. if ( ! is_array($object))
  4830. {
  4831. $object = array($object);
  4832. }
  4833. if (empty($object))
  4834. {
  4835. $this->db->where($this_model . '_id', $this->id);
  4836. $this->db->update($relationship_table, $field);
  4837. }
  4838. else
  4839. {
  4840. foreach ($object as $obj)
  4841. {
  4842. $this->db->where($this_model . '_id', $this->id);
  4843. $this->db->where($other_model . '_id', $obj->id);
  4844. $this->db->update($relationship_table, $field);
  4845. }
  4846. }
  4847. // For method chaining
  4848. return $this;
  4849. }
  4850. // --------------------------------------------------------------------
  4851. /**
  4852. * Join Field
  4853. *
  4854. * Adds a query of a join table's extra field
  4855. * Accessed via __call
  4856. *
  4857. * @ignore
  4858. * @param string $query Query method.
  4859. * @param array $arguments Arguments for query.
  4860. * @return DataMapper Returns self for method chaining.
  4861. */
  4862. protected function _join_field($query, $arguments)
  4863. {
  4864. if ( ! empty($query) && count($arguments) >= 3)
  4865. {
  4866. $object = $field = $value = NULL;
  4867. // Prepare model
  4868. if (is_object($arguments[0]))
  4869. {
  4870. $object = $arguments[0];
  4871. $class = get_class($object);
  4872. $related_field = $object->model;
  4873. }
  4874. else
  4875. {
  4876. $related_field = $arguments[0];
  4877. // the TRUE allows conversion to singular
  4878. $related_properties = $this->_get_related_properties($related_field, TRUE);
  4879. $class = $related_properties['class'];
  4880. $object = new $class();
  4881. }
  4882. // Prepare field and value
  4883. $field = $arguments[1];
  4884. $value = $arguments[2];
  4885. // Determine relationship table name, and join the tables
  4886. $rel_table = $this->_get_relationship_table($object, $related_field);
  4887. // only add $related_field to the table name if the 'class' and 'related_field' aren't equal
  4888. // and the related object is in a different table
  4889. if ( ($class != $related_field) or ($this->table == $object->table) )
  4890. {
  4891. $rel_table = str_replace('.', '_', $related_field . '_' . $rel_table);
  4892. }
  4893. // Add query clause
  4894. $extra = NULL;
  4895. if(count($arguments) > 3) {
  4896. $extra = $arguments[3];
  4897. }
  4898. if(is_null($extra)) {
  4899. $this->{$query}($rel_table . '.' . $field, $value);
  4900. } else {
  4901. $this->{$query}($rel_table . '.' . $field, $value, $extra);
  4902. }
  4903. }
  4904. // For method chaining
  4905. return $this;
  4906. }
  4907. // --------------------------------------------------------------------
  4908. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  4909. * *
  4910. * Related Validation methods *
  4911. * *
  4912. * The following are methods used to validate the *
  4913. * relationships of this object. *
  4914. * *
  4915. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
  4916. // --------------------------------------------------------------------
  4917. /**
  4918. * Related Required (pre-process)
  4919. *
  4920. * Checks if the related object has the required related item
  4921. * or if the required relation already exists.
  4922. *
  4923. * @ignore
  4924. */
  4925. protected function _related_required($object, $model)
  4926. {
  4927. return ($this->_count_related($model, $object) == 0) ? FALSE : TRUE;
  4928. }
  4929. // --------------------------------------------------------------------
  4930. /**
  4931. * Related Min Size (pre-process)
  4932. *
  4933. * Checks if the value of a property is at most the minimum size.
  4934. *
  4935. * @ignore
  4936. */
  4937. protected function _related_min_size($object, $model, $size = 0)
  4938. {
  4939. return ($this->_count_related($model, $object) < $size) ? FALSE : TRUE;
  4940. }
  4941. // --------------------------------------------------------------------
  4942. /**
  4943. * Related Max Size (pre-process)
  4944. *
  4945. * Checks if the value of a property is at most the maximum size.
  4946. *
  4947. * @ignore
  4948. */
  4949. protected function _related_max_size($object, $model, $size = 0)
  4950. {
  4951. return ($this->_count_related($model, $object) > $size) ? FALSE : TRUE;
  4952. }
  4953. // --------------------------------------------------------------------
  4954. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  4955. * *
  4956. * Validation methods *
  4957. * *
  4958. * The following are methods used to validate the *
  4959. * values of this objects properties. *
  4960. * *
  4961. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
  4962. // --------------------------------------------------------------------
  4963. /**
  4964. * Always Validate
  4965. *
  4966. * Does nothing, but forces a validation even if empty (for non-required fields)
  4967. *
  4968. * @ignore
  4969. */
  4970. protected function _always_validate()
  4971. {
  4972. }
  4973. // --------------------------------------------------------------------
  4974. /**
  4975. * Alpha Dash Dot (pre-process)
  4976. *
  4977. * Alpha-numeric with underscores, dashes and full stops.
  4978. *
  4979. * @ignore
  4980. */
  4981. protected function _alpha_dash_dot($field)
  4982. {
  4983. return ( ! preg_match('/^([\.-a-z0-9_-])+$/i', $this->{$field})) ? FALSE : TRUE;
  4984. }
  4985. // --------------------------------------------------------------------
  4986. /**
  4987. * Alpha Slash Dot (pre-process)
  4988. *
  4989. * Alpha-numeric with underscores, dashes, forward slashes and full stops.
  4990. *
  4991. * @ignore
  4992. */
  4993. protected function _alpha_slash_dot($field)
  4994. {
  4995. return ( ! preg_match('/^([\.\/-a-z0-9_-])+$/i', $this->{$field})) ? FALSE : TRUE;
  4996. }
  4997. // --------------------------------------------------------------------
  4998. /**
  4999. * Matches (pre-process)
  5000. *
  5001. * Match one field to another.
  5002. * This replaces the version in CI_Form_validation.
  5003. *
  5004. * @ignore
  5005. */
  5006. protected function _matches($field, $other_field)
  5007. {
  5008. return ($this->{$field} !== $this->{$other_field}) ? FALSE : TRUE;
  5009. }
  5010. // --------------------------------------------------------------------
  5011. /**
  5012. * Min Date (pre-process)
  5013. *
  5014. * Checks if the value of a property is at least the minimum date.
  5015. *
  5016. * @ignore
  5017. */
  5018. protected function _min_date($field, $date)
  5019. {
  5020. return (strtotime($this->{$field}) < strtotime($date)) ? FALSE : TRUE;
  5021. }
  5022. // --------------------------------------------------------------------
  5023. /**
  5024. * Max Date (pre-process)
  5025. *
  5026. * Checks if the value of a property is at most the maximum date.
  5027. *
  5028. * @ignore
  5029. */
  5030. protected function _max_date($field, $date)
  5031. {
  5032. return (strtotime($this->{$field}) > strtotime($date)) ? FALSE : TRUE;
  5033. }
  5034. // --------------------------------------------------------------------
  5035. /**
  5036. * Min Size (pre-process)
  5037. *
  5038. * Checks if the value of a property is at least the minimum size.
  5039. *
  5040. * @ignore
  5041. */
  5042. protected function _min_size($field, $size)
  5043. {
  5044. return ($this->{$field} < $size) ? FALSE : TRUE;
  5045. }
  5046. // --------------------------------------------------------------------
  5047. /**
  5048. * Max Size (pre-process)
  5049. *
  5050. * Checks if the value of a property is at most the maximum size.
  5051. *
  5052. * @ignore
  5053. */
  5054. protected function _max_size($field, $size)
  5055. {
  5056. return ($this->{$field} > $size) ? FALSE : TRUE;
  5057. }
  5058. // --------------------------------------------------------------------
  5059. /**
  5060. * Unique (pre-process)
  5061. *
  5062. * Checks if the value of a property is unique.
  5063. * If the property belongs to this object, we can ignore it.
  5064. *
  5065. * @ignore
  5066. */
  5067. protected function _unique($field)
  5068. {
  5069. if ( ! empty($this->{$field}))
  5070. {
  5071. $query = $this->db->get_where($this->table, array($field => $this->{$field}), 1, 0);
  5072. if ($query->num_rows() > 0)
  5073. {
  5074. $row = $query->row();
  5075. // If unique value does not belong to this object
  5076. if ($this->id != $row->id)
  5077. {
  5078. // Then it is not unique
  5079. return FALSE;
  5080. }
  5081. }
  5082. }
  5083. // No matches found so is unique
  5084. return TRUE;
  5085. }
  5086. // --------------------------------------------------------------------
  5087. /**
  5088. * Unique Pair (pre-process)
  5089. *
  5090. * Checks if the value of a property, paired with another, is unique.
  5091. * If the properties belongs to this object, we can ignore it.
  5092. *
  5093. * @ignore
  5094. */
  5095. protected function _unique_pair($field, $other_field = '')
  5096. {
  5097. if ( ! empty($this->{$field}) && ! empty($this->{$other_field}))
  5098. {
  5099. $query = $this->db->get_where($this->table, array($field => $this->{$field}, $other_field => $this->{$other_field}), 1, 0);
  5100. if ($query->num_rows() > 0)
  5101. {
  5102. $row = $query->row();
  5103. // If unique pair value does not belong to this object
  5104. if ($this->id != $row->id)
  5105. {
  5106. // Then it is not a unique pair
  5107. return FALSE;
  5108. }
  5109. }
  5110. }
  5111. // No matches found so is unique
  5112. return TRUE;
  5113. }
  5114. // --------------------------------------------------------------------
  5115. /**
  5116. * Valid Date (pre-process)
  5117. *
  5118. * Checks whether the field value is a valid DateTime.
  5119. *
  5120. * @ignore
  5121. */
  5122. protected function _valid_date($field)
  5123. {
  5124. // Ignore if empty
  5125. if (empty($this->{$field}))
  5126. {
  5127. return TRUE;
  5128. }
  5129. $date = date_parse($this->{$field});
  5130. return checkdate($date['month'], $date['day'],$date['year']);
  5131. }
  5132. // --------------------------------------------------------------------
  5133. /**
  5134. * Valid Date Group (pre-process)
  5135. *
  5136. * Checks whether the field value, grouped with other field values, is a valid DateTime.
  5137. *
  5138. * @ignore
  5139. */
  5140. protected function _valid_date_group($field, $fields = array())
  5141. {
  5142. // Ignore if empty
  5143. if (empty($this->{$field}))
  5144. {
  5145. return TRUE;
  5146. }
  5147. $date = date_parse($this->{$fields['year']} . '-' . $this->{$fields['month']} . '-' . $this->{$fields['day']});
  5148. return checkdate($date['month'], $date['day'],$date['year']);
  5149. }
  5150. // --------------------------------------------------------------------
  5151. /**
  5152. * Valid Match (pre-process)
  5153. *
  5154. * Checks whether the field value matches one of the specified array values.
  5155. *
  5156. * @ignore
  5157. */
  5158. protected function _valid_match($field, $param = array())
  5159. {
  5160. return in_array($this->{$field}, $param);
  5161. }
  5162. // --------------------------------------------------------------------
  5163. /**
  5164. * Boolean (pre-process)
  5165. *
  5166. * Forces a field to be either TRUE or FALSE.
  5167. * Uses PHP's built-in boolean conversion.
  5168. *
  5169. * @ignore
  5170. */
  5171. protected function _boolean($field)
  5172. {
  5173. $this->{$field} = (boolean)$this->{$field};
  5174. }
  5175. // --------------------------------------------------------------------
  5176. /**
  5177. * Encode PHP Tags (prep)
  5178. *
  5179. * Convert PHP tags to entities.
  5180. * This replaces the version in CI_Form_validation.
  5181. *
  5182. * @ignore
  5183. */
  5184. protected function _encode_php_tags($field)
  5185. {
  5186. $this->{$field} = encode_php_tags($this->{$field});
  5187. }
  5188. // --------------------------------------------------------------------
  5189. /**
  5190. * Prep for Form (prep)
  5191. *
  5192. * Converts special characters to allow HTML to be safely shown in a form.
  5193. * This replaces the version in CI_Form_validation.
  5194. *
  5195. * @ignore
  5196. */
  5197. protected function _prep_for_form($field)
  5198. {
  5199. $this->{$field} = $this->form_validation->prep_for_form($this->{$field});
  5200. }
  5201. // --------------------------------------------------------------------
  5202. /**
  5203. * Prep URL (prep)
  5204. *
  5205. * Adds "http://" to URLs if missing.
  5206. * This replaces the version in CI_Form_validation.
  5207. *
  5208. * @ignore
  5209. */
  5210. protected function _prep_url($field)
  5211. {
  5212. $this->{$field} = $this->form_validation->prep_url($this->{$field});
  5213. }
  5214. // --------------------------------------------------------------------
  5215. /**
  5216. * Strip Image Tags (prep)
  5217. *
  5218. * Strips the HTML from image tags leaving the raw URL.
  5219. * This replaces the version in CI_Form_validation.
  5220. *
  5221. * @ignore
  5222. */
  5223. protected function _strip_image_tags($field)
  5224. {
  5225. $this->{$field} = strip_image_tags($this->{$field});
  5226. }
  5227. // --------------------------------------------------------------------
  5228. /**
  5229. * XSS Clean (prep)
  5230. *
  5231. * Runs the data through the XSS filtering function, described in the Input Class page.
  5232. * This replaces the version in CI_Form_validation.
  5233. *
  5234. * @ignore
  5235. */
  5236. protected function _xss_clean($field, $is_image = FALSE)
  5237. {
  5238. $this->{$field} = xss_clean($this->{$field}, $is_image);
  5239. }
  5240. // --------------------------------------------------------------------
  5241. /**
  5242. * Trim
  5243. * Custom trim rule that ignores NULL values
  5244. *
  5245. * @ignore
  5246. */
  5247. protected function _trim($field) {
  5248. if( ! empty($this->{$field})) {
  5249. $this->{$field} = trim($this->{$field});
  5250. }
  5251. }
  5252. // --------------------------------------------------------------------
  5253. /**
  5254. * Serialize
  5255. * Custom serialize rule that deals with non-arrays
  5256. *
  5257. * @ignore
  5258. */
  5259. protected function _serialize($field) {
  5260. $this->{$field} = is_array($this->{$field}) ? serialize($this->{$field}) : array($this->{$field});
  5261. }
  5262. // --------------------------------------------------------------------
  5263. /**
  5264. * Unserialize
  5265. * Custom unserialize rule that deals with empty strings
  5266. *
  5267. * @ignore
  5268. */
  5269. protected function _unserialize($field) {
  5270. if (empty($this->{$field}))
  5271. {
  5272. $this->{$field} = array();
  5273. }
  5274. elseif(is_string($this->{$field}))
  5275. {
  5276. $this->{$field} = unserialize($this->{$field});
  5277. }
  5278. }
  5279. // --------------------------------------------------------------------
  5280. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  5281. * *
  5282. * Common methods *
  5283. * *
  5284. * The following are common methods used by other methods. *
  5285. * *
  5286. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
  5287. // --------------------------------------------------------------------
  5288. /**
  5289. * A specialized language lookup function that will automatically
  5290. * insert the model, table, and (optional) field into a key, and return the
  5291. * language result for the replaced key.
  5292. *
  5293. * @param string $key Basic key to use
  5294. * @param string $field Optional field value
  5295. * @return string|bool
  5296. */
  5297. public function localize_by_model($key, $field = NULL)
  5298. {
  5299. $s = array('${model}', '${table}');
  5300. $r = array($this->model, $this->table);
  5301. if(!is_null($field))
  5302. {
  5303. $s[] = '${field}';
  5304. $r[] = $field;
  5305. }
  5306. $key = str_replace($s, $r, $key);
  5307. return $this->ci_lang->dm_line($key);
  5308. }
  5309. // --------------------------------------------------------------------
  5310. /**
  5311. * Variant that handles looking up a field labels
  5312. * @param string $field Name of field
  5313. * @param string|bool $label If not FALSE overrides default label.
  5314. * @return string|bool
  5315. */
  5316. public function localize_label($field, $label = FALSE)
  5317. {
  5318. if($label === FALSE)
  5319. {
  5320. $label = $field;
  5321. if(!empty($this->field_label_lang_format))
  5322. {
  5323. $label = $this->localize_by_model($this->field_label_lang_format, $field);
  5324. if($label === FALSE)
  5325. {
  5326. $label = $field;
  5327. }
  5328. }
  5329. }
  5330. else if(strpos($label, 'lang:') === 0)
  5331. {
  5332. $label = $this->localize_by_model(substr($label, 5), $field);
  5333. }
  5334. return $label;
  5335. }
  5336. // --------------------------------------------------------------------
  5337. /**
  5338. * Allows you to define has_one relations at runtime
  5339. * @param string name of the model to make a relation with
  5340. * @param array optional, array with advanced relationship definitions
  5341. * @return bool
  5342. */
  5343. public function has_one( $parm1 = NULL, $parm2 = NULL )
  5344. {
  5345. if ( is_null($parm1) && is_null($parm2) )
  5346. {
  5347. return FALSE;
  5348. }
  5349. elseif ( is_array($parm2) )
  5350. {
  5351. return $this->_relationship('has_one', $parm2, $parm1);
  5352. }
  5353. else
  5354. {
  5355. return $this->_relationship('has_one', $parm1, 0);
  5356. }
  5357. }
  5358. // --------------------------------------------------------------------
  5359. /**
  5360. * Allows you to define has_many relations at runtime
  5361. * @param string name of the model to make a relation with
  5362. * @param array optional, array with advanced relationship definitions
  5363. * @return bool
  5364. */
  5365. public function has_many( $parm1 = NULL, $parm2 = NULL )
  5366. {
  5367. if ( is_null($parm1) && is_null($parm2) )
  5368. {
  5369. return FALSE;
  5370. }
  5371. elseif ( is_array($parm2) )
  5372. {
  5373. return $this->_relationship('has_many', $parm2, $parm1);
  5374. }
  5375. else
  5376. {
  5377. return $this->_relationship('has_many', $parm1, 0);
  5378. }
  5379. }
  5380. // --------------------------------------------------------------------
  5381. /**
  5382. * Creates or updates the production schema cache file for this model
  5383. * @param void
  5384. * @return void
  5385. */
  5386. public function production_cache()
  5387. {
  5388. // if requested, store the item to the production cache
  5389. if( ! empty(DataMapper::$config['production_cache']))
  5390. {
  5391. // check if it's a fully qualified path first
  5392. if (!is_dir($cache_folder = DataMapper::$config['production_cache']))
  5393. {
  5394. // if not, it's relative to the application path
  5395. $cache_folder = APPPATH . DataMapper::$config['production_cache'];
  5396. }
  5397. if(file_exists($cache_folder) && is_dir($cache_folder) && is_writeable($cache_folder))
  5398. {
  5399. $common_key = DataMapper::$common[DMZ_CLASSNAMES_KEY][strtolower(get_class($this))];
  5400. $cache_file = $cache_folder . '/' . $common_key . '.php';
  5401. $cache = "<"."?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); \n";
  5402. $cache .= '$cache = ' . var_export(DataMapper::$common[$common_key], TRUE) . ';';
  5403. if ( ! $fp = @fopen($cache_file, 'w'))
  5404. {
  5405. show_error('Error creating production cache file: ' . $cache_file);
  5406. }
  5407. flock($fp, LOCK_EX);
  5408. fwrite($fp, $cache);
  5409. flock($fp, LOCK_UN);
  5410. fclose($fp);
  5411. @chmod($cache_file, FILE_WRITE_MODE);
  5412. }
  5413. }
  5414. }
  5415. // --------------------------------------------------------------------
  5416. /**
  5417. * Define a new relationship for the current model
  5418. */
  5419. protected function _relationship($type = '', $definition = array(), $name = 0)
  5420. {
  5421. // check the parameters
  5422. if (empty($type) OR ! in_array($type, array('has_one','has_many')))
  5423. {
  5424. return FALSE;
  5425. }
  5426. // allow for simple (old-style) associations
  5427. if (is_int($name))
  5428. {
  5429. // delete the old style entry, we're going to convert it
  5430. if (isset($this->{$type}[$name]))
  5431. {
  5432. unset($this->{$type}[$name]);
  5433. }
  5434. $name = $definition;
  5435. }
  5436. // get the current relationships
  5437. $new = (array) $this->{$type};
  5438. // convert value into array if necessary
  5439. if ( ! is_array($definition))
  5440. {
  5441. $definition = array('class' => $definition);
  5442. }
  5443. else if ( ! isset($definition['class']))
  5444. {
  5445. // if already an array, ensure that the class attribute is set
  5446. $definition['class'] = $name;
  5447. }
  5448. if( ! isset($definition['other_field']))
  5449. {
  5450. // add this model as the model to use in queries if not set
  5451. $definition['other_field'] = $this->model;
  5452. }
  5453. if( ! isset($definition['join_self_as']))
  5454. {
  5455. // add this model as the model to use in queries if not set
  5456. $definition['join_self_as'] = $definition['other_field'];
  5457. }
  5458. if( ! isset($definition['join_other_as']))
  5459. {
  5460. // add the key as the model to use in queries if not set
  5461. $definition['join_other_as'] = $name;
  5462. }
  5463. if( ! isset($definition['join_table']))
  5464. {
  5465. // by default, automagically determine the join table name
  5466. $definition['join_table'] = '';
  5467. }
  5468. if(isset($definition['reciprocal']))
  5469. {
  5470. // only allow a reciprocal relationship to be defined if this is a has_many self relationship
  5471. $definition['reciprocal'] = ($definition['reciprocal'] && $type == 'has_many' && $definition['class'] == strtolower(get_class($this)));
  5472. }
  5473. else
  5474. {
  5475. $definition['reciprocal'] = FALSE;
  5476. }
  5477. if(!isset($definition['auto_populate']) OR ! is_bool($definition['auto_populate']))
  5478. {
  5479. $definition['auto_populate'] = NULL;
  5480. }
  5481. if(!isset($definition['cascade_delete']) OR ! is_bool($definition['cascade_delete']))
  5482. {
  5483. $definition['cascade_delete'] = $this->cascade_delete;
  5484. }
  5485. $new[$name] = $definition;
  5486. // load in labels for each not-already-set field
  5487. if(!isset($this->validation[$name]))
  5488. {
  5489. $label = $this->localize_label($name);
  5490. if(!empty($label))
  5491. {
  5492. // label is re-set below, to prevent caching language-based labels
  5493. $this->validation[$name] = array('field' => $name, 'rules' => array());
  5494. }
  5495. }
  5496. // replace the old array
  5497. $this->{$type} = $new;
  5498. }
  5499. // --------------------------------------------------------------------
  5500. /**
  5501. * To Array
  5502. *
  5503. * Converts this objects current record into an array for database queries.
  5504. * If validate is TRUE (getting by objects properties) empty objects are ignored.
  5505. *
  5506. * @ignore
  5507. * @param bool $validate
  5508. * @return array
  5509. */
  5510. protected function _to_array($validate = FALSE)
  5511. {
  5512. $data = array();
  5513. foreach ($this->fields as $field)
  5514. {
  5515. if ($validate && ! isset($this->{$field}))
  5516. {
  5517. continue;
  5518. }
  5519. $data[$field] = $this->{$field};
  5520. }
  5521. return $data;
  5522. }
  5523. // --------------------------------------------------------------------
  5524. /**
  5525. * Process Query
  5526. *
  5527. * Converts a query result into an array of objects.
  5528. * Also updates this object
  5529. *
  5530. * @ignore
  5531. * @param CI_DB_result $query
  5532. */
  5533. protected function _process_query($query)
  5534. {
  5535. if ($query->num_rows() > 0)
  5536. {
  5537. // Populate all with records as objects
  5538. $this->all = array();
  5539. $this->_to_object($this, $query->row());
  5540. // don't bother recreating the first item.
  5541. $index = ($this->all_array_uses_ids && isset($this->id)) ? $this->id : 0;
  5542. $this->all[$index] = $this->get_clone();
  5543. if($query->num_rows() > 1)
  5544. {
  5545. $model = get_class($this);
  5546. $first = TRUE;
  5547. foreach ($query->result() as $row)
  5548. {
  5549. if($first)
  5550. {
  5551. $first = FALSE;
  5552. continue;
  5553. }
  5554. $item = new $model();
  5555. $this->_to_object($item, $row);
  5556. if($this->all_array_uses_ids && isset($item->id))
  5557. {
  5558. $this->all[$item->id] = $item;
  5559. }
  5560. else
  5561. {
  5562. $this->all[] = $item;
  5563. }
  5564. }
  5565. }
  5566. // remove instantiations
  5567. $this->_instantiations = NULL;
  5568. // free large queries
  5569. if($query->num_rows() > $this->free_result_threshold)
  5570. {
  5571. $query->free_result();
  5572. }
  5573. }
  5574. else
  5575. {
  5576. // Refresh stored values is called by _to_object normally
  5577. $this->_refresh_stored_values();
  5578. }
  5579. }
  5580. // --------------------------------------------------------------------
  5581. /**
  5582. * To Object
  5583. * Copies the values from a query result row to an object.
  5584. * Also initializes that object by running get rules, and
  5585. * refreshing stored values on the object.
  5586. *
  5587. * Finally, if any "instantiations" are requested, those related objects
  5588. * are created off of query results
  5589. *
  5590. * This is only public so that the iterator can access it.
  5591. *
  5592. * @ignore
  5593. * @param DataMapper $item Item to configure
  5594. * @param object $row Query results
  5595. */
  5596. public function _to_object($item, $row)
  5597. {
  5598. // Populate this object with values from first record
  5599. foreach ($row as $key => $value)
  5600. {
  5601. $item->{$key} = $value;
  5602. }
  5603. foreach ($this->fields as $field)
  5604. {
  5605. if (! isset($row->{$field}))
  5606. {
  5607. $item->{$field} = NULL;
  5608. }
  5609. }
  5610. // Force IDs to integers
  5611. foreach($this->_field_tracking['intval'] as $field)
  5612. {
  5613. if(isset($item->{$field}))
  5614. {
  5615. $item->{$field} = intval($item->{$field});
  5616. }
  5617. }
  5618. if (!empty($this->_field_tracking['get_rules']))
  5619. {
  5620. $item->_run_get_rules();
  5621. }
  5622. $item->_refresh_stored_values();
  5623. if($this->_instantiations) {
  5624. foreach($this->_instantiations as $related_field => $field_map) {
  5625. // convert fields to a 'row' object
  5626. $row = new stdClass();
  5627. foreach($field_map as $item_field => $c_field) {
  5628. $row->{$c_field} = $item->{$item_field};
  5629. }
  5630. // get the related item
  5631. $c =& $item->_get_without_auto_populating($related_field);
  5632. // set the values
  5633. $c->_to_object($c, $row);
  5634. // also set up the ->all array
  5635. $c->all = array();
  5636. $c->all[0] = $c->get_clone();
  5637. }
  5638. }
  5639. }
  5640. // --------------------------------------------------------------------
  5641. /**
  5642. * Run Get Rules
  5643. *
  5644. * Processes values loaded from the database
  5645. *
  5646. * @ignore
  5647. */
  5648. protected function _run_get_rules()
  5649. {
  5650. // Loop through each property to be validated
  5651. foreach ($this->_field_tracking['get_rules'] as $field)
  5652. {
  5653. // Get validation settings
  5654. $rules = $this->validation[$field]['get_rules'];
  5655. // only process non-empty keys that are not specifically
  5656. // set to be null
  5657. if( ! isset($this->{$field}) && ! in_array('allow_null', $rules))
  5658. {
  5659. if(isset($this->has_one[$field]))
  5660. {
  5661. // automatically process $item_id values
  5662. $field = $field . '_id';
  5663. if( ! isset($this->{$field}) && ! in_array('allow_null', $rules))
  5664. {
  5665. continue;
  5666. }
  5667. } else {
  5668. continue;
  5669. }
  5670. }
  5671. // Loop through each rule to validate this property against
  5672. foreach ($rules as $rule => $param)
  5673. {
  5674. // Check for parameter
  5675. if (is_numeric($rule))
  5676. {
  5677. $rule = $param;
  5678. $param = '';
  5679. }
  5680. if($rule == 'allow_null')
  5681. {
  5682. continue;
  5683. }
  5684. if (method_exists($this, '_' . $rule))
  5685. {
  5686. // Run rule from DataMapper or the class extending DataMapper
  5687. $result = $this->{'_' . $rule}($field, $param);
  5688. }
  5689. else if($this->_extension_method_exists('rule_' . $rule))
  5690. {
  5691. // Run an extension-based rule.
  5692. $result = $this->{'rule_' . $rule}($field, $param);
  5693. }
  5694. else if (method_exists($this->form_validation, $rule))
  5695. {
  5696. // Run rule from CI Form Validation
  5697. $result = $this->form_validation->{$rule}($this->{$field}, $param);
  5698. }
  5699. else if (function_exists($rule))
  5700. {
  5701. // Run rule from PHP
  5702. $this->{$field} = $rule($this->{$field});
  5703. }
  5704. }
  5705. }
  5706. }
  5707. // --------------------------------------------------------------------
  5708. /**
  5709. * Refresh Stored Values
  5710. *
  5711. * Refreshes the stored values with the current values.
  5712. *
  5713. * @ignore
  5714. */
  5715. protected function _refresh_stored_values()
  5716. {
  5717. // Update stored values
  5718. foreach ($this->fields as $field)
  5719. {
  5720. $this->stored->{$field} = $this->{$field};
  5721. }
  5722. // If there is a "matches" validation rule, match the field value with the other field value
  5723. foreach ($this->_field_tracking['matches'] as $field_name => $match_name)
  5724. {
  5725. $this->{$field_name} = $this->stored->{$field_name} = $this->{$match_name};
  5726. }
  5727. }
  5728. // --------------------------------------------------------------------
  5729. /**
  5730. * Assign Libraries
  5731. *
  5732. * Originally used by CodeIgniter, now just logs a warning.
  5733. *
  5734. * @ignore
  5735. */
  5736. public function _assign_libraries()
  5737. {
  5738. log_message('debug', "Warning: A DMZ model ({$this->model}) was either loaded via autoload, or manually. DMZ automatically loads models, so this is unnecessary.");
  5739. }
  5740. // --------------------------------------------------------------------
  5741. /**
  5742. * Assign Libraries
  5743. *
  5744. * Assigns required CodeIgniter libraries to DataMapper.
  5745. *
  5746. * @ignore
  5747. */
  5748. protected function _dmz_assign_libraries()
  5749. {
  5750. static $CI;
  5751. if ($CI || $CI =& get_instance())
  5752. {
  5753. // make sure these exists to not trip __get()
  5754. $this->ci_load = NULL;
  5755. $this->ci_config = NULL;
  5756. $this->ci_lang = NULL;
  5757. // access to the loader
  5758. $this->ci_load =& $CI->load;
  5759. // to the config
  5760. $this->ci_config =& $CI->config;
  5761. // and the language class
  5762. $this->ci_lang =& $CI->lang;
  5763. }
  5764. }
  5765. // --------------------------------------------------------------------
  5766. /**
  5767. * Load Languages
  5768. *
  5769. * Loads required language files.
  5770. *
  5771. * @ignore
  5772. */
  5773. protected function _load_languages()
  5774. {
  5775. // Load the DataMapper language file
  5776. $this->ci_lang->load('datamapper');
  5777. }
  5778. // --------------------------------------------------------------------
  5779. /**
  5780. * Load Helpers
  5781. *
  5782. * Loads required CodeIgniter helpers.
  5783. *
  5784. * @ignore
  5785. */
  5786. protected function _load_helpers()
  5787. {
  5788. // Load inflector helper for singular and plural functions
  5789. $this->ci_load->helper('inflector');
  5790. // Load security helper for prepping functions
  5791. $this->ci_load->helper('security');
  5792. }
  5793. }
  5794. /**
  5795. * Simple class to prevent errors with unset fields.
  5796. * @package DMZ
  5797. *
  5798. * @param string $FIELD Get the error message for a given field or custom error
  5799. * @param string $RELATED Get the error message for a given relationship
  5800. * @param string $transaction Get the transaction error.
  5801. */
  5802. class DM_Error_Object {
  5803. /**
  5804. * Array of all error messages.
  5805. * @var array
  5806. */
  5807. public $all = array();
  5808. /**
  5809. * String containing entire error message.
  5810. * @var string
  5811. */
  5812. public $string = '';
  5813. /**
  5814. * All unset fields are returned as empty strings by default.
  5815. * @ignore
  5816. * @param string $field
  5817. * @return string Empty string
  5818. */
  5819. public function __get($field) {
  5820. return '';
  5821. }
  5822. }
  5823. /**
  5824. * Iterator for get_iterated
  5825. *
  5826. * @package DMZ
  5827. */
  5828. class DM_DatasetIterator implements Iterator, Countable
  5829. {
  5830. /**
  5831. * The parent DataMapper object that contains important info.
  5832. * @var DataMapper
  5833. */
  5834. protected $parent;
  5835. /**
  5836. * The temporary DM object used in the loops.
  5837. * @var DataMapper
  5838. */
  5839. protected $object;
  5840. /**
  5841. * Results array
  5842. * @var array
  5843. */
  5844. protected $result;
  5845. /**
  5846. * Number of results
  5847. * @var int
  5848. */
  5849. protected $count;
  5850. /**
  5851. * Current position
  5852. * @var int
  5853. */
  5854. protected $pos;
  5855. /**
  5856. * @param DataMapper $object Should be cloned ahead of time
  5857. * @param DB_result $query result from a CI DB query
  5858. */
  5859. function __construct($object, $query)
  5860. {
  5861. // store the object as a main object
  5862. $this->parent = $object;
  5863. // clone the parent object, so it can be manipulated safely.
  5864. $this->object = $object->get_clone();
  5865. // Now get the information on the current query object
  5866. $this->result = $query->result();
  5867. $this->count = count($this->result);
  5868. $this->pos = 0;
  5869. }
  5870. /**
  5871. * Gets the item at the current index $pos
  5872. * @return DataMapper
  5873. */
  5874. function current()
  5875. {
  5876. return $this->get($this->pos);
  5877. }
  5878. function key()
  5879. {
  5880. return $this->pos;
  5881. }
  5882. /**
  5883. * Gets the item at index $index
  5884. * @param int $index
  5885. * @return DataMapper
  5886. */
  5887. function get($index) {
  5888. // clear to ensure that the item is not duplicating data
  5889. $this->object->clear();
  5890. // set the current values on the object
  5891. $this->parent->_to_object($this->object, $this->result[$index]);
  5892. return $this->object;
  5893. }
  5894. function next()
  5895. {
  5896. $this->pos++;
  5897. }
  5898. function rewind()
  5899. {
  5900. $this->pos = 0;
  5901. }
  5902. function valid()
  5903. {
  5904. return ($this->pos < $this->count);
  5905. }
  5906. /**
  5907. * Returns the number of results
  5908. * @return int
  5909. */
  5910. function count()
  5911. {
  5912. return $this->count;
  5913. }
  5914. // Alias for count();
  5915. function result_count() {
  5916. return $this->count;
  5917. }
  5918. }
  5919. // --------------------------------------------------------------------------
  5920. /**
  5921. * Autoload
  5922. *
  5923. * Autoloads object classes that are used with DataMapper.
  5924. * Must be at end due to implements IteratorAggregate...
  5925. */
  5926. spl_autoload_register('DataMapper::autoload');
  5927. /* End of file datamapper.php */
  5928. /* Location: ./application/models/datamapper.php */