source: Dev/trunk/rdfapi/util/adodb/adodb-xmlschema.inc.php @ 12

Last change on this file since 12 was 12, checked in by basvannuland, 14 years ago

Added RAP RDF API
Added RDF reader writer for save and load survey

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