PageRenderTime 67ms CodeModel.GetById 26ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/rdfapi-php/api/util/adodb/adodb-xmlschema.inc.php

https://github.com/komagata/plnet
PHP | 2194 lines | 1012 code | 306 blank | 876 comment | 133 complexity | bf1fe091f05d14cbffac5c6c6a3a26fa MD5 | raw file
Possible License(s): LGPL-2.1

Large files files are truncated, but you can click here to view the full file

  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: tgauss $
  14. * @author Richard Tango-Lowy & Dan Cech
  15. * @version $Revision: 1.3 $
  16. *
  17. * @package axmls
  18. * @tutorial getting_started.pkg
  19. */
  20. /**
  21. * Debug on or off
  22. */
  23. if( !defined( 'XMLS_DEBUG' ) ) {
  24. define( 'XMLS_DEBUG', FALSE );
  25. }
  26. /**
  27. * Default prefix key
  28. */
  29. if( !defined( 'XMLS_PREFIX' ) ) {
  30. define( 'XMLS_PREFIX', '%%P' );
  31. }
  32. /**
  33. * Maximum length allowed for object prefix
  34. */
  35. if( !defined( 'XMLS_PREFIX_MAXLEN' ) ) {
  36. define( 'XMLS_PREFIX_MAXLEN', 10 );
  37. }
  38. /**
  39. * Execute SQL inline as it is generated
  40. */
  41. if( !defined( 'XMLS_EXECUTE_INLINE' ) ) {
  42. define( 'XMLS_EXECUTE_INLINE', FALSE );
  43. }
  44. /**
  45. * Continue SQL Execution if an error occurs?
  46. */
  47. if( !defined( 'XMLS_CONTINUE_ON_ERROR' ) ) {
  48. define( 'XMLS_CONTINUE_ON_ERROR', FALSE );
  49. }
  50. /**
  51. * Current Schema Version
  52. */
  53. if( !defined( 'XMLS_SCHEMA_VERSION' ) ) {
  54. define( 'XMLS_SCHEMA_VERSION', '0.2' );
  55. }
  56. /**
  57. * Default Schema Version. Used for Schemas without an explicit version set.
  58. */
  59. if( !defined( 'XMLS_DEFAULT_SCHEMA_VERSION' ) ) {
  60. define( 'XMLS_DEFAULT_SCHEMA_VERSION', '0.1' );
  61. }
  62. /**
  63. * Default Schema Version. Used for Schemas without an explicit version set.
  64. */
  65. if( !defined( 'XMLS_DEFAULT_UPGRADE_METHOD' ) ) {
  66. define( 'XMLS_DEFAULT_UPGRADE_METHOD', 'ALTER' );
  67. }
  68. /**
  69. * Include the main ADODB library
  70. */
  71. if( !defined( '_ADODB_LAYER' ) ) {
  72. require( 'adodb.inc.php' );
  73. require( 'adodb-datadict.inc.php' );
  74. }
  75. /**
  76. * Abstract DB Object. This class provides basic methods for database objects, such
  77. * as tables and indexes.
  78. *
  79. * @package axmls
  80. * @access private
  81. */
  82. class dbObject {
  83. /**
  84. * var object Parent
  85. */
  86. var $parent;
  87. /**
  88. * var string current element
  89. */
  90. var $currentElement;
  91. /**
  92. * NOP
  93. */
  94. function dbObject( &$parent, $attributes = NULL ) {
  95. $this->parent =& $parent;
  96. }
  97. /**
  98. * XML Callback to process start elements
  99. *
  100. * @access private
  101. */
  102. function _tag_open( &$parser, $tag, $attributes ) {
  103. }
  104. /**
  105. * XML Callback to process CDATA elements
  106. *
  107. * @access private
  108. */
  109. function _tag_cdata( &$parser, $cdata ) {
  110. }
  111. /**
  112. * XML Callback to process end elements
  113. *
  114. * @access private
  115. */
  116. function _tag_close( &$parser, $tag ) {
  117. }
  118. function create() {
  119. return array();
  120. }
  121. /**
  122. * Destroys the object
  123. */
  124. function destroy() {
  125. unset( $this );
  126. }
  127. /**
  128. * Checks whether the specified RDBMS is supported by the current
  129. * database object or its ranking ancestor.
  130. *
  131. * @param string $platform RDBMS platform name (from ADODB platform list).
  132. * @return boolean TRUE if RDBMS is supported; otherwise returns FALSE.
  133. */
  134. function supportedPlatform( $platform = NULL ) {
  135. return is_object( $this->parent ) ? $this->parent->supportedPlatform( $platform ) : TRUE;
  136. }
  137. /**
  138. * Returns the prefix set by the ranking ancestor of the database object.
  139. *
  140. * @param string $name Prefix string.
  141. * @return string Prefix.
  142. */
  143. function prefix( $name = '' ) {
  144. return is_object( $this->parent ) ? $this->parent->prefix( $name ) : $name;
  145. }
  146. /**
  147. * Extracts a field ID from the specified field.
  148. *
  149. * @param string $field Field.
  150. * @return string Field ID.
  151. */
  152. function FieldID( $field ) {
  153. return strtoupper( preg_replace( '/^`(.+)`$/', '$1', $field ) );
  154. }
  155. }
  156. /**
  157. * Creates a table object in ADOdb's datadict format
  158. *
  159. * This class stores information about a database table. As charactaristics
  160. * of the table are loaded from the external source, methods and properties
  161. * of this class are used to build up the table description in ADOdb's
  162. * datadict format.
  163. *
  164. * @package axmls
  165. * @access private
  166. */
  167. class dbTable extends dbObject {
  168. /**
  169. * @var string Table name
  170. */
  171. var $name;
  172. /**
  173. * @var array Field specifier: Meta-information about each field
  174. */
  175. var $fields = array();
  176. /**
  177. * @var array List of table indexes.
  178. */
  179. var $indexes = array();
  180. /**
  181. * @var array Table options: Table-level options
  182. */
  183. var $opts = array();
  184. /**
  185. * @var string Field index: Keeps track of which field is currently being processed
  186. */
  187. var $current_field;
  188. /**
  189. * @var boolean Mark table for destruction
  190. * @access private
  191. */
  192. var $drop_table;
  193. /**
  194. * @var boolean Mark field for destruction (not yet implemented)
  195. * @access private
  196. */
  197. var $drop_field = array();
  198. /**
  199. * Iniitializes a new table object.
  200. *
  201. * @param string $prefix DB Object prefix
  202. * @param array $attributes Array of table attributes.
  203. */
  204. function dbTable( &$parent, $attributes = NULL ) {
  205. $this->parent =& $parent;
  206. $this->name = $this->prefix($attributes['NAME']);
  207. }
  208. /**
  209. * XML Callback to process start elements. Elements currently
  210. * processed are: INDEX, DROP, FIELD, KEY, NOTNULL, AUTOINCREMENT & DEFAULT.
  211. *
  212. * @access private
  213. */
  214. function _tag_open( &$parser, $tag, $attributes ) {
  215. $this->currentElement = strtoupper( $tag );
  216. switch( $this->currentElement ) {
  217. case 'INDEX':
  218. if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) {
  219. xml_set_object( $parser, $this->addIndex( $attributes ) );
  220. }
  221. break;
  222. case 'DATA':
  223. if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) {
  224. xml_set_object( $parser, $this->addData( $attributes ) );
  225. }
  226. break;
  227. case 'DROP':
  228. $this->drop();
  229. break;
  230. case 'FIELD':
  231. // Add a field
  232. $fieldName = $attributes['NAME'];
  233. $fieldType = $attributes['TYPE'];
  234. $fieldSize = isset( $attributes['SIZE'] ) ? $attributes['SIZE'] : NULL;
  235. $fieldOpts = isset( $attributes['OPTS'] ) ? $attributes['OPTS'] : NULL;
  236. $this->addField( $fieldName, $fieldType, $fieldSize, $fieldOpts );
  237. break;
  238. case 'KEY':
  239. case 'NOTNULL':
  240. case 'AUTOINCREMENT':
  241. // Add a field option
  242. $this->addFieldOpt( $this->current_field, $this->currentElement );
  243. break;
  244. case 'DEFAULT':
  245. // Add a field option to the table object
  246. // Work around ADOdb datadict issue that misinterprets empty strings.
  247. if( $attributes['VALUE'] == '' ) {
  248. $attributes['VALUE'] = " '' ";
  249. }
  250. $this->addFieldOpt( $this->current_field, $this->currentElement, $attributes['VALUE'] );
  251. break;
  252. case 'DEFDATE':
  253. case 'DEFTIMESTAMP':
  254. // Add a field option to the table object
  255. $this->addFieldOpt( $this->current_field, $this->currentElement );
  256. break;
  257. default:
  258. // print_r( array( $tag, $attributes ) );
  259. }
  260. }
  261. /**
  262. * XML Callback to process CDATA elements
  263. *
  264. * @access private
  265. */
  266. function _tag_cdata( &$parser, $cdata ) {
  267. switch( $this->currentElement ) {
  268. // Table constraint
  269. case 'CONSTRAINT':
  270. if( isset( $this->current_field ) ) {
  271. $this->addFieldOpt( $this->current_field, $this->currentElement, $cdata );
  272. } else {
  273. $this->addTableOpt( $cdata );
  274. }
  275. break;
  276. // Table option
  277. case 'OPT':
  278. $this->addTableOpt( $cdata );
  279. break;
  280. default:
  281. }
  282. }
  283. /**
  284. * XML Callback to process end elements
  285. *
  286. * @access private
  287. */
  288. function _tag_close( &$parser, $tag ) {
  289. $this->currentElement = '';
  290. switch( strtoupper( $tag ) ) {
  291. case 'TABLE':
  292. $this->parent->addSQL( $this->create( $this->parent ) );
  293. xml_set_object( $parser, $this->parent );
  294. $this->destroy();
  295. break;
  296. case 'FIELD':
  297. unset($this->current_field);
  298. break;
  299. }
  300. }
  301. /**
  302. * Adds an index to a table object
  303. *
  304. * @param array $attributes Index attributes
  305. * @return object dbIndex object
  306. */
  307. function &addIndex( $attributes ) {
  308. $name = strtoupper( $attributes['NAME'] );
  309. $this->indexes[$name] =& new dbIndex( $this, $attributes );
  310. return $this->indexes[$name];
  311. }
  312. /**
  313. * Adds data to a table object
  314. *
  315. * @param array $attributes Data attributes
  316. * @return object dbData object
  317. */
  318. function &addData( $attributes ) {
  319. if( !isset( $this->data ) ) {
  320. $this->data =& new dbData( $this, $attributes );
  321. }
  322. return $this->data;
  323. }
  324. /**
  325. * Adds a field to a table object
  326. *
  327. * $name is the name of the table to which the field should be added.
  328. * $type is an ADODB datadict field type. The following field types
  329. * are supported as of ADODB 3.40:
  330. * - C: varchar
  331. * - X: CLOB (character large object) or largest varchar size
  332. * if CLOB is not supported
  333. * - C2: Multibyte varchar
  334. * - X2: Multibyte CLOB
  335. * - B: BLOB (binary large object)
  336. * - D: Date (some databases do not support this, and we return a datetime type)
  337. * - T: Datetime or Timestamp
  338. * - L: Integer field suitable for storing booleans (0 or 1)
  339. * - I: Integer (mapped to I4)
  340. * - I1: 1-byte integer
  341. * - I2: 2-byte integer
  342. * - I4: 4-byte integer
  343. * - I8: 8-byte integer
  344. * - F: Floating point number
  345. * - N: Numeric or decimal number
  346. *
  347. * @param string $name Name of the table to which the field will be added.
  348. * @param string $type ADODB datadict field type.
  349. * @param string $size Field size
  350. * @param array $opts Field options array
  351. * @return array Field specifier array
  352. */
  353. function addField( $name, $type, $size = NULL, $opts = NULL ) {
  354. $field_id = $this->FieldID( $name );
  355. // Set the field index so we know where we are
  356. $this->current_field = $field_id;
  357. // Set the field name (required)
  358. $this->fields[$field_id]['NAME'] = $name;
  359. // Set the field type (required)
  360. $this->fields[$field_id]['TYPE'] = $type;
  361. // Set the field size (optional)
  362. if( isset( $size ) ) {
  363. $this->fields[$field_id]['SIZE'] = $size;
  364. }
  365. // Set the field options
  366. if( isset( $opts ) ) {
  367. $this->fields[$field_id]['OPTS'][] = $opts;
  368. }
  369. }
  370. /**
  371. * Adds a field option to the current field specifier
  372. *
  373. * This method adds a field option allowed by the ADOdb datadict
  374. * and appends it to the given field.
  375. *
  376. * @param string $field Field name
  377. * @param string $opt ADOdb field option
  378. * @param mixed $value Field option value
  379. * @return array Field specifier array
  380. */
  381. function addFieldOpt( $field, $opt, $value = NULL ) {
  382. if( !isset( $value ) ) {
  383. $this->fields[$this->FieldID( $field )]['OPTS'][] = $opt;
  384. // Add the option and value
  385. } else {
  386. $this->fields[$this->FieldID( $field )]['OPTS'][] = array( $opt => $value );
  387. }
  388. }
  389. /**
  390. * Adds an option to the table
  391. *
  392. * This method takes a comma-separated list of table-level options
  393. * and appends them to the table object.
  394. *
  395. * @param string $opt Table option
  396. * @return array Options
  397. */
  398. function addTableOpt( $opt ) {
  399. $this->opts[] = $opt;
  400. return $this->opts;
  401. }
  402. /**
  403. * Generates the SQL that will create the table in the database
  404. *
  405. * @param object $xmls adoSchema object
  406. * @return array Array containing table creation SQL
  407. */
  408. function create( &$xmls ) {
  409. $sql = array();
  410. // drop any existing indexes
  411. if( is_array( $legacy_indexes = $xmls->dict->MetaIndexes( $this->name ) ) ) {
  412. foreach( $legacy_indexes as $index => $index_details ) {
  413. $sql[] = $xmls->dict->DropIndexSQL( $index, $this->name );
  414. }
  415. }
  416. // remove fields to be dropped from table object
  417. foreach( $this->drop_field as $field ) {
  418. unset( $this->fields[$field] );
  419. }
  420. // if table exists
  421. if( is_array( $legacy_fields = $xmls->dict->MetaColumns( $this->name ) ) ) {
  422. // drop table
  423. if( $this->drop_table ) {
  424. $sql[] = $xmls->dict->DropTableSQL( $this->name );
  425. return $sql;
  426. }
  427. // drop any existing fields not in schema
  428. foreach( $legacy_fields as $field_id => $field ) {
  429. if( !isset( $this->fields[$field_id] ) ) {
  430. $sql[] = $xmls->dict->DropColumnSQL( $this->name, '`'.$field->name.'`' );
  431. }
  432. }
  433. // if table doesn't exist
  434. } else {
  435. if( $this->drop_table ) {
  436. return $sql;
  437. }
  438. $legacy_fields = array();
  439. }
  440. // Loop through the field specifier array, building the associative array for the field options
  441. $fldarray = array();
  442. foreach( $this->fields as $field_id => $finfo ) {
  443. // Set an empty size if it isn't supplied
  444. if( !isset( $finfo['SIZE'] ) ) {
  445. $finfo['SIZE'] = '';
  446. }
  447. // Initialize the field array with the type and size
  448. $fldarray[$field_id] = array(
  449. 'NAME' => $finfo['NAME'],
  450. 'TYPE' => $finfo['TYPE'],
  451. 'SIZE' => $finfo['SIZE']
  452. );
  453. // Loop through the options array and add the field options.
  454. if( isset( $finfo['OPTS'] ) ) {
  455. foreach( $finfo['OPTS'] as $opt ) {
  456. // Option has an argument.
  457. if( is_array( $opt ) ) {
  458. $key = key( $opt );
  459. $value = $opt[key( $opt )];
  460. @$fldarray[$field_id][$key] .= $value;
  461. // Option doesn't have arguments
  462. } else {
  463. $fldarray[$field_id][$opt] = $opt;
  464. }
  465. }
  466. }
  467. }
  468. if( empty( $legacy_fields ) ) {
  469. // Create the new table
  470. $sql[] = $xmls->dict->CreateTableSQL( $this->name, $fldarray, $this->opts );
  471. logMsg( end( $sql ), 'Generated CreateTableSQL' );
  472. } else {
  473. // Upgrade an existing table
  474. logMsg( "Upgrading {$this->name} using '{$xmls->upgrade}'" );
  475. switch( $xmls->upgrade ) {
  476. // Use ChangeTableSQL
  477. case 'ALTER':
  478. logMsg( 'Generated ChangeTableSQL (ALTERing table)' );
  479. $sql[] = $xmls->dict->ChangeTableSQL( $this->name, $fldarray, $this->opts );
  480. break;
  481. case 'REPLACE':
  482. logMsg( 'Doing upgrade REPLACE (testing)' );
  483. $sql[] = $xmls->dict->DropTableSQL( $this->name );
  484. $sql[] = $xmls->dict->CreateTableSQL( $this->name, $fldarray, $this->opts );
  485. break;
  486. // ignore table
  487. default:
  488. return array();
  489. }
  490. }
  491. foreach( $this->indexes as $index ) {
  492. $sql[] = $index->create( $xmls );
  493. }
  494. if( isset( $this->data ) ) {
  495. $sql[] = $this->data->create( $xmls );
  496. }
  497. return $sql;
  498. }
  499. /**
  500. * Marks a field or table for destruction
  501. */
  502. function drop() {
  503. if( isset( $this->current_field ) ) {
  504. // Drop the current field
  505. logMsg( "Dropping field '{$this->current_field}' from table '{$this->name}'" );
  506. // $this->drop_field[$this->current_field] = $xmls->dict->DropColumnSQL( $this->name, $this->current_field );
  507. $this->drop_field[$this->current_field] = $this->current_field;
  508. } else {
  509. // Drop the current table
  510. logMsg( "Dropping table '{$this->name}'" );
  511. // $this->drop_table = $xmls->dict->DropTableSQL( $this->name );
  512. $this->drop_table = TRUE;
  513. }
  514. }
  515. }
  516. /**
  517. * Creates an index object in ADOdb's datadict format
  518. *
  519. * This class stores information about a database index. As charactaristics
  520. * of the index are loaded from the external source, methods and properties
  521. * of this class are used to build up the index description in ADOdb's
  522. * datadict format.
  523. *
  524. * @package axmls
  525. * @access private
  526. */
  527. class dbIndex extends dbObject {
  528. /**
  529. * @var string Index name
  530. */
  531. var $name;
  532. /**
  533. * @var array Index options: Index-level options
  534. */
  535. var $opts = array();
  536. /**
  537. * @var array Indexed fields: Table columns included in this index
  538. */
  539. var $columns = array();
  540. /**
  541. * @var boolean Mark index for destruction
  542. * @access private
  543. */
  544. var $drop = FALSE;
  545. /**
  546. * Initializes the new dbIndex object.
  547. *
  548. * @param object $parent Parent object
  549. * @param array $attributes Attributes
  550. *
  551. * @internal
  552. */
  553. function dbIndex( &$parent, $attributes = NULL ) {
  554. $this->parent =& $parent;
  555. $this->name = $this->prefix ($attributes['NAME']);
  556. }
  557. /**
  558. * XML Callback to process start elements
  559. *
  560. * Processes XML opening tags.
  561. * Elements currently processed are: DROP, CLUSTERED, BITMAP, UNIQUE, FULLTEXT & HASH.
  562. *
  563. * @access private
  564. */
  565. function _tag_open( &$parser, $tag, $attributes ) {
  566. $this->currentElement = strtoupper( $tag );
  567. switch( $this->currentElement ) {
  568. case 'DROP':
  569. $this->drop();
  570. break;
  571. case 'CLUSTERED':
  572. case 'BITMAP':
  573. case 'UNIQUE':
  574. case 'FULLTEXT':
  575. case 'HASH':
  576. // Add index Option
  577. $this->addIndexOpt( $this->currentElement );
  578. break;
  579. default:
  580. // print_r( array( $tag, $attributes ) );
  581. }
  582. }
  583. /**
  584. * XML Callback to process CDATA elements
  585. *
  586. * Processes XML cdata.
  587. *
  588. * @access private
  589. */
  590. function _tag_cdata( &$parser, $cdata ) {
  591. switch( $this->currentElement ) {
  592. // Index field name
  593. case 'COL':
  594. $this->addField( $cdata );
  595. break;
  596. default:
  597. }
  598. }
  599. /**
  600. * XML Callback to process end elements
  601. *
  602. * @access private
  603. */
  604. function _tag_close( &$parser, $tag ) {
  605. $this->currentElement = '';
  606. switch( strtoupper( $tag ) ) {
  607. case 'INDEX':
  608. xml_set_object( $parser, $this->parent );
  609. break;
  610. }
  611. }
  612. /**
  613. * Adds a field to the index
  614. *
  615. * @param string $name Field name
  616. * @return string Field list
  617. */
  618. function addField( $name ) {
  619. $this->columns[$this->FieldID( $name )] = $name;
  620. // Return the field list
  621. return $this->columns;
  622. }
  623. /**
  624. * Adds options to the index
  625. *
  626. * @param string $opt Comma-separated list of index options.
  627. * @return string Option list
  628. */
  629. function addIndexOpt( $opt ) {
  630. $this->opts[] = $opt;
  631. // Return the options list
  632. return $this->opts;
  633. }
  634. /**
  635. * Generates the SQL that will create the index in the database
  636. *
  637. * @param object $xmls adoSchema object
  638. * @return array Array containing index creation SQL
  639. */
  640. function create( &$xmls ) {
  641. if( $this->drop ) {
  642. return NULL;
  643. }
  644. // eliminate any columns that aren't in the table
  645. foreach( $this->columns as $id => $col ) {
  646. if( !isset( $this->parent->fields[$id] ) ) {
  647. unset( $this->columns[$id] );
  648. }
  649. }
  650. return $xmls->dict->CreateIndexSQL( $this->name, $this->parent->name, $this->columns, $this->opts );
  651. }
  652. /**
  653. * Marks an index for destruction
  654. */
  655. function drop() {
  656. $this->drop = TRUE;
  657. }
  658. }
  659. /**
  660. * Creates a data object in ADOdb's datadict format
  661. *
  662. * This class stores information about table data.
  663. *
  664. * @package axmls
  665. * @access private
  666. */
  667. class dbData extends dbObject {
  668. var $data = array();
  669. var $row;
  670. /**
  671. * Initializes the new dbIndex object.
  672. *
  673. * @param object $parent Parent object
  674. * @param array $attributes Attributes
  675. *
  676. * @internal
  677. */
  678. function dbData( &$parent, $attributes = NULL ) {
  679. $this->parent =& $parent;
  680. }
  681. /**
  682. * XML Callback to process start elements
  683. *
  684. * Processes XML opening tags.
  685. * Elements currently processed are: DROP, CLUSTERED, BITMAP, UNIQUE, FULLTEXT & HASH.
  686. *
  687. * @access private
  688. */
  689. function _tag_open( &$parser, $tag, $attributes ) {
  690. $this->currentElement = strtoupper( $tag );
  691. switch( $this->currentElement ) {
  692. case 'ROW':
  693. $this->row = count( $this->data );
  694. $this->data[$this->row] = array();
  695. break;
  696. case 'F':
  697. $this->addField($attributes);
  698. default:
  699. // print_r( array( $tag, $attributes ) );
  700. }
  701. }
  702. /**
  703. * XML Callback to process CDATA elements
  704. *
  705. * Processes XML cdata.
  706. *
  707. * @access private
  708. */
  709. function _tag_cdata( &$parser, $cdata ) {
  710. switch( $this->currentElement ) {
  711. // Index field name
  712. case 'F':
  713. $this->addData( $cdata );
  714. break;
  715. default:
  716. }
  717. }
  718. /**
  719. * XML Callback to process end elements
  720. *
  721. * @access private
  722. */
  723. function _tag_close( &$parser, $tag ) {
  724. $this->currentElement = '';
  725. switch( strtoupper( $tag ) ) {
  726. case 'DATA':
  727. xml_set_object( $parser, $this->parent );
  728. break;
  729. }
  730. }
  731. /**
  732. * Adds a field to the index
  733. *
  734. * @param string $name Field name
  735. * @return string Field list
  736. */
  737. function addField( $attributes ) {
  738. if( isset( $attributes['NAME'] ) ) {
  739. $name = $attributes['NAME'];
  740. } else {
  741. $name = count($this->data[$this->row]);
  742. }
  743. // Set the field index so we know where we are
  744. $this->current_field = $this->FieldID( $name );
  745. }
  746. /**
  747. * Adds options to the index
  748. *
  749. * @param string $opt Comma-separated list of index options.
  750. * @return string Option list
  751. */
  752. function addData( $cdata ) {
  753. if( !isset( $this->data[$this->row] ) ) {
  754. $this->data[$this->row] = array();
  755. }
  756. if( !isset( $this->data[$this->row][$this->current_field] ) ) {
  757. $this->data[$this->row][$this->current_field] = '';
  758. }
  759. $this->data[$this->row][$this->current_field] .= $cdata;
  760. }
  761. /**
  762. * Generates the SQL that will create the index in the database
  763. *
  764. * @param object $xmls adoSchema object
  765. * @return array Array containing index creation SQL
  766. */
  767. function create( &$xmls ) {
  768. $table = $xmls->dict->TableName($this->parent->name);
  769. $table_field_count = count($this->parent->fields);
  770. $sql = array();
  771. // eliminate any columns that aren't in the table
  772. foreach( $this->data as $row ) {
  773. $table_fields = $this->parent->fields;
  774. $fields = array();
  775. foreach( $row as $field_id => $field_data ) {
  776. if( !array_key_exists( $field_id, $table_fields ) ) {
  777. if( is_numeric( $field_id ) ) {
  778. $field_id = reset( array_keys( $table_fields ) );
  779. } else {
  780. continue;
  781. }
  782. }
  783. $name = $table_fields[$field_id]['NAME'];
  784. switch( $table_fields[$field_id]['TYPE'] ) {
  785. case 'C':
  786. case 'C2':
  787. case 'X':
  788. case 'X2':
  789. $fields[$name] = $xmls->db->qstr( $field_data );
  790. break;
  791. case 'I':
  792. case 'I1':
  793. case 'I2':
  794. case 'I4':
  795. case 'I8':
  796. $fields[$name] = intval($field_data);
  797. break;
  798. default:
  799. $fields[$name] = $field_data;
  800. }
  801. unset($table_fields[$field_id]);
  802. }
  803. // check that at least 1 column is specified
  804. if( empty( $fields ) ) {
  805. continue;
  806. }
  807. // check that no required columns are missing
  808. if( count( $fields ) < $table_field_count ) {
  809. foreach( $table_fields as $field ) {
  810. if( ( in_array( 'NOTNULL', $field['OPTS'] ) || in_array( 'KEY', $field['OPTS'] ) ) && !in_array( 'AUTOINCREMENT', $field['OPTS'] ) ) {
  811. continue(2);
  812. }
  813. }
  814. }
  815. $sql[] = 'INSERT INTO '. $table .' ('. implode( ',', array_keys( $fields ) ) .') VALUES ('. implode( ',', $fields ) .')';
  816. }
  817. return $sql;
  818. }
  819. }
  820. /**
  821. * Creates the SQL to execute a list of provided SQL queries
  822. *
  823. * @package axmls
  824. * @access private
  825. */
  826. class dbQuerySet extends dbObject {
  827. /**
  828. * @var array List of SQL queries
  829. */
  830. var $queries = array();
  831. /**
  832. * @var string String used to build of a query line by line
  833. */
  834. var $query;
  835. /**
  836. * @var string Query prefix key
  837. */
  838. var $prefixKey = '';
  839. /**
  840. * @var boolean Auto prefix enable (TRUE)
  841. */
  842. var $prefixMethod = 'AUTO';
  843. /**
  844. * Initializes the query set.
  845. *
  846. * @param object $parent Parent object
  847. * @param array $attributes Attributes
  848. */
  849. function dbQuerySet( &$parent, $attributes = NULL ) {
  850. $this->parent =& $parent;
  851. // Overrides the manual prefix key
  852. if( isset( $attributes['KEY'] ) ) {
  853. $this->prefixKey = $attributes['KEY'];
  854. }
  855. $prefixMethod = isset( $attributes['PREFIXMETHOD'] ) ? strtoupper( trim( $attributes['PREFIXMETHOD'] ) ) : '';
  856. // Enables or disables automatic prefix prepending
  857. switch( $prefixMethod ) {
  858. case 'AUTO':
  859. $this->prefixMethod = 'AUTO';
  860. break;
  861. case 'MANUAL':
  862. $this->prefixMethod = 'MANUAL';
  863. break;
  864. case 'NONE':
  865. $this->prefixMethod = 'NONE';
  866. break;
  867. }
  868. }
  869. /**
  870. * XML Callback to process start elements. Elements currently
  871. * processed are: QUERY.
  872. *
  873. * @access private
  874. */
  875. function _tag_open( &$parser, $tag, $attributes ) {
  876. $this->currentElement = strtoupper( $tag );
  877. switch( $this->currentElement ) {
  878. case 'QUERY':
  879. // Create a new query in a SQL queryset.
  880. // Ignore this query set if a platform is specified and it's different than the
  881. // current connection platform.
  882. if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) {
  883. $this->newQuery();
  884. } else {
  885. $this->discardQuery();
  886. }
  887. break;
  888. default:
  889. // print_r( array( $tag, $attributes ) );
  890. }
  891. }
  892. /**
  893. * XML Callback to process CDATA elements
  894. */
  895. function _tag_cdata( &$parser, $cdata ) {
  896. switch( $this->currentElement ) {
  897. // Line of queryset SQL data
  898. case 'QUERY':
  899. $this->buildQuery( $cdata );
  900. break;
  901. default:
  902. }
  903. }
  904. /**
  905. * XML Callback to process end elements
  906. *
  907. * @access private
  908. */
  909. function _tag_close( &$parser, $tag ) {
  910. $this->currentElement = '';
  911. switch( strtoupper( $tag ) ) {
  912. case 'QUERY':
  913. // Add the finished query to the open query set.
  914. $this->addQuery();
  915. break;
  916. case 'SQL':
  917. $this->parent->addSQL( $this->create( $this->parent ) );
  918. xml_set_object( $parser, $this->parent );
  919. $this->destroy();
  920. break;
  921. default:
  922. }
  923. }
  924. /**
  925. * Re-initializes the query.
  926. *
  927. * @return boolean TRUE
  928. */
  929. function newQuery() {
  930. $this->query = '';
  931. return TRUE;
  932. }
  933. /**
  934. * Discards the existing query.
  935. *
  936. * @return boolean TRUE
  937. */
  938. function discardQuery() {
  939. unset( $this->query );
  940. return TRUE;
  941. }
  942. /**
  943. * Appends a line to a query that is being built line by line
  944. *
  945. * @param string $data Line of SQL data or NULL to initialize a new query
  946. * @return string SQL query string.
  947. */
  948. function buildQuery( $sql = NULL ) {
  949. if( !isset( $this->query ) OR empty( $sql ) ) {
  950. return FALSE;
  951. }
  952. $this->query .= $sql;
  953. return $this->query;
  954. }
  955. /**
  956. * Adds a completed query to the query list
  957. *
  958. * @return string SQL of added query
  959. */
  960. function addQuery() {
  961. if( !isset( $this->query ) ) {
  962. return FALSE;
  963. }
  964. $this->queries[] = $return = trim($this->query);
  965. unset( $this->query );
  966. return $return;
  967. }
  968. /**
  969. * Creates and returns the current query set
  970. *
  971. * @param object $xmls adoSchema object
  972. * @return array Query set
  973. */
  974. function create( &$xmls ) {
  975. foreach( $this->queries as $id => $query ) {
  976. switch( $this->prefixMethod ) {
  977. case 'AUTO':
  978. // Enable auto prefix replacement
  979. // Process object prefix.
  980. // Evaluate SQL statements to prepend prefix to objects
  981. $query = $this->prefixQuery( '/^\s*((?is)INSERT\s+(INTO\s+)?)((\w+\s*,?\s*)+)(\s.*$)/', $query, $xmls->objectPrefix );
  982. $query = $this->prefixQuery( '/^\s*((?is)UPDATE\s+(FROM\s+)?)((\w+\s*,?\s*)+)(\s.*$)/', $query, $xmls->objectPrefix );
  983. $query = $this->prefixQuery( '/^\s*((?is)DELETE\s+(FROM\s+)?)((\w+\s*,?\s*)+)(\s.*$)/', $query, $xmls->objectPrefix );
  984. // SELECT statements aren't working yet
  985. #$data = preg_replace( '/(?ias)(^\s*SELECT\s+.*\s+FROM)\s+(\W\s*,?\s*)+((?i)\s+WHERE.*$)/', "\1 $prefix\2 \3", $data );
  986. case 'MANUAL':
  987. // If prefixKey is set and has a value then we use it to override the default constant XMLS_PREFIX.
  988. // If prefixKey is not set, we use the default constant XMLS_PREFIX
  989. if( isset( $this->prefixKey ) AND( $this->prefixKey !== '' ) ) {
  990. // Enable prefix override
  991. $query = str_replace( $this->prefixKey, $xmls->objectPrefix, $query );
  992. } else {
  993. // Use default replacement
  994. $query = str_replace( XMLS_PREFIX , $xmls->objectPrefix, $query );
  995. }
  996. }
  997. $this->queries[$id] = trim( $query );
  998. }
  999. // Return the query set array
  1000. return $this->queries;
  1001. }
  1002. /**
  1003. * Rebuilds the query with the prefix attached to any objects
  1004. *
  1005. * @param string $regex Regex used to add prefix
  1006. * @param string $query SQL query string
  1007. * @param string $prefix Prefix to be appended to tables, indices, etc.
  1008. * @return string Prefixed SQL query string.
  1009. */
  1010. function prefixQuery( $regex, $query, $prefix = NULL ) {
  1011. if( !isset( $prefix ) ) {
  1012. return $query;
  1013. }
  1014. if( preg_match( $regex, $query, $match ) ) {
  1015. $preamble = $match[1];
  1016. $postamble = $match[5];
  1017. $objectList = explode( ',', $match[3] );
  1018. // $prefix = $prefix . '_';
  1019. $prefixedList = '';
  1020. foreach( $objectList as $object ) {
  1021. if( $prefixedList !== '' ) {
  1022. $prefixedList .= ', ';
  1023. }
  1024. $prefixedList .= $prefix . trim( $object );
  1025. }
  1026. $query = $preamble . ' ' . $prefixedList . ' ' . $postamble;
  1027. }
  1028. return $query;
  1029. }
  1030. }
  1031. /**
  1032. * Loads and parses an XML file, creating an array of "ready-to-run" SQL statements
  1033. *
  1034. * This class is used to load and parse the XML file, to create an array of SQL statements
  1035. * that can be used to build a database, and to build the database using the SQL array.
  1036. *
  1037. * @tutorial getting_started.pkg
  1038. *
  1039. * @author Richard Tango-Lowy & Dan Cech
  1040. * @version $Revision: 1.3 $
  1041. *
  1042. * @package axmls
  1043. */
  1044. class adoSchema {
  1045. /**
  1046. * @var array Array containing SQL queries to generate all objects
  1047. * @access private
  1048. */
  1049. var $sqlArray;
  1050. /**
  1051. * @var object ADOdb connection object
  1052. * @access private
  1053. */
  1054. var $db;
  1055. /**
  1056. * @var object ADOdb Data Dictionary
  1057. * @access private
  1058. */
  1059. var $dict;
  1060. /**
  1061. * @var string Current XML element
  1062. * @access private
  1063. */
  1064. var $currentElement = '';
  1065. /**
  1066. * @var string If set (to 'ALTER' or 'REPLACE'), upgrade an existing database
  1067. * @access private
  1068. */
  1069. var $upgrade = '';
  1070. /**
  1071. * @var string Optional object prefix
  1072. * @access private
  1073. */
  1074. var $objectPrefix = '';
  1075. /**
  1076. * @var long Original Magic Quotes Runtime value
  1077. * @access private
  1078. */
  1079. var $mgq;
  1080. /**
  1081. * @var long System debug
  1082. * @access private
  1083. */
  1084. var $debug;
  1085. /**
  1086. * @var string Regular expression to find schema version
  1087. * @access private
  1088. */
  1089. var $versionRegex = '/<schema.*?( version="([^"]*)")?.*?>/';
  1090. /**
  1091. * @var string Current schema version
  1092. * @access private
  1093. */
  1094. var $schemaVersion;
  1095. /**
  1096. * @var int Success of last Schema execution
  1097. */
  1098. var $success;
  1099. /**
  1100. * @var bool Execute SQL inline as it is generated
  1101. */
  1102. var $executeInline;
  1103. /**
  1104. * @var bool Continue SQL execution if errors occur
  1105. */
  1106. var $continueOnError;
  1107. /**
  1108. * Creates an adoSchema object
  1109. *
  1110. * Creating an adoSchema object is the first step in processing an XML schema.
  1111. * The only parameter is an ADOdb database connection object, which must already
  1112. * have been created.
  1113. *
  1114. * @param object $db ADOdb database connection object.
  1115. */
  1116. function adoSchema( &$db ) {
  1117. // Initialize the environment
  1118. $this->mgq = get_magic_quotes_runtime();
  1119. set_magic_quotes_runtime(0);
  1120. $this->db =& $db;
  1121. $this->debug = $this->db->debug;
  1122. $this->dict = NewDataDictionary( $this->db );
  1123. $this->sqlArray = array();
  1124. $this->schemaVersion = XMLS_SCHEMA_VERSION;
  1125. $this->executeInline( XMLS_EXECUTE_INLINE );
  1126. $this->continueOnError( XMLS_CONTINUE_ON_ERROR );
  1127. $this->setUpgradeMethod();
  1128. }
  1129. /**
  1130. * Sets the method to be used for upgrading an existing database
  1131. *
  1132. * Use this method to specify how existing database objects should be upgraded.
  1133. * The method option can be set to ALTER, REPLACE, BEST, or NONE. ALTER attempts to
  1134. * alter each database object directly, REPLACE attempts to rebuild each object
  1135. * from scratch, BEST attempts to determine the best upgrade method for each
  1136. * object, and NONE disables upgrading.
  1137. *
  1138. * This method is not yet used by AXMLS, but exists for backward compatibility.
  1139. * The ALTER method is automatically assumed when the adoSchema object is
  1140. * instantiated; other upgrade methods are not currently supported.
  1141. *
  1142. * @param string $method Upgrade method (ALTER|REPLACE|BEST|NONE)
  1143. * @returns string Upgrade method used
  1144. */
  1145. function SetUpgradeMethod( $method = '' ) {
  1146. if( !is_string( $method ) ) {
  1147. return FALSE;
  1148. }
  1149. $method = strtoupper( $method );
  1150. // Handle the upgrade methods
  1151. switch( $method ) {
  1152. case 'ALTER':
  1153. $this->upgrade = $method;
  1154. break;
  1155. case 'REPLACE':
  1156. $this->upgrade = $method;
  1157. break;
  1158. case 'BEST':
  1159. $this->upgrade = 'ALTER';
  1160. break;
  1161. case 'NONE':
  1162. $this->upgrade = 'NONE';
  1163. break;
  1164. default:
  1165. // Use default if no legitimate method is passed.
  1166. $this->upgrade = XMLS_DEFAULT_UPGRADE_METHOD;
  1167. }
  1168. return $this->upgrade;
  1169. }
  1170. /**
  1171. * Enables/disables inline SQL execution.
  1172. *
  1173. * Call this method to enable or disable inline execution of the schema. If the mode is set to TRUE (inline execution),
  1174. * AXMLS applies the SQL to the database immediately as each schema entity is parsed. If the mode
  1175. * is set to FALSE (post execution), AXMLS parses the entire schema and you will need to call adoSchema::ExecuteSchema()
  1176. * to apply the schema to the database.
  1177. *
  1178. * @param bool $mode execute
  1179. * @return bool current execution mode
  1180. *
  1181. * @see ParseSchema(), ExecuteSchema()
  1182. */
  1183. function ExecuteInline( $mode = NULL ) {
  1184. if( is_bool( $mode ) ) {
  1185. $this->executeInline = $mode;
  1186. }
  1187. return $this->executeInline;
  1188. }
  1189. /**
  1190. * Enables/disables SQL continue on error.
  1191. *
  1192. * Call this method to enable or disable continuation of SQL execution if an error occurs.
  1193. * If the mode is set to TRUE (continue), AXMLS will continue to apply SQL to the database, even if an error occurs.
  1194. * If the mode is set to FALSE (halt), AXMLS will halt execution of generated sql if an error occurs, though parsing
  1195. * of the schema will continue.
  1196. *
  1197. * @param bool $mode execute
  1198. * @return bool current continueOnError mode
  1199. *
  1200. * @see addSQL(), ExecuteSchema()
  1201. */
  1202. function ContinueOnError( $mode = NULL ) {
  1203. if( is_bool( $mode ) ) {
  1204. $this->continueOnError = $mode;
  1205. }
  1206. return $this->continueOnError;
  1207. }
  1208. /**
  1209. * Loads an XML schema from a file and converts it to SQL.
  1210. *
  1211. * Call this method to load the specified schema (see the DTD for the proper format) from
  1212. * the filesystem and generate the SQL necessary to create the database described.
  1213. * @see ParseSchemaString()
  1214. *
  1215. * @param string $file Name of XML schema file.
  1216. * @param bool $returnSchema Return schema rather than parsing.
  1217. * @return array Array of SQL queries, ready to execute
  1218. */
  1219. function ParseSchema( $filename, $returnSchema = FALSE ) {
  1220. return $this->ParseSchemaString( $this->ConvertSchemaFile( $filename ), $returnSchema );
  1221. }
  1222. /**
  1223. * Loads an XML schema from a file and converts it to SQL.
  1224. *
  1225. * Call this method to load the specified schema from a file (see the DTD for the proper format)
  1226. * and generate the SQL necessary to create the database described by the schema.
  1227. *
  1228. * @param string $file Name of XML schema file.
  1229. * @param bool $returnSchema Return schema rather than parsing.
  1230. * @return array Array of SQL queries, ready to execute.
  1231. *
  1232. * @deprecated Replaced by adoSchema::ParseSchema() and adoSchema::ParseSchemaString()
  1233. * @see ParseSchema(), ParseSchemaString()
  1234. */
  1235. function ParseSchemaFile( $filename, $returnSchema = FALSE ) {
  1236. // Open the file
  1237. if( !($fp = fopen( $filename, 'r' )) ) {
  1238. // die( 'Unable to open file' );
  1239. return FALSE;
  1240. }
  1241. // do version detection here
  1242. if( $this->SchemaFileVersion( $filename ) != $this->schemaVersion ) {
  1243. return FALSE;
  1244. }
  1245. if ( $returnSchema )
  1246. {
  1247. return $xmlstring;
  1248. }
  1249. $this->success = 2;
  1250. $xmlParser = $this->create_parser();
  1251. // Process the file
  1252. while( $data = fread( $fp, 4096 ) ) {
  1253. if( !xml_parse( $xmlParser, $data, feof( $fp ) ) ) {
  1254. die( sprintf(
  1255. "XML error: %s at line %d",
  1256. xml_error_string( xml_get_error_code( $xmlParser) ),
  1257. xml_get_current_line_number( $xmlParser)
  1258. ) );
  1259. }
  1260. }
  1261. xml_parser_free( $xmlParser );
  1262. return $this->sqlArray;
  1263. }
  1264. /**
  1265. * Converts an XML schema string to SQL.
  1266. *
  1267. * Call this method to parse a string containing an XML schema (see the DTD for the proper format)
  1268. * and generate the SQL necessary to create the database described by the schema.
  1269. * @see ParseSchema()
  1270. *
  1271. * @param string $xmlstring XML schema string.
  1272. * @param bool $returnSchema Return schema rather than parsing.
  1273. * @return array Array of SQL queries, ready to execute.
  1274. */
  1275. function ParseSchemaString( $xmlstring, $returnSchema = FALSE ) {
  1276. if( !is_string( $xmlstring ) OR empty( $xmlstring ) ) {
  1277. return FALSE;
  1278. }
  1279. // do version detection here
  1280. if( $this->SchemaStringVersion( $xmlstring ) != $this->schemaVersion ) {
  1281. return FALSE;
  1282. }
  1283. if ( $returnSchema )
  1284. {
  1285. return $xmlstring;
  1286. }
  1287. $this->success = 2;
  1288. $xmlParser = $this->create_parser();
  1289. if( !xml_parse( $xmlParser, $xmlstring, TRUE ) ) {
  1290. die( sprintf(
  1291. "XML error: %s at line %d",
  1292. xml_error_string( xml_get_error_code( $xmlParser) ),
  1293. xml_get_current_line_number( $xmlParser)
  1294. ) );
  1295. }
  1296. xml_parser_free( $xmlParser );
  1297. return $this->sqlArray;
  1298. }
  1299. /**
  1300. * Loads an XML schema from a file and converts it to uninstallation SQL.
  1301. *
  1302. * Call this method to load the specified schema (see the DTD for the proper format) from
  1303. * the filesystem and generate the SQL necessary to remove the database described.
  1304. * @see RemoveSchemaString()
  1305. *
  1306. * @param string $file Name of XML schema file.
  1307. * @param bool $returnSchema Return schema rather than parsing.
  1308. * @return array Array of SQL queries, ready to execute
  1309. */
  1310. function RemoveSchema( $filename, $returnSchema = FALSE ) {
  1311. return $this->RemoveSchemaString( $this->ConvertSchemaFile( $filename ), $returnSchema );
  1312. }
  1313. /**
  1314. * Converts an XML schema string to uninstallation SQL.
  1315. *
  1316. * Call this method to parse a string containing an XML schema (see the DTD for the proper format)
  1317. * and generate the SQL necessary to uninstall the database described by the schema.
  1318. * @see RemoveSchema()
  1319. *
  1320. * @param string $schema XML schema string.
  1321. * @param bool $returnSchema Return schema rather than parsing.
  1322. * @return array Array of SQL queries, ready to execute.
  1323. */
  1324. function RemoveSchemaString( $schema, $returnSchema = FALSE ) {
  1325. // grab current version
  1326. if( !( $version = $this->SchemaStringVersion( $schema ) ) ) {
  1327. return FALSE;
  1328. }
  1329. return $this->ParseSchemaString( $this->TransformSchema( $schema, 'remove-' . $version), $returnSchema );
  1330. }
  1331. /**
  1332. * Applies the current XML schema to the database (post execution).
  1333. *
  1334. * Call this method to apply the current schema (generally created by calling
  1335. * ParseSchema() or ParseSchemaString() ) to the database (creating the tables, indexes,
  1336. * and executing other SQL specified in the schema) after parsing.
  1337. * @see ParseSchema(), ParseSchemaString(), ExecuteInline()
  1338. *
  1339. * @param array $sqlArray Array of SQL statements that will be applied rather than
  1340. * the current schema.
  1341. * @param boolean $continueOnErr Continue to apply the schema even if an error occurs.
  1342. * @returns integer 0 if failure, 1 if errors, 2 if successful.
  1343. */
  1344. function ExecuteSchema( $sqlArray = NULL, $continueOnErr = NULL ) {
  1345. if( !is_bool( $continueOnErr ) ) {
  1346. $continueOnErr = $this->ContinueOnError();
  1347. }
  1348. if( !isset( $sqlArray ) ) {
  1349. $sqlArray = $this->sqlArray;
  1350. }
  1351. if( !is_array( $sqlArray ) ) {
  1352. $this->success = 0;
  1353. } else {
  1354. $this->success = $this->dict->ExecuteSQLArray( $sqlArray, $continueOnErr );
  1355. }
  1356. return $this->success;
  1357. }
  1358. /**
  1359. * Returns the current SQL array.
  1360. *
  1361. * Call this method to fetch the array of SQL queries resulting from
  1362. * ParseSchema() or ParseSchemaString().
  1363. *
  1364. * @param string $format Format: HTML, TEXT, or NONE (PHP array)
  1365. * @return array Array of SQL statements or FALSE if an error occurs
  1366. */
  1367. function PrintSQL( $format = 'NONE' ) {
  1368. return $this->getSQL( $format, $sqlArray );
  1369. }
  1370. /**
  1371. * Saves the current SQL array to the local filesystem as a list of SQL queries.
  1372. *
  1373. * Call this method to save the array of SQL queries (generally resulting from a
  1374. * parsed XML schema) to the filesystem.
  1375. *
  1376. * @param string $filename Path and name where the file should be saved.
  1377. * @return boolean TRUE if save is successful, else FALSE.
  1378. */
  1379. function SaveSQL( $filename = './schema.sql' ) {
  1380. if( !isset( $sqlArray ) ) {
  1381. $sqlArray = $this->sqlArray;
  1382. }
  1383. if( !isset( $sqlArray ) ) {
  1384. return FALSE;
  1385. }
  1386. $fp = fopen( $filename, "w" );
  1387. foreach( $sqlArray as $key => $query ) {
  1388. fwrite( $fp, $query . ";\n" );
  1389. }
  1390. fclose( $fp );
  1391. }
  1392. /**
  1393. * Create an xml parser
  1394. *
  1395. * @return object PHP XML parser object
  1396. *
  1397. * @access private
  1398. */
  1399. function &create_parser() {
  1400. // Create the parser
  1401. $xmlParser = xml_parser_create();
  1402. xml_set_object( $xmlParser, $this );
  1403. // Initialize the XML callback functions
  1404. xml_set_element_handler( $xmlParser, '_tag_open', '_tag_close' );
  1405. xml_set_character_data_handler( $xmlParser, '_tag_cdata' );
  1406. return $xmlParser;
  1407. }
  1408. /**
  1409. * XML Callback to process start elements
  1410. *
  1411. * @access private
  1412. */
  1413. function _tag_open( &$parser, $tag, $attributes ) {
  1414. switch( strtoupper( $tag ) ) {
  1415. case 'TABLE':
  1416. $this->obj = new dbTable( $this, $attributes );
  1417. xml_set_object( $parser, $this->obj );
  1418. break;
  1419. case 'SQL':
  1420. if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) {
  1421. $this->obj = new dbQuerySet( $this, $attributes );
  1422. xml_set_object( $parser, $this->obj );
  1423. }
  1424. break;
  1425. default:
  1426. // print_r( array( $tag, $attributes ) );
  1427. }
  1428. }
  1429. /**
  1430. * XML Callback to process CDATA elements
  1431. *
  1432. * @access private
  1433. */
  1434. function _tag_cdata( &$parser, $cdata ) {
  1435. }
  1436. /**
  1437. * XML Callback to process end elements
  1438. *
  1439. * @access private
  1440. * @internal
  1441. */
  1442. function _tag_close( &$parser, $tag ) {
  1443. }
  1444. /**
  1445. * Converts an XML schema string to the specified DTD version.
  1446. *
  1447. * Call this method to convert a string containing an XML schema to a different AXMLS
  1448. * DTD version. For instance, to convert a schema created for an pre-1.0 version for
  1449. * AXMLS (DTD version 0.1) to a newer version of the DTD (e.g. 0.2). If no DTD version
  1450. * parameter is specified, the schema will be converted to the current DTD version.
  1451. * If the newFile parameter is provided, the converted schema will be written to the specified
  1452. * file.
  1453. * @see ConvertSchemaFile()
  1454. *
  1455. * @param string $schema String containing XML schema that will be converted.
  1456. * @param string $newVersion DTD version to convert to.
  1457. * @param string $newFile File name of (converted) output file.
  1458. * @return string Converted XML schema or FALSE if an error occurs.
  1459. */
  1460. function ConvertSchemaString( $schema, $newVersion = NULL, $newFile = NULL ) {
  1461. // grab current version
  1462. if( !( $version = $this->SchemaStringVersion( $schema ) ) ) {
  1463. return FALSE;
  1464. }
  1465. if( !isset ($newVersion) ) {
  1466. $newVersion = $this->schemaVersion;
  1467. }
  1468. if( $version == $newVersion ) {
  1469. $result = $schema;
  1470. } else {
  1471. $result = $this->TransformSchema( $schema, 'convert-' . $version . '-' . $newVersion);
  1472. }
  1473. if( is_string( $result ) AND is_string( $newFile ) AND ( $fp = fopen( $newFile, 'w' ) ) ) {
  1474. fwrite( $fp, $result );
  1475. fclose( $fp );
  1476. }
  1477. return $result;
  1478. }
  1479. /**
  1480. * Converts an XML schema file to the specified DTD version.
  1481. *
  1482. * Call this method to convert the specified XML schema file to a different AXMLS
  1483. * DTD version. For instance, to convert a schema created for an pre-1.0 version for
  1484. * AXMLS (DTD version 0.1) to a newer version of the DTD (e.g. 0.2). If no DTD version
  1485. * parameter is specified, the schema will be converted to the current DTD version.
  1486. * If the newFile parameter is provided, the converted schema will be written to the specified
  1487. * file.
  1488. * @see ConvertSchemaString()
  1489. *
  1490. * @param string $filename Name of XML schema file that will be converted.
  1491. * @param string $newVersion DTD version to convert to.
  1492. * @param string $newFile File name of (converted) output file.
  1493. * @return string Converted XML schema or FALSE if an error occurs.
  1494. */
  1495. function ConvertSchemaFile( $filename, $newVersion = NULL, $newFile = NULL ) {
  1496. // grab current version
  1497. if( !( $version = $this->SchemaFileVersion( $filename ) ) ) {
  1498. return FALSE;
  1499. }
  1500. if( !isset ($newVersion) ) {
  1501. $newVersion = $this->schemaVersion;
  1502. }
  1503. if( $version == $newVersion ) {
  1504. $result = file_get_contents( $filename );
  1505. // remove unicode BOM if present
  1506. if( substr( $result, 0, 3 ) == sprintf( '%c%c%c', 239, 187, 191 ) ) {
  1507. $result = substr( $result, 3 );
  1508. }
  1509. } else {
  1510. $result = $this->TransformSchema( $filename, 'convert-' . $version . '-' . $newVersion, 'file' );
  1511. }
  1512. if( is_string( $result ) AND is_string( $newFile ) AND ( $fp = fopen( $newFile, 'w' ) ) ) {
  1513. fwrite( $fp, $result );
  1514. fclose( $fp );
  1515. }
  1516. return $result;
  1517. }
  1518. function TransformSchema( $schema, $xsl, $schematype='string' )
  1519. {
  1520. // Fail if XSLT extension is not available
  1521. if( ! function_exists( 'xslt_create' ) ) {
  1522. return FALSE;
  1523. }
  1524. $xsl_file = dirname( __FILE__ ) . '/xsl/' . $xsl . '.xsl';
  1525. // look for xsl
  1526. if( !is_readable( $xsl_file ) ) {
  1527. return FALSE;
  1528. }
  1529. switch( $schematype )
  1530. {
  1531. case 'file':
  1532. if( !is_readable( $schema ) ) {
  1533. return FALSE;
  1534. }
  1535. $schema = file_get_contents( $schema );
  1536. break;
  1537. case 'string':
  1538. default:
  1539. if( !is_string( $schema ) ) {
  1540. return FALSE;
  1541. }
  1542. }
  1543. $arguments = array (
  1544. '/_xml' => $schema,
  1545. '/_xsl' => file_get_contents( $xsl_file )
  1546. );
  1547. // create an XSLT processor
  1548. $xh = xslt_create ();
  1549. // set error handler
  1550. xslt_set_error_handler ($xh, array (&$this, 'xslt_error_handler'));
  1551. // process the schema
  1552. $result = xslt_process ($xh, 'arg:/_xml', 'arg:/_xsl', NULL, $arguments);
  1553. xslt_free ($xh);
  1554. return $result;
  1555. }
  1556. /**
  1557. * Processes XSLT transformation errors
  1558. *
  1559. * @param object $parser XML parser object
  1560. * @param integer $errno Error number
  1561. * @param integer $level Error level
  1562. * @param array $fields Error information fields
  1563. *
  1564. * @access private
  1565. */
  1566. function xslt_error_handler( $parser, $errno, $level, $fields ) {
  1567. if( is_array( $fields ) ) {
  1568. $msg = array(
  1569. 'Message Type' => ucfirst( $fields['msgtype'] ),
  1570. 'Message Code' => $fields['code'],
  1571. 'Message' => $fields['msg'],
  1572. 'Error Number' => $errno,
  1573. 'Level' => $level
  1574. );
  1575. switch( $fields['URI'] ) {
  1576. case 'arg:/_xml':
  1577. $msg['Input'] = 'XML';
  1578. break;
  1579. case 'arg:/_xsl':
  1580. $msg['Input'] = 'XSL';
  1581. break;
  1582. default:
  1583. $msg['Input'] = $fields['URI'];
  1584. }
  1585. $msg['Line'] = $fields['line'];
  1586. } else {
  1587. $msg = array(
  1588. 'Message Type' => 'Error',
  1589. 'Error Number' => $errno,
  1590. 'Level' => $level,
  1591. 'Fields' => var_export( $fields, TRUE )
  1592. );
  1593. }
  1594. $error_details = $msg['Message Type'] . ' in XSLT Transformation' . "\n"
  1595. . '<table>' . "\n";
  1596. foreach( $msg as $label => $details ) {
  1597. $error_details .= '<tr><td><b>' . $label . ': </b></td><td>' . htmlentities( $details ) . '</td></tr>' . "\n";
  1598. }
  1599. $error_details .= '</table>';
  1600. trigger_error( $error_details, E_USER_ERROR );
  1601. }
  1602. /**
  1603. * Returns the AXMLS Schema Version of the requested XML schema file.
  1604. *
  1605. * Call this method to obtain the AXMLS DTD version of the requested XML schema file.
  1606. * @see SchemaStringVersion()
  1607. *
  1608. * @param string $filename AXMLS schema file
  1609. * @return string Schema version number or FALSE on error
  1610. */
  1611. function SchemaFileVersion( $filename ) {
  1612. // Open the file
  1613. if( !($fp = fopen( $filename, 'r' )) ) {
  1614. // die( 'Unable to open file' );
  1615. return FALSE;
  1616. }
  1617. // Process the file
  1618. while( $data = fread( $fp, 4096 ) ) {
  1619. if( preg_match( $this->versionRegex, $data, $matches ) ) {
  1620. return !empty( $matches[2] ) ? $matches[2] : XMLS_DEFAULT_SCHEMA_VERSION;
  1621. }
  1622. }
  1623. return FALSE;
  1624. }
  1625. /**
  1626. * Returns the AXMLS Schema Version of the provided XML schema string.
  1627. *
  1628. * Call this method to obtain the AXMLS DTD version of the provided XML schema string.
  1629. * @see SchemaFileVersion()
  1630. *
  1631. * @param string $xmlstring XML schema string
  1632. * @return string Schema version number or FALSE on error
  1633. */
  1634. function SchemaStringVersion( $xmlstring ) {
  1635. if( !is_string( $xmlstring ) OR empty( $xmlstring ) ) {
  1636. return FALSE;
  1637. }
  1638. if( preg_match( $this->versionRegex, $xmlstring, $matches ) ) {
  1639. return !empty( $matches[2] ) ? $matches[2] : XMLS_DEFAULT_SCHEMA_VERSION;
  1640. }
  1641. return FALSE;
  1642. }
  1643. /**
  1644. * Extracts an XML schema from an existing database.
  1645. *
  1646. * Call this method to create an XML schema string from an existing database.
  1647. * If the data parameter is set to TRUE, AXMLS will include the data from the database
  1648. * in the schema.
  1649. *
  1650. * @param boolean $data Include data in schema dump
  1651. * @return string Generated XML schema
  1652. */
  1653. function ExtractSchema( $data = FALSE ) {
  1654. $old_mode = $this->db->SetFetchMode( ADODB_FETCH_NUM );
  1655. $schema = '<?xml version="1.0"?>' . "\n"
  1656. . '<schema version="' . $this->schemaVersion . '">' . "\n";
  1657. if( is_array( $tables = $this->db->MetaTables( 'TABLES' ) ) ) {
  1658. foreach( $tables as $table ) {
  1659. $schema .= ' <table name=…

Large files files are truncated, but you can click here to view the full file