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