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