[ Index ]

PHP Cross Reference of vtigercrm-6.1.0

title

Body

[close]

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


Generated: Fri Nov 28 20:08:37 2014 Cross-referenced by PHPXref 0.7.1