PageRenderTime 62ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/system-apps/includes/ringside/apps/model/active_record.php

https://github.com/jkinner/ringside
PHP | 3257 lines | 1738 code | 201 blank | 1318 comment | 334 complexity | 893da4bfcc491e9314594838d95fdb3f MD5 | raw file
Possible License(s): LGPL-2.1, Apache-2.0

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

  1. <?php
  2. /**
  3. * File containing the ActiveRecord class
  4. *
  5. * (PHP 5)
  6. *
  7. * @package PHPonTrax
  8. * @version $Id: active_record.php 283 2007-02-17 08:54:28Z john $
  9. * @copyright (c) 2005 John Peterson
  10. *
  11. * Permission is hereby granted, free of charge, to any person obtaining
  12. * a copy of this software and associated documentation files (the
  13. * "Software"), to deal in the Software without restriction, including
  14. * without limitation the rights to use, copy, modify, merge, publish,
  15. * distribute, sublicense, and/or sell copies of the Software, and to
  16. * permit persons to whom the Software is furnished to do so, subject to
  17. * the following conditions:
  18. *
  19. * The above copyright notice and this permission notice shall be
  20. * included in all copies or substantial portions of the Software.
  21. *
  22. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  23. * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  24. * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  25. * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  26. * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  27. * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  28. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  29. */
  30. /**
  31. * Load the {@link http://pear.php.net/manual/en/package.pear.php PEAR base class}
  32. */
  33. require_once('PEAR.php');
  34. /**
  35. * Load the {@link http://pear.php.net/manual/en/package.database.mdb2.php PEAR MDB2 package}
  36. * PEAR::DB is now deprecated.
  37. * (This package(DB) been superseded by MDB2 but is still maintained for bugs and security fixes)
  38. */
  39. require_once('MDB2.php');
  40. /**
  41. * Base class for the ActiveRecord design pattern
  42. *
  43. * <p>Each subclass of this class is associated with a database table
  44. * in the Model section of the Model-View-Controller architecture.
  45. * By convention, the name of each subclass is the CamelCase singular
  46. * form of the table name, which is in the lower_case_underscore
  47. * plural notation. For example,
  48. * a table named "order_details" would be associated with a subclass
  49. * of ActiveRecord named "OrderDetail", and a table named "people"
  50. * would be associated with subclass "Person". See the tutorial
  51. * {@tutorial PHPonTrax/naming.pkg}</p>
  52. *
  53. * <p>For a discussion of the ActiveRecord design pattern, see
  54. * "Patterns of Enterprise
  55. * Application Architecture" by Martin Fowler, pp. 160-164.</p>
  56. *
  57. * <p>Unit tester: {@link ActiveRecordTest}</p>
  58. *
  59. * @tutorial PHPonTrax/ActiveRecord.cls
  60. */
  61. class ActiveRecord {
  62. /**
  63. * Reference to the database object
  64. *
  65. * Reference to the database object returned by
  66. * {@link http://pear.php.net/manual/en/package.database.mdb2.intro-connect.php PEAR MDB2::Connect()}
  67. * @var object DB
  68. * see
  69. * {@link http://pear.php.net/manual/en/package.database.mdb2.php PEAR MDB2}
  70. */
  71. protected static $db = null;
  72. /**
  73. * Description of a row in the associated table in the database
  74. *
  75. * <p>Retrieved from the RDBMS by {@link set_content_columns()}.
  76. * See {@link
  77. * http://pear.php.net/package/MDB2/docs/2.3.0/MDB2/MDB2_Driver_Reverse_Common.html#methodtableInfo
  78. * DB_common::tableInfo()} for the format. <b>NOTE:</b> Some
  79. * RDBMS's don't return all values.</p>
  80. *
  81. * <p>An additional element 'human_name' is added to each column
  82. * by {@link set_content_columns()}. The actual value contained
  83. * in each column is stored in an object variable with the name
  84. * given by the 'name' element of the column description for each
  85. * column.</p>
  86. *
  87. * <p><b>NOTE:</b>The information from the database about which
  88. * columns are primary keys is <b>not used</b>. Instead, the
  89. * primary keys in the table are listed in {@link $primary_keys},
  90. * which is maintained independently.</p>
  91. * @var string[]
  92. * @see $primary_keys
  93. * @see quoted_attributes()
  94. * @see __set()
  95. */
  96. public $content_columns = null; # info about each column in the table
  97. /**
  98. * Table Info
  99. *
  100. * Array to hold all the info about table columns. Indexed on $table_name.
  101. * @var array
  102. */
  103. public static $table_info = array();
  104. /**
  105. * Class name
  106. *
  107. * Name of the child class. (this is optional and will automatically be determined)
  108. * Normally set to the singular camel case form of the table name.
  109. * May be overridden.
  110. * @var string
  111. */
  112. public $class_name = null;
  113. /**
  114. * Table name
  115. *
  116. * Name of the table in the database associated with the subclass.
  117. * Normally set to the pluralized lower case underscore form of
  118. * the class name by the constructor. May be overridden.
  119. * @var string
  120. */
  121. public $table_name = null;
  122. /**
  123. * Table prefix
  124. *
  125. * Name to prefix to the $table_name. May be overridden.
  126. * @var string
  127. */
  128. public $table_prefix = null;
  129. /**
  130. * Database name override
  131. *
  132. * Name of the database to use, if you are not using the value
  133. * read from file config/database.ini
  134. * @var string
  135. */
  136. public $database_name = null;
  137. /**
  138. * Index into the $active_connections array
  139. *
  140. * Name of the index to use to return or set the current db connection
  141. * Mainly used if you want to connect to different databases between
  142. * different models.
  143. * @var string
  144. */
  145. public $connection_name = TRAX_ENV;
  146. /**
  147. * Stores the database settings
  148. */
  149. public static $database_settings = array();
  150. /**
  151. * Stores the active connections. Indexed on $connection_name.
  152. */
  153. public static $active_connections = array();
  154. /**
  155. * Mode to use when fetching data from database
  156. *
  157. * See {@link
  158. * http://pear.php.net/package/MDB2/docs/2.3.0/MDB2/MDB2_Driver_Common.html#methodsetFetchMode
  159. * the relevant PEAR MDB2 class documentation}
  160. * @var integer
  161. */
  162. public $fetch_mode = MDB2_FETCHMODE_ASSOC;
  163. /**
  164. * Force reconnect to database every page load
  165. *
  166. * @var boolean
  167. */
  168. public $force_reconnect = false;
  169. /**
  170. * find_all() returns an array of objects,
  171. * each object index is off of this field
  172. *
  173. * @var string
  174. */
  175. public $index_on = "id";
  176. /**
  177. * Not yet implemented (page 222 Rails books)
  178. *
  179. * @var boolean
  180. */
  181. public $lock_optimistically = true;
  182. /**
  183. * Composite custom user created objects
  184. * @var mixed
  185. */
  186. public $composed_of = null;
  187. # Table associations
  188. /**
  189. * @todo Document this variable
  190. * @var string[]
  191. */
  192. protected $has_many = null;
  193. /**
  194. * @todo Document this variable
  195. * @var string[]
  196. */
  197. protected $has_one = null;
  198. /**
  199. * @todo Document this variable
  200. * @var string[]
  201. */
  202. protected $has_and_belongs_to_many = null;
  203. /**
  204. * @todo Document this variable
  205. * @var string[]
  206. */
  207. protected $belongs_to = null;
  208. /**
  209. * @todo Document this variable
  210. * @var string[]
  211. */
  212. protected $habtm_attributes = null;
  213. /**
  214. * @todo Document this property
  215. */
  216. protected $save_associations = array();
  217. /**
  218. * Whether or not to auto save defined associations if set
  219. * @var boolean
  220. */
  221. public $auto_save_associations = true;
  222. /**
  223. * Whether this object represents a new record
  224. *
  225. * true => This object was created without reading a row from the
  226. * database, so use SQL 'INSERT' to put it in the database.
  227. * false => This object was a row read from the database, so use
  228. * SQL 'UPDATE' to update database with new values.
  229. * @var boolean
  230. */
  231. protected $new_record = true;
  232. /**
  233. * Names of automatic update timestamp columns
  234. *
  235. * When a row containing one of these columns is updated and
  236. * {@link $auto_timestamps} is true, update the contents of the
  237. * timestamp columns with the current date and time.
  238. * @see $auto_timestamps
  239. * @see $auto_create_timestamps
  240. * @var string[]
  241. */
  242. public $auto_update_timestamps = array("updated_at","updated_on");
  243. /**
  244. * Names of automatic create timestamp columns
  245. *
  246. * When a row containing one of these columns is created and
  247. * {@link $auto_timestamps} is true, store the current date and
  248. * time in the timestamp columns.
  249. * @see $auto_timestamps
  250. * @see $auto_update_timestamps
  251. * @var string[]
  252. */
  253. public $auto_create_timestamps = array("created_at","created_on");
  254. /**
  255. * Date format for use with auto timestamping
  256. *
  257. * The format for this should be compatiable with the php date() function.
  258. * http://www.php.net/date
  259. * @var string
  260. */
  261. public $date_format = "Y-m-d";
  262. /**
  263. * Time format for use with auto timestamping
  264. *
  265. * The format for this should be compatiable with the php date() function.
  266. * http://www.php.net/date
  267. * @var string
  268. */
  269. public $time_format = "H:i:s";
  270. /**
  271. * Whether to keep date/datetime fields NULL if not set
  272. *
  273. * true => If date field is not set it try to preserve NULL
  274. * false => Don't try to preserve NULL if field is already NULL
  275. * @var boolean
  276. */
  277. protected $preserve_null_dates = true;
  278. /**
  279. * SQL aggregate functions that may be applied to the associated
  280. * table.
  281. *
  282. * SQL defines aggregate functions AVG, COUNT, MAX, MIN and SUM.
  283. * Not all of these functions are implemented by all DBMS's
  284. * @var string[]
  285. */
  286. protected $aggregations = array("count","sum","avg","max","min");
  287. /**
  288. * Primary key of the associated table
  289. *
  290. * Array element(s) name the primary key column(s), as used to
  291. * specify the row to be updated or deleted. To be a primary key
  292. * a column must be listed both here and in {@link
  293. * $content_columns}. <b>NOTE:</b>This
  294. * field is maintained by hand. It is not derived from the table
  295. * description read from the database.
  296. * @var string[]
  297. * @see $content_columns
  298. * @see find()
  299. * @see find_all()
  300. * @see find_first()
  301. */
  302. public $primary_keys = array("id");
  303. /**
  304. * Default for how many rows to return from {@link find_all()}
  305. * @var integer
  306. */
  307. public $rows_per_page_default = 20;
  308. /**
  309. * Pagination how many numbers in the list << < 1 2 3 4 > >>
  310. */
  311. public $display = 10;
  312. /**
  313. * @todo Document this variable
  314. */
  315. public $pagination_count = 0;
  316. /**
  317. * Description of non-fatal errors found
  318. *
  319. * For every non-fatal error found, an element describing the
  320. * error is added to $errors. Initialized to an empty array in
  321. * {@link valid()} before validating object. When an error
  322. * message is associated with a particular attribute, the message
  323. * should be stored with the attribute name as its key. If the
  324. * message is independent of attributes, store it with a numeric
  325. * key beginning with 0.
  326. *
  327. * @var string[]
  328. * @see add_error()
  329. * @see get_errors()
  330. */
  331. public $errors = array();
  332. /**
  333. * An array with all the default error messages.
  334. */
  335. public $default_error_messages = array(
  336. 'inclusion' => "is not included in the list",
  337. 'exclusion' => "is reserved",
  338. 'invalid' => "is invalid",
  339. 'confirmation' => "doesn't match confirmation",
  340. 'accepted ' => "must be accepted",
  341. 'empty' => "can't be empty",
  342. 'blank' => "can't be blank",
  343. 'too_long' => "is too long (max is %d characters)",
  344. 'too_short' => "is too short (min is %d characters)",
  345. 'wrong_length' => "is the wrong length (should be %d characters)",
  346. 'taken' => "has already been taken",
  347. 'not_a_number' => "is not a number",
  348. 'not_an_integer' => "is not an integer"
  349. );
  350. /**
  351. * An array of all the builtin validation function calls.
  352. */
  353. protected $builtin_validation_functions = array(
  354. 'validates_acceptance_of',
  355. 'validates_confirmation_of',
  356. 'validates_exclusion_of',
  357. 'validates_format_of',
  358. 'validates_inclusion_of',
  359. 'validates_length_of',
  360. 'validates_numericality_of',
  361. 'validates_presence_of',
  362. 'validates_uniqueness_of'
  363. );
  364. /**
  365. * Whether to automatically update timestamps in certain columns
  366. *
  367. * @see $auto_create_timestamps
  368. * @see $auto_update_timestamps
  369. * @var boolean
  370. */
  371. public $auto_timestamps = true;
  372. /**
  373. * Auto insert / update $has_and_belongs_to_many tables
  374. */
  375. public $auto_save_habtm = true;
  376. /**
  377. * Auto delete $has_and_belongs_to_many associations
  378. */
  379. public $auto_delete_habtm = true;
  380. /**
  381. * Transactions (only use if your db supports it)
  382. * This is for transactions only to let query() know that a 'BEGIN' has been executed
  383. */
  384. private static $begin_executed = false;
  385. /**
  386. * Transactions (only use if your db supports it)
  387. * This will issue a rollback command if any sql fails.
  388. */
  389. public static $use_transactions = false;
  390. /**
  391. * Keep a log of queries executed if in development env
  392. */
  393. public static $query_log = array();
  394. /**
  395. * Construct an ActiveRecord object
  396. *
  397. * <ol>
  398. * <li>Establish a connection to the database</li>
  399. * <li>Find the name of the table associated with this object</li>
  400. * <li>Read description of this table from the database</li>
  401. * <li>Optionally apply update information to column attributes</li>
  402. * </ol>
  403. * @param string[] $attributes Updates to column attributes
  404. * @uses establish_connection()
  405. * @uses set_content_columns()
  406. * @uses $table_name
  407. * @uses set_table_name_using_class_name()
  408. * @uses update_attributes()
  409. */
  410. function __construct($attributes = null) {
  411. # Open the database connection
  412. $this->establish_connection();
  413. # Set $table_name
  414. if($this->table_name == null) {
  415. $this->set_table_name_using_class_name();
  416. }
  417. # Set column info
  418. if($this->table_name) {
  419. $this->set_content_columns($this->table_name);
  420. }
  421. # If $attributes array is passed in update the class with its contents
  422. if(!is_null($attributes)) {
  423. $this->update_attributes($attributes);
  424. }
  425. # If callback is defined in model run it.
  426. # this could hurt performance...
  427. if(method_exists($this, 'after_initialize')) {
  428. $this->after_initialize();
  429. }
  430. }
  431. /**
  432. * Override get() if they do $model->some_association->field_name
  433. * dynamically load the requested contents from the database.
  434. * @todo Document this API
  435. * @uses $belongs_to
  436. * @uses get_association_type()
  437. * @uses $has_and_belongs_to_many
  438. * @uses $has_many
  439. * @uses $has_one
  440. * @uses find_all_has_many()
  441. * @uses find_all_habtm()
  442. * @uses find_one_belongs_to()
  443. * @uses find_one_has_one()
  444. */
  445. function __get($key) {
  446. if($association_type = $this->get_association_type($key)) {
  447. //error_log("association_type:$association_type");
  448. switch($association_type) {
  449. case "has_many":
  450. //print("DOING HASMANY $key\n");
  451. $parameters = is_array($this->has_many) ? $this->has_many[$key] : null;
  452. //var_dump($parameters);
  453. $this->$key = $this->find_all_has_many($key, $parameters);
  454. //var_dump($this->$key);
  455. break;
  456. case "has_one":
  457. $parameters = is_array($this->has_one) ? $this->has_one[$key] : null;
  458. $this->$key = $this->find_one_has_one($key, $parameters);
  459. break;
  460. case "belongs_to":
  461. $parameters = is_array($this->belongs_to) ? $this->belongs_to[$key] : null;
  462. $this->$key = $this->find_one_belongs_to($key, $parameters);
  463. break;
  464. case "has_and_belongs_to_many":
  465. $parameters = is_array($this->has_and_belongs_to_many) ? $this->has_and_belongs_to_many[$key] : null;
  466. $this->$key = $this->find_all_habtm($key, $parameters);
  467. break;
  468. }
  469. } elseif($this->is_composite($key)) {
  470. $composite_object = $this->get_composite_object($key);
  471. if(is_object($composite_object)) {
  472. $this->$key = $composite_object;
  473. }
  474. }
  475. //echo "<pre>getting: $key = ".$this->$key."<br></pre>";
  476. return $this->$key;
  477. }
  478. /**
  479. * Store column value or description of the table format
  480. *
  481. * If called with key 'table_name', $value is stored as the
  482. * description of the table format in $content_columns.
  483. * Any other key causes an object variable with the same name to
  484. * be created and stored into. If the value of $key matches the
  485. * name of a column in content_columns, the corresponding object
  486. * variable becomes the content of the column in this row.
  487. * @uses $auto_save_associations
  488. * @uses get_association_type()
  489. * @uses set_content_columns()
  490. */
  491. function __set($key, $value) {
  492. //echo "setting: $key = $value<br>";
  493. if($key == "table_name") {
  494. $this->set_content_columns($value);
  495. # this elseif checks if first its an object if its parent is ActiveRecord
  496. } elseif(is_object($value) && get_parent_class($value) == __CLASS__ && $this->auto_save_associations) {
  497. if($association_type = $this->get_association_type($key)) {
  498. $this->save_associations[$association_type][] = $value;
  499. if($association_type == "belongs_to") {
  500. $primary_key = $value->primary_keys[0];
  501. $foreign_key = Inflector::singularize($value->table_name)."_".$primary_key;
  502. $this->$foreign_key = $value->$primary_key;
  503. }
  504. }
  505. # this elseif checks if its an array of objects and if its parent is ActiveRecord
  506. } elseif(is_array($value) && $this->auto_save_associations) {
  507. if($association_type = $this->get_association_type($key)) {
  508. $this->save_associations[$association_type][] = $value;
  509. }
  510. }
  511. // Assignment to something else, do it
  512. $this->$key = $value;
  513. }
  514. /**
  515. * Override call() to dynamically call the database associations
  516. * @todo Document this API
  517. * @uses $aggregations
  518. * @uses aggregate_all()
  519. * @uses get_association_type()
  520. * @uses $belongs_to
  521. * @uses $has_one
  522. * @uses $has_and_belongs_to_many
  523. * @uses $has_many
  524. * @uses find_all_by()
  525. * @uses find_by()
  526. */
  527. function __call($method_name, $parameters) {
  528. if(method_exists($this, $method_name)) {
  529. # If the method exists, just call it
  530. $result = call_user_func_array(array($this, $method_name), $parameters);
  531. } else {
  532. # ... otherwise, check to see if the method call is one of our
  533. # special Trax methods ...
  534. # ... first check for method names that match any of our explicitly
  535. # declared associations for this model ( e.g. public $has_many = "movies" ) ...
  536. if(is_array($parameters[0])) {
  537. $parameters = $parameters[0];
  538. }
  539. $association_type = $this->get_association_type($method_name);
  540. switch($association_type) {
  541. case "has_many":
  542. $result = $this->find_all_has_many($method_name, $parameters);
  543. break;
  544. case "has_one":
  545. $result = $this->find_one_has_one($method_name, $parameters);
  546. break;
  547. case "belongs_to":
  548. $result = $this->find_one_belongs_to($method_name, $parameters);
  549. break;
  550. case "has_and_belongs_to_many":
  551. $result = $this->find_all_habtm($method_name, $parameters);
  552. break;
  553. }
  554. # check for the [count,sum,avg,etc...]_all magic functions
  555. if(substr($method_name, -4) == "_all" && in_array(substr($method_name, 0, -4), $this->aggregations)) {
  556. //echo "calling method: $method_name<br>";
  557. $result = $this->aggregate_all($method_name, $parameters);
  558. }
  559. # check for the find_all_by_* magic functions
  560. elseif(strlen($method_name) > 11 && substr($method_name, 0, 11) == "find_all_by") {
  561. //echo "calling method: $method_name<br>";
  562. $result = $this->find_by($method_name, $parameters, "all");
  563. }
  564. # check for the find_by_* magic functions
  565. elseif(strlen($method_name) > 7 && substr($method_name, 0, 7) == "find_by") {
  566. //echo "calling method: $method_name<br>";
  567. $result = $this->find_by($method_name, $parameters);
  568. }
  569. # check for find_or_create_by_* magic functions
  570. elseif(strlen($method_name) > 17 && substr($method_name, 0, 17) == "find_or_create_by") {
  571. $result = $this->find_by($method_name, $parameters, "find_or_create");
  572. }
  573. }
  574. return $result;
  575. }
  576. /**
  577. * Find all records using a "has_and_belongs_to_many" relationship
  578. * (many-to-many with a join table in between). Note that you can also
  579. * specify an optional "paging limit" by setting the corresponding "limit"
  580. * instance variable. For example, if you want to return 10 movies from the
  581. * 5th movie on, you could set $this->movies_limit = "10, 5"
  582. *
  583. * Parameters: $this_table_name: The name of the database table that has the
  584. * one row you are interested in. E.g. genres
  585. * $other_table_name: The name of the database table that has the
  586. * many rows you are interested in. E.g. movies
  587. * Returns: An array of ActiveRecord objects. (e.g. Movie objects)
  588. * @todo Document this API
  589. */
  590. private function find_all_habtm($other_table_name, $parameters = null) {
  591. $additional_conditions = null;
  592. # Use any passed-in parameters
  593. if(!is_null($parameters)) {
  594. if(@array_key_exists("conditions", $parameters)) {
  595. $additional_conditions = " AND (".$parameters['conditions'].")";
  596. } elseif($parameters[0] != "") {
  597. $additional_conditions = " AND (".$parameters[0].")";
  598. }
  599. if(@array_key_exists("order", $parameters)) {
  600. $order = $parameters['order'];
  601. } elseif($parameters[1] != "") {
  602. $order = $parameters[1];
  603. }
  604. if(@array_key_exists("limit", $parameters)) {
  605. $limit = $parameters['limit'];
  606. } elseif($parameters[2] != "") {
  607. $limit = $parameters[2];
  608. }
  609. if(@array_key_exists("class_name", $parameters)) {
  610. $other_object_name = $parameters['class_name'];
  611. }
  612. if(@array_key_exists("join_table", $parameters)) {
  613. $join_table = $parameters['join_table'];
  614. }
  615. if(@array_key_exists("foreign_key", $parameters)) {
  616. $this_foreign_key = $parameters['foreign_key'];
  617. }
  618. if(@array_key_exists("association_foreign_key", $parameters)) {
  619. $other_foreign_key = $parameters['association_foreign_key'];
  620. }
  621. if(@array_key_exists("finder_sql", $parameters)) {
  622. $finder_sql = $parameters['finder_sql'];
  623. }
  624. }
  625. if(!is_null($other_object_name)) {
  626. $other_class_name = Inflector::camelize($other_object_name);
  627. $other_table_name = Inflector::tableize($other_object_name);
  628. } else {
  629. $other_class_name = Inflector::classify($other_table_name);
  630. }
  631. # Instantiate an object to access find_all
  632. $other_class_object = new $other_class_name();
  633. # If finder_sql is specified just use it instead of determining the joins/sql
  634. if(!is_null($finder_sql)) {
  635. $conditions = $finder_sql;
  636. $order = null;
  637. $limit = null;
  638. $joins = null;
  639. } else {
  640. # Prepare the join table name primary keys (fields) to do the join on
  641. if(is_null($join_table)) {
  642. $join_table = $this->get_join_table_name($this->table_name, $other_table_name);
  643. }
  644. # Primary keys
  645. $this_primary_key = $this->primary_keys[0];
  646. $other_primary_key = $other_class_object->primary_keys[0];
  647. # Foreign keys
  648. if(is_null($this_foreign_key)) {
  649. $this_foreign_key = Inflector::singularize($this->table_name)."_".$this_primary_key;
  650. }
  651. if(is_null($other_foreign_key)) {
  652. $other_foreign_key = Inflector::singularize($other_table_name)."_".$other_primary_key;
  653. }
  654. # Primary key value
  655. if($this->attribute_is_string($this_primary_key)) {
  656. $this_primary_key_value = "'".$this->$this_primary_key."'";
  657. } elseif(is_numeric($this->$this_primary_key)) {
  658. $this_primary_key_value = $this->$this_primary_key;
  659. } else {
  660. #$this_primary_key_value = 0;
  661. # no primary key value so just return empty array same as find_all()
  662. return array();
  663. }
  664. # Set up the SQL segments
  665. $conditions = "{$join_table}.{$this_foreign_key} = {$this_primary_key_value}".$additional_conditions;
  666. $joins = "LEFT JOIN {$join_table} ON {$other_table_name}.{$other_primary_key} = {$join_table}.{$other_foreign_key}";
  667. }
  668. # Get the list of other_class_name objects
  669. return $other_class_object->find_all($conditions, $order, $limit, $joins);
  670. }
  671. /**
  672. * Find all records using a "has_many" relationship (one-to-many)
  673. *
  674. * Parameters: $other_table_name: The name of the other table that contains
  675. * many rows relating to this object's id.
  676. * Returns: An array of ActiveRecord objects. (e.g. Contact objects)
  677. * @todo Document this API
  678. */
  679. private function find_all_has_many($other_table_name, $parameters = null) {
  680. $additional_conditions = null;
  681. # Use any passed-in parameters
  682. if(is_array($parameters)) {
  683. if(@array_key_exists("conditions", $parameters)) {
  684. $additional_conditions = " AND (".$parameters['conditions'].")";
  685. } elseif($parameters[0] != "") {
  686. $additional_conditions = " AND (".$parameters[0].")";
  687. }
  688. if(@array_key_exists("order", $parameters)) {
  689. $order = $parameters['order'];
  690. } elseif($parameters[1] != "") {
  691. $order = $parameters[1];
  692. }
  693. if(@array_key_exists("limit", $parameters)) {
  694. $limit = $parameters['limit'];
  695. } elseif($parameters[2] != "") {
  696. $limit = $parameters[2];
  697. }
  698. if(@array_key_exists("foreign_key", $parameters)) {
  699. $foreign_key = $parameters['foreign_key'];
  700. }
  701. if(@array_key_exists("class_name", $parameters)) {
  702. $other_object_name = $parameters['class_name'];
  703. }
  704. if(@array_key_exists("finder_sql", $parameters)) {
  705. $finder_sql = $parameters['finder_sql'];
  706. }
  707. }
  708. if(!is_null($other_object_name)) {
  709. $other_class_name = Inflector::camelize($other_object_name);
  710. } else {
  711. $other_class_name = Inflector::classify($other_table_name);
  712. }
  713. # Instantiate an object to access find_all
  714. $other_class_object = new $other_class_name();
  715. # If finder_sql is specified just use it instead of determining the association
  716. if(!is_null($finder_sql)) {
  717. $conditions = $finder_sql;
  718. $order = null;
  719. $limit = null;
  720. $joins = null;
  721. } else {
  722. # This class primary key
  723. $this_primary_key = $this->primary_keys[0];
  724. if(!$foreign_key) {
  725. # this should end up being like user_id or account_id but if you specified
  726. # a primaray key other than 'id' it will be like user_field
  727. $foreign_key = Inflector::singularize($this->table_name)."_".$this_primary_key;
  728. }
  729. //print("FORGIN KEY= $foreign_key\n");
  730. $foreign_key_value = $this->$this_primary_key;
  731. //print("FORGIN KEY VALUE = $foreign_key_value\n");
  732. if($other_class_object->attribute_is_string($foreign_key)) {
  733. $conditions = "{$foreign_key} = '{$foreign_key_value}'";
  734. } elseif(is_numeric($foreign_key_value)) {
  735. $conditions = "{$foreign_key} = {$foreign_key_value}";
  736. } else {
  737. #$conditions = "{$foreign_key} = 0";
  738. # no primary key value so just return empty array same as find_all()
  739. return array();
  740. }
  741. $conditions .= $additional_conditions;
  742. }
  743. # Get the list of other_class_name objects
  744. return $other_class_object->find_all($conditions, $order, $limit, $joins);
  745. }
  746. /**
  747. * Find all records using a "has_one" relationship (one-to-one)
  748. * (the foreign key being in the other table)
  749. * Parameters: $other_table_name: The name of the other table that contains
  750. * many rows relating to this object's id.
  751. * Returns: An array of ActiveRecord objects. (e.g. Contact objects)
  752. * @todo Document this API
  753. */
  754. private function find_one_has_one($other_object_name, $parameters = null) {
  755. $additional_conditions = null;
  756. # Use any passed-in parameters
  757. if(is_array($parameters)) {
  758. //echo "<pre>";print_r($parameters);
  759. if(@array_key_exists("conditions", $parameters)) {
  760. $additional_conditions = " AND (".$parameters['conditions'].")";
  761. }
  762. // Removed, WREICHARDT
  763. //elseif($parameters[0] != "") {
  764. // $additional_conditions = " AND (".$parameters[0].")";
  765. //}
  766. if(@array_key_exists("order", $parameters)) {
  767. $order = $parameters['order'];
  768. } elseif($parameters[1] != "") {
  769. $order = $parameters[1];
  770. }
  771. if(@array_key_exists("foreign_key", $parameters)) {
  772. $foreign_key = $parameters['foreign_key'];
  773. }
  774. if(@array_key_exists("class_name", $parameters)) {
  775. $other_object_name = $parameters['class_name'];
  776. }
  777. }
  778. $other_class_name = Inflector::camelize($other_object_name);
  779. # Instantiate an object to access find_all
  780. $other_class_object = new $other_class_name();
  781. # This class primary key
  782. $this_primary_key = $this->primary_keys[0];
  783. if(!$foreign_key){
  784. $foreign_key = Inflector::singularize($this->table_name)."_".$this_primary_key;
  785. }
  786. $foreign_key_value = $this->$this_primary_key;
  787. if($other_class_object->attribute_is_string($foreign_key)) {
  788. $conditions = "{$foreign_key} = '{$foreign_key_value}'";
  789. } elseif(is_numeric($foreign_key_value)) {
  790. $conditions = "{$foreign_key} = {$foreign_key_value}";
  791. } else {
  792. #$conditions = "{$foreign_key} = 0";
  793. return null;
  794. }
  795. $conditions .= $additional_conditions;
  796. # Get the list of other_class_name objects
  797. return $other_class_object->find_first($conditions, $order);
  798. }
  799. /**
  800. * Find all records using a "belongs_to" relationship (one-to-one)
  801. * (the foreign key being in the table itself)
  802. * Parameters: $other_object_name: The singularized version of a table name.
  803. * E.g. If the Contact class belongs_to the
  804. * Customer class, then $other_object_name
  805. * will be "customer".
  806. * @todo Document this API
  807. */
  808. private function find_one_belongs_to($other_object_name, $parameters = null) {
  809. $additional_conditions = null;
  810. # Use any passed-in parameters
  811. if(is_array($parameters)) {
  812. //echo "<pre>";print_r($parameters);
  813. if(@array_key_exists("conditions", $parameters)) {
  814. $additional_conditions = " AND (".$parameters['conditions'].")";
  815. } elseif($parameters[0] != "") {
  816. $additional_conditions = " AND (".$parameters[0].")";
  817. }
  818. if(@array_key_exists("order", $parameters)) {
  819. $order = $parameters['order'];
  820. } elseif($parameters[1] != "") {
  821. $order = $parameters[1];
  822. }
  823. if(@array_key_exists("foreign_key", $parameters)) {
  824. $foreign_key = $parameters['foreign_key'];
  825. }
  826. if(@array_key_exists("class_name", $parameters)) {
  827. $other_object_name = $parameters['class_name'];
  828. }
  829. }
  830. $other_class_name = Inflector::camelize($other_object_name);
  831. # Instantiate an object to access find_all
  832. $other_class_object = new $other_class_name();
  833. # This class primary key
  834. $other_primary_key = $other_class_object->primary_keys[0];
  835. if(!$foreign_key) {
  836. $foreign_key = $other_object_name."_".$other_primary_key;
  837. }
  838. $other_primary_key_value = $this->$foreign_key;
  839. if($other_class_object->attribute_is_string($other_primary_key)) {
  840. $conditions = "{$other_primary_key} = '{$other_primary_key_value}'";
  841. } elseif(is_numeric($other_primary_key_value)) {
  842. $conditions = "{$other_primary_key} = {$other_primary_key_value}";
  843. } else {
  844. #$conditions = "{$other_primary_key} = 0";
  845. return null;
  846. }
  847. $conditions .= $additional_conditions;
  848. # Get the list of other_class_name objects
  849. return $other_class_object->find_first($conditions, $order);
  850. }
  851. /**
  852. * Implement *_all() functions (SQL aggregate functions)
  853. *
  854. * Apply one of the SQL aggregate functions to a column of the
  855. * table associated with this object. The SQL aggregate
  856. * functions are AVG, COUNT, MAX, MIN and SUM. Not all DBMS's
  857. * implement all of these functions.
  858. * @param string $agrregrate_type SQL aggregate function to
  859. * apply, suffixed '_all'. The aggregate function is one of
  860. * the strings in {@link $aggregations}.
  861. * @param string[] $parameters Conditions to apply to the
  862. * aggregate function. If present, must be an array of three
  863. * strings:<ol>
  864. * <li>$parameters[0]: If present, expression to apply
  865. * the aggregate function to. Otherwise, '*' will be used.
  866. * <b>NOTE:</b>SQL uses '*' only for the COUNT() function,
  867. * where it means "including rows with NULL in this column".</li>
  868. * <li>$parameters[1]: argument to WHERE clause</li>
  869. * <li>$parameters[2]: joins??? @todo Document this parameter</li>
  870. * </ol>
  871. * @throws {@link ActiveRecordError}
  872. * @uses query()
  873. * @uses is_error()
  874. */
  875. private function aggregate_all($aggregate_type, $parameters = null) {
  876. $aggregate_type = strtoupper(substr($aggregate_type, 0, -4));
  877. ($parameters[0]) ? $field = $parameters[0] : $field = "*";
  878. $sql = "SELECT {$aggregate_type}({$field}) AS agg_result FROM {$this->table_prefix}{$this->table_name} ";
  879. # Use any passed-in parameters
  880. if(is_array($parameters[1])) {
  881. extract($parameters[1]);
  882. } elseif(!is_null($parameters)) {
  883. $conditions = $parameters[1];
  884. $order = $parameters[2];
  885. $joins = $parameters[3];
  886. }
  887. if(!empty($joins)) $sql .= " $joins ";
  888. if(!empty($conditions)) $sql .= " WHERE $conditions ";
  889. if(!empty($order)) $sql .= " ORDER BY $order ";
  890. # echo "$aggregate_type sql:$sql<br>";
  891. if($this->is_error($rs = $this->query($sql))) {
  892. $this->raise($rs->getMessage());
  893. } else {
  894. $row = $rs->fetchRow();
  895. if($row["agg_result"]) {
  896. return $row["agg_result"];
  897. }
  898. }
  899. return 0;
  900. }
  901. /**
  902. * Returns a the name of the join table that would be used for the two
  903. * tables. The join table name is decided from the alphabetical order
  904. * of the two tables. e.g. "genres_movies" because "g" comes before "m"
  905. *
  906. * Parameters: $first_table, $second_table: the names of two database tables,
  907. * e.g. "movies" and "genres"
  908. * @todo Document this API
  909. */
  910. public function get_join_table_name($first_table, $second_table) {
  911. $tables = array($first_table, $second_table);
  912. @sort($tables);
  913. return $this->table_prefix.@implode("_", $tables);
  914. }
  915. /**
  916. * Test whether this object represents a new record
  917. * @uses $new_record
  918. * @return boolean Whether this object represents a new record
  919. */
  920. function is_new_record() {
  921. return $this->new_record;
  922. }
  923. /**
  924. * get the attributes for a specific column.
  925. * @uses $content_columns
  926. * @todo Document this API
  927. */
  928. function column_for_attribute($attribute) {
  929. if(is_array($this->content_columns)) {
  930. foreach($this->content_columns as $column) {
  931. if($column['name'] == $attribute) {
  932. return $column;
  933. }
  934. }
  935. }
  936. return null;
  937. }
  938. /**
  939. * get the columns data type.
  940. * @uses column_for_attribute()
  941. * @todo Document this API
  942. */
  943. function column_type($attribute) {
  944. $column = $this->column_for_attribute($attribute);
  945. if(isset($column['type'])) {
  946. return $column['type'];
  947. }
  948. return null;
  949. }
  950. /**
  951. * Check whether a column exists in the associated table
  952. *
  953. * When called, {@link $content_columns} lists the columns in
  954. * the table described by this object.
  955. * @param string Name of the column
  956. * @return boolean true=>the column exists; false=>it doesn't
  957. * @uses content_columns
  958. */
  959. function column_attribute_exists($attribute) {
  960. if(is_array($this->content_columns)) {
  961. foreach($this->content_columns as $column) {
  962. if($column['name'] == $attribute) {
  963. return true;
  964. }
  965. }
  966. }
  967. return false;
  968. }
  969. /**
  970. * Get contents of one column of record selected by id and table
  971. *
  972. * When called, {@link $id} identifies one record in the table
  973. * identified by {@link $table}. Fetch from the database the
  974. * contents of column $column of this record.
  975. * @param string Name of column to retrieve
  976. * @uses $db
  977. * @uses column_attribute_exists()
  978. * @throws {@link ActiveRecordError}
  979. * @uses is_error()
  980. */
  981. function send($column) {
  982. if($this->column_attribute_exists($column) && ($conditions = $this->get_primary_key_conditions())) {
  983. # Run the query to grab a specific columns value.
  984. $sql = "SELECT {$column} FROM {$this->table_prefix}{$this->table_name} WHERE {$conditions}";
  985. $this->log_query($sql);
  986. $result = self::$db->queryOne($sql);
  987. if($this->is_error($result)) {
  988. $this->raise($result->getMessage());
  989. }
  990. }
  991. return $result;
  992. }
  993. /**
  994. * Only used if you want to do transactions and your db supports transactions
  995. *
  996. * @uses $db
  997. * @todo Document this API
  998. */
  999. function begin() {
  1000. self::$db->query("BEGIN");
  1001. $this->begin_executed = true;
  1002. }
  1003. /**
  1004. * Only used if you want to do transactions and your db supports transactions
  1005. *
  1006. * @uses $db
  1007. * @todo Document this API
  1008. */
  1009. function commit() {
  1010. self::$db->query("COMMIT");
  1011. $this->begin_executed = false;
  1012. }
  1013. /**
  1014. * Only used if you want to do transactions and your db supports transactions
  1015. *
  1016. * @uses $db
  1017. * @todo Document this API
  1018. */
  1019. function rollback() {
  1020. self::$db->query("ROLLBACK");
  1021. }
  1022. /**
  1023. * Perform an SQL query and return the results
  1024. *
  1025. * @param string $sql SQL for the query command
  1026. * @return $mdb2->query {@link http://pear.php.net/manual/en/package.database.mdb2.intro-query.php}
  1027. * Result set from query
  1028. * @uses $db
  1029. * @uses is_error()
  1030. * @uses log_query()
  1031. * @throws {@link ActiveRecordError}
  1032. */
  1033. function query($sql) {
  1034. //print($sql."\n");
  1035. # Run the query
  1036. $this->log_query($sql);
  1037. $rs =& self::$db->query($sql);
  1038. if ($this->is_error($rs)) {
  1039. if(self::$use_transactions && self::$begin_executed) {
  1040. $this->rollback();
  1041. }
  1042. $this->raise($rs->getMessage());
  1043. }
  1044. return $rs;
  1045. }
  1046. /**
  1047. * Implement find_by_*() and =_* methods
  1048. *
  1049. * Converts a method name beginning 'find_by_' or 'find_all_by_'
  1050. * into a query for rows matching the rest of the method name and
  1051. * the arguments to the function. The part of the method name
  1052. * after '_by' is parsed for columns and logical relationships
  1053. * (AND and OR) to match. For example, the call
  1054. * find_by_fname('Ben')
  1055. * is converted to
  1056. * SELECT * ... WHERE fname='Ben'
  1057. * and the call
  1058. * find_by_fname_and_lname('Ben','Dover')
  1059. * is converted to
  1060. * SELECT * ... WHERE fname='Ben' AND lname='Dover'
  1061. *
  1062. * @uses find_all()
  1063. * @uses find_first()
  1064. */
  1065. private function find_by($method_name, $parameters, $find_type = null) {
  1066. //print("find_by $method_name, $parameters, $find_type");
  1067. if($find_type == "find_or_create") {
  1068. $explode_len = 18;
  1069. } elseif($find_type == "all") {
  1070. $explode_len = 12;
  1071. } else {
  1072. $explode_len = 8;
  1073. }
  1074. $method_name = substr(strtolower($method_name), $explode_len);
  1075. $method_parts = explode("|", str_replace("_and_", "|AND|", $method_name));
  1076. if(count($method_parts)) {
  1077. $options = array();
  1078. $create_fields = array();
  1079. $param_index = 0;
  1080. foreach($method_parts as $part) {
  1081. if($part == "AND") {
  1082. $conditions .= " AND ";
  1083. $param_index++;
  1084. } else {
  1085. $value = $this->attribute_is_string($part) ?
  1086. "'".$parameters[$param_index]."'" :
  1087. $parameters[$param_index];
  1088. $create_fields[$part] = $parameters[$param_index];
  1089. $conditions .= "{$part} = {$value}";
  1090. }
  1091. }
  1092. # If last param exists and is a string set it as the ORDER BY clause
  1093. # or if the last param is an array set it as the $options
  1094. if($last_param = $parameters[++$param_index]) {
  1095. if(is_string($last_param)) {
  1096. $options['order'] = $last_param;
  1097. } elseif(is_array($last_param)) {
  1098. $options = $last_param;
  1099. }
  1100. }
  1101. # Set the conditions
  1102. if($options['conditions'] && $conditions) {
  1103. $options['conditions'] = "(".$options['conditions'].") AND (".$conditions.")";
  1104. } else {
  1105. $options['conditions'] = $conditions;
  1106. }
  1107. # Now do the actual find with condtions from above
  1108. if($find_type == "find_or_create") {
  1109. # see if we can find a record with specified parameters
  1110. $object = $this->find($options);
  1111. if(is_object($object)) {
  1112. # we found a record with the specified parameters so return it
  1113. return $object;
  1114. } elseif(count($create_fields)) {
  1115. # can't find a record with specified parameters so create a new record
  1116. # and return new object
  1117. foreach($create_fields as $field => $value) {
  1118. $this->$field = $value;
  1119. }
  1120. $this->save();
  1121. return $this->find($options);
  1122. }
  1123. } elseif($find_type == "all") {
  1124. return $this->find_all($options);
  1125. } else {
  1126. return $this->find($options);
  1127. }
  1128. }
  1129. }
  1130. /**
  1131. * Builds a sql statement.
  1132. *
  1133. * @uses $rows_per_page_default
  1134. * @uses $rows_per_page
  1135. * @uses $offset
  1136. * @uses $page
  1137. *
  1138. */
  1139. function build_sql($conditions = null, $order = null, $limit = null, $joins = null) {
  1140. $offset = null;
  1141. $per_page = null;
  1142. $select = null;
  1143. # this is if they passed in an associative array to emulate
  1144. # named parameters.
  1145. if(is_array($conditions)) {
  1146. if(@array_key_exists("per_page", $conditions) && !is_numeric($conditions['per_page'])) {
  1147. extract($conditions);
  1148. $per_page = 0;
  1149. } else {
  1150. extract($conditions);
  1151. }
  1152. # If conditions wasn't in the array set it to null
  1153. if(is_array($conditions)) {
  1154. $conditions = null;
  1155. }
  1156. }
  1157. # Test source of SQL for query
  1158. if(stristr($conditions, "SELECT")) {
  1159. # SQL completely specified in argument so use it as is
  1160. $sql = $conditions;
  1161. } else {
  1162. # If select fields not specified just do a SELECT *
  1163. if(is_null($select)) {
  1164. $select = "*";
  1165. }
  1166. # SQL will be built from specifications in argument
  1167. $sql = "SELECT {$select} FROM {$this->table_prefix}{$this->table_name} ";
  1168. # If join specified, include it
  1169. if(!is_null($joins)) {
  1170. $sql .= " $joins ";
  1171. }
  1172. # If conditions specified, include them
  1173. if(!is_null($conditions)) {
  1174. $sql .= "WHERE $conditions ";
  1175. }
  1176. # If ordering specified, include it
  1177. if(!is_null($order)) {
  1178. $sql .= "ORDER BY $order ";
  1179. }
  1180. # Is output to be generated in pages?
  1181. if(is_numeric($limit) || is_numeric($offset) || is_numeric($per_page)) {
  1182. if(is_numeric($limit)) {
  1183. $this->rows_per_page = $limit;
  1184. }
  1185. if(is_numeric($per_page)) {
  1186. $this->rows_per_page = $per_page;
  1187. }
  1188. # Default for rows_per_page:
  1189. if ($this->rows_per_page <= 0) {
  1190. $this->rows_per_page = $this->rows_per_page_default;
  1191. }
  1192. # Only use request's page if you are calling from find_all_with_pagination() and if it is int
  1193. if(strval(intval($_REQUEST['page'])) == $_REQUEST['page']) {
  1194. $this->page = $_REQUEST['page'];
  1195. }
  1196. if($this->page <= 0) {
  1197. $this->page = 1;
  1198. }
  1199. # Set the LIMIT string segment for the SQL
  1200. if(is_null($offset)) {
  1201. $offset = ($this->page - 1) * $this->rows_per_page;
  1202. }
  1203. $sql .= "LIMIT {$this->rows_per_page} OFFSET {$offset}";
  1204. # $sql .= "LIMIT $offset, $this->rows_per_page";
  1205. # Set number of total pages in result set
  1206. if($count = $this->count_all($this->primary_keys[0], $conditions, $joins)) {
  1207. $this->pagination_count = $count;
  1208. $this->pages = (($count % $this->rows_per_page) == 0)
  1209. ? $count / $this->rows_per_page
  1210. : floor($count / $this->rows_per_page) + 1;
  1211. }
  1212. }
  1213. }
  1214. //print("SQL:$sql\n");
  1215. return $sql;
  1216. }
  1217. /**
  1218. * Return rows selected by $conditions
  1219. *
  1220. * If no rows match, an empty array is returned.
  1221. * @p…

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