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