]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/WikiDB/adodb/adodb-xmlschema.inc.php
Upgrade adodb
[SourceForge/phpwiki.git] / lib / WikiDB / adodb / adodb-xmlschema.inc.php
1 <?php
2 // Copyright (c) 2004 ars Cognita Inc., all rights reserved
3 /* ******************************************************************************
4     Released under both BSD license and Lesser GPL library license. 
5         Whenever there is any discrepancy between the two licenses, 
6         the BSD license will take precedence. 
7 *******************************************************************************/
8 /**
9  * xmlschema is a class that allows the user to quickly and easily
10  * build a database on any ADOdb-supported platform using a simple
11  * XML schema.
12  *
13  * Last Editor: $Author: jlim $
14  * @author Richard Tango-Lowy & Dan Cech
15  * @version $Revision: 1.12 $
16  *
17  * @package axmls
18  * @tutorial getting_started.pkg
19  */
20  
21 function _file_get_contents($file) 
22 {
23         if (function_exists('file_get_contents')) return file_get_contents($file);
24         
25         $f = fopen($file,'r');
26         if (!$f) return '';
27         $t = '';
28         
29         while ($s = fread($f,100000)) $t .= $s;
30         fclose($f);
31         return $t;
32 }
33
34
35 /**
36 * Debug on or off
37 */
38 if( !defined( 'XMLS_DEBUG' ) ) {
39         define( 'XMLS_DEBUG', FALSE );
40 }
41
42 /**
43 * Default prefix key
44 */
45 if( !defined( 'XMLS_PREFIX' ) ) {
46         define( 'XMLS_PREFIX', '%%P' );
47 }
48
49 /**
50 * Maximum length allowed for object prefix
51 */
52 if( !defined( 'XMLS_PREFIX_MAXLEN' ) ) {
53         define( 'XMLS_PREFIX_MAXLEN', 10 );
54 }
55
56 /**
57 * Execute SQL inline as it is generated
58 */
59 if( !defined( 'XMLS_EXECUTE_INLINE' ) ) {
60         define( 'XMLS_EXECUTE_INLINE', FALSE );
61 }
62
63 /**
64 * Continue SQL Execution if an error occurs?
65 */
66 if( !defined( 'XMLS_CONTINUE_ON_ERROR' ) ) {
67         define( 'XMLS_CONTINUE_ON_ERROR', FALSE );
68 }
69
70 /**
71 * Current Schema Version
72 */
73 if( !defined( 'XMLS_SCHEMA_VERSION' ) ) {
74         define( 'XMLS_SCHEMA_VERSION', '0.2' );
75 }
76
77 /**
78 * Default Schema Version.  Used for Schemas without an explicit version set.
79 */
80 if( !defined( 'XMLS_DEFAULT_SCHEMA_VERSION' ) ) {
81         define( 'XMLS_DEFAULT_SCHEMA_VERSION', '0.1' );
82 }
83
84 /**
85 * Default Schema Version.  Used for Schemas without an explicit version set.
86 */
87 if( !defined( 'XMLS_DEFAULT_UPGRADE_METHOD' ) ) {
88         define( 'XMLS_DEFAULT_UPGRADE_METHOD', 'ALTER' );
89 }
90
91 /**
92 * Include the main ADODB library
93 */
94 if( !defined( '_ADODB_LAYER' ) ) {
95         require( 'adodb.inc.php' );
96         require( 'adodb-datadict.inc.php' );
97 }
98
99 /**
100 * Abstract DB Object. This class provides basic methods for database objects, such
101 * as tables and indexes.
102 *
103 * @package axmls
104 * @access private
105 */
106 class dbObject {
107         
108         /**
109         * var object Parent
110         */
111         var $parent;
112         
113         /**
114         * var string current element
115         */
116         var $currentElement;
117         
118         /**
119         * NOP
120         */
121         function dbObject( &$parent, $attributes = NULL ) {
122                 $this->parent = $parent;
123         }
124         
125         /**
126         * XML Callback to process start elements
127         *
128         * @access private
129         */
130         function _tag_open( &$parser, $tag, $attributes ) {
131                 
132         }
133         
134         /**
135         * XML Callback to process CDATA elements
136         *
137         * @access private
138         */
139         function _tag_cdata( &$parser, $cdata ) {
140                 
141         }
142         
143         /**
144         * XML Callback to process end elements
145         *
146         * @access private
147         */
148         function _tag_close( &$parser, $tag ) {
149                 
150         }
151         
152         function create(&$xmls) {
153                 return array();
154         }
155         
156         /**
157         * Destroys the object
158         */
159         function destroy() {
160                 unset( $this );
161         }
162         
163         /**
164         * Checks whether the specified RDBMS is supported by the current
165         * database object or its ranking ancestor.
166         *
167         * @param string $platform RDBMS platform name (from ADODB platform list).
168         * @return boolean TRUE if RDBMS is supported; otherwise returns FALSE.
169         */
170         function supportedPlatform( $platform = NULL ) {
171                 return is_object( $this->parent ) ? $this->parent->supportedPlatform( $platform ) : TRUE;
172         }
173         
174         /**
175         * Returns the prefix set by the ranking ancestor of the database object.
176         *
177         * @param string $name Prefix string.
178         * @return string Prefix.
179         */
180         function prefix( $name = '' ) {
181                 return is_object( $this->parent ) ? $this->parent->prefix( $name ) : $name;
182         }
183         
184         /**
185         * Extracts a field ID from the specified field.
186         *
187         * @param string $field Field.
188         * @return string Field ID.
189         */
190         function FieldID( $field ) {
191                 return strtoupper( preg_replace( '/^`(.+)`$/', '$1', $field ) );
192         }
193 }
194
195 /**
196 * Creates a table object in ADOdb's datadict format
197 *
198 * This class stores information about a database table. As charactaristics
199 * of the table are loaded from the external source, methods and properties
200 * of this class are used to build up the table description in ADOdb's
201 * datadict format.
202 *
203 * @package axmls
204 * @access private
205 */
206 class dbTable extends dbObject {
207         
208         /**
209         * @var string Table name
210         */
211         var $name;
212         
213         /**
214         * @var array Field specifier: Meta-information about each field
215         */
216         var $fields = array();
217         
218         /**
219         * @var array List of table indexes.
220         */
221         var $indexes = array();
222         
223         /**
224         * @var array Table options: Table-level options
225         */
226         var $opts = array();
227         
228         /**
229         * @var string Field index: Keeps track of which field is currently being processed
230         */
231         var $current_field;
232         
233         /**
234         * @var boolean Mark table for destruction
235         * @access private
236         */
237         var $drop_table;
238         
239         /**
240         * @var boolean Mark field for destruction (not yet implemented)
241         * @access private
242         */
243         var $drop_field = array();
244         
245         /**
246         * Iniitializes a new table object.
247         *
248         * @param string $prefix DB Object prefix
249         * @param array $attributes Array of table attributes.
250         */
251         function dbTable( &$parent, $attributes = NULL ) {
252                 $this->parent = $parent;
253                 $this->name = $this->prefix($attributes['NAME']);
254         }
255         
256         /**
257         * XML Callback to process start elements. Elements currently 
258         * processed are: INDEX, DROP, FIELD, KEY, NOTNULL, AUTOINCREMENT & DEFAULT. 
259         *
260         * @access private
261         */
262         function _tag_open( &$parser, $tag, $attributes ) {
263                 $this->currentElement = strtoupper( $tag );
264                 
265                 switch( $this->currentElement ) {
266                         case 'INDEX':
267                                 if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) {
268                                         xml_set_object( $parser, $this->addIndex( $attributes ) );
269                                 }
270                                 break;
271                         case 'DATA':
272                                 if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) {
273                                         xml_set_object( $parser, $this->addData( $attributes ) );
274                                 }
275                                 break;
276                         case 'DROP':
277                                 $this->drop();
278                                 break;
279                         case 'FIELD':
280                                 // Add a field
281                                 $fieldName = $attributes['NAME'];
282                                 $fieldType = $attributes['TYPE'];
283                                 $fieldSize = isset( $attributes['SIZE'] ) ? $attributes['SIZE'] : NULL;
284                                 $fieldOpts = isset( $attributes['OPTS'] ) ? $attributes['OPTS'] : NULL;
285                                 
286                                 $this->addField( $fieldName, $fieldType, $fieldSize, $fieldOpts );
287                                 break;
288                         case 'KEY':
289                         case 'NOTNULL':
290                         case 'AUTOINCREMENT':
291                                 // Add a field option
292                                 $this->addFieldOpt( $this->current_field, $this->currentElement );
293                                 break;
294                         case 'DEFAULT':
295                                 // Add a field option to the table object
296                                 
297                                 // Work around ADOdb datadict issue that misinterprets empty strings.
298                                 if( $attributes['VALUE'] == '' ) {
299                                         $attributes['VALUE'] = " '' ";
300                                 }
301                                 
302                                 $this->addFieldOpt( $this->current_field, $this->currentElement, $attributes['VALUE'] );
303                                 break;
304                         case 'DEFDATE':
305                         case 'DEFTIMESTAMP':
306                                 // Add a field option to the table object
307                                 $this->addFieldOpt( $this->current_field, $this->currentElement );
308                                 break;
309                         default:
310                                 // print_r( array( $tag, $attributes ) );
311                 }
312         }
313         
314         /**
315         * XML Callback to process CDATA elements
316         *
317         * @access private
318         */
319         function _tag_cdata( &$parser, $cdata ) {
320                 switch( $this->currentElement ) {
321                         // Table constraint
322                         case 'CONSTRAINT':
323                                 if( isset( $this->current_field ) ) {
324                                         $this->addFieldOpt( $this->current_field, $this->currentElement, $cdata );
325                                 } else {
326                                         $this->addTableOpt( $cdata );
327                                 }
328                                 break;
329                         // Table option
330                         case 'OPT':
331                                 $this->addTableOpt( $cdata );
332                                 break;
333                         default:
334                                 
335                 }
336         }
337         
338         /**
339         * XML Callback to process end elements
340         *
341         * @access private
342         */
343         function _tag_close( &$parser, $tag ) {
344                 $this->currentElement = '';
345                 
346                 switch( strtoupper( $tag ) ) {
347                         case 'TABLE':
348                                 $this->parent->addSQL( $this->create( $this->parent ) );
349                                 xml_set_object( $parser, $this->parent );
350                                 $this->destroy();
351                                 break;
352                         case 'FIELD':
353                                 unset($this->current_field);
354                                 break;
355
356                 }
357         }
358         
359         /**
360         * Adds an index to a table object
361         *
362         * @param array $attributes Index attributes
363         * @return object dbIndex object
364         */
365         function addIndex( $attributes ) {
366                 $name = strtoupper( $attributes['NAME'] );
367                 $this->indexes[$name] = new dbIndex( $this, $attributes );
368                 return $this->indexes[$name];
369         }
370         
371         /**
372         * Adds data to a table object
373         *
374         * @param array $attributes Data attributes
375         * @return object dbData object
376         */
377         function addData( $attributes ) {
378                 if( !isset( $this->data ) ) {
379                         $this->data = new dbData( $this, $attributes );
380                 }
381                 return $this->data;
382         }
383         
384         /**
385         * Adds a field to a table object
386         *
387         * $name is the name of the table to which the field should be added. 
388         * $type is an ADODB datadict field type. The following field types
389         * are supported as of ADODB 3.40:
390         *       - C:  varchar
391         *       - X:  CLOB (character large object) or largest varchar size
392         *          if CLOB is not supported
393         *       - C2: Multibyte varchar
394         *       - X2: Multibyte CLOB
395         *       - B:  BLOB (binary large object)
396         *       - D:  Date (some databases do not support this, and we return a datetime type)
397         *       - T:  Datetime or Timestamp
398         *       - L:  Integer field suitable for storing booleans (0 or 1)
399         *       - I:  Integer (mapped to I4)
400         *       - I1: 1-byte integer
401         *       - I2: 2-byte integer
402         *       - I4: 4-byte integer
403         *       - I8: 8-byte integer
404         *       - F:  Floating point number
405         *       - N:  Numeric or decimal number
406         *
407         * @param string $name Name of the table to which the field will be added.
408         * @param string $type   ADODB datadict field type.
409         * @param string $size   Field size
410         * @param array $opts    Field options array
411         * @return array Field specifier array
412         */
413         function addField( $name, $type, $size = NULL, $opts = NULL ) {
414                 $field_id = $this->FieldID( $name );
415                 
416                 // Set the field index so we know where we are
417                 $this->current_field = $field_id;
418                 
419                 // Set the field name (required)
420                 $this->fields[$field_id]['NAME'] = $name;
421                 
422                 // Set the field type (required)
423                 $this->fields[$field_id]['TYPE'] = $type;
424                 
425                 // Set the field size (optional)
426                 if( isset( $size ) ) {
427                         $this->fields[$field_id]['SIZE'] = $size;
428                 }
429                 
430                 // Set the field options
431                 if( isset( $opts ) ) {
432                         $this->fields[$field_id]['OPTS'][] = $opts;
433                 }
434         }
435         
436         /**
437         * Adds a field option to the current field specifier
438         *
439         * This method adds a field option allowed by the ADOdb datadict 
440         * and appends it to the given field.
441         *
442         * @param string $field  Field name
443         * @param string $opt ADOdb field option
444         * @param mixed $value Field option value
445         * @return array Field specifier array
446         */
447         function addFieldOpt( $field, $opt, $value = NULL ) {
448                 if( !isset( $value ) ) {
449                         $this->fields[$this->FieldID( $field )]['OPTS'][] = $opt;
450                 // Add the option and value
451                 } else {
452                         $this->fields[$this->FieldID( $field )]['OPTS'][] = array( $opt => $value );
453                 }
454         }
455         
456         /**
457         * Adds an option to the table
458         *
459         * This method takes a comma-separated list of table-level options
460         * and appends them to the table object.
461         *
462         * @param string $opt Table option
463         * @return array Options
464         */
465         function addTableOpt( $opt ) {
466                 if(isset($this->currentPlatform)) {
467                         $this->opts[$this->parent->db->databaseType] = $opt;
468                 }
469                 return $this->opts;
470         }
471
472         
473         /**
474         * Generates the SQL that will create the table in the database
475         *
476         * @param object $xmls adoSchema object
477         * @return array Array containing table creation SQL
478         */
479         function create( &$xmls ) {
480                 $sql = array();
481                 
482                 // drop any existing indexes
483                 if( is_array( $legacy_indexes = $xmls->dict->MetaIndexes( $this->name ) ) ) {
484                         foreach( $legacy_indexes as $index => $index_details ) {
485                                 $sql[] = $xmls->dict->DropIndexSQL( $index, $this->name );
486                         }
487                 }
488                 
489                 // remove fields to be dropped from table object
490                 foreach( $this->drop_field as $field ) {
491                         unset( $this->fields[$field] );
492                 }
493                 
494                 // if table exists
495                 if( is_array( $legacy_fields = $xmls->dict->MetaColumns( $this->name ) ) ) {
496                         // drop table
497                         if( $this->drop_table ) {
498                                 $sql[] = $xmls->dict->DropTableSQL( $this->name );
499                                 
500                                 return $sql;
501                         }
502                         
503                         // drop any existing fields not in schema
504                         foreach( $legacy_fields as $field_id => $field ) {
505                                 if( !isset( $this->fields[$field_id] ) ) {
506                                         $sql[] = $xmls->dict->DropColumnSQL( $this->name, '`'.$field->name.'`' );
507                                 }
508                         }
509                 // if table doesn't exist
510                 } else {
511                         if( $this->drop_table ) {
512                                 return $sql;
513                         }
514                         
515                         $legacy_fields = array();
516                 }
517                 
518                 // Loop through the field specifier array, building the associative array for the field options
519                 $fldarray = array();
520                 
521                 foreach( $this->fields as $field_id => $finfo ) {
522                         // Set an empty size if it isn't supplied
523                         if( !isset( $finfo['SIZE'] ) ) {
524                                 $finfo['SIZE'] = '';
525                         }
526                         
527                         // Initialize the field array with the type and size
528                         $fldarray[$field_id] = array(
529                                 'NAME' => $finfo['NAME'],
530                                 'TYPE' => $finfo['TYPE'],
531                                 'SIZE' => $finfo['SIZE']
532                         );
533                         
534                         // Loop through the options array and add the field options. 
535                         if( isset( $finfo['OPTS'] ) ) {
536                                 foreach( $finfo['OPTS'] as $opt ) {
537                                         // Option has an argument.
538                                         if( is_array( $opt ) ) {
539                                                 $key = key( $opt );
540                                                 $value = $opt[key( $opt )];
541                                                 @$fldarray[$field_id][$key] .= $value;
542                                         // Option doesn't have arguments
543                                         } else {
544                                                 $fldarray[$field_id][$opt] = $opt;
545                                         }
546                                 }
547                         }
548                 }
549                 
550                 if( empty( $legacy_fields ) ) {
551                         // Create the new table
552                         $sql[] = $xmls->dict->CreateTableSQL( $this->name, $fldarray, $this->opts );
553                         logMsg( end( $sql ), 'Generated CreateTableSQL' );
554                 } else {
555                         // Upgrade an existing table
556                         logMsg( "Upgrading {$this->name} using '{$xmls->upgrade}'" );
557                         switch( $xmls->upgrade ) {
558                                 // Use ChangeTableSQL
559                                 case 'ALTER':
560                                         logMsg( 'Generated ChangeTableSQL (ALTERing table)' );
561                                         $sql[] = $xmls->dict->ChangeTableSQL( $this->name, $fldarray, $this->opts );
562                                         break;
563                                 case 'REPLACE':
564                                         logMsg( 'Doing upgrade REPLACE (testing)' );
565                                         $sql[] = $xmls->dict->DropTableSQL( $this->name );
566                                         $sql[] = $xmls->dict->CreateTableSQL( $this->name, $fldarray, $this->opts );
567                                         break;
568                                 // ignore table
569                                 default:
570                                         return array();
571                         }
572                 }
573                 
574                 foreach( $this->indexes as $index ) {
575                         $sql[] = $index->create( $xmls );
576                 }
577                 
578                 if( isset( $this->data ) ) {
579                         $sql[] = $this->data->create( $xmls );
580                 }
581                 
582                 return $sql;
583         }
584         
585         /**
586         * Marks a field or table for destruction
587         */
588         function drop() {
589                 if( isset( $this->current_field ) ) {
590                         // Drop the current field
591                         logMsg( "Dropping field '{$this->current_field}' from table '{$this->name}'" );
592                         // $this->drop_field[$this->current_field] = $xmls->dict->DropColumnSQL( $this->name, $this->current_field );
593                         $this->drop_field[$this->current_field] = $this->current_field;
594                 } else {
595                         // Drop the current table
596                         logMsg( "Dropping table '{$this->name}'" );
597                         // $this->drop_table = $xmls->dict->DropTableSQL( $this->name );
598                         $this->drop_table = TRUE;
599                 }
600         }
601 }
602
603 /**
604 * Creates an index object in ADOdb's datadict format
605 *
606 * This class stores information about a database index. As charactaristics
607 * of the index are loaded from the external source, methods and properties
608 * of this class are used to build up the index description in ADOdb's
609 * datadict format.
610 *
611 * @package axmls
612 * @access private
613 */
614 class dbIndex extends dbObject {
615         
616         /**
617         * @var string   Index name
618         */
619         var $name;
620         
621         /**
622         * @var array    Index options: Index-level options
623         */
624         var $opts = array();
625         
626         /**
627         * @var array    Indexed fields: Table columns included in this index
628         */
629         var $columns = array();
630         
631         /**
632         * @var boolean Mark index for destruction
633         * @access private
634         */
635         var $drop = FALSE;
636         
637         /**
638         * Initializes the new dbIndex object.
639         *
640         * @param object $parent Parent object
641         * @param array $attributes Attributes
642         *
643         * @internal
644         */
645         function dbIndex( &$parent, $attributes = NULL ) {
646                 $this->parent = $parent;
647                 
648                 $this->name = $this->prefix ($attributes['NAME']);
649         }
650         
651         /**
652         * XML Callback to process start elements
653         *
654         * Processes XML opening tags. 
655         * Elements currently processed are: DROP, CLUSTERED, BITMAP, UNIQUE, FULLTEXT & HASH. 
656         *
657         * @access private
658         */
659         function _tag_open( &$parser, $tag, $attributes ) {
660                 $this->currentElement = strtoupper( $tag );
661                 
662                 switch( $this->currentElement ) {
663                         case 'DROP':
664                                 $this->drop();
665                                 break;
666                         case 'CLUSTERED':
667                         case 'BITMAP':
668                         case 'UNIQUE':
669                         case 'FULLTEXT':
670                         case 'HASH':
671                                 // Add index Option
672                                 $this->addIndexOpt( $this->currentElement );
673                                 break;
674                         default:
675                                 // print_r( array( $tag, $attributes ) );
676                 }
677         }
678         
679         /**
680         * XML Callback to process CDATA elements
681         *
682         * Processes XML cdata.
683         *
684         * @access private
685         */
686         function _tag_cdata( &$parser, $cdata ) {
687                 switch( $this->currentElement ) {
688                         // Index field name
689                         case 'COL':
690                                 $this->addField( $cdata );
691                                 break;
692                         default:
693                                 
694                 }
695         }
696         
697         /**
698         * XML Callback to process end elements
699         *
700         * @access private
701         */
702         function _tag_close( &$parser, $tag ) {
703                 $this->currentElement = '';
704                 
705                 switch( strtoupper( $tag ) ) {
706                         case 'INDEX':
707                                 xml_set_object( $parser, $this->parent );
708                                 break;
709                 }
710         }
711         
712         /**
713         * Adds a field to the index
714         *
715         * @param string $name Field name
716         * @return string Field list
717         */
718         function addField( $name ) {
719                 $this->columns[$this->FieldID( $name )] = $name;
720                 
721                 // Return the field list
722                 return $this->columns;
723         }
724         
725         /**
726         * Adds options to the index
727         *
728         * @param string $opt Comma-separated list of index options.
729         * @return string Option list
730         */
731         function addIndexOpt( $opt ) {
732                 $this->opts[] = $opt;
733                 
734                 // Return the options list
735                 return $this->opts;
736         }
737         
738         /**
739         * Generates the SQL that will create the index in the database
740         *
741         * @param object $xmls adoSchema object
742         * @return array Array containing index creation SQL
743         */
744         function create( &$xmls ) {
745                 if( $this->drop ) {
746                         return NULL;
747                 }
748                 
749                 // eliminate any columns that aren't in the table
750                 foreach( $this->columns as $id => $col ) {
751                         if( !isset( $this->parent->fields[$id] ) ) {
752                                 unset( $this->columns[$id] );
753                         }
754                 }
755                 
756                 return $xmls->dict->CreateIndexSQL( $this->name, $this->parent->name, $this->columns, $this->opts );
757         }
758         
759         /**
760         * Marks an index for destruction
761         */
762         function drop() {
763                 $this->drop = TRUE;
764         }
765 }
766
767 /**
768 * Creates a data object in ADOdb's datadict format
769 *
770 * This class stores information about table data.
771 *
772 * @package axmls
773 * @access private
774 */
775 class dbData extends dbObject {
776         
777         var $data = array();
778         
779         var $row;
780         
781         /**
782         * Initializes the new dbIndex object.
783         *
784         * @param object $parent Parent object
785         * @param array $attributes Attributes
786         *
787         * @internal
788         */
789         function dbData( &$parent, $attributes = NULL ) {
790                 $this->parent = $parent;
791         }
792         
793         /**
794         * XML Callback to process start elements
795         *
796         * Processes XML opening tags. 
797         * Elements currently processed are: DROP, CLUSTERED, BITMAP, UNIQUE, FULLTEXT & HASH. 
798         *
799         * @access private
800         */
801         function _tag_open( &$parser, $tag, $attributes ) {
802                 $this->currentElement = strtoupper( $tag );
803                 
804                 switch( $this->currentElement ) {
805                         case 'ROW':
806                                 $this->row = count( $this->data );
807                                 $this->data[$this->row] = array();
808                                 break;
809                         case 'F':
810                                 $this->addField($attributes);
811                         default:
812                                 // print_r( array( $tag, $attributes ) );
813                 }
814         }
815         
816         /**
817         * XML Callback to process CDATA elements
818         *
819         * Processes XML cdata.
820         *
821         * @access private
822         */
823         function _tag_cdata( &$parser, $cdata ) {
824                 switch( $this->currentElement ) {
825                         // Index field name
826                         case 'F':
827                                 $this->addData( $cdata );
828                                 break;
829                         default:
830                                 
831                 }
832         }
833         
834         /**
835         * XML Callback to process end elements
836         *
837         * @access private
838         */
839         function _tag_close( &$parser, $tag ) {
840                 $this->currentElement = '';
841                 
842                 switch( strtoupper( $tag ) ) {
843                         case 'DATA':
844                                 xml_set_object( $parser, $this->parent );
845                                 break;
846                 }
847         }
848         
849         /**
850         * Adds a field to the index
851         *
852         * @param string $name Field name
853         * @return string Field list
854         */
855         function addField( $attributes ) {
856                 if( isset( $attributes['NAME'] ) ) {
857                         $name = $attributes['NAME'];
858                 } else {
859                         $name = count($this->data[$this->row]);
860                 }
861                 
862                 // Set the field index so we know where we are
863                 $this->current_field = $this->FieldID( $name );
864         }
865         
866         /**
867         * Adds options to the index
868         *
869         * @param string $opt Comma-separated list of index options.
870         * @return string Option list
871         */
872         function addData( $cdata ) {
873                 if( !isset( $this->data[$this->row] ) ) {
874                         $this->data[$this->row] = array();
875                 }
876                 
877                 if( !isset( $this->data[$this->row][$this->current_field] ) ) {
878                         $this->data[$this->row][$this->current_field] = '';
879                 }
880                 
881                 $this->data[$this->row][$this->current_field] .= $cdata;
882         }
883         
884         /**
885         * Generates the SQL that will create the index in the database
886         *
887         * @param object $xmls adoSchema object
888         * @return array Array containing index creation SQL
889         */
890         function create( &$xmls ) {
891                 $table = $xmls->dict->TableName($this->parent->name);
892                 $table_field_count = count($this->parent->fields);
893                 $sql = array();
894                 
895                 // eliminate any columns that aren't in the table
896                 foreach( $this->data as $row ) {
897                         $table_fields = $this->parent->fields;
898                         $fields = array();
899                         
900                         foreach( $row as $field_id => $field_data ) {
901                                 if( !array_key_exists( $field_id, $table_fields ) ) {
902                                         if( is_numeric( $field_id ) ) {
903                                                 $field_id = reset( array_keys( $table_fields ) );
904                                         } else {
905                                                 continue;
906                                         }
907                                 }
908                                 
909                                 $name = $table_fields[$field_id]['NAME'];
910                                 
911                                 switch( $table_fields[$field_id]['TYPE'] ) {
912                                         case 'C':
913                                         case 'C2':
914                                         case 'X':
915                                         case 'X2':
916                                                 $fields[$name] = $xmls->db->qstr( $field_data );
917                                                 break;
918                                         case 'I':
919                                         case 'I1':
920                                         case 'I2':
921                                         case 'I4':
922                                         case 'I8':
923                                                 $fields[$name] = intval($field_data);
924                                                 break;
925                                         default:
926                                                 $fields[$name] = $field_data;
927                                 }
928                                 
929                                 unset($table_fields[$field_id]);
930                         }
931                         
932                         // check that at least 1 column is specified
933                         if( empty( $fields ) ) {
934                                 continue;
935                         }
936                         
937                         // check that no required columns are missing
938                         if( count( $fields ) < $table_field_count ) {
939                                 foreach( $table_fields as $field ) {
940                                         if (isset( $field['OPTS'] ))
941                                                 if( ( in_array( 'NOTNULL', $field['OPTS'] ) || in_array( 'KEY', $field['OPTS'] ) ) && !in_array( 'AUTOINCREMENT', $field['OPTS'] ) ) {
942                                                         continue(2);
943                                                 }
944                                 }
945                         }
946                         
947                         $sql[] = 'INSERT INTO '. $table .' ('. implode( ',', array_keys( $fields ) ) .') VALUES ('. implode( ',', $fields ) .')';
948                 }
949                 
950                 return $sql;
951         }
952 }
953
954 /**
955 * Creates the SQL to execute a list of provided SQL queries
956 *
957 * @package axmls
958 * @access private
959 */
960 class dbQuerySet extends dbObject {
961         
962         /**
963         * @var array    List of SQL queries
964         */
965         var $queries = array();
966         
967         /**
968         * @var string   String used to build of a query line by line
969         */
970         var $query;
971         
972         /**
973         * @var string   Query prefix key
974         */
975         var $prefixKey = '';
976         
977         /**
978         * @var boolean  Auto prefix enable (TRUE)
979         */
980         var $prefixMethod = 'AUTO';
981         
982         /**
983         * Initializes the query set.
984         *
985         * @param object $parent Parent object
986         * @param array $attributes Attributes
987         */
988         function dbQuerySet( &$parent, $attributes = NULL ) {
989                 $this->parent = $parent;
990                         
991                 // Overrides the manual prefix key
992                 if( isset( $attributes['KEY'] ) ) {
993                         $this->prefixKey = $attributes['KEY'];
994                 }
995                 
996                 $prefixMethod = isset( $attributes['PREFIXMETHOD'] ) ? strtoupper( trim( $attributes['PREFIXMETHOD'] ) ) : '';
997                 
998                 // Enables or disables automatic prefix prepending
999                 switch( $prefixMethod ) {
1000                         case 'AUTO':
1001                                 $this->prefixMethod = 'AUTO';
1002                                 break;
1003                         case 'MANUAL':
1004                                 $this->prefixMethod = 'MANUAL';
1005                                 break;
1006                         case 'NONE':
1007                                 $this->prefixMethod = 'NONE';
1008                                 break;
1009                 }
1010         }
1011         
1012         /**
1013         * XML Callback to process start elements. Elements currently 
1014         * processed are: QUERY. 
1015         *
1016         * @access private
1017         */
1018         function _tag_open( &$parser, $tag, $attributes ) {
1019                 $this->currentElement = strtoupper( $tag );
1020                 
1021                 switch( $this->currentElement ) {
1022                         case 'QUERY':
1023                                 // Create a new query in a SQL queryset.
1024                                 // Ignore this query set if a platform is specified and it's different than the 
1025                                 // current connection platform.
1026                                 if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) {
1027                                         $this->newQuery();
1028                                 } else {
1029                                         $this->discardQuery();
1030                                 }
1031                                 break;
1032                         default:
1033                                 // print_r( array( $tag, $attributes ) );
1034                 }
1035         }
1036         
1037         /**
1038         * XML Callback to process CDATA elements
1039         */
1040         function _tag_cdata( &$parser, $cdata ) {
1041                 switch( $this->currentElement ) {
1042                         // Line of queryset SQL data
1043                         case 'QUERY':
1044                                 $this->buildQuery( $cdata );
1045                                 break;
1046                         default:
1047                                 
1048                 }
1049         }
1050         
1051         /**
1052         * XML Callback to process end elements
1053         *
1054         * @access private
1055         */
1056         function _tag_close( &$parser, $tag ) {
1057                 $this->currentElement = '';
1058                 
1059                 switch( strtoupper( $tag ) ) {
1060                         case 'QUERY':
1061                                 // Add the finished query to the open query set.
1062                                 $this->addQuery();
1063                                 break;
1064                         case 'SQL':
1065                                 $this->parent->addSQL( $this->create( $this->parent ) );
1066                                 xml_set_object( $parser, $this->parent );
1067                                 $this->destroy();
1068                                 break;
1069                         default:
1070                                 
1071                 }
1072         }
1073         
1074         /**
1075         * Re-initializes the query.
1076         *
1077         * @return boolean TRUE
1078         */
1079         function newQuery() {
1080                 $this->query = '';
1081                 
1082                 return TRUE;
1083         }
1084         
1085         /**
1086         * Discards the existing query.
1087         *
1088         * @return boolean TRUE
1089         */
1090         function discardQuery() {
1091                 unset( $this->query );
1092                 
1093                 return TRUE;
1094         }
1095         
1096         /** 
1097         * Appends a line to a query that is being built line by line
1098         *
1099         * @param string $data Line of SQL data or NULL to initialize a new query
1100         * @return string SQL query string.
1101         */
1102         function buildQuery( $sql = NULL ) {
1103                 if( !isset( $this->query ) OR empty( $sql ) ) {
1104                         return FALSE;
1105                 }
1106                 
1107                 $this->query .= $sql;
1108                 
1109                 return $this->query;
1110         }
1111         
1112         /**
1113         * Adds a completed query to the query list
1114         *
1115         * @return string        SQL of added query
1116         */
1117         function addQuery() {
1118                 if( !isset( $this->query ) ) {
1119                         return FALSE;
1120                 }
1121                 
1122                 $this->queries[] = $return = trim($this->query);
1123                 
1124                 unset( $this->query );
1125                 
1126                 return $return;
1127         }
1128         
1129         /**
1130         * Creates and returns the current query set
1131         *
1132         * @param object $xmls adoSchema object
1133         * @return array Query set
1134         */
1135         function create( &$xmls ) {
1136                 foreach( $this->queries as $id => $query ) {
1137                         switch( $this->prefixMethod ) {
1138                                 case 'AUTO':
1139                                         // Enable auto prefix replacement
1140                                         
1141                                         // Process object prefix.
1142                                         // Evaluate SQL statements to prepend prefix to objects
1143                                         $query = $this->prefixQuery( '/^\s*((?is)INSERT\s+(INTO\s+)?)((\w+\s*,?\s*)+)(\s.*$)/', $query, $xmls->objectPrefix );
1144                                         $query = $this->prefixQuery( '/^\s*((?is)UPDATE\s+(FROM\s+)?)((\w+\s*,?\s*)+)(\s.*$)/', $query, $xmls->objectPrefix );
1145                                         $query = $this->prefixQuery( '/^\s*((?is)DELETE\s+(FROM\s+)?)((\w+\s*,?\s*)+)(\s.*$)/', $query, $xmls->objectPrefix );
1146                                         
1147                                         // SELECT statements aren't working yet
1148                                         #$data = preg_replace( '/(?ias)(^\s*SELECT\s+.*\s+FROM)\s+(\W\s*,?\s*)+((?i)\s+WHERE.*$)/', "\1 $prefix\2 \3", $data );
1149                                         
1150                                 case 'MANUAL':
1151                                         // If prefixKey is set and has a value then we use it to override the default constant XMLS_PREFIX.
1152                                         // If prefixKey is not set, we use the default constant XMLS_PREFIX
1153                                         if( isset( $this->prefixKey ) AND( $this->prefixKey !== '' ) ) {
1154                                                 // Enable prefix override
1155                                                 $query = str_replace( $this->prefixKey, $xmls->objectPrefix, $query );
1156                                         } else {
1157                                                 // Use default replacement
1158                                                 $query = str_replace( XMLS_PREFIX , $xmls->objectPrefix, $query );
1159                                         }
1160                         }
1161                         
1162                         $this->queries[$id] = trim( $query );
1163                 }
1164                 
1165                 // Return the query set array
1166                 return $this->queries;
1167         }
1168         
1169         /**
1170         * Rebuilds the query with the prefix attached to any objects
1171         *
1172         * @param string $regex Regex used to add prefix
1173         * @param string $query SQL query string
1174         * @param string $prefix Prefix to be appended to tables, indices, etc.
1175         * @return string Prefixed SQL query string.
1176         */
1177         function prefixQuery( $regex, $query, $prefix = NULL ) {
1178                 if( !isset( $prefix ) ) {
1179                         return $query;
1180                 }
1181                 
1182                 if( preg_match( $regex, $query, $match ) ) {
1183                         $preamble = $match[1];
1184                         $postamble = $match[5];
1185                         $objectList = explode( ',', $match[3] );
1186                         // $prefix = $prefix . '_';
1187                         
1188                         $prefixedList = '';
1189                         
1190                         foreach( $objectList as $object ) {
1191                                 if( $prefixedList !== '' ) {
1192                                         $prefixedList .= ', ';
1193                                 }
1194                                 
1195                                 $prefixedList .= $prefix . trim( $object );
1196                         }
1197                         
1198                         $query = $preamble . ' ' . $prefixedList . ' ' . $postamble;
1199                 }
1200                 
1201                 return $query;
1202         }
1203 }
1204
1205 /**
1206 * Loads and parses an XML file, creating an array of "ready-to-run" SQL statements
1207
1208 * This class is used to load and parse the XML file, to create an array of SQL statements
1209 * that can be used to build a database, and to build the database using the SQL array.
1210 *
1211 * @tutorial getting_started.pkg
1212 *
1213 * @author Richard Tango-Lowy & Dan Cech
1214 * @version $Revision: 1.12 $
1215 *
1216 * @package axmls
1217 */
1218 class adoSchema {
1219         
1220         /**
1221         * @var array    Array containing SQL queries to generate all objects
1222         * @access private
1223         */
1224         var $sqlArray;
1225         
1226         /**
1227         * @var object   ADOdb connection object
1228         * @access private
1229         */
1230         var $db;
1231         
1232         /**
1233         * @var object   ADOdb Data Dictionary
1234         * @access private
1235         */
1236         var $dict;
1237         
1238         /**
1239         * @var string Current XML element
1240         * @access private
1241         */
1242         var $currentElement = '';
1243         
1244         /**
1245         * @var string If set (to 'ALTER' or 'REPLACE'), upgrade an existing database
1246         * @access private
1247         */
1248         var $upgrade = '';
1249         
1250         /**
1251         * @var string Optional object prefix
1252         * @access private
1253         */
1254         var $objectPrefix = '';
1255         
1256         /**
1257         * @var long     Original Magic Quotes Runtime value
1258         * @access private
1259         */
1260         var $mgq;
1261         
1262         /**
1263         * @var long     System debug
1264         * @access private
1265         */
1266         var $debug;
1267         
1268         /**
1269         * @var string Regular expression to find schema version
1270         * @access private
1271         */
1272         var $versionRegex = '/<schema.*?( version="([^"]*)")?.*?>/';
1273         
1274         /**
1275         * @var string Current schema version
1276         * @access private
1277         */
1278         var $schemaVersion;
1279         
1280         /**
1281         * @var int      Success of last Schema execution
1282         */
1283         var $success;
1284         
1285         /**
1286         * @var bool     Execute SQL inline as it is generated
1287         */
1288         var $executeInline;
1289         
1290         /**
1291         * @var bool     Continue SQL execution if errors occur
1292         */
1293         var $continueOnError;
1294         
1295         /**
1296         * Creates an adoSchema object
1297         *
1298         * Creating an adoSchema object is the first step in processing an XML schema.
1299         * The only parameter is an ADOdb database connection object, which must already
1300         * have been created.
1301         *
1302         * @param object $db ADOdb database connection object.
1303         */
1304         function adoSchema( $db ) {
1305                 // Initialize the environment
1306                 $this->mgq = get_magic_quotes_runtime();
1307                 ini_set("magic_quotes_runtime", 0);
1308                 #set_magic_quotes_runtime(0);
1309                 
1310                 $this->db = $db;
1311                 $this->debug = $this->db->debug;
1312                 $this->dict = NewDataDictionary( $this->db );
1313                 $this->sqlArray = array();
1314                 $this->schemaVersion = XMLS_SCHEMA_VERSION;
1315                 $this->executeInline( XMLS_EXECUTE_INLINE );
1316                 $this->continueOnError( XMLS_CONTINUE_ON_ERROR );
1317                 $this->setUpgradeMethod();
1318         }
1319         
1320         /**
1321         * Sets the method to be used for upgrading an existing database
1322         *
1323         * Use this method to specify how existing database objects should be upgraded.
1324         * The method option can be set to ALTER, REPLACE, BEST, or NONE. ALTER attempts to
1325         * alter each database object directly, REPLACE attempts to rebuild each object
1326         * from scratch, BEST attempts to determine the best upgrade method for each
1327         * object, and NONE disables upgrading.
1328         *
1329         * This method is not yet used by AXMLS, but exists for backward compatibility.
1330         * The ALTER method is automatically assumed when the adoSchema object is
1331         * instantiated; other upgrade methods are not currently supported.
1332         *
1333         * @param string $method Upgrade method (ALTER|REPLACE|BEST|NONE)
1334         * @returns string Upgrade method used
1335         */
1336         function SetUpgradeMethod( $method = '' ) {
1337                 if( !is_string( $method ) ) {
1338                         return FALSE;
1339                 }
1340                 
1341                 $method = strtoupper( $method );
1342                 
1343                 // Handle the upgrade methods
1344                 switch( $method ) {
1345                         case 'ALTER':
1346                                 $this->upgrade = $method;
1347                                 break;
1348                         case 'REPLACE':
1349                                 $this->upgrade = $method;
1350                                 break;
1351                         case 'BEST':
1352                                 $this->upgrade = 'ALTER';
1353                                 break;
1354                         case 'NONE':
1355                                 $this->upgrade = 'NONE';
1356                                 break;
1357                         default:
1358                                 // Use default if no legitimate method is passed.
1359                                 $this->upgrade = XMLS_DEFAULT_UPGRADE_METHOD;
1360                 }
1361                 
1362                 return $this->upgrade;
1363         }
1364         
1365         /**
1366         * Enables/disables inline SQL execution.
1367         *
1368         * Call this method to enable or disable inline execution of the schema. If the mode is set to TRUE (inline execution),
1369         * AXMLS applies the SQL to the database immediately as each schema entity is parsed. If the mode
1370         * is set to FALSE (post execution), AXMLS parses the entire schema and you will need to call adoSchema::ExecuteSchema()
1371         * to apply the schema to the database.
1372         *
1373         * @param bool $mode execute
1374         * @return bool current execution mode
1375         *
1376         * @see ParseSchema(), ExecuteSchema()
1377         */
1378         function ExecuteInline( $mode = NULL ) {
1379                 if( is_bool( $mode ) ) {
1380                         $this->executeInline = $mode;
1381                 }
1382                 
1383                 return $this->executeInline;
1384         }
1385         
1386         /**
1387         * Enables/disables SQL continue on error.
1388         *
1389         * Call this method to enable or disable continuation of SQL execution if an error occurs.
1390         * If the mode is set to TRUE (continue), AXMLS will continue to apply SQL to the database, even if an error occurs.
1391         * If the mode is set to FALSE (halt), AXMLS will halt execution of generated sql if an error occurs, though parsing
1392         * of the schema will continue.
1393         *
1394         * @param bool $mode execute
1395         * @return bool current continueOnError mode
1396         *
1397         * @see addSQL(), ExecuteSchema()
1398         */
1399         function ContinueOnError( $mode = NULL ) {
1400                 if( is_bool( $mode ) ) {
1401                         $this->continueOnError = $mode;
1402                 }
1403                 
1404                 return $this->continueOnError;
1405         }
1406         
1407         /**
1408         * Loads an XML schema from a file and converts it to SQL.
1409         *
1410         * Call this method to load the specified schema (see the DTD for the proper format) from
1411         * the filesystem and generate the SQL necessary to create the database described. 
1412         * @see ParseSchemaString()
1413         *
1414         * @param string $file Name of XML schema file.
1415         * @param bool $returnSchema Return schema rather than parsing.
1416         * @return array Array of SQL queries, ready to execute
1417         */
1418         function ParseSchema( $filename, $returnSchema = FALSE ) {
1419                 return $this->ParseSchemaString( $this->ConvertSchemaFile( $filename ), $returnSchema );
1420         }
1421         
1422         /**
1423         * Loads an XML schema from a file and converts it to SQL.
1424         *
1425         * Call this method to load the specified schema from a file (see the DTD for the proper format) 
1426         * and generate the SQL necessary to create the database described by the schema.
1427         *
1428         * @param string $file Name of XML schema file.
1429         * @param bool $returnSchema Return schema rather than parsing.
1430         * @return array Array of SQL queries, ready to execute.
1431         *
1432         * @deprecated Replaced by adoSchema::ParseSchema() and adoSchema::ParseSchemaString()
1433         * @see ParseSchema(), ParseSchemaString()
1434         */
1435         function ParseSchemaFile( $filename, $returnSchema = FALSE ) {
1436                 // Open the file
1437                 if( !($fp = fopen( $filename, 'r' )) ) {
1438                         // die( 'Unable to open file' );
1439                         return FALSE;
1440                 }
1441                 
1442                 // do version detection here
1443                 if( $this->SchemaFileVersion( $filename ) != $this->schemaVersion ) {
1444                         return FALSE;
1445                 }
1446                 
1447                 if ( $returnSchema )
1448                 {
1449                         $xmlstring = '';
1450                         while( $data = fread( $fp, 100000 ) ) {
1451                                 $xmlstring .= $data;
1452                         }
1453                         return $xmlstring;
1454                 }
1455                 
1456                 $this->success = 2;
1457                 
1458                 $xmlParser = $this->create_parser();
1459                 
1460                 // Process the file
1461                 while( $data = fread( $fp, 4096 ) ) {
1462                         if( !xml_parse( $xmlParser, $data, feof( $fp ) ) ) {
1463                                 die( sprintf(
1464                                         "XML error: %s at line %d",
1465                                         xml_error_string( xml_get_error_code( $xmlParser) ),
1466                                         xml_get_current_line_number( $xmlParser)
1467                                 ) );
1468                         }
1469                 }
1470                 
1471                 xml_parser_free( $xmlParser );
1472                 
1473                 return $this->sqlArray;
1474         }
1475         
1476         /**
1477         * Converts an XML schema string to SQL.
1478         *
1479         * Call this method to parse a string containing an XML schema (see the DTD for the proper format)
1480         * and generate the SQL necessary to create the database described by the schema. 
1481         * @see ParseSchema()
1482         *
1483         * @param string $xmlstring XML schema string.
1484         * @param bool $returnSchema Return schema rather than parsing.
1485         * @return array Array of SQL queries, ready to execute.
1486         */
1487         function ParseSchemaString( $xmlstring, $returnSchema = FALSE ) {
1488                 if( !is_string( $xmlstring ) OR empty( $xmlstring ) ) {
1489                         return FALSE;
1490                 }
1491                 
1492                 // do version detection here
1493                 if( $this->SchemaStringVersion( $xmlstring ) != $this->schemaVersion ) {
1494                         return FALSE;
1495                 }
1496                 
1497                 if ( $returnSchema )
1498                 {
1499                         return $xmlstring;
1500                 }
1501                 
1502                 $this->success = 2;
1503                 
1504                 $xmlParser = $this->create_parser();
1505                 
1506                 if( !xml_parse( $xmlParser, $xmlstring, TRUE ) ) {
1507                         die( sprintf(
1508                                 "XML error: %s at line %d",
1509                                 xml_error_string( xml_get_error_code( $xmlParser) ),
1510                                 xml_get_current_line_number( $xmlParser)
1511                         ) );
1512                 }
1513                 
1514                 xml_parser_free( $xmlParser );
1515                 
1516                 return $this->sqlArray;
1517         }
1518         
1519         /**
1520         * Loads an XML schema from a file and converts it to uninstallation SQL.
1521         *
1522         * Call this method to load the specified schema (see the DTD for the proper format) from
1523         * the filesystem and generate the SQL necessary to remove the database described.
1524         * @see RemoveSchemaString()
1525         *
1526         * @param string $file Name of XML schema file.
1527         * @param bool $returnSchema Return schema rather than parsing.
1528         * @return array Array of SQL queries, ready to execute
1529         */
1530         function RemoveSchema( $filename, $returnSchema = FALSE ) {
1531                 return $this->RemoveSchemaString( $this->ConvertSchemaFile( $filename ), $returnSchema );
1532         }
1533         
1534         /**
1535         * Converts an XML schema string to uninstallation SQL.
1536         *
1537         * Call this method to parse a string containing an XML schema (see the DTD for the proper format)
1538         * and generate the SQL necessary to uninstall the database described by the schema. 
1539         * @see RemoveSchema()
1540         *
1541         * @param string $schema XML schema string.
1542         * @param bool $returnSchema Return schema rather than parsing.
1543         * @return array Array of SQL queries, ready to execute.
1544         */
1545         function RemoveSchemaString( $schema, $returnSchema = FALSE ) {
1546                 
1547                 // grab current version
1548                 if( !( $version = $this->SchemaStringVersion( $schema ) ) ) {
1549                         return FALSE;
1550                 }
1551                 
1552                 return $this->ParseSchemaString( $this->TransformSchema( $schema, 'remove-' . $version), $returnSchema );
1553         }
1554         
1555         /**
1556         * Applies the current XML schema to the database (post execution).
1557         *
1558         * Call this method to apply the current schema (generally created by calling 
1559         * ParseSchema() or ParseSchemaString() ) to the database (creating the tables, indexes, 
1560         * and executing other SQL specified in the schema) after parsing.
1561         * @see ParseSchema(), ParseSchemaString(), ExecuteInline()
1562         *
1563         * @param array $sqlArray Array of SQL statements that will be applied rather than
1564         *               the current schema.
1565         * @param boolean $continueOnErr Continue to apply the schema even if an error occurs.
1566         * @returns integer 0 if failure, 1 if errors, 2 if successful.
1567         */
1568         function ExecuteSchema( $sqlArray = NULL, $continueOnErr =  NULL ) {
1569                 if( !is_bool( $continueOnErr ) ) {
1570                         $continueOnErr = $this->ContinueOnError();
1571                 }
1572                 
1573                 if( !isset( $sqlArray ) ) {
1574                         $sqlArray = $this->sqlArray;
1575                 }
1576                 
1577                 if( !is_array( $sqlArray ) ) {
1578                         $this->success = 0;
1579                 } else {
1580                         $this->success = $this->dict->ExecuteSQLArray( $sqlArray, $continueOnErr );
1581                 }
1582                 
1583                 return $this->success;
1584         }
1585         
1586         /**
1587         * Returns the current SQL array. 
1588         *
1589         * Call this method to fetch the array of SQL queries resulting from 
1590         * ParseSchema() or ParseSchemaString(). 
1591         *
1592         * @param string $format Format: HTML, TEXT, or NONE (PHP array)
1593         * @return array Array of SQL statements or FALSE if an error occurs
1594         */
1595         function PrintSQL( $format = 'NONE' ) {
1596                 $sqlArray = null;
1597                 return $this->getSQL( $format, $sqlArray );
1598         }
1599         
1600         /**
1601         * Saves the current SQL array to the local filesystem as a list of SQL queries.
1602         *
1603         * Call this method to save the array of SQL queries (generally resulting from a
1604         * parsed XML schema) to the filesystem.
1605         *
1606         * @param string $filename Path and name where the file should be saved.
1607         * @return boolean TRUE if save is successful, else FALSE. 
1608         */
1609         function SaveSQL( $filename = './schema.sql' ) {
1610                 
1611                 if( !isset( $sqlArray ) ) {
1612                         $sqlArray = $this->sqlArray;
1613                 }
1614                 if( !isset( $sqlArray ) ) {
1615                         return FALSE;
1616                 }
1617                 
1618                 $fp = fopen( $filename, "w" );
1619                 
1620                 foreach( $sqlArray as $key => $query ) {
1621                         fwrite( $fp, $query . ";\n" );
1622                 }
1623                 fclose( $fp );
1624         }
1625         
1626         /**
1627         * Create an xml parser
1628         *
1629         * @return object PHP XML parser object
1630         *
1631         * @access private
1632         */
1633         function create_parser() {
1634                 // Create the parser
1635                 $xmlParser = xml_parser_create();
1636                 xml_set_object( $xmlParser, $this );
1637                 
1638                 // Initialize the XML callback functions
1639                 xml_set_element_handler( $xmlParser, '_tag_open', '_tag_close' );
1640                 xml_set_character_data_handler( $xmlParser, '_tag_cdata' );
1641                 
1642                 return $xmlParser;
1643         }
1644         
1645         /**
1646         * XML Callback to process start elements
1647         *
1648         * @access private
1649         */
1650         function _tag_open( &$parser, $tag, $attributes ) {
1651                 switch( strtoupper( $tag ) ) {
1652                         case 'TABLE':
1653                                 $this->obj = new dbTable( $this, $attributes );
1654                                 xml_set_object( $parser, $this->obj );
1655                                 break;
1656                         case 'SQL':
1657                                 if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) {
1658                                         $this->obj = new dbQuerySet( $this, $attributes );
1659                                         xml_set_object( $parser, $this->obj );
1660                                 }
1661                                 break;
1662                         default:
1663                                 // print_r( array( $tag, $attributes ) );
1664                 }
1665                 
1666         }
1667         
1668         /**
1669         * XML Callback to process CDATA elements
1670         *
1671         * @access private
1672         */
1673         function _tag_cdata( &$parser, $cdata ) {
1674         }
1675         
1676         /**
1677         * XML Callback to process end elements
1678         *
1679         * @access private
1680         * @internal
1681         */
1682         function _tag_close( &$parser, $tag ) {
1683                 
1684         }
1685         
1686         /**
1687         * Converts an XML schema string to the specified DTD version.
1688         *
1689         * Call this method to convert a string containing an XML schema to a different AXMLS
1690         * DTD version. For instance, to convert a schema created for an pre-1.0 version for 
1691         * AXMLS (DTD version 0.1) to a newer version of the DTD (e.g. 0.2). If no DTD version 
1692         * parameter is specified, the schema will be converted to the current DTD version. 
1693         * If the newFile parameter is provided, the converted schema will be written to the specified
1694         * file.
1695         * @see ConvertSchemaFile()
1696         *
1697         * @param string $schema String containing XML schema that will be converted.
1698         * @param string $newVersion DTD version to convert to.
1699         * @param string $newFile File name of (converted) output file.
1700         * @return string Converted XML schema or FALSE if an error occurs.
1701         */
1702         function ConvertSchemaString( $schema, $newVersion = NULL, $newFile = NULL ) {
1703                 
1704                 // grab current version
1705                 if( !( $version = $this->SchemaStringVersion( $schema ) ) ) {
1706                         return FALSE;
1707                 }
1708                 
1709                 if( !isset ($newVersion) ) {
1710                         $newVersion = $this->schemaVersion;
1711                 }
1712                 
1713                 if( $version == $newVersion ) {
1714                         $result = $schema;
1715                 } else {
1716                         $result = $this->TransformSchema( $schema, 'convert-' . $version . '-' . $newVersion);
1717                 }
1718                 
1719                 if( is_string( $result ) AND is_string( $newFile ) AND ( $fp = fopen( $newFile, 'w' ) ) ) {
1720                         fwrite( $fp, $result );
1721                         fclose( $fp );
1722                 }
1723                 
1724                 return $result;
1725         }
1726         
1727         // compat for pre-4.3 - jlim
1728         function _file_get_contents($path)
1729         {
1730                 if (function_exists('file_get_contents')) return file_get_contents($path);
1731                 return join('',file($path));
1732         }
1733         
1734         /**
1735         * Converts an XML schema file to the specified DTD version.
1736         *
1737         * Call this method to convert the specified XML schema file to a different AXMLS
1738         * DTD version. For instance, to convert a schema created for an pre-1.0 version for 
1739         * AXMLS (DTD version 0.1) to a newer version of the DTD (e.g. 0.2). If no DTD version 
1740         * parameter is specified, the schema will be converted to the current DTD version. 
1741         * If the newFile parameter is provided, the converted schema will be written to the specified
1742         * file.
1743         * @see ConvertSchemaString()
1744         *
1745         * @param string $filename Name of XML schema file that will be converted.
1746         * @param string $newVersion DTD version to convert to.
1747         * @param string $newFile File name of (converted) output file.
1748         * @return string Converted XML schema or FALSE if an error occurs.
1749         */
1750         function ConvertSchemaFile( $filename, $newVersion = NULL, $newFile = NULL ) {
1751                 
1752                 // grab current version
1753                 if( !( $version = $this->SchemaFileVersion( $filename ) ) ) {
1754                         return FALSE;
1755                 }
1756                 
1757                 if( !isset ($newVersion) ) {
1758                         $newVersion = $this->schemaVersion;
1759                 }
1760                 
1761                 if( $version == $newVersion ) {
1762                         $result = _file_get_contents( $filename );
1763                         
1764                         // remove unicode BOM if present
1765                         if( substr( $result, 0, 3 ) == sprintf( '%c%c%c', 239, 187, 191 ) ) {
1766                                 $result = substr( $result, 3 );
1767                         }
1768                 } else {
1769                         $result = $this->TransformSchema( $filename, 'convert-' . $version . '-' . $newVersion, 'file' );
1770                 }
1771                 
1772                 if( is_string( $result ) AND is_string( $newFile ) AND ( $fp = fopen( $newFile, 'w' ) ) ) {
1773                         fwrite( $fp, $result );
1774                         fclose( $fp );
1775                 }
1776                 
1777                 return $result;
1778         }
1779         
1780         function TransformSchema( $schema, $xsl, $schematype='string' )
1781         {
1782                 // Fail if XSLT extension is not available
1783                 if( ! function_exists( 'xslt_create' ) ) {
1784                         return FALSE;
1785                 }
1786                 
1787                 $xsl_file = dirname( __FILE__ ) . '/xsl/' . $xsl . '.xsl';
1788                 
1789                 // look for xsl
1790                 if( !is_readable( $xsl_file ) ) {
1791                         return FALSE;
1792                 }
1793                 
1794                 switch( $schematype )
1795                 {
1796                         case 'file':
1797                                 if( !is_readable( $schema ) ) {
1798                                         return FALSE;
1799                                 }
1800                                 
1801                                 $schema = _file_get_contents( $schema );
1802                                 break;
1803                         case 'string':
1804                         default:
1805                                 if( !is_string( $schema ) ) {
1806                                         return FALSE;
1807                                 }
1808                 }
1809                 
1810                 $arguments = array (
1811                         '/_xml' => $schema,
1812                         '/_xsl' => _file_get_contents( $xsl_file )
1813                 );
1814                 
1815                 // create an XSLT processor
1816                 $xh = xslt_create ();
1817                 
1818                 // set error handler
1819                 xslt_set_error_handler ($xh, array (&$this, 'xslt_error_handler'));
1820                 
1821                 // process the schema
1822                 $result = xslt_process ($xh, 'arg:/_xml', 'arg:/_xsl', NULL, $arguments); 
1823                 
1824                 xslt_free ($xh);
1825                 
1826                 return $result;
1827         }
1828         
1829         /**
1830         * Processes XSLT transformation errors
1831         *
1832         * @param object $parser XML parser object
1833         * @param integer $errno Error number
1834         * @param integer $level Error level
1835         * @param array $fields Error information fields
1836         *
1837         * @access private
1838         */
1839         function xslt_error_handler( $parser, $errno, $level, $fields ) {
1840                 if( is_array( $fields ) ) {
1841                         $msg = array(
1842                                 'Message Type' => ucfirst( $fields['msgtype'] ),
1843                                 'Message Code' => $fields['code'],
1844                                 'Message' => $fields['msg'],
1845                                 'Error Number' => $errno,
1846                                 'Level' => $level
1847                         );
1848                         
1849                         switch( $fields['URI'] ) {
1850                                 case 'arg:/_xml':
1851                                         $msg['Input'] = 'XML';
1852                                         break;
1853                                 case 'arg:/_xsl':
1854                                         $msg['Input'] = 'XSL';
1855                                         break;
1856                                 default:
1857                                         $msg['Input'] = $fields['URI'];
1858                         }
1859                         
1860                         $msg['Line'] = $fields['line'];
1861                 } else {
1862                         $msg = array(
1863                                 'Message Type' => 'Error',
1864                                 'Error Number' => $errno,
1865                                 'Level' => $level,
1866                                 'Fields' => var_export( $fields, TRUE )
1867                         );
1868                 }
1869                 
1870                 $error_details = $msg['Message Type'] . ' in XSLT Transformation' . "\n"
1871                                            . '<table>' . "\n";
1872                 
1873                 foreach( $msg as $label => $details ) {
1874                         $error_details .= '<tr><td><b>' . $label . ': </b></td><td>' . htmlentities( $details ) . '</td></tr>' . "\n";
1875                 }
1876                 
1877                 $error_details .= '</table>';
1878                 
1879                 trigger_error( $error_details, E_USER_ERROR );
1880         }
1881         
1882         /**
1883         * Returns the AXMLS Schema Version of the requested XML schema file.
1884         *
1885         * Call this method to obtain the AXMLS DTD version of the requested XML schema file.
1886         * @see SchemaStringVersion()
1887         *
1888         * @param string $filename AXMLS schema file
1889         * @return string Schema version number or FALSE on error
1890         */
1891         function SchemaFileVersion( $filename ) {
1892                 // Open the file
1893                 if( !($fp = fopen( $filename, 'r' )) ) {
1894                         // die( 'Unable to open file' );
1895                         return FALSE;
1896                 }
1897                 
1898                 // Process the file
1899                 while( $data = fread( $fp, 4096 ) ) {
1900                         if( preg_match( $this->versionRegex, $data, $matches ) ) {
1901                                 return !empty( $matches[2] ) ? $matches[2] : XMLS_DEFAULT_SCHEMA_VERSION;
1902                         }
1903                 }
1904                 
1905                 return FALSE;
1906         }
1907         
1908         /**
1909         * Returns the AXMLS Schema Version of the provided XML schema string.
1910         *
1911         * Call this method to obtain the AXMLS DTD version of the provided XML schema string.
1912         * @see SchemaFileVersion()
1913         *
1914         * @param string $xmlstring XML schema string
1915         * @return string Schema version number or FALSE on error
1916         */
1917         function SchemaStringVersion( $xmlstring ) {
1918                 if( !is_string( $xmlstring ) OR empty( $xmlstring ) ) {
1919                         return FALSE;
1920                 }
1921                 
1922                 if( preg_match( $this->versionRegex, $xmlstring, $matches ) ) {
1923                         return !empty( $matches[2] ) ? $matches[2] : XMLS_DEFAULT_SCHEMA_VERSION;
1924                 }
1925                 
1926                 return FALSE;
1927         }
1928         
1929         /**
1930         * Extracts an XML schema from an existing database.
1931         *
1932         * Call this method to create an XML schema string from an existing database.
1933         * If the data parameter is set to TRUE, AXMLS will include the data from the database
1934         * in the schema. 
1935         *
1936         * @param boolean $data Include data in schema dump
1937         * @return string Generated XML schema
1938         */
1939         function ExtractSchema( $data = FALSE ) {
1940                 $old_mode = $this->db->SetFetchMode( ADODB_FETCH_NUM );
1941                 
1942                 $schema = '<?xml version="1.0"?>' . "\n"
1943                                 . '<schema version="' . $this->schemaVersion . '">' . "\n";
1944                 
1945                 if( is_array( $tables = $this->db->MetaTables( 'TABLES' ) ) ) {
1946                         foreach( $tables as $table ) {
1947                                 $schema .= '    <table name="' . $table . '">' . "\n";
1948                                 
1949                                 // grab details from database
1950                                 $rs = $this->db->Execute( 'SELECT * FROM ' . $table . ' WHERE 1=1' );
1951                                 $fields = $this->db->MetaColumns( $table );
1952                                 $indexes = $this->db->MetaIndexes( $table );
1953                                 
1954                                 if( is_array( $fields ) ) {
1955                                         foreach( $fields as $details ) {
1956                                                 $extra = '';
1957                                                 $content = array();
1958                                                 
1959                                                 if( $details->max_length > 0 ) {
1960                                                         $extra .= ' size="' . $details->max_length . '"';
1961                                                 }
1962                                                 
1963                                                 if( $details->primary_key ) {
1964                                                         $content[] = '<KEY/>';
1965                                                 } elseif( $details->not_null ) {
1966                                                         $content[] = '<NOTNULL/>';
1967                                                 }
1968                                                 
1969                                                 if( $details->has_default ) {
1970                                                         $content[] = '<DEFAULT value="' . $details->default_value . '"/>';
1971                                                 }
1972                                                 
1973                                                 if( $details->auto_increment ) {
1974                                                         $content[] = '<AUTOINCREMENT/>';
1975                                                 }
1976                                                 
1977                                                 // this stops the creation of 'R' columns,
1978                                                 // AUTOINCREMENT is used to create auto columns
1979                                                 $details->primary_key = 0;
1980                                                 $type = $rs->MetaType( $details );
1981                                                 
1982                                                 $schema .= '            <field name="' . $details->name . '" type="' . $type . '"' . $extra . '>';
1983                                                 
1984                                                 if( !empty( $content ) ) {
1985                                                         $schema .= "\n                  " . implode( "\n                        ", $content ) . "\n             ";
1986                                                 }
1987                                                 
1988                                                 $schema .= '</field>' . "\n";
1989                                         }
1990                                 }
1991                                 
1992                                 if( is_array( $indexes ) ) {
1993                                         foreach( $indexes as $index => $details ) {
1994                                                 $schema .= '            <index name="' . $index . '">' . "\n";
1995                                                 
1996                                                 if( $details['unique'] ) {
1997                                                         $schema .= '                    <UNIQUE/>' . "\n";
1998                                                 }
1999                                                 
2000                                                 foreach( $details['columns'] as $column ) {
2001                                                         $schema .= '                    <col>' . $column . '</col>' . "\n";
2002                                                 }
2003                                                 
2004                                                 $schema .= '            </index>' . "\n";
2005                                         }
2006                                 }
2007                                 
2008                                 if( $data ) {
2009                                         $rs = $this->db->Execute( 'SELECT * FROM ' . $table );
2010                                         
2011                                         if( is_object( $rs ) ) {
2012                                                 $schema .= '            <data>' . "\n";
2013                                                 
2014                                                 while( $row = $rs->FetchRow() ) {
2015                                                         foreach( $row as $key => $val ) {
2016                                                                 $row[$key] = htmlentities($val);
2017                                                         }
2018                                                         
2019                                                         $schema .= '                    <row><f>' . implode( '</f><f>', $row ) . '</f></row>' . "\n";
2020                                                 }
2021                                                 
2022                                                 $schema .= '            </data>' . "\n";
2023                                         }
2024                                 }
2025                                 
2026                                 $schema .= '    </table>' . "\n";
2027                         }
2028                 }
2029                 
2030                 $this->db->SetFetchMode( $old_mode );
2031                 
2032                 $schema .= '</schema>';
2033                 return $schema;
2034         }
2035         
2036         /**
2037         * Sets a prefix for database objects
2038         *
2039         * Call this method to set a standard prefix that will be prepended to all database tables 
2040         * and indices when the schema is parsed. Calling setPrefix with no arguments clears the prefix.
2041         *
2042         * @param string $prefix Prefix that will be prepended.
2043         * @param boolean $underscore If TRUE, automatically append an underscore character to the prefix.
2044         * @return boolean TRUE if successful, else FALSE
2045         */
2046         function SetPrefix( $prefix = '', $underscore = TRUE ) {
2047                 switch( TRUE ) {
2048                         // clear prefix
2049                         case empty( $prefix ):
2050                                 logMsg( 'Cleared prefix' );
2051                                 $this->objectPrefix = '';
2052                                 return TRUE;
2053                         // prefix too long
2054                         case strlen( $prefix ) > XMLS_PREFIX_MAXLEN:
2055                         // prefix contains invalid characters
2056                         case !preg_match( '/^[a-z][a-z0-9_]+$/i', $prefix ):
2057                                 logMsg( 'Invalid prefix: ' . $prefix );
2058                                 return FALSE;
2059                 }
2060                 
2061                 if( $underscore AND substr( $prefix, -1 ) != '_' ) {
2062                         $prefix .= '_';
2063                 }
2064                 
2065                 // prefix valid
2066                 logMsg( 'Set prefix: ' . $prefix );
2067                 $this->objectPrefix = $prefix;
2068                 return TRUE;
2069         }
2070         
2071         /**
2072         * Returns an object name with the current prefix prepended.
2073         *
2074         * @param string $name Name
2075         * @return string        Prefixed name
2076         *
2077         * @access private
2078         */
2079         function prefix( $name = '' ) {
2080                 // if prefix is set
2081                 if( !empty( $this->objectPrefix ) ) {
2082                         // Prepend the object prefix to the table name
2083                         // prepend after quote if used
2084                         return preg_replace( '/^(`?)(.+)$/', '$1' . $this->objectPrefix . '$2', $name );
2085                 }
2086                 
2087                 // No prefix set. Use name provided.
2088                 return $name;
2089         }
2090         
2091         /**
2092         * Checks if element references a specific platform
2093         *
2094         * @param string $platform Requested platform
2095         * @returns boolean TRUE if platform check succeeds
2096         *
2097         * @access private
2098         */
2099         function supportedPlatform( $platform = NULL ) {
2100                 $regex = '/^(\w*\|)*' . $this->db->databaseType . '(\|\w*)*$/';
2101                 
2102                 if( !isset( $platform ) OR preg_match( $regex, $platform ) ) {
2103                         logMsg( "Platform $platform is supported" );
2104                         return TRUE;
2105                 } else {
2106                         logMsg( "Platform $platform is NOT supported" );
2107                         return FALSE;
2108                 }
2109         }
2110         
2111         /**
2112         * Clears the array of generated SQL.
2113         *
2114         * @access private
2115         */
2116         function clearSQL() {
2117                 $this->sqlArray = array();
2118         }
2119         
2120         /**
2121         * Adds SQL into the SQL array.
2122         *
2123         * @param mixed $sql SQL to Add
2124         * @return boolean TRUE if successful, else FALSE.
2125         *
2126         * @access private
2127         */      
2128         function addSQL( $sql = NULL ) {
2129                 if( is_array( $sql ) ) {
2130                         foreach( $sql as $line ) {
2131                                 $this->addSQL( $line );
2132                         }
2133                         
2134                         return TRUE;
2135                 }
2136                 
2137                 if( is_string( $sql ) ) {
2138                         $this->sqlArray[] = $sql;
2139                         
2140                         // if executeInline is enabled, and either no errors have occurred or continueOnError is enabled, execute SQL.
2141                         if( $this->ExecuteInline() && ( $this->success == 2 || $this->ContinueOnError() ) ) {
2142                                 $saved = $this->db->debug;
2143                                 $this->db->debug = $this->debug;
2144                                 $ok = $this->db->Execute( $sql );
2145                                 $this->db->debug = $saved;
2146                                 
2147                                 if( !$ok ) {
2148                                         if( $this->debug ) {
2149                                                 ADOConnection::outp( $this->db->ErrorMsg() );
2150                                         }
2151                                         
2152                                         $this->success = 1;
2153                                 }
2154                         }
2155                         
2156                         return TRUE;
2157                 }
2158                 
2159                 return FALSE;
2160         }
2161         
2162         /**
2163         * Gets the SQL array in the specified format.
2164         *
2165         * @param string $format Format
2166         * @return mixed SQL
2167         *       
2168         * @access private
2169         */
2170         function getSQL( $format = NULL, $sqlArray = NULL ) {
2171                 if( !is_array( $sqlArray ) ) {
2172                         $sqlArray = $this->sqlArray;
2173                 }
2174                 
2175                 if( !is_array( $sqlArray ) ) {
2176                         return FALSE;
2177                 }
2178                 
2179                 switch( strtolower( $format ) ) {
2180                         case 'string':
2181                         case 'text':
2182                                 return !empty( $sqlArray ) ? implode( ";\n\n", $sqlArray ) . ';' : '';
2183                         case'html':
2184                                 return !empty( $sqlArray ) ? nl2br( htmlentities( implode( ";\n\n", $sqlArray ) . ';' ) ) : '';
2185                 }
2186                 
2187                 return $this->sqlArray;
2188         }
2189         
2190         /**
2191         * Destroys an adoSchema object.
2192         *
2193         * Call this method to clean up after an adoSchema object that is no longer in use.
2194         * @deprecated adoSchema now cleans up automatically.
2195         */
2196         function Destroy() {
2197                 ini_set("magic_quotes_runtime", $this->mgq );
2198                 #set_magic_quotes_runtime( $this->mgq );
2199                 unset( $this );
2200         }
2201 }
2202
2203 /**
2204 * Message logging function
2205 *
2206 * @access private
2207 */
2208 function logMsg( $msg, $title = NULL, $force = FALSE ) {
2209         if( XMLS_DEBUG or $force ) {
2210                 echo '<pre>';
2211                 
2212                 if( isset( $title ) ) {
2213                         echo '<h3>' . htmlentities( $title ) . '</h3>';
2214                 }
2215                 
2216                 if( is_object( $this ) ) {
2217                         echo '[' . get_class( $this ) . '] ';
2218                 }
2219                 
2220                 print_r( $msg );
2221                 
2222                 echo '</pre>';
2223         }
2224 }
2225 ?>