PageRenderTime 59ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 1ms

/application/libraries/datamapper.php

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