Differences Between: [Versions 310 and 311] [Versions 311 and 400] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403] [Versions 39 and 311]
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 System debug 1259 * @access private 1260 */ 1261 var $debug; 1262 1263 /** 1264 * @var string Regular expression to find schema version 1265 * @access private 1266 */ 1267 var $versionRegex = '/<schema.*?( version="([^"]*)")?.*?>/'; 1268 1269 /** 1270 * @var string Current schema version 1271 * @access private 1272 */ 1273 var $schemaVersion; 1274 1275 /** 1276 * @var int Success of last Schema execution 1277 */ 1278 var $success; 1279 1280 /** 1281 * @var bool Execute SQL inline as it is generated 1282 */ 1283 var $executeInline; 1284 1285 /** 1286 * @var bool Continue SQL execution if errors occur 1287 */ 1288 var $continueOnError; 1289 1290 /** 1291 * Creates an adoSchema object 1292 * 1293 * Creating an adoSchema object is the first step in processing an XML schema. 1294 * The only parameter is an ADOdb database connection object, which must already 1295 * have been created. 1296 * 1297 * @param object $db ADOdb database connection object. 1298 */ 1299 function __construct( $db ) { 1300 $this->db = $db; 1301 $this->debug = $this->db->debug; 1302 $this->dict = NewDataDictionary( $this->db ); 1303 $this->sqlArray = array(); 1304 $this->schemaVersion = XMLS_SCHEMA_VERSION; 1305 $this->executeInline( XMLS_EXECUTE_INLINE ); 1306 $this->continueOnError( XMLS_CONTINUE_ON_ERROR ); 1307 $this->setUpgradeMethod(); 1308 } 1309 1310 /** 1311 * Sets the method to be used for upgrading an existing database 1312 * 1313 * Use this method to specify how existing database objects should be upgraded. 1314 * The method option can be set to ALTER, REPLACE, BEST, or NONE. ALTER attempts to 1315 * alter each database object directly, REPLACE attempts to rebuild each object 1316 * from scratch, BEST attempts to determine the best upgrade method for each 1317 * object, and NONE disables upgrading. 1318 * 1319 * This method is not yet used by AXMLS, but exists for backward compatibility. 1320 * The ALTER method is automatically assumed when the adoSchema object is 1321 * instantiated; other upgrade methods are not currently supported. 1322 * 1323 * @param string $method Upgrade method (ALTER|REPLACE|BEST|NONE) 1324 * @returns string Upgrade method used 1325 */ 1326 function SetUpgradeMethod( $method = '' ) { 1327 if( !is_string( $method ) ) { 1328 return FALSE; 1329 } 1330 1331 $method = strtoupper( $method ); 1332 1333 // Handle the upgrade methods 1334 switch( $method ) { 1335 case 'ALTER': 1336 $this->upgrade = $method; 1337 break; 1338 case 'REPLACE': 1339 $this->upgrade = $method; 1340 break; 1341 case 'BEST': 1342 $this->upgrade = 'ALTER'; 1343 break; 1344 case 'NONE': 1345 $this->upgrade = 'NONE'; 1346 break; 1347 default: 1348 // Use default if no legitimate method is passed. 1349 $this->upgrade = XMLS_DEFAULT_UPGRADE_METHOD; 1350 } 1351 1352 return $this->upgrade; 1353 } 1354 1355 /** 1356 * Enables/disables inline SQL execution. 1357 * 1358 * Call this method to enable or disable inline execution of the schema. If the mode is set to TRUE (inline execution), 1359 * AXMLS applies the SQL to the database immediately as each schema entity is parsed. If the mode 1360 * is set to FALSE (post execution), AXMLS parses the entire schema and you will need to call adoSchema::ExecuteSchema() 1361 * to apply the schema to the database. 1362 * 1363 * @param bool $mode execute 1364 * @return bool current execution mode 1365 * 1366 * @see ParseSchema(), ExecuteSchema() 1367 */ 1368 function ExecuteInline( $mode = NULL ) { 1369 if( is_bool( $mode ) ) { 1370 $this->executeInline = $mode; 1371 } 1372 1373 return $this->executeInline; 1374 } 1375 1376 /** 1377 * Enables/disables SQL continue on error. 1378 * 1379 * Call this method to enable or disable continuation of SQL execution if an error occurs. 1380 * If the mode is set to TRUE (continue), AXMLS will continue to apply SQL to the database, even if an error occurs. 1381 * If the mode is set to FALSE (halt), AXMLS will halt execution of generated sql if an error occurs, though parsing 1382 * of the schema will continue. 1383 * 1384 * @param bool $mode execute 1385 * @return bool current continueOnError mode 1386 * 1387 * @see addSQL(), ExecuteSchema() 1388 */ 1389 function ContinueOnError( $mode = NULL ) { 1390 if( is_bool( $mode ) ) { 1391 $this->continueOnError = $mode; 1392 } 1393 1394 return $this->continueOnError; 1395 } 1396 1397 /** 1398 * Loads an XML schema from a file and converts it to SQL. 1399 * 1400 * Call this method to load the specified schema (see the DTD for the proper format) from 1401 * the filesystem and generate the SQL necessary to create the database described. 1402 * @see ParseSchemaString() 1403 * 1404 * @param string $file Name of XML schema file. 1405 * @param bool $returnSchema Return schema rather than parsing. 1406 * @return array Array of SQL queries, ready to execute 1407 */ 1408 function ParseSchema( $filename, $returnSchema = FALSE ) { 1409 return $this->ParseSchemaString( $this->ConvertSchemaFile( $filename ), $returnSchema ); 1410 } 1411 1412 /** 1413 * Loads an XML schema from a file and converts it to SQL. 1414 * 1415 * Call this method to load the specified schema from a file (see the DTD for the proper format) 1416 * and generate the SQL necessary to create the database described by the schema. 1417 * 1418 * @param string $file Name of XML schema file. 1419 * @param bool $returnSchema Return schema rather than parsing. 1420 * @return array Array of SQL queries, ready to execute. 1421 * 1422 * @deprecated Replaced by adoSchema::ParseSchema() and adoSchema::ParseSchemaString() 1423 * @see ParseSchema(), ParseSchemaString() 1424 */ 1425 function ParseSchemaFile( $filename, $returnSchema = FALSE ) { 1426 // Open the file 1427 if( !($fp = fopen( $filename, 'r' )) ) { 1428 // die( 'Unable to open file' ); 1429 return FALSE; 1430 } 1431 1432 // do version detection here 1433 if( $this->SchemaFileVersion( $filename ) != $this->schemaVersion ) { 1434 return FALSE; 1435 } 1436 1437 if ( $returnSchema ) 1438 { 1439 $xmlstring = ''; 1440 while( $data = fread( $fp, 100000 ) ) { 1441 $xmlstring .= $data; 1442 } 1443 return $xmlstring; 1444 } 1445 1446 $this->success = 2; 1447 1448 $xmlParser = $this->create_parser(); 1449 1450 // Process the file 1451 while( $data = fread( $fp, 4096 ) ) { 1452 if( !xml_parse( $xmlParser, $data, feof( $fp ) ) ) { 1453 die( sprintf( 1454 "XML error: %s at line %d", 1455 xml_error_string( xml_get_error_code( $xmlParser) ), 1456 xml_get_current_line_number( $xmlParser) 1457 ) ); 1458 } 1459 } 1460 1461 xml_parser_free( $xmlParser ); 1462 1463 return $this->sqlArray; 1464 } 1465 1466 /** 1467 * Converts an XML schema string to SQL. 1468 * 1469 * Call this method to parse a string containing an XML schema (see the DTD for the proper format) 1470 * and generate the SQL necessary to create the database described by the schema. 1471 * @see ParseSchema() 1472 * 1473 * @param string $xmlstring XML schema string. 1474 * @param bool $returnSchema Return schema rather than parsing. 1475 * @return array Array of SQL queries, ready to execute. 1476 */ 1477 function ParseSchemaString( $xmlstring, $returnSchema = FALSE ) { 1478 if( !is_string( $xmlstring ) OR empty( $xmlstring ) ) { 1479 return FALSE; 1480 } 1481 1482 // do version detection here 1483 if( $this->SchemaStringVersion( $xmlstring ) != $this->schemaVersion ) { 1484 return FALSE; 1485 } 1486 1487 if ( $returnSchema ) 1488 { 1489 return $xmlstring; 1490 } 1491 1492 $this->success = 2; 1493 1494 $xmlParser = $this->create_parser(); 1495 1496 if( !xml_parse( $xmlParser, $xmlstring, TRUE ) ) { 1497 die( sprintf( 1498 "XML error: %s at line %d", 1499 xml_error_string( xml_get_error_code( $xmlParser) ), 1500 xml_get_current_line_number( $xmlParser) 1501 ) ); 1502 } 1503 1504 xml_parser_free( $xmlParser ); 1505 1506 return $this->sqlArray; 1507 } 1508 1509 /** 1510 * Loads an XML schema from a file and converts it to uninstallation SQL. 1511 * 1512 * Call this method to load the specified schema (see the DTD for the proper format) from 1513 * the filesystem and generate the SQL necessary to remove the database described. 1514 * @see RemoveSchemaString() 1515 * 1516 * @param string $file Name of XML schema file. 1517 * @param bool $returnSchema Return schema rather than parsing. 1518 * @return array Array of SQL queries, ready to execute 1519 */ 1520 function RemoveSchema( $filename, $returnSchema = FALSE ) { 1521 return $this->RemoveSchemaString( $this->ConvertSchemaFile( $filename ), $returnSchema ); 1522 } 1523 1524 /** 1525 * Converts an XML schema string to uninstallation SQL. 1526 * 1527 * Call this method to parse a string containing an XML schema (see the DTD for the proper format) 1528 * and generate the SQL necessary to uninstall the database described by the schema. 1529 * @see RemoveSchema() 1530 * 1531 * @param string $schema XML schema string. 1532 * @param bool $returnSchema Return schema rather than parsing. 1533 * @return array Array of SQL queries, ready to execute. 1534 */ 1535 function RemoveSchemaString( $schema, $returnSchema = FALSE ) { 1536 1537 // grab current version 1538 if( !( $version = $this->SchemaStringVersion( $schema ) ) ) { 1539 return FALSE; 1540 } 1541 1542 return $this->ParseSchemaString( $this->TransformSchema( $schema, 'remove-' . $version), $returnSchema ); 1543 } 1544 1545 /** 1546 * Applies the current XML schema to the database (post execution). 1547 * 1548 * Call this method to apply the current schema (generally created by calling 1549 * ParseSchema() or ParseSchemaString() ) to the database (creating the tables, indexes, 1550 * and executing other SQL specified in the schema) after parsing. 1551 * @see ParseSchema(), ParseSchemaString(), ExecuteInline() 1552 * 1553 * @param array $sqlArray Array of SQL statements that will be applied rather than 1554 * the current schema. 1555 * @param boolean $continueOnErr Continue to apply the schema even if an error occurs. 1556 * @returns integer 0 if failure, 1 if errors, 2 if successful. 1557 */ 1558 function ExecuteSchema( $sqlArray = NULL, $continueOnErr = NULL ) { 1559 if( !is_bool( $continueOnErr ) ) { 1560 $continueOnErr = $this->ContinueOnError(); 1561 } 1562 1563 if( !isset( $sqlArray ) ) { 1564 $sqlArray = $this->sqlArray; 1565 } 1566 1567 if( !is_array( $sqlArray ) ) { 1568 $this->success = 0; 1569 } else { 1570 $this->success = $this->dict->ExecuteSQLArray( $sqlArray, $continueOnErr ); 1571 } 1572 1573 return $this->success; 1574 } 1575 1576 /** 1577 * Returns the current SQL array. 1578 * 1579 * Call this method to fetch the array of SQL queries resulting from 1580 * ParseSchema() or ParseSchemaString(). 1581 * 1582 * @param string $format Format: HTML, TEXT, or NONE (PHP array) 1583 * @return array Array of SQL statements or FALSE if an error occurs 1584 */ 1585 function PrintSQL( $format = 'NONE' ) { 1586 $sqlArray = null; 1587 return $this->getSQL( $format, $sqlArray ); 1588 } 1589 1590 /** 1591 * Saves the current SQL array to the local filesystem as a list of SQL queries. 1592 * 1593 * Call this method to save the array of SQL queries (generally resulting from a 1594 * parsed XML schema) to the filesystem. 1595 * 1596 * @param string $filename Path and name where the file should be saved. 1597 * @return boolean TRUE if save is successful, else FALSE. 1598 */ 1599 function SaveSQL( $filename = './schema.sql' ) { 1600 1601 if( !isset( $sqlArray ) ) { 1602 $sqlArray = $this->sqlArray; 1603 } 1604 if( !isset( $sqlArray ) ) { 1605 return FALSE; 1606 } 1607 1608 $fp = fopen( $filename, "w" ); 1609 1610 foreach( $sqlArray as $key => $query ) { 1611 fwrite( $fp, $query . ";\n" ); 1612 } 1613 fclose( $fp ); 1614 } 1615 1616 /** 1617 * Create an xml parser 1618 * 1619 * @return object PHP XML parser object 1620 * 1621 * @access private 1622 */ 1623 function create_parser() { 1624 // Create the parser 1625 $xmlParser = xml_parser_create(); 1626 xml_set_object( $xmlParser, $this ); 1627 1628 // Initialize the XML callback functions 1629 xml_set_element_handler( $xmlParser, '_tag_open', '_tag_close' ); 1630 xml_set_character_data_handler( $xmlParser, '_tag_cdata' ); 1631 1632 return $xmlParser; 1633 } 1634 1635 /** 1636 * XML Callback to process start elements 1637 * 1638 * @access private 1639 */ 1640 function _tag_open( &$parser, $tag, $attributes ) { 1641 switch( strtoupper( $tag ) ) { 1642 case 'TABLE': 1643 $this->obj = new dbTable( $this, $attributes ); 1644 xml_set_object( $parser, $this->obj ); 1645 break; 1646 case 'SQL': 1647 if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) { 1648 $this->obj = new dbQuerySet( $this, $attributes ); 1649 xml_set_object( $parser, $this->obj ); 1650 } 1651 break; 1652 default: 1653 // print_r( array( $tag, $attributes ) ); 1654 } 1655 1656 } 1657 1658 /** 1659 * XML Callback to process CDATA elements 1660 * 1661 * @access private 1662 */ 1663 function _tag_cdata( &$parser, $cdata ) { 1664 } 1665 1666 /** 1667 * XML Callback to process end elements 1668 * 1669 * @access private 1670 * @internal 1671 */ 1672 function _tag_close( &$parser, $tag ) { 1673 1674 } 1675 1676 /** 1677 * Converts an XML schema string to the specified DTD version. 1678 * 1679 * Call this method to convert a string containing an XML schema to a different AXMLS 1680 * DTD version. For instance, to convert a schema created for an pre-1.0 version for 1681 * AXMLS (DTD version 0.1) to a newer version of the DTD (e.g. 0.2). If no DTD version 1682 * parameter is specified, the schema will be converted to the current DTD version. 1683 * If the newFile parameter is provided, the converted schema will be written to the specified 1684 * file. 1685 * @see ConvertSchemaFile() 1686 * 1687 * @param string $schema String containing XML schema that will be converted. 1688 * @param string $newVersion DTD version to convert to. 1689 * @param string $newFile File name of (converted) output file. 1690 * @return string Converted XML schema or FALSE if an error occurs. 1691 */ 1692 function ConvertSchemaString( $schema, $newVersion = NULL, $newFile = NULL ) { 1693 1694 // grab current version 1695 if( !( $version = $this->SchemaStringVersion( $schema ) ) ) { 1696 return FALSE; 1697 } 1698 1699 if( !isset ($newVersion) ) { 1700 $newVersion = $this->schemaVersion; 1701 } 1702 1703 if( $version == $newVersion ) { 1704 $result = $schema; 1705 } else { 1706 $result = $this->TransformSchema( $schema, 'convert-' . $version . '-' . $newVersion); 1707 } 1708 1709 if( is_string( $result ) AND is_string( $newFile ) AND ( $fp = fopen( $newFile, 'w' ) ) ) { 1710 fwrite( $fp, $result ); 1711 fclose( $fp ); 1712 } 1713 1714 return $result; 1715 } 1716 1717 // compat for pre-4.3 - jlim 1718 function _file_get_contents($path) 1719 { 1720 if (function_exists('file_get_contents')) return file_get_contents($path); 1721 return join('',file($path)); 1722 } 1723 1724 /** 1725 * Converts an XML schema file to the specified DTD version. 1726 * 1727 * Call this method to convert the specified XML schema file to a different AXMLS 1728 * DTD version. For instance, to convert a schema created for an pre-1.0 version for 1729 * AXMLS (DTD version 0.1) to a newer version of the DTD (e.g. 0.2). If no DTD version 1730 * parameter is specified, the schema will be converted to the current DTD version. 1731 * If the newFile parameter is provided, the converted schema will be written to the specified 1732 * file. 1733 * @see ConvertSchemaString() 1734 * 1735 * @param string $filename Name of XML schema file that will be converted. 1736 * @param string $newVersion DTD version to convert to. 1737 * @param string $newFile File name of (converted) output file. 1738 * @return string Converted XML schema or FALSE if an error occurs. 1739 */ 1740 function ConvertSchemaFile( $filename, $newVersion = NULL, $newFile = NULL ) { 1741 1742 // grab current version 1743 if( !( $version = $this->SchemaFileVersion( $filename ) ) ) { 1744 return FALSE; 1745 } 1746 1747 if( !isset ($newVersion) ) { 1748 $newVersion = $this->schemaVersion; 1749 } 1750 1751 if( $version == $newVersion ) { 1752 $result = _file_get_contents( $filename ); 1753 1754 // remove unicode BOM if present 1755 if( substr( $result, 0, 3 ) == sprintf( '%c%c%c', 239, 187, 191 ) ) { 1756 $result = substr( $result, 3 ); 1757 } 1758 } else { 1759 $result = $this->TransformSchema( $filename, 'convert-' . $version . '-' . $newVersion, 'file' ); 1760 } 1761 1762 if( is_string( $result ) AND is_string( $newFile ) AND ( $fp = fopen( $newFile, 'w' ) ) ) { 1763 fwrite( $fp, $result ); 1764 fclose( $fp ); 1765 } 1766 1767 return $result; 1768 } 1769 1770 function TransformSchema( $schema, $xsl, $schematype='string' ) 1771 { 1772 // Fail if XSLT extension is not available 1773 if( ! function_exists( 'xslt_create' ) ) { 1774 return FALSE; 1775 } 1776 1777 $xsl_file = dirname( __FILE__ ) . '/xsl/' . $xsl . '.xsl'; 1778 1779 // look for xsl 1780 if( !is_readable( $xsl_file ) ) { 1781 return FALSE; 1782 } 1783 1784 switch( $schematype ) 1785 { 1786 case 'file': 1787 if( !is_readable( $schema ) ) { 1788 return FALSE; 1789 } 1790 1791 $schema = _file_get_contents( $schema ); 1792 break; 1793 case 'string': 1794 default: 1795 if( !is_string( $schema ) ) { 1796 return FALSE; 1797 } 1798 } 1799 1800 $arguments = array ( 1801 '/_xml' => $schema, 1802 '/_xsl' => _file_get_contents( $xsl_file ) 1803 ); 1804 1805 // create an XSLT processor 1806 $xh = xslt_create (); 1807 1808 // set error handler 1809 xslt_set_error_handler ($xh, array (&$this, 'xslt_error_handler')); 1810 1811 // process the schema 1812 $result = xslt_process ($xh, 'arg:/_xml', 'arg:/_xsl', NULL, $arguments); 1813 1814 xslt_free ($xh); 1815 1816 return $result; 1817 } 1818 1819 /** 1820 * Processes XSLT transformation errors 1821 * 1822 * @param object $parser XML parser object 1823 * @param integer $errno Error number 1824 * @param integer $level Error level 1825 * @param array $fields Error information fields 1826 * 1827 * @access private 1828 */ 1829 function xslt_error_handler( $parser, $errno, $level, $fields ) { 1830 if( is_array( $fields ) ) { 1831 $msg = array( 1832 'Message Type' => ucfirst( $fields['msgtype'] ), 1833 'Message Code' => $fields['code'], 1834 'Message' => $fields['msg'], 1835 'Error Number' => $errno, 1836 'Level' => $level 1837 ); 1838 1839 switch( $fields['URI'] ) { 1840 case 'arg:/_xml': 1841 $msg['Input'] = 'XML'; 1842 break; 1843 case 'arg:/_xsl': 1844 $msg['Input'] = 'XSL'; 1845 break; 1846 default: 1847 $msg['Input'] = $fields['URI']; 1848 } 1849 1850 $msg['Line'] = $fields['line']; 1851 } else { 1852 $msg = array( 1853 'Message Type' => 'Error', 1854 'Error Number' => $errno, 1855 'Level' => $level, 1856 'Fields' => var_export( $fields, TRUE ) 1857 ); 1858 } 1859 1860 $error_details = $msg['Message Type'] . ' in XSLT Transformation' . "\n" 1861 . '<table>' . "\n"; 1862 1863 foreach( $msg as $label => $details ) { 1864 $error_details .= '<tr><td><b>' . $label . ': </b></td><td>' . htmlentities( $details ) . '</td></tr>' . "\n"; 1865 } 1866 1867 $error_details .= '</table>'; 1868 1869 trigger_error( $error_details, E_USER_ERROR ); 1870 } 1871 1872 /** 1873 * Returns the AXMLS Schema Version of the requested XML schema file. 1874 * 1875 * Call this method to obtain the AXMLS DTD version of the requested XML schema file. 1876 * @see SchemaStringVersion() 1877 * 1878 * @param string $filename AXMLS schema file 1879 * @return string Schema version number or FALSE on error 1880 */ 1881 function SchemaFileVersion( $filename ) { 1882 // Open the file 1883 if( !($fp = fopen( $filename, 'r' )) ) { 1884 // die( 'Unable to open file' ); 1885 return FALSE; 1886 } 1887 1888 // Process the file 1889 while( $data = fread( $fp, 4096 ) ) { 1890 if( preg_match( $this->versionRegex, $data, $matches ) ) { 1891 return !empty( $matches[2] ) ? $matches[2] : XMLS_DEFAULT_SCHEMA_VERSION; 1892 } 1893 } 1894 1895 return FALSE; 1896 } 1897 1898 /** 1899 * Returns the AXMLS Schema Version of the provided XML schema string. 1900 * 1901 * Call this method to obtain the AXMLS DTD version of the provided XML schema string. 1902 * @see SchemaFileVersion() 1903 * 1904 * @param string $xmlstring XML schema string 1905 * @return string Schema version number or FALSE on error 1906 */ 1907 function SchemaStringVersion( $xmlstring ) { 1908 if( !is_string( $xmlstring ) OR empty( $xmlstring ) ) { 1909 return FALSE; 1910 } 1911 1912 if( preg_match( $this->versionRegex, $xmlstring, $matches ) ) { 1913 return !empty( $matches[2] ) ? $matches[2] : XMLS_DEFAULT_SCHEMA_VERSION; 1914 } 1915 1916 return FALSE; 1917 } 1918 1919 /** 1920 * Extracts an XML schema from an existing database. 1921 * 1922 * Call this method to create an XML schema string from an existing database. 1923 * If the data parameter is set to TRUE, AXMLS will include the data from the database 1924 * in the schema. 1925 * 1926 * @param boolean $data Include data in schema dump 1927 * @return string Generated XML schema 1928 */ 1929 function ExtractSchema( $data = FALSE ) { 1930 $old_mode = $this->db->SetFetchMode( ADODB_FETCH_NUM ); 1931 1932 $schema = '<?xml version="1.0"?>' . "\n" 1933 . '<schema version="' . $this->schemaVersion . '">' . "\n"; 1934 1935 if( is_array( $tables = $this->db->MetaTables( 'TABLES' ) ) ) { 1936 foreach( $tables as $table ) { 1937 $schema .= ' <table name="' . $table . '">' . "\n"; 1938 1939 // grab details from database 1940 $rs = $this->db->Execute( 'SELECT * FROM ' . $table . ' WHERE 1=1' ); 1941 $fields = $this->db->MetaColumns( $table ); 1942 $indexes = $this->db->MetaIndexes( $table ); 1943 1944 if( is_array( $fields ) ) { 1945 foreach( $fields as $details ) { 1946 $extra = ''; 1947 $content = array(); 1948 1949 if( $details->max_length > 0 ) { 1950 $extra .= ' size="' . $details->max_length . '"'; 1951 } 1952 1953 if( $details->primary_key ) { 1954 $content[] = '<KEY/>'; 1955 } elseif( $details->not_null ) { 1956 $content[] = '<NOTNULL/>'; 1957 } 1958 1959 if( $details->has_default ) { 1960 $content[] = '<DEFAULT value="' . $details->default_value . '"/>'; 1961 } 1962 1963 if( $details->auto_increment ) { 1964 $content[] = '<AUTOINCREMENT/>'; 1965 } 1966 1967 // this stops the creation of 'R' columns, 1968 // AUTOINCREMENT is used to create auto columns 1969 $details->primary_key = 0; 1970 $type = $rs->MetaType( $details ); 1971 1972 $schema .= ' <field name="' . $details->name . '" type="' . $type . '"' . $extra . '>'; 1973 1974 if( !empty( $content ) ) { 1975 $schema .= "\n " . implode( "\n ", $content ) . "\n "; 1976 } 1977 1978 $schema .= '</field>' . "\n"; 1979 } 1980 } 1981 1982 if( is_array( $indexes ) ) { 1983 foreach( $indexes as $index => $details ) { 1984 $schema .= ' <index name="' . $index . '">' . "\n"; 1985 1986 if( $details['unique'] ) { 1987 $schema .= ' <UNIQUE/>' . "\n"; 1988 } 1989 1990 foreach( $details['columns'] as $column ) { 1991 $schema .= ' <col>' . $column . '</col>' . "\n"; 1992 } 1993 1994 $schema .= ' </index>' . "\n"; 1995 } 1996 } 1997 1998 if( $data ) { 1999 $rs = $this->db->Execute( 'SELECT * FROM ' . $table ); 2000 2001 if( is_object( $rs ) ) { 2002 $schema .= ' <data>' . "\n"; 2003 2004 while( $row = $rs->FetchRow() ) { 2005 foreach( $row as $key => $val ) { 2006 $row[$key] = htmlentities($val); 2007 } 2008 2009 $schema .= ' <row><f>' . implode( '</f><f>', $row ) . '</f></row>' . "\n"; 2010 } 2011 2012 $schema .= ' </data>' . "\n"; 2013 } 2014 } 2015 2016 $schema .= ' </table>' . "\n"; 2017 } 2018 } 2019 2020 $this->db->SetFetchMode( $old_mode ); 2021 2022 $schema .= '</schema>'; 2023 return $schema; 2024 } 2025 2026 /** 2027 * Sets a prefix for database objects 2028 * 2029 * Call this method to set a standard prefix that will be prepended to all database tables 2030 * and indices when the schema is parsed. Calling setPrefix with no arguments clears the prefix. 2031 * 2032 * @param string $prefix Prefix that will be prepended. 2033 * @param boolean $underscore If TRUE, automatically append an underscore character to the prefix. 2034 * @return boolean TRUE if successful, else FALSE 2035 */ 2036 function SetPrefix( $prefix = '', $underscore = TRUE ) { 2037 switch( TRUE ) { 2038 // clear prefix 2039 case empty( $prefix ): 2040 logMsg( 'Cleared prefix' ); 2041 $this->objectPrefix = ''; 2042 return TRUE; 2043 // prefix too long 2044 case strlen( $prefix ) > XMLS_PREFIX_MAXLEN: 2045 // prefix contains invalid characters 2046 case !preg_match( '/^[a-z][a-z0-9_]+$/i', $prefix ): 2047 logMsg( 'Invalid prefix: ' . $prefix ); 2048 return FALSE; 2049 } 2050 2051 if( $underscore AND substr( $prefix, -1 ) != '_' ) { 2052 $prefix .= '_'; 2053 } 2054 2055 // prefix valid 2056 logMsg( 'Set prefix: ' . $prefix ); 2057 $this->objectPrefix = $prefix; 2058 return TRUE; 2059 } 2060 2061 /** 2062 * Returns an object name with the current prefix prepended. 2063 * 2064 * @param string $name Name 2065 * @return string Prefixed name 2066 * 2067 * @access private 2068 */ 2069 function prefix( $name = '' ) { 2070 // if prefix is set 2071 if( !empty( $this->objectPrefix ) ) { 2072 // Prepend the object prefix to the table name 2073 // prepend after quote if used 2074 return preg_replace( '/^(`?)(.+)$/', '$1' . $this->objectPrefix . '$2', $name ); 2075 } 2076 2077 // No prefix set. Use name provided. 2078 return $name; 2079 } 2080 2081 /** 2082 * Checks if element references a specific platform 2083 * 2084 * @param string $platform Requested platform 2085 * @returns boolean TRUE if platform check succeeds 2086 * 2087 * @access private 2088 */ 2089 function supportedPlatform( $platform = NULL ) { 2090 $regex = '/^(\w*\|)*' . $this->db->databaseType . '(\|\w*)*$/'; 2091 2092 if( !isset( $platform ) OR preg_match( $regex, $platform ) ) { 2093 logMsg( "Platform $platform is supported" ); 2094 return TRUE; 2095 } else { 2096 logMsg( "Platform $platform is NOT supported" ); 2097 return FALSE; 2098 } 2099 } 2100 2101 /** 2102 * Clears the array of generated SQL. 2103 * 2104 * @access private 2105 */ 2106 function clearSQL() { 2107 $this->sqlArray = array(); 2108 } 2109 2110 /** 2111 * Adds SQL into the SQL array. 2112 * 2113 * @param mixed $sql SQL to Add 2114 * @return boolean TRUE if successful, else FALSE. 2115 * 2116 * @access private 2117 */ 2118 function addSQL( $sql = NULL ) { 2119 if( is_array( $sql ) ) { 2120 foreach( $sql as $line ) { 2121 $this->addSQL( $line ); 2122 } 2123 2124 return TRUE; 2125 } 2126 2127 if( is_string( $sql ) ) { 2128 $this->sqlArray[] = $sql; 2129 2130 // if executeInline is enabled, and either no errors have occurred or continueOnError is enabled, execute SQL. 2131 if( $this->ExecuteInline() && ( $this->success == 2 || $this->ContinueOnError() ) ) { 2132 $saved = $this->db->debug; 2133 $this->db->debug = $this->debug; 2134 $ok = $this->db->Execute( $sql ); 2135 $this->db->debug = $saved; 2136 2137 if( !$ok ) { 2138 if( $this->debug ) { 2139 ADOConnection::outp( $this->db->ErrorMsg() ); 2140 } 2141 2142 $this->success = 1; 2143 } 2144 } 2145 2146 return TRUE; 2147 } 2148 2149 return FALSE; 2150 } 2151 2152 /** 2153 * Gets the SQL array in the specified format. 2154 * 2155 * @param string $format Format 2156 * @return mixed SQL 2157 * 2158 * @access private 2159 */ 2160 function getSQL( $format = NULL, $sqlArray = NULL ) { 2161 if( !is_array( $sqlArray ) ) { 2162 $sqlArray = $this->sqlArray; 2163 } 2164 2165 if( !is_array( $sqlArray ) ) { 2166 return FALSE; 2167 } 2168 2169 switch( strtolower( $format ) ) { 2170 case 'string': 2171 case 'text': 2172 return !empty( $sqlArray ) ? implode( ";\n\n", $sqlArray ) . ';' : ''; 2173 case'html': 2174 return !empty( $sqlArray ) ? nl2br( htmlentities( implode( ";\n\n", $sqlArray ) . ';' ) ) : ''; 2175 } 2176 2177 return $this->sqlArray; 2178 } 2179 2180 /** 2181 * Destroys an adoSchema object. 2182 * 2183 * Call this method to clean up after an adoSchema object that is no longer in use. 2184 * @deprecated adoSchema now cleans up automatically. 2185 */ 2186 function Destroy() {} 2187 } 2188 2189 /** 2190 * Message logging function 2191 * 2192 * @access private 2193 */ 2194 function logMsg( $msg, $title = NULL, $force = FALSE ) { 2195 if( XMLS_DEBUG or $force ) { 2196 echo '<pre>'; 2197 2198 if( isset( $title ) ) { 2199 echo '<h3>' . htmlentities( $title ) . '</h3>'; 2200 } 2201 2202 if( is_object( $this ) ) { 2203 echo '[' . get_class( $this ) . '] '; 2204 } 2205 2206 print_r( $msg ); 2207 2208 echo '</pre>'; 2209 } 2210 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body