Subversion Repositories svnkaklik

Rev

Details | Last modification | View Log

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