[ Index ]
 

Code source de vtiger CRM 5.0.2

Accédez au Source d'autres logiciels libresSoutenez Angelica Josefina !

title

Body

[fermer]

/adodb/ -> adodb-xmlschema.inc.php (source)

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


Généré le : Sun Feb 25 10:22:19 2007 par Balluche grâce à PHPXref 0.7