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