upload
[pear] / DB / DataObject / Generator.php
1 <?php
2 /**
3  * Generation tools for DB_DataObject
4  *
5  * PHP versions 4 and 5
6  *
7  * LICENSE: This source file is subject to version 3.01 of the PHP license
8  * that is available through the world-wide-web at the following URI:
9  * http://www.php.net/license/3_01.txt.  If you did not receive a copy of
10  * the PHP License and are unable to obtain it through the web, please
11  * send a note to license@php.net so we can mail you a copy immediately.
12  *
13  * @category   Database
14  * @package    DB_DataObject
15  * @author     Alan Knowles <alan@akbkhome.com>
16  * @copyright  1997-2006 The PHP Group
17  * @license    http://www.php.net/license/3_01.txt  PHP License 3.01
18  * @version    CVS: $Id: Generator.php 289384 2009-10-09 00:17:26Z alan_k $
19  * @link       http://pear.php.net/package/DB_DataObject
20  */
21  
22  /*
23  * Security Notes:
24  *   This class uses eval to create classes on the fly.
25  *   The table name and database name are used to check the database before writing the
26  *   class definitions, we now check for quotes and semi-colon's in both variables
27  *   so I cant see how it would be possible to generate code even if
28  *   for some crazy reason you took the classname and table name from User Input.
29  *   
30  *   If you consider that wrong, or can prove it.. let me know!
31  */
32  
33  /**
34  * 
35  * Config _$ptions
36  * [DB_DataObject]
37  * ; optional default = DB/DataObject.php
38  * extends_location =
39  * ; optional default = DB_DataObject
40  * extends =
41  * ; alter the extends field when updating a class (defaults to only replacing DB_DataObject)
42  * generator_class_rewrite = ANY|specific_name   // default is DB_DataObject
43  *
44  */
45
46 /**
47  * Needed classes
48  * We lazy load here, due to problems with the tests not setting up include path correctly.
49  * FIXME!
50  */
51 class_exists('DB_DataObject') ? '' : require_once 'DB/DataObject.php';
52 //require_once('Config.php');
53
54 /**
55  * Generator class
56  *
57  * @package DB_DataObject
58  */
59 class DB_DataObject_Generator extends DB_DataObject
60 {
61     /* =========================================================== */
62     /*  Utility functions - for building db config files           */
63     /* =========================================================== */
64
65     /**
66      * Array of table names
67      *
68      * @var array
69      * @access private
70      */
71     var $tables;
72
73     /**
74      * associative array table -> array of table row objects
75      *
76      * @var array
77      * @access private
78      */
79     var $_definitions;
80
81     /**
82      * active table being output
83      *
84      * @var string
85      * @access private
86      */
87     var $table; // active tablename
88
89
90     /**
91      * The 'starter' = call this to start the process
92      *
93      * @access  public
94      * @return  none
95      */
96     function start()
97     {
98         $options = &PEAR::getStaticProperty('DB_DataObject','options');
99         $db_driver = empty($options['db_driver']) ? 'DB' : $options['db_driver'];
100
101         $databases = array();
102         foreach($options as $k=>$v) {
103             if (substr($k,0,9) == 'database_') {
104                 $databases[substr($k,9)] = $v;
105             }
106         }
107
108         if (isset($options['database'])) {
109             if ($db_driver == 'DB') {
110                 require_once 'DB.php';
111                 $dsn = DB::parseDSN($options['database']);
112             } else {
113                 require_once 'MDB2.php';
114                 $dsn = MDB2::parseDSN($options['database']);
115             }
116
117             if (!isset($database[$dsn['database']])) {
118                 $databases[$dsn['database']] = $options['database'];
119             }
120         }
121
122         foreach($databases as $databasename => $database) {
123             if (!$database) {
124                 continue;
125             }
126             $this->debug("CREATING FOR $databasename\n");
127             $class = get_class($this);
128             $t = new $class;
129             $t->_database_dsn = $database;
130
131
132             $t->_database = $databasename;
133             if ($db_driver == 'DB') {
134                 require_once 'DB.php';
135                 $dsn = DB::parseDSN($database);
136             } else {
137                 require_once 'MDB2.php';
138                 $dsn = MDB2::parseDSN($database);
139             }
140
141             if (($dsn['phptype'] == 'sqlite') && is_file($databasename)) {
142                 $t->_database = basename($t->_database);
143             }
144             $t->_createTableList();
145
146             foreach(get_class_methods($class) as $method) {
147                 if (substr($method,0,8 ) != 'generate') {
148                     continue;
149                 }
150                 $this->debug("calling $method");
151                 $t->$method();
152             }
153         }
154         $this->debug("DONE\n\n");
155     }
156
157     /**
158      * Output File was config object, now just string
159      * Used to generate the Tables
160      *
161      * @var    string outputbuffer for table definitions
162      * @access private
163      */
164     var $_newConfig;
165
166     /**
167      * Build a list of tables;
168      * and store it in $this->tables and $this->_definitions[tablename];
169      *
170      * @access  private
171      * @return  none
172      */
173     function _createTableList()
174     {
175         $this->_connect();
176         $options = &PEAR::getStaticProperty('DB_DataObject','options');
177
178         $__DB= &$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'][$this->_database_dsn_md5];
179
180         $db_driver = empty($options['db_driver']) ? 'DB' : $options['db_driver'];
181         $is_MDB2 = ($db_driver != 'DB') ? true : false;
182
183         if (is_a($__DB , 'PEAR_Error')) {
184             return PEAR::raiseError($__DB->toString(), null, PEAR_ERROR_DIE);
185         }
186         
187         if (!$is_MDB2) {
188             // try getting a list of schema tables first. (postgres)
189             $__DB->expectError(DB_ERROR_UNSUPPORTED);
190             $this->tables = $__DB->getListOf('schema.tables');
191             $__DB->popExpect();
192         } else {
193             /**
194              * set portability and some modules to fetch the informations
195              */
196             $db_options = PEAR::getStaticProperty('MDB2','options');
197             if (empty($db_options)) {
198                 $__DB->setOption('portability', MDB2_PORTABILITY_ALL ^ MDB2_PORTABILITY_FIX_CASE);
199             }
200             
201             $__DB->loadModule('Manager');
202             $__DB->loadModule('Reverse');
203         }
204
205         if ((empty($this->tables) || is_a($this->tables , 'PEAR_Error'))) {
206             //if that fails fall back to clasic tables list.
207             if (!$is_MDB2) {
208                 // try getting a list of schema tables first. (postgres)
209                 $__DB->expectError(DB_ERROR_UNSUPPORTED);
210                 $this->tables = $__DB->getListOf('tables');
211                 $__DB->popExpect();
212             } else  {
213                 $this->tables = $__DB->manager->listTables();
214                 $sequences = $__DB->manager->listSequences();
215                 foreach ($sequences as $k => $v) {
216                     $this->tables[] = $__DB->getSequenceName($v);
217                 }
218             }
219         }
220
221         if (is_a($this->tables , 'PEAR_Error')) {
222             return PEAR::raiseError($this->tables->toString(), null, PEAR_ERROR_DIE);
223         }
224
225         // build views as well if asked to.
226         if (!empty($options['build_views'])) {
227             if (!$is_MDB2) {
228                 $views = $__DB->getListOf('views');
229             } else {
230                 $views = $__DB->manager->listViews();
231             }
232             if (is_a($views,'PEAR_Error')) {
233                 return PEAR::raiseError(
234                 'Error getting Views (check the PEAR bug database for the fix to DB), ' .
235                 $views->toString(),
236                 null,
237                 PEAR_ERROR_DIE
238                 );
239             }
240             $this->tables = array_merge ($this->tables, $views);
241         }
242
243         // declare a temporary table to be filled with matching tables names
244         $tmp_table = array();
245
246
247         foreach($this->tables as $table) {
248             if (isset($options['generator_include_regex']) &&
249             !preg_match($options['generator_include_regex'],$table)) {
250                 continue;
251             } else if (isset($options['generator_exclude_regex']) &&
252             preg_match($options['generator_exclude_regex'],$table)) {
253                 continue;
254             }
255             // postgres strip the schema bit from the
256             if (!empty($options['generator_strip_schema'])) {
257                 $bits = explode('.', $table,2);
258                 $table = $bits[0];
259                 if (count($bits) > 1) {
260                     $table = $bits[1];
261                 }
262             }
263             $quotedTable = !empty($options['quote_identifiers_tableinfo']) ? 
264                 $__DB->quoteIdentifier($table) : $table;
265                 
266             if (!$is_MDB2) {
267                 
268                 $defs =  $__DB->tableInfo($quotedTable);
269             } else {
270                 $defs =  $__DB->reverse->tableInfo($quotedTable);
271                 // rename the length value, so it matches db's return.
272                 
273             }
274
275             if (is_a($defs,'PEAR_Error')) {
276                 // running in debug mode should pick this up as a big warning..
277                 $this->raiseError('Error reading tableInfo, '. $defs->toString());
278                 continue;
279             }
280             // cast all definitions to objects - as we deal with that better.
281
282
283
284             foreach($defs as $def) {
285                 if (!is_array($def)) {
286                     continue;
287                 }
288                 // rename the length value, so it matches db's return.
289                 if (isset($def['length']) && !isset($def['len'])) {
290                     $def['len'] = $def['length'];
291                 }
292                 $this->_definitions[$table][] = (object) $def;
293
294             }
295             // we find a matching table, just  store it into a temporary array
296             $tmp_table[] = $table;
297
298
299         }
300         // the temporary table array is now the right one (tables names matching
301         // with regex expressions have been removed)
302         $this->tables = $tmp_table;
303         //print_r($this->_definitions);
304     }
305     
306     /**
307      * Auto generation of table data.
308      *
309      * it will output to db_oo_{database} the table definitions
310      *
311      * @access  private
312      * @return  none
313      */
314     function generateDefinitions()
315     {
316         $this->debug("Generating Definitions file:        ");
317         if (!$this->tables) {
318             $this->debug("-- NO TABLES -- \n");
319             return;
320         }
321
322         $options = &PEAR::getStaticProperty('DB_DataObject','options');
323
324
325         //$this->_newConfig = new Config('IniFile');
326         $this->_newConfig = '';
327         foreach($this->tables as $this->table) {
328             $this->_generateDefinitionsTable();
329         }
330         $this->_connect();
331         // dont generate a schema if location is not set
332         // it's created on the fly!
333         if (empty($options['schema_location']) && empty($options["ini_{$this->_database}"]) ) {
334             return;
335         }
336         if (!empty($options['generator_no_ini'])) { // built in ini files..
337             return;
338         }
339         $base =  @$options['schema_location'];
340         if (isset($options["ini_{$this->_database}"])) {
341             $file = $options["ini_{$this->_database}"];
342         } else {
343             $file = "{$base}/{$this->_database}.ini";
344         }
345         
346         if (!file_exists(dirname($file))) {
347             require_once 'System.php';
348             System::mkdir(array('-p','-m',0755,dirname($file)));
349         }
350         $this->debug("Writing ini as {$file}\n");
351         //touch($file);
352         $tmpname = tempnam(session_save_path(),'DataObject_');
353         //print_r($this->_newConfig);
354         $fh = fopen($tmpname,'w');
355         fwrite($fh,$this->_newConfig);
356         fclose($fh);
357         $perms = file_exists($file) ? fileperms($file) : 0755;
358         // windows can fail doing this. - not a perfect solution but otherwise it's getting really kludgy..
359         
360         if (!@rename($tmpname, $file)) { 
361             unlink($file); 
362             rename($tmpname, $file);
363         }
364         chmod($file,$perms);
365         //$ret = $this->_newConfig->writeInput($file,false);
366
367         //if (PEAR::isError($ret) ) {
368         //    return PEAR::raiseError($ret->message,null,PEAR_ERROR_DIE);
369         // }
370     }
371
372     /**
373      * generate Foreign Keys (for links.ini) 
374      * Currenly only works with mysql / mysqli
375      * to use, you must set option: generate_links=true
376      * 
377      * @author Pascal Schöni 
378      */
379     function generateForeignKeys() 
380     {
381         $options = PEAR::getStaticProperty('DB_DataObject','options');
382         if (empty($options['generate_links'])) {
383             return false;
384         }
385         $__DB = &$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'][$this->_database_dsn_md5];
386         if (!in_array($__DB->phptype, array('mysql','mysqli'))) {
387             echo "WARNING: cant handle non-mysql introspection for defaults.";
388             return; // cant handle non-mysql introspection for defaults.
389         }
390
391         $DB = $this->getDatabaseConnection();
392
393         $fk = array();
394
395         foreach($this->tables as $this->table) {
396             $quotedTable = !empty($options['quote_identifiers_tableinfo']) ?  $DB->quoteIdentifier($table)  : $this->table;
397             
398             $res =& $DB->query('SHOW CREATE TABLE ' . $quotedTable );
399
400             if (PEAR::isError($res)) {
401                 die($res->getMessage());
402             }
403
404             $text = $res->fetchRow(DB_FETCHMODE_DEFAULT, 0);
405             $treffer = array();
406             // Extract FOREIGN KEYS
407             preg_match_all(
408                 "/FOREIGN KEY \(`(\w*)`\) REFERENCES `(\w*)` \(`(\w*)`\)/i", 
409                 $text[1], 
410                 $treffer, 
411                 PREG_SET_ORDER);
412
413             if (count($treffer) < 1) {
414                 continue;
415             }
416             for ($i = 0; $i < count($treffer); $i++) {
417                 $fk[$this->table][$treffer[$i][1]] = $treffer[$i][2] . ":" . $treffer[$i][3];
418             }
419             
420         }
421
422         $links_ini = "";
423
424         foreach($fk as $table => $details) {
425             $links_ini .= "[$table]\n";
426             foreach ($details as $col => $ref) {
427                 $links_ini .= "$col = $ref\n";
428             }
429             $links_ini .= "\n";
430         }
431
432         // dont generate a schema if location is not set
433         // it's created on the fly!
434         $options = PEAR::getStaticProperty('DB_DataObject','options');
435
436         if (empty($options['schema_location'])) {
437             return;
438         }
439
440         
441         $file = "{$options['schema_location']}/{$this->_database}.links.ini";
442
443         if (!file_exists(dirname($file))) {
444             require_once 'System.php';
445             System::mkdir(array('-p','-m',0755,dirname($file)));
446         }
447
448         $this->debug("Writing ini as {$file}\n");
449         
450         //touch($file); // not sure why this is needed?
451         $tmpname = tempnam(session_save_path(),'DataObject_');
452        
453         $fh = fopen($tmpname,'w');
454         fwrite($fh,$links_ini);
455         fclose($fh);
456         $perms = file_exists($file) ? fileperms($file) : 0755;
457         // windows can fail doing this. - not a perfect solution but otherwise it's getting really kludgy..
458         if (!@rename($tmpname, $file)) { 
459             unlink($file); 
460             rename($tmpname, $file);
461         }
462         chmod($file, $perms);
463     }
464
465       
466     /**
467      * The table geneation part
468      *
469      * @access  private
470      * @return  tabledef and keys array.
471      */
472     function _generateDefinitionsTable()
473     {
474         global $_DB_DATAOBJECT;
475         $options = PEAR::getStaticProperty('DB_DataObject','options');
476         $defs = $this->_definitions[$this->table];
477         $this->_newConfig .= "\n[{$this->table}]\n";
478         $keys_out =  "\n[{$this->table}__keys]\n";
479         $keys_out_primary = '';
480         $keys_out_secondary = '';
481         if (@$_DB_DATAOBJECT['CONFIG']['debug'] > 2) {
482             echo "TABLE STRUCTURE FOR {$this->table}\n";
483             print_r($defs);
484         }
485         $DB = $this->getDatabaseConnection();
486         $dbtype = $DB->phptype;
487         
488         $ret = array(
489                 'table' => array(),
490                 'keys' => array(),
491             );
492             
493         $ret_keys_primary = array();
494         $ret_keys_secondary = array();
495         
496         
497         
498         foreach($defs as $t) {
499              
500             $n=0;
501             $write_ini = true;
502             
503             
504             switch (strtoupper($t->type)) {
505
506                 case 'INT':
507                 case 'INT2':    // postgres
508                 case 'INT4':    // postgres
509                 case 'INT8':    // postgres
510                 case 'SERIAL4': // postgres
511                 case 'SERIAL8': // postgres
512                 case 'INTEGER':
513                 case 'TINYINT':
514                 case 'SMALLINT':
515                 case 'MEDIUMINT':
516                 case 'BIGINT':
517                     $type = DB_DATAOBJECT_INT;
518                     if ($t->len == 1) {
519                         $type +=  DB_DATAOBJECT_BOOL;
520                     }
521                     break;
522                
523                 case 'REAL':
524                 case 'DOUBLE':
525                 case 'DOUBLE PRECISION': // double precision (firebird)
526                 case 'FLOAT':
527                 case 'FLOAT4': // real (postgres)
528                 case 'FLOAT8': // double precision (postgres)
529                 case 'DECIMAL':
530                 case 'MONEY':  // mssql and maybe others
531                 case 'NUMERIC':
532                 case 'NUMBER': // oci8 
533                     $type = DB_DATAOBJECT_INT; // should really by FLOAT!!! / MONEY...
534                     break;
535                     
536                 case 'YEAR':
537                     $type = DB_DATAOBJECT_INT; 
538                     break;
539                     
540                 case 'BIT':
541                 case 'BOOL':   
542                 case 'BOOLEAN':   
543                 
544                     $type = DB_DATAOBJECT_BOOL;
545                     // postgres needs to quote '0'
546                     if ($dbtype == 'pgsql') {
547                         $type +=  DB_DATAOBJECT_STR;
548                     }
549                     break;
550                     
551                 case 'STRING':
552                 case 'CHAR':
553                 case 'VARCHAR':
554                 case 'VARCHAR2':
555                 case 'TINYTEXT':
556                 
557                 case 'ENUM':
558                 case 'SET':         // not really but oh well
559                 
560                 case 'POINT':       // mysql geometry stuff - not really string - but will do..
561                 
562                 case 'TIMESTAMPTZ': // postgres
563                 case 'BPCHAR':      // postgres
564                 case 'INTERVAL':    // postgres (eg. '12 days')
565                 
566                 case 'CIDR':        // postgres IP net spec
567                 case 'INET':        // postgres IP
568                 case 'MACADDR':     // postgress network Mac address.
569                 
570                 case 'INTEGER[]':   // postgres type
571                 case 'BOOLEAN[]':   // postgres type
572                 
573                     $type = DB_DATAOBJECT_STR;
574                     break;
575                 
576                 case 'TEXT':
577                 case 'MEDIUMTEXT':
578                 case 'LONGTEXT':
579                     
580                     $type = DB_DATAOBJECT_STR + DB_DATAOBJECT_TXT;
581                     break;
582                 
583                 
584                 case 'DATE':    
585                     $type = DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE;
586                     break;
587                     
588                 case 'TIME':    
589                     $type = DB_DATAOBJECT_STR + DB_DATAOBJECT_TIME;
590                     break;    
591                     
592                 
593                 case 'DATETIME': 
594                      
595                     $type = DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME;
596                     break;    
597                     
598                 case 'TIMESTAMP': // do other databases use this???
599                     
600                     $type = ($dbtype == 'mysql') ?
601                         DB_DATAOBJECT_MYSQLTIMESTAMP : 
602                         DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME;
603                     break;    
604                     
605                 
606                 case 'BLOB':       /// these should really be ignored!!!???
607                 case 'TINYBLOB':
608                 case 'MEDIUMBLOB':
609                 case 'LONGBLOB':
610                 
611                 case 'CLOB': // oracle character lob support
612                 
613                 case 'BYTEA':   // postgres blob support..
614                     $type = DB_DATAOBJECT_STR + DB_DATAOBJECT_BLOB;
615                     break;
616                     
617                 default:     
618                     echo "*****************************************************************\n".
619                          "**               WARNING UNKNOWN TYPE                          **\n".
620                          "** Found column '{$t->name}', of type  '{$t->type}'            **\n".
621                          "** Please submit a bug, describe what type you expect this     **\n".
622                          "** column  to be                                               **\n".
623                          "** ---------POSSIBLE FIX / WORKAROUND -------------------------**\n".
624                          "** Try using MDB2 as the backend - eg set the config option    **\n".
625                          "** db_driver = MDB2                                            **\n".
626                          "*****************************************************************\n";
627                     $write_ini = false;
628                     break;
629             }
630             
631             if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $t->name)) {
632                 echo "*****************************************************************\n".
633                      "**               WARNING COLUMN NAME UNUSABLE                  **\n".
634                      "** Found column '{$t->name}', of type  '{$t->type}'            **\n".
635                      "** Since this column name can't be converted to a php variable **\n".
636                      "** name, and the whole idea of mapping would result in a mess  **\n".
637                      "** This column has been ignored...                             **\n".
638                      "*****************************************************************\n";
639                 continue;
640             }
641             
642             if (!strlen(trim($t->name))) {
643                 continue; // is this a bug?
644             }
645             
646             if (preg_match('/not[ _]null/i',$t->flags)) {
647                 $type += DB_DATAOBJECT_NOTNULL;
648             }
649            
650            
651             if (in_array($t->name,array('null','yes','no','true','false'))) {
652                 echo "*****************************************************************\n".
653                      "**                             WARNING                         **\n".
654                      "** Found column '{$t->name}', which is invalid in an .ini file **\n".
655                      "** This line will not be writen to the file - you will have    **\n".
656                      "** define the keys()/method manually.                          **\n".
657                      "*****************************************************************\n";
658                 $write_ini = false;
659             } else {
660                 $this->_newConfig .= "{$t->name} = $type\n";
661             }
662             
663             $ret['table'][$t->name] = $type;
664             // i've no idea if this will work well on other databases?
665             // only use primary key or nextval(), cause the setFrom blocks you setting all key items...
666             // if no keys exist fall back to using unique
667             //echo "\n{$t->name} => {$t->flags}\n";
668             $secondary_key_match = isset($options['generator_secondary_key_match']) ? $options['generator_secondary_key_match'] : 'primary|unique';
669             
670             if (preg_match('/(auto_increment|nextval\()/i',rawurldecode($t->flags)) 
671                 || (isset($t->autoincrement) && ($t->autoincrement === true))) {
672                     
673                 // native sequences = 2
674                 if ($write_ini) {
675                     $keys_out_primary .= "{$t->name} = N\n";
676                 }
677                 $ret_keys_primary[$t->name] = 'N';
678             
679             } else if ($secondary_key_match && preg_match('/('.$secondary_key_match.')/i',$t->flags)) {
680                 // keys.. = 1
681                 $key_type = 'K';
682                 if (!preg_match("/(primary)/i",$t->flags)) {
683                     $key_type = 'U';
684                 }
685                 
686                 if ($write_ini) {
687                     $keys_out_secondary .= "{$t->name} = {$key_type}\n";
688                 }
689                 $ret_keys_secondary[$t->name] = $key_type;
690             }
691             
692         
693         }
694         
695         $this->_newConfig .= $keys_out . (empty($keys_out_primary) ? $keys_out_secondary : $keys_out_primary);
696         $ret['keys'] = empty($keys_out_primary) ? $ret_keys_secondary : $ret_keys_primary;
697         
698         if (@$_DB_DATAOBJECT['CONFIG']['debug'] > 2) {
699             print_r(array("dump for {$this->table}", $ret));
700         }
701         
702         return $ret;
703         
704         
705     }
706
707     /**
708     * Convert a table name into a class name -> override this if you want a different mapping
709     *
710     * @access  public
711     * @return  string class name;
712     */
713     
714     
715     function getClassNameFromTableName($table)
716     {
717         $options = &PEAR::getStaticProperty('DB_DataObject','options');
718         $class_prefix  = empty($options['class_prefix']) ? '' : $options['class_prefix'];
719         return  $class_prefix.preg_replace('/[^A-Z0-9]/i','_',ucfirst(trim($this->table)));
720     }
721     
722     
723     /**
724     * Convert a table name into a file name -> override this if you want a different mapping
725     *
726     * @access  public
727     * @return  string file name;
728     */
729     
730     
731     function getFileNameFromTableName($table)
732     {
733         $options = &PEAR::getStaticProperty('DB_DataObject','options');
734         $base = $options['class_location'];
735         if (strpos($base,'%s') !== false) {
736             $base = dirname($base);
737         } 
738         if (!file_exists($base)) {
739             require_once 'System.php';
740             System::mkdir(array('-p',$base));
741         }
742         if (strpos($options['class_location'],'%s') !== false) {
743             $outfilename   = sprintf($options['class_location'], 
744                     preg_replace('/[^A-Z0-9]/i','_',ucfirst($this->table)));
745         } else { 
746             $outfilename = "{$base}/".preg_replace('/[^A-Z0-9]/i','_',ucfirst($this->table)).".php";
747         }
748         return $outfilename;
749         
750     }
751     
752     
753      /**
754     * Convert a column name into a method name (usually prefixed by get/set/validateXXXXX)
755     *
756     * @access  public
757     * @return  string method name;
758     */
759     
760     
761     function getMethodNameFromColumnName($col)
762     {
763         return ucfirst($col);
764     }
765     
766     
767     
768     
769     /*
770      * building the class files
771      * for each of the tables output a file!
772      */
773     function generateClasses()
774     {
775         //echo "Generating Class files:        \n";
776         $options = &PEAR::getStaticProperty('DB_DataObject','options');
777        
778         $this->_extends = empty($options['extends']) ? $this->_extends : $options['extends'];
779         $this->_extendsFile = empty($options['extends_location']) ? $this->_extendsFile : $options['extends_location'];
780  
781
782         foreach($this->tables as $this->table) {
783             $this->table        = trim($this->table);
784             $this->classname    = $this->getClassNameFromTableName($this->table);
785             $i = '';
786             $outfilename        = $this->getFileNameFromTableName($this->table);
787             
788             $oldcontents = '';
789             if (file_exists($outfilename)) {
790                 // file_get_contents???
791                 $oldcontents = implode('',file($outfilename));
792             }
793             
794             $out = $this->_generateClassTable($oldcontents);
795             $this->debug( "writing $this->classname\n");
796             $tmpname = tempnam(session_save_path(),'DataObject_');
797        
798             $fh = fopen($tmpname, "w");
799             fputs($fh,$out);
800             fclose($fh);
801             $perms = file_exists($outfilename) ? fileperms($outfilename) : 0755;
802             
803             // windows can fail doing this. - not a perfect solution but otherwise it's getting really kludgy..
804             if (!@rename($tmpname, $outfilename)) {
805                 unlink($outfilename); 
806                 rename($tmpname, $outfilename);
807             }
808             
809             chmod($outfilename, $perms);
810         }
811         //echo $out;
812     }
813
814     /**
815      * class being extended (can be overridden by [DB_DataObject] extends=xxxx
816      *
817      * @var    string
818      * @access private
819      */
820     var $_extends = 'DB_DataObject';
821
822     /**
823      * line to use for require('DB/DataObject.php');
824      *
825      * @var    string
826      * @access private
827      */
828     var $_extendsFile = "DB/DataObject.php";
829
830     /**
831      * class being generated
832      *
833      * @var    string
834      * @access private
835      */
836     var $_className;
837
838     /**
839      * The table class geneation part - single file.
840      *
841      * @access  private
842      * @return  none
843      */
844     function _generateClassTable($input = '')
845     {
846         // title = expand me!
847         $foot = "";
848         $head = "<?php\n/**\n * Table Definition for {$this->table}\n";
849         $head .= $this->derivedHookPageLevelDocBlock();
850         $head .= " */\n";
851         $head .= $this->derivedHookExtendsDocBlock();
852
853         
854         // requires
855         $head .= "require_once '{$this->_extendsFile}';\n\n";
856         // add dummy class header in...
857         // class 
858         $head .= $this->derivedHookClassDocBlock();
859         $head .= "class {$this->classname} extends {$this->_extends} \n{";
860
861         $body =  "\n    ###START_AUTOCODE\n";
862         $body .= "    /* the code below is auto generated do not remove the above tag */\n\n";
863         // table
864         $padding = (30 - strlen($this->table));
865         $padding  = ($padding < 2) ? 2 : $padding;
866         
867         $p =  str_repeat(' ',$padding) ;
868         
869         $options = &PEAR::getStaticProperty('DB_DataObject','options');
870         
871         
872         $var = (substr(phpversion(),0,1) > 4) ? 'public' : 'var';
873         $var = !empty($options['generator_var_keyword']) ? $options['generator_var_keyword'] : $var;
874         
875         
876         $body .= "    {$var} \$__table = '{$this->table}';  {$p}// table name\n";
877     
878         
879         // if we are using the option database_{databasename} = dsn
880         // then we should add var $_database = here
881         // as database names may not always match.. 
882         
883         if (empty($GLOBALS['_DB_DATAOBJECT']['CONFIG'])) {
884             DB_DataObject::_loadConfig();
885         }
886
887          // Only include the $_database property if the omit_database_var is unset or false
888         
889         if (isset($options["database_{$this->_database}"]) && empty($GLOBALS['_DB_DATAOBJECT']['CONFIG']['generator_omit_database_var'])) {
890             $body .= "    {$var} \$_database = '{$this->_database}';  {$p}// database name (used with database_{*} config)\n";
891         }
892         
893         
894         if (!empty($options['generator_novars'])) {
895             $var = '//'.$var;
896         }
897         
898         $defs = $this->_definitions[$this->table];
899
900         // show nice information!
901         $connections = array();
902         $sets = array();
903         foreach($defs as $t) {
904             if (!strlen(trim($t->name))) {
905                 continue;
906             }
907             if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $t->name)) {
908                 echo "*****************************************************************\n".
909                      "**               WARNING COLUMN NAME UNUSABLE                  **\n".
910                      "** Found column '{$t->name}', of type  '{$t->type}'            **\n".
911                      "** Since this column name can't be converted to a php variable **\n".
912                      "** name, and the whole idea of mapping would result in a mess  **\n".
913                      "** This column has been ignored...                             **\n".
914                      "*****************************************************************\n";
915                 continue;
916             }
917             
918             
919             $padding = (30 - strlen($t->name));
920             if ($padding < 2) $padding =2;
921             $p =  str_repeat(' ',$padding) ;
922             
923             $length = empty($t->len) ? '' : '('.$t->len.')';
924             $body .="    {$var} \${$t->name};  {$p}// {$t->type}$length  {$t->flags}\n";
925             
926             // can not do set as PEAR::DB table info doesnt support it.
927             //if (substr($t->Type,0,3) == "set")
928             //    $sets[$t->Field] = "array".substr($t->Type,3);
929             $body .= $this->derivedHookVar($t,$padding);
930         }
931
932         // THIS IS TOTALLY BORKED old FC creation
933         // IT WILL BE REMOVED!!!!! in DataObjects 1.6
934         // grep -r __clone * to find all it's uses
935         // and replace them with $x = clone($y);
936         // due to the change in the PHP5 clone design.
937         
938         if ( substr(phpversion(),0,1) < 5) {
939             $body .= "\n";
940             $body .= "    /* ZE2 compatibility trick*/\n";
941             $body .= "    function __clone() { return \$this;}\n";
942         }
943
944         // simple creation tools ! (static stuff!)
945         $body .= "\n";
946         $body .= "    /* Static get */\n";
947         $body .= "    function staticGet(\$k,\$v=NULL) { return DB_DataObject::staticGet('{$this->classname}',\$k,\$v); }\n";
948         
949         // generate getter and setter methods
950         $body .= $this->_generateGetters($input);
951         $body .= $this->_generateSetters($input);
952         
953         /*
954         theoretically there is scope here to introduce 'list' methods
955         based up 'xxxx_up' column!!! for heiracitcal trees..
956         */
957
958         // set methods
959         //foreach ($sets as $k=>$v) {
960         //    $kk = strtoupper($k);
961         //    $body .="    function getSets{$k}() { return {$v}; }\n";
962         //}
963         
964         if (!empty($options['generator_no_ini'])) {
965             $def = $this->_generateDefinitionsTable();  // simplify this!?
966             $body .= $this->_generateTableFunction($def['table']);
967             $body .= $this->_generateKeysFunction($def['keys']);
968             $body .= $this->_generateSequenceKeyFunction($def);
969             $body .= $this->_generateDefaultsFunction($this->table, $def['table']);
970         }  else if (!empty($options['generator_add_defaults'])) {   
971             // I dont really like doing it this way (adding another option)
972             // but it helps on older projects.
973             $def = $this->_generateDefinitionsTable();  // simplify this!?
974             $body .= $this->_generateDefaultsFunction($this->table,$def['table']);
975              
976         }
977         $body .= $this->derivedHookFunctions($input);
978
979         $body .= "\n    /* the code above is auto generated do not remove the tag below */";
980         $body .= "\n    ###END_AUTOCODE\n";
981
982
983         // stubs..
984         
985         if (!empty($options['generator_add_validate_stubs'])) {
986             foreach($defs as $t) {
987                 if (!strlen(trim($t->name))) {
988                     continue;
989                 }
990                 $validate_fname = 'validate' . $this->getMethodNameFromColumnName($t->name);
991                 // dont re-add it..
992                 if (preg_match('/\s+function\s+' . $validate_fname . '\s*\(/i', $input)) {
993                     continue;
994                 }
995                 $body .= "\n    function {$validate_fname}()\n    {\n        return false;\n    }\n";
996             }
997         }
998
999
1000
1001
1002         $foot .= "}\n";
1003         $full = $head . $body . $foot;
1004
1005         if (!$input) {
1006             return $full;
1007         }
1008         if (!preg_match('/(\n|\r\n)\s*###START_AUTOCODE(\n|\r\n)/s',$input))  {
1009             return $full;
1010         }
1011         if (!preg_match('/(\n|\r\n)\s*###END_AUTOCODE(\n|\r\n)/s',$input)) {
1012             return $full;
1013         }
1014
1015
1016         /* this will only replace extends DB_DataObject by default,
1017             unless use set generator_class_rewrite to ANY or a name*/
1018
1019         $class_rewrite = 'DB_DataObject';
1020         $options = &PEAR::getStaticProperty('DB_DataObject','options');
1021         if (empty($options['generator_class_rewrite']) || !($class_rewrite = $options['generator_class_rewrite'])) {
1022             $class_rewrite = 'DB_DataObject';
1023         }
1024         if ($class_rewrite == 'ANY') {
1025             $class_rewrite = '[a-z_]+';
1026         }
1027
1028         $input = preg_replace(
1029             '/(\n|\r\n)class\s*[a-z0-9_]+\s*extends\s*' .$class_rewrite . '\s*(\n|\r\n)\{(\n|\r\n)/si',
1030             "\nclass {$this->classname} extends {$this->_extends} \n{\n",
1031             $input);
1032
1033         $ret =  preg_replace(
1034             '/(\n|\r\n)\s*###START_AUTOCODE(\n|\r\n).*(\n|\r\n)\s*###END_AUTOCODE(\n|\r\n)/s',
1035             $body,$input);
1036         
1037         if (!strlen($ret)) {
1038             return PEAR::raiseError(
1039                 "PREG_REPLACE failed to replace body, - you probably need to set these in your php.ini\n".
1040                 "pcre.backtrack_limit=1000000\n".
1041                 "pcre.recursion_limit=1000000\n"
1042                 ,null, PEAR_ERROR_DIE);
1043        }
1044         
1045         return $ret;
1046     }
1047
1048     /**
1049      * hook to add extra methods to all classes
1050      *
1051      * called once for each class, use with $this->table and
1052      * $this->_definitions[$this->table], to get data out of the current table,
1053      * use it to add extra methods to the default classes.
1054      *
1055      * @access   public
1056      * @return  string added to class eg. functions.
1057      */
1058     function derivedHookFunctions($input = "")
1059     {
1060         // This is so derived generator classes can generate functions
1061         // It MUST NOT be changed here!!!
1062         return "";
1063     }
1064
1065     /**
1066      * hook for var lines
1067      * called each time a var line is generated, override to add extra var
1068      * lines
1069      *
1070      * @param object t containing type,len,flags etc. from tableInfo call
1071      * @param int padding number of spaces
1072      * @access   public
1073      * @return  string added to class eg. functions.
1074      */
1075     function derivedHookVar(&$t,$padding)
1076     {
1077         // This is so derived generator classes can generate variabels
1078         // It MUST NOT be changed here!!!
1079         return "";
1080     }
1081
1082     /**
1083      * hook to add extra page-level (in terms of phpDocumentor) DocBlock
1084      *
1085      * called once for each class, use it add extra page-level docs
1086      * @access public
1087      * @return string added to class eg. functions.
1088      */
1089     function derivedHookPageLevelDocBlock() {
1090         return '';
1091     }
1092
1093     /**
1094      * hook to add extra doc block (in terms of phpDocumentor) to extend string
1095      *
1096      * called once for each class, use it add extra comments to extends
1097      * string (require_once...)
1098      * @access public
1099      * @return string added to class eg. functions.
1100      */
1101     function derivedHookExtendsDocBlock() {
1102         return '';
1103     }
1104
1105     /**
1106      * hook to add extra class level DocBlock (in terms of phpDocumentor)
1107      *
1108      * called once for each class, use it add extra comments to class
1109      * string (require_once...)
1110      * @access public
1111      * @return string added to class eg. functions.
1112      */
1113     function derivedHookClassDocBlock() {
1114         return '';
1115     }
1116
1117     /**
1118
1119     /**
1120     * getProxyFull - create a class definition on the fly and instantate it..
1121     *
1122     * similar to generated files - but also evals the class definitoin code.
1123     * 
1124     * 
1125     * @param   string database name
1126     * @param   string  table   name of table to create proxy for.
1127     * 
1128     *
1129     * @return   object    Instance of class. or PEAR Error
1130     * @access   public
1131     */
1132     function getProxyFull($database,$table) 
1133     {
1134         
1135         if ($err = $this->fillTableSchema($database,$table)) {
1136             return $err;
1137         }
1138         
1139         
1140         $options = &PEAR::getStaticProperty('DB_DataObject','options');
1141         $class_prefix  = empty($options['class_prefix']) ? '' : $options['class_prefix'];
1142         
1143         $this->_extends = empty($options['extends']) ? $this->_extends : $options['extends'];
1144         $this->_extendsFile = empty($options['extends_location']) ? $this->_extendsFile : $options['extends_location'];
1145  
1146         $classname = $this->classname = $this->getClassNameFromTableName($this->table);
1147         
1148         $out = $this->_generateClassTable();
1149         //echo $out;
1150         eval('?>'.$out);
1151         return new $classname;
1152         
1153     }
1154     
1155      /**
1156     * fillTableSchema - set the database schema on the fly
1157     *
1158     * 
1159     * 
1160     * @param   string database name
1161     * @param   string  table   name of table to create schema info for
1162     *
1163     * @return   none | PEAR::error()
1164     * @access   public
1165     */
1166     function fillTableSchema($database,$table) 
1167     {
1168         global $_DB_DATAOBJECT;
1169          // a little bit of sanity testing.
1170         if ((false !== strpos($database,"'")) || (false !== strpos($database,";"))) {   
1171             return PEAR::raiseError("Error: Database name contains a quote or semi-colon", null, PEAR_ERROR_DIE);
1172         }
1173         
1174         $this->_database  = $database; 
1175         
1176         $this->_connect();
1177         $table = trim($table);
1178         
1179         // a little bit of sanity testing.
1180         if ((false !== strpos($table,"'")) || (false !== strpos($table,";"))) {   
1181             return PEAR::raiseError("Error: Table contains a quote or semi-colon", null, PEAR_ERROR_DIE);
1182         }
1183         $__DB= &$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'][$this->_database_dsn_md5];
1184         
1185         
1186         $options   = PEAR::getStaticProperty('DB_DataObject','options');
1187         $db_driver = empty($options['db_driver']) ? 'DB' : $options['db_driver'];
1188         $is_MDB2   = ($db_driver != 'DB') ? true : false;
1189         
1190         if (!$is_MDB2) {
1191             // try getting a list of schema tables first. (postgres)
1192             $__DB->expectError(DB_ERROR_UNSUPPORTED);
1193             $this->tables = $__DB->getListOf('schema.tables');
1194             $__DB->popExpect();
1195         } else {
1196             /**
1197              * set portability and some modules to fetch the informations
1198              */
1199             $__DB->setOption('portability', MDB2_PORTABILITY_ALL ^ MDB2_PORTABILITY_FIX_CASE);
1200             $__DB->loadModule('Manager');
1201             $__DB->loadModule('Reverse');
1202         }
1203         $quotedTable = !empty($options['quote_identifiers_tableinfo']) ? 
1204                 $__DB->quoteIdentifier($table) : $table;
1205           
1206         if (!$is_MDB2) {
1207             $defs =  $__DB->tableInfo($quotedTable);
1208         } else {
1209             $defs =  $__DB->reverse->tableInfo($quotedTable);
1210             foreach ($defs as $k => $v) {
1211                 if (!isset($defs[$k]['length'])) {
1212                     continue;
1213                 }
1214                 $defs[$k]['len'] = $defs[$k]['length'];
1215             }
1216         }
1217         
1218          
1219         
1220         
1221         if (PEAR::isError($defs)) {
1222             return $defs;
1223         }
1224         if (@$_DB_DATAOBJECT['CONFIG']['debug'] > 2) {
1225             $this->debug("getting def for $database/$table",'fillTable');
1226             $this->debug(print_r($defs,true),'defs');
1227         }
1228         // cast all definitions to objects - as we deal with that better.
1229         
1230             
1231         foreach($defs as $def) {
1232             if (is_array($def)) {
1233                 $this->_definitions[$table][] = (object) $def;
1234             }
1235         }
1236
1237         $this->table = trim($table);
1238         $ret = $this->_generateDefinitionsTable();
1239         
1240         $_DB_DATAOBJECT['INI'][$database][$table] = $ret['table'];
1241         $_DB_DATAOBJECT['INI'][$database][$table.'__keys'] = $ret['keys'];
1242         return false;
1243         
1244     }
1245     
1246     /**
1247     * Generate getter methods for class definition
1248     *
1249     * @param    string  $input  Existing class contents
1250     * @return   string
1251     * @access   public
1252     */
1253     function _generateGetters($input) 
1254     {
1255
1256         $options = &PEAR::getStaticProperty('DB_DataObject','options');
1257         $getters = '';
1258
1259         // only generate if option is set to true
1260         if  (empty($options['generate_getters'])) {
1261             return '';
1262         }
1263
1264         // remove auto-generated code from input to be able to check if the method exists outside of the auto-code
1265         $input = preg_replace('/(\n|\r\n)\s*###START_AUTOCODE(\n|\r\n).*(\n|\r\n)\s*###END_AUTOCODE(\n|\r\n)/s', '', $input);
1266
1267         $getters .= "\n\n";
1268         $defs     = $this->_definitions[$this->table];
1269
1270         // loop through properties and create getter methods
1271         foreach ($defs = $defs as $t) {
1272
1273             // build mehtod name
1274             $methodName = 'get' . $this->getMethodNameFromColumnName($t->name);
1275
1276             if (!strlen(trim($t->name)) || preg_match("/function[\s]+[&]?$methodName\(/i", $input)) {
1277                 continue;
1278             }
1279
1280             $getters .= "   /**\n";
1281             $getters .= "    * Getter for \${$t->name}\n";
1282             $getters .= "    *\n";
1283             $getters .= (stristr($t->flags, 'multiple_key')) ? "    * @return   object\n"
1284                                                              : "    * @return   {$t->type}\n";
1285             $getters .= "    * @access   public\n";
1286             $getters .= "    */\n";
1287             $getters .= (substr(phpversion(),0,1) > 4) ? '    public '
1288                                                        : '    ';
1289             $getters .= "function $methodName() {\n";
1290             $getters .= "        return \$this->{$t->name};\n";
1291             $getters .= "    }\n\n";
1292         }
1293    
1294
1295         return $getters;
1296     }
1297
1298
1299    /**
1300     * Generate setter methods for class definition
1301     *
1302     * @param    string  Existing class contents
1303     * @return   string
1304     * @access   public
1305     */
1306     function _generateSetters($input) 
1307     {
1308
1309         $options = &PEAR::getStaticProperty('DB_DataObject','options');
1310         $setters = '';
1311
1312         // only generate if option is set to true
1313         if  (empty($options['generate_setters'])) {
1314             return '';
1315         }
1316
1317         // remove auto-generated code from input to be able to check if the method exists outside of the auto-code
1318         $input = preg_replace('/(\n|\r\n)\s*###START_AUTOCODE(\n|\r\n).*(\n|\r\n)\s*###END_AUTOCODE(\n|\r\n)/s', '', $input);
1319
1320         $setters .= "\n";
1321         $defs     = $this->_definitions[$this->table];
1322
1323         // loop through properties and create setter methods
1324         foreach ($defs = $defs as $t) {
1325
1326             // build mehtod name
1327             $methodName = 'set' . $this->getMethodNameFromColumnName($t->name);
1328
1329             if (!strlen(trim($t->name)) || preg_match("/function[\s]+[&]?$methodName\(/i", $input)) {
1330                 continue;
1331             }
1332
1333             $setters .= "   /**\n";
1334             $setters .= "    * Setter for \${$t->name}\n";
1335             $setters .= "    *\n";
1336             $setters .= "    * @param    mixed   input value\n";
1337             $setters .= "    * @access   public\n";
1338             $setters .= "    */\n";
1339             $setters .= (substr(phpversion(),0,1) > 4) ? '    public '
1340                                                        : '    ';
1341             $setters .= "function $methodName(\$value) {\n";
1342             $setters .= "        \$this->{$t->name} = \$value;\n";
1343             $setters .= "    }\n\n";
1344         }
1345         
1346
1347         return $setters;
1348     }
1349     /**
1350     * Generate table Function - used when generator_no_ini is set.
1351     *
1352     * @param    array  table array.
1353     * @return   string
1354     * @access   public
1355     */
1356     function _generateTableFunction($def) 
1357     {
1358         $defines = explode(',','INT,STR,DATE,TIME,BOOL,TXT,BLOB,NOTNULL,MYSQLTIMESTAMP');
1359     
1360         $ret = "\n" .
1361                "    function table()\n" .
1362                "    {\n" .
1363                "         return array(\n";
1364         
1365         foreach($def as $k=>$v) {
1366             $str = '0';
1367             foreach($defines as $dn) {
1368                 if ($v & constant('DB_DATAOBJECT_' . $dn)) {
1369                     $str .= ' + DB_DATAOBJECT_' . $dn;
1370                 }
1371             }
1372             if (strlen($str) > 1) {
1373                 $str = substr($str,3); // strip the 0 +
1374             }
1375             // hopefully addslashes is good enough here!!!
1376             $ret .= '             \''.addslashes($k).'\' => ' . $str . ",\n";
1377         }
1378         return $ret . "         );\n" .
1379                       "    }\n";
1380             
1381     
1382     
1383     }
1384     /**
1385     * Generate keys Function - used generator_no_ini is set.
1386     *
1387     * @param    array  keys array.
1388     * @return   string
1389     * @access   public
1390     */
1391     function _generateKeysFunction($def) 
1392     {
1393          
1394         $ret = "\n" .
1395                "    function keys()\n" .
1396                "    {\n" .
1397                "         return array(";
1398             
1399         foreach($def as $k=>$type) {
1400             // hopefully addslashes is good enough here!!!
1401             $ret .= '\''.addslashes($k).'\', ';
1402         }
1403         $ret = preg_replace('#, $#', '', $ret);
1404         return $ret . ");\n" .
1405                       "    }\n";
1406             
1407     
1408     
1409     }
1410     /**
1411     * Generate sequenceKey Function - used generator_no_ini is set.
1412     *
1413     * @param    array  table and key definition.
1414     * @return   string
1415     * @access   public
1416     */
1417     function _generateSequenceKeyFunction($def)
1418     {
1419     
1420         //print_r($def);
1421         // DB_DataObject::debugLevel(5);
1422         global $_DB_DATAOBJECT;
1423         // print_r($def);
1424         
1425         
1426         $dbtype     = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['phptype'];
1427         $realkeys   = $def['keys'];
1428         $keys       = array_keys($realkeys);
1429         $usekey     = isset($keys[0]) ? $keys[0] : false;
1430         $table      = $def['table'];
1431         
1432          
1433         $seqname = false;
1434         
1435         
1436         
1437         
1438         $ar = array(false,false,false);
1439         if ($usekey !== false) {
1440             if (!empty($_DB_DATAOBJECT['CONFIG']['sequence_'.$this->__table])) {
1441                 $usekey = $_DB_DATAOBJECT['CONFIG']['sequence_'.$this->__table];
1442                 if (strpos($usekey,':') !== false) {
1443                     list($usekey,$seqname) = explode(':',$usekey);
1444                 }
1445             }  
1446         
1447             if (in_array($dbtype , array( 'mysql', 'mysqli', 'mssql', 'ifx')) && 
1448                 ($table[$usekey] & DB_DATAOBJECT_INT) && 
1449                 isset($realkeys[$usekey]) && ($realkeys[$usekey] == 'N')
1450                 ) {
1451                 // use native sequence keys.
1452                 $ar =  array($usekey,true,$seqname);
1453             } else {
1454                 // use generated sequence keys..
1455                 if ($table[$usekey] & DB_DATAOBJECT_INT) {
1456                     $ar = array($usekey,false,$seqname);
1457                 }
1458             }
1459         }
1460     
1461     
1462       
1463      
1464         $ret = "\n" .
1465                "    function sequenceKey() // keyname, use native, native name\n" .
1466                "    {\n" .
1467                "         return array(";
1468         foreach($ar as $v) {
1469             switch (gettype($v)) {
1470                 case 'boolean':
1471                     $ret .= ($v ? 'true' : 'false') . ', ';
1472                     break;
1473                     
1474                 case 'string':
1475                     $ret .= "'" . $v . "', ";
1476                     break;
1477                     
1478                 default:    // eak
1479                     $ret .= "null, ";
1480         
1481             }
1482         }
1483         $ret = preg_replace('#, $#', '', $ret);
1484         return $ret . ");\n" .
1485                       "    }\n";
1486         
1487     }
1488     /**
1489     * Generate defaults Function - used generator_add_defaults or generator_no_ini is set.
1490     * Only supports mysql and mysqli ... welcome ideas for more..
1491     * 
1492     *
1493     * @param    array  table and key definition.
1494     * @return   string
1495     * @access   public
1496     */
1497     function _generateDefaultsFunction($table,$defs)
1498     {
1499         $__DB= &$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'][$this->_database_dsn_md5];
1500         if (!in_array($__DB->phptype, array('mysql','mysqli'))) {
1501             return; // cant handle non-mysql introspection for defaults.
1502         }
1503         $options = PEAR::getStaticProperty('DB_DataObject','options'); 
1504         $db_driver = empty($options['db_driver']) ? 'DB' : $options['db_driver'];
1505         $method = $db_driver == 'DB' ? 'getAll' : 'queryAll'; 
1506         $res = $__DB->$method('DESCRIBE ' . $table,DB_FETCHMODE_ASSOC);
1507         $defaults = array();
1508         foreach($res as $ar) {
1509             // this is initially very dumb... -> and it may mess up..
1510             $type = $defs[$ar['Field']];
1511             
1512             switch (true) {
1513                 
1514                 case (is_null( $ar['Default'])):
1515                     $defaults[$ar['Field']]  = 'null';
1516                     break;
1517                 
1518                 case ($type & DB_DATAOBJECT_DATE): 
1519                 case ($type & DB_DATAOBJECT_TIME): 
1520                 case ($type & DB_DATAOBJECT_MYSQLTIMESTAMP): // not supported yet..
1521                     break;
1522                     
1523                 case ($type & DB_DATAOBJECT_BOOL): 
1524                     $defaults[$ar['Field']] = (int)(boolean) $ar['Default'];
1525                     break;
1526                     
1527                 
1528                 case ($type & DB_DATAOBJECT_STR): 
1529                     $defaults[$ar['Field']] =  "'" . addslashes($ar['Default']) . "'";
1530                     break;
1531                 
1532                  
1533                 default:    // hopefully eveything else...  - numbers etc.
1534                     if (!strlen($ar['Default'])) {
1535                         continue;
1536                     }
1537                     if (is_numeric($ar['Default'])) {
1538                         $defaults[$ar['Field']] =   $ar['Default'];
1539                     }
1540                     break;
1541             
1542             }
1543             //var_dump(array($ar['Field'], $ar['Default'], $defaults[$ar['Field']]));
1544         }
1545         if (empty($defaults)) {
1546             return;
1547         }
1548         
1549         $ret = "\n" .
1550                "    function defaults() // column default values \n" .
1551                "    {\n" .
1552                "         return array(\n";
1553         foreach($defaults as $k=>$v) {
1554             $ret .= '             \''.addslashes($k).'\' => ' . $v . ",\n";
1555         }
1556         return $ret . "         );\n" .
1557                       "    }\n";
1558          
1559      
1560     
1561     
1562     }
1563     
1564     
1565      
1566     
1567     
1568 }