]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - extlib/DB/DataObject/Generator.php
Merge remote-tracking branch 'upstream/master'
[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 327926 2012-10-08 02:42:09Z 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             foreach ($defs as $k => $v) {
1353                 if (!isset($defs[$k]['length'])) {
1354                     continue;
1355                 }
1356                 $defs[$k]['len'] = $defs[$k]['length'];
1357             }
1358         }
1359         
1360          
1361         
1362         
1363         if (PEAR::isError($defs)) {
1364             return $defs;
1365         }
1366         if (@$_DB_DATAOBJECT['CONFIG']['debug'] > 2) {
1367             $this->debug("getting def for $database/$table",'fillTable');
1368             $this->debug(print_r($defs,true),'defs');
1369         }
1370         // cast all definitions to objects - as we deal with that better.
1371         
1372             
1373         foreach($defs as $def) {
1374             if (is_array($def)) {
1375                 $this->_definitions[$table][] = (object) $def;
1376             }
1377         }
1378
1379         $this->table = trim($table);
1380         $ret = $this->_generateDefinitionsTable();
1381         
1382         $_DB_DATAOBJECT['INI'][$database][$table] = $ret['table'];
1383         $_DB_DATAOBJECT['INI'][$database][$table.'__keys'] = $ret['keys'];
1384         return false;
1385         
1386     }
1387     
1388     /**
1389     * Generate getter methods for class definition
1390     *
1391     * @param    string  $input  Existing class contents
1392     * @return   string
1393     * @access   public
1394     */
1395     function _generateGetters($input) 
1396     {
1397
1398         $options = &PEAR::getStaticProperty('DB_DataObject','options');
1399         $getters = '';
1400
1401         // only generate if option is set to true
1402         if  (empty($options['generate_getters'])) {
1403             return '';
1404         }
1405
1406         // remove auto-generated code from input to be able to check if the method exists outside of the auto-code
1407         $input = preg_replace('/(\n|\r\n)\s*###START_AUTOCODE(\n|\r\n).*(\n|\r\n)\s*###END_AUTOCODE(\n|\r\n)/s', '', $input);
1408
1409         $getters .= "\n\n";
1410         $defs     = $this->_definitions[$this->table];
1411
1412         // loop through properties and create getter methods
1413         foreach ($defs = $defs as $t) {
1414
1415             // build mehtod name
1416             $methodName = 'get' . $this->getMethodNameFromColumnName($t->name);
1417
1418             if (!strlen(trim($t->name)) || preg_match("/function[\s]+[&]?$methodName\(/i", $input)) {
1419                 continue;
1420             }
1421
1422             $getters .= "   /**\n";
1423             $getters .= "    * Getter for \${$t->name}\n";
1424             $getters .= "    *\n";
1425             $getters .= (stristr($t->flags, 'multiple_key')) ? "    * @return   object\n"
1426                                                              : "    * @return   {$t->type}\n";
1427             $getters .= "    * @access   public\n";
1428             $getters .= "    */\n";
1429             $getters .= (substr(phpversion(),0,1) > 4) ? '    public '
1430                                                        : '    ';
1431             $getters .= "function $methodName() {\n";
1432             $getters .= "        return \$this->{$t->name};\n";
1433             $getters .= "    }\n\n";
1434         }
1435    
1436
1437         return $getters;
1438     }
1439     /**
1440     * Generate link setter/getter methods for class definition
1441     *
1442     * @param    string  Existing class contents
1443     * @return   string
1444     * @access   public
1445     */
1446     function _generateLinkMethods($input) 
1447     {
1448
1449         $options = &PEAR::getStaticProperty('DB_DataObject','options');
1450         $setters = '';
1451
1452         // only generate if option is set to true
1453         
1454         // generate_link_methods true::
1455         
1456         
1457         if  (empty($options['generate_link_methods'])) {
1458             //echo "skip lm? - not set";
1459             return '';
1460         }
1461         
1462         if (empty($this->_fkeys)) {
1463             // echo "skip lm? - fkyes empty";
1464             return '';
1465         }
1466         if (empty($this->_fkeys[$this->table])) {
1467             //echo "skip lm? - no fkeys for {$this->table}";
1468             return '';
1469         }
1470             
1471         // remove auto-generated code from input to be able to check if the method exists outside of the auto-code
1472         $input = preg_replace('/(\n|\r\n)\s*###START_AUTOCODE(\n|\r\n).*(\n|\r\n)\s*###END_AUTOCODE(\n|\r\n)/s', '', $input);
1473
1474         $setters .= "\n";
1475         $defs     = $this->_fkeys[$this->table];
1476          
1477         
1478         // $fk[$this->table][$tref[1]] = $tref[2] . ":" . $tref[3];
1479
1480         // loop through properties and create setter methods
1481         foreach ($defs as $k => $info) {
1482
1483             // build mehtod name
1484             $methodName =  is_callable($options['generate_link_methods']) ?
1485                     $options['generate_link_methods']($k) : $k;
1486
1487             if (!strlen(trim($k)) || preg_match("/function[\s]+[&]?$methodName\(/i", $input)) {
1488                 continue;
1489             }
1490
1491             $setters .= "   /**\n";
1492             $setters .= "    * Getter / Setter for \${$k}\n";
1493             $setters .= "    *\n";
1494             $setters .= "    * @param    mixed   (optional) value to assign\n";
1495             $setters .= "    * @access   public\n";
1496             
1497             $setters .= "    */\n";
1498             $setters .= (substr(phpversion(),0,1) > 4) ? '    public '
1499                                                        : '    ';
1500             $setters .= "function $methodName() {\n";
1501             $setters .= "        return \$this->link('$k', func_get_args());\n";
1502             $setters .= "    }\n\n";
1503         }
1504          
1505         return $setters;
1506     }
1507
1508    /**
1509     * Generate setter methods for class definition
1510     *
1511     * @param    string  Existing class contents
1512     * @return   string
1513     * @access   public
1514     */
1515     function _generateSetters($input) 
1516     {
1517
1518         $options = &PEAR::getStaticProperty('DB_DataObject','options');
1519         $setters = '';
1520
1521         // only generate if option is set to true
1522         if  (empty($options['generate_setters'])) {
1523             return '';
1524         }
1525
1526         // remove auto-generated code from input to be able to check if the method exists outside of the auto-code
1527         $input = preg_replace('/(\n|\r\n)\s*###START_AUTOCODE(\n|\r\n).*(\n|\r\n)\s*###END_AUTOCODE(\n|\r\n)/s', '', $input);
1528
1529         $setters .= "\n";
1530         $defs     = $this->_definitions[$this->table];
1531
1532         // loop through properties and create setter methods
1533         foreach ($defs = $defs as $t) {
1534
1535             // build mehtod name
1536             $methodName = 'set' . $this->getMethodNameFromColumnName($t->name);
1537
1538             if (!strlen(trim($t->name)) || preg_match("/function[\s]+[&]?$methodName\(/i", $input)) {
1539                 continue;
1540             }
1541
1542             $setters .= "   /**\n";
1543             $setters .= "    * Setter for \${$t->name}\n";
1544             $setters .= "    *\n";
1545             $setters .= "    * @param    mixed   input value\n";
1546             $setters .= "    * @access   public\n";
1547             $setters .= "    */\n";
1548             $setters .= (substr(phpversion(),0,1) > 4) ? '    public '
1549                                                        : '    ';
1550             $setters .= "function $methodName(\$value) {\n";
1551             $setters .= "        \$this->{$t->name} = \$value;\n";
1552             $setters .= "    }\n\n";
1553         }
1554         
1555
1556         return $setters;
1557     }
1558     /**
1559     * Generate table Function - used when generator_no_ini is set.
1560     *
1561     * @param    array  table array.
1562     * @return   string
1563     * @access   public
1564     */
1565     function _generateTableFunction($def) 
1566     {
1567         $defines = explode(',','INT,STR,DATE,TIME,BOOL,TXT,BLOB,NOTNULL,MYSQLTIMESTAMP');
1568     
1569         $ret = "\n" .
1570                "    function table()\n" .
1571                "    {\n" .
1572                "         return array(\n";
1573         
1574         foreach($def as $k=>$v) {
1575             $str = '0';
1576             foreach($defines as $dn) {
1577                 if ($v & constant('DB_DATAOBJECT_' . $dn)) {
1578                     $str .= ' + DB_DATAOBJECT_' . $dn;
1579                 }
1580             }
1581             if (strlen($str) > 1) {
1582                 $str = substr($str,3); // strip the 0 +
1583             }
1584             // hopefully addslashes is good enough here!!!
1585             $ret .= '             \''.addslashes($k).'\' => ' . $str . ",\n";
1586         }
1587         return $ret . "         );\n" .
1588                       "    }\n";
1589             
1590     
1591     
1592     }
1593     /**
1594     * Generate keys Function - used generator_no_ini is set.
1595     *
1596     * @param    array  keys array.
1597     * @return   string
1598     * @access   public
1599     */
1600     function _generateKeysFunction($def) 
1601     {
1602          
1603         $ret = "\n" .
1604                "    function keys()\n" .
1605                "    {\n" .
1606                "         return array(";
1607             
1608         foreach($def as $k=>$type) {
1609             // hopefully addslashes is good enough here!!!
1610             $ret .= '\''.addslashes($k).'\', ';
1611         }
1612         $ret = preg_replace('#, $#', '', $ret);
1613         return $ret . ");\n" .
1614                       "    }\n";
1615             
1616     
1617     
1618     }
1619     /**
1620     * Generate sequenceKey Function - used generator_no_ini is set.
1621     *
1622     * @param    array  table and key definition.
1623     * @return   string
1624     * @access   public
1625     */
1626     function _generateSequenceKeyFunction($def)
1627     {
1628     
1629         //print_r($def);
1630         // DB_DataObject::debugLevel(5);
1631         global $_DB_DATAOBJECT;
1632         // print_r($def);
1633         
1634         
1635         $dbtype     = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['phptype'];
1636         $realkeys   = $def['keys'];
1637         $keys       = array_keys($realkeys);
1638         $usekey     = isset($keys[0]) ? $keys[0] : false;
1639         $table      = $def['table'];
1640         
1641          
1642         $seqname = false;
1643         
1644         
1645         
1646         
1647         $ar = array(false,false,false);
1648         if ($usekey !== false) {
1649             if (!empty($_DB_DATAOBJECT['CONFIG']['sequence_'.$this->__table])) {
1650                 $usekey = $_DB_DATAOBJECT['CONFIG']['sequence_'.$this->__table];
1651                 if (strpos($usekey,':') !== false) {
1652                     list($usekey,$seqname) = explode(':',$usekey);
1653                 }
1654             }  
1655         
1656             if (in_array($dbtype , array( 'mysql', 'mysqli', 'mssql', 'ifx')) && 
1657                 ($table[$usekey] & DB_DATAOBJECT_INT) && 
1658                 isset($realkeys[$usekey]) && ($realkeys[$usekey] == 'N')
1659                 ) {
1660                 // use native sequence keys.
1661                 $ar =  array($usekey,true,$seqname);
1662             } else {
1663                 // use generated sequence keys..
1664                 if ($table[$usekey] & DB_DATAOBJECT_INT) {
1665                     $ar = array($usekey,false,$seqname);
1666                 }
1667             }
1668         }
1669     
1670     
1671       
1672      
1673         $ret = "\n" .
1674                "    function sequenceKey() // keyname, use native, native name\n" .
1675                "    {\n" .
1676                "         return array(";
1677         foreach($ar as $v) {
1678             switch (gettype($v)) {
1679                 case 'boolean':
1680                     $ret .= ($v ? 'true' : 'false') . ', ';
1681                     break;
1682                     
1683                 case 'string':
1684                     $ret .= "'" . $v . "', ";
1685                     break;
1686                     
1687                 default:    // eak
1688                     $ret .= "null, ";
1689         
1690             }
1691         }
1692         $ret = preg_replace('#, $#', '', $ret);
1693         return $ret . ");\n" .
1694                       "    }\n";
1695         
1696     }
1697     /**
1698     * Generate defaults Function - used generator_add_defaults or generator_no_ini is set.
1699     * Only supports mysql and mysqli ... welcome ideas for more..
1700     * 
1701     *
1702     * @param    array  table and key definition.
1703     * @return   string
1704     * @access   public
1705     */
1706     function _generateDefaultsFunction($table,$defs)
1707     {
1708         $__DB= &$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'][$this->_database_dsn_md5];
1709         if (!in_array($__DB->phptype, array('mysql','mysqli'))) {
1710             return; // cant handle non-mysql introspection for defaults.
1711         }
1712         $options = PEAR::getStaticProperty('DB_DataObject','options'); 
1713         $db_driver = empty($options['db_driver']) ? 'DB' : $options['db_driver'];
1714         $method = $db_driver == 'DB' ? 'getAll' : 'queryAll'; 
1715         $res = $__DB->$method('DESCRIBE ' . $table,DB_FETCHMODE_ASSOC);
1716         $defaults = array();
1717         foreach($res as $ar) {
1718             // this is initially very dumb... -> and it may mess up..
1719             $type = $defs[$ar['Field']];
1720             
1721             switch (true) {
1722                 
1723                 case (is_null( $ar['Default'])):
1724                     $defaults[$ar['Field']]  = 'null';
1725                     break;
1726                 
1727                 case ($type & DB_DATAOBJECT_DATE): 
1728                 case ($type & DB_DATAOBJECT_TIME): 
1729                 case ($type & DB_DATAOBJECT_MYSQLTIMESTAMP): // not supported yet..
1730                     break;
1731                     
1732                 case ($type & DB_DATAOBJECT_BOOL): 
1733                     $defaults[$ar['Field']] = (int)(boolean) $ar['Default'];
1734                     break;
1735                     
1736                 
1737                 case ($type & DB_DATAOBJECT_STR): 
1738                     $defaults[$ar['Field']] =  "'" . addslashes($ar['Default']) . "'";
1739                     break;
1740                 
1741                  
1742                 default:    // hopefully eveything else...  - numbers etc.
1743                     if (!strlen($ar['Default'])) {
1744                         continue;
1745                     }
1746                     if (is_numeric($ar['Default'])) {
1747                         $defaults[$ar['Field']] =   $ar['Default'];
1748                     }
1749                     break;
1750             
1751             }
1752             //var_dump(array($ar['Field'], $ar['Default'], $defaults[$ar['Field']]));
1753         }
1754         if (empty($defaults)) {
1755             return;
1756         }
1757         
1758         $ret = "\n" .
1759                "    function defaults() // column default values \n" .
1760                "    {\n" .
1761                "         return array(\n";
1762         foreach($defaults as $k=>$v) {
1763             $ret .= '             \''.addslashes($k).'\' => ' . $v . ",\n";
1764         }
1765         return $ret . "         );\n" .
1766                       "    }\n";
1767          
1768      
1769     
1770     
1771     }
1772     
1773     
1774      
1775     
1776     
1777 }