source: moodle/trunk/fuentes/lib/adodb/adodb-xmlschema.inc.php @ 1331

Last change on this file since 1331 was 1331, checked in by jrpelegrina, 3 years ago

Updated to moodle 3.0.3

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