[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

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


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1