PageRenderTime 85ms CodeModel.GetById 28ms RepoModel.GetById 0ms app.codeStats 1ms

/application/libraries/datamapper.php

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