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

/application/libraries/datamapper.php

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