PageRenderTime 120ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 1ms

/application/libraries/Datamapper.php

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