PageRenderTime 48ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/libraries/adodb.493a/adodb-xmlschema.inc.php

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

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