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

/concrete/libraries/3rdparty/adodb/adodb-xmlschema.inc.php

https://bitbucket.org/selfeky/xclusivescardwebsite
PHP | 2225 lines | 1035 code | 311 blank | 879 comment | 139 complexity | d2563ca7be8620796e3474d8211a5b06 MD5 | raw file

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(&$xmls) {
  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. if(isset($this->currentPlatform)) {
  410. $this->opts[$this->parent->db->databaseType] = $opt;
  411. }
  412. return $this->opts;
  413. }
  414. /**
  415. * Generates the SQL that will create the table in the database
  416. *
  417. * @param object $xmls adoSchema object
  418. * @return array Array containing table creation SQL
  419. */
  420. function create( &$xmls ) {
  421. $sql = array();
  422. // drop any existing indexes
  423. if( is_array( $legacy_indexes = $xmls->dict->MetaIndexes( $this->name ) ) ) {
  424. foreach( $legacy_indexes as $index => $index_details ) {
  425. $sql[] = $xmls->dict->DropIndexSQL( $index, $this->name );
  426. }
  427. }
  428. // remove fields to be dropped from table object
  429. foreach( $this->drop_field as $field ) {
  430. unset( $this->fields[$field] );
  431. }
  432. // if table exists
  433. if( is_array( $legacy_fields = $xmls->dict->MetaColumns( $this->name ) ) ) {
  434. // drop table
  435. if( $this->drop_table ) {
  436. $sql[] = $xmls->dict->DropTableSQL( $this->name );
  437. return $sql;
  438. }
  439. // drop any existing fields not in schema
  440. foreach( $legacy_fields as $field_id => $field ) {
  441. if( !isset( $this->fields[$field_id] ) ) {
  442. $sql[] = $xmls->dict->DropColumnSQL( $this->name, '`'.$field->name.'`' );
  443. }
  444. }
  445. // if table doesn't exist
  446. } else {
  447. if( $this->drop_table ) {
  448. return $sql;
  449. }
  450. $legacy_fields = array();
  451. }
  452. // Loop through the field specifier array, building the associative array for the field options
  453. $fldarray = array();
  454. foreach( $this->fields as $field_id => $finfo ) {
  455. // Set an empty size if it isn't supplied
  456. if( !isset( $finfo['SIZE'] ) ) {
  457. $finfo['SIZE'] = '';
  458. }
  459. // Initialize the field array with the type and size
  460. $fldarray[$field_id] = array(
  461. 'NAME' => $finfo['NAME'],
  462. 'TYPE' => $finfo['TYPE'],
  463. 'SIZE' => $finfo['SIZE']
  464. );
  465. // Loop through the options array and add the field options.
  466. if( isset( $finfo['OPTS'] ) ) {
  467. foreach( $finfo['OPTS'] as $opt ) {
  468. // Option has an argument.
  469. if( is_array( $opt ) ) {
  470. $key = key( $opt );
  471. $value = $opt[key( $opt )];
  472. @$fldarray[$field_id][$key] .= $value;
  473. // Option doesn't have arguments
  474. } else {
  475. $fldarray[$field_id][$opt] = $opt;
  476. }
  477. }
  478. }
  479. }
  480. if( empty( $legacy_fields ) ) {
  481. // Create the new table
  482. $sql[] = $xmls->dict->CreateTableSQL( $this->name, $fldarray, $this->opts );
  483. logMsg( end( $sql ), 'Generated CreateTableSQL' );
  484. } else {
  485. // Upgrade an existing table
  486. logMsg( "Upgrading {$this->name} using '{$xmls->upgrade}'" );
  487. switch( $xmls->upgrade ) {
  488. // Use ChangeTableSQL
  489. case 'ALTER':
  490. logMsg( 'Generated ChangeTableSQL (ALTERing table)' );
  491. $sql[] = $xmls->dict->ChangeTableSQL( $this->name, $fldarray, $this->opts );
  492. break;
  493. case 'REPLACE':
  494. logMsg( 'Doing upgrade REPLACE (testing)' );
  495. $sql[] = $xmls->dict->DropTableSQL( $this->name );
  496. $sql[] = $xmls->dict->CreateTableSQL( $this->name, $fldarray, $this->opts );
  497. break;
  498. // ignore table
  499. default:
  500. return array();
  501. }
  502. }
  503. foreach( $this->indexes as $index ) {
  504. $sql[] = $index->create( $xmls );
  505. }
  506. if( isset( $this->data ) ) {
  507. $sql[] = $this->data->create( $xmls );
  508. }
  509. return $sql;
  510. }
  511. /**
  512. * Marks a field or table for destruction
  513. */
  514. function drop() {
  515. if( isset( $this->current_field ) ) {
  516. // Drop the current field
  517. logMsg( "Dropping field '{$this->current_field}' from table '{$this->name}'" );
  518. // $this->drop_field[$this->current_field] = $xmls->dict->DropColumnSQL( $this->name, $this->current_field );
  519. $this->drop_field[$this->current_field] = $this->current_field;
  520. } else {
  521. // Drop the current table
  522. logMsg( "Dropping table '{$this->name}'" );
  523. // $this->drop_table = $xmls->dict->DropTableSQL( $this->name );
  524. $this->drop_table = TRUE;
  525. }
  526. }
  527. }
  528. /**
  529. * Creates an index object in ADOdb's datadict format
  530. *
  531. * This class stores information about a database index. As charactaristics
  532. * of the index are loaded from the external source, methods and properties
  533. * of this class are used to build up the index description in ADOdb's
  534. * datadict format.
  535. *
  536. * @package axmls
  537. * @access private
  538. */
  539. class dbIndex extends dbObject {
  540. /**
  541. * @var string Index name
  542. */
  543. var $name;
  544. /**
  545. * @var array Index options: Index-level options
  546. */
  547. var $opts = array();
  548. /**
  549. * @var array Indexed fields: Table columns included in this index
  550. */
  551. var $columns = array();
  552. /**
  553. * @var boolean Mark index for destruction
  554. * @access private
  555. */
  556. var $drop = FALSE;
  557. /**
  558. * Initializes the new dbIndex object.
  559. *
  560. * @param object $parent Parent object
  561. * @param array $attributes Attributes
  562. *
  563. * @internal
  564. */
  565. function dbIndex( &$parent, $attributes = NULL ) {
  566. $this->parent = $parent;
  567. $this->name = $this->prefix ($attributes['NAME']);
  568. }
  569. /**
  570. * XML Callback to process start elements
  571. *
  572. * Processes XML opening tags.
  573. * Elements currently processed are: DROP, CLUSTERED, BITMAP, UNIQUE, FULLTEXT & HASH.
  574. *
  575. * @access private
  576. */
  577. function _tag_open( &$parser, $tag, $attributes ) {
  578. $this->currentElement = strtoupper( $tag );
  579. switch( $this->currentElement ) {
  580. case 'DROP':
  581. $this->drop();
  582. break;
  583. case 'CLUSTERED':
  584. case 'BITMAP':
  585. case 'UNIQUE':
  586. case 'FULLTEXT':
  587. case 'HASH':
  588. // Add index Option
  589. $this->addIndexOpt( $this->currentElement );
  590. break;
  591. default:
  592. // print_r( array( $tag, $attributes ) );
  593. }
  594. }
  595. /**
  596. * XML Callback to process CDATA elements
  597. *
  598. * Processes XML cdata.
  599. *
  600. * @access private
  601. */
  602. function _tag_cdata( &$parser, $cdata ) {
  603. switch( $this->currentElement ) {
  604. // Index field name
  605. case 'COL':
  606. $this->addField( $cdata );
  607. break;
  608. default:
  609. }
  610. }
  611. /**
  612. * XML Callback to process end elements
  613. *
  614. * @access private
  615. */
  616. function _tag_close( &$parser, $tag ) {
  617. $this->currentElement = '';
  618. switch( strtoupper( $tag ) ) {
  619. case 'INDEX':
  620. xml_set_object( $parser, $this->parent );
  621. break;
  622. }
  623. }
  624. /**
  625. * Adds a field to the index
  626. *
  627. * @param string $name Field name
  628. * @return string Field list
  629. */
  630. function addField( $name ) {
  631. $this->columns[$this->FieldID( $name )] = $name;
  632. // Return the field list
  633. return $this->columns;
  634. }
  635. /**
  636. * Adds options to the index
  637. *
  638. * @param string $opt Comma-separated list of index options.
  639. * @return string Option list
  640. */
  641. function addIndexOpt( $opt ) {
  642. $this->opts[] = $opt;
  643. // Return the options list
  644. return $this->opts;
  645. }
  646. /**
  647. * Generates the SQL that will create the index in the database
  648. *
  649. * @param object $xmls adoSchema object
  650. * @return array Array containing index creation SQL
  651. */
  652. function create( &$xmls ) {
  653. if( $this->drop ) {
  654. return NULL;
  655. }
  656. // eliminate any columns that aren't in the table
  657. foreach( $this->columns as $id => $col ) {
  658. if( !isset( $this->parent->fields[$id] ) ) {
  659. unset( $this->columns[$id] );
  660. }
  661. }
  662. return $xmls->dict->CreateIndexSQL( $this->name, $this->parent->name, $this->columns, $this->opts );
  663. }
  664. /**
  665. * Marks an index for destruction
  666. */
  667. function drop() {
  668. $this->drop = TRUE;
  669. }
  670. }
  671. /**
  672. * Creates a data object in ADOdb's datadict format
  673. *
  674. * This class stores information about table data.
  675. *
  676. * @package axmls
  677. * @access private
  678. */
  679. class dbData extends dbObject {
  680. var $data = array();
  681. var $row;
  682. /**
  683. * Initializes the new dbIndex object.
  684. *
  685. * @param object $parent Parent object
  686. * @param array $attributes Attributes
  687. *
  688. * @internal
  689. */
  690. function dbData( &$parent, $attributes = NULL ) {
  691. $this->parent = $parent;
  692. }
  693. /**
  694. * XML Callback to process start elements
  695. *
  696. * Processes XML opening tags.
  697. * Elements currently processed are: DROP, CLUSTERED, BITMAP, UNIQUE, FULLTEXT & HASH.
  698. *
  699. * @access private
  700. */
  701. function _tag_open( &$parser, $tag, $attributes ) {
  702. $this->currentElement = strtoupper( $tag );
  703. switch( $this->currentElement ) {
  704. case 'ROW':
  705. $this->row = count( $this->data );
  706. $this->data[$this->row] = array();
  707. break;
  708. case 'F':
  709. $this->addField($attributes);
  710. default:
  711. // print_r( array( $tag, $attributes ) );
  712. }
  713. }
  714. /**
  715. * XML Callback to process CDATA elements
  716. *
  717. * Processes XML cdata.
  718. *
  719. * @access private
  720. */
  721. function _tag_cdata( &$parser, $cdata ) {
  722. switch( $this->currentElement ) {
  723. // Index field name
  724. case 'F':
  725. $this->addData( $cdata );
  726. break;
  727. default:
  728. }
  729. }
  730. /**
  731. * XML Callback to process end elements
  732. *
  733. * @access private
  734. */
  735. function _tag_close( &$parser, $tag ) {
  736. $this->currentElement = '';
  737. switch( strtoupper( $tag ) ) {
  738. case 'DATA':
  739. xml_set_object( $parser, $this->parent );
  740. break;
  741. }
  742. }
  743. /**
  744. * Adds a field to the index
  745. *
  746. * @param string $name Field name
  747. * @return string Field list
  748. */
  749. function addField( $attributes ) {
  750. if( isset( $attributes['NAME'] ) ) {
  751. $name = $attributes['NAME'];
  752. } else {
  753. $name = count($this->data[$this->row]);
  754. }
  755. // Set the field index so we know where we are
  756. $this->current_field = $this->FieldID( $name );
  757. }
  758. /**
  759. * Adds options to the index
  760. *
  761. * @param string $opt Comma-separated list of index options.
  762. * @return string Option list
  763. */
  764. function addData( $cdata ) {
  765. if( !isset( $this->data[$this->row] ) ) {
  766. $this->data[$this->row] = array();
  767. }
  768. if( !isset( $this->data[$this->row][$this->current_field] ) ) {
  769. $this->data[$this->row][$this->current_field] = '';
  770. }
  771. $this->data[$this->row][$this->current_field] .= $cdata;
  772. }
  773. /**
  774. * Generates the SQL that will create the index in the database
  775. *
  776. * @param object $xmls adoSchema object
  777. * @return array Array containing index creation SQL
  778. */
  779. function create( &$xmls ) {
  780. $table = $xmls->dict->TableName($this->parent->name);
  781. $table_field_count = count($this->parent->fields);
  782. $sql = array();
  783. // eliminate any columns that aren't in the table
  784. foreach( $this->data as $row ) {
  785. $table_fields = $this->parent->fields;
  786. $fields = array();
  787. foreach( $row as $field_id => $field_data ) {
  788. if( !array_key_exists( $field_id, $table_fields ) ) {
  789. if( is_numeric( $field_id ) ) {
  790. $field_id = reset( array_keys( $table_fields ) );
  791. } else {
  792. continue;
  793. }
  794. }
  795. $name = $table_fields[$field_id]['NAME'];
  796. switch( $table_fields[$field_id]['TYPE'] ) {
  797. case 'C':
  798. case 'C2':
  799. case 'X':
  800. case 'X2':
  801. $fields[$name] = $xmls->db->qstr( $field_data );
  802. break;
  803. case 'I':
  804. case 'I1':
  805. case 'I2':
  806. case 'I4':
  807. case 'I8':
  808. $fields[$name] = intval($field_data);
  809. break;
  810. default:
  811. $fields[$name] = $field_data;
  812. }
  813. unset($table_fields[$field_id]);
  814. }
  815. // check that at least 1 column is specified
  816. if( empty( $fields ) ) {
  817. continue;
  818. }
  819. // check that no required columns are missing
  820. if( count( $fields ) < $table_field_count ) {
  821. foreach( $table_fields as $field ) {
  822. if (isset( $field['OPTS'] ))
  823. if( ( in_array( 'NOTNULL', $field['OPTS'] ) || in_array( 'KEY', $field['OPTS'] ) ) && !in_array( 'AUTOINCREMENT', $field['OPTS'] ) ) {
  824. continue(2);
  825. }
  826. }
  827. }
  828. $sql[] = 'INSERT INTO '. $table .' ('. implode( ',', array_keys( $fields ) ) .') VALUES ('. implode( ',', $fields ) .')';
  829. }
  830. return $sql;
  831. }
  832. }
  833. /**
  834. * Creates the SQL to execute a list of provided SQL queries
  835. *
  836. * @package axmls
  837. * @access private
  838. */
  839. class dbQuerySet extends dbObject {
  840. /**
  841. * @var array List of SQL queries
  842. */
  843. var $queries = array();
  844. /**
  845. * @var string String used to build of a query line by line
  846. */
  847. var $query;
  848. /**
  849. * @var string Query prefix key
  850. */
  851. var $prefixKey = '';
  852. /**
  853. * @var boolean Auto prefix enable (TRUE)
  854. */
  855. var $prefixMethod = 'AUTO';
  856. /**
  857. * Initializes the query set.
  858. *
  859. * @param object $parent Parent object
  860. * @param array $attributes Attributes
  861. */
  862. function dbQuerySet( &$parent, $attributes = NULL ) {
  863. $this->parent = $parent;
  864. // Overrides the manual prefix key
  865. if( isset( $attributes['KEY'] ) ) {
  866. $this->prefixKey = $attributes['KEY'];
  867. }
  868. $prefixMethod = isset( $attributes['PREFIXMETHOD'] ) ? strtoupper( trim( $attributes['PREFIXMETHOD'] ) ) : '';
  869. // Enables or disables automatic prefix prepending
  870. switch( $prefixMethod ) {
  871. case 'AUTO':
  872. $this->prefixMethod = 'AUTO';
  873. break;
  874. case 'MANUAL':
  875. $this->prefixMethod = 'MANUAL';
  876. break;
  877. case 'NONE':
  878. $this->prefixMethod = 'NONE';
  879. break;
  880. }
  881. }
  882. /**
  883. * XML Callback to process start elements. Elements currently
  884. * processed are: QUERY.
  885. *
  886. * @access private
  887. */
  888. function _tag_open( &$parser, $tag, $attributes ) {
  889. $this->currentElement = strtoupper( $tag );
  890. switch( $this->currentElement ) {
  891. case 'QUERY':
  892. // Create a new query in a SQL queryset.
  893. // Ignore this query set if a platform is specified and it's different than the
  894. // current connection platform.
  895. if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) {
  896. $this->newQuery();
  897. } else {
  898. $this->discardQuery();
  899. }
  900. break;
  901. default:
  902. // print_r( array( $tag, $attributes ) );
  903. }
  904. }
  905. /**
  906. * XML Callback to process CDATA elements
  907. */
  908. function _tag_cdata( &$parser, $cdata ) {
  909. switch( $this->currentElement ) {
  910. // Line of queryset SQL data
  911. case 'QUERY':
  912. $this->buildQuery( $cdata );
  913. break;
  914. default:
  915. }
  916. }
  917. /**
  918. * XML Callback to process end elements
  919. *
  920. * @access private
  921. */
  922. function _tag_close( &$parser, $tag ) {
  923. $this->currentElement = '';
  924. switch( strtoupper( $tag ) ) {
  925. case 'QUERY':
  926. // Add the finished query to the open query set.
  927. $this->addQuery();
  928. break;
  929. case 'SQL':
  930. $this->parent->addSQL( $this->create( $this->parent ) );
  931. xml_set_object( $parser, $this->parent );
  932. $this->destroy();
  933. break;
  934. default:
  935. }
  936. }
  937. /**
  938. * Re-initializes the query.
  939. *
  940. * @return boolean TRUE
  941. */
  942. function newQuery() {
  943. $this->query = '';
  944. return TRUE;
  945. }
  946. /**
  947. * Discards the existing query.
  948. *
  949. * @return boolean TRUE
  950. */
  951. function discardQuery() {
  952. unset( $this->query );
  953. return TRUE;
  954. }
  955. /**
  956. * Appends a line to a query that is being built line by line
  957. *
  958. * @param string $data Line of SQL data or NULL to initialize a new query
  959. * @return string SQL query string.
  960. */
  961. function buildQuery( $sql = NULL ) {
  962. if( !isset( $this->query ) OR empty( $sql ) ) {
  963. return FALSE;
  964. }
  965. $this->query .= $sql;
  966. return $this->query;
  967. }
  968. /**
  969. * Adds a completed query to the query list
  970. *
  971. * @return string SQL of added query
  972. */
  973. function addQuery() {
  974. if( !isset( $this->query ) ) {
  975. return FALSE;
  976. }
  977. $this->queries[] = $return = trim($this->query);
  978. unset( $this->query );
  979. return $return;
  980. }
  981. /**
  982. * Creates and returns the current query set
  983. *
  984. * @param object $xmls adoSchema object
  985. * @return array Query set
  986. */
  987. function create( &$xmls ) {
  988. foreach( $this->queries as $id => $query ) {
  989. switch( $this->prefixMethod ) {
  990. case 'AUTO':
  991. // Enable auto prefix replacement
  992. // Process object prefix.
  993. // Evaluate SQL statements to prepend prefix to objects
  994. $query = $this->prefixQuery( '/^\s*((?is)INSERT\s+(INTO\s+)?)((\w+\s*,?\s*)+)(\s.*$)/', $query, $xmls->objectPrefix );
  995. $query = $this->prefixQuery( '/^\s*((?is)UPDATE\s+(FROM\s+)?)((\w+\s*,?\s*)+)(\s.*$)/', $query, $xmls->objectPrefix );
  996. $query = $this->prefixQuery( '/^\s*((?is)DELETE\s+(FROM\s+)?)((\w+\s*,?\s*)+)(\s.*$)/', $query, $xmls->objectPrefix );
  997. // SELECT statements aren't working yet
  998. #$data = preg_replace( '/(?ias)(^\s*SELECT\s+.*\s+FROM)\s+(\W\s*,?\s*)+((?i)\s+WHERE.*$)/', "\1 $prefix\2 \3", $data );
  999. case 'MANUAL':
  1000. // If prefixKey is set and has a value then we use it to override the default constant XMLS_PREFIX.
  1001. // If prefixKey is not set, we use the default constant XMLS_PREFIX
  1002. if( isset( $this->prefixKey ) AND( $this->prefixKey !== '' ) ) {
  1003. // Enable prefix override
  1004. $query = str_replace( $this->prefixKey, $xmls->objectPrefix, $query );
  1005. } else {
  1006. // Use default replacement
  1007. $query = str_replace( XMLS_PREFIX , $xmls->objectPrefix, $query );
  1008. }
  1009. }
  1010. $this->queries[$id] = trim( $query );
  1011. }
  1012. // Return the query set array
  1013. return $this->queries;
  1014. }
  1015. /**
  1016. * Rebuilds the query with the prefix attached to any objects
  1017. *
  1018. * @param string $regex Regex used to add prefix
  1019. * @param string $query SQL query string
  1020. * @param string $prefix Prefix to be appended to tables, indices, etc.
  1021. * @return string Prefixed SQL query string.
  1022. */
  1023. function prefixQuery( $regex, $query, $prefix = NULL ) {
  1024. if( !isset( $prefix ) ) {
  1025. return $query;
  1026. }
  1027. if( preg_match( $regex, $query, $match ) ) {
  1028. $preamble = $match[1];
  1029. $postamble = $match[5];
  1030. $objectList = explode( ',', $match[3] );
  1031. // $prefix = $prefix . '_';
  1032. $prefixedList = '';
  1033. foreach( $objectList as $object ) {
  1034. if( $prefixedList !== '' ) {
  1035. $prefixedList .= ', ';
  1036. }
  1037. $prefixedList .= $prefix . trim( $object );
  1038. }
  1039. $query = $preamble . ' ' . $prefixedList . ' ' . $postamble;
  1040. }
  1041. return $query;
  1042. }
  1043. }
  1044. /**
  1045. * Loads and parses an XML file, creating an array of "ready-to-run" SQL statements
  1046. *
  1047. * This class is used to load and parse the XML file, to create an array of SQL statements
  1048. * that can be used to build a database, and to build the database using the SQL array.
  1049. *
  1050. * @tutorial getting_started.pkg
  1051. *
  1052. * @author Richard Tango-Lowy & Dan Cech
  1053. * @version $Revision: 1.12 $
  1054. *
  1055. * @package axmls
  1056. */
  1057. class adoSchema {
  1058. /**
  1059. * @var array Array containing SQL queries to generate all objects
  1060. * @access private
  1061. */
  1062. var $sqlArray;
  1063. /**
  1064. * @var object ADOdb connection object
  1065. * @access private
  1066. */
  1067. var $db;
  1068. /**
  1069. * @var object ADOdb Data Dictionary
  1070. * @access private
  1071. */
  1072. var $dict;
  1073. /**
  1074. * @var string Current XML element
  1075. * @access private
  1076. */
  1077. var $currentElement = '';
  1078. /**
  1079. * @var string If set (to 'ALTER' or 'REPLACE'), upgrade an existing database
  1080. * @access private
  1081. */
  1082. var $upgrade = '';
  1083. /**
  1084. * @var string Optional object prefix
  1085. * @access private
  1086. */
  1087. var $objectPrefix = '';
  1088. /**
  1089. * @var long Original Magic Quotes Runtime value
  1090. * @access private
  1091. */
  1092. var $mgq;
  1093. /**
  1094. * @var long System debug
  1095. * @access private
  1096. */
  1097. var $debug;
  1098. /**
  1099. * @var string Regular expression to find schema version
  1100. * @access private
  1101. */
  1102. var $versionRegex = '/<schema.*?( version="([^"]*)")?.*?>/';
  1103. /**
  1104. * @var string Current schema version
  1105. * @access private
  1106. */
  1107. var $schemaVersion;
  1108. /**
  1109. * @var int Success of last Schema execution
  1110. */
  1111. var $success;
  1112. /**
  1113. * @var bool Execute SQL inline as it is generated
  1114. */
  1115. var $executeInline;
  1116. /**
  1117. * @var bool Continue SQL execution if errors occur
  1118. */
  1119. var $continueOnError;
  1120. /**
  1121. * Creates an adoSchema object
  1122. *
  1123. * Creating an adoSchema object is the first step in processing an XML schema.
  1124. * The only parameter is an ADOdb database connection object, which must already
  1125. * have been created.
  1126. *
  1127. * @param object $db ADOdb database connection object.
  1128. */
  1129. function adoSchema( $db ) {
  1130. // Initialize the environment
  1131. $this->mgq = get_magic_quotes_runtime();
  1132. ini_set("magic_quotes_runtime", 0);
  1133. #set_magic_quotes_runtime(0);
  1134. $this->db = $db;
  1135. $this->debug = $this->db->debug;
  1136. $this->dict = NewDataDictionary( $this->db );
  1137. $this->sqlArray = array();
  1138. $this->schemaVersion = XMLS_SCHEMA_VERSION;
  1139. $this->executeInline( XMLS_EXECUTE_INLINE );
  1140. $this->continueOnError( XMLS_CONTINUE_ON_ERROR );
  1141. $this->setUpgradeMethod();
  1142. }
  1143. /**
  1144. * Sets the method to be used for upgrading an existing database
  1145. *
  1146. * Use this method to specify how existing database objects should be upgraded.
  1147. * The method option can be set to ALTER, REPLACE, BEST, or NONE. ALTER attempts to
  1148. * alter each database object directly, REPLACE attempts to rebuild each object
  1149. * from scratch, BEST attempts to determine the best upgrade method for each
  1150. * object, and NONE disables upgrading.
  1151. *
  1152. * This method is not yet used by AXMLS, but exists for backward compatibility.
  1153. * The ALTER method is automatically assumed when the adoSchema object is
  1154. * instantiated; other upgrade methods are not currently supported.
  1155. *
  1156. * @param string $method Upgrade method (ALTER|REPLACE|BEST|NONE)
  1157. * @returns string Upgrade method used
  1158. */
  1159. function SetUpgradeMethod( $method = '' ) {
  1160. if( !is_string( $method ) ) {
  1161. return FALSE;
  1162. }
  1163. $method = strtoupper( $method );
  1164. // Handle the upgrade methods
  1165. switch( $method ) {
  1166. case 'ALTER':
  1167. $this->upgrade = $method;
  1168. break;
  1169. case 'REPLACE':
  1170. $this->upgrade = $method;
  1171. break;
  1172. case 'BEST':
  1173. $this->upgrade = 'ALTER';
  1174. break;
  1175. case 'NONE':
  1176. $this->upgrade = 'NONE';
  1177. break;
  1178. default:
  1179. // Use default if no legitimate method is passed.
  1180. $this->upgrade = XMLS_DEFAULT_UPGRADE_METHOD;
  1181. }
  1182. return $this->upgrade;
  1183. }
  1184. /**
  1185. * Enables/disables inline SQL execution.
  1186. *
  1187. * Call this method to enable or disable inline execution of the schema. If the mode is set to TRUE (inline execution),
  1188. * AXMLS applies the SQL to the database immediately as each schema entity is parsed. If the mode
  1189. * is set to FALSE (post execution), AXMLS parses the entire schema and you will need to call adoSchema::ExecuteSchema()
  1190. * to apply the schema to the database.
  1191. *
  1192. * @param bool $mode execute
  1193. * @return bool current execution mode
  1194. *
  1195. * @see ParseSchema(), ExecuteSchema()
  1196. */
  1197. function ExecuteInline( $mode = NULL ) {
  1198. if( is_bool( $mode ) ) {
  1199. $this->executeInline = $mode;
  1200. }
  1201. return $this->executeInline;
  1202. }
  1203. /**
  1204. * Enables/disables SQL continue on error.
  1205. *
  1206. * Call this method to enable or disable continuation of SQL execution if an error occurs.
  1207. * If the mode is set to TRUE (continue), AXMLS will continue to apply SQL to the database, even if an error occurs.
  1208. * If the mode is set to FALSE (halt), AXMLS will halt execution of generated sql if an error occurs, though parsing
  1209. * of the schema will continue.
  1210. *
  1211. * @param bool $mode execute
  1212. * @return bool current continueOnError mode
  1213. *
  1214. * @see addSQL(), ExecuteSchema()
  1215. */
  1216. function ContinueOnError( $mode = NULL ) {
  1217. if( is_bool( $mode ) ) {
  1218. $this->continueOnError = $mode;
  1219. }
  1220. return $this->continueOnError;
  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 (see the DTD for the proper format) from
  1226. * the filesystem and generate the SQL necessary to create the database described.
  1227. * @see ParseSchemaString()
  1228. *
  1229. * @param string $file Name of XML schema file.
  1230. * @param bool $returnSchema Return schema rather than parsing.
  1231. * @return array Array of SQL queries, ready to execute
  1232. */
  1233. function ParseSchema( $filename, $returnSchema = FALSE ) {
  1234. return $this->ParseSchemaString( $this->ConvertSchemaFile( $filename ), $returnSchema );
  1235. }
  1236. /**
  1237. * Loads an XML schema from a file and converts it to SQL.
  1238. *
  1239. * Call this method to load the specified schema from a file (see the DTD for the proper format)
  1240. * and generate the SQL necessary to create the database described by the schema.
  1241. *
  1242. * @param string $file Name of XML schema file.
  1243. * @param bool $returnSchema Return schema rather than parsing.
  1244. * @return array Array of SQL queries, ready to execute.
  1245. *
  1246. * @deprecated Replaced by adoSchema::ParseSchema() and adoSchema::ParseSchemaString()
  1247. * @see ParseSchema(), ParseSchemaString()
  1248. */
  1249. function ParseSchemaFile( $filename, $returnSchema = FALSE ) {
  1250. // Open the file
  1251. if( !($fp = fopen( $filename, 'r' )) ) {
  1252. // die( 'Unable to open file' );
  1253. return FALSE;
  1254. }
  1255. // do version detection here
  1256. if( $this->SchemaFileVersion( $filename ) != $this->schemaVersion ) {
  1257. return FALSE;
  1258. }
  1259. if ( $returnSchema )
  1260. {
  1261. $xmlstring = '';
  1262. while( $data = fread( $fp, 100000 ) ) {
  1263. $xmlstring .= $data;
  1264. }
  1265. return $xmlstring;
  1266. }
  1267. $this->success = 2;
  1268. $xmlParser = $this->create_parser();
  1269. // Process the file
  1270. while( $data = fread( $fp, 4096 ) ) {
  1271. if( !xml_parse( $xmlParser, $data, feof( $fp ) ) ) {
  1272. die( sprintf(
  1273. "XML error: %s at line %d",
  1274. xml_error_string( xml_get_error_code( $xmlParser) ),
  1275. xml_get_current_line_number( $xmlParser)
  1276. ) );
  1277. }
  1278. }
  1279. xml_parser_free( $xmlParser );
  1280. return $this->sqlArray;
  1281. }
  1282. /**
  1283. * Converts an XML schema string to SQL.
  1284. *
  1285. * Call this method to parse a string containing an XML schema (see the DTD for the proper format)
  1286. * and generate the SQL necessary to create the database described by the schema.
  1287. * @see ParseSchema()
  1288. *
  1289. * @param string $xmlstring XML schema string.
  1290. * @param bool $returnSchema Return schema rather than parsing.
  1291. * @return array Array of SQL queries, ready to execute.
  1292. */
  1293. function ParseSchemaString( $xmlstring, $returnSchema = FALSE ) {
  1294. if( !is_string( $xmlstring ) OR empty( $xmlstring ) ) {
  1295. return FALSE;
  1296. }
  1297. // do version detection here
  1298. if( $this->SchemaStringVersion( $xmlstring ) != $this->schemaVersion ) {
  1299. return FALSE;
  1300. }
  1301. if ( $returnSchema )
  1302. {
  1303. return $xmlstring;
  1304. }
  1305. $this->success = 2;
  1306. $xmlParser = $this->create_parser();
  1307. if( !xml_parse( $xmlParser, $xmlstring, TRUE ) ) {
  1308. die( sprintf(
  1309. "XML error: %s at line %d",
  1310. xml_error_string( xml_get_error_code( $xmlParser) ),
  1311. xml_get_current_line_number( $xmlParser)
  1312. ) );
  1313. }
  1314. xml_parser_free( $xmlParser );
  1315. return $this->sqlArray;
  1316. }
  1317. /**
  1318. * Loads an XML schema from a file and converts it to uninstallation SQL.
  1319. *
  1320. * Call this method to load the specified schema (see the DTD for the proper format) from
  1321. * the filesystem and generate the SQL necessary to remove the database described.
  1322. * @see RemoveSchemaString()
  1323. *
  1324. * @param string $file Name of XML schema file.
  1325. * @param bool $returnSchema Return schema rather than parsing.
  1326. * @return array Array of SQL queries, ready to execute
  1327. */
  1328. function RemoveSchema( $filename, $returnSchema = FALSE ) {
  1329. return $this->RemoveSchemaString( $this->ConvertSchemaFile( $filename ), $returnSchema );
  1330. }
  1331. /**
  1332. * Converts an XML schema string to uninstallation SQL.
  1333. *
  1334. * Call this method to parse a string containing an XML schema (see the DTD for the proper format)
  1335. * and generate the SQL necessary to uninstall the database described by the schema.
  1336. * @see RemoveSchema()
  1337. *
  1338. * @param string $schema XML schema string.
  1339. * @param bool $returnSchema Return schema rather than parsing.
  1340. * @return array Array of SQL queries, ready to execute.
  1341. */
  1342. function RemoveSchemaString( $schema, $returnSchema = FALSE ) {
  1343. // grab current version
  1344. if( !( $version = $this->SchemaStringVersion( $schema ) ) ) {
  1345. return FALSE;
  1346. }
  1347. return $this->ParseSchemaString( $this->TransformSchema( $schema, 'remove-' . $version), $returnSchema );
  1348. }
  1349. /**
  1350. * Applies the current XML schema to the database (post execution).
  1351. *
  1352. * Call this method to apply the current schema (generally created by calling
  1353. * ParseSchema() or ParseSchemaString() ) to the database (creating the tables, indexes,
  1354. * and executing other SQL specified in the schema) after parsing.
  1355. * @see ParseSchema(), ParseSchemaString(), ExecuteInline()
  1356. *
  1357. * @param array $sqlArray Array of SQL statements that will be applied rather than
  1358. * the current schema.
  1359. * @param boolean $continueOnErr Continue to apply the schema even if an error occurs.
  1360. * @returns integer 0 if failure, 1 if errors, 2 if successful.
  1361. */
  1362. function ExecuteSchema( $sqlArray = NULL, $continueOnErr = NULL ) {
  1363. if( !is_bool( $continueOnErr ) ) {
  1364. $continueOnErr = $this->ContinueOnError();
  1365. }
  1366. if( !isset( $sqlArray ) ) {
  1367. $sqlArray = $this->sqlArray;
  1368. }
  1369. if( !is_array( $sqlArray ) ) {
  1370. $this->success = 0;
  1371. } else {
  1372. $this->success = $this->dict->ExecuteSQLArray( $sqlArray, $continueOnErr );
  1373. }
  1374. return $this->success;
  1375. }
  1376. /**
  1377. * Returns the current SQL array.
  1378. *
  1379. * Call this method to fetch the array of SQL queries resulting from
  1380. * ParseSchema() or ParseSchemaString().
  1381. *
  1382. * @param string $format Format: HTML, TEXT, or NONE (PHP array)
  1383. * @return array Array of SQL statements or FALSE if an error occurs
  1384. */
  1385. function PrintSQL( $format = 'NONE' ) {
  1386. $sqlArray = null;
  1387. return $this->getSQL( $format, $sqlArray );
  1388. }
  1389. /**
  1390. * Saves the current SQL array to the local filesystem as a list of SQL queries.
  1391. *
  1392. * Call this method to save the array of SQL queries (generally resulting from a
  1393. * parsed XML schema) to the filesystem.
  1394. *
  1395. * @param string $filename Path and name where the file should be saved.
  1396. * @return boolean TRUE if save is successful, else FALSE.
  1397. */
  1398. function SaveSQL( $filename = './schema.sql' ) {
  1399. if( !isset( $sqlArray ) ) {
  1400. $sqlArray = $this->sqlArray;
  1401. }
  1402. if( !isset( $sqlArray ) ) {
  1403. return FALSE;
  1404. }
  1405. $fp = fopen( $filename, "w" );
  1406. foreach( $sqlArray as $key => $query ) {
  1407. fwrite( $fp, $query . ";\n" );
  1408. }
  1409. fclose( $fp );
  1410. }
  1411. /**
  1412. * Create an xml parser
  1413. *
  1414. * @return object PHP XML parser object
  1415. *
  1416. * @access private
  1417. */
  1418. function create_parser() {
  1419. // Create the parser
  1420. $xmlParser = xml_parser_create();
  1421. xml_set_object( $xmlParser, $this );
  1422. // Initialize the XML callback functions
  1423. xml_set_element_handler( $xmlParser, '_tag_open', '_tag_close' );
  1424. xml_set_character_data_handler( $xmlParser, '_tag_cdata' );
  1425. return $xmlParser;
  1426. }
  1427. /**
  1428. * XML Callback to process start elements
  1429. *
  1430. * @access private
  1431. */
  1432. function _tag_open( &$parser, $tag, $attributes ) {
  1433. switch( strtoupper( $tag ) ) {
  1434. case 'TABLE':
  1435. $this->obj = new dbTable( $this, $attributes );
  1436. xml_set_object( $parser, $this->obj );
  1437. break;
  1438. case 'SQL':
  1439. if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) {
  1440. $this->obj = new dbQuerySet( $this, $attributes );
  1441. xml_set_object( $parser, $this->obj );
  1442. }
  1443. break;
  1444. default:
  1445. // print_r( array( $tag, $attributes ) );
  1446. }
  1447. }
  1448. /**
  1449. * XML Callback to process CDATA elements
  1450. *
  1451. * @access private
  1452. */
  1453. function _tag_cdata( &$parser, $cdata ) {
  1454. }
  1455. /**
  1456. * XML Callback to process end elements
  1457. *
  1458. * @access private
  1459. * @internal
  1460. */
  1461. function _tag_close( &$parser, $tag ) {
  1462. }
  1463. /**
  1464. * Converts an XML schema string to the specified DTD version.
  1465. *
  1466. * Call this method to convert a string containing an XML schema to a different AXMLS
  1467. * DTD version. For instance, to convert a schema created for an pre-1.0 version for
  1468. * AXMLS (DTD version 0.1) to a newer version of the DTD (e.g. 0.2). If no DTD version
  1469. * parameter is specified, the schema will be converted to the current DTD version.
  1470. * If the newFile parameter is provided, the converted schema will be written to the specified
  1471. * file.
  1472. * @see ConvertSchemaFile()
  1473. *
  1474. * @param string $schema String containing XML schema that will be converted.
  1475. * @param string $newVersion DTD version to convert to.
  1476. * @param string $newFile File name of (converted) output file.
  1477. * @return string Converted XML schema or FALSE if an error occurs.
  1478. */
  1479. function ConvertSchemaString( $schema, $newVersion = NULL, $newFile = NULL ) {
  1480. // grab current version
  1481. if( !( $version = $this->SchemaStringVersion( $schema ) ) ) {
  1482. return FALSE;
  1483. }
  1484. if( !isset ($newVersion) ) {
  1485. $newVersion = $this->schemaVersion;
  1486. }
  1487. if( $version == $newVersion ) {
  1488. $result = $schema;
  1489. } else {
  1490. $result = $this->TransformSchema( $schema, 'convert-' . $version . '-' . $newVersion);
  1491. }
  1492. if( is_string( $result ) AND is_string( $newFile ) AND ( $fp = fopen( $newFile, 'w' ) ) ) {
  1493. fwrite( $fp, $result );
  1494. fclose( $fp );
  1495. }
  1496. return $result;
  1497. }
  1498. // compat for pre-4.3 - jlim
  1499. function _file_get_contents($path)
  1500. {
  1501. if (function_exists('file_get_contents')) return file_get_contents($path);
  1502. return join('',file($path));
  1503. }
  1504. /**
  1505. * Converts an XML schema file to the specified DTD version.
  1506. *
  1507. * Call this method to convert the specified XML schema file to a different AXMLS
  1508. * DTD version. For instance, to convert a schema created for an pre-1.0 version for
  1509. * AXMLS (DTD version 0.1) to a newer version of the DTD (e.g. 0.2). If no DTD version
  1510. * parameter is specified, the schema will be converted to the current DTD version.
  1511. * If the newFile parameter is provided, the converted schema will be written to the specified
  1512. * file.
  1513. * @see ConvertSchemaString()
  1514. *
  1515. * @param string $filename Name of XML schema file that will be converted.
  1516. * @param string $newVersion DTD version to convert to.
  1517. * @param string $newFile File name of (converted) output file.
  1518. * @return string Converted XML schema or FALSE if an error occurs.
  1519. */
  1520. function ConvertSchemaFile( $filename, $newVersion = NULL, $newFile = NULL ) {
  1521. // grab current version
  1522. if( !( $version = $this->SchemaFileVersion( $filename ) ) ) {
  1523. return FALSE;
  1524. }
  1525. if( !isset ($newVersion) ) {
  1526. $newVersion = $this->schemaVersion;
  1527. }
  1528. if( $version == $newVersion ) {
  1529. $result = _file_get_contents( $filename );
  1530. // remove unicode BOM if present
  1531. if( substr( $result, 0, 3 ) == sprintf( '%c%c%c', 239, 187, 191 ) ) {
  1532. $result = substr( $result, 3 );
  1533. }
  1534. } else {
  1535. $result = $this->TransformSchema( $filename, 'convert-' . $version . '-' . $newVersion, 'file' );
  1536. }
  1537. if( is_string( $result ) AND is_string( $newFile ) AND ( $fp = fopen( $newFile, 'w' ) ) ) {
  1538. fwrite( $fp, $result );
  1539. fclose( $fp );
  1540. }
  1541. return $result;
  1542. }
  1543. function TransformSchema( $schema, $xsl, $schematype='string' )
  1544. {
  1545. // Fail if XSLT extension is not available
  1546. if( ! function_exists( 'xslt_create' ) ) {
  1547. return FALSE;
  1548. }
  1549. $xsl_file = dirname( __FILE__ ) . '/xsl/' . $xsl . '.xsl';
  1550. // look for xsl
  1551. if( !is_readable( $xsl_file ) ) {
  1552. return FALSE;
  1553. }
  1554. switch( $schematype )
  1555. {
  1556. case 'file':
  1557. if( !is_readable( $schema ) ) {
  1558. return FALSE;
  1559. }
  1560. $schema = _file_get_contents( $schema );
  1561. break;
  1562. case 'string':
  1563. default:
  1564. if( !is_string( $schema ) ) {
  1565. return FALSE;
  1566. }
  1567. }
  1568. $arguments = array (
  1569. '/_xml' => $schema,
  1570. '/_xsl' => _file_get_contents( $xsl_file )
  1571. );
  1572. // create an XSLT processor
  1573. $xh = xslt_create ();
  1574. // set error handler
  1575. xslt_set_error_handler ($xh, array (&$this, 'xslt_error_handler'));
  1576. // process the schema
  1577. $result = xslt_process ($xh, 'arg:/_xml', 'arg:/_xsl', NULL, $arguments);
  1578. xslt_free ($xh);
  1579. return $result;
  1580. }
  1581. /**
  1582. * Processes XSLT transformation errors
  1583. *
  1584. * @param object $parser XML parser object
  1585. * @param integer $errno Error number
  1586. * @param integer $level Error level
  1587. * @param array $fields Error information fields
  1588. *
  1589. * @access private
  1590. */
  1591. function xslt_error_handler( $parser, $errno, $level, $fields ) {
  1592. if( is_array( $fields ) ) {
  1593. $msg = array(
  1594. 'Message Type' => ucfirst( $fields['msgtype'] ),
  1595. 'Message Code' => $fields['code'],
  1596. 'Message' => $fields['msg'],
  1597. 'Error Number' => $errno,
  1598. 'Level' => $level
  1599. );
  1600. switch( $fields['URI'] ) {
  1601. case 'arg:/_xml':
  1602. $msg['Input'] = 'XML';
  1603. break;
  1604. case 'arg:/_xsl':
  1605. $msg['Input'] = 'XSL';
  1606. break;
  1607. default:
  1608. $msg['Input'] = $fields['URI'];
  1609. }
  1610. $msg['Line'] = $fields['line'];
  1611. } else {
  1612. $msg = array(
  1613. 'Message Type' => 'Error',
  1614. 'Error Number' => $errno,
  1615. 'Level' => $level,
  1616. 'Fields' => var_export( $fields, TRUE )
  1617. );
  1618. }
  1619. $error_details = $msg['Message Type'] . ' in XSLT Transformation' . "\n"
  1620. . '<table>' . "\n";
  1621. foreach( $msg as $label => $details ) {
  1622. $error_details .= '<tr><td><b>' . $label . ': </b></td><td>' . htmlentities( $details ) . '</td></tr>' . "\n";
  1623. }
  1624. $error_details .= '</table>';
  1625. trigger_error( $error_details, E_USER_ERROR );
  1626. }
  1627. /**
  1628. * Returns the AXMLS Schema Version of the requested XML schema file.
  1629. *
  1630. * Call this method to obtain the AXMLS DTD version of the requested XML schema file.
  1631. * @see SchemaStringVersion()
  1632. *
  1633. * @param string $filename AXMLS schema file
  1634. * @return string Schema version number or FALSE on error
  1635. */
  1636. function SchemaFileVersion( $filename ) {
  1637. // Open the file
  1638. if( !($fp = fopen( $filename, 'r' )) ) {
  1639. // die( 'Unable to open file' );
  1640. return FALSE;
  1641. }
  1642. // Process the file
  1643. while( $data = fread( $fp, 4096 ) ) {
  1644. if( preg_match( $this->versionRegex, $data, $matches ) ) {
  1645. return !empty( $matches[2] ) ? $matches[2] : XMLS_DEFAULT_SCHEMA_VERSION;
  1646. }
  1647. }
  1648. return FALSE;
  1649. }
  1650. /**
  1651. * Returns the AXMLS Schema Version of the provided XML schema string.
  1652. *
  1653. * Call this method to obtain the AXMLS DTD version of the provided XML schema string.
  1654. * @see SchemaFileVersion()
  1655. *
  1656. * @param string $xmlstring XML schema string
  1657. * @return string Schema version number or FALSE on error
  1658. */
  1659. function SchemaStringVersion( $xmlstring ) {
  1660. if( !is_string( $xmlstring ) OR empty( $xmlstring ) ) {
  1661. return FALSE;
  1662. }
  1663. if( preg_match( $this->versionRegex, $xmlstring, $matches ) ) {
  1664. return !empty( $matches[2] ) ? $matches[2] : XMLS_DEFAULT_SCHEMA_VERSION;
  1665. }
  1666. return FALSE;
  1667. }
  1668. /**
  1669. * Extracts an XML schema from an existing database.
  1670. *
  1671. * Call this method to create an XML schema string from an existing database.
  1672. * If the data parameter is set to TRUE, AXMLS will include the data from the database
  1673. * in the schema.
  1674. *
  1675. * @param boolean $data Include data in schema dump
  1676. * @return string Generated XML schema
  1677. */
  1678. function ExtractSchema( $data = FALSE ) {
  1679. $old_mode = $this->db->SetFetchMode( ADODB_FETCH_NUM );
  1680. $schema = '<?xml version="1.0"?>' . "\n"
  1681. . '<schema version="' . $this->schemaVersion . '">' . "\n";
  1682. if( is_array( $tables = $this->db->MetaTables( 'TABLES' ) ) ) {
  1683. foreach( $tables as $table ) {
  1684. $schema .= ' <table name="' . $table . '">' . "\n";
  1685. // grab details from database
  1686. $rs = $this->db->Execute( 'SELECT * FROM ' . $table . ' WHERE 1=1' );
  1687. $fields = $this->db->MetaColumns( $table );
  1688. $indexes = $this->db->MetaIndexes( $table );
  1689. if( is_array( $fields ) ) {
  1690. foreach( $fields as $details ) {
  1691. $extra = '';
  1692. $content = array();
  1693. if( $details->max_length > 0 ) {
  1694. $extra .= ' size="' . $details->max_length . '"';
  1695. }
  1696. if( $details->primary_key ) {
  1697. $content[] = '<KEY/>';
  1698. } elseif( $details->not_null ) {
  1699. $content[] = '<NOTNULL/>';
  1700. }
  1701. if( $details->has_default ) {
  1702. $content[] = '<DEFAULT value="' . $details->default_value . '"/>';
  1703. }
  1704. if( $details->auto_increment ) {
  1705. $content[] = '<AUTOINCREMENT/>';
  1706. }
  1707. // this stops the creation of 'R' columns,
  1708. // AUTOINCREMENT is used to create auto columns
  1709. $details->primary_key = 0;
  1710. $type = $rs->MetaType( $details );
  1711. $schema .= ' <field name="' . $details->name . '" type="' . $type . '"' . $extra . '>';
  1712. if( !empty( $content ) ) {
  1713. $schema .= "\n " . implode( "\n ", $content ) . "\n …

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