PageRenderTime 72ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/AkActiveRecord.php

http://akelosframework.googlecode.com/
PHP | 5018 lines | 3286 code | 556 blank | 1176 comment | 580 complexity | 47070d1ff38415bf2a32501b1638610c MD5 | raw file
Possible License(s): LGPL-2.1

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

  1. <?php
  2. /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
  3. // +----------------------------------------------------------------------+
  4. // | Akelos Framework - http://www.akelos.org |
  5. // +----------------------------------------------------------------------+
  6. // | Copyright (c) 2002-2006, Akelos Media, S.L. & Bermi Ferrer Martinez |
  7. // | Released under the GNU Lesser General Public License, see LICENSE.txt|
  8. // +----------------------------------------------------------------------+
  9. /**
  10. * @package AkelosFramework
  11. * @subpackage AkActiveRecord
  12. * @author Bermi Ferrer <bermi a.t akelos c.om> 2004 - 2007
  13. * @author Kaste 2007
  14. * @copyright Copyright (c) 2002-2006, Akelos Media, S.L. http://www.akelos.org
  15. * @license GNU Lesser General Public License <http://www.gnu.org/copyleft/lesser.html>
  16. */
  17. require_once(AK_LIB_DIR.DS.'AkActiveRecord'.DS.'AkAssociatedActiveRecord.php');
  18. // Akelos args is a short way to call functions that is only intended for fast prototyping
  19. defined('AK_ENABLE_AKELOS_ARGS') ? null : define('AK_ENABLE_AKELOS_ARGS', false);
  20. // Use setColumnName if available when using set('column_name', $value);
  21. defined('AK_ACTIVE_RECORD_INTERNATIONALIZE_MODELS_BY_DEFAULT') ? null : define('AK_ACTIVE_RECORD_INTERNATIONALIZE_MODELS_BY_DEFAULT', true);
  22. defined('AK_ACTIVE_RECORD_ENABLE_CALLBACK_SETTERS') ? null : define('AK_ACTIVE_RECORD_ENABLE_CALLBACK_SETTERS', true);
  23. defined('AK_ACTIVE_RECORD_ENABLE_CALLBACK_GETTERS') ? null : define('AK_ACTIVE_RECORD_ENABLE_CALLBACK_GETTERS', true);
  24. defined('AK_ACTIVE_RECORD_ENABLE_PERSISTENCE') ? null : define('AK_ACTIVE_RECORD_ENABLE_PERSISTENCE', AK_ENVIRONMENT != 'testing');
  25. defined('AK_ACTIVE_RECORD_CACHE_DATABASE_SCHEMA') ? null : define('AK_ACTIVE_RECORD_CACHE_DATABASE_SCHEMA', AK_ACTIVE_RECORD_ENABLE_PERSISTENCE && AK_ENVIRONMENT != 'development');
  26. defined('AK_ACTIVE_RECORD_CACHE_DATABASE_SCHEMA_LIFE') ? null : define('AK_ACTIVE_RECORD_CACHE_DATABASE_SCHEMA_LIFE', 300);
  27. defined('AK_ACTIVE_RECORD_VALIDATE_TABLE_NAMES') ? null : define('AK_ACTIVE_RECORD_VALIDATE_TABLE_NAMES', true);
  28. defined('AK_ACTIVE_RECORD_SKIP_SETTING_ACTIVE_RECORD_DEFAULTS') ? null : define('AK_ACTIVE_RECORD_SKIP_SETTING_ACTIVE_RECORD_DEFAULTS', false);
  29. defined('AK_NOT_EMPTY_REGULAR_EXPRESSION') ? null : define('AK_NOT_EMPTY_REGULAR_EXPRESSION','/.+/');
  30. defined('AK_EMAIL_REGULAR_EXPRESSION') ? null : define('AK_EMAIL_REGULAR_EXPRESSION',"/^([a-z0-9_\-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-z0-9\-]+\.)+))([a-z]{2,4}|[0-9]{1,3})(\]?)$/i");
  31. defined('AK_NUMBER_REGULAR_EXPRESSION') ? null : define('AK_NUMBER_REGULAR_EXPRESSION',"/^[0-9]+$/");
  32. defined('AK_PHONE_REGULAR_EXPRESSION') ? null : define('AK_PHONE_REGULAR_EXPRESSION',"/^([\+]?[(]?[\+]?[ ]?[0-9]{2,3}[)]?[ ]?)?[0-9 ()\-]{4,25}$/");
  33. defined('AK_DATE_REGULAR_EXPRESSION') ? null : define('AK_DATE_REGULAR_EXPRESSION',"/^(([0-9]{1,2}(\-|\/|\.| )[0-9]{1,2}(\-|\/|\.| )[0-9]{2,4})|([0-9]{2,4}(\-|\/|\.| )[0-9]{1,2}(\-|\/|\.| )[0-9]{1,2})){1}$/");
  34. defined('AK_IP4_REGULAR_EXPRESSION') ? null : define('AK_IP4_REGULAR_EXPRESSION',"/^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/");
  35. defined('AK_POST_CODE_REGULAR_EXPRESSION') ? null : define('AK_POST_CODE_REGULAR_EXPRESSION',"/^[0-9A-Za-z -]{2,9}$/");
  36. // Forces loading database schema on every call
  37. if(AK_DEV_MODE && isset($_SESSION['__activeRecordColumnsSettingsCache'])){
  38. unset($_SESSION['__activeRecordColumnsSettingsCache']);
  39. }
  40. ak_compat('array_combine');
  41. /**
  42. * Active Record objects doesn't specify their attributes directly, but rather infer them from the table definition with
  43. * which they're linked. Adding, removing, and changing attributes and their type is done directly in the database. Any change
  44. * is instantly reflected in the Active Record objects. The mapping that binds a given Active Record class to a certain
  45. * database table will happen automatically in most common cases, but can be overwritten for the uncommon ones.
  46. *
  47. * See the mapping rules in table_name and the full example in README.txt for more insight.
  48. *
  49. * == Creation ==
  50. *
  51. * Active Records accepts constructor parameters either in an array or as a list of parameters in a specific format. The array method is especially useful when
  52. * you're receiving the data from somewhere else, like a HTTP request. It works like this:
  53. *
  54. * $user = new User(array('name' => 'David', 'occupation' => 'Code Artist'));
  55. * echo $user->name; // Will print "David"
  56. *
  57. * You can also use a parameter list initialization.:
  58. *
  59. * $user = new User('name->', 'David', 'occupation->', 'Code Artist');
  60. *
  61. * And of course you can just create a bare object and specify the attributes after the fact:
  62. *
  63. * $user = new User();
  64. * $user->name = 'David';
  65. * $user->occupation = 'Code Artist';
  66. *
  67. * == Conditions ==
  68. *
  69. * Conditions can either be specified as a string or an array representing the WHERE-part of an SQL statement.
  70. * The array form is to be used when the condition input is tainted and requires sanitization. The string form can
  71. * be used for statements that doesn't involve tainted data. Examples:
  72. *
  73. * class User extends AkActiveRecord
  74. * {
  75. * function authenticateUnsafely($user_name, $password)
  76. * {
  77. * return findFirst("user_name = '$user_name' AND password = '$password'");
  78. * }
  79. *
  80. * function authenticateSafely($user_name, $password)
  81. * {
  82. * return findFirst("user_name = ? AND password = ?", $user_name, $password);
  83. * }
  84. * }
  85. *
  86. * The <tt>authenticateUnsafely</tt> method inserts the parameters directly into the query and is thus susceptible to SQL-injection
  87. * attacks if the <tt>$user_name</tt> and <tt>$password</tt> parameters come directly from a HTTP request. The <tt>authenticateSafely</tt> method,
  88. * on the other hand, will sanitize the <tt>$user_name</tt> and <tt>$password</tt> before inserting them in the query, which will ensure that
  89. * an attacker can't escape the query and fake the login (or worse).
  90. *
  91. * When using multiple parameters in the conditions, it can easily become hard to read exactly what the fourth or fifth
  92. * question mark is supposed to represent. In those cases, you can resort to named bind variables instead. That's done by replacing
  93. * the question marks with symbols and supplying a hash with values for the matching symbol keys:
  94. *
  95. * $Company->findFirst(
  96. * "id = :id AND name = :name AND division = :division AND created_at > :accounting_date",
  97. * array(':id' => 3, ':name' => "37signals", ':division' => "First", ':accounting_date' => '2005-01-01')
  98. * );
  99. *
  100. * == Accessing attributes before they have been type casted ==
  101. *
  102. * Some times you want to be able to read the raw attribute data without having the column-determined type cast run its course first.
  103. * That can be done by using the <attribute>_before_type_cast accessors that all attributes have. For example, if your Account model
  104. * has a balance attribute, you can call $Account->balance_before_type_cast or $Account->id_before_type_cast.
  105. *
  106. * This is especially useful in validation situations where the user might supply a string for an integer field and you want to display
  107. * the original string back in an error message. Accessing the attribute normally would type cast the string to 0, which isn't what you
  108. * want.
  109. *
  110. * == Saving arrays, hashes, and other non-mappable objects in text columns ==
  111. *
  112. * Active Record can serialize any object in text columns. To do so, you must specify this with by setting the attribute serialize whith
  113. * a comma separated list of columns or an array.
  114. * This makes it possible to store arrays, hashes, and other non-mappeable objects without doing any additional work. Example:
  115. *
  116. * class User extends AkActiveRecord
  117. * {
  118. * var $serialize = 'preferences';
  119. * }
  120. *
  121. * $User = new User(array('preferences'=>array("background" => "black", "display" => 'large')));
  122. * $User->find($user_id);
  123. * $User->preferences // array("background" => "black", "display" => 'large')
  124. *
  125. * == Single table inheritance ==
  126. *
  127. * Active Record allows inheritance by storing the name of the class in a column that by default is called "type" (can be changed
  128. * by overwriting <tt>AkActiveRecord->_inheritanceColumn</tt>). This means that an inheritance looking like this:
  129. *
  130. * class Company extends AkActiveRecord{}
  131. * class Firm extends Company{}
  132. * class Client extends Company{}
  133. * class PriorityClient extends Client{}
  134. *
  135. * When you do $Firm->create('name =>', "akelos"), this record will be saved in the companies table with type = "Firm". You can then
  136. * fetch this row again using $Company->find('first', "name = '37signals'") and it will return a Firm object.
  137. *
  138. * If you don't have a type column defined in your table, single-table inheritance won't be triggered. In that case, it'll work just
  139. * like normal subclasses with no special magic for differentiating between them or reloading the right type with find.
  140. *
  141. * Note, all the attributes for all the cases are kept in the same table. Read more:
  142. * http://www.martinfowler.com/eaaCatalog/singleTableInheritance.html
  143. *
  144. * == Connection to multiple databases in different models ==
  145. *
  146. * Connections are usually created through AkActiveRecord->establishConnection and retrieved by AkActiveRecord->connection.
  147. * All classes inheriting from AkActiveRecord will use this connection. But you can also set a class-specific connection.
  148. * For example, if $Course is a AkActiveRecord, but resides in a different database you can just say $Course->establishConnection
  149. * and $Course and all its subclasses will use this connection instead.
  150. *
  151. * Active Records will automatically record creation and/or update timestamps of database objects
  152. * if fields of the names created_at/created_on or updated_at/updated_on are present.
  153. * Date only: created_on, updated_on
  154. * Date and time: created_at, updated_at
  155. *
  156. * This behavior can be turned off by setting <tt>$this->_recordTimestamps = false</tt>.
  157. */
  158. class AkActiveRecord extends AkAssociatedActiveRecord
  159. {
  160. //var $disableAutomatedAssociationLoading = true;
  161. var $_tableName;
  162. var $_db;
  163. var $_newRecord;
  164. var $_freeze;
  165. var $_dataDictionary;
  166. var $_primaryKey;
  167. var $_inheritanceColumn;
  168. var $_associations;
  169. var $_internationalize;
  170. var $_errors = array();
  171. var $_attributes = array();
  172. var $_protectedAttributes = array();
  173. var $_accessibleAttributes = array();
  174. var $_recordTimestamps = true;
  175. // Column description
  176. var $_columnNames = array();
  177. // Array of column objects for the table associated with this class.
  178. var $_columns = array();
  179. // Columns that can be edited/viewed
  180. var $_contentColumns = array();
  181. // Methods that will be dinamically loaded for the model (EXPERIMENTAL) This pretends to generate something similar to Ruby on Rails finders.
  182. // If you set array('findOneByUsernameAndPassword', 'findByCompany', 'findAllByExipringDate')
  183. // You'll get $User->findOneByUsernameAndPassword('admin', 'pass');
  184. var $_dynamicMethods = false;
  185. var $_combinedAttributes = array();
  186. var $_BlobQueryStack = null;
  187. var $_automated_max_length_validator = true;
  188. var $_automated_validators_enabled = true;
  189. var $_automated_not_null_validator = false;
  190. var $_set_default_attribute_values_automatically = true;
  191. // This is needed for enabling support for static active record instantation under php
  192. var $_activeRecordHasBeenInstantiated = true;
  193. var $__ActsLikeAttributes = array();
  194. var $__coreActsLikeAttributes = array('nested_set', 'list', 'tree');
  195. /**
  196. * Holds a hash with all the default error messages, such that they can be replaced by your own copy or localizations.
  197. */
  198. var $_defaultErrorMessages = array(
  199. 'inclusion' => "is not included in the list",
  200. 'exclusion' => "is reserved",
  201. 'invalid' => "is invalid",
  202. 'confirmation' => "doesn't match confirmation",
  203. 'accepted' => "must be accepted",
  204. 'empty' => "can't be empty",
  205. 'blank' => "can't be blank",
  206. 'too_long' => "is too long (max is %d characters)",
  207. 'too_short' => "is too short (min is %d characters)",
  208. 'wrong_length' => "is the wrong length (should be %d characters)",
  209. 'taken' => "has already been taken",
  210. 'not_a_number' => "is not a number"
  211. );
  212. var $__activeRecordObject = true;
  213. function __construct()
  214. {
  215. $attributes = (array)func_get_args();
  216. return $this->init($attributes);
  217. }
  218. function init($attributes = array())
  219. {
  220. AK_LOG_EVENTS ? ($this->Logger =& Ak::getLogger()) : null;
  221. $this->_internationalize = is_null($this->_internationalize) && AK_ACTIVE_RECORD_INTERNATIONALIZE_MODELS_BY_DEFAULT ? count($this->getAvaliableLocales()) > 1 : $this->_internationalize;
  222. @$this->_instatiateDefaultObserver();
  223. $this->setConnection();
  224. if(!empty($this->table_name)){
  225. $this->setTableName($this->table_name);
  226. }
  227. $this->_loadActAsBehaviours();
  228. if(!empty($this->combined_attributes)){
  229. foreach ($this->combined_attributes as $combined_attribute){
  230. $this->addCombinedAttributeConfiguration($combined_attribute);
  231. }
  232. }
  233. if(isset($attributes[0]) && is_array($attributes[0]) && count($attributes) === 1){
  234. $attributes = $attributes[0];
  235. $this->_newRecord = true;
  236. }
  237. // new AkActiveRecord(23); //Returns object with primary key 23
  238. if(isset($attributes[0]) && count($attributes) === 1 && $attributes[0] > 0){
  239. $record = $this->find($attributes[0]);
  240. if(!$record){
  241. return false;
  242. }else {
  243. $this->setAttributes($record->getAttributes(), true);
  244. }
  245. // This option is only used internally for loading found objects
  246. }elseif(isset($attributes[0]) && isset($attributes[1]) && $attributes[0] == 'attributes' && is_array($attributes[1])){
  247. foreach($attributes[1] as $k=>$v){
  248. $attributes[1][$k] = $this->castAttributeFromDatabase($k, $v);
  249. }
  250. $avoid_loading_associations = isset($attributes[1]['load_associations']) ? false : !empty($this->disableAutomatedAssociationLoading);
  251. $this->setAttributes($attributes[1], true);
  252. }else{
  253. $this->newRecord($attributes);
  254. }
  255. $this->_buildFinders();
  256. empty($avoid_loading_associations) ? $this->loadAssociations() : null;
  257. }
  258. function __destruct()
  259. {
  260. }
  261. /**
  262. * If this macro is used, only those attributed named in it will be accessible
  263. * for mass-assignment, such as new ModelName($attributes) and $this->attributes($attributes).
  264. * This is the more conservative choice for mass-assignment protection.
  265. * If you'd rather start from an all-open default and restrict attributes as needed,
  266. * have a look at AkActiveRecord::setProtectedAttributes().
  267. */
  268. function setAccessibleAttributes()
  269. {
  270. $args = func_get_args();
  271. $this->_accessibleAttributes = array_unique(array_merge((array)$this->_accessibleAttributes, $args));
  272. }
  273. /**
  274. * Attributes named in this macro are protected from mass-assignment, such as
  275. * new ModelName($attributes) and $this->attributes(attributes). Their assignment
  276. * will simply be ignored. Instead, you can use the direct writer methods to do assignment.
  277. * This is meant to protect sensitive attributes to be overwritten by URL/form hackers.
  278. *
  279. * Example:
  280. * <code>
  281. * class Customer extends AkActiveRecord
  282. * {
  283. * function Customer()
  284. * {
  285. * $this->setProtectedAttributes('credit_rating');
  286. * }
  287. * }
  288. *
  289. * $Customer = new Customer('name' => 'David', 'credit_rating' => 'Excellent');
  290. * $Customer->credit_rating // => null
  291. * $Customer->attributes(array('description' => 'Jolly fellow', 'credit_rating' => 'Superb'));
  292. * $Customer->credit_rating // => null
  293. *
  294. * $Customer->credit_rating = 'Average'
  295. * $Customer->credit_rating // => 'Average'
  296. * </code>
  297. */
  298. function setProtectedAttributes()
  299. {
  300. $args = func_get_args();
  301. $this->_protectedAttributes = array_unique(array_merge((array)$this->_protectedAttributes, $args));
  302. }
  303. /**
  304. * Returns true if a connection that?s accessible to this class have already been opened.
  305. */
  306. function isConnected()
  307. {
  308. return isset($this->_db);
  309. }
  310. /**
  311. * Returns the connection currently associated with the class. This can also be used to
  312. * "borrow" the connection to do database work unrelated to any of the specific Active Records.
  313. */
  314. function &getConnection()
  315. {
  316. return $this->_db;
  317. }
  318. /**
  319. * Set the connection for the class.
  320. */
  321. function setConnection($dns = null, $connection_id = null)
  322. {
  323. $this->_db =& Ak::db($dns, $connection_id);
  324. }
  325. /**
  326. * Returns an array of columns objects where the primary id, all columns ending in "_id" or "_count",
  327. * and columns used for single table inheritance has been removed.
  328. */
  329. function getContentColumns()
  330. {
  331. $inheritance_column = $this->getInheritanceColumn();
  332. $columns = $this->getColumns();
  333. foreach ($columns as $name=>$details){
  334. if((substr($name,-3) == '_id' || substr($name,-6) == '_count') ||
  335. !empty($details['primaryKey']) || ($inheritance_column !== false && $inheritance_column == $name)){
  336. unset($columns[$name]);
  337. }
  338. }
  339. return $columns;
  340. }
  341. /**
  342. * Creates an object, instantly saves it as a record (if the validation permits it), and returns it.
  343. * If the save fail under validations, the unsaved object is still returned.
  344. */
  345. function &create($attributes = null)
  346. {
  347. if(!isset($this->_activeRecordHasBeenInstantiated)){
  348. return Ak::handleStaticCall();
  349. }
  350. if(func_num_args() > 1){
  351. $attributes = func_get_args();
  352. }
  353. $model = $this->getModelName();
  354. $object =& new $model();
  355. $object->setAttributes($attributes);
  356. $object->save();
  357. return $object;
  358. }
  359. /**
  360. * Returns the result of an SQL statement that should only include a COUNT(*) in the SELECT part.
  361. *
  362. * $Product->countBySql("SELECT COUNT(*) FROM sales s, customers c WHERE s.customer_id = c.id");
  363. */
  364. function countBySql($sql)
  365. {
  366. if(!isset($this->_activeRecordHasBeenInstantiated)){
  367. return Ak::handleStaticCall();
  368. }
  369. if(!stristr($sql, 'COUNT') && stristr($sql, ' FROM ')){
  370. $sql = 'SELECT COUNT(*) '.substr($sql,strpos(str_replace(' from ',' FROM ', $sql),' FROM '));
  371. }
  372. if(!$this->isConnected()){
  373. $this->setConnection();
  374. }
  375. AK_LOG_EVENTS ? ($this->Logger->message($this->getModelName().' executing SQL: '.$sql)) : null;
  376. $rs = $this->_db->Execute($sql);
  377. return @(integer)$rs->fields[0];
  378. }
  379. /**
  380. * Increments the specified counter by one. So $DiscussionBoard->incrementCounter("post_count",
  381. * $discussion_board_id); would increment the "post_count" counter on the board responding to
  382. * $discussion_board_id. This is used for caching aggregate values, so that they doesn't need to
  383. * be computed every time. Especially important for looping over a collection where each element
  384. * require a number of aggregate values. Like the $DiscussionBoard that needs to list both the number of posts and comments.
  385. */
  386. function incrementCounter($counter_name, $id, $difference = 1)
  387. {
  388. $new_value = $this->getAttribute($counter_name) + $difference;
  389. if($this->updateAll($counter_name.' = '.$new_value, $this->getPrimaryKey().' = '.$this->castAttributeForDatabase($this->getPrimaryKey(), $id)) === 0){
  390. return false;
  391. }
  392. return $new_value;
  393. }
  394. /**
  395. * Works like AkActiveRecord::incrementCounter, but decrements instead.
  396. */
  397. function decrementCounter($counter_name, $id, $difference = 1)
  398. {
  399. $new_value = $this->getAttribute($counter_name) - $difference;
  400. if(!$this->updateAll($counter_name.' = '.$new_value, $this->getPrimaryKey().' = '.$this->castAttributeForDatabase($this->getPrimaryKey(), $id)) === 0){
  401. return false;
  402. }
  403. return $new_value;
  404. }
  405. /**
  406. * Finds the record from the passed id, instantly saves it with the passed attributes (if the validation permits it),
  407. * and returns it. If the save fail under validations, the unsaved object is still returned.
  408. */
  409. function update($id, $attributes)
  410. {
  411. if(!isset($this->_activeRecordHasBeenInstantiated)){
  412. return Ak::handleStaticCall();
  413. }
  414. if(is_array($id)){
  415. $results = array();
  416. foreach ($id as $idx=>$single_id){
  417. $results[] = $this->update($single_id, isset($attributes[$idx]) ? $attributes[$idx] : $attributes);
  418. }
  419. return $results;
  420. }else{
  421. $object =& $this->find($id);
  422. $object->updateAttributes($attributes);
  423. return $object;
  424. }
  425. }
  426. /**
  427. * Updates a single attribute and saves the record. This is especially useful for boolean flags on existing records.
  428. * Note: Make sure that updates made with this method doesn't get subjected to validation checks.
  429. * Hence, attributes can be updated even if the full object isn't valid.
  430. */
  431. function updateAttribute($name, $value)
  432. {
  433. $this->setAttribute($name, $value);
  434. return $this->save(false);
  435. }
  436. /**
  437. * Updates all the attributes in from the passed array and saves the record. If the object is
  438. * invalid, the saving will fail and false will be returned.
  439. */
  440. function updateAttributes($attributes, $object = null)
  441. {
  442. isset($object) ? $object->setAttributes($attributes) : $this->setAttributes($attributes);
  443. return isset($object) ? $object->save() : $this->save();
  444. }
  445. /**
  446. * Updates all records with the SET-part of an SQL update statement in updates and returns an
  447. * integer with the number of rows updates. A subset of the records can be selected by specifying conditions. Example:
  448. * <code>$Billing->updateAll("category = 'authorized', approved = 1", "author = 'David'");</code>
  449. *
  450. * Important note: Condifitons are not sanitized yet so beware of accepting
  451. * variable conditions when using this function
  452. */
  453. function updateAll($updates, $conditions = null)
  454. {
  455. if(!isset($this->_activeRecordHasBeenInstantiated)){
  456. return Ak::handleStaticCall();
  457. }
  458. /**
  459. * @todo sanitize sql conditions
  460. */
  461. $sql = 'UPDATE '.$this->getTableName().' SET '.$updates;
  462. $sql .= isset($conditions) ? ' WHERE '.$conditions : '';
  463. $this->_executeSql($sql);
  464. return $this->_db->Affected_Rows();
  465. }
  466. /**
  467. * Deletes the record with the given id without instantiating an object first. If an array of
  468. * ids is provided, all of them are deleted.
  469. */
  470. function delete($id)
  471. {
  472. if(!isset($this->_activeRecordHasBeenInstantiated)){
  473. return Ak::handleStaticCall();
  474. }
  475. $id = func_num_args() > 1 ? func_get_args() : $id;
  476. return $this->deleteAll($this->getPrimaryKey().' IN ('.(is_array($id) ? join(', ',$id) : $id).')');
  477. }
  478. /**
  479. * Returns an array of names for the attributes available on this object sorted alphabetically.
  480. */
  481. function getAttributeNames()
  482. {
  483. if(!isset($this->_activeRecordHasBeenInstantiated)){
  484. return Ak::handleStaticCall();
  485. }
  486. $attributes = array_keys($this->getAvailableAttributes());
  487. $names = array_combine($attributes,array_map(array(&$this,'getAttributeCaption'), $attributes));
  488. natsort($names);
  489. return $names;
  490. }
  491. /**
  492. * Returns true if the specified attribute has been set by the user or by a database load and is neither null nor empty?
  493. */
  494. function isAttributePresent($attribute)
  495. {
  496. $value = $this->getAttribute($attribute);
  497. return !empty($value);
  498. }
  499. /**
  500. * Deletes all the records that matches the condition without instantiating the objects first
  501. * (and hence not calling the destroy method). Example:
  502. *
  503. * <code>$Post->destroyAll("person_id = 5 AND (category = 'Something' OR category = 'Else')");</code>
  504. *
  505. * Important note: Condifitons are not sanitized yet so beware of accepting
  506. * variable conditions when using this function
  507. */
  508. function deleteAll($conditions = null)
  509. {
  510. if(!isset($this->_activeRecordHasBeenInstantiated)){
  511. return Ak::handleStaticCall();
  512. }
  513. /**
  514. * @todo sanitize sql conditions
  515. */
  516. $sql = 'DELETE FROM '.$this->getTableName();
  517. $sql .= isset($conditions) ? ' WHERE '.$conditions : ($this->_getDatabaseType() == 'sqlite' ? ' WHERE 1' : ''); // (HACK) If where clause is not included sqlite_changes will not get the right result
  518. $this->_executeSql($sql);
  519. return $this->_db->Affected_Rows() > 0;
  520. }
  521. /**
  522. * Destroys the record with the given id by instantiating the object and calling destroy
  523. * (all the callbacks are the triggered). If an array of ids is provided, all of them are destroyed.
  524. * Deletes the record in the database and freezes this instance to reflect that no changes should be
  525. * made (since they can't be persisted).
  526. */
  527. function destroy($id = null)
  528. {
  529. if(!isset($this->_activeRecordHasBeenInstantiated)){
  530. return Ak::handleStaticCall();
  531. }
  532. $this->transactionStart();
  533. $id = func_num_args() > 1 ? func_get_args() : $id;
  534. if(isset($id)){
  535. $id_arr = is_array($id) ? $id : array($id);
  536. if($objects = $this->find($id_arr)){
  537. $results = count($objects);
  538. $no_problems = true;
  539. for ($i=0; $results > $i; $i++){
  540. if(!$objects[$i]->destroy()){
  541. $no_problems = false;
  542. }
  543. }
  544. $this->transactionComplete();
  545. return $no_problems;
  546. }else {
  547. $this->transactionComplete();
  548. return false;
  549. }
  550. }else{
  551. if(!$this->isNewRecord()){
  552. if($this->beforeDestroy()){
  553. $this->notifyObservers('beforeDestroy');
  554. $sql = 'DELETE FROM '.$this->getTableName().' WHERE '.$this->getPrimaryKey().' = '.$this->_db->qstr($this->getId());
  555. $this->_executeSql($sql);
  556. $had_success = ($this->_db->Affected_Rows() > 0);
  557. if(!$had_success || ($had_success && !$this->afterDestroy())){
  558. $this->transactionFail();
  559. $had_success = false;
  560. }else{
  561. $had_success = $this->notifyObservers('afterDestroy') === false ? false : true;
  562. }
  563. $this->transactionComplete();
  564. $this->freeze();
  565. return $had_success;
  566. }else {
  567. $this->transactionFail();
  568. $this->transactionComplete();
  569. return false;
  570. }
  571. }
  572. }
  573. if(!$this->afterDestroy()){
  574. $this->transactionFail();
  575. }else{
  576. $this->notifyObservers('afterDestroy');
  577. }
  578. $this->transactionComplete();
  579. return false;
  580. }
  581. /**
  582. * Destroys the objects for all the records that matches the condition by instantiating
  583. * each object and calling the destroy method.
  584. *
  585. * Example:
  586. *
  587. * $Person->destroyAll("last_login < '2004-04-04'");
  588. */
  589. function destroyAll($conditions)
  590. {
  591. if($objects = $this->find('all',array('conditions'=>$conditions))){
  592. $results = count($objects);
  593. $no_problems = true;
  594. for ($i=0; $results > $i; $i++){
  595. if(!$objects[$i]->destroy()){
  596. $no_problems = false;
  597. }
  598. }
  599. return $no_problems;
  600. }else {
  601. return false;
  602. }
  603. }
  604. /**
  605. * Establishes the connection to the database. Accepts an array as input where the 'adapter'
  606. * key must be specified with the name of a database adapter (in lower-case) example for regular
  607. * databases (MySQL, Postgresql, etc):
  608. *
  609. * $AkActiveRecord->establishConnection(
  610. * array(
  611. * 'adapter' => "mysql",
  612. * 'host' => "localhost",
  613. * 'username' => "myuser",
  614. * 'password' => "mypass",
  615. * 'database' => "somedatabase"
  616. * ));
  617. *
  618. * Example for SQLite database:
  619. *
  620. * $AkActiveRecord->establishConnection(
  621. * array(
  622. * 'adapter' => "sqlite",
  623. * 'dbfile' => "path/to/dbfile"
  624. * )
  625. * )
  626. */
  627. function &establishConnection($spec = null)
  628. {
  629. if(isset($spec)){
  630. $dns = is_string($spec) ? $spec : '';
  631. if(!empty($spec['adapter'])){
  632. $dsn = $spec['adapter'] == 'sqlite' ?
  633. 'sqlite://'.urlencode($spec['dbfile']).'/' :
  634. $spec['adapter'].'://'.@$spec['username'].':'.@$spec['password'].'@'.@$spec['host'].'/'.@$spec['database'];
  635. }
  636. $dsn .= isset($spec['persist']) && $spec['persist'] === false ? '' : '?persist';
  637. return $this->setConnection($dns);
  638. }else{
  639. return false;
  640. }
  641. }
  642. /**
  643. * Just freeze the attributes hash, such that associations are still accessible even on destroyed records.
  644. *
  645. * @todo implement freeze correctly for its intended use
  646. */
  647. function freeze()
  648. {
  649. $this->_freeze = true;
  650. }
  651. function isFrozen()
  652. {
  653. return !empty($this->_freeze);
  654. }
  655. /**
  656. * Returns true if the given id represents the primary key of a record in the database, false otherwise. Example:
  657. *
  658. * $Person->exists(5);
  659. */
  660. function exists($id)
  661. {
  662. return $this->find('first',array('conditions' => array($this->getPrimaryKey().' = '.$id))) !== false;
  663. }
  664. /**
  665. * Find operates with three different retrieval approaches:
  666. * * Find by id: This can either be a specific id find(1), a list of ids find(1, 5, 6),
  667. * or an array of ids find(array(5, 6, 10)). If no record can be found for all of the listed ids,
  668. * then RecordNotFound will be raised.
  669. * * Find first: This will return the first record matched by the options used. These options
  670. * can either be specific conditions or merely an order.
  671. * If no record can matched, false is returned.
  672. * * Find all: This will return all the records matched by the options used. If no records are found, an empty array is returned.
  673. *
  674. * All approaches accepts an $option array as their last parameter. The options are:
  675. *
  676. * 'conditions' => An SQL fragment like "administrator = 1" or array("user_name = ?" => $username). See conditions in the intro.
  677. * 'order' => An SQL fragment like "created_at DESC, name".
  678. * 'limit' => An integer determining the limit on the number of rows that should be returned.
  679. * 'offset' => An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows.
  680. * 'joins' => An SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = $id". (Rarely needed).
  681. * 'include' => Names associations that should be loaded alongside using LEFT OUTER JOINs. The symbols
  682. * named refer to already defined associations. See eager loading under Associations.
  683. *
  684. * Examples for find by id:
  685. *
  686. * $Person->find(1); // returns the object for ID = 1
  687. * $Person->find(1, 2, 6); // returns an array for objects with IDs in (1, 2, 6), Returns false if any of those IDs is not available
  688. * $Person->find(array(7, 17)); // returns an array for objects with IDs in (7, 17)
  689. * $Person->find(array(1)); // returns an array for objects the object with ID = 1
  690. * $Person->find(1, array('conditions' => "administrator = 1", 'order' => "created_on DESC"));
  691. *
  692. * Examples for find first:
  693. *
  694. * $Person->find('first'); // returns the first object fetched by SELECT * FROM people
  695. * $Person->find('first', array('conditions' => array("user_name = ':user_name'", ':user_name' => $user_name)));
  696. * $Person->find('first', array('order' => "created_on DESC", 'offset' => 5));
  697. *
  698. * Examples for find all:
  699. *
  700. * $Person->find('all'); // returns an array of objects for all the rows fetched by SELECT * FROM people
  701. * $Person->find(); // Same as $Person->find('all');
  702. * $Person->find('all', array('conditions => array("category IN (categories)", 'categories' => join(','$categories)), 'limit' => 50));
  703. * $Person->find('all', array('offset' => 10, 'limit' => 10));
  704. * $Person->find('all', array('include' => array('account', 'friends'));
  705. *
  706. */
  707. function &find()
  708. {
  709. if(!isset($this->_activeRecordHasBeenInstantiated)){
  710. return Ak::handleStaticCall();
  711. }
  712. $num_args = func_num_args();
  713. if($num_args === 2 && func_get_arg(0) == 'set arguments'){
  714. $args = func_get_arg(1);
  715. $num_args = count($args);
  716. }
  717. $args = $num_args > 0 ? (!isset($args) ? func_get_args() : $args) : array('all');
  718. if($num_args === 1 && is_numeric($args[0]) && $args[0] > 0){
  719. $args[0] = (integer)$args[0]; //Cast query by Id
  720. }
  721. $options = $num_args > 0 && (is_array($args[$num_args-1]) && isset($args[0][0]) && !is_numeric($args[0][0])) ? array_pop($args) : array();
  722. //$options = func_get_arg(func_num_args()-1);
  723. if(!empty($options['conditions']) && is_array($options['conditions'])){
  724. if (isset($options['conditions'][0]) && strstr($options['conditions'][0], '?') && count($options['conditions']) > 1){
  725. $pattern = array_shift($options['conditions']);
  726. $options['bind'] = array_values($options['conditions']);
  727. $options['conditions'] = $pattern;
  728. }elseif (isset($options['conditions'][0])){
  729. $pattern = array_shift($options['conditions']);
  730. $options['conditions'] = str_replace(array_keys($options['conditions']), array_values($this->getSanitizedConditionsArray($options['conditions'])),$pattern);
  731. }else{
  732. $options['conditions'] = join(' AND ',(array)$this->getAttributesQuoted($options['conditions']));
  733. }
  734. }
  735. if ($num_args === 2 && !empty($args[0]) && !empty($args[1]) && is_string($args[0]) && ($args[0] == 'all' || $args[0] == 'first') && is_string($args[1])){
  736. if (!is_array($args[1]) && $args[1] > 0 && $args[0] == 'first'){
  737. $num_args = 1;
  738. $args = array($args[1]);
  739. $options = array();
  740. }else{
  741. $options['conditions'] = $args[1];
  742. $args = array($args[0]);
  743. }
  744. }elseif ($num_args === 1 && isset($args[0]) && is_string($args[0]) && $args[0] != 'all' && $args[0] != 'first'){
  745. $options = array('conditions'=> $args[0]);
  746. $args = array('first');
  747. }
  748. if(!empty($options['conditions']) && is_numeric($options['conditions']) && $options['conditions'] > 0){
  749. unset($options['conditions']);
  750. }
  751. if($num_args > 1){
  752. if(!empty($args[0]) && is_string($args[0]) && strstr($args[0],'?')){
  753. $options = array_merge(array('conditions' => array_shift($args)), $options);
  754. $options['bind'] = $args;
  755. $args = array('all');
  756. }elseif (!empty($args[1]) && is_string($args[1]) && strstr($args[1],'?')){
  757. $_tmp_mode = array_shift($args);
  758. $options = array_merge(array('conditions' => array_shift($args)),$options);
  759. $options['bind'] = $args;
  760. $args = array($_tmp_mode);
  761. }
  762. }
  763. switch ($args[0]) {
  764. case 'first':
  765. $options = array_merge($options, array((!empty($options['include']) && $this->hasAssociations() ?'virtual_limit':'limit')=>1));
  766. $result =& $this->find('all', $options);
  767. if(!empty($result) && is_array($result)){
  768. $_result =& $result[0];
  769. }else{
  770. $_result = false;
  771. }
  772. return $_result;
  773. break;
  774. case 'all':
  775. $limit = isset($options['limit']) ? $options['limit'] : null;
  776. $offset = isset($options['offset']) ? $options['offset'] : null;
  777. if((empty($options['conditions']) && empty($options['order']) && is_null($offset) && $this->_getDatabaseType() == 'postgre' ? 1 : 0)){
  778. $options['order'] = $this->getPrimaryKey();
  779. }
  780. $sql = $this->constructFinderSql($options);
  781. if(!empty($options['bind']) && is_array($options['bind']) && strstr($sql,'?')){
  782. $sql = array_merge(array($sql),$options['bind']);
  783. }
  784. if((!empty($options['include']) && $this->hasAssociations())){
  785. $result =& $this->findWithAssociations($options, $limit, $offset);
  786. }else{
  787. $result =& $this->findBySql($sql, $limit, $offset);
  788. }
  789. if(!empty($result) && is_array($result)){
  790. $_result =& $result;
  791. }else{
  792. $_result = false;
  793. }
  794. return $_result;
  795. break;
  796. default:
  797. $ids = array_unique(isset($args[0]) ? (is_array($args[0]) ? $args[0] : (array)$args) : array());
  798. $num_ids = count($ids);
  799. $num_args = count($args);
  800. if(isset($ids[$num_ids-1]) && is_array($ids[$num_ids-1])){
  801. $options = array_merge($options, array_pop($ids));
  802. $num_ids--;
  803. }
  804. if($num_args === 1 && !$args[0] > 0){
  805. $options['conditions'] = $args[0];
  806. }
  807. $conditions = !empty($options['conditions']) ? ' AND '.$options['conditions'] : '';
  808. if(empty($options) && !empty($args[0]) && !empty($args[1]) && is_array($args[0]) && is_array($args[1])){
  809. $options = array_pop($args);
  810. }
  811. switch ($num_ids){
  812. case 0 :
  813. trigger_error($this->t('Couldn\'t find %object_name without an ID%conditions',array('%object_name'=>$this->getModelName(),'%conditions'=>$conditions)), E_USER_ERROR);
  814. break;
  815. case 1 :
  816. $table_name = !empty($options['include']) && $this->hasAssociations() ? '__owner' : $this->getTableName();
  817. $result =& $this->find('first', array_merge($options, array('conditions' => $table_name.'.'.$this->getPrimaryKey().' = '.$ids[0].$conditions)));
  818. if(is_array($args[0]) && $result !== false){
  819. //This is a dirty hack for avoiding PHP4 pass by reference error
  820. $result_for_ref = array(&$result);
  821. $_result =& $result_for_ref;
  822. }else{
  823. $_result =& $result;
  824. }
  825. return $_result;
  826. break;
  827. default:
  828. $ids_condition = $this->getPrimaryKey().' IN ('.join(', ',$ids).')';
  829. if(!empty($options['conditions']) && is_array($options['conditions'])){
  830. $options['conditions'][0] = $ids_condition.' AND '.$options['conditions'][0];
  831. }elseif(!empty($options['conditions'])){
  832. $options['conditions'] = $ids_condition.' AND '.$options['conditions'];
  833. }else{
  834. $without_conditions = true;
  835. $options['conditions'] = $ids_condition;
  836. }
  837. $result =& $this->find('all', $options);
  838. if(is_array($result) && (count($result) == $num_ids || empty($without_conditions))){
  839. if($result === false){
  840. $_result = false;
  841. }else{
  842. $_result =& $result;
  843. }
  844. return $_result;
  845. }else{
  846. $result = false;
  847. return $result;
  848. }
  849. break;
  850. }
  851. break;
  852. }
  853. $result = false;
  854. return $result;
  855. }
  856. function &findFirst()
  857. {
  858. if(!isset($this->_activeRecordHasBeenInstantiated)){
  859. return Ak::handleStaticCall();
  860. }
  861. $args = func_get_args();
  862. $result =& Ak::call_user_func_array(array(&$this,'find'), array_merge(array('first'),$args));
  863. return $result;
  864. }
  865. function &findAll()
  866. {
  867. if(!isset($this->_activeRecordHasBeenInstantiated)){
  868. return Ak::handleStaticCall();
  869. }
  870. $args = func_get_args();
  871. $result =& Ak::call_user_func_array(array(&$this,'find'), array_merge(array('all'),$args));
  872. return $result;
  873. }
  874. function &objectCache()
  875. {
  876. static $cache;
  877. $args =& func_get_args();
  878. if(count($args) == 2){
  879. if(!isset($cache[$args[0]])){
  880. $cache[$args[0]] =& $args[1];
  881. }
  882. }elseif(!isset($cache[$args[0]])){
  883. return false;
  884. }
  885. return $cache[$args[0]];
  886. }
  887. /**
  888. * Gets an array from a string.
  889. *
  890. * Acts like Php explode() function but uses any of this as valid separators ' AND ',' and ',' + ',' ',',',';'
  891. */
  892. function getArrayFromAkString($string)
  893. {
  894. if(is_array($string)){
  895. return $string;
  896. }
  897. $string = str_replace(array(' AND ',' and ',' + ',' ',',',';'),array('|','|','|','','|','|'),trim($string));
  898. return strstr($string,'|') ? explode('|', $string) : array($string);
  899. }
  900. // Gets the column name for use with single table inheritance ? can be overridden in subclasses.
  901. function getInheritanceColumn()
  902. {
  903. return empty($this->_inheritanceColumn) ? ($this->hasColumn('type') ? 'type' : false ) : $this->_inheritanceColumn;
  904. }
  905. // Defines the column name for use with single table inheritance ? can be overridden in subclasses.
  906. function setInheritanceColumn($column_name)
  907. {
  908. if(!$this->hasColumn($column_name)){
  909. trigger_error(Ak::t('Could not set "%column_name" as the inheritance column as this column is not available on the database.',array('%column_name'=>$column_name)), E_USER_NOTICE);
  910. return false;
  911. }elseif($this->getColumnType($column_name) != 'string'){
  912. trigger_error(Ak::t('Could not set %column_name as the inheritance column as this column type is "%column_type" instead of "string".',array('%column_name'=>$column_name,'%column_type'=>$this->getColumnType($column_name))), E_USER_NOTICE);
  913. return false;
  914. }else{
  915. $this->_inheritanceColumn = $column_name;
  916. return true;
  917. }
  918. }
  919. function getColumnsWithRegexBoundaries()
  920. {
  921. $columns = array_keys($this->getColumns());
  922. foreach ($columns as $k=>$column){
  923. $columns[$k] = '/([^\.])\b('.$column.')\b/';
  924. }
  925. return $columns;
  926. }
  927. //SELECT t.code as codigo , c.description as descripcion FROM technical_listings as t LEFT OUTER JOIN categories as c ON c.id = t.category_id
  928. /**
  929. * Works like find_all, but requires a complete SQL string. Examples:
  930. * $Post->findBySql("SELECT p.*, c.author FROM posts p, comments c WHERE p.id = c.post_id");
  931. * $Post->findBySql(array("SELECT * FROM posts WHERE author = ? AND created_on > ?", $author_id, $start_date));
  932. */
  933. function &findBySql($sql, $limit = null, $offset = null, $bindings = null)
  934. {
  935. if(!isset($this->_activeRecordHasBeenInstantiated)){
  936. return Ak::handleStaticCall();
  937. }
  938. if(is_array($sql)){
  939. $sql_query = array_shift($sql);
  940. $bindings = is_array($sql) && count($sql) > 0 ? $sql : array($sql);
  941. $sql = $sql_query;
  942. }
  943. $this->setConnection();
  944. AK_LOG_EVENTS ? $this->_startSqlBlockLog() : null;
  945. $objects = array();
  946. if(is_integer($limit)){
  947. if(is_integer($offset)){
  948. $results = !empty($bindings) ? $this->_db->SelectLimit($sql, $limit, $offset, $bindings) : $this->_db->SelectLimit($sql, $limit, $offset);
  949. }else {
  950. $results = !empty($bindings) ? $this->_db->SelectLimit($sql, $limit, -1, $bindings) : $this->_db->SelectLimit($sql, $limit);
  951. }
  952. }else{
  953. $results = !empty($bindings) ? $this->_db->Execute($sql, $bindings) : $this->_db->Execute($sql);
  954. }
  955. AK_LOG_EVENTS ? $this->_endSqlBlockLog() : null;
  956. if(!$results){
  957. AK_DEBUG ? trigger_error($this->_db->ErrorMsg(), E_USER_NOTICE) : null;
  958. }else{
  959. $objects = array();
  960. while ($record = $results->FetchRow()) {
  961. $objects[] =& $this->instantiate($this->getOnlyAvailableAtrributes($record), false);
  962. }
  963. }
  964. return $objects;
  965. }
  966. /**
  967. * This function pretends to emulate ror finders until AkActiveRecord::addMethod becomes stable on future PHP versions.
  968. * @todo use PHP5 __call method for handling the magic finder methods like findFirstByUnsenameAndPassword('bermi','pass')
  969. */
  970. function &findFirstBy()
  971. {
  972. if(!isset($this->_activeRecordHasBeenInstantiated)){
  973. return Ak::handleStaticCall();
  974. }
  975. $args = func_get_args();
  976. if($args[0] != 'first'){
  977. array_unshift($args,'first');
  978. }
  979. $result =& Ak::call_user_func_array(array(&$this,'findBy'), $args);
  980. return $result;
  981. }
  982. function &findLastBy()
  983. {
  984. if(!isset($this->_activeRecordHasBeenInstantiated)){
  985. return Ak::handleStaticCall();
  986. }
  987. $args = func_get_args();
  988. $options = array_pop($args);
  989. if(!is_array($options)){
  990. array_push($args, $options);
  991. $options = array();
  992. }
  993. $options['order'] = $this->getPrimaryKey().' DESC';
  994. array_push($args, $options);
  995. $result =& Ak::call_user_func_array(array(&$this,'findFirstBy'), $args);
  996. return $result;
  997. }
  998. function &findAllBy()
  999. {
  1000. if(!isset($this->_activeRecordHasBeenInstantiated)){
  1001. return Ak::handleStaticCall();
  1002. }
  1003. $args = func_get_args();
  1004. if($args[0] == 'first'){
  1005. array_shift($args);
  1006. }
  1007. $result =& Ak::call_user_func_array(array(&$this,'findBy'), $args);
  1008. return $result;
  1009. }
  1010. function &findBy()
  1011. {
  1012. if(!isset($this->_activeRecordHasBeenInstantiated)){
  1013. return Ak::handleStaticCall();
  1014. }
  1015. $args = func_get_args();
  1016. $sql = array_shift($args);
  1017. if($sql == 'all' || $sql == 'first'){
  1018. $fetch = $sql;
  1019. $sql = array_shift($args);
  1020. }else{
  1021. $fetch = 'all';
  1022. }
  1023. $options = array_pop($args);
  1024. if(!is_array($options)){
  1025. array_push($args, $options);
  1026. $options = array();
  1027. }
  1028. $query_values = $args;
  1029. $query_arguments_count = count($query_values);
  1030. $sql = str_replace(array('(',')','||','|','&&','&',' '),array(' ( ',' ) ',' OR ',' OR ',' AND ',' AND ',' '),$sql);
  1031. $operators = array('AND','and','(',')','&','&&','NOT','<>','OR','|','||');
  1032. $pieces = explode(' ',$sql);
  1033. $pieces = array_diff($pieces,array(' ',''));
  1034. $params = array_diff($pieces,$operators);
  1035. $operators = array_diff($pieces,$params);
  1036. $new_sql = '';
  1037. $parameter_count = 0;
  1038. $requested_args = array();
  1039. foreach ($pieces as $piece){
  1040. if(in_array($piece,$params) && $this->hasColumn($piece)){
  1041. $new_sql .= $piece.' = ? ';
  1042. $requested_args[$parameter_count] = $piece;
  1043. $parameter_count++;
  1044. }elseif (!in_array($piece,$operators)){
  1045. if(strstr($piece,':')){
  1046. $_tmp_parts = explode(':',$piece);
  1047. if($this->hasColumn($_tmp_parts[0])){
  1048. switch (strtolower($_tmp_parts[1])) {
  1049. case 'like':
  1050. case '%like%':
  1051. case 'is':
  1052. case 'has':
  1053. case 'contains':
  1054. $query_values[$parameter_count] = '%'.$query_values[$parameter_count].'%';
  1055. $new_sql .= $_tmp_parts[0]." LIKE ? ";
  1056. break;
  1057. case 'like_left':
  1058. case 'like%':
  1059. case 'begins':
  1060. case 'begins_with':
  1061. case 'starts':
  1062. case 'starts_with':
  1063. $query_values[$parameter_count] = $query_values[$parameter_count].'%';
  1064. $new_sql .= $_tmp_parts[0]." LIKEā€¦

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