home *** CD-ROM | disk | FTP | other *** search
/ Cricao de Sites - 650 Layouts Prontos / WebMasters.iso / Servidores / xampp-win32-1.6.7-installer.exe / php / PEAR / DB / Table / Database.php < prev    next >
Encoding:
PHP Script  |  2008-07-02  |  131.3 KB  |  3,496 lines

  1. <?php
  2.  
  3. // vim: set et ts=4 sw=4 fdm=marker:
  4.  
  5. /**
  6.  * DB_Table_Database relational database abstraction class
  7.  * 
  8.  * PHP versions 4 and 5
  9.  *
  10.  * LICENSE:
  11.  * 
  12.  * Copyright (c) 1997-2007, Paul M. Jones <pmjones@php.net>
  13.  *                          David C. Morse <morse@php.net>
  14.  *                          Mark Wiesemann <wiesemann@php.net>
  15.  * All rights reserved.
  16.  *
  17.  * Redistribution and use in source and binary forms, with or without
  18.  * modification, are permitted provided that the following conditions
  19.  * are met:
  20.  *
  21.  *    * Redistributions of source code must retain the above copyright
  22.  *      notice, this list of conditions and the following disclaimer.
  23.  *    * Redistributions in binary form must reproduce the above copyright
  24.  *      notice, this list of conditions and the following disclaimer in the 
  25.  *      documentation and/or other materials provided with the distribution.
  26.  *    * The names of the authors may not be used to endorse or promote products 
  27.  *      derived from this software without specific prior written permission.
  28.  *
  29.  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
  30.  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
  31.  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  32.  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  33.  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  34.  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  35.  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  36.  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
  37.  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  38.  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  39.  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  40.  *
  41.  * @category Database
  42.  * @package  DB_Table
  43.  * @author   David C. Morse <morse@php.net>
  44.  * @license  http://opensource.org/licenses/bsd-license.php New BSD License
  45.  * @version  CVS: $Id: Database.php,v 1.15 2007/12/13 16:52:14 wiesemann Exp $
  46.  * @link     http://pear.php.net/package/DB_Table
  47.  */
  48.  
  49. // {{{ Error code constants
  50.  
  51. /**
  52.  * Parameter is not a DB/MDB2 object
  53.  */
  54. define('DB_TABLE_DATABASE_ERR_DB_OBJECT', -201);
  55.  
  56. /**
  57.  * Error in addTable, parameter $table_obj is not a DB_Table object
  58.  */
  59. define('DB_TABLE_DATABASE_ERR_DBTABLE_OBJECT', -202);
  60.  
  61. /**
  62.  * Error for table name that does not exist in the database
  63.  */
  64. define('DB_TABLE_DATABASE_ERR_NO_TBL', -203);
  65.  
  66. /**
  67.  * Error for table name parameter that is not a string
  68.  */
  69. define('DB_TABLE_DATABASE_ERR_TBL_NOT_STRING', -204);
  70.  
  71. /**
  72.  * Error in getCol for a non-existent column name
  73.  */
  74. define('DB_TABLE_DATABASE_ERR_NO_COL', -205);
  75.  
  76. /**
  77.  * Error in getForeignCol for a non-existent foreign key column
  78.  */
  79. define('DB_TABLE_DATABASE_ERR_NO_FOREIGN_COL', -206);
  80.  
  81. /**
  82.  * Error for column name that is not a string
  83.  */
  84. define('DB_TABLE_DATABASE_ERR_COL_NOT_STRING', -207);
  85.  
  86. /**
  87.  * Error in addTable for multiple primary keys
  88.  */
  89. define('DB_TABLE_DATABASE_ERR_MULT_PKEY', -208);
  90.  
  91. /**
  92.  * Error in addRef for a non-existent foreign key table
  93.  */
  94. define('DB_TABLE_DATABASE_ERR_NO_FTABLE', -209);
  95.  
  96. /**
  97.  * Error in addRef for non-existence referenced table
  98.  */
  99. define('DB_TABLE_DATABASE_ERR_NO_RTABLE', -210);
  100.  
  101. /**
  102.  * Error in addRef for null referenced key in a table with no primary key
  103.  */
  104. define('DB_TABLE_DATABASE_ERR_NO_PKEY', -211);
  105.  
  106. /**
  107.  * Error in addRef for an invalid foreign key, neither string nor array
  108.  */
  109. define('DB_TABLE_DATABASE_ERR_FKEY', -212);
  110.  
  111. /**
  112.  * Error in addRef for referenced key that is not a string, string foreign key
  113.  */
  114. define('DB_TABLE_DATABASE_ERR_RKEY_NOT_STRING', -213);
  115.  
  116. /**
  117.  * Error in addRef for referenced key that is not an array, array foreign key
  118.  */
  119. define('DB_TABLE_DATABASE_ERR_RKEY_NOT_ARRAY', -214);
  120.  
  121. /**
  122.  * Error in addRef for wrong number of columns in referenced key
  123.  */
  124. define('DB_TABLE_DATABASE_ERR_RKEY_COL_NUMBER', -215);
  125.  
  126. /**
  127.  * Error in addRef for non-existence foreign key (referencing) column
  128.  */
  129. define('DB_TABLE_DATABASE_ERR_NO_FCOL', -216);
  130.  
  131. /**
  132.  * Error in addRef for non-existence referenced column
  133.  */
  134. define('DB_TABLE_DATABASE_ERR_NO_RCOL', -217);
  135.  
  136. /**
  137.  * Error in addRef for referencing and referenced columns of different types
  138.  */
  139. define('DB_TABLE_DATABASE_ERR_REF_TYPE', -218);
  140.  
  141. /**
  142.  * Error in addRef for multiple references from one table to another
  143.  */
  144. define('DB_TABLE_DATABASE_ERR_MULT_REF', -219);
  145.  
  146. /**
  147.  * Error due to invalid ON DELETE action name
  148.  */
  149. define('DB_TABLE_DATABASE_ERR_ON_DELETE_ACTION', -220);
  150.  
  151. /**
  152.  * Error due to invalid ON UPDATE action name
  153.  */
  154. define('DB_TABLE_DATABASE_ERR_ON_UPDATE_ACTION', -221);
  155.  
  156. /**
  157.  * Error in addLink due to missing required reference
  158.  */
  159. define('DB_TABLE_DATABASE_ERR_NO_REF_LINK', -222);
  160.  
  161. /**
  162.  * Error in validCol for a column name that does not exist in the datase
  163.  */
  164. define('DB_TABLE_DATABASE_ERR_NO_COL_DB', -223);
  165.  
  166. /**
  167.  * Error in validCol for column name that does not exist in the specified table
  168.  */
  169. define('DB_TABLE_DATABASE_ERR_NO_COL_TBL', -224);
  170.  
  171. /**
  172.  * Error in a buildSQL or select* method for an undefined key of $this->sql
  173.  */
  174. define('DB_TABLE_DATABASE_ERR_SQL_UNDEF', -225);
  175.  
  176. /**
  177.  * Error in a buildSQL or select* method for a key of $this->sql that is 
  178.  * not a string
  179.  */
  180. define('DB_TABLE_DATABASE_ERR_SQL_NOT_STRING', -226);
  181.  
  182. /**
  183.  * Error in buildFilter due to invalid match type 
  184.  */
  185. define('DB_TABLE_DATABASE_ERR_MATCH_TYPE', -227);
  186.  
  187. /**
  188.  * Error in buildFilter due to invalid key for full match
  189.  */
  190. define('DB_TABLE_DATABASE_ERR_DATA_KEY', -228);
  191.  
  192. /**
  193.  * Error in buildFilter due to invalid key for full match
  194.  */
  195. define('DB_TABLE_DATABASE_ERR_FILT_KEY', -229);
  196.  
  197. /**
  198.  * Error in buildFilter due to invalid key for full match
  199.  */
  200. define('DB_TABLE_DATABASE_ERR_FULL_KEY', -230);
  201.  
  202. /**
  203.  * Error in insert for a failed foreign key constraint
  204.  */
  205. define('DB_TABLE_DATABASE_ERR_FKEY_CONSTRAINT', -231);
  206.  
  207. /**
  208.  * Error in delete due to a referentially triggered 'restrict' action
  209.  */
  210. define('DB_TABLE_DATABASE_ERR_RESTRICT_DELETE', -232);
  211.  
  212. /**
  213.  * Error in update due to a referentially triggered 'restrict' action
  214.  */
  215. define('DB_TABLE_DATABASE_ERR_RESTRICT_UPDATE', -233);
  216.  
  217. /**
  218.  * Error in fromXML for table with multiple auto_increment columns
  219.  */
  220. define('DB_TABLE_DATABASE_ERR_XML_MULT_AUTO_INC', -234);
  221.  
  222. /**
  223.  * Error in autoJoin, column and tables parameter both null
  224.  */
  225. define('DB_TABLE_DATABASE_ERR_NO_COL_NO_TBL', -235);
  226.  
  227. /**
  228.  * Error in autoJoin for ambiguous column name
  229.  */
  230. define('DB_TABLE_DATABASE_ERR_COL_NOT_UNIQUE', -236);
  231.  
  232. /**
  233.  * Error in autoJoin for non-unique set of join conditions
  234.  */
  235. define('DB_TABLE_DATABASE_ERR_AMBIG_JOIN', -237);
  236.  
  237. /**
  238.  * Error in autoJoin for failed construction of join 
  239.  */
  240. define('DB_TABLE_DATABASE_ERR_FAIL_JOIN', -238);
  241.  
  242. /**
  243.  * Error in fromXML for PHP 4 (this function requires PHP 5)
  244.  */
  245. define('DB_TABLE_DATABASE_ERR_PHP_VERSION', -239);
  246.  
  247. /**
  248.  * Error parsing XML string in fromXML
  249.  */
  250. define('DB_TABLE_DATABASE_ERR_XML_PARSE', -240);
  251.  
  252. // }}}
  253. // {{{ Includes
  254.  
  255. /**
  256.  * DB_Table_Base base class
  257.  */
  258. require_once 'DB/Table/Base.php';
  259.  
  260. /**
  261.  * DB_Table table abstraction class
  262.  */
  263. require_once 'DB/Table.php';
  264.  
  265. /**
  266.  * The PEAR class for errors
  267.  */
  268. require_once 'PEAR.php';
  269.  
  270. // }}}
  271. // {{{ Error messages
  272.  
  273. /** 
  274.  * US-English default error messages. If you want to internationalize, you can
  275.  * set the translated messages via $GLOBALS['_DB_TABLE_DATABASE']['error']. 
  276.  * You can also use DB_Table_Database::setErrorMessage(). Examples:
  277.  * 
  278.  * <code>
  279.  * (1) $GLOBALS['_DB_TABLE_DATABASE']['error'] = array(
  280.  *                                           DB_TABLE_DATABASE_ERR_.. => '...',
  281.  *                                           DB_TABLE_DATABASE_ERR_.. => '...');
  282.  * (2) DB_Table_Database::setErrorMessage(DB_TABLE_DATABASE_ERR_.., '...');
  283.  *     DB_Table_Database::setErrorMessage(DB_TABLE_DATABASE_ERR_.., '...');
  284.  * (3) DB_Table_Database::setErrorMessage(array(
  285.  *                                        DB_TABLE_DATABASE_ERR_.. => '...');
  286.  *                                        DB_TABLE_DATABASE_ERR_.. => '...');
  287.  * (4) $obj =& new DB_Table();
  288.  *     $obj->setErrorMessage(DB_TABLE_DATABASE_ERR_.., '...');
  289.  *     $obj->setErrorMessage(DB_TABLE_DATABASE_ERR_.., '...');
  290.  * (5) $obj =& new DB_Table();
  291.  *     $obj->setErrorMessage(array(DB_TABLE_DATABASE_ERR_.. => '...');
  292.  *                                 DB_TABLE_DATABASE_ERR_.. => '...');
  293.  * </code>
  294.  * 
  295.  * For errors that can occur with-in the constructor call (i.e. e.g. creating
  296.  * or altering the database table), only the code from examples (1) to (3)
  297.  * will alter the default error messages early enough. For errors that can
  298.  * occur later, examples (4) and (5) are also valid.
  299.  */
  300. $GLOBALS['_DB_TABLE_DATABASE']['default_error'] = array(
  301.         DB_TABLE_DATABASE_ERR_DB_OBJECT =>
  302.         'Invalid DB/MDB2 object parameter. Function',
  303.         DB_TABLE_DATABASE_ERR_NO_TBL =>
  304.         'Table does not exist in database. Method, Table =',
  305.         DB_TABLE_DATABASE_ERR_TBL_NOT_STRING =>
  306.         'Table name parameter is not a string in method',
  307.         DB_TABLE_DATABASE_ERR_NO_COL =>
  308.         'In getCol, non-existent column name parameter',
  309.         DB_TABLE_DATABASE_ERR_NO_FOREIGN_COL =>
  310.         'In getForeignCol, non-existent column name parameter',
  311.         DB_TABLE_DATABASE_ERR_COL_NOT_STRING =>
  312.         'Column name parameter is not a string in method',
  313.         DB_TABLE_DATABASE_ERR_DBTABLE_OBJECT =>
  314.         'Parameter of addTable is not a DB_Table object',
  315.         DB_TABLE_DATABASE_ERR_MULT_PKEY =>
  316.         'Multiple primary keys in one table detected in addTable. Table',
  317.         DB_TABLE_DATABASE_ERR_NO_FTABLE =>
  318.         'Foreign key reference from non-existent table in addRef. Reference',
  319.         DB_TABLE_DATABASE_ERR_NO_RTABLE =>
  320.         'Reference to a non-existent referenced table in addRef. Reference',
  321.         DB_TABLE_DATABASE_ERR_NO_PKEY =>
  322.         'Missing primary key of referenced table in addRef. Reference',
  323.         DB_TABLE_DATABASE_ERR_FKEY =>
  324.         'Foreign / referencing key is not a string or array in addRef',
  325.         DB_TABLE_DATABASE_ERR_RKEY_NOT_STRING =>
  326.         'Foreign key is a string, referenced key is not a string in addRef',
  327.         DB_TABLE_DATABASE_ERR_RKEY_NOT_ARRAY =>
  328.         'Foreign key is an array, referenced key is not an array in addRef',
  329.         DB_TABLE_DATABASE_ERR_RKEY_COL_NUMBER =>
  330.         'Wrong number of columns in referencing key in addRef',
  331.         DB_TABLE_DATABASE_ERR_NO_FCOL =>
  332.         'Nonexistent foreign / referencing key column in addRef. Reference',
  333.         DB_TABLE_DATABASE_ERR_NO_RCOL =>
  334.         'Nonexistent referenced key column in addRef. Reference',
  335.         DB_TABLE_DATABASE_ERR_REF_TYPE =>
  336.         'Different referencing and referenced column types in addRef. Reference',
  337.         DB_TABLE_DATABASE_ERR_MULT_REF =>
  338.         'Multiple references between two tables in addRef. Reference',
  339.         DB_TABLE_DATABASE_ERR_ON_DELETE_ACTION =>
  340.         'Invalid ON DELETE action. Reference',
  341.         DB_TABLE_DATABASE_ERR_ON_UPDATE_ACTION =>
  342.         'Invalid ON UPDATE action. Reference',
  343.         DB_TABLE_DATABASE_ERR_NO_REF_LINK =>
  344.         'Error in addLink due to missing required reference(s)',
  345.         DB_TABLE_DATABASE_ERR_NO_COL_DB =>
  346.         'In validCol, column name does not exist in database. Column',
  347.         DB_TABLE_DATABASE_ERR_NO_COL_TBL =>
  348.         'In validCol, column does not exist in specified table. Column',
  349.         DB_TABLE_DATABASE_ERR_SQL_UNDEF =>
  350.         'Query string is not a key of $sql property array. Key is',
  351.         DB_TABLE_DATABASE_ERR_SQL_NOT_STRING =>
  352.         'Query is neither an array nor a string',
  353.         DB_TABLE_DATABASE_ERR_MATCH_TYPE =>
  354.         'Invalid match parameter of buildFilter',
  355.         DB_TABLE_DATABASE_ERR_DATA_KEY =>
  356.         'Invalid data_key in buildFilter, neither string nor array',
  357.         DB_TABLE_DATABASE_ERR_FILT_KEY =>
  358.         'Incompatible data_key and filter_key in buildFilter',
  359.         DB_TABLE_DATABASE_ERR_FULL_KEY =>
  360.         'Invalid key value in buildFilter: Mixed null and not null',
  361.         DB_TABLE_DATABASE_ERR_FKEY_CONSTRAINT =>
  362.         'Foreign key constraint failure: Key does not reference any rows',
  363.         DB_TABLE_DATABASE_ERR_RESTRICT_DELETE =>
  364.         'Referentially trigger restrict of delete from table',
  365.         DB_TABLE_DATABASE_ERR_RESTRICT_UPDATE =>
  366.         'Referentially trigger restrict of update of table',
  367.         DB_TABLE_DATABASE_ERR_NO_COL_NO_TBL =>
  368.         'No columns or tables provided as parameters to autoJoin',
  369.         DB_TABLE_DATABASE_ERR_COL_NOT_UNIQUE =>
  370.         'Ambiguous column name in autoJoin. Column',
  371.         DB_TABLE_DATABASE_ERR_AMBIG_JOIN =>
  372.         'Ambiguous join in autoJoin, during join of table',
  373.         DB_TABLE_DATABASE_ERR_FAIL_JOIN =>
  374.         'Failed join in autoJoin, failed to join table',
  375.         DB_TABLE_DATABASE_ERR_PHP_VERSION =>
  376.         'PHP 5 is required for fromXML method. Interpreter version is',
  377.         DB_TABLE_DATABASE_ERR_XML_PARSE =>
  378.         'Error parsing XML in fromXML method'
  379.     );
  380.  
  381. // merge default and user-defined error messages
  382. if (!isset($GLOBALS['_DB_TABLE_DATABASE']['error'])) {
  383.     $GLOBALS['_DB_TABLE_DATABASE']['error'] = array();
  384. }
  385. foreach ($GLOBALS['_DB_TABLE_DATABASE']['default_error'] as $code => $message) {
  386.     if (!array_key_exists($code, $GLOBALS['_DB_TABLE_DATABASE']['error'])) {
  387.         $GLOBALS['_DB_TABLE_DATABASE']['error'][$code] = $message;
  388.     }
  389. }
  390.  
  391. // }}}
  392. // {{{ DB_Table_Database
  393.  
  394. /**
  395.  * Relational database abstraction class
  396.  *
  397.  * DB_Table_Database is an abstraction class for a relational database.
  398.  * It is a layer built on top of DB_Table, in which each table in a
  399.  * database is represented as an instance of DB_Table. It provides: 
  400.  *
  401.  *   - an object-oriented representation of the database schema
  402.  *   - automated construction of SQL commands for simple joins
  403.  *   - an API for insert, update, and select commands very similar
  404.  *     to that of DB_Table, with optional emulation of standard SQL
  405.  *     foreign key integrity checks and referential triggered actions
  406.  *     such as cascading deletes.
  407.  *   - Serialization and unserialization of the database schema via 
  408.  *     either php serialization or XML, using the MDB2 XML schema. 
  409.  *
  410.  * @category Database
  411.  * @package  DB_Table
  412.  * @author   David C. Morse <morse@php.net>
  413.  * @version  Release: 1.5.5
  414.  * @link     http://pear.php.net/package/DB_Table
  415.  */
  416. class DB_Table_Database extends DB_Table_Base
  417. {
  418.  
  419.     // {{{ properties
  420.  
  421.     /**
  422.      * Name of the database
  423.      *
  424.      * @var    string
  425.      * @access public
  426.      */
  427.     var $name   = null;
  428.  
  429.     /**
  430.      * Associative array of DB_Table object references. Keys are table names.
  431.      *
  432.      * Associative array in which keys are table names, values are references to
  433.      * DB_Table objects.  Each referenced DB_Table object represents one table in
  434.      * the database.
  435.      *
  436.      * @var    array
  437.      * @access private
  438.      */
  439.     var $_table = array();
  440.  
  441.     /**
  442.      * Array in which keys are table names, values are DB_Table subclass names.
  443.      *
  444.      * See the getTableSubclass() method docblock for further details. 
  445.      * 
  446.      * @var    array
  447.      * @access private
  448.      */
  449.     var $_table_subclass = array();
  450.  
  451.     /**
  452.      * Path to directory containing DB_Table subclass declaration files
  453.      *
  454.      * See the setTableSubclassPath() method docblock for further details. 
  455.      * 
  456.      * @var    string
  457.      * @access private
  458.      */
  459.     var $_table_subclass_path = '';
  460.  
  461.     /**
  462.      * Array in which keys are table names, values are primary keys.
  463.      *
  464.      * Each primary key value may be a column name string, a sequential array of
  465.      * column name strings, or null. 
  466.      *
  467.      * See the getPrimaryKey() method docblock for details. 
  468.      *
  469.      * @var    array
  470.      * @access private
  471.      */
  472.     var $_primary_key = array();
  473.  
  474.     /**
  475.      * Associative array that maps column names keys to table names.
  476.      *
  477.      * Each key is the name string of a column in the database. Each value
  478.      * is a numerical array containing the names of all tables that contain 
  479.      * a column with that name. 
  480.      *
  481.      * See the getCol() method docblock for details.
  482.      *
  483.      * @var    array
  484.      * @access private
  485.      */
  486.     var $_col = array();
  487.  
  488.     /**
  489.      * Associative array that maps names of foreign key columns to table names
  490.      *
  491.      * Each key is the name string of a foreign key column. Each value is a
  492.      * sequential array containing the names of all tables that contain a 
  493.      * foreign key column with that name. 
  494.      *
  495.      * See the getForeignCol() method docblock for further details. 
  496.      *
  497.      * @var    array
  498.      * @access private
  499.      */
  500.     var $_foreign_col = array();
  501.  
  502.     /**
  503.      * Two-dimensional associative array of foreign key references. 
  504.      *
  505.      * Keys are pairs of table names (referencing table first, referenced
  506.      * table second). Each value is an array containing information about 
  507.      * the referencing and referenced keys, and about any referentially 
  508.      * triggered actions (e.g., cascading delete). 
  509.      *
  510.      * See the getRef() docblock for further details. 
  511.      *
  512.      * @var    array
  513.      * @access private
  514.      */
  515.     var $_ref = array();
  516.  
  517.     /**
  518.      * Array in which each key is the names of a referenced tables, each value 
  519.      * an sequential array containing names of referencing tables.
  520.      *
  521.      * See the docblock for the getRefTo() method for further discussion. 
  522.      * 
  523.      * @var    array
  524.      * @access private
  525.      */
  526.     var $_ref_to = array();
  527.  
  528.     /**
  529.      * Two-dimensional associative array of linking tables. 
  530.      *
  531.      * Two-dimensional associative array in which pairs of keys are names
  532.      * of pairs of tables that are linked by one or more linking/association 
  533.      * table. Each value is an array containing the names of all table that
  534.      * link the tables specified by the pair of keys. A linking table is a 
  535.      * table that creates a many-to-many relationship between two linked
  536.      * tables, via foreign key references from the linking table to the two
  537.      * linked tables. The $_link property is used by the autoJoin() method 
  538.      * to join tables that are related only through such a linking table. 
  539.      * 
  540.      * See the getLink() method docblock for further details. 
  541.      *
  542.      * @var    array
  543.      * @access private
  544.      */
  545.     var $_link = array();
  546.  
  547.     /**
  548.      * Take on_update actions if $_act_on_update is true
  549.      *
  550.      * By default, on_update actions are enabled ($_act_on_update = true)
  551.      *
  552.      * @var    boolean
  553.      * @access private
  554.      */
  555.     var $_act_on_update = true;
  556.  
  557.     /**
  558.      * Take on_delete actions if $_act_on_delete is true
  559.      *
  560.      * By default, on_delete actions are enabled ($_act_on_delete = true)
  561.      *
  562.      * @var    boolean
  563.      * @access private
  564.      */
  565.     var $_act_on_delete = true;
  566.  
  567.     /**
  568.      * Validate foreign keys before insert or update if $_check_fkey is true
  569.      *
  570.      * By default, validation is disabled ($_check_fkey = false)
  571.      *
  572.      * @var    boolean
  573.      * @access private
  574.      */
  575.     var $_check_fkey = false;
  576.  
  577.     /**
  578.      * If the column keys in associative array return sets are fixed case
  579.      * (all upper or lower case) this property should be set true. 
  580.      *
  581.      * The column keys in rows of associative array return sets may either 
  582.      * preserve capitalization of the column names or they may be fixed case,
  583.      * depending on the options set in the backend (DB/MDB2) and on phptype.
  584.      * If these column names are returned with a fixed case (either upper 
  585.      * or lower), $_fix_case must be set true in order for php emulation of
  586.      * ON DELETE and ON UPDATE actions to work correctly. Otherwise, the
  587.      * $_fix_case property should be false (the default).
  588.      *
  589.      * The choice between mixed or fixed case column keys may be made by using
  590.      * using the setFixCase() method, which resets both the behavior of the
  591.      * backend and the $_fix_case property. It may also be changed by using the 
  592.      * setOption() method of the DB or MDB2 backend object to directly set the 
  593.      * DB_PORTABILITY_LOWERCASE or MDB2_PORTABILITY_FIX_CASE bits of the 
  594.      * DB/MDB2 'portability' option.
  595.      *
  596.      * By default, DB returns mixed case and MDB2 returns lower case. 
  597.      * 
  598.      * @see DB_Table_Database::setFixCase()
  599.      * @see DB::setOption()
  600.      * @see MDB2::setOption()
  601.      *
  602.      * @var    boolean
  603.      * @access private
  604.      */
  605.     var $_fix_case = false;
  606.  
  607.     // }}}
  608.     // {{{ Methods
  609.  
  610.     // {{{ function DB_Table_Database(&$db, $name)
  611.  
  612.     /**
  613.      * Constructor
  614.      *
  615.      * If an error is encountered during instantiation, the error
  616.      * message is stored in the $this->error property of the resulting
  617.      * object. See $error property docblock for a discussion of error
  618.      * handling. 
  619.      * 
  620.      * @param  object &$db   DB/MDB2 database connection object
  621.      * @param  string $name the database name
  622.      * @return object DB_Table_Database
  623.      * @access public
  624.      */
  625.     function DB_Table_Database(&$db, $name)
  626.     {
  627.         // Is $db an DB/MDB2 object or null?
  628.         if (is_a($db, 'db_common')) {
  629.             $this->backend = 'db';
  630.             $this->fetchmode = DB_FETCHMODE_ORDERED;
  631.         } elseif (is_a($db, 'mdb2_driver_common')) {
  632.             $this->backend = 'mdb2';
  633.             $this->fetchmode = MDB2_FETCHMODE_ORDERED;
  634.         } else {
  635.             $code = DB_TABLE_DATABASE_ERR_DB_OBJECT ;
  636.             $text = $GLOBALS['_DB_TABLE_DATABASE']['error'][$code]
  637.                   . ' DB_Table_Database';
  638.             $this->error =& PEAR::throwError($text, $code);
  639.             return;
  640.         }
  641.         $this->db  =& $db;
  642.         $this->name = $name;
  643.  
  644.         $this->_primary_subclass = 'DB_TABLE_DATABASE';
  645.         $this->setFixCase(false);
  646.     }
  647.  
  648.     // }}}
  649.     // {{{ function setDBconnection(&$db)
  650.  
  651.     /**
  652.      * Set DB/MDB2 connection instance for database and all tables
  653.      *
  654.      * Assign a reference to the DB/MDB2 object $db to $this->db, set
  655.      * $this->backend to 'db' or 'mdb2', and set the same pair of 
  656.      * values for the $db and $backend properties of every DB_Table
  657.      * object in the database.  
  658.      *
  659.      * @param  object  &$db DB/MDB2 connection object
  660.      * @return boolean True on success (PEAR_Error on failure)
  661.      *
  662.      * @throws PEAR_Error if 
  663.      *     $db is not a DB or MDB2 object(DB_TABLE_DATABASE_ERR_DB_OBJECT)
  664.      *
  665.      * @access public
  666.      */
  667.     function setDBconnection(&$db)
  668.     {
  669.         // Is the first argument a DB/MDB2 object ?
  670.         if (is_subclass_of($db, 'DB_Common')) {
  671.             $backend = 'db';
  672.         } elseif (is_subclass_of($db, 'MDB2_Driver_Common')) {
  673.             $backend = 'mdb2';
  674.         } else {
  675.             return $this->throwError(
  676.                       DB_TABLE_DATABASE_ERR_DB_OBJECT,
  677.                       "setDBconnection");
  678.         }
  679.  
  680.         // Set db and backend for database and all of its tables
  681.         $this->db =& $db;
  682.         $this->backend = $backend;
  683.         foreach ($this->_table as $name => $table) {
  684.             $table->db =& $db;
  685.             $table->backend = $backend;
  686.         }
  687.         return true;
  688.     }
  689.  
  690.     // }}}
  691.     // {{{ function setActOnDelete($flag = true)
  692.  
  693.     /**
  694.      * Turns on (or off) automatic php emulation of SQL ON DELETE actions
  695.      *
  696.      * @param  bool $flag True to enable action, false to disable
  697.      * @return void
  698.      * @access public
  699.      */
  700.     function setActOnDelete($flag = true)
  701.     {
  702.         if ($flag) {
  703.             $this->_act_on_delete = true;
  704.         } else {
  705.             $this->_act_on_delete = false;
  706.         }
  707.     }
  708.     
  709.     // }}}
  710.     // {{{ function setActOnUpdate($flag = true)
  711.  
  712.     /**
  713.      * Turns on (or off) automatic php emulation of ON UPDATE actions
  714.      *
  715.      * @param  bool $flag True to enable action, false to disable
  716.      * @return void
  717.      * @access public
  718.      */
  719.     function setActOnUpdate($flag = true)
  720.     {
  721.         if ($flag) {
  722.             $this->_act_on_update = true;
  723.         } else {
  724.             $this->_act_on_update = false;
  725.         }
  726.     }
  727.     
  728.     // }}}
  729.     // {{{ function setCheckFKey($flag = true)
  730.  
  731.     /**
  732.      * Turns on (or off) validation of foreign key values on insert and update
  733.      *
  734.      * @param  bool $flag True to enable foreign key validation, false to disable
  735.      * @return void
  736.      * @access public
  737.      */
  738.     function setCheckFKey($flag = true)
  739.     {
  740.         if ($flag) {
  741.             $this->_check_fkey = true;
  742.         } else {
  743.             $this->_check_fkey = false;
  744.         }
  745.     }
  746.  
  747.     // }}}
  748.     // {{{ function setFixCase($flag = false) 
  749.  
  750.     /**
  751.      * Sets backend option such that column keys in associative array return
  752.      * sets are converted to fixed case, if true, or mixed case, if false.
  753.      * 
  754.      * Sets the DB/MDB2 'portability' option, and sets $this->_fix_case = $flag.
  755.      * Because it sets an option in the underlying DB/MDB2 connection object, 
  756.      * this effects the behavior of all objects that share the connection.
  757.      * 
  758.      * @param  bool $flag True for fixed lower case, false for mixed
  759.      * @return void
  760.      * @access public
  761.      */
  762.     function setFixCase($flag = false) 
  763.     {
  764.         $flag = (bool) $flag;
  765.         $option = $this->db->getOption('portability');
  766.         if ($this->backend == 'db') {
  767.             $option = $option | DB_PORTABILITY_LOWERCASE;
  768.             if (!$flag) {
  769.                 $option = $option ^ DB_PORTABILITY_LOWERCASE;
  770.             }
  771.         } else {
  772.             $option = $option | MDB2_PORTABILITY_FIX_CASE;
  773.             if (!$flag) {
  774.                 $option = $option ^ MDB2_PORTABILITY_FIX_CASE;
  775.             }
  776.         } 
  777.         $this->db->setOption('portability', $option);
  778.         $this->_fix_case = $flag;
  779.     }
  780.     
  781.     // }}}
  782.     // {{{ function &getDBInstance() 
  783.  
  784.     /**
  785.      * Return reference to $this->db DB/MDB2 object wrapped by $this
  786.      *
  787.      * @return object Reference to DB/MDB2 object
  788.      * @access public
  789.      */
  790.     function &getDBInstance() 
  791.     {
  792.         return $this->db;
  793.     }
  794.  
  795.     // }}}
  796.     // {{{ function getTable($name = null) 
  797.  
  798.     /**
  799.      * Returns all or part of $_table property array
  800.      *
  801.      * If $name is absent or null, return entire $_table property array.
  802.      * If $name is a table name, return $this->_table[$name] DB_Table object
  803.      * reference
  804.      *
  805.      * The $_table property is an associative array in which keys are table
  806.      * name strings and values are references to DB_Table objects. Each of 
  807.      * the referenced objects represents one table in the database.
  808.      *
  809.      * @param  string $name Name of table
  810.      * @return mixed  $_table property, or one element of $_table 
  811.      *                (PEAR_Error on failure)
  812.      *
  813.      * @throws PEAR_Error if:
  814.      *    - $name is not a string ( DB_TABLE_DATABASE_ERR_TBL_NOT_STRING )
  815.      *    - $name is not valid table name ( DB_TABLE_DATABASE_ERR_NO_TBL )
  816.      * 
  817.      * @access public
  818.      */
  819.     function getTable($name = null) 
  820.     {
  821.         if (is_null($name)) {
  822.             return $this->_table;
  823.         } elseif (is_string($name)) {
  824.             if (isset($this->_table[$name])) {
  825.                 return $this->_table[$name];
  826.             } else {
  827.                 return $this->throwError(
  828.                           DB_TABLE_DATABASE_ERR_NO_TBL,
  829.                           "getTable, $name");
  830.             }
  831.         } else {
  832.             return $this->throwError(
  833.                       DB_TABLE_DATABASE_ERR_TBL_NOT_STRING,
  834.                       "getTable");
  835.         }
  836.     }
  837.  
  838.     // }}}
  839.     // {{{ function getPrimaryKey($name = null) 
  840.  
  841.     /**
  842.      * Returns all or part of the $_primary_key property array
  843.      *
  844.      * If $name is null, return the $this->_primary_key property array
  845.      * If $name is a table name, return $this->_primary_key[$name]
  846.      *
  847.      * The $_primary_key property is an associative array in which each key
  848.      * a table name, and each value is the primary key of that table. Each
  849.      * primary key value may be a column name string, a sequential array of 
  850.      * column name strings (for a multi-column key), or null (if no primary
  851.      * key has been declared).
  852.      *
  853.      * @param  string $name Name of table
  854.      * @return mixed  $this->primary_key array or $this->_primary_key[$name]
  855.      *                (PEAR_Error on failure)
  856.      *
  857.      * @throws PEAR_Error if:
  858.      *    - $name is not a string ( DB_TABLE_DATABASE_ERR_TBL_NOT_STRING )
  859.      *    - $name is not valid table name ( DB_TABLE_DATABASE_ERR_NO_TBL )
  860.      * 
  861.      * @access public
  862.      */
  863.     function getPrimaryKey($name = null) 
  864.     {
  865.         if (is_null($name)) {
  866.             return $this->_primary_key;
  867.         } elseif (is_string($name)) {
  868.             if (isset($this->_primary_key[$name])) {
  869.                 return $this->_primary_key[$name];
  870.             } else {
  871.                 return $this->throwError(
  872.                           DB_TABLE_DATABASE_ERR_NO_TBL,
  873.                           "getPrimaryKey, $name");
  874.             }
  875.         } else {
  876.             return $this->throwError(
  877.                       DB_TABLE_DATABASE_ERR_TBL_NOT_STRING,
  878.                       "getPrimaryKey");
  879.         }
  880.     }
  881.  
  882.     // }}}
  883.     // {{{ function getTableSubclass($name = null) 
  884.  
  885.     /**
  886.      * Returns all or part of the $_table_subclass property array
  887.      *
  888.      * If $name is null, return the $this->_table_subclass property array
  889.      * If $name is a table name, return $this->_table_subclass[$name]
  890.      *
  891.      * The $_table_subclass property is an associative array in which each key
  892.      * is a table name string, and each value is the name of the corresponding 
  893.      * subclass of DB_Table. The value is null if the table is an instance of 
  894.      * DB_Table itself. 
  895.      *
  896.      * Subclass names are set within the addTable method by applying the 
  897.      * built in get_class() function to a DB_Table object. The class names 
  898.      * returned by get_class() are stored unmodified. In PHP 4, get_class
  899.      * converts all class names to lower case. In PHP 5, it preserves the 
  900.      * capitalization of the name used in the class definition. 
  901.      *
  902.      * For autoloading of class definitions to work properly in the 
  903.      * __wakeup() method, the base name of each subclass definition
  904.      * file (excluding the .php extension) should thus be a identical
  905.      * to the class name in PHP 5, and a lower case version of the 
  906.      * class name in PHP 4 or 
  907.      * 
  908.      * @param  string $name Name of table
  909.      * @return mixed  $_table_subclass array or $this->_table_subclass[$name]
  910.      *                (PEAR_Error on failure)
  911.      *
  912.      * @throws PEAR_Error if:
  913.      *    - $name is not a string ( DB_TABLE_DATABASE_TBL_NOT_STRING )
  914.      *    - $name is not valid table name ( DB_TABLE_DATABASE_NO_TBL )
  915.      *
  916.      * @access public
  917.      * 
  918.      @ @see DB_Table_Database::__wakeup()
  919.      */
  920.     function getTableSubclass($name = null) 
  921.     {
  922.         if (is_null($name)) {
  923.             return $this->_table_subclass;
  924.         } elseif (is_string($name)) {
  925.             if (isset($this->_table_subclass[$name])) {
  926.                 return $this->_table_subclass[$name];
  927.             } else {
  928.                 return $this->throwError(
  929.                           DB_TABLE_DATABASE_ERR_NO_TBL,
  930.                           "getTableSubclass, $name");
  931.             }
  932.         } else {
  933.             return $this->throwError(
  934.                       DB_TABLE_DATABASE_ERR_TBL_NOT_STRING,
  935.                       "getTableSubclass");
  936.         }
  937.     }
  938.  
  939.     // }}}
  940.     // {{{ function getCol($column_name = null) 
  941.  
  942.     /**
  943.      * Returns all or part of the $_col property array
  944.      *
  945.      * If $column_name is null, return $_col property array
  946.      * If $column_name is valid, return $_col[$column_name] subarray
  947.      *
  948.      * The $_col property is an associative array in which each key is the
  949.      * name of a column in the database, and each value is a numerical array 
  950.      * containing the names of all tables that contain a column with that 
  951.      * name.
  952.      *
  953.      * @param string $column_name a column name string
  954.      * @return mixed $this->_col property array or $this->_col[$column_name]
  955.      *               (PEAR_Error on failure)
  956.      *
  957.      * @throws PEAR_Error if:
  958.      *    - $column_name is not a string (DB_TABLE_DATABASE_ERR_COL_NOT_STRING)
  959.      *    - $column_name is not valid column name (DB_TABLE_DATABASE_NO_COL)
  960.      *
  961.      * @access public
  962.      */
  963.     function getCol($column_name = null) 
  964.     {
  965.         if (is_null($column_name)) {
  966.             return $this->_col;
  967.         } elseif (is_string($column_name)) {
  968.             if (isset($this->_col[$column_name])) {
  969.                 return $this->_col[$column_name];
  970.             } else {
  971.                 return $this->throwError(
  972.                           DB_TABLE_DATABASE_ERR_NO_COL,
  973.                           "'$column_name'");
  974.             }
  975.         } else {
  976.             return $this->throwError(
  977.                        DB_TABLE_DATABASE_ERR_COL_NOT_STRING,
  978.                        'getCol');
  979.         }
  980.     }
  981.  
  982.     // }}}
  983.     // {{{ function getForeignCol($column_name = null) 
  984.  
  985.     /**
  986.      * Returns all or part of the $_foreign_col property array
  987.      *
  988.      * If $column_name is null, return $this->_foreign_col property array
  989.      * If $column_name is valid, return $this->_foreign_col[$column_name] 
  990.      *
  991.      * The $_foreign_col property is an associative array in which each 
  992.      * key is the name string of a foreign key column, and each value is a
  993.      * sequential array containing the names of all tables that contain a 
  994.      * foreign key column with that name. 
  995.      *
  996.      * If a column $column in a referencing table $ftable is part of the 
  997.      * foreign key for references to two or more different referenced tables
  998.      * tables, the name $ftable will also appear multiple times in the array 
  999.      * $this->_foreign_col[$column].
  1000.      *
  1001.      * Returns a PEAR_Error with the following DB_TABLE_DATABASE_* error
  1002.      * codes if:
  1003.      *    - $column_name is not a string ( _COL_NOT_STRING )
  1004.      *    - $column_name is not valid foreign column name ( _NO_FOREIGN_COL )
  1005.      *
  1006.      * @param  string column name string for foreign key column
  1007.      * @return array  $_foreign_col property array
  1008.      * @access public
  1009.      */
  1010.     function getForeignCol($column_name = null) 
  1011.     {
  1012.         if (is_null($column_name)) {
  1013.             return $this->_foreign_col;
  1014.         } elseif (is_string($column_name)) {
  1015.             if (isset($this->_foreign_col[$column_name])) {
  1016.                 return $this->_foreign_col[$column_name];
  1017.             } else {
  1018.                 return $this->throwError(
  1019.                           DB_TABLE_DATABASE_ERR_NO_FOREIGN_COL,
  1020.                           $column_name);
  1021.             }
  1022.         } else {
  1023.             return $this->throwError(
  1024.                       DB_TABLE_DATABASE_ERR_COL_NOT_STRING,
  1025.                       'getForeignCol');
  1026.         }
  1027.     }
  1028.  
  1029.     // }}}
  1030.     // {{{ function getRef($table1 = null, $table2 = null) 
  1031.  
  1032.     /**
  1033.      * Returns all or part of the $_ref two-dimensional property array
  1034.      *
  1035.      * Returns $this->_ref 2D property array if $table1 and $table2 are null.
  1036.      * Returns $this->_ref[$table1] subarray if only $table2 is null.
  1037.      * Returns $this->_ref[$table1][$table2] if both parameters are present.
  1038.      *
  1039.      * Returns null if $table1 is a table that references no others, or 
  1040.      * if $table1 and $table2 are both valid table names, but there is no 
  1041.      * reference from $table1 to $table2.
  1042.      * 
  1043.      * The $_ref property is a two-dimensional associative array in which
  1044.      * the keys are pairs of table names, each value is an array containing 
  1045.      * information about referenced and referencing keys, and referentially
  1046.      * triggered actions (if any).  An element of the $_ref array is of the 
  1047.      * form $ref[$ftable][$rtable] = $reference, where $ftable is the name 
  1048.      * of a referencing (or foreign key) table and $rtable is the name of 
  1049.      * a corresponding referenced table. The value $reference is an array 
  1050.      * $reference = array($fkey, $rkey, $on_delete, $on_update) in which
  1051.      * $fkey and $rkey are the foreign (or referencing) and referenced 
  1052.      * keys, respectively: Foreign key $fkey of table $ftable references
  1053.      * key $rkey of table $rtable. The values of $fkey and $rkey must either 
  1054.      * both be valid column name strings for columns of the same type, or 
  1055.      * they may both be sequential arrays of column name names, with equal 
  1056.      * numbers of columns of corresponding types, for multi-column keys. The 
  1057.      * $on_delete and $on_update values may be either null or string values 
  1058.      * that indicate actions to be taken upon deletion or updating of a 
  1059.      * referenced row (e.g., cascading deletes). A null value of $on_delete
  1060.      * or $on_update indicates that no referentially triggered action will 
  1061.      * be taken. See addRef() for further details about allowed values of
  1062.      * these action strings. 
  1063.      *
  1064.      * @param  string $table1 name of referencing table
  1065.      * @param  string $table2 name of referenced table
  1066.      * @return mixed $ref property array, sub-array, or value
  1067.      * 
  1068.      * @throws a PEAR_Error if:
  1069.      *    - $table1 or $table2 is not a string (.._DATABASE_ERR_TBL_NOT_STRING)
  1070.      *    - $table1 or $table2 is not a table name (.._DATABASE_ERR_NO_TBL)
  1071.      *
  1072.      * @access public
  1073.      */
  1074.     function getRef($table1 = null, $table2 = null) 
  1075.     {
  1076.         if (is_null($table1)) {
  1077.             return $this->_ref;
  1078.         } elseif (is_string($table1)) {
  1079.             if (isset($this->_ref[$table1])) {
  1080.                 if (is_null($table2)) {
  1081.                     return $this->_ref[$table1];
  1082.                 } elseif (is_string($table2)) {
  1083.                     if (isset($this->_ref[$table1][$table2])) {
  1084.                         return $this->_ref[$table1][$table2];
  1085.                     } else {
  1086.                         if (isset($this->_table[$table2])) {
  1087.                             // Valid table names but no references to
  1088.                             return null;
  1089.                         } else {
  1090.                             // Invalid table name
  1091.                             return $this->throwError(
  1092.                                       DB_TABLE_DATABASE_ERR_NO_TBL,
  1093.                                       "getRef, $table2");
  1094.                         }
  1095.                     }
  1096.                 } else {
  1097.                     return $this->throwError(
  1098.                               DB_TABLE_DATABASE_ERR_TBL_NOT_STRING,
  1099.                               "getRef");
  1100.                 }
  1101.             } else {
  1102.                 if (isset($this->_table[$table1])) {
  1103.                     // Valid table name, but no references from
  1104.                     return null;
  1105.                 } else {
  1106.                     // Invalid table name
  1107.                     return $this->throwError(
  1108.                               DB_TABLE_DATABASE_ERR_NO_TBL,
  1109.                               "getRef, $table1");
  1110.                 }
  1111.             }
  1112.         } else {
  1113.             return $this->throwError(
  1114.                        DB_TABLE_DATABASE_ERR_TBL_NOT_STRING,
  1115.                        "getRef");
  1116.         }
  1117.  
  1118.     }
  1119.  
  1120.     // }}}
  1121.     // {{{ function getRefTo($table_name = null)
  1122.  
  1123.     /**
  1124.      * Returns all or part of the $_ref_to property array
  1125.      *
  1126.      * Returns $this->_ref_to property array if $table_name is null.
  1127.      * Returns $this->_ref_to[$table_name] if $table_name is not null.
  1128.      *
  1129.      * The $_ref_to property is an associative array in which each key
  1130.      * is the name of a referenced table, and each value is a sequential
  1131.      * array containing the names of all tables that contain foreign keys
  1132.      * that reference that table. Each element is thus of the form
  1133.      * $_ref_to[$rtable] = array($ftable1, $ftable2,...), where
  1134.      * $ftable1, $ftable2, ... are the names of tables that reference 
  1135.      * the table named $rtable.
  1136.      *
  1137.      * @param string $table_name name of table
  1138.      * @return mixed $_ref_to property array or subarray 
  1139.      *               (PEAR_Error on failure)
  1140.      * 
  1141.      * @throws PEAR_Error if:
  1142.      *    - $table_name is not a string ( .._DATABASE_ERR_TBL_NOT_STRING )
  1143.      *    - $table_name is not a table name ( .._DATABASE_ERR_NO_TBL )
  1144.      *
  1145.      * @access public
  1146.      */
  1147.     function getRefTo($table_name = null)
  1148.     {
  1149.         if (is_null($table_name)) {
  1150.             return $this->_ref_to;
  1151.         } elseif (is_string($table_name)) {
  1152.             if (isset($this->_ref_to[$table_name])) {
  1153.                 return $this->_ref_to[$table_name];
  1154.             } else {
  1155.                 if (isset($this->_table[$table_name])) {
  1156.                     // Valid table name, but no references to
  1157.                     return null;
  1158.                 } else {
  1159.                     // Invalid table name
  1160.                     return $this->throwError(
  1161.                               DB_TABLE_DATABASE_ERR_NO_TBL,
  1162.                               "getRefTo, $table_name");
  1163.                 }
  1164.             }
  1165.         } else {
  1166.             return $this->throwError(
  1167.                       DB_TABLE_DATABASE_ERR_TBL_NOT_STRING,
  1168.                       "getRefTo");
  1169.         }
  1170.     }
  1171.  
  1172.     // }}}
  1173.     // {{{ function getLink($table1 = null, $table2 = null) 
  1174.  
  1175.     /**
  1176.      * Returns all or part of the $link two-dimensional property array
  1177.      *
  1178.      * Returns $this->_link 2D property array if $table1 and $table2 are null.
  1179.      * Returns $this->_link[$table1] subarray if only $table2 is null.
  1180.      * Returns $this->_link[$table1][$table2] if both parameters are present.
  1181.      *
  1182.      * Returns null if $table1 is a valid table with links to no others, or 
  1183.      * if $table1 and $table2 are both valid table names but there is no 
  1184.      * link between them.
  1185.      * 
  1186.      * The $_link property is a two-dimensional associative array with 
  1187.      * elements of the form $this->_link[$table1][$table2] = array($link1, ...), 
  1188.      * in which the value is an array containing the names of all tables 
  1189.      * that `link' tables named $table1 and $table2, and thereby create a
  1190.      * many-to-many relationship between these two tables. 
  1191.      *
  1192.      * The $_link property is used in the autoJoin method to join tables
  1193.      * that are related by a many-to-many relationship via a linking table,
  1194.      * rather than via a direct foreign key reference. A table that is
  1195.      * declared to be linking table for tables $table1 and $table2 must 
  1196.      * contain foreign keys that reference both of these tables. 
  1197.      *
  1198.      * Each binary link in a database is listed twice in $_link, in
  1199.      * $_link[$table1][$table2] and in $_link[$table2][$table1]. If a
  1200.      * linking table contains foreign key references to N tables, with
  1201.      * N > 2, each of the resulting binary links is listed separately.
  1202.      * For example, a table with references to 3 tables A, B, and C can 
  1203.      * create three binary links (AB, AC, and BC) and six entries in the 
  1204.      * link property array (i.e., in $_link[A][B], $_link[B][A], ... ).
  1205.      *
  1206.      * Linking tables may be added to the $_link property by using the 
  1207.      * addLink method or deleted using the delLink method. Alternatively, 
  1208.      * all possible linking tables can be identified and added to the 
  1209.      * $_link array at once by the addAllLinks() method.
  1210.      *
  1211.      * @param string $table1 name of linked table
  1212.      * @param string $table2 name of linked table
  1213.      * @return mixed $_link property array, sub-array, or value
  1214.      *
  1215.      * @throws PEAR_Error:
  1216.      *    - $table1 or $table2 is not a string (..DATABASE_ERR_TBL_NOT_STRING)
  1217.      *    - $table1 or $table2 is not a table name (..DATABASE_ERR_NO_TBL)
  1218.      *
  1219.      * @access public
  1220.      */
  1221.     function getLink($table1 = null, $table2 = null) 
  1222.     {
  1223.         if (is_null($table1)) {
  1224.             return $this->_link;
  1225.         } elseif (is_string($table1)) {
  1226.             if (isset($this->_link[$table1])) {
  1227.                 if (is_null($table2)) {
  1228.                     return $this->_link[$table1];
  1229.                 } elseif (is_string($table2)) {
  1230.                     if (isset($this->_link[$table1][$table2])) {
  1231.                         return $this->_link[$table1][$table2];
  1232.                     } else {
  1233.                         if (isset($this->_table[$table2])) {
  1234.                             // Valid table names, but no links
  1235.                             return null;
  1236.                         } else {
  1237.                             // Invalid 2nd table name string
  1238.                             return $this->throwError(
  1239.                                       DB_TABLE_DATABASE_ERR_NO_TBL,
  1240.                                       "getLink, $table2");
  1241.                         }
  1242.                     }
  1243.                 } else {
  1244.                     return $this->throwError(
  1245.                               DB_TABLE_DATABASE_ERR_TBL_NOT_STRING,
  1246.                               "getLink");
  1247.                 }
  1248.             } else {
  1249.                 if (isset($this->_table[$table1])) {
  1250.                     // Valid first table name, but no links
  1251.                     return null;
  1252.                 } else {
  1253.                     // Invalid 1st table name string
  1254.                     return $this->throwError(
  1255.                               DB_TABLE_DATABASE_ERR_NO_TBL,
  1256.                               "getLink, $table1");
  1257.                 }
  1258.             }
  1259.         } else {
  1260.             return $this->throwError(
  1261.                       DB_TABLE_DATABASE_ERR_TBL_NOT_STRING,
  1262.                       "getLink");
  1263.         }
  1264.     }
  1265.  
  1266.     // }}}
  1267.     // {{{ function setTableSubclassPath($path) 
  1268.  
  1269.     /**
  1270.      * Sets path to a directory containing DB_Table subclass definitions.
  1271.      *
  1272.      * This method sets the $_table_subclass_path string property. The value of
  1273.      * this property is the path to the directory containing DB_Table subclass 
  1274.      * definitions, without a trailing directory separator. 
  1275.      *  
  1276.      * This path may be used by the __wakeup(), if necessary, in an attempt to 
  1277.      * autoload class definitions when unserializing a DB_Table_Database object 
  1278.      * and its child DB_Table objects. If a DB_Table subclass $subclass_name
  1279.      * has not been defined when it is needed in DB_Table_Database::__wakeup(), 
  1280.      * to unserialize an instance of this class, the __wakeup() method attempts
  1281.      * to include a class definition file from this directory, as follows:
  1282.      * <code>
  1283.      *     $dir = $this->_table_subclass_path;
  1284.      *     require_once $dir . '/' . $subclass . '.php';
  1285.      * </code>
  1286.      * See the getTableSubclass() docblock for a discusion of capitalization
  1287.      * conventions in PHP 4 and 5 for subclass file names. 
  1288.      * 
  1289.      * @param string $path path to directory containing class definitions
  1290.      * @return void
  1291.      * @access public
  1292.      *
  1293.      * @see DB_Table_Database::getTableSubclass()
  1294.      */
  1295.     function setTableSubclassPath($path) 
  1296.     {
  1297.         $this->_table_subclass_path = $path; 
  1298.     }
  1299.  
  1300.     // }}}
  1301.     // {{{ function addTable(&$table_obj)
  1302.  
  1303.     /**
  1304.      * Adds a table to the database.
  1305.      *
  1306.      * Creates references between $this DB_Table_Database object and
  1307.      * the child DB_Table object, by adding a reference to $table_obj
  1308.      * to the $this->_table array, and setting $table_obj->database =
  1309.      * $this. 
  1310.      *
  1311.      * Adds the primary key to $this->_primary_key array. The relevant
  1312.      * element of $this->_primary_key is set to null if no primary key 
  1313.      * index is declared. Returns an error if more than one primary key
  1314.      * is declared.
  1315.      *
  1316.      * Returns true on success, and PEAR error on failure. Returns the
  1317.      * following DB_TABLE_DATABASE_ERR_* error codes if:
  1318.      *    - $table_obj is not a DB_Table ( _DBTABLE_OBJECT )
  1319.      *    - more than one primary key is defined  ( _ERR_MULT_PKEY )
  1320.      *
  1321.      * @param  object &$table_obj the DB_Table object (reference)
  1322.      * @return boolean true on success (PEAR_Error on failure)
  1323.      * @access public
  1324.      */
  1325.     function addTable(&$table_obj)
  1326.     {
  1327.         // Check that $table_obj is a DB_Table object 
  1328.         // Identify subclass name, if any
  1329.         if (is_subclass_of($table_obj, 'DB_Table')) {
  1330.             $subclass = get_class($table_obj);
  1331.         } elseif (is_a($table_obj, 'DB_Table')) {
  1332.             $subclass = null;
  1333.         } else {
  1334.             return $this->throwError(
  1335.                       DB_TABLE_DATABASE_ERR_DBTABLE_OBJECT);
  1336.         }
  1337.  
  1338.         // Identify table name and table object (sub)class name
  1339.         $table = $table_obj->table;
  1340.         
  1341.         // Set $this->_primary_key[$table] 
  1342.         $this->_primary_key[$table] = null;
  1343.         foreach ($table_obj->idx as $idx_name => $idx_def) {
  1344.             if ($idx_def['type'] == 'primary') {
  1345.                 if (is_null($this->_primary_key[$table])) {
  1346.                     $this->_primary_key[$table] = $idx_def['cols'];
  1347.                 } else {
  1348.                     // More than one primary key defined in the table
  1349.                     unset($this->_primary_key[$table]);
  1350.                     return $this->throwError(
  1351.                               DB_TABLE_DATABASE_ERR_MULT_PKEY, $table);
  1352.                 }
  1353.             }
  1354.         }
  1355.  
  1356.         // Add references between $this parent and child table object
  1357.         $this->_table[$table] =& $table_obj;
  1358.         $table_obj->setDatabaseInstance($this); 
  1359.  
  1360.         // Add subclass name (if any) to $this->_table_subclass
  1361.         $this->_table_subclass[$table] = $subclass;
  1362.  
  1363.         // Set shared properties
  1364.         $table_obj->db =& $this->db;
  1365.         $table_obj->backend   = $this->backend;
  1366.         $table_obj->fetchmode = $this->fetchmode;
  1367.  
  1368.         // Add all columns to $_col property
  1369.         foreach ($table_obj->col as $key => $def) {
  1370.             if (!isset($this->_col[$key])) {
  1371.                 $this->_col[$key] = array();
  1372.             }
  1373.             $this->_col[$key][] = $table;
  1374.         }
  1375.  
  1376.         return true;
  1377.     }
  1378.  
  1379.     // }}}
  1380.     // {{{ function deleteTable($table) 
  1381.  
  1382.     /**
  1383.      * Deletes a table from $this database object.
  1384.      *
  1385.      * Removes all dependencies on $table from the database model. The table 
  1386.      * is removed from $_table and $_primary_key properties. Its columns are
  1387.      * removed from the $_col and $_foreign_col properties. References to
  1388.      * and from the table are removed from the $_ref, $_ref_to, and $_link
  1389.      * properties. Referencing columns are removed from $_foreign_col.
  1390.      * 
  1391.      * @param  string $table name of table to be deleted
  1392.      * @return void
  1393.      * @access public
  1394.      */
  1395.     function deleteTable($table) 
  1396.     {
  1397.         if (isset($this->_table[$table])) {
  1398.             $table_obj =& $this->_table[$table];
  1399.         } else {
  1400.             return;
  1401.         }
  1402.  
  1403.         // Remove reference to database from table object
  1404.         $null_instance = null;
  1405.         $table_obj->setDatabaseInstance($null_instance);
  1406.  
  1407.         // Remove columns from $_col and $_foreign_col property arrays
  1408.         foreach ($table_obj->col as $column => $def) {
  1409.             $key = array_search($table, $this->_col[$column]);
  1410.             if (is_integer($key)) {
  1411.                 unset($this->_col[$column][$key]);
  1412.                 if (count($this->_col[$column]) == 0) {
  1413.                     unset($this->_col[$column]);
  1414.                 } else {
  1415.                     $new = array_values($this->_col[$column]);
  1416.                     $this->_col[$column] = $new;
  1417.                 }
  1418.             }
  1419.             if (isset($this->_foreign_col[$column])) {
  1420.                 $key = array_search($table, $this->_foreign_col[$column]);
  1421.                 if (is_integer($key)) {
  1422.                     unset($this->_foreign_col[$column][$key]);
  1423.                     if (count($this->_foreign_col[$column]) == 0) {
  1424.                         unset($this->_foreign_col[$column]);
  1425.                     } else {
  1426.                         $new = array_values($this->_foreign_col[$column]);
  1427.                         $this->_foreign_col[$column] = $new;
  1428.                     }
  1429.                 }
  1430.             }
  1431.         }
  1432.  
  1433.         // Remove all references involving the deleted table.
  1434.         // Corresponding links are removed from $this->_link by deleteRef 
  1435.         // Referencing columns are removed from $this->_foreign_col by deleteRef
  1436.         foreach ($this->_ref as $ftable => $referenced) {
  1437.             foreach ($referenced as $rtable => $ref) {
  1438.                 if ($ftable == $table || $rtable == $table) {
  1439.                     $this->deleteRef($ftable, $rtable);
  1440.                 } 
  1441.             } 
  1442.         }
  1443.  
  1444.         // Remove table from $this->_table and $this->_primary_key 
  1445.         unset($this->_table[$table]);
  1446.         unset($this->_primary_key[$table]);
  1447.     }
  1448.  
  1449.     // }}}
  1450.     // {{{ function addRef($ftable, $fkey, $rtable, [$rkey], [$on_delete], [$on_update])
  1451.  
  1452.     /**
  1453.      * Adds a foreign key reference to the database.
  1454.      *
  1455.      * Adds a reference from foreign key $fkey of table $ftable to
  1456.      * referenced key $rkey of table named $rtable to the $this->_ref
  1457.      * property. The values of $fkey and $rkey (if not null) may either 
  1458.      * both be column name strings (for single column keys) or they 
  1459.      * may both be numerically indexed arrays of corresponding column 
  1460.      * names (for multi-column keys). If $rkey is null (the default), 
  1461.      * the referenced key taken to be the primary key of $rtable, if 
  1462.      * any.
  1463.      *
  1464.      * The $on_delete and $on_update parameters may be either be null, 
  1465.      * or may have string values 'restrict', 'cascade', 'set null', or 
  1466.      * 'set default' that indicate referentially triggered actions to be 
  1467.      * taken deletion or updating of referenced row in $rtable. Each of 
  1468.      * these actions corresponds to a standard SQL action (e.g., cascading 
  1469.      * delete) that may be taken upon referencing rows of table $ftable 
  1470.      * when a referenced row of $rtable is deleted or updated.  A PHP 
  1471.      * null value for either parameter (the default) signifies that no
  1472.      * such action will be taken upon deletion or updating. 
  1473.      *
  1474.      * There may no more than one reference from a table to another, though
  1475.      * reference may contain multiple columns. 
  1476.      *
  1477.      * Returns true on success, and PEAR error on failure. Returns the
  1478.      * following DB_TABLE_DATABASE_ERR_* error codes if:
  1479.      *   - $ftable does not exist ( _NO_FTABLE )
  1480.      *   - $rtable does not exist ( _NO_RTABLE )
  1481.      *   - $rkey is null and $rtable has no primary key ( _NO_PKEY )
  1482.      *   - $fkey is neither a string nor an array ( _FKEY )
  1483.      *   - $rkey is not a string, $fkey is a string ( _RKEY_NOT_STRING )
  1484.      *   - $rkey is not an array, $fkey is an array ( _RKEY_NOT_ARRAY )
  1485.      *   - A column of $fkey does not exist ( _NO_FCOL )
  1486.      *   - A column of $rkey does not exist ( _NO_RCOL )
  1487.      *   - A column of $fkey and $rkey have different types ( _REF_TYPE )
  1488.      *   - A reference from $ftable to $rtable already exists ( _MULT_REF )
  1489.      * 
  1490.      * @param  string  $ftable    name of foreign/referencing table
  1491.      * @param  mixed   $fkey      foreign key in referencing table 
  1492.      * @param  string  $rtable    name of referenced table
  1493.      * @param  mixed   $rkey      referenced key in referenced table
  1494.      * @param  string  $on_delete action upon delete of a referenced row.
  1495.      * @param  string  $on_update action upon update of a referenced row.
  1496.      * @return boolean true on success (PEAR_Error on failure)
  1497.      * @access public
  1498.      */
  1499.     function addRef($ftable, $fkey, $rtable, $rkey = null,
  1500.                              $on_delete = null, $on_update = null)
  1501.     {
  1502.         // Check existence of $ftable is a key in $this->_table.
  1503.         if (isset($this->_table[$ftable])) {
  1504.             $ftable_obj =& $this->_table[$ftable];
  1505.         } else {
  1506.             return $this->throwError(
  1507.                       DB_TABLE_DATABASE_ERR_NO_FTABLE,
  1508.                       "$ftable => $rtable");
  1509.         }
  1510.  
  1511.         // Check existence of referenced table
  1512.         if (isset($this->_table[$rtable])) {
  1513.             $rtable_obj =& $this->_table[$rtable];
  1514.         } else {
  1515.             return $this->throwError(
  1516.                       DB_TABLE_DATABASE_ERR_NO_RTABLE,
  1517.                       "$ftable => $rtable");
  1518.         }
  1519.  
  1520.         // If referenced key is null, set it to the primary key
  1521.         if (!$rkey) {
  1522.             if (isset($this->_primary_key[$rtable])) {
  1523.                 $rkey = $this->_primary_key[$rtable];
  1524.             } else {
  1525.                 // Error: null referenced key and no primary key
  1526.                 return $this->throwError(
  1527.                           DB_TABLE_DATABASE_ERR_NO_PKEY,
  1528.                           "$ftable => $rtable");
  1529.             }
  1530.         }
  1531.  
  1532.         // Check $fkey and $rkey types and compatibility
  1533.         if (is_string($fkey)) {
  1534.             if (!is_string($rkey)) {
  1535.                 return $this->throwError(
  1536.                           DB_TABLE_DATABASE_ERR_RKEY_NOT_STRING,
  1537.                           "$ftable => $rtable");
  1538.             }
  1539.             if (!isset($ftable_obj->col[$fkey])) {
  1540.                 return $this->throwError(
  1541.                           DB_TABLE_DATABASE_ERR_NO_FCOL,
  1542.                           "$ftable.$fkey => $rtable.$rkey");
  1543.             }
  1544.             if (!isset($rtable_obj->col[$rkey])) {
  1545.                 return $this->throwError(
  1546.                           DB_TABLE_DATABASE_ERR_NO_RCOL,
  1547.                           "$ftable.$fkey => $rtable.$rkey");
  1548.             }
  1549.             $ftype = $ftable_obj->col[$fkey]['type'];
  1550.             $rtype = $rtable_obj->col[$rkey]['type'];
  1551.             if (!($rtype == $ftype)) {
  1552.                 return $this->throwError(
  1553.                           DB_TABLE_DATABASE_ERR_REF_TYPE,
  1554.                           "$ftable.$fkey => $rtable.$rkey");
  1555.             }
  1556.         } elseif (is_array($fkey)) {
  1557.             if (!is_array($rkey)) {
  1558.                 return $this->throwError(
  1559.                           DB_TABLE_DATABASE_ERR_RKEY_NOT_ARRAY,
  1560.                           "$ftable => $rtable");
  1561.             }
  1562.             if (!(count($fkey) == count($rkey))) {
  1563.                 return $this->throwError(
  1564.                           DB_TABLE_DATABASE_ERR_RKEY_COL_NUMBER,
  1565.                           "$ftable => $rtable");
  1566.             }
  1567.             for ($i=0 ; $i < count($rkey) ; $i++) {
  1568.                 $fcol = $fkey[$i];
  1569.                 $rcol = $rkey[$i];
  1570.                 if (!isset($ftable_obj->col[$fcol])) {
  1571.                     return $this->throwError(
  1572.                               DB_TABLE_DATABASE_ERR_NO_FCOL,
  1573.                               "$ftable.$fcol => $rtable.$rcol");
  1574.                 }
  1575.                 if (!isset($rtable_obj->col[$rcol])) {
  1576.                     return $this->throwError(
  1577.                               DB_TABLE_DATABASE_ERR_NO_RCOL,
  1578.                               "$ftable.$fcol => $rtable.$rcol");
  1579.                 }
  1580.                 $ftype = $ftable_obj->col[$fcol]['type'];
  1581.                 $rtype = $rtable_obj->col[$rcol]['type'];
  1582.                 if (!($rtype == $ftype)) {
  1583.                     return $this->throwError(
  1584.                               DB_TABLE_DATABASE_ERR_REF_TYPE,
  1585.                               "$ftable.$fcol => $rtable.$rcol");
  1586.                 }
  1587.             }
  1588.         } else {
  1589.             return $this->throwError(
  1590.                       DB_TABLE_DATABASE_ERR_FKEY,
  1591.                       "$ftable => $rtable");
  1592.         }
  1593.  
  1594.         // Check validity of on_delete and on_update actions
  1595.         $valid_actions = 
  1596.                array(null, 'cascade', 'set null', 'set default', 'restrict');
  1597.         if (!in_array($on_delete, $valid_actions)) {
  1598.             return $this->throwError(
  1599.                    DB_TABLE_DATABASE_ERR_ON_DELETE_ACTION,
  1600.                    "$ftable => $rtable");
  1601.         }
  1602.         if (!in_array($on_update, $valid_actions)) {
  1603.             return $this->throwError(
  1604.                    DB_TABLE_DATABASE_ERR_ON_UPDATE_ACTION,
  1605.                    "$ftable => $rtable");
  1606.         }
  1607.  
  1608.         // Add reference to $this->_ref;
  1609.         $ref = array(
  1610.                'fkey' => $fkey, 
  1611.                'rkey' => $rkey,
  1612.                'on_delete' => $on_delete, 
  1613.                'on_update' => $on_update);
  1614.         if (!isset($this->_ref[$ftable])) {
  1615.             $this->_ref[$ftable] = array();
  1616.         } else {
  1617.              if (isset($this->_ref[$ftable][$rtable])) {
  1618.                  // Multiple references from $ftable to $rtable
  1619.                  return $this->throwError(
  1620.                            DB_TABLE_DATABASE_ERR_MULT_REF,
  1621.                            "$ftable => $rtable");
  1622.              }
  1623.         }
  1624.         $this->_ref[$ftable][$rtable] = $ref;
  1625.  
  1626.         // Add referencing table $ftable to $ref_to property
  1627.         if (!isset($this->_ref_to[$rtable])) {
  1628.             $this->_ref_to[$rtable] = array();
  1629.         }
  1630.         $this->_ref_to[$rtable][] = $ftable;
  1631.  
  1632.         // Add foreign key columns to $this->_foreign_col
  1633.         if (is_string($fkey)) {
  1634.             if (!isset($this->_foreign_col[$fkey])) {
  1635.                 $this->_foreign_col[$fkey] = array();
  1636.             }
  1637.             $this->_foreign_col[$fkey][] = $ftable;
  1638.         } elseif (is_array($fkey)) {
  1639.             foreach ($fkey as $fcol) {
  1640.                 if (!isset($this->_foreign_col[$fcol])) {
  1641.                     $this->_foreign_col[$fcol] = array();
  1642.                 }
  1643.                 $this->_foreign_col[$fcol][] = $ftable;
  1644.             }
  1645.         }
  1646.  
  1647.         // Normal completion
  1648.         return true;
  1649.     }
  1650.  
  1651.     // }}}
  1652.     // {{{ function deleteRef($ftable, $rtable) 
  1653.  
  1654.     /**
  1655.      * Deletes one reference from database model
  1656.      *
  1657.      * Removes reference from referencing (foreign key) table named
  1658.      * $ftable to referenced table named $rtable. Unsets relevant elements
  1659.      * of the $ref, $_ref_to, and $_link property arrays, and removes the
  1660.      * foreign key columns of $ftable from the $_foreign_col property. 
  1661.      *
  1662.      * Does nothing, silently, if no such reference exists, i.e., if 
  1663.      * $this->_ref[$ftable][$rtable] is not set.
  1664.      *
  1665.      * @param $ftable name of referencing (foreign key) table
  1666.      * @param $rtable name of referenced table
  1667.      * @return void
  1668.      * @access public
  1669.      */ 
  1670.     function deleteRef($ftable, $rtable) 
  1671.     {
  1672.         // Delete from $_ref property
  1673.         if (isset($this->_ref[$ftable])) {
  1674.             if (isset($this->_ref[$ftable][$rtable])) {
  1675.                 $fkey = $this->_ref[$ftable][$rtable]['fkey'];
  1676.                 unset($this->_ref[$ftable][$rtable]);
  1677.             } else {
  1678.                 // No such reference, abort silently
  1679.                 return;
  1680.             }
  1681.         }
  1682.  
  1683.         // Remove foreign key columns from $foreign_col property
  1684.         if (isset($fkey)) {
  1685.             if (is_string($fkey)) {
  1686.                 $fkey = array($fkey);
  1687.             }
  1688.             foreach ($fkey as $column) {
  1689.                 if (isset($this->_foreign_col[$column])) {
  1690.                     $key = array_search($ftable, 
  1691.                                         $this->_foreign_col[$column]);
  1692.                     if (is_integer($key)) {
  1693.                         unset($this->_foreign_col[$column][$key]);
  1694.                         if (count($this->_foreign_col[$column]) == 0) {
  1695.                             unset($this->_foreign_col[$column]);
  1696.                         } else {
  1697.                             $new = array_values($this->_foreign_col[$column]);
  1698.                             $this->_foreign_col[$column] = $new;
  1699.                         }
  1700.                     }
  1701.                 }
  1702.             }
  1703.         }
  1704.  
  1705.         // Delete from $_ref_to property
  1706.         if (isset($this->_ref_to[$rtable])) {
  1707.             $key = array_search($ftable, $this->_ref_to[$rtable]);
  1708.             // Unset element 
  1709.             unset($this->_ref_to[$rtable][$key]);
  1710.             if (count($this->_ref_to[$rtable]) == 0) {
  1711.                 unset($this->_ref_to[$rtable]);
  1712.             } else {
  1713.                 // Redefine numerical keys of remaining elements
  1714.                 $ref_to = array_values($this->_ref_to[$rtable]);
  1715.                 $this->_ref_to[$rtable] = $ref_to;
  1716.             }
  1717.         }
  1718.  
  1719.         // Delete all relevant links from $_link property
  1720.         if (isset($this->_link[$rtable])) {
  1721.             foreach ($this->_link[$rtable] as $table2 => $links) {
  1722.                 if (in_array($ftable, $links)) {
  1723.                     $this->deleteLink($rtable, $table2, $ftable);
  1724.                 }
  1725.             }
  1726.         }
  1727.     }
  1728.  
  1729.     // }}}
  1730.     // {{{ function setOnDelete($ftable, $rtable, $action)
  1731.  
  1732.     /**
  1733.      * Modifies the on delete action for one foreign key reference.
  1734.      *
  1735.      * Modifies the value of the on_delete action associated with a reference
  1736.      * from $ftable to $rtable. The parameter action may be one of the action
  1737.      * strings 'cascade', 'restrict', 'set null', or 'set default', or it may
  1738.      * be php null. A null value of $action indicates that no action should be
  1739.      * taken upon deletion of a referenced row. 
  1740.      *
  1741.      * Returns true on success, and PEAR error on failure. Returns the error
  1742.      * code DB_TABLE_DATABASE_ERR_REF_TRIG_ACTION if $action is a neither a 
  1743.      * valid action string nor null. Returns true, and does nothing, if 
  1744.      * $this->_ref[$ftable][$rtable] is not set. 
  1745.      *
  1746.      * @param  string $ftable  name of referencing (foreign key) table
  1747.      * @param  string $rtable  name of referenced table
  1748.      * @param  string $action  on delete action (action string or null)
  1749.      * @return boolean true on normal completion (PEAR_Error on failure)
  1750.      * @access public
  1751.      */ 
  1752.     function setOnDelete($ftable, $rtable, $action)
  1753.     {
  1754.         $valid_actions = 
  1755.              array(null, 'cascade', 'set null', 'set default', 'restrict');
  1756.  
  1757.         if (isset($this->_ref[$ftable])) {
  1758.             if (isset($this->_ref[$ftable][$rtable])) {
  1759.                 if (!in_array($action, $valid_actions)) {
  1760.                     return $this->throwError(
  1761.                            DB_TABLE_DATABASE_ERR_REF_ON_DELETE_ACTION,
  1762.                            "$ftable => $rtable");
  1763.                 }
  1764.                 $this->_ref[$ftable][$rtable]['on_delete'] = $action;
  1765.             }
  1766.         }
  1767.         return true;
  1768.     }
  1769.  
  1770.     // }}}
  1771.     // {{{ function setOnUpdate($ftable, $rtable, $action)
  1772.  
  1773.     /**
  1774.      * Modifies on update action for one foreign key reference.
  1775.      *
  1776.      * Similar to setOnDelete. See setOnDelete for further details.
  1777.      *
  1778.      * @param string $ftable  name of referencing (foreign key) table
  1779.      * @param string $rtable  name of referenced table
  1780.      * @param array  $action  on update action (action string or null)
  1781.      * @return boolean true on normal completion (PEAR_Error on failure)
  1782.      * @access public
  1783.      */ 
  1784.     function setOnUpdate($ftable, $rtable, $action)
  1785.     {
  1786.         $valid_actions = 
  1787.              array(null, 'cascade', 'set null', 'set default', 'restrict');
  1788.  
  1789.         if (isset($this->_ref[$ftable])) {
  1790.             if (isset($this->_ref[$ftable][$rtable])) {
  1791.                 if (!in_array($action, $valid_actions)) {
  1792.                     return $this->throwError(
  1793.                            DB_TABLE_DATABASE_ERR_REF_ON_UPDATE_ACTION,
  1794.                            "$ftable => $rtable");
  1795.                 }
  1796.                 $this->_ref[$ftable][$rtable]['on_update'] = $action;
  1797.             }
  1798.         }
  1799.         return true;
  1800.     }
  1801.  
  1802.     // }}}
  1803.     // {{{ function addLink($table1, $table2, $link)
  1804.  
  1805.     /**
  1806.      * Identifies a linking/association table that links two others
  1807.      *
  1808.      * Adds table name $link to $this->_link[$table1][$table2] and 
  1809.      * to $this->_link[$table2][$table1].
  1810.      *
  1811.      * Returns true on success, and PEAR error on failure. Returns the
  1812.      * following DB_TABLE_DATABASE_ERR_* error codes if:
  1813.      *   - $ftable does not exist ( _NO_FTABLE )
  1814.      *   - $rtable does not exist ( _NO_RTABLE )
  1815.      *
  1816.      * @param  string $table1 name of 1st linked table
  1817.      * @param  string $table2 name of 2nd linked table
  1818.      * @param  string $link   name of linking/association table.
  1819.      * @return boolean true on success (PEAR_Error on failure)
  1820.      * @access public
  1821.      */
  1822.     function addLink($table1, $table2, $link)
  1823.     {
  1824.  
  1825.         // Check for existence of all three tables
  1826.         if (is_string($table1)) {
  1827.             if (!isset($this->_table[$table1])) {
  1828.                 return $this->throwError(
  1829.                           DB_TABLE_DATABASE_ERR_NO_TBL,
  1830.                           "addLink, $table1");
  1831.             }
  1832.         } else {
  1833.             return $this->throwError(
  1834.                       DB_TABLE_DATABASE_ERR_NO_TBL,
  1835.                       "addLink, $table1");
  1836.         }
  1837.         if (!isset($this->_table[$table2])) {
  1838.             return $this->throwError(
  1839.                       DB_TABLE_DATABASE_ERR_NO_TBL,
  1840.                       "addLink, $table2");
  1841.         }
  1842.         if (!isset($this->_table[$link])) {
  1843.             return $this->throwError(
  1844.                       DB_TABLE_DATABASE_ERR_NO_TBL,
  1845.                       "addLink, $link");
  1846.         }
  1847.         if (!isset($this->_ref[$link])) {
  1848.             return $this->throwError(
  1849.                       DB_TABLE_DATABASE_ERR_NO_REF_LINK,
  1850.                       "$link => $table1, $table2");
  1851.         } else {
  1852.             if (!isset($this->_ref[$link][$table1])) {
  1853.                 return $this->throwError(
  1854.                           DB_TABLE_DATABASE_ERR_NO_REF_LINK,
  1855.                           "$link => $table1");
  1856.             }
  1857.             if (!isset($this->_ref[$link][$table2])) {
  1858.                 return $this->throwError(
  1859.                           DB_TABLE_DATABASE_ERR_NO_REF_LINK,
  1860.                           "$link => $table2");
  1861.             }
  1862.         }
  1863.  
  1864.         // Add $this_link[$table1][$table2]
  1865.         if (!key_exists($table1, $this->_link)) {
  1866.             $this->_link[$table1] = array();
  1867.         }
  1868.         if (!key_exists($table2, $this->_link[$table1])) {
  1869.             $this->_link[$table1][$table2] = array();
  1870.         }
  1871.         $this->_link[$table1][$table2][] = $link;
  1872.  
  1873.         // Add $this_link[$table2][$table1]
  1874.         if (!key_exists($table2, $this->_link)) {
  1875.             $this->_link[$table2] = array();
  1876.         }
  1877.         if (!key_exists($table1, $this->_link[$table2])) {
  1878.             $this->_link[$table2][$table1] = array();
  1879.         } 
  1880.         $this->_link[$table2][$table1][] = $link;
  1881.     }
  1882.  
  1883.     // }}}
  1884.     // {{{ function addAllLink()
  1885.  
  1886.     /**
  1887.      * Adds all possible linking tables to the $_link property array
  1888.      *
  1889.      * Identifies all potential linking tables in the datbase, and adds
  1890.      * them all to the $_link property.  Table $link is taken to be a 
  1891.      * link between tables $table1 and $table2 if it contains foreign 
  1892.      * key references to both $table1 and $table2. 
  1893.      *
  1894.      * @return void
  1895.      * @access public
  1896.      */
  1897.     function addAllLinks()
  1898.     {
  1899.         foreach ($this->_table as $link => $link_obj) {
  1900.             if (isset($this->_ref[$link])) {
  1901.                 $ref  = $this->_ref[$link];
  1902.                 $n     = count($ref);
  1903.                 $names = array_keys($ref);
  1904.                 if ($n > 1) {
  1905.                     $is_link = true;
  1906.                 } else {
  1907.                     $is_link = false;
  1908.                 }
  1909.                 if ($is_link) {
  1910.                     if ($n == 2) {
  1911.                         $table1 = $names[0];
  1912.                         $table2 = $names[1];
  1913.                         $this->addLink($table1, $table2, $link);
  1914.                     } elseif ($n > 2) {
  1915.                         for ($i=1 ; $i < $n; $i++) {
  1916.                             for ($j=0 ; $j < $i; $j++) {
  1917.                                 $table1 = $names[$j];
  1918.                                 $table2 = $names[$i];
  1919.                                 $this->addLink($table1, $table2, $link);
  1920.                             }
  1921.                         }
  1922.                     }
  1923.                 }
  1924.             }
  1925.         }
  1926.     }
  1927.  
  1928.     // }}}
  1929.     // {{{ function deleteLink($table1, $table2, $link = null)
  1930.  
  1931.     /**
  1932.      * Removes a link between two tables from the $_link property
  1933.      *
  1934.      * If $link is not null, remove table $link from the list of links
  1935.      * between $table1 and $table2, if present. If $link is null, delete
  1936.      * all links between $table1 and $table2. 
  1937.      *
  1938.      * @param  string $table1 name of 1st linked table
  1939.      * @param  string $table2 name of 2nd linked table
  1940.      * @param  string $link   name of linking table
  1941.      * @return void
  1942.      * @access public
  1943.      */
  1944.     function deleteLink($table1, $table2, $link = null)
  1945.     {
  1946.         if (isset($this->_link[$table1])) {
  1947.             if (isset($this->_link[$table1][$table2])) {
  1948.                 if ($link) {
  1949.                     // Find numerical key of $link in _link[$table1][$table2]
  1950.                     $key = array_search($link, $this->_link[$table1][$table2]);
  1951.                     if (is_integer($key)) {
  1952.                         unset($this->_link[$table1][$table2][$key]);
  1953.                         if (count($this->_link[$table1][$table2]) == 0) {
  1954.                             unset($this->_link[$table1][$table2]);
  1955.                             unset($this->_link[$table2][$table1]);
  1956.                             if (count($this->_link[$table1]) == 0) {
  1957.                                 unset($this->_link[$table1]);
  1958.                             }
  1959.                             if (count($this->_link[$table2]) == 0) {
  1960.                                 unset($this->_link[$table2]);
  1961.                             }
  1962.                         } else { 
  1963.                             // Reset remaining indices sequentially from zero
  1964.                             $new = array_values($this->_link[$table1][$table2]);
  1965.                             $this->_link[$table1][$table2] = $new;
  1966.                             $this->_link[$table2][$table1] = $new;
  1967.                         }
  1968.                     }
  1969.                 } else {
  1970.                     unset($this->_link[$table1][$table2]);
  1971.                     unset($this->_link[$table2][$table1]);
  1972.                     if (count($this->_link[$table1]) == 0) {
  1973.                         unset($this->_link[$table1]);
  1974.                     }
  1975.                     if (count($this->_link[$table2]) == 0) {
  1976.                         unset($this->_link[$table2]);
  1977.                     }
  1978.                 }
  1979.             }
  1980.         }
  1981.     }
  1982.  
  1983.     // }}}
  1984.     // {{{ function validCol($col, $from = null)
  1985.  
  1986.     /**
  1987.      * Validates and (if necessary) disambiguates a column name.
  1988.      *
  1989.      * The parameter $col is a string may be either a column name or
  1990.      * a column name qualified by a table name, using the SQL syntax
  1991.      * "$table.$column". If $col contains a table name, and is valid,
  1992.      * an array($table, $column) is returned.  If $col is not qualified 
  1993.      * by a column name, an array array($table, $column) is returned,
  1994.      * in which $table is either the name of one table, or an array
  1995.      * containing the names of two or more tables containing a column 
  1996.      * named $col. 
  1997.      *
  1998.      * The $from parameter, if present, is a numerical array of
  1999.      * names of tables with which $col should be associated, if no
  2000.      * explicit table name is provided, and if possible. If one 
  2001.      * or more of the tables in $from contains a column $col, the 
  2002.      * returned table or set of tables is restricted to those in 
  2003.      * array $from.
  2004.      *
  2005.      * If the table name remains ambiguous after testing for tables in
  2006.      * the $from set, and $col is not a foreign key in one or more of 
  2007.      * the remaining tables, the returned table or set of tables is 
  2008.      * restricted to those in which $col is not a foreign key. 
  2009.      *
  2010.      * Returns a PEAR_Error with the following DB_TABLE_DATABASE_ERR_* error
  2011.      * codes if:
  2012.      *    - column $col does not exist in the database ( _NO_COL_DB )
  2013.      *    - column $col does not exist in the specified table ( _NO_COL_TBL )
  2014.      * 
  2015.      * @param  string $col  column name, optionally qualified by a table name
  2016.      * @param  array  $from array of tables from which $col should be chosen,
  2017.      *                      if possible.
  2018.      * @return array  array($table, $column), or PEAR_Error on failure
  2019.      *                $column is a string, $table is a string or array
  2020.      * @access public
  2021.      */
  2022.     function validCol($col, $from = null)
  2023.     {
  2024.         $col = explode('.',trim($col));
  2025.         if (count($col) == 1) { 
  2026.             // Parameter $col is a column name with no table name
  2027.             $column = $col[0];
  2028.             // Does $column exist in database ?
  2029.             if (!isset($this->_col[$column])) {
  2030.                 return $this->throwError(
  2031.                           DB_TABLE_DATABASE_ERR_NO_COL_DB, 
  2032.                           "$column");
  2033.             }
  2034.             $table = $this->_col[$column];
  2035.             // If $table is not unique, try restricting to arrays in $from
  2036.             if (count($table) > 1 && $from) {
  2037.                 $ptable = array_intersect($table, $from);
  2038.                 if (count($ptable) > 0) {
  2039.                     $table = array_values($ptable);
  2040.                 }
  2041.             }
  2042.             // If count($table)>1, try excluding foreign key columns
  2043.             if (count($table) > 1 && isset($this->_foreign_col[$column])) {
  2044.                 $ptable = array_diff($table, $this->_foreign_col[$column]);
  2045.                 if (count($ptable) > 0) {
  2046.                     $table = array_values($ptable);
  2047.                 }
  2048.             }
  2049.             // If only one table remains, set $table = table name string
  2050.             if (count($table) == 1) {
  2051.                 $table = $table[0];
  2052.             }
  2053.         } elseif (count($col) == 2) { 
  2054.             // parameter $col is qualified by a table name
  2055.             $table  = $col[0];
  2056.             $column = $col[1];
  2057.             if (isset($this->_table[$table])) {
  2058.                  $table_obj =& $this->_table[$table];
  2059.                  $col_array = $table_obj->col;
  2060.                  if (!isset($col_array[$column])) {
  2061.                      return $this->throwError(
  2062.                                DB_TABLE_DATABASE_ERR_NO_COL_TBL,
  2063.                                "$table.$column");
  2064.                  }
  2065.             } else {
  2066.                 return $this->throwError(
  2067.                           DB_TABLE_DATABASE_ERR_NO_TBL, "validCol, $table");
  2068.             }
  2069.         }
  2070.         return array($table, $column);
  2071.     }
  2072.  
  2073.     // }}}
  2074.     // {{{ function createTables($flag = 'safe')
  2075.  
  2076.     /**
  2077.      * Creates all the tables in a database in a RDBMS
  2078.      *
  2079.      * Note: this method creates all the tables in a database, but does
  2080.      * NOT create the parent database or set it to the current or default
  2081.      * database -- the database must exist before the method is called.
  2082.      *
  2083.      * If creation of any table fails, the method immediately returns the
  2084.      * PEAR error returned by DB_Table::create($flag).
  2085.      *
  2086.      * @param mixed $flag The automatic database creation mode, which is
  2087.      *                    applied to each table in the database. It can have
  2088.      *                    values:
  2089.      *                    - 'safe' to create a table only if it does not exist
  2090.      *                    - 'drop' to drop and recreate any existing table 
  2091.                              with the same name
  2092.      *
  2093.      * @return boolean true on sucess (PEAR_Error on failure of any table)
  2094.      * @access public
  2095.      *
  2096.      * @see DB_Table::create()
  2097.      */
  2098.     function createTables($flag = 'safe')
  2099.     {
  2100.         foreach ($this->_table as $name => $table) {
  2101.             $result = $table->create($flag);
  2102.             if (PEAR::isError($result)) {
  2103.                 return $result;
  2104.             }
  2105.         }
  2106.         return true;
  2107.     }
  2108.  
  2109.     // }}}
  2110.     // {{{ function validForeignKeys($table_name, $data)
  2111.  
  2112.     /**
  2113.      * Check validity of any foreign key values in associative array $data
  2114.      * containing values to be inserted or updated in table $table_name.
  2115.      *
  2116.      * Returns true if each foreign key in $data matches a row in the
  2117.      * referenced table, or if there are no foreign key columns in $data.  
  2118.      * Returns a PEAR_Error if any foreign key column in associative array 
  2119.      * $data (which may contain a full or partial row of $table_name), does 
  2120.      * not match the the value of the referenced column in any row of the 
  2121.      * referenced table.
  2122.      *
  2123.      * @param $table_name name of the referencing table containing $data
  2124.      * @param @data       associative array containing all or part of a row
  2125.      *                    of data of $table_name, with column name keys.
  2126.      * @return bool true if all foreign keys are valid, returns PEAR_Error
  2127.      *              if foreign keys are invalid or if an error is thrown 
  2128.      *              by a required query
  2129.      * 
  2130.      * @throws PEAR error if:
  2131.      *    - Error thrown by _buildFKeyFilter method (bubbles up)
  2132.      *    - Error thrown by select method for required query (bubbles up)
  2133.      *
  2134.      * @access public
  2135.      */
  2136.     function validForeignKeys($table_name, $data)
  2137.     {
  2138.         if (isset($this->_ref[$table_name])) {
  2139.             foreach ($this->_ref[$table_name] as $rtable_name => $ref) {
  2140.                 $fkey = $ref['fkey'];
  2141.                 $rkey = $ref['rkey'];
  2142.                 $rtable_obj =& $this->_table[$rtable_name];
  2143.  
  2144.                 // Construct select where clause for referenced rows,
  2145.                 // $filter = '' if $data contains no foreign key columns,
  2146.                 $filter = $this->_buildFKeyFilter($data, $fkey, $rkey);
  2147.                 if (PEAR::isError($filter)) {
  2148.                     return $filter;
  2149.                 }
  2150.  
  2151.                 // If inserted data contain FK columns referenced by rtable,
  2152.                 // select referenced row of rtable, return error if none is
  2153.                 // found
  2154.                 if ($filter) {
  2155.                     $sql = array('select'=> '*',
  2156.                                  'from'  => $rtable_name,
  2157.                                  'where' => $filter);
  2158.                     $referenced_rows = $this->select($sql);
  2159.                     // Check for failed query
  2160.                     if (PEAR::isError($referenced_rows)) {
  2161.                         return $referenced_rows;
  2162.                     }
  2163.                     // Check for failed foreign key constraint
  2164.                     if (count($referenced_rows) == 0) {
  2165.                         return $this->throwError( 
  2166.                                DB_TABLE_DATABASE_ERR_FKEY_CONSTRAINT);
  2167.                     }
  2168.                 }
  2169.             }
  2170.         }
  2171.         return true;
  2172.     }
  2173.  
  2174.     // }}}
  2175.     // {{{ function insert($table_name, $data)
  2176.  
  2177.     /**
  2178.      * Inserts a single table row 
  2179.      *
  2180.      * Wrapper for insert method of the corresponding DB_Table object.
  2181.      *
  2182.      * Data will be validated before insertion using validForeignKey(),
  2183.      * if foreign key validation in enabled.
  2184.      *
  2185.      * @param string $table_name Name of table into which to insert data
  2186.      * @param array $data Associative array, in which each key is a column
  2187.      *                     name and each value is that column's value.
  2188.      *                     This is the data that will be inserted into
  2189.      *                     the table. Data is checked against the column
  2190.      *                     names and data types for validity.
  2191.      * @return boolean true on success (PEAR_Error on failure)
  2192.      * @access public
  2193.      */
  2194.     function insert($table_name, $data)
  2195.     {
  2196.         // Dereference table object
  2197.         if (isset($this->_table[$table_name])) {
  2198.             $table_obj =& $this->_table[$table_name];
  2199.         } else {
  2200.             return $this->throwError(
  2201.                        DB_TABLE_DATABASE_ERR_NO_TBL,
  2202.                        "insert, $table_name");
  2203.         }
  2204.  
  2205.         // Insert into $table_obj
  2206.         $result = $table_obj->insert($data);
  2207.  
  2208.         // Return value: true or PEAR_Error
  2209.         if (PEAR::isError($result)) {
  2210.             return $result;
  2211.         } else {
  2212.             return true;
  2213.         }
  2214.  
  2215.     }
  2216.  
  2217.     // }}}
  2218.     // {{{ function autoValidInsert($flag = true)
  2219.  
  2220.     /**
  2221.      * Turns on or off automatic validation of inserted data for all tables
  2222.      *
  2223.      * @param bool $flag true to turn on auto-validation, false to turn off.
  2224.      * @return void
  2225.      * @access public
  2226.      */
  2227.     function autoValidInsert($flag = true)
  2228.     {
  2229.         foreach ($this->_table as $table_obj) {
  2230.            $table_obj->autoValidInsert($flag);
  2231.         }
  2232.     }
  2233.  
  2234.     // }}}
  2235.     // {{{ function update($table_name, $data, $where)
  2236.  
  2237.     /**
  2238.      * Updates all row(s) of table that match a custom where clause.
  2239.      *
  2240.      * Wrapper for insert method of the corresponding DB_Table object.
  2241.      * 
  2242.      * Data will be validated before insertion using validForeignKey(),
  2243.      * if foreign key validation in enabled.
  2244.      *
  2245.      * Implements any required ON UPDATE actions on tables that 
  2246.      * reference updated columns, if on update actions are enabled.
  2247.      *
  2248.      * @param string $table_name name of table to update
  2249.      * @param array  $data  associative array in which keys are names of
  2250.      *                       columns to be updated values are new values.
  2251.      * @param string $where SQL WHERE clause that limits the set of
  2252.      *                       records to update.
  2253.      * @return boolean true on success (PEAR_Error on failure)
  2254.      * @access public
  2255.      */
  2256.     function update($table_name, $data, $where)
  2257.     {
  2258.         // Dereference table object
  2259.         if (isset($this->_table[$table_name])) {
  2260.             $table_obj =& $this->_table[$table_name];
  2261.         } else {
  2262.             return $this->throwError(
  2263.                        DB_TABLE_DATABASE_ERR_NO_TBL,
  2264.                        "update, $table_name");
  2265.         }
  2266.  
  2267.         // Apply update
  2268.         $result = $table_obj->update($data, $where);
  2269.  
  2270.         // Return value: true or PEAR_Error
  2271.         if (PEAR::isError($result)) {
  2272.             return $result;
  2273.         } else {
  2274.             return true;
  2275.         }
  2276.  
  2277.     }
  2278.  
  2279.     // }}}
  2280.     // {{{ function autoValidUpdate($flag = true)
  2281.  
  2282.     /**
  2283.      * Turns on (or off) automatic validation of updated data for all tables.
  2284.      *
  2285.      * @param  bool $flag true to turn on auto-validation, false to turn off
  2286.      * @return void
  2287.      * @access public
  2288.      */
  2289.     function autoValidUpdate($flag = true)
  2290.     {
  2291.         foreach ($this->_table as $table_obj) {
  2292.             $table_obj->autoValidUpdate($flag);
  2293.         }
  2294.     }
  2295.  
  2296.     // }}}
  2297.     // {{{ function onUpdateAction(&$table_obj, $data, $where)
  2298.  
  2299.     /**
  2300.      * Implements any ON UPDATE actions triggered by updating of rows of
  2301.      * $table_obj that match logical condition $where.
  2302.      *
  2303.      * This method is called by the DB_Table::update() method if the table
  2304.      * has a parent DB_Table_Database object, and if ON UPDATE actions are
  2305.      * enabled in the database object. It is called indirectly by the
  2306.      * DB_Table_Database::delete() method, which is simply a wrapper for
  2307.      * the DB_Table method. 
  2308.      *
  2309.      * @param  object &$table_obj Reference to a DB_Table object
  2310.      * @param  array  $data  Data to updated, column name keys, data values
  2311.      * @param  string $where SQL logical condition for updated rows
  2312.      * @return boolean true on success (PEAR_Error on failure)
  2313.      * @access public
  2314.      */
  2315.     function onUpdateAction(&$table_obj, $data, $where)
  2316.     {
  2317.         $table_name = $table_obj->table;
  2318.         if ($this->_act_on_update and isset($this->_ref_to[$table_name])) {
  2319.             $update_rows = null;
  2320.             foreach ($this->_ref_to[$table_name] as $ftable_name) {
  2321.                 $ref    = $this->_ref[$ftable_name][$table_name];
  2322.                 $action = isset($ref['on_update']) ? $ref['on_update'] : null;
  2323.                 if (is_null($action)) {
  2324.                    continue;
  2325.                 }
  2326.                 $rtable_obj =& $this->_table[$table_name];
  2327.                 $ftable_obj =& $this->_table[$ftable_name];
  2328.                 $fkey = $ref['fkey'];
  2329.                 $rkey = $ref['rkey'];
  2330.  
  2331.                 // Check if any column(s) of referenced $rkey are updated
  2332.                 $rkey_updated = false;
  2333.                 foreach ($data as $key => $value) {
  2334.                     if (is_string($rkey)){
  2335.                         if ($key == $rkey) {
  2336.                             $rkey_updated = true;
  2337.                             break;
  2338.                         }
  2339.                     } else {
  2340.                         if (in_array($key, $rkey)) {
  2341.                             $rkey_updated = true;
  2342.                             break;
  2343.                         }
  2344.                     }
  2345.                 }
  2346.  
  2347.                 // If $rkey is not updated, continue to next referencing table
  2348.                 if (!$rkey_updated) {
  2349.                     continue;
  2350.                 }
  2351.  
  2352.                 // Select rows to be updated, if not done previously
  2353.                 if ($update_rows === null) {
  2354.                     if ($this->backend == 'mdb2') {
  2355.                         $fetchmode_assoc = MDB2_FETCHMODE_ASSOC;
  2356.                     } else {
  2357.                         $fetchmode_assoc = DB_FETCHMODE_ASSOC;
  2358.                     }
  2359.                     $sql = array('select' => '*',
  2360.                                  'from'   => $table_name,
  2361.                                  'where'  => $where,
  2362.                                  'fetchmode' => $fetchmode_assoc);
  2363.                     $update_rows = $this->select($sql);
  2364.                     if (PEAR::isError($update_rows)) {
  2365.                         return $update_rows;
  2366.                     }
  2367.                 }
  2368.  
  2369.                 // Construct $fdata array if cascade, set null, or set default
  2370.                 $fdata = null;
  2371.                 if ($action == 'cascade') {
  2372.                     if (is_string($rkey)) {
  2373.                         if (array_key_exists($rkey, $data)) {
  2374.                             $fdata = array($fkey => $data[$rkey]);
  2375.                         }
  2376.                     } else {
  2377.                         $fdata = array();
  2378.                         for ($i=0; $i < count($rkey); $i++) {
  2379.                             $rcol = $rkey[$i];
  2380.                             $fcol = $fkey[$i];
  2381.                             if (array_key_exists($rcol, $data)) {
  2382.                                 $fdata[$fcol] = $data[$rcol];
  2383.                             }
  2384.                         }
  2385.                         if (count($fdata) == 0) {
  2386.                            $fdata = null;
  2387.                         }
  2388.                     }
  2389.                 } elseif ($action == 'set null' or $action == 'set default') {
  2390.                     if (is_string($fkey)) {
  2391.                         if ($action == 'set default') {
  2392.                             $value = isset($ftable_obj->col[$fkey]['default'])
  2393.                                    ? $ftable_obj->col[$fkey]['default'] : null;
  2394.                         } else {
  2395.                             $value = null;
  2396.                         }
  2397.                         $fdata = array($fkey => $value);
  2398.                     } else {
  2399.                         $fdata = array();
  2400.                         foreach ($fkey as $fcol) {
  2401.                             if ($action == 'set default') {
  2402.                                 $value = isset($ftable_obj->col[$fcol]['default'])
  2403.                                       ? $ftable_obj->col[$fcol]['default'] : null;
  2404.                             } else {
  2405.                                 $value = null;
  2406.                             }
  2407.                             $fdata[$fcol] = $value;
  2408.                         }
  2409.                         if (count($fdata) == 0) {
  2410.                            $fdata = null;
  2411.                         }
  2412.                     }
  2413.                 } elseif ($action == 'restrict') {
  2414.                     $fdata = true;
  2415.                 } elseif ($action == null) {
  2416.                     $fdata = null;
  2417.                 } else {
  2418.                     return $this->throwError(
  2419.                         DB_TABLE_DATABASE_ERR_ON_UPDATE_ACTION,
  2420.                         "$ftable_name => $table_name");
  2421.                 }
  2422.  
  2423.                 if (!is_null($fdata)) {
  2424.  
  2425.                     // Loop over rows to be updated from $table
  2426.                     foreach ($update_rows as $update_row) {
  2427.  
  2428.                         // If necessary, restore case of column names
  2429.                         if ($this->_fix_case) {
  2430.                             $cols = array_keys($table_obj->col);
  2431.                             $update_row = $this->_replaceKeys($update_row, $cols);
  2432.                         }
  2433.  
  2434.                         // Construct filter for rows that reference $update_row
  2435.                         $filter = $this->_buildFKeyFilter($update_row, 
  2436.                                                           $rkey, $fkey);
  2437.     
  2438.                         // Apply action to foreign/referencing rows
  2439.                         if ($action == 'restrict') {
  2440.                             $sql = array('select'=>'*',
  2441.                                          'from'  => $ftable_name,
  2442.                                          'where' => $filter);
  2443.                             $frows = $this->select($sql);
  2444.                             if (PEAR::isError($frows)) {
  2445.                                 return $frows;
  2446.                             }
  2447.                             if (count($frows) > 0) {
  2448.                                  return $this->throwError(
  2449.                                         DB_TABLE_DATABASE_ERR_RESTRICT_UPDATE,
  2450.                                         $table_name);
  2451.                             }
  2452.                         } else {
  2453.                             // If 'cascade', 'set null', or 'set default',
  2454.                             // then update the referencing foreign key.
  2455.                             // Note: Turn off foreign key validity check
  2456.                             // during update, then restore original value
  2457.                             $check_fkey = $this->_check_fkey;
  2458.                             $this->_check_fkey = false;
  2459.                             $result = $this->update($ftable_name, $fdata, 
  2460.                                                     $filter);
  2461.                             $this->_check_fkey = $check_fkey;
  2462.                             if (PEAR::isError($result)) {
  2463.                                 return $result;
  2464.                             }
  2465.                         }
  2466.                     } // foreach ($update_row)
  2467.                 } // if (!is_null($fdata))
  2468.  
  2469.             } // foreach loop over referencing tables
  2470.         } // end if
  2471.  
  2472.         // Normal completion
  2473.         return true;
  2474.  
  2475.     }
  2476.  
  2477.     // }}}
  2478.     // {{{ function autoRecast($flag = true)
  2479.  
  2480.     /**
  2481.      * Turns on (or off) automatic recasting of insert and update data
  2482.      * for all tables
  2483.      *
  2484.      * @param bool $flag True to automatically recast insert and update
  2485.      *                   data, in all tables, false to not do so.
  2486.      * @return void
  2487.      * @access public
  2488.      */
  2489.     function autoRecast($flag = true)
  2490.     {
  2491.         foreach ($this->_table as $table_obj) {
  2492.             $table_obj->autoRecast($flag);
  2493.         }
  2494.     }
  2495.  
  2496.     // }}}
  2497.     // {{{ function autoInc($flag = true)
  2498.  
  2499.     /**
  2500.      * Turns on (or off) php implementation of auto-incrementing on insertion
  2501.      * for all tables
  2502.      *
  2503.      * @param bool $flag True to turn on auto-incrementing, false to turn off
  2504.      * @return void
  2505.      * @access public
  2506.      */
  2507.     function autoInc($flag = true)
  2508.     {
  2509.         foreach ($this->_table as $table_obj) {
  2510.             $table_obj->auto_inc = $flag;
  2511.         }
  2512.     }
  2513.  
  2514.     // }}}
  2515.     // {{{ function delete($table_name, $where)
  2516.  
  2517.     /**
  2518.      * Deletes all row(s) of table that match a custom where clause.
  2519.      *
  2520.      * Wrapper for insert method of the corresponding DB_Table object.
  2521.      *
  2522.      * Implements any required ON DELETE action on tables that reference
  2523.      * deleted rows, if on delete actions are enabled.
  2524.      *
  2525.      * @param string $table_name name of table from which to delete
  2526.      * @param string $where      SQL WHERE clause that limits the set
  2527.      *                           of records to delete
  2528.      * @return boolean true on success (PEAR_Error on failure)
  2529.      * @access public
  2530.      */
  2531.     function delete($table_name, $where)
  2532.     {
  2533.         // Dereference table object
  2534.         if (isset($this->_table[$table_name])) {
  2535.             $table_obj =& $this->_table[$table_name];
  2536.         } else {
  2537.             return $this->throwError(
  2538.                        DB_TABLE_DATABASE_ERR_NO_TBL,
  2539.                        "delete, $table_name");
  2540.         }
  2541.  
  2542.         // Delete from $table_obj
  2543.         $result = $table_obj->delete($where);
  2544.  
  2545.         // Return value: true or PEAR_Error
  2546.         if (PEAR::isError($result)) {
  2547.             return $result;
  2548.         } else {
  2549.             return true;
  2550.         }
  2551.  
  2552.     }
  2553.  
  2554.     // }}}
  2555.     // {{{ function onDeleteAction(&$table_obj, $where)
  2556.  
  2557.     /**
  2558.      * Implements ON DELETE actions triggered by deletion of rows of
  2559.      * $table_obj that match logical condition $where.
  2560.      *
  2561.      * This method is called by the DB_Table::delete() method if the table
  2562.      * has a parent DB_Table_Database object, and if ON DELETE actions are
  2563.      * enabled in the database object. It is called indirectly by the
  2564.      * DB_Table_Database::delete() method, which is simply a wrapper for
  2565.      * the DB_Table method. 
  2566.      *
  2567.      * @param  object &$table_obj Reference to a DB_Table object
  2568.      * @param  string $where SQL logical condition for deleted rows
  2569.      * @return boolean true on success (PEAR_Error on failure)
  2570.      * @access public
  2571.      */
  2572.     function onDeleteAction(&$table_obj, $where)
  2573.     {
  2574.         $table_name = $table_obj->table;
  2575.         if ($this->_act_on_delete and isset($this->_ref_to[$table_name])) {
  2576.             $delete_rows = null;
  2577.             foreach ($this->_ref_to[$table_name] as $ftable_name) {
  2578.                 $ref    = $this->_ref[$ftable_name][$table_name];
  2579.                 $action = $ref['on_delete'];
  2580.                 if (is_null($action)) {
  2581.                    continue;
  2582.                 } 
  2583.                 $ftable_obj =& $this->_table[$ftable_name];
  2584.                 $rtable_obj =& $this->_table[$table_name];
  2585.                 $fkey = $ref['fkey'];
  2586.                 $rkey = $ref['rkey'];
  2587.  
  2588.                 // Select rows to be deleted, if not done previously
  2589.                 if ($delete_rows === null) {
  2590.                     if ($this->backend == 'mdb2') {
  2591.                         $fetchmode_assoc = MDB2_FETCHMODE_ASSOC;
  2592.                     } else {
  2593.                         $fetchmode_assoc = DB_FETCHMODE_ASSOC;
  2594.                     }
  2595.                     $sql = array('select' => '*',
  2596.                                  'from'   => $table_name,
  2597.                                  'where'  => $where,
  2598.                                  'fetchmode' => $fetchmode_assoc);
  2599.                     $delete_rows = $this->select($sql);
  2600.                     if (PEAR::isError($delete_rows)) {
  2601.                         return $delete_rows;
  2602.                     }
  2603.                 }
  2604.  
  2605.                 // If set null or set default, construct update $fdata
  2606.                 // $fdata contains data for updating referencing rows
  2607.                 if ($action == 'set null' or $action == 'set default') {
  2608.                     if (is_string($fkey)) {
  2609.                         if ($action == 'set default') {
  2610.                             $value = isset($ftable_obj->col[$fkey]['default'])
  2611.                                    ? $ftable_obj->col[$fkey]['default'] : null;
  2612.                         } else {
  2613.                             $value = null;
  2614.                         }
  2615.                         $fdata = array($fkey => $value);
  2616.                     } else {
  2617.                         $fdata = array();
  2618.                         foreach ($fkey as $fcol) {
  2619.                             if ($action == 'set default') {
  2620.                                 $value = isset($ftable_obj->col[$fcol]['default'])
  2621.                                       ? $ftable_obj->col[$fcol]['default'] : null;
  2622.                             } else {
  2623.                                 $value = null;
  2624.                             }
  2625.                             $fdata[$fcol] = $value;
  2626.                         }
  2627.                     }
  2628.                 }
  2629.  
  2630.                 // Loop over rows to be deleted from $table_name
  2631.                 foreach ($delete_rows as $delete_row) {
  2632.  
  2633.                     // If necessary, restore case of $delete_row column names
  2634.                     if ($this->_fix_case) {
  2635.                         $cols = array_keys($table_obj->col);
  2636.                         $delete_row = $this->_replaceKeys($delete_row, $cols);
  2637.                     }
  2638.  
  2639.                     // Construct filter for referencing rows in $ftable_name
  2640.                     $filter = $this->_buildFKeyFilter($delete_row, 
  2641.                                                       $rkey, $fkey);
  2642.  
  2643.                     // Apply action for one deleted row
  2644.                     if ($action == 'restrict') {
  2645.                         // Select for referencing rows throw error if found
  2646.                         $sql = array('select'=>'*',
  2647.                                      'from'  => $ftable_name,
  2648.                                      'where' => $filter);
  2649.                         $frows = $this->select($sql);
  2650.                         if (PEAR::isError($frows)) {
  2651.                             return $frows;
  2652.                         }
  2653.                         if (count($frows) > 0) {
  2654.                              return $this->throwError(
  2655.                                        DB_TABLE_DATABASE_ERR_RESTRICT_DELETE,
  2656.                                        $table_name);
  2657.                         }
  2658.                     } elseif ($action == 'cascade') {
  2659.                         // Delete referencing rows
  2660.                         // Note: Recursion on delete
  2661.                         $result = $this->delete($ftable_name, $filter);
  2662.                         if (PEAR::isError($result)) {
  2663.                             return $result;
  2664.                         }
  2665.                     } elseif ($action == 'set null' OR $action == 'set default') {
  2666.                         // Update referencing rows, using $fdata
  2667.                         // Note: Turn off foreign key validity check during
  2668.                         // update of referencing key to null or default, then
  2669.                         // restore $this->_check_fkey to original value
  2670.                         $check_fkey = $this->_check_fkey;
  2671.                         $this->_check_fkey = false;
  2672.                         $result = $this->update($ftable_name, $fdata, $filter);
  2673.                         $this->_check_fkey = $check_fkey;
  2674.                         #$result = $ftable_obj->update($fdata, $filter);
  2675.                         if (PEAR::isError($result)) {
  2676.                             return $result;
  2677.                         }
  2678.                     } else {
  2679.                         // Invalid $action name, throw Error
  2680.                         return $this->throwError(
  2681.                            DB_TABLE_DATABASE_ERR_ON_DELETE_ACTION,
  2682.                            "$ftable_name => $table_name");
  2683.                     }
  2684.                 } // end foreach ($delete_rows)
  2685.  
  2686.             } // end foreach ($this->_ref_to[...] as $ftable_name)
  2687.         } // end if 
  2688.  
  2689.         // Normal completion
  2690.         return true; 
  2691.  
  2692.     }
  2693.  
  2694.     // }}}
  2695.     // {{{ function _replaceKeys($data, $keys) 
  2696.  
  2697.     /**
  2698.      * Returns array in which keys of associative array $data are replaced 
  2699.      * by values of sequential array $keys.
  2700.      *
  2701.      * This function is used by the onDeleteAction() and onUpdateAction() 
  2702.      * methods to restore the case of column names in associative arrays 
  2703.      * that are returned from an automatically generated query "SELECT * 
  2704.      * FROM $table WHERE ...", when these column name keys are returned 
  2705.      * with a fixed case. In this usage, $keys is a sequential array of 
  2706.      * the names of all columns in $table. 
  2707.      *
  2708.      * @param  array $data associative array
  2709.      * @param  array $key  numerical array of replacement key names
  2710.      * @return array associative array in which keys of $data have been 
  2711.      *               replaced by the values of array $keys.
  2712.      * @access private
  2713.      */
  2714.     function _replaceKeys($data, $keys) 
  2715.     {
  2716.         $new_data = array();
  2717.         $i = 0;
  2718.         foreach ($data as $old_key => $value) {
  2719.             $new_key = $keys[$i];
  2720.             $new_data[$new_key] = $value;
  2721.             $i = $i + 1;
  2722.         }
  2723.         return $new_data;
  2724.     }
  2725.  
  2726.     // }}}
  2727.     // {{{ function autoJoin($cols = null, $tables = null, $filter = null)
  2728.  
  2729.     /**
  2730.      * Builds a select command involving joined tables from 
  2731.      * a list of column names and/or a list of table names.
  2732.      *
  2733.      * Returns an query array of the form used in $this->buildSQL,
  2734.      * constructed on the basis of a sequential array $cols of
  2735.      * column names and/or a sequential array $tables of table
  2736.      * names.  The 'FROM' clause in the resulting SQL contains 
  2737.      * all the table listed in the $tables parameter and all 
  2738.      * those containing the columns listed in the $cols array,
  2739.      * as well as any linking tables required to establish 
  2740.      * many to many relationships between these tables. The
  2741.      * 'WHERE' clause is constructed so as to create an inner
  2742.      * join of these tables.
  2743.      *
  2744.      * The $cols parameter is a sequential array in which the
  2745.      * values are column names. Column names may be qualified
  2746.      * by a table name, using the SQL table.column syntax, but
  2747.      * need not be qualified if they are unambiguous. The 
  2748.      * values in $cols can only be column names, and may not 
  2749.      * be functions or more complicated SQL expressions. If
  2750.      * cols is null, the resulting SQL command will start with 
  2751.      * 'SELECT * FROM ...' .
  2752.      *
  2753.      * The $tables parameter is a sequential array in which the
  2754.      * values are table names. If $tables is null, the FROM
  2755.      * clause is constructed from the tables containing the
  2756.      * columns in the $cols. 
  2757.      * 
  2758.      * The $params array is an associative array can have
  2759.      * 'filter', and 'order' keys, which are both optional.
  2760.      * A value $params['filter'] is an condition string to
  2761.      * add (i.e., AND) to the automatically constructed set
  2762.      * of join conditions. A value $params['order'] is an
  2763.      * SQL 'ORDER BY' clause, with no 'ORDER BY' prefix.
  2764.      *
  2765.      * The function returns an associative array with keys
  2766.      * ('select', 'from', 'where', ['order']), for which the
  2767.      * associated values are strings containing the SELECT,
  2768.      * FROM, WHERE and (optionally) ORDER BY clauses of the
  2769.      * select statement. The entire SELECT command string
  2770.      * can be obtained by passing the resulting array to
  2771.      * the buildSQL method.
  2772.      *
  2773.      * @param  array $cols   sequential array of column names
  2774.      * @param  array $tables sequential array of table names
  2775.      * @param  array $filter SQL logical expression to be added 
  2776.      *                       (ANDed) to the where clause
  2777.      * @return array sql query array for select statement
  2778.      * @access public
  2779.      */
  2780.     function autoJoin($cols = null, $tables = null, $filter = null)
  2781.     {
  2782.         // initialize array containing clauses of select statement
  2783.         $query = array();
  2784.  
  2785.         if (is_null($tables)) {
  2786.             if (is_null($cols)) {
  2787.                 return $this->throwError(
  2788.                           DB_TABLE_DATABASE_ERR_NO_COL_NO_TBL);
  2789.             }
  2790.             $tables = array();
  2791.         }
  2792.  
  2793.         if (!$cols) {
  2794.             // If no columns specified, SELECT * FROM ...
  2795.             $query['select'] = '*';
  2796.         } else {
  2797.  
  2798.             // Qualify unqualified columns with table names
  2799.             $all_tables = $tables;
  2800.             foreach ($cols as $key => $col) {
  2801.                 $col_array  = $this->validCol($col, $tables);
  2802.                 if (PEAR::isError($col_array)) {
  2803.                      return $col_array;
  2804.                 }
  2805.                 $table  = $col_array[0];
  2806.                 $column = $col_array[1];
  2807.                 if (is_array($table)) {
  2808.                     return $this->throwError(
  2809.                            DB_TABLE_DATABASE_ERR_COL_NOT_UNIQUE, $col);
  2810.                 }
  2811.                 $cols[$key] = "$table.$column";
  2812.                 if (!in_array($table, $all_tables)) {
  2813.                     $all_tables[] = $table;
  2814.                 }
  2815.             }
  2816.             $tables = $all_tables;
  2817.  
  2818.             // Construct select clause
  2819.             $query['select'] = implode(', ', $cols);
  2820.  
  2821.         }
  2822.  
  2823.         // Construct array $joins of join conditions
  2824.         $n_tables = count($tables);
  2825.         if ($n_tables == 1) {
  2826.             $query['from'] = $tables[0];
  2827.         } else {
  2828.             $join_tables = array($tables[0]); // list of joined tables
  2829.             $link_tables = array();           // list of required linking tables
  2830.             $joins       = array();           // list of join conditions
  2831.             // Initialize linked list of unjoined tables
  2832.             $next  = array();                  
  2833.             for ( $i=1 ; $i < $n_tables-1 ; $i++) {
  2834.                 $next[$tables[$i]]   = $tables[$i+1];
  2835.                 $prev[$tables[$i+1]] = $tables[$i];
  2836.             }
  2837.             $next[$tables[$n_tables-1]] = $tables[1];
  2838.             $prev[$tables[1]] = $tables[$n_tables-1];
  2839.             $n_remain = $n_tables - 1;
  2840.             $head     = $tables[1];
  2841.             $table1   = $tables[1];
  2842.             $joined   = false;
  2843.             $direct   = true;
  2844.             while ($n_remain > 0) {
  2845.  
  2846.                 if ($direct) {
  2847.  
  2848.                     // Search for references from table1 to joined tables
  2849.                     if (isset($this->_ref[$table1])) {
  2850.                         $list = $this->_ref[$table1];
  2851.                         foreach ($list as $table2 => $def) {
  2852.                             if (in_array($table2, $join_tables)) {
  2853.                                 if ($joined) {
  2854.                                     return $this->throwError(
  2855.                                            DB_TABLE_DATABASE_ERR_AMBIG_JOIN,
  2856.                                            $table1);
  2857.                                 }
  2858.                                 $fkey = $def['fkey'];
  2859.                                 $rkey = $def['rkey'];
  2860.                                 if (is_string($fkey)) {
  2861.                                     $joins[] = "$table1.$fkey = $table2.$rkey";
  2862.                                 } else {
  2863.                                     for ($i=0; $i < count($fkey); $i++ ) {
  2864.                                         $fcol = $fkey[$i];
  2865.                                         $rcol = $rkey[$i];
  2866.                                         $joins[] = 
  2867.                                                "$table1.$fcol = $table2.$rcol";
  2868.                                     }
  2869.                                 }
  2870.                                 $joined  = true;
  2871.                             }
  2872.                         }
  2873.                     }
  2874.  
  2875.                     // Search for references to table1 from joined tables
  2876.                     if (isset($this->_ref_to[$table1])) {
  2877.                         $list = $this->_ref_to[$table1];
  2878.                         foreach ($list as $table2) {
  2879.                             if (in_array($table2, $join_tables)) {
  2880.                                 if ($joined) {
  2881.                                     return $this->throwError(
  2882.                                               DB_TABLE_DATABASE_ERR_AMBIG_JOIN,
  2883.                                               $table1);
  2884.                                 }
  2885.                                 $def  = $this->_ref[$table2][$table1];
  2886.                                 $fkey = $def['fkey'];
  2887.                                 $rkey = $def['rkey'];
  2888.                                 if (is_string($fkey)) {
  2889.                                     $joins[] = "$table2.$fkey = $table1.$rkey";
  2890.                                 } else {
  2891.                                     for ($i=0; $i < count($fkey); $i++ ) {
  2892.                                         $fcol = $fkey[$i];
  2893.                                         $rcol = $rkey[$i];
  2894.                                         $joins[] = 
  2895.                                                "$table2.$fcol = $table1.$rcol";
  2896.                                     }
  2897.                                 }
  2898.                                 $joined  = true;
  2899.                             }
  2900.                         }
  2901.                     }
  2902.  
  2903.                 } else {
  2904.  
  2905.                     // Search for indirect linking table to table1
  2906.                     if (isset($this->_link[$table1])) {
  2907.                         foreach ($this->_link[$table1] as $table2 => $links) {
  2908.                             if (in_array($table2, $join_tables)) {
  2909.                                 $n_link = count($links);
  2910.                                 if ($n_link > 1) {
  2911.                                     return $this->throwError(
  2912.                                       DB_TABLE_DATABASE_ERR_AMBIG_JOIN,
  2913.                                       $table1);
  2914.                                 }
  2915.                                 if ($joined and $n_link > 0) {
  2916.                                     return $this->throwError(
  2917.                                       DB_TABLE_DATABASE_ERR_AMBIG_JOIN,
  2918.                                       $table1);
  2919.                                 }
  2920.                                 $link  = $links[0];
  2921.                                 $def1  = $this->_ref[$link][$table1];
  2922.                                 $fkey1 = $def1['fkey'];
  2923.                                 $rkey1 = $def1['rkey'];
  2924.                                 if (is_string($fkey1)) {
  2925.                                     $joins[] = "$link.$fkey1 = $table1.$rkey1";
  2926.                                 } else {
  2927.                                     for ($i=0; $i < count($fkey1); $i++ ) {
  2928.                                         $fcol1 = $fkey1[$i];
  2929.                                         $rcol1 = $rkey1[$i];
  2930.                                         $joins[] = 
  2931.                                                "$link.$fcol1 = $table1.$rcol1";
  2932.                                     }
  2933.                                 }
  2934.                                 $def2  = $this->_ref[$link][$table2];
  2935.                                 $fkey2 = $def2['fkey'];
  2936.                                 $rkey2 = $def2['rkey'];
  2937.                                 if (is_string($fkey2)) {
  2938.                                     $joins[] = "$link.$fkey2 = $table2.$rkey2";
  2939.                                 } else {
  2940.                                     for ($i=0; $i < count($fkey2); $i++ ) {
  2941.                                         $fcol2 = $fkey2[$i];
  2942.                                         $rcol2 = $rkey2[$i];
  2943.                                         $joins[] = 
  2944.                                               "$link.$fcol2 = $table2.$rcol2";
  2945.                                     }
  2946.                                 }
  2947.                                 $link_tables[] = $link;
  2948.                                 $joined = true;
  2949.                             }
  2950.                         }
  2951.                     }
  2952.  
  2953.                 }
  2954.  
  2955.                 if ($joined) {
  2956.                     $join_tables[] = $table1;
  2957.                     $n_remain = $n_remain - 1;
  2958.                     if ($n_remain > 0) {
  2959.                         $head   = $next[$table1];
  2960.                         $tail   = $prev[$table1];
  2961.                         $prev[$head] = $tail;
  2962.                         $next[$tail] = $head;
  2963.                         $table1 = $head;
  2964.                         $joined = false;
  2965.                         $direct = true;
  2966.                     }
  2967.                 } else {
  2968.                     $table1 = $next[$table1];
  2969.                     if ($table1 == $head) {
  2970.                         if ($direct) {
  2971.                             $direct = false;
  2972.                         } else {
  2973.                             return $this->throwError(
  2974.                                    DB_TABLE_DATABASE_ERR_FAIL_JOIN,$table1);
  2975.                         }
  2976.                     }
  2977.                 }
  2978.  
  2979.             }
  2980.  
  2981.             // Add any required linking tables to $tables array 
  2982.             if ($link_tables) {
  2983.                 foreach ($link_tables as $link) {
  2984.                     if (!in_array($link, $tables)) {
  2985.                         $tables[] = $link;
  2986.                     }
  2987.                 }
  2988.             }
  2989.  
  2990.             // Construct from clause from $tables array
  2991.             $query['from'] = implode(', ', $tables);
  2992.  
  2993.             // Construct where clause from $joins array
  2994.             $query['where'] = implode("\n  AND ", $joins);
  2995.  
  2996.         }
  2997.  
  2998.         // Add $filter condition, if present
  2999.         if ($filter) {
  3000.            if (isset($query['where'])) {
  3001.                $query['where'] = '( ' . $query['where'] . " )\n" .
  3002.                            '  AND ( ' . $filter . ')';
  3003.            } else {
  3004.                $query['where'] = $filter;
  3005.            }
  3006.         }
  3007.  
  3008.         return $query;
  3009.     }
  3010.  
  3011.     // }}}
  3012.     // {{{ function _buildFKeyFilter($data, $data_key = null, $filt_key = null, $match = 'simple')
  3013.  
  3014.     /**
  3015.      * Returns WHERE clause equating values of $data array to database column 
  3016.      * values
  3017.      *
  3018.      * Usage: The function is designed to return an SQL logical 
  3019.      * expression that equates the values of a set of foreign key columns in
  3020.      * associative array $data, which is a row to be inserted or updated in
  3021.      * one table, to the values of the corresponding columns of a referenced 
  3022.      * table. In this usage, $data_key is the foreign key (a column name or
  3023.      * numerical array of column names), and $filt_key is the corresponding
  3024.      * referenced key. 
  3025.      * 
  3026.      * Parameters: Parameter $data is an associative array containing data to 
  3027.      * be inserted into or used to update one row of a database table, in which
  3028.      * array keys are column names. When present, $data_key contains either
  3029.      * the name of a single array key of interest, or a numerical array of such
  3030.      * keys. These are usually the names of the columns of a foreign key in 
  3031.      * that table. When, $data_key is null or absent, it is taken to be equal 
  3032.      * to an array containing all of the keys of $data. When present, $filt_key
  3033.      * contains either a string or a numerical array of strings that are 
  3034.      * aliases for the keys in $data_key.  These are usually the names of the
  3035.      * corresponding columns in the referenced table. When $filt_key is null 
  3036.      * or absent, it is equated with $data_key internally.  The function 
  3037.      * returns an SQL logical expression that equates the values in $data 
  3038.      * whose keys are specified by $data_key, to the values of database 
  3039.      * columns whose names are specified in $filt_key. 
  3040.      *
  3041.      * General case: _buildFKeyFilter returns a SQL logical expression that 
  3042.      * equates the values of $data whose keys are given in $data_key with the 
  3043.      * values of database columns with names given in $filt_key. For example,
  3044.      * if
  3045.      * <code>
  3046.      *    $data = array( 'k1' => $v1, 'k2' => $v2, ... , 'k10' => $v10 );
  3047.      *    $data_key = array('k2', 'k5', 'k7');
  3048.      *    $filt_key = array('c2', 'c5', 'c7');
  3049.      * </code>
  3050.      * then buildFilter($data, $data_key, $filt_key) returns a string
  3051.      * <code>
  3052.      *    "c2 = $v2 AND c5 = $v5 AND c7 = $v7" 
  3053.      * </code>
  3054.      * in which the values $v2, $v5, $v7 are replaced by properly quoted 
  3055.      * SQL literal values. If, in the above example, $data_key = 'k5' 
  3056.      * and $filt_key = 'c5', then the function will return
  3057.      * <code>
  3058.      *    "c5 = $v5" 
  3059.      * </code>
  3060.      * where (again) $v5 is replaced by an SQL literal. 
  3061.      *
  3062.      * Simple case: If parameters $data_key and $filt_key are null, the 
  3063.      * behavior is the same as that of the DB_Table_Base::buildFilter() method. 
  3064.      * For example, if
  3065.      * <code>
  3066.      *     $data = array( 'c1' => $v1, 'c2' => $v2, 'c3' => $v3)
  3067.      * </code>
  3068.      * then _buildFKeyFilter($data) returns a string 
  3069.      * <code>
  3070.      *     "c1 => $val1 AND c2 => $val2 AND c3 = $v3"
  3071.      * </code>
  3072.      * in which the values $v1, $v2, $v3 are replaced by SQL literal values,
  3073.      * quoted and escaped as appropriate for each data type and the backend.
  3074.      *
  3075.      * Quoting is done by the DB_Table_Database::quote() method, based on
  3076.      * the php type of the values in $array.  The treatment of null values 
  3077.      * in $data depends upon the value of the $match parameter.
  3078.      *
  3079.      * Null values: The treatment to null values in $data depends upon 
  3080.      * the value of the $match parameter . If $match == 'simple', an empty
  3081.      * string is returned if any $value of $data with a key in $data_key
  3082.      * is null. If $match == 'partial', the returned SQL expression 
  3083.      * equates only the relevant non-null values of $data to the values of
  3084.      * corresponding database columns. If $match == 'full', the function
  3085.      * returns an empty string if all of the relevant values of data are
  3086.      * null, and returns a PEAR_Error if some of the selected values are
  3087.      * null and others are not null.
  3088.      *
  3089.      * @param array $data     associative array, keys are column names
  3090.      * @param mixed $data_key string or numerical array of strings, in which
  3091.      *                        values are a set of keys of interest in $data
  3092.      * @param mixed $data_key string or numerical array of strings, in which
  3093.      *                        values are names of a corresponding set of
  3094.      *                        database column names.
  3095.      * @return string SQL expression equating values in $data, for which keys
  3096.      *                also appear in $data_key, to values of corresponding 
  3097.      *                database columns named in $filt_key.
  3098.      * @access private
  3099.      */
  3100.     function _buildFKeyFilter($data, $data_key = null, $filt_key = null, 
  3101.                               $match = 'simple')
  3102.     {
  3103.         // Check $match type value
  3104.         if (!in_array($match, array('simple', 'partial', 'full'))) {
  3105.             return $this->throwError(
  3106.                             DB_TABLE_DATABASE_ERR_MATCH_TYPE);
  3107.         }
  3108.  
  3109.         // Simple case: Build filter from $data array alone
  3110.         if (is_null($data_key) && is_null($filt_key)) {
  3111.             return $this->buildFilter($data, $match);
  3112.         }
  3113.  
  3114.         // Defaults for $data_key and $filt_key:
  3115.         if (is_null($data_key)) {
  3116.             $data_key = array_keys($data);
  3117.         }
  3118.         if (is_null($filt_key)) {
  3119.             $filt_key = $data_key;
  3120.         }
  3121.  
  3122.         // General case: $data_key and/or $filt_key not null
  3123.         if (is_string($data_key)) {
  3124.             if (!is_string($filt_key)) {
  3125.                  return $this->throwError(
  3126.                             DB_TABLE_DATABASE_ERR_FILT_KEY);
  3127.             }
  3128.             if (array_key_exists($data_key, $data)) {
  3129.                 $value = $data[$data_key];
  3130.                 if (!is_null($value)) {
  3131.                     $value = (string) $this->quote($data[$data_key]);
  3132.                     return "$filt_key = $value";
  3133.                 } else {
  3134.                     return '';
  3135.                 }
  3136.             } else {
  3137.                 return '';
  3138.             }
  3139.         } elseif (is_array($data_key)) {
  3140.             if (!is_array($filt_key)) {
  3141.                 return $this->throwError(
  3142.                           DB_TABLE_DATABASE_ERR_FILT_KEY);
  3143.             }
  3144.             $filter = array();
  3145.             for ($i=0; $i < count($data_key); $i++) {
  3146.                 $data_col = $data_key[$i];
  3147.                 $filt_col = $filt_key[$i];
  3148.                 if (array_key_exists($data_col, $data)) {
  3149.                     $value = $data[$data_col];
  3150.                     if (!is_null($value)) {
  3151.                         if ($match == 'full' && isset($found_null)) {
  3152.                             return $this->throwError(
  3153.                                       DB_TABLE_DATABASE_ERR_FULL_KEY);
  3154.                         }
  3155.                         $value = $this->quote($value);
  3156.                         $filter[] = "$filt_col = $value";
  3157.                     } else {
  3158.                         $found_null = true;
  3159.                     }
  3160.                 }
  3161.             }
  3162.             if ($match == 'simple' && isset($found_null)) {
  3163.                 return '';
  3164.             }
  3165.             if (count($filter) == 0) {
  3166.                 return '';
  3167.             }
  3168.             return implode(' AND ', $filter);
  3169.         } else {
  3170.             // Invalid data key
  3171.             return $this->throwError(
  3172.                       DB_TABLE_DATABASE_ERR_DATA_KEY);
  3173.         }
  3174.     }
  3175.  
  3176.     // }}}
  3177.     // {{{ function quote($value)
  3178.  
  3179.     /**
  3180.      * Returns SQL literal string representation of a php value
  3181.      *
  3182.      * Calls MDB2::quote() or DB_Common::quoteSmart() to enquote and
  3183.      * escape string values. If $value is: 
  3184.      *    - a string, return the string enquoted and escaped
  3185.      *    - a number, return cast of number to string, without quotes
  3186.      *    - a boolean, return '1' for true and '0' for false
  3187.      *    - null, return the string 'NULL'
  3188.      * 
  3189.      * @param  mixed  $value 
  3190.      * @return string Representation of value as an SQL literal
  3191.      * 
  3192.      * @access public
  3193.      *
  3194.      * @see DB_Common::quoteSmart()
  3195.      * @see MDB2::quote()
  3196.      */
  3197.     function quote($value)
  3198.     {
  3199.         if (is_bool($value)) {
  3200.            return $value ? '1' : '0';
  3201.         } 
  3202.         if ($this->backend == 'mdb2') {
  3203.             $value = $this->db->quote($value);
  3204.         } else {
  3205.             $value = $this->db->quoteSmart($value);
  3206.         }
  3207.         return (string) $value;
  3208.     }
  3209.     
  3210.     // }}}
  3211.     // {{{ function __sleep()
  3212.  
  3213.     /**
  3214.      * Serializes all table references and sets $db = null, $backend = null
  3215.      *
  3216.      * @return array names of all properties
  3217.      * @access public
  3218.      */
  3219.     function __sleep()
  3220.     {
  3221.         $this->db      = null;
  3222.         $this->backend = null;
  3223.         // needed in setDatabaseInstance, where null is passed by reference
  3224.         $null_variable  = null;
  3225.         foreach ($this->_table as $name => $table_obj) {
  3226.             $table_obj->db = null;
  3227.             $table_obj->setDatabaseInstance($null_variable);
  3228.             $this->_table[$name] = serialize($table_obj);
  3229.         }
  3230.         return array_keys(get_object_vars($this));
  3231.     }
  3232.  
  3233.     // }}}
  3234.     // {{{ function __wakeup()
  3235.  
  3236.     /**
  3237.      * Unserializes DB_Table_Database object and all child DB_Table objects
  3238.      *
  3239.      * Immediately after unserialization, a DB_Table_Database object 
  3240.      * has null $db and $backend properties, as do all of its child 
  3241.      * DB_Table objects. The DB_Table_Database::setDB method should 
  3242.      * be called immediately after unserialization to re-establish 
  3243.      * the database connection, like so:
  3244.      * <code>
  3245.      *    $db_object = unserialize($serialized_db);
  3246.      *    $db_object->setDB($conn);
  3247.      * </code>
  3248.      * where $conn is a DB/MDB2 object.  This establishes a DB/MDB2
  3249.      * connection for both the parent database and all child tables.
  3250.      *
  3251.      * This method unserializes all of the child DB_Table objects of
  3252.      * a DB_Table_Database object. It must thus have access to the 
  3253.      * definitions of the associated DB_Table subclasses. These are
  3254.      * listed in the $_table_subclass property. If a required subclass 
  3255.      * named $subclass is not defined, the __wakeup() method attempts 
  3256.      * to autoload a file "$subclass.php" in the directory specified
  3257.      * by $this->table_subclass_path. 
  3258.      *
  3259.      * @return void
  3260.      * @access public
  3261.      */
  3262.     function __wakeup()
  3263.     {
  3264.         foreach ($this->_table as $name => $table_string) {
  3265.  
  3266.             // Check for subclass definition, and autoload if necessary.
  3267.             $subclass = $this->_table_subclass[$name];
  3268.             if (!is_null($subclass)) {
  3269.                 if (!class_exists($subclass)) {
  3270.                     $dir = $this->_table_subclass_path;
  3271.                     require_once $dir . '/' . $subclass . '.php';
  3272.                 }
  3273.             }
  3274.             // Unserialize table
  3275.             $table_obj = unserialize($table_string);
  3276.             // Reset references between database and table objects
  3277.             $table_obj->setDatabaseInstance($this);
  3278.             $this->_table[$name] = $table_obj;
  3279.         }
  3280.     }
  3281.  
  3282.     // }}}
  3283.     // {{{ function toXML()
  3284.  
  3285.     /**
  3286.      * Returns XML string representation of database declaration
  3287.      *
  3288.      * @param  string $indent string of whitespace, prefix to each line
  3289.      * @return string XML string representation
  3290.      * @access public
  3291.      */
  3292.     function toXML($indent = '') {
  3293.         require_once 'DB/Table/XML.php';
  3294.         $s = array();
  3295.         $s[] = DB_Table_XML::openTag('database', $indent);
  3296.         foreach ($this->_table as $name => $table_obj) {
  3297.             $s[] = $table_obj->toXML($indent);
  3298.         }
  3299.         $s[] = DB_Table_XML::closeTag('database', $indent);
  3300.         return implode("\n", $s);
  3301.     }
  3302.  
  3303.     // }}}
  3304.     // {{{ function fromXML($xml_string, $conn)
  3305.  
  3306.     /**
  3307.      * Returns a DB_Table_Database object constructed from an XML string
  3308.      *
  3309.      * Uses the MDB2 XML schema for a database element, including a new
  3310.      * syntax for foreign key indices. 
  3311.      *
  3312.      * NOTE: This function requires PHP 5. It throws an error if used
  3313.      * with PHP 4. 
  3314.      *
  3315.      * @param  string XML string representation
  3316.      * @return object DB_Table_Database object on success (PEAR_Error on failure)
  3317.      *
  3318.      * @throws PEAR_Error if:
  3319.      *    - PHP version is not >= 5.0.0 (...DATABASE_ERR_PHP_VERSION )
  3320.      *    - Parsing by simpleXML fails (...DATABASE_ERR_XML_PARSE )
  3321.      *
  3322.      * @access public
  3323.      */
  3324.     function fromXML($xml_string, $conn)
  3325.     {
  3326.         // Check PHP version. Throw error if not >= PHP 5.0.0
  3327.         $version = phpversion();
  3328.         if (version_compare($version, '5.0.0', "<")) {
  3329.             return $this->throwError(
  3330.                    DB_TABLE_DATABASE_ERR_PHP_VERSION,
  3331.                    $version);
  3332.         }
  3333.  
  3334.         $xml = simplexml_load_string($xml_string);
  3335.         if ($xml == false) {
  3336.             return $this->throwError(
  3337.                    DB_TABLE_DATABASE_ERR_XML_PARSE);
  3338.         }
  3339.     
  3340.         // Instantiate database object
  3341.         $database_name = (string) $xml->name;
  3342.         $database_obj  = new DB_Table_Database($conn, $database_name);
  3343.    
  3344.         // Create array of foreign key references
  3345.         $ref = array();
  3346.    
  3347.         // Loop over tables
  3348.         foreach ($xml->table as $table) {
  3349.             $table_name = (string) $table->name;
  3350.     
  3351.             // Instantiate table object
  3352.             $table_obj = new DB_Table($conn, $table_name);
  3353.         
  3354.             // Add columns to table object
  3355.             $declaration = $table->declaration;
  3356.             foreach ($declaration->field as $field) {
  3357.                 $col_name = (string) $field->name;
  3358.                 $type     = (string) $field->type;
  3359.                 $def = array('type' => $type);
  3360.                 if (isset($field->length)) {
  3361.                     $def['size'] = (integer) $field->length;
  3362.                 }
  3363.                 if (isset($field->notnull)) {
  3364.                     if ($field->notnull) {
  3365.                         $def['require'] = true;
  3366.                     } else {
  3367.                         $def['require'] = false;
  3368.                     }
  3369.                 }
  3370.                 if (isset($field->default)) {
  3371.                     $def['default'] = $field->default;
  3372.                 }
  3373.                 if (isset($field->autoincrement)) {
  3374.                     if (is_null($table_obj->auto_inc_col)) {
  3375.                         $table_obj->auto_inc_col = $col_name;
  3376.                     } else {
  3377.                         return $this->throwError(
  3378.                                   DB_TABLE_DATABASE_ERR_XML_MULT_AUTO_INC);
  3379.                     }
  3380.                 }
  3381.                 $table_obj->col[$col_name] = $def;
  3382.             }
  3383.         
  3384.             // Add indices 
  3385.             foreach ($declaration->index as $index) {
  3386.                 if (isset($index->name)) {
  3387.                     $name = (string) $index->name;
  3388.                 } else {
  3389.                     $name = null;
  3390.                 }
  3391.                 $def = array();
  3392.                 if (isset($index->primary)) {
  3393.                     $def['type'] = 'primary';
  3394.                 } elseif (isset($index->unique)) {
  3395.                     $def['type'] = 'unique';
  3396.                 } else {
  3397.                     $def['type'] = 'normal';
  3398.                 }
  3399.                 foreach ($index->field as $field) {
  3400.                     $def['cols'][] = (string) $field;
  3401.                 }
  3402.                 if ($name) {
  3403.                     $table_obj->idx[$name] = $def;
  3404.                 } else {
  3405.                     $table_obj->idx[] = $def;
  3406.                 }
  3407.             }
  3408.  
  3409.             // Add table object to database object
  3410.             $database_obj->addTable($table_obj);
  3411.  
  3412.             // Foreign key references
  3413.             foreach ($declaration->foreign as $foreign) {
  3414.                 if (isset($foreign->name)) {
  3415.                     $name = (string) $foreign->name;
  3416.                 } else {
  3417.                     $name = null;
  3418.                 }
  3419.                 $fkey = array();
  3420.                 foreach ($foreign->field as $field) {
  3421.                     $fkey[] = (string) $field;
  3422.                 }
  3423.                 if (count($fkey) == 1) {
  3424.                     $fkey = $fkey[0];
  3425.                 }
  3426.                 $rtable = (string) $foreign->references->table;
  3427.                 if (isset($foreign->references->field)) {
  3428.                     $rkey = array();
  3429.                     foreach ($foreign->references->field as $field) {
  3430.                         $rkey[] = (string) $field;
  3431.                     }
  3432.                     if (count($rkey)==1) {
  3433.                         $rkey = $rkey[0];
  3434.                     }
  3435.                 } else {
  3436.                     $rkey = null;
  3437.                 }
  3438.                 if (isset($foreign->ondelete)) {
  3439.                     $on_delete = (string) $foreign->ondelete;
  3440.                 } else {
  3441.                     $on_delete = null;
  3442.                 }
  3443.                 if (isset($foreign->onupdate)) {
  3444.                     $on_update = (string) $foreign->onupdate;
  3445.                 } else {
  3446.                     $on_update = null;
  3447.                 }
  3448.  
  3449.                 // Add reference definition to $ref array
  3450.                 $def = array();
  3451.                 $def['fkey'] = $fkey;
  3452.                 $def['rkey'] = $rkey;
  3453.                 $def['on_delete'] = $on_delete;
  3454.                 $def['on_update'] = $on_update;
  3455.                 if (!isset($ref[$table_name])) {
  3456.                     $ref[$table_name] = array();
  3457.                 }
  3458.                 $ref[$table_name][$rtable] = $def;
  3459.  
  3460.             }
  3461.  
  3462.             // Release variable $table_obj to refer to another table
  3463.             unset($table_obj);
  3464.         }
  3465.  
  3466.         // Add all references to database object
  3467.         foreach ($ref as $ftable => $list) {
  3468.             foreach ($list as $rtable => $def) {
  3469.                 $fkey = $def['fkey'];
  3470.                 $rkey = $def['rkey'];
  3471.                 $on_delete = $def['on_delete'];
  3472.                 $on_update = $def['on_update'];
  3473.                 $database_obj->addRef($ftable, $fkey, $rtable, $rkey,
  3474.                                       $on_delete, $on_update);
  3475.             }
  3476.         }
  3477.  
  3478.         return $database_obj;
  3479.     }
  3480.  
  3481.     // }}}
  3482.  
  3483.     // }}}
  3484. }
  3485.  
  3486. // }}}
  3487.  
  3488. /* Local variables:
  3489.  * tab-width: 4
  3490.  * c-basic-offset: 4
  3491.  * c-hanging-comment-ender-p: nil
  3492.  * End:
  3493.  */
  3494.  
  3495. ?>
  3496.