Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

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