Subversion Repositories svnkaklik

Rev

Go to most recent revision | Details | Last modification | View Log

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