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

/application/libraries/Datamapper.php

https://github.com/th3hamburgler/Simple-Site
PHP | 6285 lines | 3068 code | 754 blank | 2463 comment | 535 complexity | 6355bf2589044f70b34e68a0c2f1c1bf MD5 | raw file

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /**
  3. * Data Mapper Class, OverZealous Edition
  4. *
  5. * Transforms database tables into objects.
  6. *
  7. * @license MIT License
  8. * @package DMZ
  9. * @category DMZ
  10. * @author Phil DeJarnett
  11. * @link http://www.overzealous.com/dmz/
  12. * @version 1.7.1 ($Rev: 395 $)
  13. */
  14. /**
  15. * Key for storing pre-converted classnames
  16. */
  17. define('DMZ_CLASSNAMES_KEY', '_dmz_classnames');
  18. /**
  19. * Data Mapper Class, OverZealous Edition
  20. *
  21. * Transforms database tables into objects.
  22. *
  23. * @package DMZ
  24. *
  25. * Properties (for code completion)
  26. * @property CI_DB_driver $db The CodeIgniter Database Library
  27. * @property CI_Loader $load The CodeIgnter Loader Library
  28. * @property CI_Language $lang The CodeIgniter Language Library
  29. * @property CI_Config $config The CodeIgniter Config Library
  30. * @property CI_Form_validation $form_validation The CodeIgniter Form Validation Library
  31. *
  32. *
  33. * Define some of the magic methods:
  34. *
  35. * Get By:
  36. * @method DataMapper get_by_id() get_by_id(int $value) Looks up an item by its ID.
  37. * @method DataMapper get_by_FIELD() get_by_FIELD(mixed $value) Looks up an item by a specific FIELD. Ex: get_by_name($user_name);
  38. * @method DataMapper get_by_related() get_by_related(mixed $related, string $field = NULL, string $value = NULL) Get results based on a related item.
  39. * @method DataMapper get_by_related_RELATEDFIELD() get_by_related_RELATEDFIELD(string $field = NULL, string $value = NULL) Get results based on a RELATEDFIELD. Ex: get_by_related_user('id', $userid);
  40. *
  41. * Save and Delete
  42. * @method DataMapper save_RELATEDFIELD() save_RELATEDFIELD(mixed $object) Saves relationship(s) using the specified RELATEDFIELD. Ex: save_user($user);
  43. * @method DataMapper delete_RELATEDFIELD() delete_RELATEDFIELD(mixed $object) Deletes relationship(s) using the specified RELATEDFIELD. Ex: delete_user($user);
  44. *
  45. * Related:
  46. * @method DataMapper where_related() where_related(mixed $related, string $field = NULL, string $value = NULL) Limits results based on a related field.
  47. * @method DataMapper or_where_related() or_where_related(mixed $related, string $field = NULL, string $value = NULL) Limits results based on a related field, via OR.
  48. * @method DataMapper where_in_related() where_in_related(mixed $related, string $field, array $values) Limits results by comparing a related field to a range of values.
  49. * @method DataMapper or_where_in_related() or_where_in_related(mixed $related, string $field, array $values) Limits results by comparing a related field to a range of values.
  50. * @method DataMapper where_not_in_related() where_not_in_related(mixed $related, string $field, array $values) Limits results by comparing a related field to a range of values.
  51. * @method DataMapper or_where_not_in_related() or_where_not_in_related(mixed $related, string $field, array $values) Limits results by comparing a related field to a range of values.
  52. * @method DataMapper like_related() like_related(mixed $related, string $field, string $value, string $match = 'both') Limits results by matching a related field to a value.
  53. * @method DataMapper or_like_related() like_related(mixed $related, string $field, string $value, string $match = 'both') Limits results by matching a related field to a value.
  54. * @method DataMapper not_like_related() like_related(mixed $related, string $field, string $value, string $match = 'both') Limits results by matching a related field to a value.
  55. * @method DataMapper or_not_like_related() like_related(mixed $related, string $field, string $value, string $match = 'both') Limits results by matching a related field to a value.
  56. * @method DataMapper ilike_related() like_related(mixed $related, string $field, string $value, string $match = 'both') Limits results by matching a related field to a value (case insensitive).
  57. * @method DataMapper or_ilike_related() like_related(mixed $related, string $field, string $value, string $match = 'both') Limits results by matching a related field to a value (case insensitive).
  58. * @method DataMapper not_ilike_related() like_related(mixed $related, string $field, string $value, string $match = 'both') Limits results by matching a related field to a value (case insensitive).
  59. * @method DataMapper or_not_ilike_related() like_related(mixed $related, string $field, string $value, string $match = 'both') Limits results by matching a related field to a value (case insensitive).
  60. * @method DataMapper group_by_related() group_by_related(mixed $related, string $field) Groups the query by a related field.
  61. * @method DataMapper having_related() having_related(mixed $related, string $field, string $value) Groups the querying using a HAVING clause.
  62. * @method DataMapper or_having_related() having_related(mixed $related, string $field, string $value) Groups the querying using a HAVING clause, via OR.
  63. * @method DataMapper order_by_related() order_by_related(mixed $related, string $field, string $direction) Orders the query based on a related field.
  64. *
  65. *
  66. * Join Fields:
  67. * @method DataMapper where_join_field() where_join_field(mixed $related, string $field = NULL, string $value = NULL) Limits results based on a join field.
  68. * @method DataMapper or_where_join_field() or_where_join_field(mixed $related, string $field = NULL, string $value = NULL) Limits results based on a join field, via OR.
  69. * @method DataMapper where_in_join_field() where_in_join_field(mixed $related, string $field, array $values) Limits results by comparing a join field to a range of values.
  70. * @method DataMapper or_where_in_join_field() or_where_in_join_field(mixed $related, string $field, array $values) Limits results by comparing a join field to a range of values.
  71. * @method DataMapper where_not_in_join_field() where_not_in_join_field(mixed $related, string $field, array $values) Limits results by comparing a join field to a range of values.
  72. * @method DataMapper or_where_not_in_join_field() or_where_not_in_join_field(mixed $related, string $field, array $values) Limits results by comparing a join field to a range of values.
  73. * @method DataMapper like_join_field() like_join_field(mixed $related, string $field, string $value, string $match = 'both') Limits results by matching a join field to a value.
  74. * @method DataMapper or_like_join_field() like_join_field(mixed $related, string $field, string $value, string $match = 'both') Limits results by matching a join field to a value.
  75. * @method DataMapper not_like_join_field() like_join_field(mixed $related, string $field, string $value, string $match = 'both') Limits results by matching a join field to a value.
  76. * @method DataMapper or_not_like_join_field() like_join_field(mixed $related, string $field, string $value, string $match = 'both') Limits results by matching a join field to a value.
  77. * @method DataMapper ilike_join_field() like_join_field(mixed $related, string $field, string $value, string $match = 'both') Limits results by matching a join field to a value (case insensitive).
  78. * @method DataMapper or_ilike_join_field() like_join_field(mixed $related, string $field, string $value, string $match = 'both') Limits results by matching a join field to a value (case insensitive).
  79. * @method DataMapper not_ilike_join_field() like_join_field(mixed $related, string $field, string $value, string $match = 'both') Limits results by matching a join field to a value (case insensitive).
  80. * @method DataMapper or_not_ilike_join_field() like_join_field(mixed $related, string $field, string $value, string $match = 'both') Limits results by matching a join field to a value (case insensitive).
  81. * @method DataMapper group_by_join_field() group_by_join_field(mixed $related, string $field) Groups the query by a join field.
  82. * @method DataMapper having_join_field() having_join_field(mixed $related, string $field, string $value) Groups the querying using a HAVING clause.
  83. * @method DataMapper or_having_join_field() having_join_field(mixed $related, string $field, string $value) Groups the querying using a HAVING clause, via OR.
  84. * @method DataMapper order_by_join_field() order_by_join_field(mixed $related, string $field, string $direction) Orders the query based on a join field.
  85. *
  86. * SQL Functions:
  87. * @method DataMapper select_func() select_func(string $function_name, mixed $args,..., string $alias) Selects the result of a SQL function. Alias is required.
  88. * @method DataMapper where_func() where_func(string $function_name, mixed $args,..., string $value) Limits results based on a SQL function.
  89. * @method DataMapper or_where_func() or_where_func(string $function_name, mixed $args,..., string $value) Limits results based on a SQL function, via OR.
  90. * @method DataMapper where_in_func() where_in_func(string $function_name, mixed $args,..., array $values) Limits results by comparing a SQL function to a range of values.
  91. * @method DataMapper or_where_in_func() or_where_in_func(string $function_name, mixed $args,..., array $values) Limits results by comparing a SQL function to a range of values.
  92. * @method DataMapper where_not_in_func() where_not_in_func(string $function_name, string $field, array $values) Limits results by comparing a SQL function to a range of values.
  93. * @method DataMapper or_where_not_in_func() or_where_not_in_func(string $function_name, mixed $args,..., array $values) Limits results by comparing a SQL function to a range of values.
  94. * @method DataMapper like_func() like_func(string $function_name, mixed $args,..., string $value) Limits results by matching a SQL function to a value.
  95. * @method DataMapper or_like_func() like_func(string $function_name, mixed $args,..., string $value) Limits results by matching a SQL function to a value.
  96. * @method DataMapper not_like_func() like_func(string $function_name, mixed $args,..., string $value) Limits results by matching a SQL function to a value.
  97. * @method DataMapper or_not_like_func() like_func(string $function_name, mixed $args,..., string $value) Limits results by matching a SQL function to a value.
  98. * @method DataMapper ilike_func() like_func(string $function_name, mixed $args,..., string $value) Limits results by matching a SQL function to a value (case insensitive).
  99. * @method DataMapper or_ilike_func() like_func(string $function_name, mixed $args,..., string $value) Limits results by matching a SQL function to a value (case insensitive).
  100. * @method DataMapper not_ilike_func() like_func(string $function_name, mixed $args,..., string $value) Limits results by matching a SQL function to a value (case insensitive).
  101. * @method DataMapper or_not_ilike_func() like_func(string $function_name, mixed $args,..., string $value) Limits results by matching a SQL function to a value (case insensitive).
  102. * @method DataMapper group_by_func() group_by_func(string $function_name, mixed $args,...) Groups the query by a SQL function.
  103. * @method DataMapper having_func() having_func(string $function_name, mixed $args,..., string $value) Groups the querying using a HAVING clause.
  104. * @method DataMapper or_having_func() having_func(string $function_name, mixed $args,..., string $value) Groups the querying using a HAVING clause, via OR.
  105. * @method DataMapper order_by_func() order_by_func(string $function_name, mixed $args,..., string $direction) Orders the query based on a SQL function.
  106. *
  107. * Field -> SQL functions:
  108. * @method DataMapper where_field_field_func() where_field_func($field, string $function_name, mixed $args,...) Limits results based on a SQL function.
  109. * @method DataMapper or_where_field_field_func() or_where_field_func($field, string $function_name, mixed $args,...) Limits results based on a SQL function, via OR.
  110. * @method DataMapper where_in_field_field_func() where_in_field_func($field, string $function_name, mixed $args,...) Limits results by comparing a SQL function to a range of values.
  111. * @method DataMapper or_where_in_field_field_func() or_where_in_field_func($field, string $function_name, mixed $args,...) Limits results by comparing a SQL function to a range of values.
  112. * @method DataMapper where_not_in_field_field_func() where_not_in_field_func($field, string $function_name, string $field) Limits results by comparing a SQL function to a range of values.
  113. * @method DataMapper or_where_not_in_field_field_func() or_where_not_in_field_func($field, string $function_name, mixed $args,...) Limits results by comparing a SQL function to a range of values.
  114. * @method DataMapper like_field_field_func() like_field_func($field, string $function_name, mixed $args,...) Limits results by matching a SQL function to a value.
  115. * @method DataMapper or_like_field_field_func() like_field_func($field, string $function_name, mixed $args,...) Limits results by matching a SQL function to a value.
  116. * @method DataMapper not_like_field_field_func() like_field_func($field, string $function_name, mixed $args,...) Limits results by matching a SQL function to a value.
  117. * @method DataMapper or_not_like_field_field_func() like_field_func($field, string $function_name, mixed $args,...) Limits results by matching a SQL function to a value.
  118. * @method DataMapper ilike_field_field_func() like_field_func($field, string $function_name, mixed $args,...) Limits results by matching a SQL function to a value (case insensitive).
  119. * @method DataMapper or_ilike_field_field_func() like_field_func($field, string $function_name, mixed $args,...) Limits results by matching a SQL function to a value (case insensitive).
  120. * @method DataMapper not_ilike_field_field_func() like_field_func($field, string $function_name, mixed $args,...) Limits results by matching a SQL function to a value (case insensitive).
  121. * @method DataMapper or_not_ilike_field_field_func() like_field_func($field, string $function_name, mixed $args,...) Limits results by matching a SQL function to a value (case insensitive).
  122. * @method DataMapper group_by_field_field_func() group_by_field_func($field, string $function_name, mixed $args,...) Groups the query by a SQL function.
  123. * @method DataMapper having_field_field_func() having_field_func($field, string $function_name, mixed $args,...) Groups the querying using a HAVING clause.
  124. * @method DataMapper or_having_field_field_func() having_field_func($field, string $function_name, mixed $args,...) Groups the querying using a HAVING clause, via OR.
  125. * @method DataMapper order_by_field_field_func() order_by_field_func($field, string $function_name, mixed $args,...) Orders the query based on a SQL function.
  126. *
  127. * Subqueries:
  128. * @method DataMapper select_subquery() select_subquery(DataMapper $subquery, string $alias) Selects the result of a function. Alias is required.
  129. * @method DataMapper where_subquery() where_subquery(mixed $subquery_or_field, mixed $value_or_subquery) Limits results based on a subquery.
  130. * @method DataMapper or_where_subquery() or_where_subquery(mixed $subquery_or_field, mixed $value_or_subquery) Limits results based on a subquery, via OR.
  131. * @method DataMapper where_in_subquery() where_in_subquery(mixed $subquery_or_field, mixed $values_or_subquery) Limits results by comparing a subquery to a range of values.
  132. * @method DataMapper or_where_in_subquery() or_where_in_subquery(mixed $subquery_or_field, mixed $values_or_subquery) Limits results by comparing a subquery to a range of values.
  133. * @method DataMapper where_not_in_subquery() where_not_in_subquery(mixed $subquery_or_field, string $field, mixed $values_or_subquery) Limits results by comparing a subquery to a range of values.
  134. * @method DataMapper or_where_not_in_subquery() or_where_not_in_subquery(mixed $subquery_or_field, mixed $values_or_subquery) Limits results by comparing a subquery to a range of values.
  135. * @method DataMapper like_subquery() like_subquery(DataMapper $subquery, string $value, string $match = 'both') Limits results by matching a subquery to a value.
  136. * @method DataMapper or_like_subquery() like_subquery(DataMapper $subquery, string $value, string $match = 'both') Limits results by matching a subquery to a value.
  137. * @method DataMapper not_like_subquery() like_subquery(DataMapper $subquery, string $value, string $match = 'both') Limits results by matching a subquery to a value.
  138. * @method DataMapper or_not_like_subquery() like_subquery(DataMapper $subquery, string $value, string $match = 'both') Limits results by matching a subquery to a value.
  139. * @method DataMapper ilike_subquery() like_subquery(DataMapper $subquery, string $value, string $match = 'both') Limits results by matching a subquery to a value (case insensitive).
  140. * @method DataMapper or_ilike_subquery() like_subquery(DataMapper $subquery, string $value, string $match = 'both') Limits results by matching a subquery to a value (case insensitive).
  141. * @method DataMapper not_ilike_subquery() like_subquery(DataMapper $subquery, string $value, string $match = 'both') Limits results by matching a subquery to a value (case insensitive).
  142. * @method DataMapper or_not_ilike_subquery() like_subquery(DataMapper $subquery, string $value, string $match = 'both') Limits results by matching a subquery to a value (case insensitive).
  143. * @method DataMapper having_subquery() having_subquery(string $field, DataMapper $subquery) Groups the querying using a HAVING clause.
  144. * @method DataMapper or_having_subquery() having_subquery(string $field, DataMapper $subquery) Groups the querying using a HAVING clause, via OR.
  145. * @method DataMapper order_by_subquery() order_by_subquery(DataMapper $subquery, string $direction) Orders the query based on a subquery.
  146. *
  147. * Related Subqueries:
  148. * @method DataMapper where_related_subquery() where_related_subquery(mixed $related_model, string $related_field, DataMapper $subquery) Limits results based on a subquery.
  149. * @method DataMapper or_where_related_subquery() or_where_related_subquery(mixed $related_model, string $related_field, DataMapper $subquery) Limits results based on a subquery, via OR.
  150. * @method DataMapper where_in_related_subquery() where_in_related_subquery(mixed $related_model, string $related_field, DataMapper $subquery) Limits results by comparing a subquery to a range of values.
  151. * @method DataMapper or_where_in_related_subquery() or_where_in_related_subquery(mixed $related_model, string $related_field, DataMapper $subquery) Limits results by comparing a subquery to a range of values.
  152. * @method DataMapper where_not_in_related_subquery() where_not_in_related_subquery(mixed $related_model, string $related_field, DataMapper $subquery) Limits results by comparing a subquery to a range of values.
  153. * @method DataMapper or_where_not_in_related_subquery() or_where_not_in_related_subquery(mixed $related_model, string $related_field, DataMapper $subquery) Limits results by comparing a subquery to a range of values.
  154. * @method DataMapper having_related_subquery() having_related_subquery(mixed $related_model, string $related_field, DataMapper $subquery) Groups the querying using a HAVING clause.
  155. * @method DataMapper or_having_related_subquery() having_related_subquery(mixed $related_model, string $related_field, DataMapper $subquery) Groups the querying using a HAVING clause, via OR.
  156. *
  157. * Array Extension:
  158. * @method array to_array() to_array($fields = '') NEEDS ARRAY EXTENSION. Converts this object into an associative array. @link DMZ_Array::to_array
  159. * @method array all_to_array() all_to_array($fields = '') NEEDS ARRAY EXTENSION. Converts the all array into an associative array. @link DMZ_Array::all_to_array
  160. * @method array|bool from_array() from_array($data, $fields = '', $save = FALSE) NEEDS ARRAY EXTENSION. Converts $this->all into an associative array. @link DMZ_Array::all_to_array
  161. *
  162. * CSV Extension
  163. * @method bool csv_export() csv_export($filename, $fields = '', $include_header = TRUE) NEEDS CSV EXTENSION. Exports this object as a CSV file.
  164. * @method array csv_import() csv_import($filename, $fields = '', $header_row = TRUE, $callback = NULL) NEEDS CSV EXTENSION. Imports a CSV file into this object.
  165. *
  166. * JSON Extension:
  167. * @method string to_json() to_json($fields = '', $pretty_print = FALSE) NEEDS JSON EXTENSION. Converts this object into a JSON string.
  168. * @method string all_to_json() all_to_json($fields = '', $pretty_print = FALSE) NEEDS JSON EXTENSION. Converts the all array into a JSON string.
  169. * @method bool from_json() from_json($json, $fields = '') NEEDS JSON EXTENSION. Imports the values from a JSON string into this object.
  170. * @method void set_json_content_type() set_json_content_type() NEEDS JSON EXTENSION. Sets the content type header to Content-Type: application/json.
  171. *
  172. * SimpleCache Extension:
  173. * @method DataMapper get_cached() get_cached($limit = '', $offset = '') NEEDS SIMPLECACHE EXTENSION. Enables cacheable queries.
  174. * @method DataMapper clear_cache() get_cached($segment,...) NEEDS SIMPLECACHE EXTENSION. Clears a cache for the specfied segment.
  175. *
  176. */
  177. class DataMapper implements IteratorAggregate {
  178. /**
  179. * Stores the shared configuration
  180. * @var array
  181. */
  182. static $config = array();
  183. /**
  184. * Stores settings that are common across a specific Model
  185. * @var array
  186. */
  187. static $common = array(DMZ_CLASSNAMES_KEY => array());
  188. /**
  189. * Stores global extensions
  190. * @var array
  191. */
  192. static $global_extensions = array();
  193. /**
  194. * Used to override unset default properties.
  195. * @var array
  196. */
  197. static $_dmz_config_defaults = array(
  198. 'timestamp_format' => 'Y-m-d H:i:s O',
  199. 'created_field' => 'created',
  200. 'updated_field' => 'updated',
  201. 'extensions_path' => 'datamapper',
  202. 'field_label_lang_format' => '${model}_${field}',
  203. );
  204. /**
  205. * Contains any errors that occur during validation, saving, or other
  206. * database access.
  207. * @var DM_Error_Object
  208. */
  209. public $error;
  210. /**
  211. * Used to keep track of the original values from the database, to
  212. * prevent unecessarily changing fields.
  213. * @var object
  214. */
  215. public $stored;
  216. /**
  217. * DB Table Prefix
  218. * @var string
  219. */
  220. public $prefix = '';
  221. /**
  222. * DB Join Table Prefix
  223. * @var string
  224. */
  225. public $join_prefix = '';
  226. /**
  227. * The name of the table for this model (may be automatically generated
  228. * from the classname).
  229. * @var string
  230. */
  231. public $table = '';
  232. /**
  233. * The singular name for this model (may be automatically generated from
  234. * the classname).
  235. * @var string
  236. */
  237. public $model = '';
  238. /**
  239. * Can be used to override the default database behavior.
  240. * @var mixed
  241. */
  242. public $db_params = '';
  243. /**
  244. * Prefix string used when reporting errors.
  245. * @var string
  246. */
  247. public $error_prefix = '';
  248. /**
  249. * Suffic string used when reporting errors.
  250. * @var string
  251. */
  252. public $error_suffix = '';
  253. /**
  254. * Custom name for the automatic timestamp saved with new objects.
  255. * Defaults to 'created'.
  256. * @var string
  257. */
  258. public $created_field = '';
  259. /**
  260. * Custom name for the automatic timestamp saved when an object changes.
  261. * Defaults to 'updated'.
  262. * @var string
  263. */
  264. public $updated_field = '';
  265. /**
  266. * If TRUE, automatically wrap every save and delete in a transaction.
  267. * @var bool
  268. */
  269. public $auto_transaction = FALSE;
  270. /**
  271. * If TRUE, has_many relationships are automatically loaded when accessed.
  272. * Not recommended in most situations.
  273. * @var bool
  274. */
  275. public $auto_populate_has_many = FALSE;
  276. /**
  277. * If TRUE, has_one relationships are automatically loaded when accessed.
  278. * Not recommended in some situations.
  279. * @var bool
  280. */
  281. public $auto_populate_has_one = FALSE;
  282. /**
  283. * Enables the old method of storing the all array using an object's ID.
  284. * @var bool
  285. */
  286. public $all_array_uses_ids = FALSE;
  287. /**
  288. * The result of validate is stored here.
  289. * @var bool
  290. */
  291. public $valid = FALSE;
  292. /**
  293. * If TRUE, the created/updated fields are stored using local time.
  294. * If FALSE (the default), they are stored using UTC
  295. * @var bool
  296. */
  297. public $local_time = FALSE;
  298. /**
  299. * If TRUE, the created/updated fields are stored as a unix timestamp,
  300. * as opposed to a formatted string.
  301. * Defaults to FALSE.
  302. * @var bool
  303. */
  304. public $unix_timestamp = FALSE;
  305. /**
  306. * Set to a date format to override the default format of
  307. * 'Y-m-d H:i:s O'
  308. * @var string
  309. */
  310. public $timestamp_format = '';
  311. /**
  312. * Contains the database fields for this object.
  313. * ** Automatically configured **
  314. * @var array
  315. */
  316. public $fields = array();
  317. /**
  318. * Set to a string to use when autoloading lang files.
  319. * Can contain two magic values: ${model} and ${table}.
  320. * These are automatically
  321. * replaced when looking up the language file.
  322. * Defaults to model_${model}
  323. * @var string
  324. */
  325. public $lang_file_format = '';
  326. /**
  327. * Set to a string to use when looking up field labels. Can contain three
  328. * magic values: ${model}, ${table}, and ${field}. These are automatically
  329. * replaced when looking up the language file.
  330. * Defaults to ${model}_${field}
  331. * @var string
  332. */
  333. public $field_label_lang_format = '';
  334. /**
  335. * Contains the result of the last query.
  336. * @var array
  337. */
  338. public $all = array();
  339. /**
  340. * Semi-private field used to track the parent model/id if there is one.
  341. * @var array
  342. */
  343. public $parent = array();
  344. /**
  345. * Contains the validation rules, label, and get_rules for each field.
  346. * @var array
  347. */
  348. public $validation = array();
  349. /**
  350. * Contains any related objects of which this model is related one or more times.
  351. * @var array
  352. */
  353. public $has_many = array();
  354. /**
  355. * Contains any related objects of which this model is singularly related.
  356. * @var array
  357. */
  358. public $has_one = array();
  359. /**
  360. * Used to enable or disable the production cache.
  361. * This should really only be set in the global configuration.
  362. * @var bool
  363. */
  364. public $production_cache = FALSE;
  365. /**
  366. * Used to determine where to look for extensions.
  367. * This should really only be set in the global configuration.
  368. * @var string
  369. */
  370. public $extensions_path = '';
  371. /**
  372. * If set to an array of names, this will automatically load the
  373. * specified extensions for this model.
  374. * @var mixed
  375. */
  376. public $extensions = NULL;
  377. /**
  378. * If a query returns more than the number of rows specified here,
  379. * then it will be automatically freed after a get.
  380. * @var int
  381. */
  382. public $free_result_threshold = 100;
  383. /**
  384. * This can be specified as an array of fields to sort by if no other
  385. * sorting or selection has occurred.
  386. * @var mixed
  387. */
  388. public $default_order_by = NULL;
  389. // tracks whether or not the object has already been validated
  390. protected $_validated = FALSE;
  391. // Tracks the columns that need to be instantiated after a GET
  392. protected $_instantiations = NULL;
  393. // Tracks get_rules, matches, and intval rules, to spped up _to_object
  394. protected $_field_tracking = NULL;
  395. // used to track related queries in deep relationships.
  396. protected $_query_related = array();
  397. // If true before a related get(), any extra fields on the join table will be added.
  398. protected $_include_join_fields = FALSE;
  399. // If true before a save, this will force the next save to be new.
  400. protected $_force_save_as_new = FALSE;
  401. // If true, the next where statement will not be prefixed with an AND or OR.
  402. protected $_where_group_started = FALSE;
  403. /**
  404. * Constructor
  405. *
  406. * Initialize DataMapper.
  407. * @param int $id if provided, load in the object specified by that ID.
  408. */
  409. public function DataMapper($id = NULL)
  410. {
  411. $this->_dmz_assign_libraries();
  412. $this_class = strtolower(get_class($this));
  413. $is_dmz = $this_class == 'datamapper';
  414. if($is_dmz)
  415. {
  416. $this->_load_languages();
  417. $this->_load_helpers();
  418. }
  419. // this is to ensure that singular is only called once per model
  420. if(isset(DataMapper::$common[DMZ_CLASSNAMES_KEY][$this_class])) {
  421. $common_key = DataMapper::$common[DMZ_CLASSNAMES_KEY][$this_class];
  422. } else {
  423. DataMapper::$common[DMZ_CLASSNAMES_KEY][$this_class] = $common_key = singular($this_class);
  424. }
  425. // Determine model name
  426. if (empty($this->model))
  427. {
  428. $this->model = $common_key;
  429. }
  430. // Load stored config settings by reference
  431. foreach (DataMapper::$config as $config_key => &$config_value)
  432. {
  433. // Only if they're not already set
  434. if (empty($this->{$config_key}))
  435. {
  436. $this->{$config_key} =& $config_value;
  437. }
  438. }
  439. // Load model settings if not in common storage
  440. if ( ! isset(DataMapper::$common[$common_key]))
  441. {
  442. // If model is 'datamapper' then this is the initial autoload by CodeIgniter
  443. if ($is_dmz)
  444. {
  445. // Load config settings
  446. $this->config->load('datamapper', TRUE, TRUE);
  447. // Get and store config settings
  448. DataMapper::$config = $this->config->item('datamapper');
  449. // now double check that all required config values were set
  450. foreach(DataMapper::$_dmz_config_defaults as $config_key => $config_value)
  451. {
  452. if(empty(DataMapper::$config[$config_key]))
  453. {
  454. DataMapper::$config[$config_key] = $config_value;
  455. }
  456. }
  457. DataMapper::_load_extensions(DataMapper::$global_extensions, DataMapper::$config['extensions']);
  458. unset(DataMapper::$config['extensions']);
  459. return;
  460. }
  461. // load language file, if requested and it exists
  462. if(!empty($this->lang_file_format))
  463. {
  464. $lang_file = str_replace(array('${model}', '${table}'), array($this->model, $this->table), $this->lang_file_format);
  465. $deft_lang = $this->config->item('language');
  466. $idiom = ($deft_lang == '') ? 'english' : $deft_lang;
  467. if(file_exists(APPPATH.'language/'.$idiom.'/'.$lang_file.'_lang'.EXT))
  468. {
  469. $this->lang->load($lang_file, $idiom);
  470. }
  471. }
  472. $loaded_from_cache = FALSE;
  473. // Load in the production cache for this model, if it exists
  474. if( ! empty(DataMapper::$config['production_cache']))
  475. {
  476. // attempt to load the production cache file
  477. $cache_folder = APPPATH . DataMapper::$config['production_cache'];
  478. if(file_exists($cache_folder) && is_dir($cache_folder) && is_writeable($cache_folder))
  479. {
  480. $cache_file = $cache_folder . '/' . $common_key . EXT;
  481. if(file_exists($cache_file))
  482. {
  483. include($cache_file);
  484. if(isset($cache))
  485. {
  486. DataMapper::$common[$common_key] =& $cache;
  487. unset($cache);
  488. // allow subclasses to add initializations
  489. if(method_exists($this, 'post_model_init'))
  490. {
  491. $this->post_model_init(TRUE);
  492. }
  493. // Load extensions (they are not cacheable)
  494. $this->_initiate_local_extensions($common_key);
  495. $loaded_from_cache = TRUE;
  496. }
  497. }
  498. }
  499. }
  500. if(! $loaded_from_cache)
  501. {
  502. // Determine table name
  503. if (empty($this->table))
  504. {
  505. $this->table = plural(get_class($this));
  506. }
  507. // Add prefix to table
  508. $this->table = $this->prefix . $this->table;
  509. $this->_field_tracking = array(
  510. 'get_rules' => array(),
  511. 'matches' => array(),
  512. 'intval' => array('id')
  513. );
  514. // Convert validation into associative array by field name
  515. $associative_validation = array();
  516. foreach ($this->validation as $name => $validation)
  517. {
  518. if(is_string($name)) {
  519. $validation['field'] = $name;
  520. } else {
  521. $name = $validation['field'];
  522. }
  523. // clean up possibly missing fields
  524. if( ! isset($validation['rules']))
  525. {
  526. $validation['rules'] = array();
  527. }
  528. // Populate associative validation array
  529. $associative_validation[$name] = $validation;
  530. if (!empty($validation['get_rules']))
  531. {
  532. $this->_field_tracking['get_rules'][] = $name;
  533. }
  534. // Check if there is a "matches" validation rule
  535. if (isset($validation['rules']['matches']))
  536. {
  537. $this->_field_tracking['matches'][$name] = $validation['rules']['matches'];
  538. }
  539. }
  540. // set up id column, if not set
  541. if(!isset($associative_validation['id']))
  542. {
  543. // label is set below, to prevent caching language-based labels
  544. $associative_validation['id'] = array(
  545. 'field' => 'id',
  546. 'rules' => array('integer')
  547. );
  548. }
  549. $this->validation = $associative_validation;
  550. // Force all other has_one ITFKs to integers on get
  551. foreach($this->has_one as $related => $rel_props)
  552. {
  553. $field = $related . '_id';
  554. if( in_array($field, $this->fields) &&
  555. ( ! isset($this->validation[$field]) || // does not have a validation key or...
  556. ! isset($this->validation[$field]['get_rules'])) && // a get_rules key...
  557. ( ! isset($this->validation[$related]) || // nor does the related have a validation key or...
  558. ! isset($this->validation[$related]['get_rules'])) ) // a get_rules key
  559. {
  560. // assume an int
  561. $this->_field_tracking['intval'][] = $field;
  562. }
  563. }
  564. // Get and store the table's field names and meta data
  565. $fields = $this->db->field_data($this->table);
  566. // Store only the field names and ensure validation list includes all fields
  567. foreach ($fields as $field)
  568. {
  569. // Populate fields array
  570. $this->fields[] = $field->name;
  571. // Add validation if current field has none
  572. if ( ! isset($this->validation[$field->name]))
  573. {
  574. // label is set below, to prevent caching language-based labels
  575. $this->validation[$field->name] = array('field' => $field->name, 'rules' => array());
  576. }
  577. }
  578. // convert simple has_one and has_many arrays into more advanced ones
  579. foreach(array('has_one', 'has_many') as $arr)
  580. {
  581. $new = array();
  582. foreach ($this->{$arr} as $related_field => $rel_props)
  583. {
  584. // allow for simple (old-style) associations
  585. if (is_int($related_field))
  586. {
  587. $related_field = $rel_props;
  588. }
  589. // convert value into array if necessary
  590. if ( ! is_array($rel_props))
  591. {
  592. $rel_props = array('class' => $rel_props);
  593. } else if ( ! isset($rel_props['class']))
  594. {
  595. // if already an array, ensure that the class attribute is set
  596. $rel_props['class'] = $related_field;
  597. }
  598. if( ! isset($rel_props['other_field']))
  599. {
  600. // add this model as the model to use in queries if not set
  601. $rel_props['other_field'] = $this->model;
  602. }
  603. if( ! isset($rel_props['join_self_as']))
  604. {
  605. // add this model as the model to use in queries if not set
  606. $rel_props['join_self_as'] = $rel_props['other_field'];
  607. }
  608. if( ! isset($rel_props['join_other_as']))
  609. {
  610. // add the key as the model to use in queries if not set
  611. $rel_props['join_other_as'] = $related_field;
  612. }
  613. $new[$related_field] = $rel_props;
  614. // load in labels for each not-already-set field
  615. if(!isset($this->validation[$related_field]))
  616. {
  617. $label = $this->localize_label($related_field);
  618. if(!empty($label))
  619. {
  620. // label is re-set below, to prevent caching language-based labels
  621. $this->validation[$related_field] = array('field' => $related_field, 'rules' => array());
  622. }
  623. }
  624. }
  625. // replace the old array
  626. $this->{$arr} = $new;
  627. }
  628. // allow subclasses to add initializations
  629. if(method_exists($this, 'post_model_init'))
  630. {
  631. $this->post_model_init(FALSE);
  632. }
  633. // Store common model settings
  634. foreach (array('table', 'fields', 'validation',
  635. 'has_one', 'has_many', '_field_tracking') as $item)
  636. {
  637. DataMapper::$common[$common_key][$item] = $this->{$item};
  638. }
  639. // if requested, store the item to the production cache
  640. if( ! empty(DataMapper::$config['production_cache']))
  641. {
  642. // attempt to load the production cache file
  643. $cache_folder = APPPATH . DataMapper::$config['production_cache'];
  644. if(file_exists($cache_folder) && is_dir($cache_folder) && is_writeable($cache_folder))
  645. {
  646. $cache_file = $cache_folder . '/' . $common_key . EXT;
  647. $cache = "<"."?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); \n";
  648. $cache .= '$cache = ' . var_export(DataMapper::$common[$common_key], TRUE) . ';';
  649. if ( ! $fp = @fopen($cache_file, 'w'))
  650. {
  651. show_error('Error creating production cache file: ' . $cache_file);
  652. }
  653. flock($fp, LOCK_EX);
  654. fwrite($fp, $cache);
  655. flock($fp, LOCK_UN);
  656. fclose($fp);
  657. @chmod($cache_file, FILE_WRITE_MODE);
  658. }
  659. }
  660. // Load extensions last, so they aren't cached.
  661. $this->_initiate_local_extensions($common_key);
  662. }
  663. // Finally, localize the labels here (because they shouldn't be cached
  664. // This also sets any missing labels.
  665. $validation =& DataMapper::$common[$common_key]['validation'];
  666. foreach($validation as $field => &$val)
  667. {
  668. // Localize label if necessary
  669. $val['label'] = $this->localize_label($field,
  670. isset($val['label']) ?
  671. $val['label'] :
  672. FALSE);
  673. }
  674. unset($validation);
  675. }
  676. // Load stored common model settings by reference
  677. foreach(DataMapper::$common[$common_key] as $key => &$value)
  678. {
  679. $this->{$key} =& $value;
  680. }
  681. // Clear object properties to set at default values
  682. $this->clear();
  683. if( ! empty($id) && is_numeric($id))
  684. {
  685. $this->get_by_id(intval($id));
  686. }
  687. }
  688. // --------------------------------------------------------------------
  689. /**
  690. * Reloads in the configuration data for a model. This is mainly
  691. * used to handle language changes. Only this instance and new instances
  692. * will see the changes.
  693. */
  694. public function reinitialize_model()
  695. {
  696. // this is to ensure that singular is only called once per model
  697. if(isset(DataMapper::$common[DMZ_CLASSNAMES_KEY][$this_class])) {
  698. $common_key = DataMapper::$common[DMZ_CLASSNAMES_KEY][$this_class];
  699. } else {
  700. DataMapper::$common[DMZ_CLASSNAMES_KEY][$this_class] = $common_key = singular($this_class);
  701. }
  702. unset(DataMapper::$common[$common_key]);
  703. $model = get_class($this);
  704. new $model(); // re-initialze
  705. // Load stored common model settings by reference
  706. foreach(DataMapper::$common[$common_key] as $key => &$value)
  707. {
  708. $this->{$key} =& $value;
  709. }
  710. }
  711. // --------------------------------------------------------------------
  712. /**
  713. * Autoload
  714. *
  715. * Autoloads object classes that are used with DataMapper.
  716. * This method will look in any model directories available to CI.
  717. *
  718. * Note:
  719. * It is important that they are autoloaded as loading them manually with
  720. * CodeIgniter's loader class will cause DataMapper's __get and __set functions
  721. * to not function.
  722. *
  723. * @param string $class Name of class to load.
  724. */
  725. public static function autoload($class)
  726. {
  727. // Don't attempt to autoload CI_ or MY_ prefixed classes
  728. if (in_array(substr($class, 0, 3), array('CI_', 'EE_', 'MY_')))
  729. {
  730. return;
  731. }
  732. // Prepare class
  733. $class = strtolower($class);
  734. $CI =& get_instance();
  735. // Prepare path
  736. if (isset($CI->load->_ci_model_paths) && is_array($CI->load->_ci_model_paths))
  737. {
  738. // use CI loader's model path
  739. $paths = $CI->load->_ci_model_paths;
  740. }
  741. else
  742. {
  743. $paths = array(APPPATH);
  744. }
  745. foreach ($paths as $path)
  746. {
  747. // Prepare file
  748. $file = $path . 'models/' . $class . EXT;
  749. // Check if file exists, require_once if it does
  750. if (file_exists($file))
  751. {
  752. require_once($file);
  753. break;
  754. }
  755. }
  756. // if class not loaded, do a recursive search of model paths for the class
  757. if (! class_exists($class))
  758. {
  759. foreach($paths as $path)
  760. {
  761. $found = DataMapper::recursive_require_once($class, $path . 'models');
  762. if($found)
  763. {
  764. break;
  765. }
  766. }
  767. }
  768. }
  769. // --------------------------------------------------------------------
  770. /**
  771. * Recursive Require Once
  772. *
  773. * Recursively searches the path for the class, require_once if found.
  774. *
  775. * @param string $class Name of class to look for
  776. * @param string $path Current path to search
  777. */
  778. protected static function recursive_require_once($class, $path)
  779. {
  780. $found = FALSE;
  781. $handle = opendir($path);
  782. if ($handle)
  783. {
  784. while (FALSE !== ($dir = readdir($handle)))
  785. {
  786. // If dir does not contain a dot
  787. if (strpos($dir, '.') === FALSE)
  788. {
  789. // Prepare recursive path
  790. $recursive_path = $path . '/' . $dir;
  791. // Prepare file
  792. $file = $recursive_path . '/' . $class . EXT;
  793. // Check if file exists, require_once if it does
  794. if (file_exists($file))
  795. {
  796. require_once($file);
  797. $found = TRUE;
  798. break;
  799. }
  800. else if (is_dir($recursive_path))
  801. {
  802. // Do a recursive search of the path for the class
  803. DataMapper::recursive_require_once($class, $recursive_path);
  804. }
  805. }
  806. }
  807. closedir($handle);
  808. }
  809. return $found;
  810. }
  811. // --------------------------------------------------------------------
  812. /**
  813. * Loads in any extensions used by this class or globally.
  814. *
  815. * @param array $extensions List of extensions to add to.
  816. * @param array $name List of new extensions to load.
  817. */
  818. protected static function _load_extensions(&$extensions, $names)
  819. {
  820. $CI =& get_instance();
  821. $class_prefixes = array(
  822. 0 => 'DMZ_',
  823. 1 => 'DataMapper_',
  824. 2 => $CI->config->item('subclass_prefix'),
  825. 3 => 'CI_'
  826. );
  827. foreach($names as $name => $options)
  828. {
  829. if( ! is_string($name))
  830. {
  831. $name = $options;
  832. $options = NULL;
  833. }
  834. // only load an extension if it wasn't already loaded in this context
  835. if(isset($extensions[$name]))
  836. {
  837. return;
  838. }
  839. if( ! isset($extensions['_methods']))
  840. {
  841. $extensions['_methods'] = array();
  842. }
  843. // determine the file name and class name
  844. if(strpos($name, '/') === FALSE)
  845. {
  846. $file = APPPATH . DataMapper::$config['extensions_path'] . '/' . $name . EXT;
  847. $ext = $name;
  848. }
  849. else
  850. {
  851. $file = APPPATH . $name . EXT;
  852. $ext = array_pop(explode('/', $name));
  853. }
  854. if(!file_exists($file))
  855. {
  856. show_error('DataMapper Error: loading extension ' . $name . ': File not found.');
  857. }
  858. // load class
  859. include_once($file);
  860. // Allow for DMZ_Extension, DataMapper_Extension, etc.
  861. foreach($class_prefixes as $index => $prefix)
  862. {
  863. if(class_exists($prefix.$ext))
  864. {
  865. if($index == 2) // "MY_"
  866. {
  867. // Load in the library this class is based on
  868. $CI->load->libary($ext);
  869. }
  870. $ext = $prefix.$ext;
  871. break;
  872. }
  873. }
  874. if(!class_exists($ext))
  875. {
  876. show_error("DataMapper Error: Unable to find a class for extension $name.");
  877. }
  878. // create class
  879. if(is_null($options))
  880. {
  881. $o = new $ext();
  882. }
  883. else
  884. {
  885. $o = new $ext($options);
  886. }
  887. $extensions[$name] = $o;
  888. // figure out which methods can be called on this class.
  889. $methods = get_class_methods($ext);
  890. foreach($methods as $m)
  891. {
  892. // do not load private methods or methods already loaded.
  893. if($m[0] !== '_' &&
  894. is_callable(array($o, $m)) &&
  895. ! isset($extensions['_methods'][$m])
  896. ) {
  897. // store this method.
  898. $extensions['_methods'][$m] = $name;
  899. }
  900. }
  901. }
  902. }
  903. // --------------------------------------------------------------------
  904. /**
  905. * Loads the extensions that are local to this model.
  906. * @param string $common_key Shared key to save extenions to.
  907. */
  908. private function _initiate_local_extensions($common_key)
  909. {
  910. if(!empty($this->extensions))
  911. {
  912. $extensions = $this->extensions;
  913. $this->extensions = array();
  914. DataMapper::_load_extensions($this->extensions, $extensions);
  915. }
  916. else
  917. {
  918. // ensure an empty array
  919. $this->extensions = array('_methods' => array());
  920. }
  921. // bind to the shared key, for dynamic loading
  922. DataMapper::$common[$common_key]['extensions'] =& $this->extensions;
  923. }
  924. // --------------------------------------------------------------------
  925. /**
  926. * Dynamically load an extension when needed.
  927. * @param object $name Name of the extension (or array of extensions).
  928. * @param array $options Options for the extension
  929. * @param boolean $local If TRUE, only loads the extension into this object
  930. */
  931. public function load_extension($name, $options = NULL, $local = FALSE)
  932. {
  933. if( ! is_array($name))
  934. {
  935. if( ! is_null($options))
  936. {
  937. $name = array($name => $options);
  938. }
  939. else
  940. {
  941. $name = array($name);
  942. }
  943. }
  944. // called individually to ensure that the array is modified directly
  945. // (and not copied instead)
  946. if($local)
  947. {
  948. DataMapper::_load_extensions($this->extensions, $name);
  949. }
  950. else
  951. {
  952. DataMapper::_load_extensions(DataMapper::$global_extensions, $name);
  953. }
  954. }
  955. // --------------------------------------------------------------------
  956. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  957. * *
  958. * Magic methods *
  959. * *
  960. * The following are methods to override the default PHP behaviour. *
  961. * *
  962. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
  963. // --------------------------------------------------------------------
  964. /**
  965. * Magic Get
  966. *
  967. * Returns the value of the named property.
  968. * If named property is a related item, instantiate it first.
  969. *
  970. * This method also instantiates the DB object and the form_validation
  971. * objects as necessary
  972. *
  973. * @ignore
  974. * @param string $name Name of property to look for
  975. * @return mixed
  976. */
  977. public function __get($name)
  978. {
  979. // We dynamically get DB when needed, and create a copy.
  980. // This allows multiple queries to be generated at the same time.
  981. if($name == 'db')
  982. {
  983. $CI =& get_instance();
  984. if($this->db_params === FALSE)
  985. {
  986. $this->db =& $CI->db;
  987. }
  988. else
  989. {
  990. if($this->db_params == '' || $this->db_params === TRUE)
  991. {
  992. // ensure the shared DB is disconnected, even if the app exits uncleanly
  993. if(!isset($CI->db->_has_shutdown_hook))
  994. {
  995. register_shutdown_function(array($CI->db, 'close'));
  996. $CI->db->_has_shutdown_hook = TRUE;
  997. }
  998. // clone, so we don't create additional connections to the DB
  999. $this->db = clone($CI->db);
  1000. $this->db->_reset_select();
  1001. }
  1002. else
  1003. {
  1004. // connecting to a different database, so we *must* create additional copies.
  1005. // It is up to the developer to close the connection!
  1006. $this->db = $CI->load->database($this->db_params, TRUE, TRUE);
  1007. }
  1008. // these items are shared (for debugging)
  1009. if(isset($CI->db))
  1010. {
  1011. $this->db->queries =& $CI->db->queries;
  1012. $this->db->query_times =& $CI->db->query_times;
  1013. }
  1014. }
  1015. // ensure the created DB is disconnected, even if the app exits uncleanly
  1016. if(!isset($this->db->_has_shutdown_hook))
  1017. {
  1018. register_shutdown_function(array($this->db, 'close'));
  1019. $this->db->_has_shutdown_hook = TRUE;
  1020. }
  1021. return $this->db;
  1022. }
  1023. // Special case to get form_validation when first accessed
  1024. if($name == 'form_validation')
  1025. {
  1026. $CI =& get_instance();
  1027. if( ! isset($CI->form_validation))
  1028. {
  1029. $CI->load->library('form_validation');
  1030. }
  1031. $this->form_validation = $CI->form_validation;
  1032. $this->lang->load('form_validation');
  1033. return $this->form_validation;
  1034. }
  1035. $has_many = isset($this->has_many[$name]);
  1036. $has_one = isset($this->has_one[$name]);
  1037. // If named property is a "has many" or "has one" related item
  1038. if ($has_many || $has_one)
  1039. {
  1040. $related_properties = $has_many ? $this->has_many[$name] : $this->has_one[$name];
  1041. // Instantiate it before accessing
  1042. $class = $related_properties['class'];
  1043. $this->{$name} = new $class();
  1044. // Store parent data
  1045. $this->{$name}->parent = array('model' => $related_properties['other_field'], 'id' => $this->id);
  1046. // Check if Auto Populate for "has many" or "has one" is on
  1047. // (but only if this object exists in the DB, and we aren't instantiating)
  1048. if ($this->exists() &&
  1049. ($has_many && $this->auto_populate_has_many) || ($has_one && $this->auto_populate_has_one))
  1050. {
  1051. $this->{$name}->get();
  1052. }
  1053. return $this->{$name};
  1054. }
  1055. $name_single = singular($name);
  1056. if($name_single !== $name) {
  1057. // possibly return single form of name
  1058. $test = $this->{$name_single};
  1059. if(is_object($test)) {
  1060. return $test;
  1061. }
  1062. }
  1063. return NULL;
  1064. }
  1065. // --------------------------------------------------------------------
  1066. /**
  1067. * Used several places to temporarily override the auto_populate setting
  1068. * @ignore
  1069. * @param string $related Related Name
  1070. * @return DataMapper|NULL
  1071. */
  1072. private function &_get_without_auto_populating($related)
  1073. {
  1074. $b_many = $this->auto_populate_has_many;
  1075. $b_one = $this->auto_populate_has_one;
  1076. $this->auto_populate_has_many = FALSE;
  1077. $this->auto_populate_has_one = FALSE;
  1078. $ret =& $this->{$related};
  1079. $this->auto_populate_has_many = $b_many;
  1080. $this->auto_populate_has_one = $b_one;
  1081. return $ret;
  1082. }
  1083. // --------------------------------------------------------------------
  1084. /**
  1085. * Magic Call
  1086. *
  1087. * Calls special methods, or extension methods.
  1088. *
  1089. * @ignore
  1090. * @param string $method Method name
  1091. * @param array $arguments Arguments to method
  1092. * @return mixed
  1093. */
  1094. public function __call($method, $arguments)
  1095. {
  1096. // List of watched method names
  1097. // NOTE: order matters: make sure more specific items are listed before
  1098. // less specific items
  1099. $watched_methods = array(
  1100. 'save_', 'delete_',
  1101. 'get_by_related_', 'get_by_related', 'get_by_',
  1102. '_related_subquery', '_subquery',
  1103. '_related_', '_related',
  1104. '_join_field',
  1105. '_field_func', '_func'
  1106. );
  1107. foreach ($watched_methods as $watched_method)
  1108. {
  1109. // See if called method is a watched method
  1110. if (strpos($method, $watched_method) !== FALSE)
  1111. {
  1112. $pieces = explode($watched_method, $method);
  1113. if ( ! empty($pieces[0]) && ! empty($pieces[1]))
  1114. {
  1115. // Watched method is in the middle
  1116. return $this->{'_' . trim($watched_method, '_')}($pieces[0], array_merge(array($pieces[1]), $arguments));
  1117. }
  1118. else
  1119. {
  1120. // Watched method is a prefix or suffix
  1121. return $this->{'_' . trim($watched_method, '_')}(str_replace($watched_method, '', $method), $ar

Large files files are truncated, but you can click here to view the full file