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