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