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