1<?php
2
3/**
4 * DB_Table is a database API and data type SQL abstraction class.
5 *
6 * DB_Table provides database API abstraction, data type abstraction,
7 * automated SELECT, INSERT, and UPDATE queries, automated table
8 * creation, automated validation of inserted/updated column values,
9 * and automated creation of QuickForm elements based on the column
10 * definitions.
11 *
12 * PHP versions 4 and 5
13 *
14 * LICENSE:
15 *
16 * Copyright (c) 1997-2007, Paul M. Jones <pmjones@php.net>
17 *                          David C. Morse <morse@php.net>
18 *                          Mark Wiesemann <wiesemann@php.net>
19 * All rights reserved.
20 *
21 * Redistribution and use in source and binary forms, with or without
22 * modification, are permitted provided that the following conditions
23 * are met:
24 *
25 *    * Redistributions of source code must retain the above copyright
26 *      notice, this list of conditions and the following disclaimer.
27 *    * Redistributions in binary form must reproduce the above copyright
28 *      notice, this list of conditions and the following disclaimer in the
29 *      documentation and/or other materials provided with the distribution.
30 *    * The names of the authors may not be used to endorse or promote products
31 *      derived from this software without specific prior written permission.
32 *
33 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
34 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
35 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
36 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
37 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
38 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
39 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
40 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
41 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
42 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
43 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
44 *
45 * @category Database
46 * @package  DB_Table
47 * @author   Paul M. Jones <pmjones@php.net>
48 * @author   David C. Morse <morse@php.net>
49 * @author   Mark Wiesemann <wiesemann@php.net>
50 * @license  http://opensource.org/licenses/bsd-license.php New BSD License
51 * @version  CVS: $Id: Table.php,v 1.90 2008/12/25 19:56:35 wiesemann Exp $
52 * @link     http://pear.php.net/package/DB_Table
53 */
54
55/**
56 * Error code at instantiation time when the first parameter to the
57 * constructor is not a PEAR DB object.
58 */
59define('DB_TABLE_ERR_NOT_DB_OBJECT',    -1);
60
61/**
62 * Error code at instantiation time when the PEAR DB/MDB2 $phptype is not
63 * supported by DB_Table.
64 */
65define('DB_TABLE_ERR_PHPTYPE',          -2);
66
67/**
68 * Error code when you call select() or selectResult() and the first
69 * parameter is a string that does not match any of the $this->sql keys.
70 */
71define('DB_TABLE_ERR_SQL_UNDEF',        -3);
72
73/**
74 * Error code when you call select*() or buildSQL() and the first
75 * parameter is neither an array nor a string
76 */
77define('DB_TABLE_ERR_SQL_NOT_STRING',   -4);
78
79/**
80 * Error code when you try to insert data to a column that is not in the
81 * $this->col array.
82 */
83define('DB_TABLE_ERR_INS_COL_NOMAP',    -5);
84
85/**
86 * Error code when you try to insert data, and that data does not have a
87 * column marked as 'require' in the $this->col array.
88 */
89define('DB_TABLE_ERR_INS_COL_REQUIRED', -6);
90
91/**
92 * Error code when auto-validation fails on data to be inserted.
93 */
94define('DB_TABLE_ERR_INS_DATA_INVALID', -7);
95
96/**
97 * Error code when you try to update data to a column that is not in the
98 * $this->col array.
99 */
100define('DB_TABLE_ERR_UPD_COL_NOMAP',    -8);
101
102/**
103 * Error code when you try to update data, and that data does not have a
104 * column marked as 'require' in the $this->col array.
105 */
106define('DB_TABLE_ERR_UPD_COL_REQUIRED', -9);
107
108/**
109 * Error code when auto-validation fails on update data.
110 */
111define('DB_TABLE_ERR_UPD_DATA_INVALID', -10);
112
113/**
114 * Error code when you use a create() flag that is not recognized (must
115 * be 'safe', 'drop', 'verify' or boolean false.
116 */
117define('DB_TABLE_ERR_CREATE_FLAG',      -11);
118
119/**
120 * Error code at create() time when you define an index in $this->idx
121 * that has no columns.
122 */
123define('DB_TABLE_ERR_IDX_NO_COLS',      -12);
124
125/**
126 * Error code at create() time when you define an index in $this->idx
127 * that refers to a column that does not exist in the $this->col array.
128 */
129define('DB_TABLE_ERR_IDX_COL_UNDEF',    -13);
130
131/**
132 * Error code at create() time when you define a $this->idx index type
133 * that is not recognized (must be 'normal' or 'unique').
134 */
135define('DB_TABLE_ERR_IDX_TYPE',         -14);
136
137/**
138 * Error code at create() time when you have an error in a 'char' or
139 * 'varchar' definition in $this->col (usually because 'size' is wrong).
140 */
141define('DB_TABLE_ERR_DECLARE_STRING',   -15);
142
143/**
144 * Error code at create() time when you have an error in a 'decimal'
145 * definition (usually becuase the 'size' or 'scope' are wrong).
146 */
147define('DB_TABLE_ERR_DECLARE_DECIMAL',  -16);
148
149/**
150 * Error code at create() time when you define a column in $this->col
151 * with an unrecognized 'type'.
152 */
153define('DB_TABLE_ERR_DECLARE_TYPE',     -17);
154
155/**
156 * Error code at validation time when a column in $this->col has an
157 * unrecognized 'type'.
158 */
159define('DB_TABLE_ERR_VALIDATE_TYPE',    -18);
160
161/**
162 * Error code at create() time when you define a column in $this->col
163 * with an invalid column name (usually because it's a reserved keyword).
164 */
165define('DB_TABLE_ERR_DECLARE_COLNAME',  -19);
166
167/**
168 * Error code at create() time when you define an index in $this->idx
169 * with an invalid index name (usually because it's a reserved keyword).
170 */
171define('DB_TABLE_ERR_DECLARE_IDXNAME',  -20);
172
173/**
174 * Error code at create() time when you define an index in $this->idx
175 * that refers to a CLOB column.
176 */
177define('DB_TABLE_ERR_IDX_COL_CLOB',     -21);
178
179/**
180 * Error code at create() time when you define a column name that is
181 * more than 30 chars long (an Oracle restriction).
182 */
183define('DB_TABLE_ERR_DECLARE_STRLEN',   -22);
184
185/**
186 * Error code at create() time when the index name ends up being more
187 * than 30 chars long (an Oracle restriction).
188 */
189define('DB_TABLE_ERR_IDX_STRLEN',       -23);
190
191/**
192 * Error code at create() time when the table name is more than 30 chars
193 * long (an Oracle restriction).
194 */
195define('DB_TABLE_ERR_TABLE_STRLEN',     -24);
196
197/**
198 * Error code at nextID() time when the sequence name is more than 30
199 * chars long (an Oracle restriction).
200 */
201define('DB_TABLE_ERR_SEQ_STRLEN',       -25);
202
203/**
204 * Error code at verify() time when the table does not exist in the
205 * database.
206 */
207define('DB_TABLE_ERR_VER_TABLE_MISSING', -26);
208
209/**
210 * Error code at verify() time when the column does not exist in the
211 * database table.
212 */
213define('DB_TABLE_ERR_VER_COLUMN_MISSING', -27);
214
215/**
216 * Error code at verify() time when the column type does not match the
217 * type specified in the column declaration.
218 */
219define('DB_TABLE_ERR_VER_COLUMN_TYPE',  -28);
220
221/**
222 * Error code at instantiation time when the column definition array
223 * does not contain at least one column.
224 */
225define('DB_TABLE_ERR_NO_COLS',          -29);
226
227/**
228 * Error code at verify() time when an index cannot be found in the
229 * database table.
230 */
231define('DB_TABLE_ERR_VER_IDX_MISSING',   -30);
232
233/**
234 * Error code at verify() time when an index does not contain all
235 * columns that it should contain.
236 */
237define('DB_TABLE_ERR_VER_IDX_COL_MISSING', -31);
238
239/**
240 * Error code at instantiation time when a creation mode
241 * is not available for a phptype.
242 */
243define('DB_TABLE_ERR_CREATE_PHPTYPE', -32);
244
245/**
246 * Error code at create() time when you define more than one primary key
247 * in $this->idx.
248 */
249define('DB_TABLE_ERR_DECLARE_PRIMARY', -33);
250
251/**
252 * Error code at create() time when a primary key is defined in $this->idx
253 * and SQLite is used (SQLite does not support primary keys).
254 */
255define('DB_TABLE_ERR_DECLARE_PRIM_SQLITE', -34);
256
257/**
258 * Error code at alter() time when altering a table field is not possible
259 * (e.g. because MDB2 has no support for the change or because the DBMS
260 * does not support the change).
261 */
262define('DB_TABLE_ERR_ALTER_TABLE_IMPOS', -35);
263
264/**
265 * Error code at alter() time when altering a(n) index/constraint is not possible
266 * (e.g. because MDB2 has no support for the change or because the DBMS
267 * does not support the change).
268 */
269define('DB_TABLE_ERR_ALTER_INDEX_IMPOS', -36);
270
271/**
272 * Error code at insert() time due to invalid the auto-increment column
273 * definition. This column must be an integer type and required.
274 */
275define('DB_TABLE_ERR_AUTO_INC_COL', -37);
276
277/**
278 * Error code at instantiation time when both the $table parameter
279 * and the $table class property are missing.
280 */
281define('DB_TABLE_ERR_TABLE_NAME_MISSING', -38);
282
283/**
284 * The DB_Table_Base parent class
285 */
286require_once 'DB/Table/Base.php';
287
288/**
289 * The PEAR class for errors
290 */
291require_once 'PEAR.php';
292
293/**
294 * The Date class for recasting date and time values
295 */
296require_once 'DB/Table/Date.php';
297
298
299/**
300 * DB_Table supports these RDBMS engines and their various native data
301 * types; we need these here instead of in Manager.php because the
302 * initial array key tells us what databases are supported.
303 */
304$GLOBALS['_DB_TABLE']['type'] = array(
305    'fbsql' => array(
306        'boolean'   => 'DECIMAL(1,0)',
307        'char'      => 'CHAR',
308        'varchar'   => 'VARCHAR',
309        'smallint'  => 'SMALLINT',
310        'integer'   => 'INTEGER',
311        'bigint'    => 'LONGINT',
312        'decimal'   => 'DECIMAL',
313        'single'    => 'REAL',
314        'double'    => 'DOUBLE PRECISION',
315        'clob'      => 'CLOB',
316        'date'      => 'CHAR(10)',
317        'time'      => 'CHAR(8)',
318        'timestamp' => 'CHAR(19)'
319    ),
320    'ibase' => array(
321        'boolean'   => 'DECIMAL(1,0)',
322        'char'      => 'CHAR',
323        'varchar'   => 'VARCHAR',
324        'smallint'  => 'SMALLINT',
325        'integer'   => 'INTEGER',
326        'bigint'    => 'BIGINT',
327        'decimal'   => 'DECIMAL',
328        'single'    => 'FLOAT',
329        'double'    => 'DOUBLE PRECISION',
330        'clob'      => 'BLOB SUB_TYPE 1',
331        'date'      => 'DATE',
332        'time'      => 'TIME',
333        'timestamp' => 'TIMESTAMP'
334    ),
335    'mssql' => array(
336        'boolean'   => 'DECIMAL(1,0)',
337        'char'      => 'CHAR',
338        'varchar'   => 'VARCHAR',
339        'smallint'  => 'SMALLINT',
340        'integer'   => 'INTEGER',
341        'bigint'    => 'BIGINT',
342        'decimal'   => 'DECIMAL',
343        'single'    => 'REAL',
344        'double'    => 'FLOAT',
345        'clob'      => 'TEXT',
346        'date'      => 'CHAR(10)',
347        'time'      => 'CHAR(8)',
348        'timestamp' => 'CHAR(19)'
349    ),
350    'mysql' => array(
351        'boolean'   => 'DECIMAL(1,0)',
352        'char'      => 'CHAR',
353        'varchar'   => 'VARCHAR',
354        'smallint'  => 'SMALLINT',
355        'integer'   => 'INTEGER',
356        'bigint'    => 'BIGINT',
357        'decimal'   => 'DECIMAL',
358        'single'    => 'FLOAT',
359        'double'    => 'DOUBLE',
360        'clob'      => 'LONGTEXT',
361        'date'      => 'CHAR(10)',
362        'time'      => 'CHAR(8)',
363        'timestamp' => 'CHAR(19)'
364    ),
365    'mysqli' => array(
366        'boolean'   => 'DECIMAL(1,0)',
367        'char'      => 'CHAR',
368        'varchar'   => 'VARCHAR',
369        'smallint'  => 'SMALLINT',
370        'integer'   => 'INTEGER',
371        'bigint'    => 'BIGINT',
372        'decimal'   => 'DECIMAL',
373        'single'    => 'FLOAT',
374        'double'    => 'DOUBLE',
375        'clob'      => 'LONGTEXT',
376        'date'      => 'CHAR(10)',
377        'time'      => 'CHAR(8)',
378        'timestamp' => 'CHAR(19)'
379    ),
380    'oci8' => array(
381        'boolean'   => 'NUMBER(1)',
382        'char'      => 'CHAR',
383        'varchar'   => 'VARCHAR2',
384        'smallint'  => 'NUMBER(6)',
385        'integer'   => 'NUMBER(11)',
386        'bigint'    => 'NUMBER(19)',
387        'decimal'   => 'NUMBER',
388        'single'    => 'REAL',
389        'double'    => 'DOUBLE PRECISION',
390        'clob'      => 'CLOB',
391        'date'      => 'CHAR(10)',
392        'time'      => 'CHAR(8)',
393        'timestamp' => 'CHAR(19)'
394    ),
395    'pgsql' => array(
396        'boolean'   => 'DECIMAL(1,0)',
397        'char'      => 'CHAR',
398        'varchar'   => 'VARCHAR',
399        'smallint'  => 'SMALLINT',
400        'integer'   => 'INTEGER',
401        'bigint'    => 'BIGINT',
402        'decimal'   => 'DECIMAL',
403        'single'    => 'REAL',
404        'double'    => 'DOUBLE PRECISION',
405        'clob'      => 'TEXT',
406        'date'      => 'CHAR(10)',
407        'time'      => 'CHAR(8)',
408        'timestamp' => 'CHAR(19)'
409    ),
410    'sqlite' => array(
411        'boolean'   => 'BOOLEAN',
412        'char'      => 'CHAR',
413        'varchar'   => 'VARCHAR',
414        'smallint'  => 'SMALLINT',
415        'integer'   => 'INTEGER',
416        'bigint'    => 'BIGINT',
417        'decimal'   => 'NUMERIC',
418        'single'    => 'FLOAT',
419        'double'    => 'DOUBLE',
420        'clob'      => 'CLOB',
421        'date'      => 'DATE',
422        'time'      => 'TIME',
423        'timestamp' => 'TIMESTAMP'
424    )
425);
426
427
428/**
429 * US-English default error messages. If you want to internationalize, you can
430 * set the translated messages via $GLOBALS['_DB_TABLE']['error']. You can also
431 * use DB_Table::setErrorMessage(). Examples:
432 *
433 * <code>
434 * (1) $GLOBALS['_DB_TABLE]['error'] = array(DB_TABLE_ERR_PHPTYPE   => '...',
435 *                                           DB_TABLE_ERR_SQL_UNDEF => '...');
436 * (2) DB_Table::setErrorMessage(DB_TABLE_ERR_PHPTYPE,   '...');
437 *     DB_Table::setErrorMessage(DB_TABLE_ERR_SQL_UNDEF, '...');
438 * (3) DB_Table::setErrorMessage(array(DB_TABLE_ERR_PHPTYPE   => '...');
439 *                                     DB_TABLE_ERR_SQL_UNDEF => '...');
440 * (4) $obj =& new DB_Table();
441 *     $obj->setErrorMessage(DB_TABLE_ERR_PHPTYPE,   '...');
442 *     $obj->setErrorMessage(DB_TABLE_ERR_SQL_UNDEF, '...');
443 * (5) $obj =& new DB_Table();
444 *     $obj->setErrorMessage(array(DB_TABLE_ERR_PHPTYPE   => '...');
445 *                                 DB_TABLE_ERR_SQL_UNDEF => '...');
446 * </code>
447 *
448 * For errors that can occur with-in the constructor call (i.e. e.g. creating
449 * or altering the database table), only the code from examples (1) to (3)
450 * will alter the default error messages early enough. For errors that can
451 * occur later, examples (4) and (5) are also valid.
452 */
453$GLOBALS['_DB_TABLE']['default_error'] = array(
454    DB_TABLE_ERR_NOT_DB_OBJECT       => 'First parameter must be a DB/MDB2 object',
455    DB_TABLE_ERR_PHPTYPE             => 'DB/MDB2 phptype (or dbsyntax) not supported',
456    DB_TABLE_ERR_SQL_UNDEF           => 'Select query string not in a key of $sql. Key',
457    DB_TABLE_ERR_SQL_NOT_STRING      => 'Select query is neither an array nor a string',
458    DB_TABLE_ERR_INS_COL_NOMAP       => 'Insert column not in map',
459    DB_TABLE_ERR_INS_COL_REQUIRED    => 'Insert data must be set and non-null for column',
460    DB_TABLE_ERR_INS_DATA_INVALID    => 'Insert data not valid for column',
461    DB_TABLE_ERR_UPD_COL_NOMAP       => 'Update column not in map',
462    DB_TABLE_ERR_UPD_COL_REQUIRED    => 'Update column must be set and non-null',
463    DB_TABLE_ERR_UPD_DATA_INVALID    => 'Update data not valid for column',
464    DB_TABLE_ERR_CREATE_FLAG         => 'Create flag not valid',
465    DB_TABLE_ERR_IDX_NO_COLS         => 'No columns for index',
466    DB_TABLE_ERR_IDX_COL_UNDEF       => 'Column not in map for index',
467    DB_TABLE_ERR_IDX_TYPE            => 'Type not valid for index',
468    DB_TABLE_ERR_DECLARE_STRING      => 'String column declaration not valid',
469    DB_TABLE_ERR_DECLARE_DECIMAL     => 'Decimal column declaration not valid',
470    DB_TABLE_ERR_DECLARE_TYPE        => 'Column type not valid',
471    DB_TABLE_ERR_VALIDATE_TYPE       => 'Cannot validate for unknown type on column',
472    DB_TABLE_ERR_DECLARE_COLNAME     => 'Column name not valid',
473    DB_TABLE_ERR_DECLARE_IDXNAME     => 'Index name not valid',
474    DB_TABLE_ERR_DECLARE_TYPE        => 'Column type not valid',
475    DB_TABLE_ERR_IDX_COL_CLOB        => 'CLOB column not allowed for index',
476    DB_TABLE_ERR_DECLARE_STRLEN      => 'Column name too long, 30 char max',
477    DB_TABLE_ERR_IDX_STRLEN          => 'Index name too long, 30 char max',
478    DB_TABLE_ERR_TABLE_STRLEN        => 'Table name too long, 30 char max',
479    DB_TABLE_ERR_SEQ_STRLEN          => 'Sequence name too long, 30 char max',
480    DB_TABLE_ERR_VER_TABLE_MISSING   => 'Verification failed: table does not exist',
481    DB_TABLE_ERR_VER_COLUMN_MISSING  => 'Verification failed: column does not exist',
482    DB_TABLE_ERR_VER_COLUMN_TYPE     => 'Verification failed: wrong column type',
483    DB_TABLE_ERR_NO_COLS             => 'Column definition array may not be empty',
484    DB_TABLE_ERR_VER_IDX_MISSING     => 'Verification failed: index does not exist',
485    DB_TABLE_ERR_VER_IDX_COL_MISSING => 'Verification failed: index does not contain all specified cols',
486    DB_TABLE_ERR_CREATE_PHPTYPE      => 'Creation mode is not supported for this phptype',
487    DB_TABLE_ERR_DECLARE_PRIMARY     => 'Only one primary key is allowed',
488    DB_TABLE_ERR_DECLARE_PRIM_SQLITE => 'SQLite does not support primary keys',
489    DB_TABLE_ERR_ALTER_TABLE_IMPOS   => 'Alter table failed: changing the field type not possible',
490    DB_TABLE_ERR_ALTER_INDEX_IMPOS   => 'Alter table failed: changing the index/constraint not possible',
491    DB_TABLE_ERR_AUTO_INC_COL        => 'Illegal auto-increment column definition',
492    DB_TABLE_ERR_TABLE_NAME_MISSING  => 'Table name missing in constructor and class'
493);
494
495// merge default and user-defined error messages
496if (!isset($GLOBALS['_DB_TABLE']['error'])) {
497    $GLOBALS['_DB_TABLE']['error'] = array();
498}
499foreach ($GLOBALS['_DB_TABLE']['default_error'] as $code => $message) {
500    if (!array_key_exists($code, $GLOBALS['_DB_TABLE']['error'])) {
501        $GLOBALS['_DB_TABLE']['error'][$code] = $message;
502    }
503}
504
505// set default value for length check switch
506if (!isset($GLOBALS['_DB_TABLE']['disable_length_check'])) {
507    $GLOBALS['_DB_TABLE']['disable_length_check'] = false;
508}
509
510/**
511 * DB_Table is a database API and data type SQL abstraction class.
512 *
513 * DB_Table provides database API abstraction, data type abstraction,
514 * automated SELECT, INSERT, and UPDATE queries, automated table
515 * creation, automated validation of inserted/updated column values,
516 * and automated creation of QuickForm elemnts based on the column
517 * definitions.
518 *
519 * @category Database
520 * @package  DB_Table
521 * @author   Paul M. Jones <pmjones@php.net>
522 * @author   David C. Morse <morse@php.net>
523 * @author   Mark Wiesemann <wiesemann@php.net>
524 * @version  Release: 1.5.6
525 * @link     http://pear.php.net/package/DB_Table
526 */
527
528class DB_Table extends DB_Table_Base
529{
530
531    /**
532     * The table or view in the database to which this object binds.
533     *
534     * @access public
535     * @var string
536     */
537    var $table = null;
538
539    /**
540     * DB_Table_Database instance that this table belongs to.
541     *
542     * @access private
543     * @var object
544     */
545    var $_database = null;
546
547
548    /**
549     * Associative array of column definitions.
550     *
551     * @access public
552     * @var array
553     */
554    var $col = array();
555
556
557    /**
558     * Associative array of index definitions.
559     *
560     * @access public
561     * @var array
562     */
563    var $idx = array();
564
565    /**
566     * Name of an auto-increment column, if any. Null otherwise.
567     *
568     * A table can contain at most one auto-increment column.
569     * Auto-incrementing is implemented in the insert() method,
570     * using a sequence accessed by the nextID() method.
571     *
572     * @access public
573     * @var string
574     */
575    var $auto_inc_col = null;
576
577
578    /**
579     * Boolean flag to turn on (true) or off (false) auto-incrementing.
580     *
581     * Auto-increment column $auto_inc_col upon insertion only if $_auto_inc is
582     * true and the value of that column is null in the data to be inserted.
583     *
584     * @var bool
585     * @access private
586     */
587    var $_auto_inc = true;
588
589
590    /**
591     * Whether or not to automatically validate data at insert-time.
592     *
593     * @var bool
594     * @access private
595     */
596    var $_valid_insert = true;
597
598    /**
599     * Whether or not to automatically validate data at update-time.
600     *
601     * @var bool
602     * @access private
603     */
604    var $_valid_update = true;
605
606
607    /**
608     * Whether or not to automatically recast data at insert- and update-time.
609     *
610     * @var    bool
611     * @access private
612     */
613    var $_auto_recast = true;
614
615
616    /**
617     * Constructor.
618     *
619     * The constructor returns a DB_Table object that wraps an
620     * instance $db DB or MDB2, and that binds to a specific database
621     * table named $table. It can optionally create the database table
622     * or verify that its schema matches that declared in the $col and
623     * $idx parameters, depending on the value of the $create parameter.
624     *
625     * If there is an error on instantiation, $this->error will be
626     * populated with the PEAR_Error.
627     *
628     * @param object &$db A PEAR DB/MDB2 object.
629     *
630     * @param string $table The table name to connect to in the database.
631     *
632     * @param mixed $create The automatic table creation mode to pursue:
633     * - boolean false to not attempt creation
634     * - 'safe' to create the table only if it does not exist
635     * - 'drop' to drop any existing table with the same name and re-create it
636     * - 'verify' to check whether the table exists, whether all the columns
637     *   exist, whether the columns have the right type, and whether the indexes
638     *   exist and have the right type
639     * - 'alter' does the same as 'safe' if the table does not exist; if it
640     *   exists, a verification for columns existence, the column types, the
641     *   indexes existence, and the indexes types will be performed and the
642     *   table schema will be modified if needed
643     *
644     * @return object DB_Table
645     * @access public
646     */
647    function DB_Table(&$db, $table = null, $create = false)
648    {
649        // Identify the class for error handling by parent class
650        $this->_primary_subclass = 'DB_TABLE';
651
652        // is the first argument a DB/MDB2 object?
653        $this->backend = null;
654        if (is_subclass_of($db, 'db_common')) {
655            $this->backend = 'db';
656        } elseif (is_subclass_of($db, 'mdb2_driver_common')) {
657            $this->backend = 'mdb2';
658        }
659
660        if (is_null($this->backend)) {
661            $this->error =& DB_Table::throwError(DB_TABLE_ERR_NOT_DB_OBJECT);
662            return;
663        }
664
665        // set the class properties
666        $this->db =& $db;
667        if (is_null($table)) {
668            // $table parameter not given => check $table class property
669            if (is_null($this->table)) {
670                $this->error =& DB_Table::throwError(DB_TABLE_ERR_TABLE_NAME_MISSING);
671                return;
672            }
673        } else {
674            $this->table = $table;
675        }
676
677        // is the RDBMS supported?
678        $phptype = $db->phptype;
679        $dbsyntax = $db->dbsyntax;
680        if (! DB_Table::supported($phptype, $dbsyntax)) {
681            $this->error =& DB_Table::throwError(
682                DB_TABLE_ERR_PHPTYPE,
683                "({$db->phptype})"
684            );
685            return;
686        }
687
688        // load MDB2_Extended module
689        if ($this->backend == 'mdb2') {
690            $this->db->loadModule('Extended', null, false);
691        }
692
693        // should we attempt table creation?
694        if ($create) {
695
696            if ($this->backend == 'mdb2') {
697                $this->db->loadModule('Manager');
698            }
699
700            // check whether the chosen mode is supported
701            $mode_supported = DB_Table::modeSupported($create, $phptype);
702            if (PEAR::isError($mode_supported)) {
703                $this->error =& $mode_supported;
704                return;
705            }
706            if (!$mode_supported) {
707                $this->error =& $this->throwError(
708                    DB_TABLE_ERR_CREATE_PHPTYPE,
709                    "('$create', '$phptype')"
710                );
711                return;
712            }
713
714            include_once 'DB/Table/Manager.php';
715
716            switch ($create) {
717
718                case 'alter':
719                    $result = $this->alter();
720                    break;
721
722                case 'drop':
723                case 'safe':
724                    $result = $this->create($create);
725                    break;
726
727                case 'verify':
728                    $result = $this->verify();
729                    break;
730            }
731
732            if (PEAR::isError($result)) {
733                // problem creating/altering/verifing the table
734                $this->error =& $result;
735                return;
736            }
737        }
738    }
739
740
741    /**
742     * Is a particular RDBMS supported by DB_Table?
743     *
744     * @static
745     * @param string $phptype The RDBMS type for PHP.
746     * @param string $dbsyntax The chosen database syntax.
747     * @return bool  True if supported, false if not.
748     * @access public
749     */
750
751    function supported($phptype, $dbsyntax = '')
752    {
753        // only Firebird is supported, not its ancestor Interbase
754        if ($phptype == 'ibase' && $dbsyntax != 'firebird') {
755            return false;
756        }
757        $supported = array_keys($GLOBALS['_DB_TABLE']['type']);
758        return in_array(strtolower($phptype), $supported);
759    }
760
761
762    /**
763     * Is a creation mode supported for a RDBMS by DB_Table?
764     *
765     * @param string $mode The chosen creation mode.
766     * @param string $phptype The RDBMS type for PHP.
767     * @return bool  True if supported, false if not (PEAR_Error on failure)
768     *
769     * @throws PEAR_Error if
770     *     Unknown creation mode is specified (DB_TABLE_ERR_CREATE_FLAG)
771     *
772     * @access public
773     */
774    function modeSupported($mode, $phptype)
775    {
776        // check phptype for validity
777        $supported = array_keys($GLOBALS['_DB_TABLE']['type']);
778        if (!in_array(strtolower($phptype), $supported)) {
779            return false;
780        }
781
782        switch ($mode) {
783            case 'drop':
784            case 'safe':
785                // supported for all RDBMS
786                return true;
787
788            case 'alter':
789            case 'verify':
790                // not supported for fbsql and mssql (yet)
791                switch ($phptype) {
792                    case 'fbsql':
793                    case 'mssql':
794                        return false;
795                    default:
796                        return true;
797                }
798
799            default:
800                // unknown creation mode
801                return $this->throwError(
802                    DB_TABLE_ERR_CREATE_FLAG,
803                    "('$mode')"
804                );
805        }
806    }
807
808
809    /**
810     * Overwrite one or more error messages, e.g. to internationalize them.
811     *
812     * @param mixed $code If string, the error message with code $code will
813     * be overwritten by $message. If array, the error messages with code
814     * of each array key will be overwritten by the key's value.
815     *
816     * @param string $message Only used if $key is not an array.
817     * @return void
818     * @access public
819     */
820    function setErrorMessage($code, $message = null) {
821        if (is_array($code)) {
822            foreach ($code as $single_code => $single_message) {
823                $GLOBALS['_DB_TABLE']['error'][$single_code] = $single_message;
824            }
825        } else {
826            $GLOBALS['_DB_TABLE']['error'][$code] = $message;
827        }
828    }
829
830
831    /**
832     *
833     * Returns all or part of the $this->col property array.
834     *
835     * @param mixed $col If null, returns the $this->col property array
836     * as it is.  If string, returns that column name from the $this->col
837     * array. If an array, returns those columns named as the array
838     * values from the $this->col array as an array.
839     *
840     * @return mixed All or part of the $this->col property array, or
841     *               boolean false if no matching column names are found.
842     * @access public
843     */
844    function getColumns($col = null)
845    {
846        // by default, return all column definitions
847        if (is_null($col)) {
848            return $this->col;
849        }
850
851        // if the param is a string, only return the column definition
852        // named by the that string
853        if (is_string($col)) {
854            if (isset($this->col[$col])) {
855                return $this->col[$col];
856            } else {
857                return false;
858            }
859        }
860
861        // if the param is a sequential array of column names,
862        // return only those columns named in that array
863        if (is_array($col)) {
864            $set = array();
865            foreach ($col as $name) {
866                $set[$name] = $this->getColumns($name);
867            }
868
869            if (count($set) == 0) {
870                return false;
871            } else {
872                return $set;
873            }
874        }
875
876        // param was not null, string, or array
877        return false;
878    }
879
880
881    /**
882     * Returns all or part of the $this->idx property array.
883     *
884     * @param mixed $idx Index name (key in $this->idx), or array of
885     *                   index name strings.
886     *
887     * @return mixed All or part of the $this->idx property array,
888     *               or boolean false if $idx is not null but invalid
889     *
890     * @access public
891     */
892    function getIndexes($idx = null)
893    {
894        // by default, return all index definitions
895        if (is_null($idx)) {
896            return $this->idx;
897        }
898
899        // if the param is a string, only return the index definition
900        // named by the that string
901        if (is_string($idx)) {
902            if (isset($this->idx[$idx])) {
903                return $this->idx[$idx];
904            } else {
905                return false;
906            }
907        }
908
909        // if the param is a sequential array of index names,
910        // return only those indexes named in that array
911        if (is_array($idx)) {
912            $set = array();
913            foreach ($idx as $name) {
914                $set[$name] = $this->getIndexes($name);
915            }
916
917            if (count($set) == 0) {
918                return false;
919            } else {
920                return $set;
921            }
922        }
923
924        // param was not null, string, or array
925        return false;
926    }
927
928
929    /**
930     * Connect or disconnect a DB_Table_Database instance to this table
931     * instance.
932     *
933     * Used to re-connect this DB_Table object to a parent DB_Table_Database
934     * object during unserialization. Can also disconnect if the $database
935     * parameter is null. Use the DB_Table_Database::addTable method instead
936     * to add a table to a new DB_Table_Database.
937     *
938     * @param object &$database DB_Table_Database instance that this table
939     *               belongs to (or null to disconnect from instance).
940     *
941     * @return void
942     * @access public
943     */
944    function setDatabaseInstance(&$database)
945    {
946        if (is_a($database, 'DB_Table_Database')) {
947            $this->_database =& $database;
948        } elseif (is_null($database)) {
949            $this->_database = null;
950        }
951    }
952
953
954    /**
955     * Inserts a single table row.
956     *
957     * Inserts data from associative array $data, in which keys are column
958     * names and values are column values. All required columns (except an
959     * auto-increment column) must be included in the data array. Columns
960     * values that are not set or null are inserted as SQL NULL values.
961     *
962     * If an auto-increment column is declared (by setting $this->auto_inc_col),
963     * and the value of that column in $data is not set or null, then a new
964     * sequence value will be generated and inserted.
965     *
966     * If auto-recasting is enabled (if $this->_auto_recast), the method will
967     * try, if necessary to recast $data to proper column types, with recast().
968     *
969     * If auto-validation is enabled (if $this->_valid_insert), the method
970     * will validates column types with validInsert() before insertion.
971     *
972     * @access public
973     *
974     * @param array $data An associative array of key-value pairs where
975     * the key is the column name and the value is the column value.
976     * This is the data that will be inserted into the table.
977     *
978     * @return mixed Void on success (PEAR_Error on failure)
979     *
980     * @throws PEAR_Error if:
981     *     - Error in auto_inc_col declaration (DB_TABLE_ERR_AUTO_INC_COL)
982     *     - Error returned by DB/MDB2::autoExecute() (Error bubbled up)
983     *
984     * @see validInsert()
985     * @see DB::autoExecute()
986     * @see MDB2::autoExecute()
987     */
988    function insert($data)
989    {
990        // Auto-increment if enabled and input value is null or not set
991        if ($this->_auto_inc
992            && !is_null($this->auto_inc_col)
993            && !isset($data[$this->auto_inc_col])
994           ) {
995            $column = $this->auto_inc_col;
996            // check that the auto-increment column exists
997            if (!array_key_exists($column, $this->col)) {
998                return $this->throwError(
999                        DB_TABLE_ERR_AUTO_INC_COL,
1000                        ": $column does not exist");
1001            }
1002            // check that the column is integer
1003            if (!in_array($this->col[$column]['type'],
1004                           array('integer','smallint','bigint'))) {
1005                return $this->throwError(
1006                        DB_TABLE_ERR_AUTO_INC_COL,
1007                        ": $column is not an integer");
1008            }
1009            // check that the column is required
1010            // Note: The insert method will replace a null input value
1011            // of $data[$column] with a sequence value. This makes
1012            // the column effectively 'not null'. This column must be
1013            // 'required' for consistency, to make this explicit.
1014            if (!$this->isRequired($column)) {
1015                return $this->throwError(
1016                        DB_TABLE_ERR_AUTO_INC_COL,
1017                        ": $column is not required");
1018            }
1019            // set the value
1020            $id = $this->nextID();
1021            if (PEAR::isError($id)) {
1022                return $id;
1023            }
1024            $data[$column] = $id;
1025        }
1026
1027        // forcibly recast the data elements to their proper types?
1028        if ($this->_auto_recast) {
1029            $this->recast($data);
1030        }
1031
1032        // validate the data if auto-validation is turned on
1033        if ($this->_valid_insert) {
1034            $result = $this->validInsert($data);
1035            if (PEAR::isError($result)) {
1036                return $result;
1037            }
1038        }
1039
1040        // Does a parent DB_Table_Database object exist?
1041        if ($this->_database) {
1042
1043            $_database = $this->_database;
1044
1045            // Validate foreign key values (if enabled)
1046            if ($_database->_check_fkey) {
1047               $result = $_database->validForeignKeys($this->table, $data);
1048               if (PEAR::isError($result)) {
1049                   return $result;
1050               }
1051            }
1052
1053        }
1054
1055        // Do insertion
1056        if ($this->backend == 'mdb2') {
1057            $result = $this->db->extended->autoExecute($this->table, $data,
1058                MDB2_AUTOQUERY_INSERT);
1059        } else {
1060            $result = $this->db->autoExecute($this->table, $data,
1061                DB_AUTOQUERY_INSERT);
1062        }
1063        return $result;
1064    }
1065
1066
1067    /**
1068     * Turns on or off auto-incrementing of $auto_inc_col column (if any)
1069     *
1070     * For auto-incrementing to work, an $auto_inc_col column must be declared,
1071     * auto-incrementing must be enabled (by this method), and the value of
1072     * the $auto_inc_col column must be not set or null in the $data passed to
1073     * the insert method.
1074     *
1075     * @param  bool $flag True to turn on auto-increment, false to turn off.
1076     * @return void
1077     * @access public
1078     */
1079    function setAutoInc($flag = true)
1080    {
1081        if ($flag) {
1082            $this->_auto_inc = true;
1083        } else {
1084            $this->_auto_inc = false;
1085        }
1086    }
1087
1088
1089    /**
1090     * Turns on (or off) automatic validation of inserted data.
1091     *
1092     * Enables (if $flag is true) or disables (if $flag is false) automatic
1093     * validation of data types prior to actual insertion into the database
1094     * by the DB_Table::insert() method.
1095     *
1096     * @param  bool $flag True to turn on auto-validation, false to turn off.
1097     * @return void
1098     * @access public
1099     */
1100    function autoValidInsert($flag = true)
1101    {
1102        if ($flag) {
1103            $this->_valid_insert = true;
1104        } else {
1105            $this->_valid_insert = false;
1106        }
1107    }
1108
1109
1110    /**
1111     * Validates an array for insertion into the table.
1112     *
1113     * @param array $data An associative array of key-value pairs where
1114     * the key is the column name and the value is the column value.  This
1115     * is the data that will be inserted into the table.  Data is checked
1116     * against the column data type for validity.
1117     *
1118     * @return boolean true on success (PEAR_Error on failure)
1119     *
1120     * @throws PEAR_Error if:
1121     *     - Invalid column name key in $data (DB_TABLE_ERR_INS_COL_NOMAP)
1122     *     - Missing required column value    (DB_TABLE_ERR_INS_COL_NOMAP)
1123     *     - Column value doesn't match type  (DB_TABLE_ERR_INS_DATA_INVALID)
1124     *
1125     * @access public
1126     *
1127     * @see insert()
1128     */
1129    function validInsert(&$data)
1130    {
1131        // loop through the data, and disallow insertion of unmapped
1132        // columns
1133        foreach ($data as $col => $val) {
1134            if (! isset($this->col[$col])) {
1135                return $this->throwError(
1136                    DB_TABLE_ERR_INS_COL_NOMAP,
1137                    "('$col')"
1138                );
1139            }
1140        }
1141
1142        // loop through each column mapping, and check the data to be
1143        // inserted into it against the column data type. we loop through
1144        // column mappings instead of the insert data to make sure that
1145        // all necessary columns are being inserted.
1146        foreach ($this->col as $col => $val) {
1147
1148            // is the value allowed to be null?
1149            if (isset($val['require']) &&
1150                $val['require'] == true &&
1151                (! isset($data[$col]) || is_null($data[$col]))) {
1152                return $this->throwError(
1153                    DB_TABLE_ERR_INS_COL_REQUIRED,
1154                    "'$col'"
1155                );
1156            }
1157
1158            // does the value to be inserted match the column data type?
1159            if (isset($data[$col]) &&
1160                ! $this->isValid($data[$col], $col)) {
1161                return $this->throwError(
1162                    DB_TABLE_ERR_INS_DATA_INVALID,
1163                    "'$col' ('$data[$col]')"
1164                );
1165            }
1166        }
1167
1168        return true;
1169    }
1170
1171
1172    /**
1173     * Update table row or rows that match a custom WHERE clause
1174     *
1175     * Constructs and submits an SQL UPDATE command to update columns whose
1176     * names are keys in the $data array parameter, in all rows that match
1177     * the logical condition given by the $where string parameter.
1178     *
1179     * If auto-recasting is enabled (if $this->_auto_recast), update() will
1180     * try, if necessary, to recast $data to proper column types, with recast().
1181     *
1182     * If auto-validation is enabled (if $this->_valid_insert), update()
1183     * validates column types with validUpdate() before insertion.
1184     *
1185     * @param array $data An associative array of key-value pairs where the
1186     * key is the column name and the value is the column value. These are
1187     * the columns that will be updated with new values.
1188     *
1189     * @param string $where An SQL WHERE clause limiting which records are
1190     * are to be updated.
1191     *
1192     * @return mixed Void on success, a PEAR_Error object on failure.
1193     *
1194     * @throws PEAR_Error if:
1195     *     - Data fails type validation (bubbles error returned by validUpdate)
1196     *     - Error thrown by DB/MDB2::autoexecute()
1197     *
1198     * @access public
1199     *
1200     * @see validUpdate()
1201     * @see DB::autoExecute()
1202     * @see MDB2::autoExecute()
1203     */
1204    function update($data, $where)
1205    {
1206        // forcibly recast the data elements to their proper types?
1207        if ($this->_auto_recast) {
1208            $this->recast($data);
1209        }
1210
1211        // validate the data if auto-validation is turned on
1212        if ($this->_valid_update) {
1213            $result = $this->validUpdate($data);
1214            if (PEAR::isError($result)) {
1215                return $result;
1216            }
1217        }
1218
1219        // Does a parent DB_Table_Database object exist?
1220        if ($this->_database) {
1221
1222            $_database =& $this->_database;
1223
1224            // Validate foreign key values (if enabled)
1225            if ($_database->_check_fkey) {
1226               $result = $_database->validForeignKeys($this->table, $data);
1227               if (PEAR::isError($result)) {
1228                   return $result;
1229               }
1230            }
1231
1232            // Implement any relevant ON UPDATE actions
1233            $result = $_database->onUpdateAction($this, $data, $where);
1234            if (PEAR::isError($result)) {
1235                return $result;
1236            }
1237
1238        }
1239
1240        // Submit update command
1241        if ($this->backend == 'mdb2') {
1242            $result = $this->db->extended->autoExecute($this->table, $data,
1243                MDB2_AUTOQUERY_UPDATE, $where);
1244        } else {
1245            $result = $this->db->autoExecute($this->table, $data,
1246                DB_AUTOQUERY_UPDATE, $where);
1247        }
1248        return $result;
1249
1250    }
1251
1252
1253    /**
1254     * Turns on (or off) automatic validation of updated data.
1255     *
1256     * Enables (if $flag is true) or disables (if $flag is false) automatic
1257     * validation of data types prior to updating rows in the database by
1258     * the {@link update()} method.
1259     *
1260     * @param  bool $flag True to turn on auto-validation, false to turn off.
1261     * @return void
1262     * @access public
1263     */
1264    function autoValidUpdate($flag = true)
1265    {
1266        if ($flag) {
1267            $this->_valid_update = true;
1268        } else {
1269            $this->_valid_update = false;
1270        }
1271    }
1272
1273
1274    /**
1275     * Validates an array for updating the table.
1276     *
1277     * @param array $data An associative array of key-value pairs where
1278     * the key is the column name and the value is the column value.  This
1279     * is the data that will be inserted into the table.  Data is checked
1280     * against the column data type for validity.
1281     *
1282     * @return mixed Boolean true on success (PEAR_Error object on failure)
1283     *
1284     * @throws PEAR_Error if
1285     *     - Invalid column name key in $data (DB_TABLE_ERR_UPD_COL_NOMAP)
1286     *     - Missing required column value    (DB_TABLE_ERR_UPD_COL_NOMAP)
1287     *     - Column value doesn't match type  (DB_TABLE_ERR_UPD_DATA_INVALID)
1288     *
1289     * @access public
1290     *
1291     * @see update()
1292     */
1293    function validUpdate(&$data)
1294    {
1295        // loop through each data element, and check the
1296        // data to be updated against the column data type.
1297        foreach ($data as $col => $val) {
1298
1299            // does the column exist?
1300            if (! isset($this->col[$col])) {
1301                return $this->throwError(
1302                    DB_TABLE_ERR_UPD_COL_NOMAP,
1303                    "('$col')"
1304                );
1305            }
1306
1307            // the column definition
1308            $defn = $this->col[$col];
1309
1310            // is it allowed to be null?
1311            if (isset($defn['require']) &&
1312                $defn['require'] == true &&
1313                isset($data[$col]) &&
1314                is_null($data[$col])) {
1315                return $this->throwError(
1316                    DB_TABLE_ERR_UPD_COL_REQUIRED,
1317                    $col
1318                );
1319            }
1320
1321            // does the value to be inserted match the column data type?
1322            if (! $this->isValid($data[$col], $col)) {
1323                return $this->throwError(
1324                    DB_TABLE_ERR_UPD_DATA_INVALID,
1325                    "$col ('$data[$col]')"
1326                );
1327            }
1328        }
1329
1330        return true;
1331    }
1332
1333
1334    /**
1335     * Deletes table rows matching a custom WHERE clause.
1336     *
1337     * Constructs and submits and SQL DELETE command with the specified WHERE
1338     * clause. Command is submitted by DB::query() or MDB2::exec().
1339     *
1340     * If a reference to a DB_Table_Database instance exists, carry out any
1341     * ON DELETE actions declared in that instance before actual insertion,
1342     * if emulation of ON DELETE actions is enabled in that instance.
1343     *
1344     * @param string $where Logical condition in the WHERE clause of the
1345     *                      delete command.
1346     *
1347     * @return mixed void on success (PEAR_Error on failure)
1348     *
1349     * @throws PEAR_Error if
1350     *     DB::query() or MDB2::exec() returns error (bubbles up)
1351     *
1352     * @access public
1353     *
1354     * @see DB::query()
1355     * @see MDB2::exec()
1356     */
1357    function delete($where)
1358    {
1359        // Does a parent DB_Table_Database object exist?
1360        if ($this->_database) {
1361
1362            $_database =& $this->_database;
1363
1364            // Implement any relevant ON DELETE actions
1365            $result = $_database->onDeleteAction($this, $where);
1366            if (PEAR::isError($result)) {
1367                return $result;
1368            }
1369
1370        }
1371
1372        if ($this->backend == 'mdb2') {
1373            $result = $this->db->exec("DELETE FROM $this->table WHERE $where");
1374        } else {
1375            $result = $this->db->query("DELETE FROM $this->table WHERE $where");
1376        }
1377        return $result;
1378    }
1379
1380
1381    /**
1382     *
1383     * Generates and returns a sequence value.
1384     *
1385     * Generates a sequence value by calling the DB or MDB2::nextID() method. The
1386     * sequence name defaults to the table name, or may be specified explicitly.
1387     *
1388     * @param  string  $seq_name The sequence name; defaults to table_id.
1389     *
1390     * @return integer The next value in the sequence (PEAR_Error on failure)
1391     *
1392     * @throws PEAR_Error if
1393     *     Sequence name too long (>26 char + _seq) (DB_TABLE_ERR_SEQ_STRLEN)
1394     *
1395     * @access public
1396     *
1397     * @see DB::nextID()
1398     * @see MDB2::nextID()
1399     */
1400    function nextID($seq_name = null)
1401    {
1402        if (is_null($seq_name)) {
1403            $seq_name = "{$this->table}";
1404        } else {
1405            $seq_name = "{$this->table}_{$seq_name}";
1406        }
1407
1408        // the maximum length is 30, but PEAR DB/MDB2 will add "_seq" to the
1409        // name, so the max length here is less 4 chars. we have to
1410        // check here because the sequence will be created automatically
1411        // by PEAR DB/MDB2, which will not check for length on its own.
1412        if (   $GLOBALS['_DB_TABLE']['disable_length_check'] === false
1413            && strlen($seq_name) > 26
1414           ) {
1415            return DB_Table::throwError(
1416                DB_TABLE_ERR_SEQ_STRLEN,
1417                " ('$seq_name')"
1418            );
1419
1420        }
1421        return $this->db->nextId($seq_name);
1422    }
1423
1424
1425    /**
1426     * Escapes and enquotes a value for use in an SQL query.
1427     *
1428     * Simple wrapper for DB_Common::quoteSmart() or MDB2::quote(), which
1429     * returns the value of one of these functions. Helps makes user input
1430     * safe against SQL injection attack.
1431     *
1432     * @param mixed $val The value to be quoted
1433     *
1434     * @return string The value with quotes escaped, inside single quotes if
1435     *                non-numeric.
1436     *
1437     * @throws PEAR_Error if
1438     *     DB_Common::quoteSmart() or MDB2::quote() returns Error (bubbled up)
1439     *
1440     * @access public
1441     *
1442     * @see DB_Common::quoteSmart()
1443     * @see MDB2::quote()
1444     */
1445    function quote($val)
1446    {
1447        if ($this->backend == 'mdb2') {
1448            $val = $this->db->quote($val);
1449        } else {
1450            $val = $this->db->quoteSmart($val);
1451        }
1452        return $val;
1453    }
1454
1455
1456    /**
1457     * Returns a blank row array based on the column map.
1458     *
1459     * The array keys are the column names, and all values are set to null.
1460     *
1461     * @return array An associative array where keys are column names and
1462     *               all values are null.
1463     * @access public
1464     */
1465    function getBlankRow()
1466    {
1467        $row = array();
1468
1469        foreach ($this->col as $key => $val) {
1470            $row[$key] = null;
1471        }
1472
1473        $this->recast($row);
1474
1475        return $row;
1476    }
1477
1478
1479    /**
1480     * Turns on (or off) automatic recasting of insert and update data.
1481     *
1482     * Turns on (if $flag is true) or off (if $flag is false) automatic forcible
1483     * recasting of data to the declared data type, if required, prior to inserting
1484     * or updating.  The recasting is done by calling the DB_Table::recast()
1485     * method from within the DB_Table::insert() and DB_Table::update().
1486     *
1487     * @param bool $flag True to automatically recast insert and update data,
1488     *                   false to not do so.
1489     * @return void
1490     * @access public
1491     */
1492    function autoRecast($flag = true)
1493    {
1494        if ($flag) {
1495            $this->_auto_recast = true;
1496        } else {
1497            $this->_auto_recast = false;
1498        }
1499    }
1500
1501
1502    /**
1503     * Forces array elements to the proper types for their columns.
1504     *
1505     * This will not valiate the data, and will forcibly change the data
1506     * to match the recast-type.
1507     *
1508     * The date, time, and timestamp recasting has special logic for
1509     * arrays coming from an HTML_QuickForm object so that the arrays
1510     * are converted into properly-formatted strings.
1511     *
1512     * @todo If a column key holds an array of values (say from a multiple
1513     * select) then this method will not work properly; it will recast the
1514     * value to the string 'Array'.  Is this bad?
1515     *
1516     * @param array   &$data The data array to re-cast.
1517     *
1518     * @return void
1519     *
1520     * @access public
1521     */
1522    function recast(&$data)
1523    {
1524        $keys = array_keys($data);
1525
1526        $null_if_blank = array('date', 'time', 'timestamp', 'smallint',
1527            'integer', 'bigint', 'decimal', 'single', 'double');
1528
1529        foreach ($keys as $key) {
1530
1531            if (! isset($this->col[$key])) {
1532                continue;
1533            }
1534
1535            unset($val);
1536            $val =& $data[$key];
1537
1538            // convert blanks to null for non-character field types
1539            $convert = in_array($this->col[$key]['type'], $null_if_blank);
1540            if (is_array($val)) {  // if one of the given array values is
1541                                   // empty, null will be the new value if
1542                                   // the field is not required
1543                $tmp_val = implode('', $val);
1544                foreach ($val as $array_val) {
1545                    if (trim((string) $array_val) == '') {
1546                        $tmp_val = '';
1547                        break;
1548                    }
1549                }
1550            } else {
1551                $tmp_val = $val;
1552            }
1553            if ($convert && trim((string) $tmp_val) == '' && (
1554                !isset($this->col[$key]['require']) ||
1555                $this->col[$key]['require'] === false
1556              )
1557            ) {
1558                $val = null;
1559            }
1560
1561            // skip explicit NULL values
1562            if (is_null($val)) {
1563                continue;
1564            }
1565
1566            // otherwise, recast to the column type
1567            switch ($this->col[$key]['type']) {
1568
1569            case 'boolean':
1570                $val = ($val) ? 1 : 0;
1571                break;
1572
1573            case 'char':
1574            case 'varchar':
1575            case 'clob':
1576                settype($val, 'string');
1577                break;
1578
1579            case 'date':
1580
1581                // smart handling of non-standard (i.e. Y-m-d) date formats,
1582                // this allows to use two-digit years (y) and short (M) or
1583                // long (F) names of months without having to recast the
1584                // date value yourself
1585                if (is_array($val)) {
1586                    if (isset($val['y'])) {
1587                        $val['Y'] = $val['y'];
1588                    }
1589                    if (isset($val['F'])) {
1590                        $val['m'] = $val['F'];
1591                    }
1592                    if (isset($val['M'])) {
1593                        $val['m'] = $val['M'];
1594                    }
1595                }
1596
1597                if (is_array($val) &&
1598                    isset($val['Y']) &&
1599                    isset($val['m']) &&
1600                    isset($val['d'])) {
1601
1602                    // the date is in HTML_QuickForm format,
1603                    // convert into a string
1604                    $y = (strlen($val['Y']) < 4)
1605                        ? str_pad($val['Y'], 4, '0', STR_PAD_LEFT)
1606                        : $val['Y'];
1607
1608                    $m = (strlen($val['m']) < 2)
1609                        ? '0'.$val['m'] : $val['m'];
1610
1611                    $d = (strlen($val['d']) < 2)
1612                        ? '0'.$val['d'] : $val['d'];
1613
1614                    $val = "$y-$m-$d";
1615
1616                } else {
1617
1618                    // convert using the Date class
1619                    $tmp =& new DB_Table_Date($val);
1620                    $val = $tmp->format('%Y-%m-%d');
1621
1622                }
1623
1624                break;
1625
1626            case 'time':
1627
1628                if (is_array($val) &&
1629                    isset($val['H']) &&
1630                    isset($val['i']) &&
1631                    isset($val['s'])) {
1632
1633                    // the time is in HTML_QuickForm format,
1634                    // convert into a string
1635                    $h = (strlen($val['H']) < 2)
1636                        ? '0' . $val['H'] : $val['H'];
1637
1638                    $i = (strlen($val['i']) < 2)
1639                        ? '0' . $val['i'] : $val['i'];
1640
1641                    $s = (strlen($val['s']) < 2)
1642                        ? '0' . $val['s'] : $val['s'];
1643
1644
1645                    $val = "$h:$i:$s";
1646
1647                } else {
1648                    // date does not matter in this case, so
1649                    // pre 1970 and post 2040 are not an issue.
1650                    $tmp = strtotime(date('Y-m-d') . " $val");
1651                    $val = date('H:i:s', $tmp);
1652                }
1653
1654                break;
1655
1656            case 'timestamp':
1657
1658                // smart handling of non-standard (i.e. Y-m-d) date formats,
1659                // this allows to use two-digit years (y) and short (M) or
1660                // long (F) names of months without having to recast the
1661                // date value yourself
1662                if (is_array($val)) {
1663                    if (isset($val['y'])) {
1664                        $val['Y'] = $val['y'];
1665                    }
1666                    if (isset($val['F'])) {
1667                        $val['m'] = $val['F'];
1668                    }
1669                    if (isset($val['M'])) {
1670                        $val['m'] = $val['M'];
1671                    }
1672                }
1673
1674                if (is_array($val) &&
1675                    isset($val['Y']) &&
1676                    isset($val['m']) &&
1677                    isset($val['d']) &&
1678                    isset($val['H']) &&
1679                    isset($val['i']) &&
1680                    isset($val['s'])) {
1681
1682                    // timestamp is in HTML_QuickForm format,
1683                    // convert each element to a string. pad
1684                    // with zeroes as needed.
1685
1686                    $y = (strlen($val['Y']) < 4)
1687                        ? str_pad($val['Y'], 4, '0', STR_PAD_LEFT)
1688                        : $val['Y'];
1689
1690                    $m = (strlen($val['m']) < 2)
1691                        ? '0'.$val['m'] : $val['m'];
1692
1693                    $d = (strlen($val['d']) < 2)
1694                        ? '0'.$val['d'] : $val['d'];
1695
1696                    $h = (strlen($val['H']) < 2)
1697                        ? '0' . $val['H'] : $val['H'];
1698
1699                    $i = (strlen($val['i']) < 2)
1700                        ? '0' . $val['i'] : $val['i'];
1701
1702                    $s = (strlen($val['s']) < 2)
1703                        ? '0' . $val['s'] : $val['s'];
1704
1705                    $val = "$y-$m-$d $h:$i:$s";
1706
1707                } else {
1708                    // convert using the Date class
1709                    $tmp =& new DB_Table_Date($val);
1710                    $val = $tmp->format('%Y-%m-%d %H:%M:%S');
1711                }
1712
1713                break;
1714
1715            case 'smallint':
1716            case 'integer':
1717            case 'bigint':
1718                settype($val, 'integer');
1719                break;
1720
1721            case 'decimal':
1722            case 'single':
1723            case 'double':
1724                settype($val, 'float');
1725                break;
1726
1727            }
1728        }
1729    }
1730
1731
1732    /**
1733     * Creates the table based on $this->col and $this->idx.
1734     *
1735     * @param string $flag The automatic table creation mode to pursue:
1736     * - 'safe' to create the table only if it does not exist
1737     * - 'drop' to drop any existing table with the same name and re-create it
1738     *
1739     * @return mixed Boolean true if the table was successfully created,
1740     *               false if there was no need to create the table, or
1741     *               a PEAR_Error if the attempted creation failed.
1742     *
1743     * @throws PEAR_Error if
1744     *     - DB_Table_Manager::tableExists() returns Error (bubbles up)
1745     *     - DB_Table_Manager::create() returns Error (bubbles up)
1746     *
1747     * @access public
1748     *
1749     * @see DB_Table_Manager::tableExists()
1750     * @see DB_Table_Manager::create()
1751     */
1752    function create($flag)
1753    {
1754        include_once 'DB/Table/Manager.php';
1755
1756        // are we OK to create the table?
1757        $ok = false;
1758
1759        // check the create-flag
1760        switch ($flag) {
1761
1762            case 'drop':
1763                // drop only if table exists
1764                $table_exists = DB_Table_Manager::tableExists($this->db,
1765                                                              $this->table);
1766                if (PEAR::isError($table_exists)) {
1767                    return $table_exists;
1768                }
1769                if ($table_exists) {
1770                    // forcibly drop an existing table
1771                    if ($this->backend == 'mdb2') {
1772                        $this->db->manager->dropTable($this->table);
1773                    } else {
1774                        $this->db->query("DROP TABLE {$this->table}");
1775                    }
1776                }
1777                $ok = true;
1778                break;
1779
1780            case 'safe':
1781                // create only if table does not exist
1782                $table_exists = DB_Table_Manager::tableExists($this->db,
1783                                                              $this->table);
1784                if (PEAR::isError($table_exists)) {
1785                    return $table_exists;
1786                }
1787                // ok to create only if table does not exist
1788                $ok = !$table_exists;
1789                break;
1790
1791        }
1792
1793        // are we going to create the table?
1794        if (! $ok) {
1795            return false;
1796        }
1797
1798        return DB_Table_Manager::create(
1799            $this->db, $this->table, $this->col, $this->idx
1800        );
1801    }
1802
1803
1804    /**
1805     * Alters the table to match schema declared in $this->col and $this->idx.
1806     *
1807     * If the table does not exist, create it instead.
1808     *
1809     * @return boolean true if altering is successful (PEAR_Error on failure)
1810     *
1811     * @throws PEAR_Error if
1812     *     - DB_Table_Manager::tableExists() returns Error (bubbles up)
1813     *     - DB_Table_Manager::create() returns Error (bubbles up)
1814     *     - DB_Table_Manager::alter() returns Error (bubbles up)
1815     *
1816     * @access public
1817     *
1818     * @see DB_Table_Manager::tableExists()
1819     * @see DB_Table_Manager::create()
1820     * @see DB_Table_Manager::alter()
1821     */
1822    function alter()
1823    {
1824        $create = false;
1825
1826        // alter the table columns and indexes if the table exists
1827        $table_exists = DB_Table_Manager::tableExists($this->db,
1828                                                      $this->table);
1829        if (PEAR::isError($table_exists)) {
1830            return $table_exists;
1831        }
1832        if (!$table_exists) {
1833            // table does not exist => just create the table, there is
1834            // nothing that could be altered
1835            $create = true;
1836        }
1837
1838        if ($create) {
1839            return DB_Table_Manager::create(
1840                $this->db, $this->table, $this->col, $this->idx
1841            );
1842        }
1843
1844        return DB_Table_Manager::alter(
1845            $this->db, $this->table, $this->col, $this->idx
1846        );
1847    }
1848
1849
1850    /**
1851     * Verifies the table based on $this->col and $this->idx.
1852     *
1853     * @return boolean true if verification succees (PEAR_Error on failure).
1854     *
1855     * @throws PEAR_Error if
1856     *     DB_Table_Manager::verify() returns Error (bubbles up)
1857     *
1858     * @access public
1859     *
1860     * @see DB_Table_Manager::verify()
1861     */
1862    function verify()
1863    {
1864        return DB_Table_Manager::verify(
1865            $this->db, $this->table, $this->col, $this->idx
1866        );
1867    }
1868
1869
1870    /**
1871     * Checks if a value validates against the DB_Table data type for a
1872     * given column. This only checks that it matches the data type; it
1873     * does not do extended validation.
1874     *
1875     * @param array $val A value to check against the column's DB_Table
1876     * data type.
1877     *
1878     * @param array $col A column name from $this->col.
1879     *
1880     * @return boolean True if $val validates against data type, false if not
1881     *
1882     * @throws PEAR_Error if
1883     *     Invalid column type in $this->col (DB_TABLE_ERR_VALIDATE_TYPE)
1884     *
1885     * @access public
1886     *
1887     * @see DB_Table_Valid
1888     */
1889    function isValid($val, $col)
1890    {
1891        // is the value null?
1892        if (is_null($val)) {
1893            // is the column required?
1894            if ($this->isRequired($col)) {
1895                // yes, so not valid
1896                return false;
1897            } else {
1898                // not required, so it's valid
1899                return true;
1900            }
1901        }
1902
1903        // make sure we have the validation class
1904        include_once 'DB/Table/Valid.php';
1905
1906        // validate values per the column type.  we use sqlite
1907        // as the single authentic list of allowed column types,
1908        // regardless of the actual rdbms being used.
1909        $map = array_keys($GLOBALS['_DB_TABLE']['type']['sqlite']);
1910
1911        // is the column type on the map?
1912        if (! in_array($this->col[$col]['type'], $map)) {
1913            return $this->throwError(
1914                DB_TABLE_ERR_VALIDATE_TYPE,
1915                "'$col' ('{$this->col[$col]['type']}')"
1916            );
1917        }
1918
1919        // validate for the type
1920        switch ($this->col[$col]['type']) {
1921
1922        case 'char':
1923        case 'varchar':
1924            $result = DB_Table_Valid::isChar(
1925                $val,
1926                $this->col[$col]['size']
1927            );
1928            break;
1929
1930        case 'decimal':
1931            $result = DB_Table_Valid::isDecimal(
1932                $val,
1933                $this->col[$col]['size'],
1934                $this->col[$col]['scope']
1935            );
1936            break;
1937
1938        default:
1939            $result = call_user_func(
1940                array(
1941                    'DB_Table_Valid',
1942                    'is' . ucwords($this->col[$col]['type'])
1943                ),
1944                $val
1945            );
1946            break;
1947
1948        }
1949
1950        // have we passed the check so far, and should we
1951        // also check for allowed values?
1952        if ($result && isset($this->col[$col]['qf_vals'])) {
1953            $keys = array_keys($this->col[$col]['qf_vals']);
1954
1955            $result = in_array(
1956                $val,
1957                array_keys($this->col[$col]['qf_vals'])
1958            );
1959        }
1960
1961        return $result;
1962    }
1963
1964
1965    /**
1966     * Is a specific column required to be set and non-null?
1967     *
1968     * @param mixed $column The column to check against.
1969     * @return boolean      True if required, false if not.
1970     * @access public
1971     */
1972    function isRequired($column)
1973    {
1974        if (isset($this->col[$column]['require']) &&
1975            $this->col[$column]['require'] == true) {
1976            return true;
1977        } else {
1978            return false;
1979        }
1980    }
1981
1982
1983    /**
1984     *
1985     * Creates and returns a QuickForm object based on table columns.
1986     *
1987     * @param array $columns A sequential array of column names to use in
1988     * the form; if null, uses all columns.
1989     *
1990     * @param string $array_name By default, the form will use the names
1991     * of the columns as the names of the form elements.  If you pass
1992     * $array_name, the column names will become keys in an array named
1993     * for this parameter.
1994     *
1995     * @param array $args An associative array of optional arguments to
1996     * pass to the QuickForm object.  The keys are...
1997     *
1998     * 'formName' : String, name of the form; defaults to the name of this
1999     * table.
2000     *
2001     * 'method' : String, form method; defaults to 'post'.
2002     *
2003     * 'action' : String, form action; defaults to
2004     * $_SERVER['REQUEST_URI'].
2005     *
2006     * 'target' : String, form target target; defaults to '_self'
2007     *
2008     * 'attributes' : Associative array, extra attributes for <form>
2009     * tag; the key is the attribute name and the value is attribute
2010     * value.
2011     *
2012     * 'trackSubmit' : Boolean, whether to track if the form was
2013     * submitted by adding a special hidden field
2014     *
2015     * @param string $clientValidate By default, validation will match
2016     * the 'qf_client' value from the column definition.  However, if
2017     * you set $clientValidate to true or false, this will override the
2018     * value from the column definition.
2019     *
2020     * @param array $formFilters An array with filter function names or
2021     * callbacks that will be applied to all form elements.
2022     *
2023     * @return object HTML_QuickForm
2024     *
2025     * @access public
2026     *
2027     * @see HTML_QuickForm
2028     * @see DB_Table_QuickForm
2029     */
2030    function &getForm($columns = null, $array_name = null, $args = array(),
2031        $clientValidate = null, $formFilters = null)
2032    {
2033        include_once 'DB/Table/QuickForm.php';
2034        $coldefs = $this->_getFormColDefs($columns);
2035        $form =& DB_Table_QuickForm::getForm($coldefs, $array_name, $args,
2036            $clientValidate, $formFilters);
2037        return $form;
2038    }
2039
2040
2041    /**
2042     * Adds elements and rules to a pre-existing HTML_QuickForm object.
2043     *
2044     * By default, the form will use the names of the columns as the names
2045     * of the form elements.  If you pass $array_name, the column names
2046     * will become keys in an array named for this parameter.
2047     *
2048     * @param object &$form      An HTML_QuickForm object.
2049     *
2050     * @param array $columns     A sequential array of column names to use in
2051     *                           the form; if null, uses all columns.
2052     *
2053     * @param string $array_name Name of array of column names
2054     *
2055     * @param clientValidate
2056     *
2057     * @return void
2058     *
2059     * @access public
2060     *
2061     * @see HTML_QuickForm
2062     *
2063     * @see DB_Table_QuickForm
2064     */
2065    function addFormElements(&$form, $columns = null, $array_name = null,
2066        $clientValidate = null)
2067    {
2068        include_once 'DB/Table/QuickForm.php';
2069        $coldefs = $this->_getFormColDefs($columns);
2070        DB_Table_QuickForm::addElements($form, $coldefs, $array_name);
2071        DB_Table_QuickForm::addRules($form, $coldefs, $array_name,
2072           $clientValidate);
2073    }
2074
2075
2076    /**
2077     * Adds static form elements like 'header', 'static', 'submit' or 'reset'
2078     * to a pre-existing HTML_QuickForm object. The form elements needs to be
2079     * defined in a property called $frm.
2080     *
2081     * @param object &$form An HTML_QuickForm object.
2082     * @return void
2083     * @access public
2084     *
2085     * @see HTML_QuickForm
2086     * @see DB_Table_QuickForm
2087     */
2088    function addStaticFormElements(&$form)
2089    {
2090        include_once 'DB/Table/QuickForm.php';
2091        DB_Table_QuickForm::addStaticElements($form, $this->frm);
2092    }
2093
2094
2095    /**
2096     *
2097     * Creates and returns an array of QuickForm elements based on an
2098     * array of DB_Table column names.
2099     *
2100     * @param array $columns A sequential array of column names to use in
2101     * the form; if null, uses all columns.
2102     *
2103     * @param string $array_name By default, the form will use the names
2104     * of the columns as the names of the form elements.  If you pass
2105     * $array_name, the column names will become keys in an array named
2106     * for this parameter.
2107     *
2108     * @return array An array of HTML_QuickForm_Element objects.
2109     *
2110     * @access public
2111     *
2112     * @see HTML_QuickForm
2113     * @see DB_Table_QuickForm
2114     */
2115    function &getFormGroup($columns = null, $array_name = null)
2116    {
2117        include_once 'DB/Table/QuickForm.php';
2118        $coldefs = $this->_getFormColDefs($columns);
2119        $group =& DB_Table_QuickForm::getGroup($coldefs, $array_name);
2120        return $group;
2121    }
2122
2123
2124    /**
2125     * Creates and returns a single QuickForm element based on a DB_Table
2126     * column name.
2127     *
2128     * @param string $column   A DB_Table column name.
2129     * @param string $elemname The name to use for the generated QuickForm
2130     *                         element.
2131     *
2132     * @return object HTML_QuickForm_Element
2133     *
2134     * @access public
2135     *
2136     * @see HTML_QuickForm
2137     * @see DB_Table_QuickForm
2138     */
2139
2140    function &getFormElement($column, $elemname)
2141    {
2142        include_once 'DB/Table/QuickForm.php';
2143        $coldef = $this->_getFormColDefs($column);
2144        DB_Table_QuickForm::fixColDef($coldef[$column], $elemname);
2145        $element =& DB_Table_QuickForm::getElement($coldef[$column],
2146            $elemname);
2147        return $element;
2148    }
2149
2150    /**
2151     * Creates and returns an array of QuickForm elements based on a DB_Table
2152     * column name.
2153     *
2154     * @author Ian Eure <ieure@php.net>
2155     *
2156     * @param array $cols        Array of DB_Table column names
2157     * @param string $array_name The name to use for the generated QuickForm
2158     *                           elements.
2159     * @return object HTML_QuickForm_Element
2160     *
2161     * @access public
2162     *
2163     * @see HTML_QuickForm
2164     * @see DB_Table_QuickForm
2165     */
2166    function &getFormElements($cols, $array_name = null)
2167    {
2168        include_once 'DB/Table/QuickForm.php';
2169        $elements =& DB_Table_QuickForm::getElements($cols, $array_name);
2170        return $elements;
2171    }
2172
2173
2174    /**
2175     * Creates a column definition array suitable for DB_Table_QuickForm.
2176     *
2177     * @param string|array $column_set A string column name, a sequential
2178     * array of columns names, or an associative array where the key is a
2179     * column name and the value is the default value for the generated
2180     * form element.  If null, uses all columns for this class.
2181     *
2182     * @return array An array of column defintions suitable for passing
2183     *               to DB_Table_QuickForm.
2184     *
2185     * @access public
2186     *
2187     */
2188    function _getFormColDefs($column_set = null)
2189    {
2190        if (is_null($column_set)) {
2191            // no columns or columns+values; just return the $this->col
2192            // array.
2193            return $this->getColumns($column_set);
2194        }
2195
2196        // check to see if the keys are sequential integers.  if so,
2197        // the $column_set is just a list of columns.
2198        settype($column_set, 'array');
2199        $keys = array_keys($column_set);
2200        $all_integer = true;
2201        foreach ($keys as $val) {
2202            if (! is_integer($val)) {
2203                $all_integer = false;
2204                break;
2205            }
2206        }
2207
2208        if ($all_integer) {
2209
2210            // the column_set is just a list of columns; get back the $this->col
2211            // array elements matching this list.
2212            $coldefs = $this->getColumns($column_set);
2213
2214        } else {
2215
2216            // the columns_set is an associative array where the key is a
2217            // column name and the value is the form element value.
2218            $coldefs = $this->getColumns($keys);
2219            foreach ($coldefs as $key => $val) {
2220                $coldefs[$key]['qf_setvalue'] = $column_set[$key];
2221            }
2222
2223        }
2224
2225        return $coldefs;
2226    }
2227
2228    /**
2229     * Returns XML string representation of the table
2230     *
2231     * @param  string $indent string of whitespace
2232     * @return string XML string
2233     * @access public
2234     */
2235    function toXML($indent = '') {
2236        require_once 'DB/Table/XML.php';
2237        $s = array();
2238        $s[] = DB_Table_XML::openTag('table', $indent);
2239        $s[] = DB_Table_XML::lineElement('name', $this->table, $indent);
2240        $s[] = DB_Table_XML::openTag('declaration', $indent);
2241        // Column declarations
2242        foreach ($this->col as $name => $col) {
2243            $type     = (isset($col['type'])) ? $col['type'] : null;
2244            $size     = (isset($col['size'])) ? $col['size'] : null;
2245            $scope    = (isset($col['scope'])) ? $col['scope'] : null;
2246            $require  = (isset($col['require'])) ? $col['require'] : null;
2247            $default  = (isset($col['set default'])) ? $col['set default'] : null;
2248            $line = '   ' . $name . '  ' . $type;
2249            $s[] = DB_Table_XML::openTag('field', $indent);
2250            $s[] = DB_Table_XML::lineElement('name', $name, $indent);
2251            $s[] = DB_Table_XML::lineElement('type', $type, $indent);
2252            if ($size) {
2253                $s[] = DB_Table_XML::lineElement('length', $size, $indent);
2254            }
2255            if ($require) {
2256                $require = (int) $require;
2257                $s[] = DB_Table_XML::lineElement('notnull', $require, $indent);
2258            }
2259            if (!($default === null)) {
2260               $s[] = DB_Table_XML::lineElement('set default', $default, $indent);
2261            }
2262            if ($this->auto_inc_col == $name) {
2263               $s[] = DB_Table_XML::lineElement('autoincrement', '1', $indent);
2264            }
2265            $s[] = DB_Table_XML::closeTag('field', $indent);
2266        }
2267        // Index declarations
2268        foreach ($this->idx as $name => $idx) {
2269            $s[] = DB_Table_XML::openTag('index', $indent);
2270            $cols = $idx['cols'];
2271            $type = $idx['type'];
2272            if (is_string($name)) {
2273                $s[] = DB_Table_XML::lineElement('name', $name, $indent);
2274            }
2275            if ($type == 'primary') {
2276                $s[] = DB_Table_XML::lineElement('primary', '1', $indent);
2277            } elseif ($type == 'unique') {
2278                $s[] = DB_Table_XML::lineElement('unique', '1', $indent);
2279            }
2280            if (is_string($cols)) {
2281                $cols = array($cols);
2282            }
2283            foreach ($cols as $col) {
2284                $s[] = DB_Table_XML::lineElement('field', $col, $indent);
2285            }
2286            $s[] = DB_Table_XML::closeTag('index', $indent);
2287        }
2288        // Foreign key references (if $this->_database is not null)
2289        if ($this->_database) {
2290            if (isset($this->_database->_ref[$this->table])) {
2291                $refs = $this->_database->_ref[$this->table];
2292                foreach ($refs as $rtable => $def) {
2293                    $fkey = $def['fkey']; // foreign key of referencing table
2294                    $rkey = $def['rkey']; // referenced/primary key
2295                    if (is_string($fkey)) {
2296                        $fkey = array($fkey);
2297                    }
2298                    if (is_string($rkey)) {
2299                        $rkey = array($rkey);
2300                    }
2301                    $on_delete = $def['on_delete']; // on-delete action
2302                    $on_update = $def['on_update']; // on-update action
2303                    $s[] = DB_Table_XML::openTag('foreign', $indent);
2304                    foreach ($fkey as $fcol) {
2305                        $s[] = DB_Table_XML::lineElement('field', $fcol, $indent);
2306                    }
2307                    $s[] = DB_Table_XML::openTag('references', $indent);
2308                    $s[] = DB_Table_XML::lineElement('table', $rtable, $indent);
2309                    if ($rkey) {
2310                        foreach ($rkey as $rcol) {
2311                            $s[] = DB_Table_XML::lineElement('field', $rcol,
2312                                                             $indent);
2313                        }
2314                    }
2315                    $s[] = DB_Table_XML::closeTag('references', $indent);
2316                    if ($on_delete) {
2317                        $s[] = DB_Table_XML::lineElement('ondelete', $on_delete,
2318                                                         $indent);
2319                    }
2320                    if ($on_update) {
2321                        $s[] = DB_Table_XML::lineElement('onupdate', $on_update,
2322                                                         $indent);
2323                    }
2324                    $s[] = DB_Table_XML::closeTag('foreign', $indent);
2325                }
2326            }
2327        }
2328        $s[] = DB_Table_XML::closeTag('declaration', $indent);
2329        $s[] = DB_Table_XML::closeTag('table', $indent);
2330        return implode("\n", $s);
2331    }
2332
2333}
2334?>
2335