PHPonTrax
[ class tree: PHPonTrax ] [ index: PHPonTrax ] [ all elements ]

Source for file active_record.php

Documentation is available at active_record.php

  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. /**
  32.  *  Load the {@link http://pear.php.net/manual/en/package.pear.php PEAR base class}
  33.  */
  34. require_once('PEAR.php');
  35.  
  36. /**
  37.  *  Load the {@link http://pear.php.net/manual/en/package.database.mdb2.php PEAR MDB2 package}
  38.  *  PEAR::DB is now deprecated.
  39.  *  (This package(DB) been superseded by MDB2 but is still maintained for bugs and security fixes)
  40.  */
  41. require_once('MDB2.php');
  42.  
  43. /**
  44.  *  Base class for the ActiveRecord design pattern
  45.  *
  46.  *  <p>Each subclass of this class is associated with a database table
  47.  *  in the Model section of the Model-View-Controller architecture.
  48.  *  By convention, the name of each subclass is the CamelCase singular
  49.  *  form of the table name, which is in the lower_case_underscore
  50.  *  plural notation.  For example,
  51.  *  a table named "order_details" would be associated with a subclass
  52.  *  of ActiveRecord named "OrderDetail", and a table named "people"
  53.  *  would be associated with subclass "Person".  See the tutorial
  54.  *  {@tutorial PHPonTrax/naming.pkg}</p>
  55.  *
  56.  *  <p>For a discussion of the ActiveRecord design pattern, see
  57.  *  "Patterns of Enterprise
  58.  *  Application Architecture" by Martin Fowler, pp. 160-164.</p>
  59.  *
  60.  *  <p>Unit tester: {@link ActiveRecordTest}</p>
  61.  *
  62.  *  @tutorial PHPonTrax/ActiveRecord.cls
  63.  */
  64. class ActiveRecord {
  65.  
  66.     /**
  67.      *  Reference to the database object
  68.      *
  69.      *  Reference to the database object returned by
  70.      *  {@link http://pear.php.net/manual/en/package.database.mdb2.intro-connect.php  PEAR MDB2::Connect()}
  71.      *  @var object DB 
  72.      *   see
  73.      *   {@link http://pear.php.net/manual/en/package.database.mdb2.php PEAR MDB2}
  74.      */
  75.     protected static $db null;
  76.  
  77.     /**
  78.      *  Description of a row in the associated table in the database
  79.      *
  80.      *  <p>Retrieved from the RDBMS by {@link set_content_columns()}.
  81.      *  See {@link }
  82.      *  http://pear.php.net/package/MDB2/docs/2.3.0/MDB2/MDB2_Driver_Reverse_Common.html#methodtableInfo
  83.      *  DB_common::tableInfo()} for the format.  <b>NOTE:</b> Some
  84.      *  RDBMS's don't return all values.</p>
  85.      *
  86.      *  <p>An additional element 'human_name' is added to each column
  87.      *  by {@link set_content_columns()}.  The actual value contained
  88.      *  in each column is stored in an object variable with the name
  89.      *  given by the 'name' element of the column description for each
  90.      *  column.</p>
  91.      *
  92.      *  <p><b>NOTE:</b>The information from the database about which
  93.      *  columns are primary keys is <b>not used</b>.  Instead, the
  94.      *  primary keys in the table are listed in {@link $primary_keys},
  95.      *  which is maintained independently.</p>
  96.      *  @var string[] 
  97.      *  @see $primary_keys
  98.      *  @see quoted_attributes()
  99.      *  @see __set()
  100.      */
  101.     public $content_columns = null# info about each column in the table
  102.  
  103.     /**
  104.      *  Table Info
  105.      *
  106.      *  Array to hold all the info about table columns.  Indexed on $table_name.
  107.      *  @var array 
  108.      */    
  109.     public static $table_info array();
  110.  
  111.     /**
  112.      *  Class name
  113.      *
  114.      *  Name of the child class. (this is optional and will automatically be determined)
  115.      *  Normally set to the singular camel case form of the table name.
  116.      *  May be overridden.
  117.      *  @var string 
  118.      */
  119.     public $class_name = null
  120.  
  121.     /**
  122.      *  Table name
  123.      *
  124.      *  Name of the table in the database associated with the subclass.
  125.      *  Normally set to the pluralized lower case underscore form of
  126.      *  the class name by the constructor.  May be overridden.
  127.      *  @var string 
  128.      */
  129.     public $table_name = null;
  130.     
  131.     /**
  132.      *  Table prefix
  133.      *
  134.      *  Name to prefix to the $table_name. May be overridden.
  135.      *  @var string 
  136.      */
  137.     public $table_prefix = null;
  138.  
  139.     /**
  140.      *  Database name override
  141.      *
  142.      *  Name of the database to use, if you are not using the value
  143.      *  read from file config/database.ini
  144.      *  @var string 
  145.      */
  146.     public $database_name = null;
  147.     
  148.     /**
  149.      *  Index into the $active_connections array
  150.      *
  151.      *  Name of the index to use to return or set the current db connection
  152.      *  Mainly used if you want to connect to different databases between
  153.      *  different models.
  154.      *  @var string 
  155.      */    
  156.     public $connection_name = TRAX_ENV;
  157.  
  158.     /**
  159.      * Stores the database settings
  160.      */
  161.     public static $database_settings array();
  162.     
  163.     /**
  164.      * Stores the active connections. Indexed on $connection_name.
  165.      */
  166.     public static $active_connections array();
  167.  
  168.     /**
  169.      *  Mode to use when fetching data from database
  170.      *
  171.      *  See {@link }
  172.      *  http://pear.php.net/package/MDB2/docs/2.3.0/MDB2/MDB2_Driver_Common.html#methodsetFetchMode
  173.      *  the relevant PEAR MDB2 class documentation}
  174.      *  @var integer 
  175.      */
  176.     public $fetch_mode = MDB2_FETCHMODE_ASSOC;
  177.  
  178.     /**
  179.      *  Force reconnect to database every page load
  180.      *
  181.      *  @var boolean 
  182.      */
  183.     public $force_reconnect = false;
  184.  
  185.     /**
  186.      *  find_all() returns an array of objects,
  187.      *  each object index is off of this field
  188.      *
  189.      *  @var string 
  190.      */    
  191.     public $index_on = "id"
  192.  
  193.     /**
  194.      *  Not yet implemented (page 222 Rails books)
  195.      *
  196.      *  @var boolean 
  197.      */    
  198.     public $lock_optimistically = true;
  199.     
  200.     /**
  201.      *  Composite custom user created objects
  202.      *  @var mixed 
  203.      */    
  204.     public $composed_of = null;
  205.  
  206.     # Table associations
  207.     /**
  208.      *  @todo Document this variable
  209.      *  @var string[] 
  210.      */
  211.     protected $has_many = null;
  212.  
  213.     /**
  214.      *  @todo Document this variable
  215.      *  @var string[] 
  216.      */
  217.     protected $has_one = null;
  218.  
  219.     /**
  220.      *  @todo Document this variable
  221.      *  @var string[] 
  222.      */
  223.     protected $has_and_belongs_to_many = null;
  224.  
  225.     /**
  226.      *  @todo Document this variable
  227.      *  @var string[] 
  228.      */
  229.     protected $belongs_to = null;
  230.  
  231.     /**
  232.      *  @todo Document this variable
  233.      *  @var string[] 
  234.      */
  235.     protected $habtm_attributes = null;
  236.  
  237.     /**
  238.      *  @todo Document this property
  239.      */
  240.     protected $save_associations = array();
  241.     
  242.     /**
  243.      *  Whether or not to auto save defined associations if set
  244.      *  @var boolean 
  245.      */
  246.     public $auto_save_associations = true;
  247.  
  248.     /**
  249.      *  Whether this object represents a new record
  250.      *
  251.      *  true => This object was created without reading a row from the
  252.      *          database, so use SQL 'INSERT' to put it in the database.
  253.      *  false => This object was a row read from the database, so use
  254.      *           SQL 'UPDATE' to update database with new values.
  255.      *  @var boolean 
  256.      */
  257.     protected $new_record = true;
  258.  
  259.     /**
  260.      *  Names of automatic update timestamp columns
  261.      *
  262.      *  When a row containing one of these columns is updated and
  263.      *  {@link $auto_timestamps} is true, update the contents of the
  264.      *  timestamp columns with the current date and time.
  265.      *  @see $auto_timestamps
  266.      *  @see $auto_create_timestamps
  267.      *  @var string[] 
  268.      */
  269.     public $auto_update_timestamps = array("updated_at","updated_on");
  270.  
  271.     /**
  272.      *  Names of automatic create timestamp columns
  273.      *
  274.      *  When a row containing one of these columns is created and
  275.      *  {@link $auto_timestamps} is true, store the current date and
  276.      *  time in the timestamp columns.
  277.      *  @see $auto_timestamps
  278.      *  @see $auto_update_timestamps
  279.      *  @var string[] 
  280.      */
  281.     public $auto_create_timestamps = array("created_at","created_on");
  282.  
  283.     /**
  284.      *  Date format for use with auto timestamping
  285.      *
  286.      *  The format for this should be compatiable with the php date() function.
  287.      *  http://www.php.net/date
  288.      *  @var string 
  289.      */
  290.      public $date_format = "Y-m-d";
  291.  
  292.     /**
  293.      *  Time format for use with auto timestamping
  294.      *
  295.      *  The format for this should be compatiable with the php date() function.
  296.      *  http://www.php.net/date
  297.      *  @var string 
  298.      */    
  299.      public $time_format = "H:i:s";
  300.        
  301.     /**
  302.      *  Whether to keep date/datetime fields NULL if not set
  303.      *
  304.      *  true => If date field is not set it try to preserve NULL
  305.      *  false => Don't try to preserve NULL if field is already NULL
  306.      *  @var boolean 
  307.      */       
  308.      protected $preserve_null_dates = true;
  309.  
  310.     /**
  311.      *  SQL aggregate functions that may be applied to the associated
  312.      *  table.
  313.      *
  314.      *  SQL defines aggregate functions AVG, COUNT, MAX, MIN and SUM.
  315.      *  Not all of these functions are implemented by all DBMS's
  316.      *  @var string[] 
  317.      */
  318.     protected $aggregations = array("count","sum","avg","max","min");
  319.                
  320.     /**
  321.      *  Primary key of the associated table
  322.      *
  323.      *  Array element(s) name the primary key column(s), as used to
  324.      *  specify the row to be updated or deleted.  To be a primary key
  325.      *  a column must be listed both here and in {@link }
  326.      *  $content_columns}.  <b>NOTE:</b>This
  327.      *  field is maintained by hand.  It is not derived from the table
  328.      *  description read from the database.
  329.      *  @var string[] 
  330.      *  @see $content_columns
  331.      *  @see find()
  332.      *  @see find_all()
  333.      *  @see find_first()
  334.      */
  335.     public $primary_keys = array("id");
  336.  
  337.     /**
  338.      *  Default for how many rows to return from {@link find_all()}
  339.      *  @var integer 
  340.      */
  341.     public $rows_per_page_default = 20;
  342.  
  343.     /**
  344.      *  Pagination how many numbers in the list << < 1 2 3 4 > >>
  345.      */
  346.     public $display = 10;
  347.  
  348.     /**
  349.      *  @todo Document this variable
  350.      */    
  351.     public $pagination_count = 0;
  352.  
  353.     /**
  354.      *  Description of non-fatal errors found
  355.      *
  356.      *  For every non-fatal error found, an element describing the
  357.      *  error is added to $errors.  Initialized to an empty array in
  358.      *  {@link valid()} before validating object.  When an error
  359.      *  message is associated with a particular attribute, the message
  360.      *  should be stored with the attribute name as its key.  If the
  361.      *  message is independent of attributes, store it with a numeric
  362.      *  key beginning with 0.
  363.      *  
  364.      *  @var string[] 
  365.      *  @see add_error()
  366.      *  @see get_errors()
  367.      */
  368.     public $errors = array();
  369.  
  370.     /**
  371.      * An array with all the default error messages.
  372.      */
  373.     public $default_error_messages = array(
  374.         'inclusion' => "is not included in the list",
  375.         'exclusion' => "is reserved",
  376.         'invalid' => "is invalid",
  377.         'confirmation' => "doesn't match confirmation",
  378.         'accepted ' => "must be accepted",
  379.         'empty' => "can't be empty",
  380.         'blank' => "can't be blank",
  381.         'too_long' => "is too long (max is %d characters)",
  382.         'too_short' => "is too short (min is %d characters)",
  383.         'wrong_length' => "is the wrong length (should be %d characters)",
  384.         'taken' => "has already been taken",
  385.         'not_a_number' => "is not a number",
  386.         'not_an_integer' => "is not an integer"
  387.     );
  388.  
  389.     /**
  390.      * An array of all the builtin validation function calls.
  391.      */    
  392.     protected $builtin_validation_functions = array(
  393.         'validates_acceptance_of',
  394.         'validates_confirmation_of',
  395.         'validates_exclusion_of',        
  396.         'validates_format_of',
  397.         'validates_inclusion_of',        
  398.         'validates_length_of',
  399.         'validates_numericality_of',        
  400.         'validates_presence_of',        
  401.         'validates_uniqueness_of'
  402.     );
  403.  
  404.     /**
  405.      *  Whether to automatically update timestamps in certain columns
  406.      *
  407.      *  @see $auto_create_timestamps
  408.      *  @see $auto_update_timestamps
  409.      *  @var boolean 
  410.      */
  411.     public $auto_timestamps = true;
  412.  
  413.     /**
  414.      *  Auto insert / update $has_and_belongs_to_many tables
  415.      */
  416.     public $auto_save_habtm = true;
  417.  
  418.     /**
  419.      *  Auto delete $has_and_belongs_to_many associations
  420.      */    
  421.     public $auto_delete_habtm = true
  422.  
  423.     /**
  424.      *  Transactions (only use if your db supports it)
  425.      *  This is for transactions only to let query() know that a 'BEGIN' has been executed
  426.      */
  427.     private static $begin_executed false;
  428.  
  429.     /**
  430.      *  Transactions (only use if your db supports it)
  431.      *  This will issue a rollback command if any sql fails.
  432.      */
  433.     public static $use_transactions false
  434.     
  435.     /**
  436.      *  Keep a log of queries executed if in development env
  437.      */    
  438.     public static $query_log array();
  439.  
  440.     /**
  441.      *  Construct an ActiveRecord object
  442.      *
  443.      *  <ol>
  444.      *    <li>Establish a connection to the database</li>
  445.      *    <li>Find the name of the table associated with this object</li>
  446.      *    <li>Read description of this table from the database</li>
  447.      *    <li>Optionally apply update information to column attributes</li>
  448.      *  </ol>
  449.      *  @param string[] $attributes Updates to column attributes
  450.      *  @uses establish_connection()
  451.      *  @uses set_content_columns()
  452.      *  @uses $table_name
  453.      *  @uses set_table_name_using_class_name()
  454.      *  @uses update_attributes()
  455.      */
  456.     function __construct($attributes null
  457.         # Open the database connection
  458.         $this->establish_connection();
  459.  
  460.         # Set $table_name
  461.         if($this->table_name == null{
  462.             $this->set_table_name_using_class_name();
  463.         }
  464.  
  465.         # Set column info
  466.         if($this->table_name{
  467.             $this->set_content_columns($this->table_name);
  468.         }
  469.  
  470.         # If $attributes array is passed in update the class with its contents
  471.         if(!is_null($attributes)) {
  472.             $this->update_attributes($attributes);
  473.         }
  474.         
  475.         # If callback is defined in model run it.
  476.         # this could hurt performance...
  477.         if(method_exists($this'after_initialize')) {
  478.             $this->after_initialize();    
  479.         }        
  480.     }
  481.  
  482.     /**
  483.      *  Override get() if they do $model->some_association->field_name
  484.      *  dynamically load the requested contents from the database.
  485.      *  @todo Document this API
  486.      *  @uses $belongs_to
  487.      *  @uses get_association_type()
  488.      *  @uses $has_and_belongs_to_many
  489.      *  @uses $has_many
  490.      *  @uses $has_one
  491.      *  @uses find_all_has_many()
  492.      *  @uses find_all_habtm()
  493.      *  @uses find_one_belongs_to()
  494.      *  @uses find_one_has_one()
  495.      */
  496.     function __get($key{
  497.         if($association_type $this->get_association_type($key)) {
  498.             //error_log("association_type:$association_type");
  499.             switch($association_type{
  500.                 case "has_many":
  501.                     $parameters is_array($this->has_many$this->has_many[$keynull;
  502.                     $this->$key $this->find_all_has_many($key$parameters);
  503.                     break;
  504.                 case "has_one":
  505.                     $parameters is_array($this->has_one$this->has_one[$keynull;
  506.                     $this->$key $this->find_one_has_one($key$parameters);
  507.                     break;
  508.                 case "belongs_to":
  509.                     $parameters is_array($this->belongs_to$this->belongs_to[$keynull;
  510.                     $this->$key $this->find_one_belongs_to($key$parameters);
  511.                     break;
  512.                 case "has_and_belongs_to_many":  
  513.                     $parameters is_array($this->has_and_belongs_to_many$this->has_and_belongs_to_many[$keynull;
  514.                     $this->$key $this->find_all_habtm($key$parameters)
  515.                     break;            
  516.             }        
  517.         elseif($this->is_composite($key)) {            
  518.             $composite_object $this->get_composite_object($key);
  519.             if(is_object($composite_object)) {
  520.                 $this->$key $composite_object;    
  521.             }                                
  522.         
  523.         //echo "<pre>getting: $key = ".$this->$key."<br></pre>";
  524.         return $this->$key;
  525.     }
  526.  
  527.     /**
  528.      *  Store column value or description of the table format
  529.      *
  530.      *  If called with key 'table_name', $value is stored as the
  531.      *  description of the table format in $content_columns.
  532.      *  Any other key causes an object variable with the same name to
  533.      *  be created and stored into.  If the value of $key matches the
  534.      *  name of a column in content_columns, the corresponding object
  535.      *  variable becomes the content of the column in this row.
  536.      *  @uses $auto_save_associations
  537.      *  @uses get_association_type()
  538.      *  @uses set_content_columns()
  539.      */
  540.     function __set($key$value{
  541.         //echo "setting: $key = $value<br>";
  542.         if($key == "table_name"{
  543.             $this->set_content_columns($value);           
  544.           # this elseif checks if first its an object if its parent is ActiveRecord
  545.         elseif(is_object($value&& get_parent_class($value== __CLASS__ && $this->auto_save_associations{
  546.             if($association_type $this->get_association_type($key)) {
  547.                 $this->save_associations[$association_type][$value;
  548.                 if($association_type == "belongs_to"{
  549.                     $primary_key $value->primary_keys[0];
  550.                     $foreign_key Inflector::singularize($value->table_name)."_".$primary_key;
  551.                     $this->$foreign_key $value->$primary_key
  552.                 }
  553.             }
  554.             # this elseif checks if its an array of objects and if its parent is ActiveRecord                
  555.         elseif(is_array($value&& $this->auto_save_associations{
  556.             if($association_type $this->get_association_type($key)) {
  557.                 $this->save_associations[$association_type][$value;
  558.             }
  559.         }       
  560.         
  561.         //  Assignment to something else, do it
  562.         $this->$key $value;
  563.     }
  564.  
  565.     /**
  566.      *  Override call() to dynamically call the database associations
  567.      *  @todo Document this API
  568.      *  @uses $aggregations
  569.      *  @uses aggregate_all()
  570.      *  @uses get_association_type()
  571.      *  @uses $belongs_to
  572.      *  @uses $has_one
  573.      *  @uses $has_and_belongs_to_many
  574.      *  @uses $has_many
  575.      *  @uses find_all_by()
  576.      *  @uses find_by()
  577.      */
  578.     function __call($method_name$parameters{
  579.         if(method_exists($this$method_name)) {
  580.             # If the method exists, just call it
  581.             $result call_user_func_array(array($this$method_name)$parameters);
  582.         else {
  583.             # ... otherwise, check to see if the method call is one of our
  584.             # special Trax methods ...
  585.             # ... first check for method names that match any of our explicitly
  586.             # declared associations for this model ( e.g. public $has_many = "movies" ) ...
  587.             if(is_array($parameters[0])) {
  588.                 $parameters $parameters[0];    
  589.             }
  590.             $association_type $this->get_association_type($method_name);
  591.             switch($association_type{
  592.                 case "has_many":
  593.                     $result $this->find_all_has_many($method_name$parameters);
  594.                     break;
  595.                 case "has_one":
  596.                     $result $this->find_one_has_one($method_name$parameters);
  597.                     break;
  598.                 case "belongs_to":
  599.                     $result $this->find_one_belongs_to($method_name$parameters);
  600.                     break;
  601.                 case "has_and_belongs_to_many":  
  602.                     $result $this->find_all_habtm($method_name$parameters)
  603.                     break;            
  604.             }
  605.  
  606.             # check for the [count,sum,avg,etc...]_all magic functions
  607.             if(substr($method_name-4== "_all" && in_array(substr($method_name0-4)$this->aggregations)) {
  608.                 //echo "calling method: $method_name<br>";
  609.                 $result $this->aggregate_all($method_name$parameters);
  610.             }
  611.             # check for the find_all_by_* magic functions
  612.             elseif(strlen($method_name11 && substr($method_name011== "find_all_by"{
  613.                 //echo "calling method: $method_name<br>";
  614.                 $result $this->find_by($method_name$parameters"all");
  615.             }
  616.             # check for the find_by_* magic functions
  617.             elseif(strlen($method_name&& substr($method_name07== "find_by"{
  618.                 //echo "calling method: $method_name<br>";
  619.                 $result $this->find_by($method_name$parameters);
  620.             }
  621.             # check for find_or_create_by_* magic functions
  622.             elseif(strlen($method_name17 && substr($method_name017== "find_or_create_by"{
  623.                 $result $this->find_by($method_name$parameters"find_or_create");        
  624.             }
  625.         }
  626.         return $result;
  627.     }
  628.     
  629.     /**
  630.      *  Find all records using a "has_and_belongs_to_many" relationship
  631.      * (many-to-many with a join table in between).  Note that you can also
  632.      *  specify an optional "paging limit" by setting the corresponding "limit"
  633.      *  instance variable.  For example, if you want to return 10 movies from the
  634.      *  5th movie on, you could set $this->movies_limit = "10, 5"
  635.      *
  636.      *  Parameters: $this_table_name:  The name of the database table that has the
  637.      *                                 one row you are interested in.  E.g. genres
  638.      *              $other_table_name: The name of the database table that has the
  639.      *                                 many rows you are interested in.  E.g. movies
  640.      *  Returns: An array of ActiveRecord objects. (e.g. Movie objects)
  641.      *  @todo Document this API
  642.      */
  643.     private function find_all_habtm($other_table_name$parameters null{
  644.         $additional_conditions null;
  645.         # Use any passed-in parameters
  646.         if(!is_null($parameters)) {
  647.             if(@array_key_exists("conditions"$parameters)) {
  648.                 $additional_conditions " AND (".$parameters['conditions'].")";
  649.             elseif($parameters[0!= ""{
  650.                 $additional_conditions " AND (".$parameters[0].")";
  651.             }
  652.             if(@array_key_exists("order"$parameters)) {
  653.                 $order $parameters['order'];
  654.             elseif($parameters[1!= ""{
  655.                 $order $parameters[1];
  656.             }
  657.             if(@array_key_exists("limit"$parameters)) {
  658.                 $limit $parameters['limit'];
  659.             elseif($parameters[2!= ""{
  660.                 $limit $parameters[2];
  661.             }    
  662.             if(@array_key_exists("class_name"$parameters)) {
  663.                 $other_object_name $parameters['class_name'];
  664.             }            
  665.             if(@array_key_exists("join_table"$parameters)) {
  666.                 $join_table $parameters['join_table'];
  667.             
  668.             if(@array_key_exists("foreign_key"$parameters)) {
  669.                 $this_foreign_key $parameters['foreign_key'];
  670.             }
  671.             if(@array_key_exists("association_foreign_key"$parameters)) {
  672.                 $other_foreign_key $parameters['association_foreign_key'];
  673.             }            
  674.             if(@array_key_exists("finder_sql"$parameters)) {
  675.                 $finder_sql $parameters['finder_sql'];
  676.             }   
  677.         }
  678.         
  679.         if(!is_null($other_object_name)) {
  680.             $other_class_name Inflector::camelize($other_object_name);    
  681.             $other_table_name Inflector::tableize($other_object_name);    
  682.         else {
  683.             $other_class_name Inflector::classify($other_table_name);
  684.         }
  685.         
  686.         # Instantiate an object to access find_all
  687.         $other_class_object new $other_class_name();
  688.  
  689.         # If finder_sql is specified just use it instead of determining the joins/sql
  690.         if(!is_null($finder_sql)) {
  691.             $conditions $finder_sql;    
  692.             $order null;
  693.             $limit null;
  694.             $joins null;
  695.         else {
  696.             # Prepare the join table name primary keys (fields) to do the join on
  697.             if(is_null($join_table)) {
  698.                 $join_table $this->get_join_table_name($this->table_name$other_table_name);
  699.             }
  700.             
  701.             # Primary keys
  702.             $this_primary_key  $this->primary_keys[0];
  703.             $other_primary_key $other_class_object->primary_keys[0];
  704.             
  705.             # Foreign keys
  706.             if(is_null($this_foreign_key)) {
  707.                 $this_foreign_key Inflector::singularize($this->table_name)."_".$this_primary_key;
  708.             }
  709.             if(is_null($other_foreign_key)) {
  710.                 $other_foreign_key Inflector::singularize($other_table_name)."_".$other_primary_key;
  711.             }
  712.             
  713.             # Primary key value
  714.             if($this->attribute_is_string($this_primary_key)) {
  715.                 $this_primary_key_value "'".$this->$this_primary_key."'";                    
  716.             elseif(is_numeric($this->$this_primary_key)) {
  717.                 $this_primary_key_value $this->$this_primary_key;
  718.             else {
  719.                 #$this_primary_key_value = 0;
  720.                 # no primary key value so just return empty array same as find_all()
  721.                 return array();
  722.             }
  723.  
  724.             # Set up the SQL segments
  725.             $conditions "{$join_table}.{$this_foreign_key} = {$this_primary_key_value}".$additional_conditions;
  726.             $joins "LEFT JOIN {$join_table} ON {$other_table_name}.{$other_primary_key} = {$join_table}.{$other_foreign_key}";
  727.         }
  728.         
  729.         # Get the list of other_class_name objects
  730.         return $other_class_object->find_all($conditions$order$limit$joins);
  731.     }
  732.  
  733.     /**
  734.      *  Find all records using a "has_many" relationship (one-to-many)
  735.      *
  736.      *  Parameters: $other_table_name: The name of the other table that contains
  737.      *                                 many rows relating to this object's id.
  738.      *  Returns: An array of ActiveRecord objects. (e.g. Contact objects)
  739.      *  @todo Document this API
  740.      */
  741.     private function find_all_has_many($other_table_name$parameters null{
  742.         $additional_conditions null;
  743.         # Use any passed-in parameters
  744.         if(is_array($parameters)) {
  745.             if(@array_key_exists("conditions"$parameters)) {
  746.                 $additional_conditions " AND (".$parameters['conditions'].")";
  747.             elseif($parameters[0!= ""{
  748.                 $additional_conditions " AND (".$parameters[0].")";
  749.             }
  750.             if(@array_key_exists("order"$parameters)) {
  751.                 $order $parameters['order'];
  752.             elseif($parameters[1!= ""{
  753.                 $order $parameters[1];
  754.             }
  755.             if(@array_key_exists("limit"$parameters)) {
  756.                 $limit $parameters['limit'];
  757.             elseif($parameters[2!= ""{
  758.                 $limit $parameters[2];
  759.             }
  760.             if(@array_key_exists("foreign_key"$parameters)) {
  761.                 $foreign_key $parameters['foreign_key'];
  762.             }             
  763.             if(@array_key_exists("class_name"$parameters)) {
  764.                 $other_object_name $parameters['class_name'];
  765.             }  
  766.             if(@array_key_exists("finder_sql"$parameters)) {
  767.                 $finder_sql $parameters['finder_sql'];
  768.             }
  769.         }
  770.  
  771.         if(!is_null($other_object_name)) {
  772.             $other_class_name Inflector::camelize($other_object_name);    
  773.         else {
  774.             $other_class_name Inflector::classify($other_table_name);
  775.         }
  776.  
  777.         # Instantiate an object to access find_all
  778.         $other_class_object new $other_class_name();
  779.         
  780.         # If finder_sql is specified just use it instead of determining the association
  781.         if(!is_null($finder_sql)) {
  782.             $conditions $finder_sql;  
  783.             $order null;
  784.             $limit null;
  785.             $joins null
  786.         else {          
  787.             # This class primary key
  788.             $this_primary_key $this->primary_keys[0];
  789.     
  790.             if(!$foreign_key{
  791.                 # this should end up being like user_id or account_id but if you specified
  792.                 # a primaray key other than 'id' it will be like user_field
  793.                 $foreign_key Inflector::singularize($this->table_name)."_".$this_primary_key;
  794.             }
  795.             
  796.             $foreign_key_value $this->$this_primary_key;
  797.             if($other_class_object->attribute_is_string($foreign_key)) {
  798.                 $conditions "{$foreign_key} = '{$foreign_key_value}'";                    
  799.             elseif(is_numeric($foreign_key_value)) {
  800.                 $conditions "{$foreign_key} = {$foreign_key_value}";
  801.             else {
  802.                 #$conditions = "{$foreign_key} = 0";
  803.                 # no primary key value so just return empty array same as find_all()
  804.                 return array();                
  805.             }            
  806.             $conditions .= $additional_conditions
  807.         }
  808.                          
  809.         # Get the list of other_class_name objects
  810.         return $other_class_object->find_all($conditions$order$limit$joins);
  811.     }
  812.  
  813.     /**
  814.      *  Find all records using a "has_one" relationship (one-to-one)
  815.      *  (the foreign key being in the other table)
  816.      *  Parameters: $other_table_name: The name of the other table that contains
  817.      *                                 many rows relating to this object's id.
  818.      *  Returns: An array of ActiveRecord objects. (e.g. Contact objects)
  819.      *  @todo Document this API
  820.      */
  821.     private function find_one_has_one($other_object_name$parameters null{       
  822.         $additional_conditions null;
  823.         # Use any passed-in parameters
  824.         if(is_array($parameters)) {
  825.             //echo "<pre>";print_r($parameters);
  826.             if(@array_key_exists("conditions"$parameters)) {
  827.                 $additional_conditions " AND (".$parameters['conditions'].")";
  828.             elseif($parameters[0!= ""{
  829.                 $additional_conditions " AND (".$parameters[0].")";
  830.             }
  831.             if(@array_key_exists("order"$parameters)) {
  832.                 $order $parameters['order'];
  833.             elseif($parameters[1!= ""{
  834.                 $order $parameters[1];
  835.             }
  836.             if(@array_key_exists("foreign_key"$parameters)) {
  837.                 $foreign_key $parameters['foreign_key'];
  838.             }         
  839.             if(@array_key_exists("class_name"$parameters)) {
  840.                 $other_object_name $parameters['class_name'];
  841.             }  
  842.         }
  843.         
  844.         $other_class_name Inflector::camelize($other_object_name);
  845.         
  846.         # Instantiate an object to access find_all
  847.         $other_class_object new $other_class_name();
  848.  
  849.         # This class primary key
  850.         $this_primary_key $this->primary_keys[0];
  851.         
  852.         if(!$foreign_key){
  853.             $foreign_key Inflector::singularize($this->table_name)."_".$this_primary_key;
  854.         }
  855.  
  856.         $foreign_key_value $this->$this_primary_key;
  857.         if($other_class_object->attribute_is_string($foreign_key)) {
  858.             $conditions "{$foreign_key} = '{$foreign_key_value}'";                    
  859.         elseif(is_numeric($foreign_key_value)) {
  860.             $conditions "{$foreign_key} = {$foreign_key_value}";
  861.         else {
  862.             #$conditions = "{$foreign_key} = 0";
  863.             return null;
  864.         }
  865.  
  866.         $conditions .= $additional_conditions
  867.         
  868.         # Get the list of other_class_name objects
  869.         return $other_class_object->find_first($conditions$order);
  870.     }
  871.  
  872.     /**
  873.      *  Find all records using a "belongs_to" relationship (one-to-one)
  874.      *  (the foreign key being in the table itself)
  875.      *  Parameters: $other_object_name: The singularized version of a table name.
  876.      *                                  E.g. If the Contact class belongs_to the
  877.      *                                  Customer class, then $other_object_name
  878.      *                                  will be "customer".
  879.      *  @todo Document this API
  880.      */
  881.     private function find_one_belongs_to($other_object_name$parameters null{
  882.  
  883.         $additional_conditions null;
  884.         # Use any passed-in parameters
  885.         if(is_array($parameters)) {
  886.             //echo "<pre>";print_r($parameters);
  887.             if(@array_key_exists("conditions"$parameters)) {
  888.                 $additional_conditions " AND (".$parameters['conditions'].")";
  889.             elseif($parameters[0!= ""{
  890.                 $additional_conditions " AND (".$parameters[0].")";
  891.             }
  892.             if(@array_key_exists("order"$parameters)) {
  893.                 $order $parameters['order'];
  894.             elseif($parameters[1!= ""{
  895.                 $order $parameters[1];
  896.             }
  897.             if(@array_key_exists("foreign_key"$parameters)) {
  898.                 $foreign_key $parameters['foreign_key'];
  899.             }         
  900.             if(@array_key_exists("class_name"$parameters)) {
  901.                 $other_object_name $parameters['class_name'];
  902.             }  
  903.         }
  904.         
  905.         $other_class_name Inflector::camelize($other_object_name);
  906.      
  907.         # Instantiate an object to access find_all
  908.         $other_class_object new $other_class_name();
  909.  
  910.         # This class primary key
  911.         $other_primary_key $other_class_object->primary_keys[0];
  912.  
  913.         if(!$foreign_key{
  914.             $foreign_key $other_object_name."_".$other_primary_key;
  915.         }
  916.         
  917.         $other_primary_key_value $this->$foreign_key;
  918.         if($other_class_object->attribute_is_string($other_primary_key)) {
  919.             $conditions "{$other_primary_key} = '{$other_primary_key_value}'";                    
  920.         elseif(is_numeric($other_primary_key_value)) {
  921.             $conditions "{$other_primary_key} = {$other_primary_key_value}";
  922.         else {
  923.             #$conditions = "{$other_primary_key} = 0";
  924.             return null;
  925.         }
  926.         $conditions .= $additional_conditions;
  927.         
  928.         # Get the list of other_class_name objects
  929.         return $other_class_object->find_first($conditions$order);
  930.     }
  931.  
  932.     /**
  933.      *  Implement *_all() functions (SQL aggregate functions)
  934.      *
  935.      *  Apply one of the SQL aggregate functions to a column of the
  936.      *  table associated with this object.  The SQL aggregate
  937.      *  functions are AVG, COUNT, MAX, MIN and SUM.  Not all DBMS's
  938.      *  implement all of these functions.
  939.      *  @param string $agrregrate_type SQL aggregate function to
  940.      *     apply, suffixed '_all'.  The aggregate function is one of
  941.      *   the strings in {@link $aggregations}.
  942.      *  @param string[] $parameters  Conditions to apply to the
  943.      *     aggregate function.  If present, must be an array of three
  944.      *     strings:<ol>
  945.      *      <li>$parameters[0]: If present, expression to apply
  946.      *        the aggregate function to.  Otherwise, '*' will be used.
  947.      *        <b>NOTE:</b>SQL uses '*' only for the COUNT() function,
  948.      *        where it means "including rows with NULL in this column".</li>
  949.      *      <li>$parameters[1]: argument to WHERE clause</li>
  950.      *      <li>$parameters[2]: joins??? @todo Document this parameter</li>
  951.      *     </ol>
  952.      *  @throws {@link ActiveRecordError}
  953.      *  @uses query()
  954.      *  @uses is_error()
  955.      */
  956.     private function aggregate_all($aggregate_type$parameters null{
  957.         $aggregate_type strtoupper(substr($aggregate_type0-4));
  958.         ($parameters[0]$field $parameters[0$field "*";
  959.         $sql "SELECT {$aggregate_type}({$field}AS agg_result FROM {$this->table_prefix}{$this->table_name";
  960.         
  961.         # Use any passed-in parameters
  962.         if(is_array($parameters[1])) {
  963.             extract($parameters[1]);   
  964.         } elseif(!is_null($parameters)) {
  965.             $conditions = $parameters[1];
  966.             $order = $parameters[2];
  967.             $joins = $parameters[3];
  968.         }
  969.  
  970.         if(!empty($joins)) $sql .= " $joins ";
  971.         if(!empty($conditions)) $sql .= " WHERE $conditions ";
  972.         if(!empty($order)) $sql .= " ORDER BY $order ";
  973.  
  974.         # echo "$aggregate_type sql:$sql<br>";
  975.         if($this->is_error($rs $this->query($sql))) {
  976.             $this->raise($rs->getMessage());
  977.         } else {
  978.             $row = $rs->fetchRow();
  979.             if($row["agg_result"]{
  980.                 return $row["agg_result"];    
  981.             }
  982.         }
  983.         return 0;
  984.     }
  985.  
  986.     /**
  987.      *  Returns a the name of the join table that would be used for the two
  988.      *  tables.  The join table name is decided from the alphabetical order
  989.      *  of the two tables.  e.g. "genres_movies" because "g" comes before "m"
  990.      *
  991.      *  Parameters: $first_table, $second_table: the names of two database tables,
  992.      *   e.g. "movies" and "genres"
  993.      *  @todo Document this API
  994.      */
  995.     public function get_join_table_name($first_table, $second_table) {
  996.         $tables = array($first_table, $second_table);
  997.         @sort($tables);
  998.         return $this->table_prefix.@implode("_"$tables);
  999.     }
  1000.  
  1001.     /**
  1002.      *  Test whether this object represents a new record
  1003.      *  @uses $new_record
  1004.      *  @return boolean Whether this object represents a new record
  1005.      */
  1006.    function is_new_record() {
  1007.         return $this->new_record;
  1008.     }
  1009.  
  1010.    /**
  1011.     *  get the attributes for a specific column.
  1012.     *  @uses $content_columns
  1013.     *  @todo Document this API
  1014.     */
  1015.     function column_for_attribute($attribute) {
  1016.         if(is_array($this->content_columns)) {
  1017.             foreach($this->content_columns as $column{
  1018.                 if($column['name'] == $attribute) {
  1019.                     return $column;
  1020.                 }
  1021.             }
  1022.         }
  1023.         return null;
  1024.     }
  1025.  
  1026.    /**
  1027.     *  get the columns  data type.
  1028.     *  @uses column_for_attribute()
  1029.     *  @todo Document this API
  1030.     */    
  1031.     function column_type($attribute) {
  1032.         $column = $this->column_for_attribute($attribute);
  1033.         if(isset($column['type'])) {
  1034.             return $column['type'];    
  1035.         }            
  1036.         return null;
  1037.     }
  1038.     
  1039.     /**
  1040.      *  Check whether a column exists in the associated table
  1041.      *
  1042.      *  When called, {@link $content_columns} lists the columns in
  1043.      *  the table described by this object.
  1044.      *  @param string Name of the column
  1045.      *  @return boolean true=>the column exists; false=>it doesn't
  1046.      *  @uses content_columns
  1047.      */
  1048.     function column_attribute_exists($attribute) {
  1049.         if(is_array($this->content_columns)) {
  1050.             foreach($this->content_columns as $column{
  1051.                 if($column['name'] == $attribute) {
  1052.                     return true;
  1053.                 }
  1054.             }
  1055.         } 
  1056.         return false;     
  1057.     }
  1058.  
  1059.     /**
  1060.      *  Get contents of one column of record selected by id and table
  1061.      *
  1062.      *  When called, {@link $id} identifies one record in the table
  1063.      *  identified by {@link $table}.  Fetch from the database the
  1064.      *  contents of column $column of this record.
  1065.      *  @param string Name of column to retrieve
  1066.      *  @uses $db
  1067.      *  @uses column_attribute_exists()
  1068.      *  @throws {@link ActiveRecordError}
  1069.      *  @uses is_error()
  1070.      */
  1071.     function send($column) {
  1072.         if($this->column_attribute_exists($column&& ($conditions $this->get_primary_key_conditions())) {
  1073.             # Run the query to grab a specific columns value.
  1074.             $sql = "SELECT {$column} FROM {$this->table_prefix}{$this->table_nameWHERE {$conditions}";
  1075.             $this->log_query($sql);
  1076.             $result self::$db->queryOne($sql);
  1077.             if($this->is_error($result)) {
  1078.                 $this->raise($result->getMessage());
  1079.             }
  1080.         }
  1081.         return $result;
  1082.     }
  1083.  
  1084.     /**
  1085.      * Only used if you want to do transactions and your db supports transactions
  1086.      *
  1087.      *  @uses $db
  1088.      *  @todo Document this API
  1089.      */
  1090.     function begin() {
  1091.         self::$db->query("BEGIN");
  1092.         $this->begin_executed = true;
  1093.     }
  1094.  
  1095.     /**
  1096.      *  Only used if you want to do transactions and your db supports transactions
  1097.      *
  1098.      *  @uses $db
  1099.      *  @todo Document this API
  1100.      */
  1101.     function commit() {
  1102.         self::$db->query("COMMIT")
  1103.         $this->begin_executed = false;
  1104.     }
  1105.  
  1106.     /**
  1107.      *  Only used if you want to do transactions and your db supports transactions
  1108.      *
  1109.      *  @uses $db
  1110.      *  @todo Document this API
  1111.      */
  1112.     function rollback() {
  1113.         self::$db->query("ROLLBACK");
  1114.     }
  1115.  
  1116.     /**
  1117.      *  Perform an SQL query and return the results
  1118.      *
  1119.      *  @param string $sql  SQL for the query command
  1120.      *  @return $mdb2->query {@link http://pear.php.net/manual/en/package.database.mdb2.intro-query.php}
  1121.      *    Result set from query
  1122.      *  @uses $db
  1123.      *  @uses is_error()
  1124.      *  @uses log_query()
  1125.      *  @throws {@link ActiveRecordError}
  1126.      */
  1127.     function query($sql) {
  1128.         # Run the query
  1129.         $this->log_query($sql);
  1130.         $rs =self::$db->query($sql);
  1131.         if ($this->is_error($rs)) {
  1132.             if(self::$use_transactions && self::$begin_executed) {
  1133.                 $this->rollback();
  1134.             }
  1135.             $this->raise($rs->getMessage());
  1136.         }
  1137.         return $rs;
  1138.     }
  1139.  
  1140.     /**
  1141.      *  Implement find_by_*() and =_* methods
  1142.      *  
  1143.      *  Converts a method name beginning 'find_by_' or 'find_all_by_'
  1144.      *  into a query for rows matching the rest of the method name and
  1145.      *  the arguments to the function.  The part of the method name
  1146.      *  after '_by' is parsed for columns and logical relationships
  1147.      *  (AND and OR) to match.  For example, the call
  1148.      *    find_by_fname('Ben')
  1149.      *  is converted to
  1150.      *    SELECT * ... WHERE fname='Ben'
  1151.      *  and the call
  1152.      *    find_by_fname_and_lname('Ben','Dover')
  1153.      *  is converted to
  1154.      *    SELECT * ... WHERE fname='Ben' AND lname='Dover'
  1155.      *  
  1156.      *  @uses find_all()
  1157.      *  @uses find_first()
  1158.      */
  1159.     private function find_by($method_name, $parameters, $find_type = null) {
  1160.         if($find_type == "find_or_create") {
  1161.             $explode_len = 18;
  1162.         } elseif($find_type == "all") {
  1163.             $explode_len = 12;
  1164.         } else {
  1165.             $explode_len = 8;     
  1166.         }
  1167.         $method_name = substr(strtolower($method_name), $explode_len);
  1168.         $method_parts = explode("|", str_replace("_and_", "|AND|", $method_name));
  1169.         if(count($method_parts)) {
  1170.             $options = array();
  1171.             $create_fields = array();
  1172.             $param_index = 0;
  1173.             foreach($method_parts as $part) {
  1174.                 if($part == "AND") {
  1175.                     $conditions .= " AND ";
  1176.                     $param_index++;
  1177.                 } else {
  1178.                     $value = $this->attribute_is_string($part
  1179.                         "'".$parameters[$param_index]."'" 
  1180.                         $parameters[$param_index];                    
  1181.                     $create_fields[$part$parameters[$param_index];  
  1182.                     $conditions .= "{$part} = {$value}";
  1183.                 } 
  1184.             }
  1185.             # If last param exists and is a string set it as the ORDER BY clause            
  1186.             # or if the last param is an array set it as the $options
  1187.             if($last_param = $parameters[++$param_index]) {
  1188.                 if(is_string($last_param)) {
  1189.                     $options['order'] = $last_param;        
  1190.                 } elseif(is_array($last_param)) {
  1191.                     $options = $last_param;    
  1192.                 }
  1193.             }  
  1194.             # Set the conditions
  1195.             if($options['conditions'] && $conditions) {
  1196.                 $options['conditions'] = "(".$options['conditions'].") AND (".$conditions.")";    
  1197.             } else {
  1198.                 $options['conditions'] = $conditions;    
  1199.             }
  1200.  
  1201.             # Now do the actual find with condtions from above
  1202.             if($find_type == "find_or_create") {
  1203.                 # see if we can find a record with specified parameters
  1204.                 $object = $this->find($options);
  1205.                 if(is_object($object)) {
  1206.                     # we found a record with the specified parameters so return it
  1207.                     return $object;    
  1208.                 } elseif(count($create_fields)) { 
  1209.                     # can't find a record with specified parameters so create a new record 
  1210.                     # and return new object       
  1211.                     foreach($create_fields as $field => $value) {
  1212.                         $this->$field $value;    
  1213.                     }
  1214.                     $this->save();
  1215.                     return $this->find($options);
  1216.                 }
  1217.             } elseif($find_type == "all") {
  1218.                 return $this->find_all($options);
  1219.             } else {
  1220.                 return $this->find($options);
  1221.             }
  1222.         }
  1223.     }
  1224.  
  1225.     /**
  1226.      *  Builds a sql statement.
  1227.      *  
  1228.      *  @uses $rows_per_page_default
  1229.      *  @uses $rows_per_page
  1230.      *  @uses $offset
  1231.      *  @uses $page
  1232.      *
  1233.      */
  1234.     function build_sql($conditions = null, $order = null, $limit = null, $joins = null) {
  1235.         
  1236.         $offset = null;
  1237.         $per_page = null;
  1238.         $select = null;
  1239.  
  1240.         # this is if they passed in an associative array to emulate
  1241.         # named parameters.
  1242.         if(is_array($conditions)) {
  1243.             if(@array_key_exists("per_page", $conditions) && !is_numeric($conditions['per_page'])) {
  1244.                 extract($conditions); 
  1245.                 $per_page = 0;   
  1246.             } else {
  1247.                 extract($conditions);     
  1248.             }
  1249.             # If conditions wasn't in the array set it to null
  1250.             if(is_array($conditions)) {
  1251.                 $conditions = null;    
  1252.             }  
  1253.         }
  1254.  
  1255.         # Test source of SQL for query
  1256.         if(stristr($conditions, "SELECT")) {
  1257.             # SQL completely specified in argument so use it as is
  1258.             $sql = $conditions;
  1259.         } else {
  1260.  
  1261.             # If select fields not specified just do a SELECT *
  1262.             if(is_null($select)) {
  1263.                 $select = "*";
  1264.             } 
  1265.  
  1266.             # SQL will be built from specifications in argument
  1267.             $sql  = "SELECT {$select} FROM {$this->table_prefix}{$this->table_name";         
  1268.             
  1269.             # If join specified, include it
  1270.             if(!is_null($joins)) {
  1271.                 $sql .= " $joins ";
  1272.             }
  1273.  
  1274.             # If conditions specified, include them
  1275.             if(!is_null($conditions)) {
  1276.                 $sql .= "WHERE $conditions ";
  1277.             }
  1278.  
  1279.             # If ordering specified, include it
  1280.             if(!is_null($order)) {
  1281.                 $sql .= "ORDER BY $order ";
  1282.             }
  1283.  
  1284.             # Is output to be generated in pages?
  1285.             if(is_numeric($limit) || is_numeric($offset) || is_numeric($per_page)) {
  1286.  
  1287.                 if(is_numeric($limit)) {    
  1288.                     $this->rows_per_page $limit;        
  1289.                 }
  1290.                 if(is_numeric($per_page)) {
  1291.                     $this->rows_per_page $per_page;            
  1292.                 }
  1293.                 # Default for rows_per_page:
  1294.                 if ($this->rows_per_page <= 0{
  1295.                     $this->rows_per_page $this->rows_per_page_default;
  1296.                 }
  1297.                 
  1298.                 # Only use request's page if you are calling from find_all_with_pagination() and if it is int
  1299.                 if(strval(intval($_REQUEST['page'])) == $_REQUEST['page']) {
  1300.                     $this->page $_REQUEST['page'];
  1301.                 }
  1302.                 
  1303.                 if($this->page <= 0{
  1304.                     $this->page 1;
  1305.                 }
  1306.                                 
  1307.                 # Set the LIMIT string segment for the SQL
  1308.                 if(is_null($offset)) {
  1309.                     $offset = ($this->page 1$this->rows_per_page;
  1310.                 }
  1311.  
  1312.                 $sql .= "LIMIT {$this->rows_per_pageOFFSET {$offset}";
  1313.                 # $sql .= "LIMIT $offset, $this->rows_per_page";
  1314.                 
  1315.                 # Set number of total pages in result set
  1316.                 if($count = $this->count_all($this->primary_keys[0]$conditions$joins)) {
  1317.                     $this->pagination_count = $count;
  1318.                     $this->pages (($count $this->rows_per_page== 0)
  1319.                         ? $count $this->
  1320. rows_per_page
  1321.                         : floor($count $this->rows_per_page1
  1322.                 }
  1323.             }
  1324.         }
  1325.         
  1326.         return $sql;
  1327.     }
  1328.  
  1329.     /**
  1330.      *  Return rows selected by $conditions
  1331.      *
  1332.      *  If no rows match, an empty array is returned.
  1333.      *  @param string SQL to use in the query.  If
  1334.      *    $conditions contains "SELECT", then $order, $limit and
  1335.      *    $joins are ignored and the query is completely specified by
  1336.      *    $conditions.  If $conditions is omitted or does not contain
  1337.      *    "SELECT", "SELECT * FROM" will be used.  If $conditions is
  1338.      *    specified and does not contain "SELECT", the query will
  1339.      *    include "WHERE $conditions".  If $conditions is null, the
  1340.      *    entire table is returned.
  1341.      *  @param string Argument to "ORDER BY" in query.
  1342.      *    If specified, the query will include
  1343.      *    "ORDER BY $order". If omitted, no ordering will be
  1344.      *    applied.  
  1345.      *  @param integer[] Page, rows per page???
  1346.      *  @param string ???
  1347.      *  @todo Document the $limit and $joins parameters
  1348.      *  @uses is_error()
  1349.      *  @uses $new_record
  1350.      *  @uses query()
  1351.      *  @return object[] Array of objects of the same class as this
  1352.      *    object, one object for each row returned by the query.
  1353.      *    If the column 'id' was in the results, it is used as the key
  1354.      *    for that object in the array.
  1355.      *  @throws {@link ActiveRecordError}
  1356.      */
  1357.     function find_all($conditions = null, $order = null, $limit = null, $joins = null) {
  1358.         //error_log("find_all(".(is_null($conditions)?'null':$conditions)
  1359.         //          .', ' . (is_null($order)?'null':$order)
  1360.         //          .', ' . (is_null($limit)?'null':var_export($limit,true))
  1361.         //          .', ' . (is_null($joins)?'null':$joins).')');
  1362.         # Placed the sql building code in a separate function
  1363.         $sql = $this->build_sql($conditions$order$limit$joins);
  1364.  
  1365.         # echo "ActiveRecord::find_all() - sql: $sql\n<br>";
  1366.         # echo "query: $sql\n";
  1367.         # error_log("ActiveRecord::find_all -> $sql");
  1368.         if($this->is_error($rs $this->query($sql))) {
  1369.             $this->raise($rs->getMessage());
  1370.         }
  1371.  
  1372.         $objects = array();
  1373.         while($row = $rs->fetchRow()) {
  1374.             $class_name = $this->get_class_name();
  1375.             $object new $class_name();
  1376.             $object->new_record false;
  1377.             $objects_key null;
  1378.             foreach($row as $field => $value{
  1379.                 $object->$field $value;
  1380.                 if($field == $this->index_on{
  1381.                     $objects_key = $value;
  1382.                 }
  1383.             }
  1384.             $objects[$objects_key] = $object;
  1385.             # If callback is defined in model run it.
  1386.             # this will probably hurt performance...
  1387.             if(method_exists($object, 'after_find')) {
  1388.                 $object->after_find();    
  1389.             }
  1390.             unset($object);
  1391.         }
  1392.         return $objects;
  1393.     }
  1394.  
  1395.     /**
  1396.      *  Find row(s) with specified value(s)
  1397.      *
  1398.      *  Find all the rows in the table which match the argument $id.
  1399.      *  Return zero or more objects of the same class as this
  1400.      *  class representing the rows that matched the argument.
  1401.      *  @param mixed[] $id  If $id is an array then a query will be
  1402.      *    generated selecting all of the array values in column "id".
  1403.      *    If $id is a string containing "=" then the string value of
  1404.      *    $id will be inserted in a WHERE clause in the query.  If $id
  1405.      *    is a scalar not containing "=" then a query will be generated 
  1406.      *    selecting the first row WHERE id = '$id'.
  1407.      *    <b>NOTE</b> The column name "id" is used regardless of the
  1408.      *    value of {@link $primary_keys}.  Therefore if you need to
  1409.      *    select based on some column other than "id", you must pass a
  1410.      *    string argument ready to insert in the SQL SELECT.
  1411.      *  @param string $order Argument to "ORDER BY" in query.
  1412.      *    If specified, the query will include "ORDER BY
  1413.      *    $order". If omitted, no ordering will be applied.
  1414.      *  @param integer[] $limit Page, rows per page???
  1415.      *  @param string $joins ???
  1416.      *  @todo Document the $limit and $joins parameters
  1417.      *  @uses find_all()
  1418.      *  @uses find_first()
  1419.      *  @return mixed Results of query.  If $id was a scalar then the
  1420.      *    result is an object of the same class as this class and
  1421.      *    matching $id conditions, or if no row matched the result is
  1422.      *    null. 
  1423.      *
  1424.      *    If $id was an array then the result is an array containing
  1425.      *    objects of the same class as this class and matching the
  1426.      *    conditions set by $id.  If no rows matched, the array is
  1427.      *    empty.
  1428.      *  @throws {@link ActiveRecordError}
  1429.      */
  1430.     function find($id, $order = null, $limit = null, $joins = null) {
  1431.         $find_all = false;
  1432.         if(is_array($id)) {
  1433.             if($id[0]) {
  1434.                 # passed in array of numbers array(1,2,4,23)
  1435.                 $primary_key = $this->primary_keys[0];
  1436.                 $primary_key_values $this->attribute_is_string($primary_key
  1437.                     "'".implode("','"$id)."'" 
  1438.                     implode(","$id);
  1439.                 $options['conditions'"{$primary_key} IN({$primary_key_values})";
  1440.                 $find_all = true;
  1441.             } else {
  1442.                 # passed in an options array
  1443.                 $options = $id;    
  1444.             }
  1445.         } elseif(stristr($id, "=")) { 
  1446.             # has an "=" so must be a WHERE clause
  1447.             $options['conditions'] = $id;
  1448.         } else {
  1449.             # find an single record with id = $id
  1450.             $primary_key = $this->primary_keys[0];
  1451.             $primary_key_value $this->attribute_is_string($primary_key"'".$id."'" $id ;
  1452.             $options['conditions'"{$primary_key} = {$primary_key_value}";
  1453.         }
  1454.         if(!is_null($order)) $options['order'] = $order; 
  1455.         if(!is_null($limit)) $options['limit'] = $limit;
  1456.         if(!is_null($joins)) $options['joins'] = $joins;
  1457.  
  1458.  
  1459.         if($find_all) {
  1460.             return $this->find_all($options);
  1461.         } else {
  1462.             return $this->find_first($options);
  1463.         }
  1464.     }
  1465.  
  1466.     /**
  1467.      *  Return first row selected by $conditions
  1468.      *
  1469.      *  If no rows match, null is returned.
  1470.      *  @param string $conditions SQL to use in the query.  If
  1471.      *    $conditions contains "SELECT", then $order, $limit and
  1472.      *    $joins are ignored and the query is completely specified by
  1473.      *    $conditions.  If $conditions is omitted or does not contain
  1474.      *    "SELECT", "SELECT * FROM" will be used.  If $conditions is
  1475.      *    specified and does not contain "SELECT", the query will
  1476.      *    include "WHERE $conditions".  If $conditions is null, the
  1477.      *    entire table is returned.
  1478.      *  @param string $order Argument to "ORDER BY" in query.
  1479.      *    If specified, the query will include
  1480.      *    "ORDER BY $order". If omitted, no ordering will be
  1481.      *    applied.  
  1482.      *  FIXME This parameter doesn't seem to make sense
  1483.      *  @param integer[] $limit Page, rows per page??? @todo Document this parameter
  1484.      *  FIXME This parameter doesn't seem to make sense
  1485.      *  @param string $joins ??? @todo Document this parameter
  1486.      *  @uses find_all()
  1487.      *  @return mixed An object of the same class as this class and
  1488.      *    matching $conditions, or null if none did.
  1489.      *  @throws {@link ActiveRecordError}
  1490.      */
  1491.     function find_first($conditions = null, $order = null, $limit = 1, $joins = null) {
  1492.         if(is_array($conditions)) {
  1493.             $options = $conditions;    
  1494.         } else {
  1495.             $options['conditions'] = $conditions;    
  1496.         }
  1497.         if(!is_null($order)) $options['order'] = $order; 
  1498.         if(!is_null($limit)) $options['limit'] = $limit;
  1499.         if(!is_null($joins)) $options['joins'] = $joins;
  1500.  
  1501.         $result = @current($this->find_all($options));
  1502.         return (is_object($result$result null);        
  1503.     }
  1504.  
  1505.     /**
  1506.      *  Return all the rows selected by the SQL argument
  1507.      *
  1508.      *  If no rows match, an empty array is returned.
  1509.      *  @param string $sql SQL to use in the query.
  1510.      */
  1511.     function find_by_sql($sql) {
  1512.         return $this->find_all($sql);
  1513.     }
  1514.  
  1515.     /**
  1516.      *  Reloads the attributes of this object from the database.
  1517.      *  @uses get_primary_key_conditions()
  1518.      *  @todo Document this API
  1519.      */
  1520.     function reload($conditions = null) {
  1521.         if(is_null($conditions)) {
  1522.             $conditions = $this->get_primary_key_conditions();
  1523.         }
  1524.         $object = $this->find($conditions);
  1525.         if(is_object($object)) {
  1526.             foreach($object as $key => $value) {
  1527.                 $this->$key $value;
  1528.             }
  1529.             return true;
  1530.         }
  1531.         return false;
  1532.     }
  1533.  
  1534.     /**
  1535.      *  Loads into current object values from the database.
  1536.      */
  1537.     function load($conditions = null) {
  1538.         return $this->reload($conditions);        
  1539.     }
  1540.  
  1541.     /**
  1542.      *  @todo Document this API.  What's going on here?  It appears to
  1543.      *        either create a row with all empty values, or it tries
  1544.      *        to recurse once for each attribute in $attributes.
  1545.      *  Creates an object, instantly saves it as a record (if the validation permits it).
  1546.      *  If the save fails under validations it returns false and $errors array gets set.
  1547.      */
  1548.     function create($attributes, $dont_validate = false) {
  1549.         $class_name = $this->get_class_name();
  1550.         $object new $class_name();
  1551.         $result $object->save($attributes$dont_validate);
  1552.         return ($result $object false);
  1553.     }
  1554.  
  1555.     /**
  1556.      *  Finds the record from the passed id, instantly saves it with the passed attributes 
  1557.      *  (if the validation permits it). Returns true on success and false on error.
  1558.      *  @todo Document this API
  1559.      */
  1560.     function update($id, $attributes, $dont_validate = false) {
  1561.         if(is_array($id)) {
  1562.             foreach($id as $update_id) {
  1563.                 $this->update($update_id$attributes[$update_id]$dont_validate);
  1564.             }
  1565.         } else {
  1566.             $object = $this->find($id);
  1567.             return $object->save($attributes$dont_validate);
  1568.         }
  1569.     }
  1570.  
  1571.     /**
  1572.      *  Updates all records with the SET-part of an SQL update statement in updates and 
  1573.      *  returns an integer with the number of rows updates. A subset of the records can 
  1574.      *  be selected by specifying conditions. 
  1575.      *  Example:
  1576.      *    $model->update_all("category = 'cooldude', approved = 1", "author = 'John'");
  1577.      *  @uses is_error()
  1578.      *  @uses query()
  1579.      *  @throws {@link ActiveRecordError}
  1580.      *  @todo Document this API
  1581.      */
  1582.     function update_all($updates, $conditions = null) {
  1583.         $sql = "UPDATE {$this->table_prefix}{$this->table_nameSET {$updates} WHERE {$conditions}";
  1584.         $result = $this->query($sql);
  1585.         if ($this->is_error($result)) {
  1586.             $this->raise($result->getMessage());
  1587.         } else {
  1588.             return true;
  1589.         }
  1590.     }
  1591.  
  1592.     /**
  1593.      *  Save without valdiating anything.
  1594.      *  @todo Document this API
  1595.      */
  1596.     function save_without_validation($attributes = null) {
  1597.         return $this->save($attributestrue);
  1598.     }
  1599.  
  1600.     /**
  1601.      *  Create or update a row in the table with specified attributes
  1602.      *
  1603.      *  @param string[] $attributes List of name => value pairs giving
  1604.      *    name and value of attributes to set.
  1605.      *  @param boolean $dont_validate true => Don't call validation
  1606.      *    routines before saving the row.  If false or omitted, all 
  1607.      *    applicable validation routines are called.
  1608.      *  @uses add_record_or_update_record()
  1609.      *  @uses update_attributes()
  1610.      *  @uses valid()
  1611.      *  @return boolean
  1612.      *          <ul>
  1613.      *            <li>true => row was updated or inserted successfully</li>
  1614.      *            <li>false => insert failed</li>
  1615.      *          </ul>
  1616.      */
  1617.     function save($attributes = null, $dont_validate = false) {
  1618.         //error_log("ActiveRecord::save() \$attributes="
  1619.         //          . var_export($attributes,true));
  1620.         $this->update_attributes($attributes);
  1621.         if($dont_validate || $this->valid()) {
  1622.             return $this->add_record_or_update_record();
  1623.         } else {
  1624.             return false;
  1625.         }
  1626.     }
  1627.  
  1628.     /**
  1629.      *  Create or update a row in the table
  1630.      *
  1631.      *  If this object represents a new row in the table, insert it.
  1632.      *  Otherwise, update the exiting row.  before_?() and after_?()
  1633.      *  routines will be called depending on whether the row is new.
  1634.      *  @uses add_record()
  1635.      *  @uses after_create()
  1636.      *  @uses after_update()
  1637.      *  @uses before_create()
  1638.      *  @uses before_save()
  1639.      *  @uses $new_record
  1640.      *  @uses update_record()
  1641.      *  @return boolean
  1642.      *          <ul>
  1643.      *            <li>true => row was updated or inserted successfully</li>
  1644.      *            <li>false => insert failed</li>
  1645.      *          </ul>
  1646.      */
  1647.     private function add_record_or_update_record() { 
  1648.         //error_log('add_record_or_update_record()');
  1649.         $this->before_save();
  1650.         if($this->new_record{
  1651.             $this->before_create();
  1652.             $result $this->add_record();   
  1653.             $this->after_create()
  1654.         } else {
  1655.             $this->before_update();
  1656.             $result $this->update_record();
  1657.             $this->after_update();
  1658.         }
  1659.         $this->after_save();
  1660.         return $result;
  1661.     }
  1662.  
  1663.     /**
  1664.      *  Insert a new row in the table associated with this object
  1665.      *
  1666.      *  Build an SQL INSERT statement getting the table name from
  1667.      *  {@link $table_name}, the column names from {@link
  1668.      *  $content_columns} and the values from object variables.
  1669.      *  Send the insert to the RDBMS.
  1670.      *  @uses $auto_save_habtm
  1671.      *  @uses add_habtm_records()
  1672.      *  @uses before_create()
  1673.      *  @uses get_insert_id()
  1674.      *  @uses is_error()
  1675.      *  @uses query()
  1676.      *  @uses get_inserts()
  1677.      *  @uses raise()
  1678.      *  @uses $table_name
  1679.      *  @return boolean
  1680.      *          <ul>
  1681.      *            <li>true => row was inserted successfully</li>
  1682.      *            <li>false => insert failed</li>
  1683.      *          </ul>
  1684.      *  @throws {@link ActiveRecordError}
  1685.      */
  1686.     private function add_record() {
  1687.         self::$db->loadModule('Extended'nulltrue);                
  1688.         # $primary_key_value may either be a quoted integer or php null
  1689.         $primary_key_value self::$db->getBeforeID("{$this->table_prefix}{$this->table_name}", $this->primary_keys[0]);
  1690.         if($this->is_error($primary_key_value)) {
  1691.             $this->raise($primary_key_value->getMessage());
  1692.         }
  1693.         $this->update_composite_attributes();
  1694.         $attributes $this->get_inserts();
  1695.         $fields @implode(', 'array_keys($attributes));
  1696.         $values @implode(', 'array_values($attributes));
  1697.         $sql "INSERT INTO {$this->table_prefix}{$this->table_name} ({$fields}VALUES ({$values})";
  1698.         //echo "add_record: SQL: $sql<br>";
  1699.         //error_log("add_record: SQL: $sql");
  1700.         $result = $this->query($sql);
  1701.         
  1702.         if($this->is_error($result)) {
  1703.             $this->raise($results->getMessage());
  1704.         } else {
  1705.             $habtm_result = true;
  1706.             $primary_key = $this->primary_keys[0];
  1707.             # $id is now equivalent to the value in the id field that was inserted
  1708.             $primary_key_value self::$db->getAfterID($primary_key_value"{$this->table_prefix}{$this->table_name}", $this->primary_keys[0]);
  1709.             if($this->is_error($primary_key_value)) {
  1710.                 $this->raise($primary_key_value->getMessage());
  1711.             }            
  1712.             $this->$primary_key $primary_key_value;
  1713.             if($primary_key_value != ''{
  1714.                 if($this->auto_save_habtm{
  1715.                     $habtm_result = $this->add_habtm_records($primary_key_value);
  1716.                 }
  1717.                 $this->save_associations();
  1718.             }          
  1719.             return ($result && $habtm_result);
  1720.         }
  1721.     }
  1722.  
  1723.     /**
  1724.      *  Update the row in the table described by this object
  1725.      *
  1726.      *  The primary key attributes must exist and have appropriate
  1727.      *  non-null values.  If a column is listed in {@link
  1728.      *  $content_columns} but no attribute of that name exists, the
  1729.      *  column will be set to the null string ''.
  1730.      *  @todo Describe habtm automatic update
  1731.      *  @uses is_error()
  1732.      *  @uses get_updates_sql()
  1733.      *  @uses get_primary_key_conditions()
  1734.      *  @uses query()
  1735.      *  @uses raise()
  1736.      *  @uses update_habtm_records()
  1737.      *  @return boolean
  1738.      *          <ul>
  1739.      *            <li>true => row was updated successfully</li>
  1740.      *            <li>false => update failed</li>
  1741.      *          </ul>
  1742.      *  @throws {@link ActiveRecordError}
  1743.      */
  1744.     private function update_record() {
  1745.         //error_log('update_record()');
  1746.         $this->update_composite_attributes();
  1747.         $updates $this->get_updates_sql();
  1748.         $conditions $this->get_primary_key_conditions();
  1749.         $sql "UPDATE {$this->table_prefix}{$this->table_nameSET {$updates} WHERE {$conditions}";
  1750.         //echo "update_record:$sql<br>";
  1751.         //error_log("update_record: SQL: $sql");
  1752.         $result = $this->query($sql);
  1753.         if($this->is_error($result)) {
  1754.             $this->raise($results->getMessage());
  1755.         } else {
  1756.             $habtm_result = true;
  1757.             $primary_key = $this->primary_keys[0];
  1758.             $primary_key_value $this->$primary_key;
  1759.             if($primary_key_value 0{ 
  1760.                 if($this->auto_save_habtm{
  1761.                     $habtm_result = $this->update_habtm_records($primary_key_value);
  1762.                 }
  1763.                 $this->save_associations();
  1764.             }         
  1765.             return ($result && $habtm_result);
  1766.         }
  1767.     }
  1768.  
  1769.     /**
  1770.      *  Loads the model values into composite object
  1771.      *  @todo Document this API
  1772.      */    
  1773.     private function get_composite_object($name) {
  1774.         $composite_object = null;
  1775.         $composite_attributes = array();
  1776.         if(is_array($this->composed_of)) { 
  1777.             if(array_key_exists($name, $this->composed_of)) {
  1778.                 $class_name = Inflector::classify(($this->composed_of[$name]['class_name'
  1779.                     $this->composed_of[$name]['class_name'$name));           
  1780.  
  1781.                 $mappings $this->composed_of[$name]['mapping'];
  1782.                 if(is_array($mappings)) {
  1783.                     foreach($mappings as $database_name => $composite_name) {
  1784.                         $composite_attributes[$composite_name] = $this->$database_name;                      
  1785.                     }    
  1786.                 }   
  1787.             }    
  1788.         } elseif($this->composed_of == $name{
  1789.             $class_name = $name;
  1790.             $composite_attributes[$name] = $this->$name;        
  1791.         } 
  1792.         
  1793.         if(class_exists($class_name)) {                     
  1794.             $composite_object = new $class_name;        
  1795.             if($composite_object->auto_map_attributes !== false{
  1796.                 //echo "auto_map_attributes<br>";
  1797.                 foreach($composite_attributes as $name => $value) {
  1798.                     $composite_object->$name $value;    
  1799.                 }                                      
  1800.             }           
  1801.             if(method_exists($composite_object, '__construct')) {
  1802.                 //echo "calling constructor<br>";
  1803.                 $composite_object->__construct($composite_attributes);       
  1804.             }         
  1805.         } 
  1806.         return $composite_object;
  1807.     }
  1808.     
  1809.     /**
  1810.      *  returns the association type if defined in child class or null
  1811.      *  @todo Document this API
  1812.      *  @uses $belongs_to
  1813.      *  @uses $has_and_belongs_to_many
  1814.      *  @uses $has_many
  1815.      *  @uses $has_one
  1816.      *  @return mixed Association type, one of the following:
  1817.      *  <ul>
  1818.      *    <li>"belongs_to"</li>
  1819.      *    <li>"has_and_belongs_to_many"</li>
  1820.      *    <li>"has_many"</li>
  1821.      *    <li>"has_one"</li>
  1822.      *  </ul>
  1823.      *  if an association exists, or null if no association
  1824.      */
  1825.     function get_association_type($association_name) {
  1826.         $type = null;
  1827.         if(is_string($this->has_many)) {
  1828.             if(preg_match("/\b$association_name\b/", $this->has_many)) {
  1829.                 $type = "has_many";    
  1830.             }
  1831.         } elseif(is_array($this->has_many)) {
  1832.             if(array_key_exists($association_name, $this->has_many)) {
  1833.                 $type = "has_many";     
  1834.             }
  1835.         }
  1836.         if(is_string($this->has_one)) {
  1837.             if(preg_match("/\b$association_name\b/", $this->has_one)) {
  1838.                 $type = "has_one";     
  1839.             }
  1840.         } elseif(is_array($this->has_one)) {
  1841.             if(array_key_exists($association_name, $this->has_one)) {
  1842.                 $type = "has_one";     
  1843.             }
  1844.         }
  1845.         if(is_string($this->belongs_to)) { 
  1846.             if(preg_match("/\b$association_name\b/", $this->belongs_to)) {
  1847.                 $type = "belongs_to";      
  1848.             }
  1849.         } elseif(is_array($this->belongs_to)) {
  1850.             if(array_key_exists($association_name, $this->belongs_to)) {
  1851.                 $type = "belongs_to";      
  1852.             }
  1853.         }
  1854.         if(is_string($this->has_and_belongs_to_many)) {
  1855.             if(preg_match("/\b$association_name\b/", $this->has_and_belongs_to_many)) {
  1856.                 $type = "has_and_belongs_to_many";      
  1857.             }
  1858.         } elseif(is_array($this->has_and_belongs_to_many)) {
  1859.             if(array_key_exists($association_name, $this->has_and_belongs_to_many)) {
  1860.                 $type = "has_and_belongs_to_many";      
  1861.             }
  1862.         }   
  1863.         return $type;   
  1864.     }
  1865.     
  1866.     /**
  1867.      *  Saves any associations objects assigned to this instance
  1868.      *  @uses $auto_save_associations
  1869.      *  @todo Document this API
  1870.      */
  1871.     private function save_associations() {      
  1872.         if(count($this->save_associations&& $this->auto_save_associations{
  1873.             foreach(array_keys($this->save_associationsas $type{
  1874.                 if(count($this->save_associations[$type])) {
  1875.                     foreach($this->save_associations[$typeas $object_or_array{
  1876.                         if(is_object($object_or_array)) {
  1877.                             $this->save_association($object_or_array$type);     
  1878.                         } elseif(is_array($object_or_array)) {
  1879.                             foreach($object_or_array as $object) {
  1880.                                 $this->save_association($object$type);    
  1881.                             }    
  1882.                         }
  1883.                     }
  1884.                 }
  1885.             }    
  1886.         }       
  1887.     }
  1888.     
  1889.     /**
  1890.      *  save the association to the database
  1891.      *  @todo Document this API
  1892.      */
  1893.     private function save_association($object, $type) {
  1894.         if(is_object($object) && get_parent_class($object) == __CLASS__ && $type) {
  1895.             //echo get_class($object)." - type:$type<br>";
  1896.             switch($type) {
  1897.                 case "has_many":
  1898.                 case "has_one":
  1899.                     $primary_key = $this->primary_keys[0];
  1900.                     $foreign_key Inflector::singularize($this->table_name)."_".$primary_key;
  1901.                     $object->$foreign_key $this->$primary_key
  1902.                     //echo "fk:$foreign_key = ".$this->$primary_key."<br>";
  1903.                     break;
  1904.             }
  1905.             $object->save();        
  1906.         }            
  1907.     }
  1908.  
  1909.     /**
  1910.      *  Deletes the record with the given $id or if you have done a
  1911.      *  $model = $model->find($id), then $model->delete() it will delete
  1912.      *  the record it just loaded from the find() without passing anything
  1913.      *  to delete(). If an array of ids is provided, all ids in array are deleted.
  1914.      *  @uses $errors
  1915.      *  @todo Document this API
  1916.      */
  1917.     function delete($id = null) {
  1918.         $deleted_ids = array();
  1919.         $primary_key_value = null;
  1920.         $primary_key = $this->primary_keys[0];
  1921.         if(is_null($id)) {
  1922.             # Primary key's where clause from already loaded values
  1923.             $conditions = $this->get_primary_key_conditions();
  1924.             $deleted_ids[$this->$primary_key;
  1925.         } elseif(!is_array($id)) {         
  1926.             $deleted_ids[] = $id;
  1927.             $id = $this->attribute_is_string($primary_key"'".$id."'" $id;
  1928.             $conditions "{$primary_key} = {$id}";
  1929.         } elseif(is_array($id)) {
  1930.             $deleted_ids = $id;
  1931.             $ids = ($this->attribute_is_string($primary_key)) 
  1932.                 "'".implode("','"$id)."'" 
  1933.                 implode(','$id);
  1934.             $conditions "{$primary_key} IN ({$ids})";
  1935.         }
  1936.  
  1937.         if(is_null($conditions)) {
  1938.             $this->errors["No conditions specified to delete on.";
  1939.             return false;
  1940.         }
  1941.  
  1942.         $this->before_delete()
  1943.         if($result $this->delete_all($conditions)) {
  1944.             foreach($deleted_ids as $id) {
  1945.                 if($this->auto_delete_habtm && $id != ''{
  1946.                     if(is_string($this->has_and_belongs_to_many)) {
  1947.                         $habtms = explode(",", $this->has_and_belongs_to_many);
  1948.                         foreach($habtms as $other_table_name{
  1949.                             $this->delete_all_habtm_records(trim($other_table_name)$id);                             
  1950.                         }
  1951.                     } elseif(is_array($this->has_and_belongs_to_many)) {
  1952.                         foreach($this->has_and_belongs_to_many as $other_table_name => $values{
  1953.                             $this->delete_all_habtm_records($other_table_name$id);                             
  1954.                         }
  1955.                     } 
  1956.                 }
  1957.             }
  1958.             $this->after_delete();
  1959.         }
  1960.         
  1961.         return $result;
  1962.     }
  1963.  
  1964.     /**
  1965.      *  Delete from table all rows that match argument
  1966.      *
  1967.      *  Delete the row(s), if any, matching the argument.
  1968.      *  @param string $conditions SQL argument to "WHERE" describing
  1969.      *                the rows to delete
  1970.      *  @return boolean
  1971.      *          <ul>
  1972.      *            <li>true => One or more rows were deleted</li>
  1973.      *            <li>false => $conditions was omitted</li>
  1974.      *          </ul>
  1975.      *  @uses is_error()
  1976.      *  @uses $new_record
  1977.      *  @uses $errors
  1978.      *  @uses query()
  1979.      *  @throws {@link ActiveRecordError}
  1980.      */
  1981.     function delete_all($conditions = null) {
  1982.         if(is_null($conditions)) {
  1983.             $this->errors["No conditions specified to delete on.";
  1984.             return false;
  1985.         }
  1986.  
  1987.         # Delete the record(s)
  1988.         if($this->is_error($rs $this->query("DELETE FROM {$this->table_prefix}{$this->table_nameWHERE {$conditions}"))) {
  1989.             $this->raise($rs->getMessage());
  1990.         }
  1991.         
  1992.         $this->new_record = true;
  1993.         return true;
  1994.     }
  1995.  
  1996.     /**
  1997.      *  @uses $has_and_belongs_to_many
  1998.      *  @todo Document this API
  1999.      */
  2000.     private function set_habtm_attributes($attributes) {
  2001.         if(is_array($attributes)) {
  2002.             $this->habtm_attributes = array();
  2003.             foreach($attributes as $key => $habtm_array{
  2004.                 if(is_array($habtm_array)) {
  2005.                     if(is_string($this->has_and_belongs_to_many)) {
  2006.                         if(preg_match("/\b$key\b/", $this->has_and_belongs_to_many)) {
  2007.                             $this->habtm_attributes[$key$habtm_array;
  2008.                         }
  2009.                     } elseif(is_array($this->has_and_belongs_to_many)) {
  2010.                         if(array_key_exists($key, $this->has_and_belongs_to_many)) {
  2011.                             $this->habtm_attributes[$key$habtm_array;
  2012.                         }
  2013.                     }
  2014.                 }
  2015.             }
  2016.         }
  2017.     }
  2018.  
  2019.     /**
  2020.      *
  2021.      *  @todo Document this API
  2022.      */
  2023.     private function update_habtm_records($this_foreign_value) {
  2024.         return $this->add_habtm_records($this_foreign_value);
  2025.     }
  2026.  
  2027.     /**
  2028.      *
  2029.      *  @uses is_error()
  2030.      *  @uses query()
  2031.      *  @throws {@link ActiveRecordError}
  2032.      *  @todo Document this API
  2033.      */
  2034.     private function add_habtm_records($this_foreign_value) {
  2035.         if($this_foreign_value > 0 && count($this->habtm_attributes0{
  2036.             if($this->delete_habtm_records($this_foreign_value)) {
  2037.                 reset($this->habtm_attributes);
  2038.                 foreach($this->habtm_attributes as $other_table_name => $other_foreign_values{
  2039.                     $table_name = $this->get_join_table_name($this->table_name,$other_table_name);
  2040.                     $other_foreign_key Inflector::singularize($other_table_name)."_id";
  2041.                     $this_foreign_key Inflector::singularize($this->table_name)."_id";
  2042.                     foreach($other_foreign_values as $other_foreign_value{
  2043.                         unset($attributes);
  2044.                         $attributes[$this_foreign_key] = $this_foreign_value;
  2045.                         $attributes[$other_foreign_key] = $other_foreign_value;
  2046.                         $attributes = $this->quoted_attributes($attributes);
  2047.                         $fields @implode(', 'array_keys($attributes));
  2048.                         $values @implode(', 'array_values($attributes));
  2049.                         $sql "INSERT INTO $table_name ($fieldsVALUES ($values)";
  2050.                         //echo "add_habtm_records: SQL: $sql<br>";
  2051.                         $result = $this->query($sql);
  2052.                         if ($this->is_error($result)) {
  2053.                             $this->raise($result->getMessage());
  2054.                         }
  2055.                     }
  2056.                 }
  2057.             }
  2058.         }
  2059.         return true;
  2060.     }
  2061.  
  2062.     /**
  2063.      *
  2064.      *  @uses is_error()
  2065.      *  @uses query()
  2066.      *  @throws {@link ActiveRecordError}
  2067.      *  @todo Document this API
  2068.      */
  2069.     private function delete_habtm_records($this_foreign_value) {
  2070.         if($this_foreign_value > 0 && count($this->habtm_attributes0{
  2071.             reset($this->habtm_attributes);
  2072.             foreach($this->habtm_attributes as $other_table_name => $values{
  2073.                 $this->delete_all_habtm_records($other_table_name$this_foreign_value);
  2074.             }
  2075.         }
  2076.         return true;
  2077.     }
  2078.     
  2079.     private function delete_all_habtm_records($other_table_name, $this_foreign_value) {
  2080.         if($other_table_name && $this_foreign_value > 0) {
  2081.             $habtm_table_name = $this->get_join_table_name($this->table_name,$other_table_name);
  2082.             $this_foreign_key Inflector::singularize($this->table_name)."_id";
  2083.             $sql "DELETE FROM {$habtm_table_name} WHERE {$this_foreign_key} = {$this_foreign_value}";
  2084.             //echo "delete_all_habtm_records: SQL: $sql<br>";
  2085.             $result = $this->query($sql);
  2086.             if($this->is_error($result)) {
  2087.                 $this->raise($result->getMessage());
  2088.             }            
  2089.         }
  2090.     }
  2091.  
  2092.     /**
  2093.      *  Apply automatic timestamp updates
  2094.      *
  2095.      *  If automatic timestamps are in effect (as indicated by
  2096.      *  {@link $auto_timestamps} == true) and the column named in the
  2097.      *  $field argument is of type "timestamp" and matches one of the
  2098.      *  names in {@link auto_create_timestamps} or {@link
  2099.      *  auto_update_timestamps}(as selected by {@link $new_record}),
  2100.      *  then return the current date and  time as a string formatted
  2101.      *  to insert in the database.  Otherwise return $value.
  2102.      *  @uses $new_record
  2103.      *  @uses $content_columns
  2104.      *  @uses $auto_timestamps
  2105.      *  @uses $auto_create_timestamps
  2106.      *  @uses $auto_update_timestamps
  2107.      *  @param string $field Name of a column in the table
  2108.      *  @param mixed $value Value to return if $field is not an
  2109.      *                      automatic timestamp column
  2110.      *  @return mixed Current date and time or $value
  2111.      */
  2112.     private function check_datetime($field, $value) {
  2113.         if($this->auto_timestamps{
  2114.             if(is_array($this->content_columns)) {
  2115.                 foreach($this->content_columns as $field_info{
  2116.                     if(($field_info['name'] == $field) && stristr($field_info['type'], "date")) {
  2117.                         $format = ($field_info['type'] == "date") ? $this->date_format : "{$this->date_format{$this->time_format}";
  2118.                         if($this->new_record{
  2119.                             if(in_array($field, $this->auto_create_timestamps)) {
  2120.                                 return date($format);
  2121.                             } elseif($this->preserve_null_dates && is_null($value&& !stristr($field_info['flags']"not_null")) {
  2122.                                 return null;    
  2123.                             }
  2124.                         } elseif(!$this->new_record{
  2125.                             if(in_array($field, $this->auto_update_timestamps)) {
  2126.                                 return date($format);
  2127.                             } elseif($this->preserve_null_dates && is_null($value&& !stristr($field_info['flags']"not_null")) {
  2128.                                 return null;    
  2129.                             }
  2130.                         }
  2131.                     }  
  2132.                 }
  2133.             }
  2134.         }
  2135.         return $value;
  2136.     }
  2137.  
  2138.     /**
  2139.      *  Update object attributes from list in argument
  2140.      *
  2141.      *  The elements of $attributes are parsed and assigned to
  2142.      *  attributes of the ActiveRecord object.  Date/time fields are
  2143.      *  treated according to the
  2144.      *  {@tutorial PHPonTrax/naming.pkg#naming.naming_forms}.
  2145.      *  @param string[] $attributes List of name => value pairs giving
  2146.      *    name and value of attributes to set.
  2147.      *  @uses $auto_save_associations
  2148.      *  @todo Figure out and document how datetime fields work
  2149.      */
  2150.     function update_attributes($attributes) {
  2151.         //error_log('update_attributes()');
  2152.         if(is_array($attributes)) {
  2153.             $datetime_fields = array();
  2154.             //  Test each attribute to be updated
  2155.             //  and process according to its type
  2156.             foreach($attributes as $field => $value) {
  2157.                 # datetime / date parts check
  2158.                 if(preg_match('/^\w+\(.*i\)$/i', $field)) {
  2159.                     //  The name of this attribute ends in "(?i)"
  2160.                     //  indicating that it's part of a date or time
  2161.                     $datetime_field = substr($field, 0, strpos($field, "("));
  2162.                     if(!in_array($datetime_field, $datetime_fields)) {
  2163.                         $datetime_fields[] = $datetime_field;
  2164.                     }                                             
  2165.                     # this elseif checks if first its an object if its parent is ActiveRecord            
  2166.                 } elseif(is_object($value) && get_parent_class($value) == __CLASS__ && $this->auto_save_associations{
  2167.                     if($association_type = $this->get_association_type($field)) {
  2168.                         $this->save_associations[$association_type][$value;
  2169.                         if($association_type == "belongs_to"{
  2170.                             $primary_key = $value->primary_keys[0];
  2171.                             $foreign_key Inflector::singularize($value->table_name)."_".$primary_key;
  2172.                             $this->$foreign_key $value->$primary_key
  2173.                         }
  2174.                     }
  2175.                     # this elseif checks if its an array of objects and if its parent is ActiveRecord                
  2176.                 } elseif(is_array($value) && $this->auto_save_associations{
  2177.                     if($association_type = $this->get_association_type($field)) {
  2178.                         $this->save_associations[$association_type][$value;
  2179.                     }
  2180.                 } else {
  2181.                     //  Just a simple attribute, copy it
  2182.                     $this->$field $value;
  2183.                 }
  2184.             }
  2185.     
  2186.             //  If any date/time fields were found, assign the
  2187.             //  accumulated values to corresponding attributes
  2188.             if(count($datetime_fields)) {
  2189.                 foreach($datetime_fields as $datetime_field) {
  2190.                     $datetime_format = '';
  2191.                     $datetime_value = '';
  2192.                     if($attributes[$datetime_field."(1i)"]
  2193.                         && $attributes[$datetime_field."(2i)"]
  2194.                         && $attributes[$datetime_field."(3i)"]) {
  2195.                         $datetime_value = $attributes[$datetime_field."(1i)"]
  2196.                         . "-" . $attributes[$datetime_field."(2i)"]
  2197.                         . "-" . $attributes[$datetime_field."(3i)"];
  2198.                         $datetime_format = $this->date_format;
  2199.                     }
  2200.                     $datetime_value .= " ";
  2201.                     if($attributes[$datetime_field."(4i)"]
  2202.                         && $attributes[$datetime_field."(5i)"]) {
  2203.                         $datetime_value .= $attributes[$datetime_field."(4i)"]
  2204.                         . ":" . $attributes[$datetime_field."(5i)"];
  2205.                         $datetime_format .= " ".$this->time_format;                        
  2206.                     }    
  2207.                     if($datetime_value = trim($datetime_value)) {
  2208.                         $datetime_value = date($datetime_format, strtotime($datetime_value));
  2209.                         //error_log("($field) $datetime_field = $datetime_value");
  2210.                         $this->$datetime_field $datetime_value;    
  2211.                     }
  2212.                 }    
  2213.             }
  2214.             $this->set_habtm_attributes($attributes);
  2215.         }
  2216.     }
  2217.     
  2218.     /**
  2219.      * If a composite object was specified via $composed_of, then its values 
  2220.      * mapped to the model will overwrite the models values.
  2221.      *
  2222.      */
  2223.     function update_composite_attributes() {
  2224.         if(is_array($this->composed_of)) {
  2225.             foreach($this->composed_of as $name => $options{
  2226.                 $composite_object = $this->$name;
  2227.                 if(is_array($options&& is_object($composite_object)) {
  2228.                     if(is_array($options['mapping'])) {
  2229.                         foreach($options['mapping'] as $database_name => $composite_name) {
  2230.                             $this->$database_name $composite_object->$composite_name;                          
  2231.                         }    
  2232.                     }        
  2233.                 }       
  2234.             }    
  2235.         }    
  2236.     }    
  2237.  
  2238.     /**
  2239.      *  Return pairs of column-name:column-value
  2240.      *
  2241.      *  Return the contents of the object as an array of elements
  2242.      *  where the key is the column name and the value is the column
  2243.      *  value.  Relies on a previous call to
  2244.      *  {@link set_content_columns()} for information about the format
  2245.      *  of a row in the table.
  2246.      *  @uses $content_columns
  2247.      *  @see set_content_columns
  2248.      *  @see quoted_attributes()
  2249.      */
  2250.     function get_attributes() {
  2251.         $attributes = array();
  2252.         if(is_array($this->content_columns)) {
  2253.             foreach($this->content_columns as $column{
  2254.                 //echo "attribute: $info[name] -> {$this->$info[name]}<br>";
  2255.                 $attributes[$column['name']] = $this->$column['name'];
  2256.             }
  2257.         }
  2258.         return $attributes;
  2259.     }
  2260.  
  2261.     /**
  2262.      *  Return pairs of column-name:quoted-column-value
  2263.      *
  2264.      *  Return pairs of column-name:quoted-column-value where the key
  2265.      *  is the column name and the value is the column value with
  2266.      *  automatic timestamp updating applied and characters special to
  2267.      *  SQL quoted.
  2268.      *  
  2269.      *  If $attributes is null or omitted, return all columns as
  2270.      *  currently stored in {@link content_columns()}.  Otherwise,
  2271.      *  return the name:value pairs in $attributes.
  2272.      *  @param string[] $attributes Name:value pairs to return.
  2273.      *    If null or omitted, return the column names and values
  2274.      *    of the object as stored in $content_columns.
  2275.      *  @return string[] 
  2276.      *  @uses get_attributes()
  2277.      *  @see set_content_columns()
  2278.      */
  2279.     function quoted_attributes($attributes = null) {
  2280.         if(is_null($attributes)) {
  2281.             $attributes = $this->get_attributes();
  2282.         }
  2283.         $return = array();
  2284.         foreach($attributes as $name => $value) {           
  2285.             $return[$name] = $this->quote_attribute($name$value);
  2286.         }
  2287.         return $return;
  2288.     }
  2289.  
  2290.     /**
  2291.      *  Quotes a single attribute for use in an sql statement.
  2292.      *
  2293.      */    
  2294.     function quote_attribute($attribute, $value = null) {
  2295.         $value = is_null($value) ? $this->$attribute $value;
  2296.         $value $this->check_datetime($attribute$value);        
  2297.         $column $this->column_for_attribute($attribute);
  2298.         if(isset($column['mdb2type'])) {
  2299.             $type = $column['mdb2type'];
  2300.         } else {
  2301.             $type = $this->attribute_is_string($attribute$column
  2302.                 "text" is_float($attribute"float" "integer"
  2303.         }            
  2304.         $value = self::$db->quote($value$type);    
  2305.         if($value === 'NULL' && stristr($column['flags']"not_null")) {
  2306.             $value = "''";    
  2307.         } 
  2308.         return $value;               
  2309.     }
  2310.  
  2311.     /**
  2312.      *  Escapes a string for use in an sql statement.
  2313.      *
  2314.      */    
  2315.     function escape($string) {
  2316.         return(self::$db->escape($string));
  2317.     }    
  2318.  
  2319.     /**
  2320.      *  Return column values for SQL insert statement
  2321.      *
  2322.      *  Return an array containing the column names and values of this
  2323.      *  object, filtering out the primary keys, which are not set.
  2324.      *
  2325.      *  @uses $primary_keys
  2326.      *  @uses quoted_attributes()
  2327.      */
  2328.     function get_inserts() {
  2329.         $attributes = $this->quoted_attributes();
  2330.         $inserts array();
  2331.         foreach($attributes as $key => $value{
  2332.             if(!in_array($key, $this->primary_keys|| ($value != "''" && isset($value))) {
  2333.                 $inserts[$key] = $value;
  2334.             }
  2335.         }
  2336.         return $inserts;
  2337.     }
  2338.  
  2339.     /**
  2340.      *  Return argument for a "WHERE" clause specifying this row
  2341.      *
  2342.      *  Returns a string which specifies the column(s) and value(s)
  2343.      *  which describe the primary key of this row of the associated
  2344.      *  table.  The primary key must be one or more attributes of the
  2345.      *  object and must be listed in {@link $content_columns} as
  2346.      *  columns in the row.
  2347.      *
  2348.      *  Example: if $primary_keys = array("id", "ssn") and column "id"
  2349.      *  has value "5" and column "ssn" has value "123-45-6789" then
  2350.      *  the string "id = 5 AND ssn = '123-45-6789'" would be returned.
  2351.      *  @uses $primary_keys
  2352.      *  @uses quoted_attributes()
  2353.      *  @return string Column name = 'value' [ AND name = 'value']...
  2354.      */
  2355.     function get_primary_key_conditions($operator = "=") {
  2356.         $conditions = null;
  2357.         $attributes = $this->quoted_attributes();
  2358.         if(count($attributes0{
  2359.             $conditions = array();
  2360.             # run through our fields and join them with their values
  2361.             foreach($attributes as $key => $value) {
  2362.                 if(in_array($key, $this->primary_keys&& isset($value&& $value != "''"{
  2363.                     $conditions[] = "{$key} {$operator} {$value}";    
  2364.                 }
  2365.             }
  2366.             $conditions = implode(" AND ", $conditions);
  2367.         }
  2368.         return $conditions;
  2369.     }
  2370.  
  2371.     /**
  2372.      *  Return column values of object formatted for SQL update statement
  2373.      *
  2374.      *  Return a string containing the column names and values of this
  2375.      *  object in a format ready to be inserted in a SQL UPDATE
  2376.      *  statement.  Automatic update has been applied to timestamps if
  2377.      *  enabled and characters special to SQL have been quoted.
  2378.      *  @uses quoted_attributes()
  2379.      *  @return string Column name = 'value', ... for all attributes
  2380.      */
  2381.     function get_updates_sql() {
  2382.         $updates = null;
  2383.         $attributes = $this->quoted_attributes();
  2384.         if(count($attributes0{
  2385.             $updates = array();
  2386.             # run through our fields and join them with their values
  2387.             foreach($attributes as $key => $value) {
  2388.                 if($key && isset($value) && !in_array($key, $this->primary_keys)) {
  2389.                     $updates[] = "$key = $value";
  2390.                 }
  2391.             }
  2392.             $updates = implode(", ", $updates);
  2393.         }
  2394.         return $updates;
  2395.     }
  2396.  
  2397.     /**
  2398.      *  Set {@link $table_name} from the class name of this object
  2399.      *
  2400.      *  By convention, the name of the database table represented by
  2401.      *  this object is derived from the name of the class.
  2402.      *  @uses Inflector::tableize()
  2403.      */
  2404.     function set_table_name_using_class_name() {
  2405.         if(!$this->table_name{
  2406.             $class_name = $this->get_class_name();
  2407.             $this->table_name = Inflector::tableize($class_name);
  2408.         }
  2409.     }
  2410.  
  2411.     /**
  2412.      *  Get class name of child object 
  2413.      *
  2414.      *  this will return the manually set name or get_class($this)
  2415.      *  @return string child class name
  2416.      */    
  2417.     private function get_class_name() {
  2418.         return !is_null($this->class_name$this->class_name : get_class($this);                
  2419.     }
  2420.  
  2421.     /**
  2422.      *  Populate object with information about the table it represents 
  2423.      *
  2424.      *  Call {@link 
  2425.      *  http://pear.php.net/manual/en/package.database.db.db-common.tableinfo.php
  2426.      *  DB_common::tableInfo()} to get a description of the table and
  2427.      *  store it in {@link $content_columns}.  Add a more human
  2428.      *  friendly name to the element for each column.
  2429.      *  @uses $db
  2430.      *  @uses $content_columns
  2431.      *  @uses Inflector::humanize()
  2432.      *  @see __set()
  2433.      *  @param string $table_name  Name of table to get information about
  2434.      */
  2435.     function set_content_columns($table_name) {
  2436.         if(!is_null($this->table_prefix)) {
  2437.             $table_name = $this->table_prefix.$table_name;
  2438.         }
  2439.         if(isset(self::$table_info[$table_name])) {
  2440.             $this->content_columns = self::$table_info[$table_name];  
  2441.         } else {
  2442.             self::$db->loadModule('Reverse'nulltrue);
  2443.             $this->content_columns = self::$db->reverse->tableInfo($table_name);
  2444.             if($this->is_error($this->content_columns)) {
  2445.                 $this->raise($this->content_columns->getMessage());        
  2446.             }
  2447.             if(is_array($this->content_columns)) {
  2448.                 $i = 0;
  2449.                 foreach($this->content_columns as $column{
  2450.                     $this->content_columns[$i++]['human_name'Inflector::humanize($column['name']);
  2451.                 }                
  2452.                 self::$table_info[$table_name] = $this->content_columns;
  2453.             }
  2454.         }
  2455.     }
  2456.  
  2457.     /**
  2458.      *  Returns the autogenerated id from the last insert query
  2459.      *
  2460.      *  @uses $db
  2461.      *  @uses is_error()
  2462.      *  @uses raise()
  2463.      *  @throws {@link ActiveRecordError}
  2464.      */
  2465.     function get_insert_id() {
  2466.         // fetch the last inserted id via autoincrement or current value of a sequence
  2467.         if(self::$db->supports('auto_increment'=== true{
  2468.             $id = self::$db->lastInsertID("{$this->table_prefix}{$this->table_name}", $this->primary_keys[0]);   
  2469.             if($this->is_error($id)) {
  2470.                 $this->raise($id->getMessage());
  2471.             } 
  2472.             return $id;                
  2473.         }
  2474.  
  2475.         return null;
  2476.     }
  2477.  
  2478.     /**
  2479.      *  Open a database connection if one is not currently open
  2480.      *
  2481.      *  The name of the database normally comes from
  2482.      *  $database_settings which is set in {@link
  2483.      *  environment.php} by reading file config/database.ini. The
  2484.      *  database name may be overridden by assigning a different name
  2485.      *  to {@link $database_name}. 
  2486.      *  
  2487.      *  If there is a connection now open, as indicated by the saved
  2488.      *  value of a MDB2 object in $active_connections[$connection_name], and
  2489.      *  {@link force_reconnect} is not true, then set the database
  2490.      *  fetch mode and return.
  2491.      *
  2492.      *  If there is no connection, open one and save a reference to
  2493.      *  it in $active_connections[$connection_name].
  2494.      *
  2495.      *  @uses $db
  2496.      *  @uses $database_name
  2497.      *  @uses $force_reconnect
  2498.      *  @uses $active_connections
  2499.      *  @uses is_error()
  2500.      *  @throws {@link ActiveRecordError}
  2501.      */
  2502.     function establish_connection() {
  2503.         $connection =& self::$active_connections[$this->connection_name];
  2504.         if(!is_object($connection|| $this->force_reconnect{
  2505.             $connection_settings = array();
  2506.             $connection_options = array();
  2507.             if(array_key_exists($this->connection_nameself::$database_settings)) {
  2508.                  # Use a different custom sections settings ?
  2509.                 if(array_key_exists("use", self::$database_settings[$this->connection_name])) {
  2510.                     $connection_settings = self::$database_settings[self::$database_settings[$this->connection_name]['use']];
  2511.                 } else {
  2512.                     # Custom defined db settings in database.ini 
  2513.                     $connection_settings = self::$database_settings[$this->connection_name];
  2514.                 }
  2515.             } else {
  2516.                 # Just use the current TRAX_ENV's environment db settings
  2517.                 # $this->connection_name's default value is TRAX_ENV so
  2518.                 # if should never really get here unless override $this->connection_name
  2519.                 # and you define a custom db section in database.ini and it can't find it.
  2520.                 $connection_settings = self::$database_settings[TRAX_ENV];
  2521.             }
  2522.             # Override database name if param is set
  2523.             if($this->database_name{
  2524.                 $connection_settings['database'] = $this->database_name;               
  2525.             }            
  2526.             # Set optional Pear parameters
  2527.             if(isset($connection_settings['persistent'])) {
  2528.                 $connection_options['persistent'] = $connection_settings['persistent'];
  2529.             }
  2530.             # Connect to the database and throw an error if the connect fails.
  2531.             $connection =& MDB2::Connect($connection_settings, $connection_options);
  2532.             //static $connect_cnt;  $connect_cnt++; error_log("connection #".$connect_cnt);
  2533.             
  2534.             # For Postgres schemas (http://www.postgresql.org/docs/8.0/interactive/ddl-schemas.html)
  2535.             if(isset($connection_settings['schema_search_path'])){
  2536.                 if(!$this->is_error($connection)) {
  2537.                     # Set the schema search path to a string of comma-separated schema names.
  2538.                     # First strip out all the whitespace
  2539.                     $connection->query('SET search_path TO '.preg_replace('/\s+/'''$connection_settings['schema_search_path']));
  2540.                 }
  2541.             } 
  2542.         }
  2543.         if(!$this->is_error($connection)) {
  2544.             self::$active_connections[$this->connection_name=$connection;
  2545.             self::$db =$connection;
  2546.             self::$db->setFetchMode($this->fetch_mode);
  2547.         } else {
  2548.             $this->raise($connection->getMessage());
  2549.         }      
  2550.         return self::$db;
  2551.     }
  2552.  
  2553.     /**
  2554.      *  Determine if passed in attribute (table column) is a string
  2555.      *  @param string $attribute Name of the table column
  2556.      *  @uses column_for_attribute()
  2557.      */    
  2558.     function attribute_is_string($attribute, $column = null) {
  2559.         $column = is_null($column) ? $this->column_for_attribute($attribute$column;
  2560.         switch(strtolower($column['mdb2type'])) {
  2561.             case 'text':
  2562.             case 'timestamp':
  2563.             case 'date':
  2564.             case 'time':
  2565.             case 'blob':
  2566.             case 'clob':
  2567.                 return true;       
  2568.         }
  2569.         return false;        
  2570.     }
  2571.  
  2572.     /**
  2573.      *  Determine if passed in name is a composite class or not
  2574.      *  @param string $name Name of the composed_of mapping
  2575.      *  @uses $composed_of
  2576.      */    
  2577.     private function is_composite($name) {
  2578.         if(is_array($this->composed_of)) {
  2579.             if(array_key_exists($name, $this->composed_of)) {
  2580.                 return true;     
  2581.             }
  2582.         }        
  2583.         return false;
  2584.     }    
  2585.  
  2586.     /**
  2587.      *  Runs validation routines for update or create
  2588.      *
  2589.      *  @uses after_validation();
  2590.      *  @uses after_validation_on_create();
  2591.      *  @uses after_validation_on_update();
  2592.      *  @uses before_validation();
  2593.      *  @uses before_validation_on_create();
  2594.      *  @uses before_validation_on_update();
  2595.      *  @uses $errors
  2596.      *  @uses $new_record
  2597.      *  @uses validate();
  2598.      *  @uses validate_model_attributes();
  2599.      *  @uses validate_builtin();
  2600.      *  @uses validate_on_create(); 
  2601.      *  @return boolean 
  2602.      *    <ul>
  2603.      *      <li>true => Valid, no errors found.
  2604.      *        {@link $errors} is empty</li>
  2605.      *      <li>false => Not valid, errors in {@link $errors}</li>
  2606.      *    </ul>
  2607.      */
  2608.     function valid() {
  2609.         # first clear the errors array
  2610.         $this->errors = array();
  2611.  
  2612.         if($this->new_record{
  2613.             $this->before_validation();
  2614.             $this->before_validation_on_create();
  2615.             $this->validate();
  2616.             $this->validate_model_attributes();
  2617.             $this->validate_builtin();            
  2618.             $this->after_validation();
  2619.             $this->validate_on_create()
  2620.             $this->after_validation_on_create();
  2621.         } else {
  2622.             $this->before_validation();
  2623.             $this->before_validation_on_update();
  2624.             $this->validate();
  2625.             $this->validate_model_attributes();
  2626.             $this->validate_builtin();
  2627.             $this->after_validation();
  2628.             $this->validate_on_update();
  2629.             $this->validate_on_update_builtin();
  2630.             $this->after_validation_on_update();
  2631.         }
  2632.  
  2633.         return count($this->errorsfalse true;
  2634.     }
  2635.  
  2636.     /**
  2637.      *  Call every method named "validate_*()" where * is a column name
  2638.      *
  2639.      *  Find and call every method named "validate_something()" where
  2640.      *  "something" is the name of a column.  The "validate_something()"
  2641.      *  functions are expected to return an array whose first element
  2642.      *  is true or false (indicating whether or not the validation
  2643.      *  succeeded), and whose second element is the error message to
  2644.      *  display if the first element is false.
  2645.      *
  2646.      *  @return boolean 
  2647.      *    <ul>
  2648.      *      <li>true => Valid, no errors found.
  2649.      *        {@link $errors} is empty</li>
  2650.      *      <li>false => Not valid, errors in {@link $errors}.
  2651.      *        $errors is an array whose keys are the names of columns,
  2652.      *        and the value of each key is the error message returned
  2653.      *        by the corresponding validate_*() method.</li>
  2654.      *    </ul>
  2655.      *  @uses $errors
  2656.      *  @uses get_attributes()
  2657.      */
  2658.     function validate_model_attributes() {
  2659.         $validated_ok = true;
  2660.         $attrs = $this->get_attributes();
  2661.         $methods get_class_methods($this->get_class_name());
  2662.         foreach($methods as $method{
  2663.             if(preg_match('/^validate_(.+)/', $method, $matches)) {
  2664.                 # If we find, for example, a method named validate_name, then
  2665.                 # we know that that function is validating the 'name' attribute
  2666.                 # (as found in the (.+) part of the regular expression above).
  2667.                 $validate_on_attribute = $matches[1];
  2668.                 # Check to see if the string found (e.g. 'name') really is
  2669.                 # in the list of attributes for this object...
  2670.                 if(array_key_exists($validate_on_attribute, $attrs)) {
  2671.                     # ...if so, then call the method to see if it validates to true...
  2672.                     $result = $this->$method();
  2673.                     if(is_array($result)) {
  2674.                         # $result[0] is true if validation went ok, false otherwise
  2675.                         # $result[1] is the error message if validation failed
  2676.                         if($result[0] == false) {
  2677.                             # ... and if not, then validation failed
  2678.                             $validated_ok = false;
  2679.                             # Mark the corresponding entry in the error array by
  2680.                             # putting the error message in for the attribute,
  2681.                             #   e.g. $this->errors['name'] = "can't be empty"
  2682.                             #   when 'name' was an empty string.
  2683.                             $this->errors[$validate_on_attribute$result[1];
  2684.                         }
  2685.                     }
  2686.                 }
  2687.             }
  2688.         }
  2689.         return $validated_ok;
  2690.     }  
  2691.     
  2692.     /**
  2693.      *  Overwrite this method for validation checks on all saves and
  2694.      *  use $this->errors[] = "My error message."; or
  2695.      *  for invalid attributes $this->errors['attribute'] = "Attribute is invalid.";
  2696.      *  @todo Document this API
  2697.      */
  2698.     function validate() {}
  2699.  
  2700.     /**
  2701.      *  Override this method for validation checks used only on creation.
  2702.      *  @todo Document this API
  2703.      */
  2704.     function validate_on_create() {}
  2705.  
  2706.     /**
  2707.      *  Override this method for validation checks used only on updates.
  2708.      *  @todo Document this API
  2709.      */
  2710.     function validate_on_update() {}
  2711.  
  2712.     /**
  2713.      *  Is called before validate().
  2714.      *  @todo Document this API
  2715.      */
  2716.     function before_validation() {}
  2717.  
  2718.     /**
  2719.      *  Is called after validate().
  2720.      *  @todo Document this API
  2721.      */
  2722.     function after_validation() {}
  2723.  
  2724.     /**
  2725.      *  Is called before validate() on new objects that haven't been saved yet (no record exists).
  2726.      *  @todo Document this API
  2727.      */
  2728.     function before_validation_on_create() {}
  2729.  
  2730.     /**
  2731.      *  Is called after validate() on new objects that haven't been saved yet (no record exists).
  2732.      *  @todo Document this API
  2733.      */
  2734.     function after_validation_on_create()  {}
  2735.  
  2736.     /**
  2737.      *  Is called before validate() on existing objects that has a record.
  2738.      *  @todo Document this API
  2739.      */
  2740.     function before_validation_on_update() {}
  2741.  
  2742.     /**
  2743.      *  Is called after validate() on existing objects that has a record.
  2744.      *  @todo Document this API
  2745.      */
  2746.     function after_validation_on_update()  {}
  2747.  
  2748.     /**
  2749.      *  Is called before save() (regardless of whether its a create or update save)
  2750.      *  @todo Document this API
  2751.      */
  2752.     function before_save() {}
  2753.  
  2754.     /**
  2755.      *  Is called after save (regardless of whether its a create or update save).
  2756.      *  @todo Document this API
  2757.      */
  2758.     function after_save() {}
  2759.  
  2760.     /**
  2761.      *  Is called before save() on new objects that havent been saved yet (no record exists).
  2762.      *  @todo Document this API
  2763.      */
  2764.     function before_create() {}
  2765.  
  2766.     /**
  2767.      *  Is called after save() on new objects that havent been saved yet (no record exists).
  2768.      *  @todo Document this API
  2769.      */
  2770.     function after_create() {}
  2771.  
  2772.     /**
  2773.      *  Is called before save() on existing objects that has a record.
  2774.      *  @todo Document this API
  2775.      */
  2776.     function before_update() {}
  2777.  
  2778.     /**
  2779.      *  Is called after save() on existing objects that has a record.
  2780.      *  @todo Document this API
  2781.      */
  2782.     function after_update() {}
  2783.  
  2784.     /**
  2785.      *  Is called before delete().
  2786.      *  @todo Document this API
  2787.      */
  2788.     function before_delete() {}
  2789.  
  2790.     /**
  2791.      *  Is called after delete().
  2792.      *  @todo Document this API
  2793.      */
  2794.     function after_delete() {}
  2795.  
  2796.  
  2797.     /**
  2798.      *  Validates any builtin validates_* functions defined as 
  2799.      *  class variables in child model class.
  2800.      *
  2801.      *  eg. 
  2802.      *  public $validates_presence_of = array(
  2803.      *      'first_name' => array(
  2804.      *          'message' => "is not optional.",
  2805.      *          'on' => 'update'
  2806.      *      ),
  2807.      *      'last_name' => null,
  2808.      *      'password' => array(
  2809.      *          'on' => 'create'
  2810.      *      )
  2811.      *  );
  2812.      *
  2813.      *  @uses $builtin_validation_functions
  2814.      */
  2815.     function validate_builtin() {
  2816.         foreach($this->builtin_validation_functions as $method_name{
  2817.             $validation_name = $this->$method_name;
  2818.             if(is_string($validation_name)) {
  2819.                 $validation_name = explode(",", $validation_name);    
  2820.             }
  2821.             if(method_exists($this, $method_name) && is_array($validation_name)) {
  2822.                 foreach($validation_name as $attribute_name => $options) {
  2823.                     if(!is_array($options)) {
  2824.                         $attribute_name = $options;
  2825.                         $options = array();
  2826.                     }               
  2827.                     $attribute_name = trim($attribute_name);     
  2828.                     $parameters = array();
  2829.                     $on = array_key_exists('on', $options) ? 
  2830.                         $options['on'] : 'save';
  2831.                     $message = array_key_exists('message', $options) ? 
  2832.                         $options['message'] : null;                  
  2833.                     switch($method_name) {
  2834.                         case 'validates_acceptance_of':
  2835.                             $accept = array_key_exists('accept', $options) ? $options['accept'] : 1;
  2836.                             $parameters = array($attribute_name, $message, $accept);
  2837.                             break;
  2838.                         case 'validates_confirmation_of':
  2839.                             $parameters = array($attribute_name, $message);                            
  2840.                             break;
  2841.                         case 'validates_exclusion_of'
  2842.                             $in = array_key_exists('in', $options) ? $options['in'] : array();
  2843.                             $parameters = array($attribute_name, $in, $message);  
  2844.                             break;     
  2845.                         case 'validates_format_of':
  2846.                             $with = array_key_exists('with', $options) ? $options['with'] : '';
  2847.                             $parameters = array($attribute_name, $with, $message);
  2848.                             break;
  2849.                         case 'validates_inclusion_of':   
  2850.                             $in = array_key_exists('in', $options) ? $options['in'] : array();
  2851.                             $parameters = array($attribute_name, $in, $message);   
  2852.                             break;  
  2853.                         case 'validates_length_of':
  2854.                             $parameters = array($attribute_name, $options);
  2855.                             break;
  2856.                         case 'validates_numericality_of':  
  2857.                             $only_integer = array_key_exists('only_integer', $options) ? 
  2858.                                 $options['only_integer'] : false;
  2859.                             $allow_null = array_key_exists('allow_null', $options) ? 
  2860.                                 $options['allow_null'] : false;                          
  2861.                             $parameters = array($attribute_name, $message, $only_integer, $allow_null);
  2862.                             break;      
  2863.                         case 'validates_presence_of':   
  2864.                             $parameters = array($attribute_name, $message);  
  2865.                             break;   
  2866.                         case 'validates_uniqueness_of':  
  2867.                             $parameters = array($attribute_name, $message);
  2868.                             break;                       
  2869.                     }
  2870.                     if(count($parameters)) { 
  2871.                         $call = false;
  2872.                         if($on == 'create' && $this->new_record{
  2873.                             $call = true;
  2874.                         } elseif($on == 'update' && !$this->new_record{
  2875.                             $call = true;
  2876.                         } elseif($on == 'save') {
  2877.                             $call = true;         
  2878.                         }       
  2879.                         if($call) {
  2880.                             # error_log("calling $method_name(".implode(",",$parameters).")");
  2881.                             call_user_func_array(array($this, $method_name), $parameters);        
  2882.                         }                              
  2883.                     }
  2884.                 }
  2885.             }             
  2886.         }        
  2887.     }
  2888.  
  2889.     /**
  2890.      * Validates that a checkbox is clicked.
  2891.      * eg. validates_acceptance_of('eula')
  2892.      *
  2893.      * @param string|array $attribute_names
  2894.      * @param string $message
  2895.      * @param string $accept
  2896.      */
  2897.     function validates_acceptance_of($attribute_names, $message = null, $accept = 1) {
  2898.         $message = $this->get_error_message_for_validation($message'acceptance');        
  2899.         foreach((array) $attribute_names as $attribute_name{                    
  2900.             if($this->$attribute_name != $accept{
  2901.                 $attribute_human = Inflector::humanize($attribute_name);
  2902.                 $this->add_error("{$attribute_human} {$message}", $attribute_name);
  2903.             }
  2904.         }
  2905.     }
  2906.  
  2907.     /**
  2908.      * Validates that a field has the same value as its corresponding confirmation field.
  2909.      * eg. validates_confirmation_of('password')
  2910.      *
  2911.      * @param string|array $attribute_names
  2912.      * @param string $message
  2913.      */
  2914.     function validates_confirmation_of($attribute_names, $message = null) {
  2915.         $message = $this->get_error_message_for_validation($message'confirmation');
  2916.         foreach((array) $attribute_names as $attribute_name{            
  2917.             $attribute_confirmation = $attribute_name . '_confirmation';
  2918.             if($this->$attribute_confirmation != $this->$attribute_name{
  2919.                 $attribute_human = Inflector::humanize($attribute_name);
  2920.                 $this->add_error("{$attribute_human} {$message}", $attribute_name);
  2921.             }
  2922.         }
  2923.     }
  2924.  
  2925.     /**
  2926.      * Validates that specified attributes are NOT in an array of elements.
  2927.      * eg. validates_exclusion_of('age, 'in' => array(13, 19))
  2928.      *
  2929.      * @param string|array $attribute_names
  2930.      * @param mixed $in array(1,2,3,4,5) or string 1..5
  2931.      * @param string $message
  2932.      */
  2933.     function validates_exclusion_of($attribute_names, $in = array(), $message = null) {
  2934.         $message = $this->get_error_message_for_validation($message'exclusion');
  2935.         foreach((array) $attribute_names as $attribute_name{                    
  2936.             if(is_string($in)) {
  2937.                 list($minimum, $maximum) = explode('..', $in);
  2938.                 if($this->$attribute_name >= $minimum && $this->$attribute_name <= $maximum{
  2939.                     $attribute_human = Inflector::humanize($attribute_name);
  2940.                     $this->add_error("{$attribute_human} {$message}", $attribute_name);        
  2941.                 }
  2942.             } elseif(is_array($in)) {
  2943.                 if(in_array($this->$attribute_name$in)) {
  2944.                     $attribute_human = Inflector::humanize($attribute_name);
  2945.                     $this->add_error("{$attribute_human} {$message}", $attribute_name);
  2946.                 }
  2947.             }   
  2948.         }
  2949.     }
  2950.  
  2951.     /**
  2952.      * Validates that specified attributes matches a regular expression
  2953.      * eg. validates_format_of('email', '/^(+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i')
  2954.      *
  2955.      * @param string|array $attribute_names
  2956.      * @param string $regex
  2957.      * @param string $message
  2958.      */
  2959.     function validates_format_of($attribute_names, $regex, $message = null) {
  2960.         $message = $this->get_error_message_for_validation($message'invalid');        
  2961.         foreach((array) $attribute_names as $attribute_name{                                
  2962.             $value = $this->$attribute_name;        
  2963.             # Was there an error?
  2964.             if(!preg_match($regex$value)) {
  2965.                 $attribute_human = Inflector::humanize($attribute_name);
  2966.                 $this->add_error("{$attribute_human} {$message}", $attribute_name);
  2967.             }
  2968.         }
  2969.     }
  2970.  
  2971.     /**
  2972.      * Validates that specified attributes are in an array of elements.
  2973.      * eg. validates_inclusion_of('gender', array('m', 'f'))
  2974.      *
  2975.      * @param string|array $attribute_names
  2976.      * @param mixed $in array(1,2,3,4,5) or string 1..5
  2977.      * @param string $message
  2978.      */
  2979.     function validates_inclusion_of($attribute_names, $in = array(), $message = null) {
  2980.         $message = $this->get_error_message_for_validation($message'inclusion');
  2981.         foreach((array) $attribute_names as $attribute_name{                    
  2982.             if(is_string($in)) {
  2983.                 list($minimum, $maximum) = explode('..', $in);
  2984.                 if(!($this->$attribute_name >= $minimum && $this->$attribute_name <= $maximum)) {
  2985.                     $attribute_human = Inflector::humanize($attribute_name);
  2986.                     $this->add_error("{$attribute_human} {$message}", $attribute_name);        
  2987.                 }
  2988.             } elseif(is_array($in)) {
  2989.                 if(!in_array($this->$attribute_name$in)) {
  2990.                     $attribute_human = Inflector::humanize($attribute_name);
  2991.                     $this->add_error("{$attribute_human} {$message}", $attribute_name);
  2992.                 }
  2993.             } 
  2994.         }
  2995.     }
  2996.  
  2997.     /**
  2998.      * Validates that specified attributes are of some length
  2999.      * eg. validates_length_of('password', array('minimum' => 8))
  3000.      *
  3001.      * @param string|array $attribute_names
  3002.      * @param array $options
  3003.      */    
  3004.     function validates_length_of($attribute_names, $options = array(
  3005.         'too_short' => null, 'too_long' => null, 'wrong_length' => null, 'message' => null)) {                        
  3006.         # Convert 'in' to 'minimum' and 'maximum'
  3007.         if(isset($options['in'])) {
  3008.             list($options['minimum'], $options['maximum']) = explode('..', $options['in']);
  3009.         }
  3010.         # If 'message' is set see if we need to override other messages
  3011.         if(isset($options['message'])) {
  3012.             if(!isset($options['too_short'])) $options['too_short'] = $options['message'];
  3013.             if(!isset($options['too_long'])) $options['too_long'] = $options['message'];
  3014.             if(!isset($options['wrong_length'])) $options['wrong_length'] = $options['message'];        
  3015.         }
  3016.                 
  3017.         foreach((array) $attribute_names as $attribute_name) {            
  3018.             # Attribute string length
  3019.             $len = strlen($this->$attribute_name);
  3020.             $attribute_human Inflector::humanize($attribute_name);
  3021.             
  3022.             # If you have set the min length option
  3023.             if(isset($options['minimum'])) {
  3024.                 $message = $this->get_error_message_for_validation($options['too_short']'too_short'$options['minimum']);
  3025.                 if($len $options['minimum']{
  3026.                     $this->add_error("{$attribute_human} {$message}", $attribute_name);
  3027.                 }
  3028.             }
  3029.             
  3030.             # If you have set the max length option
  3031.             if(isset($options['maximum'])) {
  3032.                 $message = $this->get_error_message_for_validation($options['too_long']'too_long'$options['maximum']);
  3033.                 if($len $options['maximum']{
  3034.                     $this->add_error("{$attribute_human} {$message}", $attribute_name);
  3035.                 }
  3036.             }
  3037.             
  3038.             # If you have set an exact length option
  3039.             if(isset($options['is'])) {
  3040.                 $message = $this->get_error_message_for_validation($options['wrong_length']'wrong_length'$options['is']);
  3041.                 if($len != $options['is']{
  3042.                     $this->add_error("{$attribute_human} {$message}", $attribute_name);
  3043.                 }
  3044.             }
  3045.         }
  3046.     }
  3047.  
  3048.     /**
  3049.      * Validates that specified attributes are numbers
  3050.      * eg. validates_numericality_of('value')
  3051.      *
  3052.      * @param string|array $attribute_names
  3053.      * @param string $message
  3054.      */
  3055.     function validates_numericality_of($attribute_names, $message = null, $only_integer = false, $allow_null = false) {         
  3056.         foreach((array) $attribute_names as $attribute_name) {    
  3057.             $value = $this->$attribute_name;                
  3058.             # Skip validation if you allow null
  3059.             if($allow_null && is_null($value)) {
  3060.                 break;
  3061.             }            
  3062.             if($only_integer) {
  3063.                 $message = $this->get_error_message_for_validation($message'not_an_integer');
  3064.                 if(!is_integer($value)) {
  3065.                     $attribute_human = Inflector::humanize($attribute_name);
  3066.                     $this->add_error("{$attribute_human} {$message}", $attribute_name);
  3067.                 }
  3068.             } else {
  3069.                 $message = $this->get_error_message_for_validation($message'not_a_number');
  3070.                 if(!is_numeric($value)) {
  3071.                     $attribute_human = Inflector::humanize($attribute_name);
  3072.                     $this->add_error("{$attribute_human} {$message}", $attribute_name);
  3073.                 }
  3074.             }
  3075.         }
  3076.     }
  3077.         
  3078.     /**
  3079.      * Validates that specified attributes are not blank
  3080.      * eg. validates_presence_of(array('firstname', 'lastname'))
  3081.      *
  3082.      * @param string|array $attribute_names
  3083.      * @param string $message
  3084.      */
  3085.     function validates_presence_of($attribute_names, $message = null) {
  3086.         $message = $this->get_error_message_for_validation($message'empty');        
  3087.         foreach((array) $attribute_names as $attribute_name{                
  3088.             if($this->$attribute_name === '' || is_null($this->$attribute_name)) {
  3089.                 $attribute_human = Inflector::humanize($attribute_name);
  3090.                 $this->add_error("{$attribute_human} {$message}", $attribute_name);
  3091.             }
  3092.         }
  3093.     }
  3094.     
  3095.     /**
  3096.      * Validates that specified attributes are unique in the model database table
  3097.      * eg. validates_uniqueness_of('username')
  3098.      *
  3099.      * @param string|array $attribute_names
  3100.      * @param string $message
  3101.      */
  3102.     function validates_uniqueness_of($attribute_names, $message = null) {
  3103.         $message = $this->get_error_message_for_validation($message'taken');
  3104.         foreach((array) $attribute_names as $attribute_name{                        
  3105.             $quoted_value = $this->quote_attribute($attribute_name);
  3106.             # Conditions for new and existing record
  3107.             if($this->new_record{
  3108.                 $conditions = sprintf("%s = %s", $attribute_name, $quoted_value);
  3109.             } else {
  3110.                 $conditions = sprintf("%s = %s AND %s", $attribute_name, 
  3111.                     $quoted_value, $this->get_primary_key_conditions("!="));
  3112.             }    
  3113.             if($this->find_first($conditions)) {
  3114.                 $attribute_human = Inflector::humanize($attribute_name);
  3115.                 $this->add_error("{$attribute_human} {$message}", $attribute_name);
  3116.             }
  3117.         }
  3118.     }
  3119.         
  3120.     /**
  3121.      * Return the error message for a validation function
  3122.      *
  3123.      * @param string $message
  3124.      * @param string $key
  3125.      * @param string $value
  3126.      * @return string
  3127.      */
  3128.     private function get_error_message_for_validation($message, $key, $value = null) {
  3129.         if(is_null($message)) {
  3130.             # Return default error message
  3131.             return sprintf($this->default_error_messages[$key]$value);
  3132.         } else { 
  3133.             # Return your custom error message
  3134.             return $message;
  3135.         }
  3136.     }
  3137.  
  3138.     /**
  3139.      *  Test whether argument is a PEAR Error object or a MDB2 Error object.
  3140.      *
  3141.      *  @param object $obj Object to test
  3142.      *  @return boolean  Whether object is one of these two errors
  3143.      */
  3144.     function is_error($obj) {
  3145.         if((PEAR::isError($obj)) || (MDB2::isError($obj))) {
  3146.             return true;
  3147.         } else {
  3148.             return false;
  3149.         }
  3150.     }
  3151.  
  3152.     /**
  3153.      *  Throw an exception describing an error in this object
  3154.      *
  3155.      *  @throws {@link ActiveRecordError}
  3156.      */
  3157.     function raise($message) {
  3158.         $error_message  = "Model Class: ".$this->get_class_name()."<br>";
  3159.         $error_message .= "Error Message: ".$message;
  3160.         throw new ActiveRecordError($error_message"ActiveRecord Error""500");
  3161.     }
  3162.  
  3163.     /**
  3164.      *  Add or overwrite description of an error to the list of errors
  3165.      *  @param string $error Error message text
  3166.      *  @param string $key Key to associate with the error (in the
  3167.      *    simple case, column name).  If omitted, numeric keys will be
  3168.      *    assigned starting with 0.  If specified and the key already
  3169.      *    exists in $errors, the old error message will be overwritten
  3170.      *    with the value of $error.
  3171.      *  @uses $errors
  3172.      */
  3173.     function add_error($error, $key = null) {
  3174.         if(!is_null($key)) {
  3175.             $this->errors[$key$error;
  3176.         } else {
  3177.             $this->errors[$error;
  3178.         }
  3179.     }
  3180.  
  3181.     /**
  3182.      *  Return description of non-fatal errors
  3183.      *
  3184.      *  @uses $errors
  3185.      *  @param boolean $return_string
  3186.      *    <ul>
  3187.      *      <li>true => Concatenate all error descriptions into a string
  3188.      *        using $seperator between elements and return the
  3189.      *        string</li>
  3190.      *      <li>false => Return the error descriptions as an array</li>
  3191.      *    </ul>
  3192.      *  @param string $seperator  String to concatenate between error
  3193.      *    descriptions if $return_string == true
  3194.      *  @return mixed Error description(s), if any
  3195.      */
  3196.     function get_errors($return_string = false, $seperator = "<br>") {
  3197.         if($return_string && count($this->errors0{
  3198.             return implode($seperator, $this->errors);
  3199.         } else {
  3200.             return $this->errors;
  3201.         }
  3202.     }
  3203.  
  3204.     /**
  3205.      *  Return errors as a string.
  3206.      *
  3207.      *  Concatenate all error descriptions into a stringusing
  3208.      *  $seperator between elements and return the string.
  3209.      *  @param string $seperator  String to concatenate between error
  3210.      *    descriptions
  3211.      *  @return string Concatenated error description(s), if any
  3212.      */
  3213.     function get_errors_as_string($seperator = "<br>") {
  3214.         return $this->get_errors(true$seperator);
  3215.     }  
  3216.  
  3217.     /**
  3218.      *  Log SQL query in development mode
  3219.      *
  3220.      *  If running in development mode, log the query to self::$query_log
  3221.      *  @param string SQL to be logged
  3222.      */
  3223.     function log_query($query) {
  3224.         if(TRAX_ENV == "development" && $query) {
  3225.             self::$query_log[] = $query;       
  3226.         }    
  3227.     }
  3228.  
  3229. }
  3230.  
  3231.  
  3232.  
  3233. // -- set Emacs parameters --
  3234. // Local variables:
  3235. // tab-width: 4
  3236. // c-basic-offset: 4
  3237. // c-hanging-comment-ender-p: nil
  3238. // indent-tabs-mode: nil
  3239. // End:

Documentation generated on Mon, 21 May 2007 22:28:04 -0600 by phpDocumentor 1.3.2