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