cb.sql.upgrader.php
上传用户:stephen_wu
上传日期:2008-07-05
资源大小:1757k
文件大小:74k
源码类别:

网络

开发平台:

Unix_Linux

  1. <?php
  2. /**
  3. * @version $Id: cb.sql.upgrader.php 444 2008-03-05 02:25:39Z beat $
  4. * @package Community Builder
  5. * @subpackage cb.sql.upgrader.php
  6. * @author Beat
  7. * @copyright (C) 2007 Beat and Lightning MultiCom SA, 1009 Pully, Switzerland
  8. * @license Lightning Proprietary. See licence. Allowed for free use within CB and for CB plugins.
  9. */
  10. // no direct access
  11. if ( ! ( defined( '_VALID_CB' ) || defined( '_JEXEC' ) || defined( '_VALID_MOS' ) ) ) { die( 'Direct Access to this location is not allowed.' ); }
  12. // cbimport('cb.xml.simplexml'); needed to use this.
  13. /**
  14.  * CB SQL versioning / upgrading functions:
  15.  * 
  16.  * WARNING:
  17.  * This new library is experimental work in progress and should not be used directly by plugins and 3pds,
  18.  * as it is subject to change without notice, and is not part of current CB API.
  19.  * 
  20.  * @access private
  21.  * 
  22.  */
  23. class CBSQLupgrader {
  24. /** Database
  25.  * @var CBdatabase */
  26. var $_db = null;
  27. var $_silentTestLogs = true;
  28. var $_logs = array();
  29. var $_errors = array();
  30. var $_logsIndex = 0;
  31. var $_dryRun = false;
  32. /**
  33.  * Constructor
  34.  *
  35.  * @param  CBdatabase     $db
  36.  * @param  boolean        $silentTestLogs  TRUE: Silent on successful tests
  37.  * @return CBSQLupgrader
  38.  */
  39. function CBSQLupgrader( &$db, $silentTestLogs = true ) {
  40. $this->_db =& $db;
  41. $this->_silentTestLogs = $silentTestLogs;
  42. }
  43. /**
  44.  * Sets if SQL tables changing queries should not be run (for a dryrun)
  45.  *
  46.  * @param  boolean  $dryRun  FALSE (default): tables are changed, TRUE: Dryrunning
  47.  */
  48. function setDryRun( $dryRun ) {
  49. $this->_dryRun = $dryRun;
  50. }
  51. /**
  52.  * LOGS OF ACTIONS AND OF ERRORS:
  53.  */
  54. /**
  55.  * Records error with details (details here is SQL query)
  56.  * @access private
  57.  *
  58.  * @param  string  $error
  59.  * @param  string  $info
  60.  */
  61. function _setError( $error, $info = null) {
  62. $this->_errors[++$this->_logsIndex] = array( $error, $info );
  63. }
  64. /**
  65.  * Returns all errors logged
  66.  *
  67.  * @param  string|boolean  $implode         False: returns full array, if string: imploding string
  68.  * @param  string|boolean  $detailsImplode  False: no details, otherwise imploding string
  69.  * @return string|array
  70.  */
  71. function getErrors( $implode = "n", $detailsImplode = false ) {
  72. if ( $implode === false) {
  73. return $this->_errors;
  74. } else {
  75. $errors = array();
  76. if ( $detailsImplode ) {
  77. foreach ( $this->_errors as $errInfo ) {
  78. $errors[] = implode( $detailsImplode, $errInfo );
  79. }
  80. } else {
  81. foreach ( $this->_errors as $errInfo ) {
  82. $errors[] = $errInfo[0];
  83. }
  84. }
  85. return implode( $implode, $errors );
  86. }
  87. }
  88. /**
  89.  * Records logs with details (details here are SQL queries ( ";n"-separated )
  90.  * @access private
  91.  *
  92.  * @param  string  $log
  93.  * @param  string  $info
  94.  * @param  string  $type  'ok': successful check, 'change': successful change
  95.  */
  96. function _setLog( $log, $info = null, $type ) {
  97. if ( ( $type != 'ok' ) || ! $this->_silentTestLogs ) {
  98. $this->_logs[++$this->_logsIndex] = array( $log, $info );
  99. }
  100. }
  101. /**
  102.  * Returns all logs logged
  103.  *
  104.  * @param  string|boolean  $implode         False: returns full array, if string: imploding string
  105.  * @param  string|boolean  $detailsImplode  False: no details, otherwise imploding string
  106.  * @return string|array
  107.  */
  108. function getLogs( $implode = "n", $detailsImplode = false ) {
  109. if ( $implode === false) {
  110. return $this->_logs;
  111. } else {
  112. $logs = array();
  113. if ( $detailsImplode ) {
  114. foreach ( $this->_logs as $logInfo ) {
  115. $logs[] = implode( $detailsImplode, $logInfo );
  116. }
  117. } else {
  118. foreach ( $this->_logs as $logInfo ) {
  119. $logs[] = $logInfo[0];
  120. }
  121. }
  122. return implode( $implode, $logs );
  123. }
  124. }
  125. /**
  126.  * SQL ACCESS FUNCTIONS:
  127.  */
  128. /**
  129.  * Checks if a given table exists in the database
  130.  * @access private
  131.  *
  132.  * @param  string  $tableName  Name of table
  133.  * @return boolean
  134.  */
  135. function checkTableExists( $tableName ) {
  136. $allTables = $this->_db->getTableList();
  137. return ( in_array( $tableName, $allTables ) );
  138. }
  139. /**
  140.  * Checks if table exists in database and returns all fields of table.
  141.  * Otherwise returns boolean false.
  142.  * @access private
  143.  *
  144.  * @param  string  $tableName  Name of table
  145.  * @return array|boolean       Array of SHOW COLUMNS FROM ... in SQL or boolean FALSE
  146.  */
  147. function getAllTableColumns( $tableName ) {
  148. if ( $this->checkTableExists( $tableName ) ) {
  149. $fields = $this->_db->getTableFields( $tableName, false );
  150. if ( isset( $fields[$tableName] ) ) {
  151. return $fields[$tableName];
  152. }
  153. }
  154. return false;
  155. }
  156. /**
  157.  * Returns all indexes of the table
  158.  * @access private
  159.  *
  160.  * @param  string  $tableName  Name of table
  161.  * @return array               Array of SHOW INDEX FROM ... in SQL
  162.  */
  163. function getAllTableIndexes( $tableName ) {
  164. $sortedIndex = array();
  165. $idx = $this->_db->getTableIndex( $tableName );
  166. if ( is_array( $idx ) ) {
  167. foreach ( $idx as $k => $n ) {
  168. $sortedIndex[$n->Key_name][$n->Seq_in_index] = array( 'name' => $n->Column_name,
  169. 'size' => $n->Sub_part,
  170. 'ordering' => $n->Collation,
  171. 'type' => ( $n->Key_name == 'PRIMARY' ? 'primary' : ( $n->Non_unique == 0 ? 'unique' : '' ) ),
  172. 'using' => ( array_key_exists( 'Index_type', $n ) ? strtolower( $n->Index_type ) : ( $n->Comment == 'FULLTEXT' ? 'fulltext' : '' ) ) // mysql <4.0.2 support
  173.  );
  174. }
  175. }
  176. return $sortedIndex;
  177. }
  178. /**
  179.  * COLUMNS CHECKS:
  180.  */
  181. /**
  182.  * Checks if a column exists and has the type of the parameters below:
  183.  * @access private
  184.  *
  185.  * @param  string              $tableName        Name of table (for error strings)
  186.  * @param  array               $allColumns       From $this->getAllTableColumns( $table )
  187.  * @param  CBSimpleXMLElement  $column           Column to check
  188.  * @param  string              $colNamePrefix    Prefix to add to all column names
  189.  * @param  boolean             $change           TRUE: only true/false check type, FALSE: logs success and if mismatch, error details
  190.  * @return boolean             TRUE: identical (no check on indexes), FALSE: errors are in $this->getErrors()
  191.  */
  192. function checkColumnExistsType( $tableName, &$allColumns, &$column, $colNamePrefix, $change ) {
  193. $colName = $this->_prefixedName( $column, $colNamePrefix );
  194. if ( isset( $allColumns[$colName] ) ) {
  195. if ( ! cbStartOfStringMatch( $column->attributes( 'type' ), 'sql:' ) ) {
  196. $this->_setError( sprintf( 'Table %s Column %s type is %s instead of being prefixed by "sql:"', $tableName, $colName, $column->attributes( 'type' ) ) );
  197. return false;
  198. }
  199. if ( $column->attributes( 'strict' ) === 'false' ) {
  200. $this->_setLog( sprintf( 'Table %s Column %s exists but is not of strict type, so not checked.', $tableName, $colName ), null, 'ok' );
  201. return true;
  202. }
  203. $type = substr( $column->attributes( 'type' ), 4 );
  204. $shouldbeType = $type . ( $column->attributes( 'unsigned' ) === 'true' ? ' unsigned' : '' );
  205. if ( $allColumns[$colName]->Type !== $shouldbeType ) {
  206. if ( $change === false ) {
  207. $this->_setError( sprintf( 'Table %s Column %s type is %s instead of %s', $tableName, $colName, $allColumns[$colName]->Type, $shouldbeType ) );
  208. }
  209. return false;
  210. }
  211. if ( ( $column->attributes( 'null' ) === 'true' ) !== ( $allColumns[$colName]->Null == 'YES' ) ) { //if ( $column->attributes( 'null' ) !== null ): no attribute NULL means NOT NULL
  212. if ( $change === false ) {
  213. $this->_setError( sprintf( 'Table %s Column %s NULL attribute is %s instead of %s', $tableName, $colName, $allColumns[$colName]->Null, ( $column->attributes( 'null' ) === 'true' ? 'YES' : 'NO') ) );
  214. }
  215. return false;
  216. }
  217. // BLOB and TEXT columns cannot have DEFAULT values. http://dev.mysql.com/doc/refman/5.0/en/blob.html
  218. $defaultValuePossible = ! in_array( $type, array( 'text', 'blob', 'tinytext', 'mediumtext', 'longtext', 'tinyblob', 'mediumblob', 'longblob' ) );
  219. // autoincremented columns don't care for default values:
  220. $autoIncrementedColumn = ! in_array( $column->attributes( 'auto_increment' ), array( null, '', 'false' ), true );
  221. if ( $defaultValuePossible && ! $autoIncrementedColumn ) {
  222. if ( $column->attributes( 'default' ) === null ) {
  223. if ( $column->attributes( 'null' ) === 'true' ) {
  224. $shouldbeDefault = array( null );
  225. } else {
  226. $shouldbeDefault = $this->defaultValuesOfTypes( $this->mysqlToXmlsql( $column->attributes( 'type' ) ) );
  227. }
  228. } else {
  229. $shouldbeDefault = ( $column->attributes( 'default' ) === 'NULL' ? array( null ) : array( $column->attributes( 'default' ) ) );
  230. }
  231. if ( ! in_array( $allColumns[$colName]->Default, $shouldbeDefault, true ) ) {
  232. if ( $change === false ) {
  233. $this->_setError( sprintf( 'Table %s Column %s DEFAULT is %s instead of %s', $tableName, $colName, $this->displayNull( $allColumns[$colName]->Default ), $column->attributes( 'default' ) ) );
  234. }
  235. return false;
  236. }
  237. }
  238. $shouldbeExtra = ( $autoIncrementedColumn ? 'auto_increment' : '' );
  239. if ( $allColumns[$colName]->Extra !== $shouldbeExtra ) {
  240. if ( $change === false ) {
  241. $this->_setError( sprintf( 'Table %s Column %s AUTO_INCREMENT attribute is "%s" instead of "%s"', $tableName, $colName, $allColumns[$colName]->Extra, $shouldbeExtra ) );
  242. }
  243. return false;
  244. }
  245. $this->_setLog( sprintf( 'Table %s Column %s structure is up-to-date.', $tableName, $colName ), null, 'ok' );
  246. return true;
  247. }
  248. if ( $change === false ) {
  249. $this->_setError( sprintf( 'Table %s Column %s does not exist', $tableName, $colName ), null );
  250. }
  251. return false;
  252. }
  253. /**
  254.  * Utility to display NULL for nulls and quotations.
  255.  *
  256.  * @param unknown_type $val
  257.  * @return unknown
  258.  */
  259. function displayNull( $val ) {
  260. if ( $val === null ) {
  261. return 'NULL';
  262. } elseif ( is_numeric( $val ) ) {
  263. return $val;
  264. } else {
  265. return "'" . $val . "'";
  266. }
  267. }
  268. /**
  269.  * Checks if a column exists and has the type of the parameters below:
  270.  * @access private
  271.  *
  272.  * @param  string              $tableName       Name of table (for error strings)
  273.  * @param  array               $allColumns      From $this->getAllTableColumns( $table )
  274.  * @param  CBSimpleXMLElement  $columns         Columns to check array of string  Name of columns which are allowed to (should) exist
  275.  * @param  string              $colNamePrefix    Prefix to add to all column names
  276.  * @param  boolean             $drop            TRUE If drops unneeded columns or not
  277.  * @return boolean             TRUE: no other columns exist, FALSE: errors are in $this->getErrors()
  278.  */
  279. function checkOtherColumnsExist( $tableName, &$allColumns, &$columns, $colNamePrefix, $drop = false ) {
  280. $isMatching = false;
  281. if ( $columns->name() == 'columns' ) {
  282. $isMatching = true;
  283. foreach ( array_keys( $allColumns ) as $existingColumnName ) {
  284. if ( ! $this->_inXmlChildrenAttribute( $existingColumnName, $columns, 'column', 'name', $colNamePrefix ) ) {
  285. if ( $drop ) {
  286. if ( ! $this->dropColumn( $tableName, $existingColumnName ) ) {
  287. $isMatching = false;
  288. }
  289. } else {
  290. $isMatching = false;
  291. $this->_setError( sprintf( 'Table %s Column %s exists but should not exist', $tableName, $existingColumnName ), null );
  292. }
  293. }
  294. }
  295. if ( $isMatching && ! $drop ) {
  296. $this->_setLog( sprintf( 'Table %s has no unneeded columns.', $tableName ), null, 'ok' );
  297. }
  298. }
  299. return $isMatching;
  300. }
  301. /**
  302.  * INDEXES CHECKS:
  303.  */
  304. /**
  305.  * Checks if an index exists and has the type of the parameters below:
  306.  * @access private
  307.  *
  308.  * @param  string              $tableName        Name of table (for error strings)
  309.  * @param  array               $allIndexes       From $this->getAllTableIndexes( $table )
  310.  * @param  CBSimpleXMLElement  $index            Index to check
  311.  * @param  string              $colNamePrefix    Prefix to add to all column names
  312.  * @param  boolean             $change           TRUE: only true/false check type, FALSE: logs success and if mismatch, error details
  313.  * @return boolean             TRUE: identical, FALSE: errors are in $this->getErrors()
  314.  */
  315. function checkIndexExistsType( $tableName, &$allIndexes, &$index, $colNamePrefix, $change ) {
  316. $indexName = $this->_prefixedName( $index, $colNamePrefix );
  317. if ( isset( $allIndexes[$indexName] ) && isset( $allIndexes[$indexName][1] ) ) {
  318. $idxType = $allIndexes[$indexName][1]['type'];
  319. $idxUsing = $allIndexes[$indexName][1]['using'];
  320. if ( $idxType != $index->attributes( 'type' ) ) {
  321. if ( $change === false ) {
  322. $this->_setError( sprintf( 'Table %s Index %s type is %s instead of %s', $tableName, $indexName, $idxType, $index->attributes( 'type' ) ) );
  323. }
  324. return false;
  325. }
  326. if ( $index->attributes( 'using' ) && ( $idxUsing != $index->attributes( 'using' ) ) ) {
  327. if ( $change === false ) {
  328. $indexShouldBeUsing = ( $index->attributes( 'using' ) ? $index->attributes( 'using' ) : 'btree' );
  329. $this->_setError( sprintf( 'Table %s Index %s is using %s instead of %s', $tableName, $indexName, $idxUsing, $indexShouldBeUsing ) );
  330. }
  331. return false;
  332. }
  333. $sequence = 1;
  334. foreach ( $index->children() as $column ) {
  335. if ( $column->name() == 'column' ) {
  336. $colName = $this->_prefixedName( $column, $colNamePrefix );
  337. if ( ! isset( $allIndexes[$indexName][$sequence] ) ) {
  338. if ( $change === false ) {
  339. $this->_setError( sprintf( 'Table %s Index %s Column %s is missing in index', $tableName, $indexName, $colName ) );
  340. }
  341. return false;
  342. }
  343. if ( $allIndexes[$indexName][$sequence]['name'] != $colName ) {
  344. if ( $change === false ) {
  345. $this->_setError( sprintf( 'Table %s Index %s Column %s is not the intended column, but %s', $tableName, $indexName, $colName, $allIndexes[$indexName][$sequence]['name'] ) );
  346. }
  347. return false;
  348. }
  349. if ( $column->attributes( 'size' ) && ( $allIndexes[$indexName][$sequence]['size'] != $column->attributes( 'size' ) ) ) {
  350. if ( $change === false ) {
  351. $this->_setError( sprintf( 'Table %s Index %s Column %s Size is %d instead of %s', $tableName, $indexName, $colName, $allIndexes[$indexName][$sequence]['size'], $column->attributes( 'size' ) ) );
  352. }
  353. return false;
  354. }
  355. // don't check ordering, as it can't be checked, and is probably irrelevant.
  356. ++$sequence;
  357. }
  358. }
  359. $this->_setLog( sprintf( 'Table %s Index %s is up-to-date.', $tableName, $indexName ), null, 'ok' );
  360. return true;
  361. }
  362. if ( $change === false ) {
  363. $this->_setError( sprintf( 'Table %s Index %s does not exist', $tableName, $indexName ), null );
  364. }
  365. return false;
  366. }
  367. /**
  368.  * Checks if no surnumerous indexes exist
  369.  * @access private
  370.  *
  371.  * @param  string              $tableName        Name of table (for error strings)
  372.  * @param  array               $allIndexes       From $this->getAllTableIndexes( $table )
  373.  * @param  CBSimpleXMLElement  $indexes          Indexes to check
  374.  * @param  string              $colNamePrefix    Prefix to add to all column names
  375.  * @param  boolean             $drop             TRUE If drops unneeded columns or not
  376.  * @return boolean             TRUE: no other columns exist, FALSE: errors are in $this->getErrors()
  377.  */
  378. function checkOtherIndexesExist( $tableName, &$allIndexes, &$indexes, $colNamePrefix, $drop = false ) {
  379. $isMatching = false;
  380. if ( $indexes->name() == 'indexes' ) {
  381. $isMatching = true;
  382. foreach ( array_keys( $allIndexes ) as $existingIndexName ) {
  383. if ( ! $this->_inXmlChildrenAttribute( $existingIndexName, $indexes, 'index', 'name', $colNamePrefix ) ) {
  384. if ( $drop ) {
  385. if ( ! $this->dropIndex( $tableName, $existingIndexName ) ) {
  386. $isMatching = false;
  387. }
  388. } else {
  389. $isMatching = false;
  390. $this->_setError( sprintf( 'Table %s Index %s exists but should not exist', $tableName, $existingIndexName ), null );
  391. }
  392. }
  393. }
  394. if ( $isMatching && ! $drop ) {
  395. $this->_setLog( sprintf( 'Table %s has no unneeded indexes.', $tableName ), null, 'ok' );
  396. }
  397. }
  398. return $isMatching;
  399. }
  400. /**
  401.  * ROWS CHECKS:
  402.  */
  403. /**
  404.  * Checks if no surnumerous indexes exist
  405.  * @access private
  406.  *
  407.  * @param  string              $tableName        Name of table (for error strings)
  408.  * @param  CBSimpleXMLElement  $rows             <rows...>
  409.  * @param  string              $colNamePrefix    Prefix to add to all column names
  410.  * @param  boolean             $drop             TRUE If drops unneeded columns or not
  411.  * @return boolean             TRUE: no other columns exist, FALSE: errors are in $this->getErrors()
  412.  */
  413. function checkOtherRowsExist( $tableName, &$rows, $colNamePrefix, $drop = false ) {
  414. $isMatching = false;
  415. if ( $rows->name() == 'rows' ) {
  416. $isMatching = true;
  417. $strictRows = ( ( $rows->attributes( 'strict' ) === 'true' ) );
  418. if ( true /* $strictRows */ ) {
  419. // Build $strictRows index of indexes:
  420. $rowIndexes = array();
  421. foreach ( $rows->children() as $row ) {
  422. if ( $row->name() == 'row' ) {
  423. $indexName = $this->_prefixedName( $row, $colNamePrefix, 'index', 'indextype' );
  424. $indexValue = $row->attributes( 'value' );
  425. $indexValueType = $row->attributes( 'valuetype' );
  426. $rowIndexes[$indexName][$indexValue] = $indexValueType;
  427. }
  428. }
  429. // Count and if asked, drop rows which don't match:
  430. $otherRowsCount = $this->countRows( $tableName, $rowIndexes, false );
  431. $isMatching = ( ( $otherRowsCount !== null ) && ( $otherRowsCount == 0 ) );
  432. if ( ! $isMatching ) {
  433. if ( $drop ) {
  434. $isMatching = $this->dropRows( $tableName, $rowIndexes, false );
  435. } else {
  436. $this->_setError( sprintf( 'Table %s has %s rows which should not exist', $tableName, $otherRowsCount ), null );
  437. }
  438. }
  439. }
  440. if ( $isMatching && ! $drop ) {
  441. $this->_setLog( sprintf( 'Table %s has no unneeded rows.', $tableName ), null, 'ok' );
  442. }
  443. }
  444. return $isMatching;
  445. }
  446. /**
  447.  * Drops $row from table $tableName
  448.  * @access private
  449.  *
  450.  * @param  string   $tableName                   Name of table (for error strings)
  451.  * @param  CBSimpleXMLElement  $row              <row index="columnname" indextype="prefixname" value="123" valuetype="sql:int" /> to delete
  452.  * @param  string              $colNamePrefix    Prefix to add to all column names
  453.  * @return boolean
  454.  */
  455. function dropRow( $tableName, &$row, $colNamePrefix ) {
  456. $indexName = $this->_prefixedName( $row, $colNamePrefix, 'index', 'indextype' );
  457. $indexValue = $row->attributes( 'value' );
  458. $indexValueType = $row->attributes( 'valuetype' );
  459. $selection = array( $indexName => array( $indexValue => $indexValueType ) );
  460. return $this->dropRows( $tableName, $selection, true );
  461. }
  462. /**
  463.  * CHANGES OF TABLE STRUCTURE:
  464.  */
  465. /**
  466.  * Changes if a column exists or Creates a new column
  467.  * @access private
  468.  *
  469.  * @param  string              $tableName        Name of table (for error strings)
  470.  * @param  array               $allColumns       From $this->getAllTableColumns( $table )
  471.  * @param  CBSimpleXMLElement  $column           Column to check
  472.  * @param  string              $colNamePrefix    Prefix to add to all column names
  473.  * @param  CBSimpleXMLElement  $columnNameAfter  The column which should be just before this one
  474.  * @return boolean  TRUE: identical (no check on indexes), FALSE: errors are in $this->getErrors()
  475.  */
  476. function changeColumn( $tableName, &$allColumns, &$column, $colNamePrefix, &$columnNameAfter ) {
  477. switch ( $columnNameAfter ) {
  478. case null:
  479. $firstAfterSQL = '';
  480. break;
  481. case 1:
  482. $firstAfterSQL = ' FIRST';
  483. break;
  484. default:
  485. $colNameAfterPrefixed = $this->_prefixedName( $columnNameAfter, $colNamePrefix );
  486. $firstAfterSQL = ' AFTER ' . $this->_db->NameQuote( $colNameAfterPrefixed );
  487. break;
  488. }
  489. $colNamePrefixed = $this->_prefixedName( $column, $colNamePrefix );
  490. $sqlUpdate = '';
  491. $updateResult = true;
  492. if ( isset( $allColumns[$colNamePrefixed] ) ) {
  493. // column exists already, change it:
  494. if ( $column->attributes( 'oldname' ) && array_key_exists( $this->_prefixedName( $column, $colNamePrefix, 'oldname' ), $allColumns ) ) {
  495. $oldColName = $this->_prefixedName( $column, $colNamePrefix, 'oldname' );
  496. } else {
  497. $oldColName = $colNamePrefixed;
  498. }
  499. if ( $column->attributes( 'initialvalue' ) && ( $column->attributes( 'null' ) !== 'true' ) ) {
  500. // we do need to treat the old NULL values specially:
  501. $sqlUpdate = 'UPDATE ' . $this->_db->NameQuote( $tableName )
  502. . "n SET " . $this->_db->NameQuote( $colNamePrefixed )
  503. . ' = ' . $this->_sqlCleanQuote( $column->attributes( 'initialvalue' ), $column->attributes( 'initialvaluetype' ) )
  504. . "n WHERE " . $this->_db->NameQuote( $oldColName ) . ' IS NULL' 
  505. ;
  506. $updateResult = $this->_doQuery( $sqlUpdate );
  507. }
  508. $alteration = 'CHANGE ' . $this->_db->NameQuote( $oldColName );
  509. } else {
  510. // column doesn't exist, create it:
  511. $alteration = 'ADD';
  512. }
  513. $sql = 'ALTER TABLE ' . $this->_db->NameQuote( $tableName )
  514. . "n " . $alteration
  515. . ' ' . $this->_db->NameQuote( $colNamePrefixed )
  516. . ' ' . $this->_fullColumnType( $column )
  517. . $firstAfterSQL
  518. ;
  519. $alterationResult = $this->_doQuery( $sql );
  520. if ( $alterationResult && ( $alteration == 'ADD' ) ) {
  521. if ( $column->attributes( 'initialvalue' ) ) {
  522. $sqlUpdate = 'UPDATE ' . $this->_db->NameQuote( $tableName )
  523. . "n SET " . $this->_db->NameQuote( $colNamePrefixed )
  524. . ' = ' . $this->_sqlCleanQuote( $column->attributes( 'initialvalue' ), $column->attributes( 'initialvaluetype' ) ) 
  525. ;
  526. $updateResult = $this->_doQuery( $sqlUpdate );
  527. }
  528. }
  529. if ( ! $alterationResult ) {
  530. $this->_setError( sprintf( '%s::changeColumn (%s) of Table %s Column %s failed with SQL error: %s', get_class( $this ), $alteration, $tableName, $colNamePrefixed, $this->_db->getErrorMsg() ), $sql );
  531. return false;
  532. } elseif ( ! $updateResult ) {
  533. $this->_setError( sprintf( '%s::changeColumn (UPDATE) of Table %s Column %s failed with SQL error: %s', get_class( $this ), $alteration, $tableName, $colNamePrefixed, $this->_db->getErrorMsg() ), $sqlUpdate );
  534. return false;
  535. } else {
  536. $this->_setLog( sprintf( 'Table %s Column %s %s successfully, type: %s', $tableName, $colNamePrefixed, ( $alteration == 'ADD' ? 'created' : 'changed' ), $this->_fullColumnType( $column ) ),
  537. ( $alteration == 'ADD' ? $sql . ( $sqlUpdate ? ";n" . $sqlUpdate : '' ) : ( $sqlUpdate ?  $sqlUpdate . ";n" : '' ) . $sql ),
  538. 'change' );
  539. return true;
  540. }
  541. }
  542. /**
  543.  * Changes if an index exists or Creates a new index
  544.  * @access private
  545.  *
  546.  * @param  string              $tableName        Name of table (for error strings)
  547.  * @param  array               $allColumns       From $this->getAllTableColumns( $table )
  548.  * @param  CBSimpleXMLElement  $column           Column to check
  549.  * @param  string              $colNamePrefix    Prefix to add to all column names
  550.  * @return boolean  TRUE: identical (no check on indexes), FALSE: errors are in $this->getErrors()
  551.  */
  552. function changeIndex( $tableName, &$allIndexes, &$index, $colNamePrefix ) {
  553. $indexName = $this->_prefixedName( $index, $colNamePrefix );
  554. $queryParts = array();
  555. if ( isset( $allIndexes[$indexName] ) ) {
  556. // index exists already,drop it:
  557. if ( $indexName == 'PRIMARY') {
  558. $queryParts[] = 'DROP PRIMARY KEY';
  559. } else {
  560. $queryParts[] = 'DROP KEY ' . $this->_db->NameQuote( $indexName );
  561. }
  562. $alteration = 'change';
  563. } else {
  564. $alteration = 'new';
  565. }
  566. // Now create new index:
  567. $queryParts[] = 'ADD ' . $this->_fullIndexType( $index, $colNamePrefix );
  568. $sql = 'ALTER TABLE ' . $this->_db->NameQuote( $tableName )
  569. . "n " . implode( ",n ", $queryParts )
  570. ;
  571. $alterationResult = $this->_doQuery( $sql );
  572. if ( ! $alterationResult ) {
  573. $this->_setError( sprintf( '%s::changeIndex (%s) of Table %s Index %s failed with SQL error: %s', get_class( $this ), $alteration, $tableName, $indexName, $this->_db->getErrorMsg() ), $sql );
  574. return false;
  575. } else {
  576. $this->_setLog( sprintf( 'Table %s Index %s successfully %s', $tableName, $indexName, ( $alteration == 'new' ? 'created' : 'changed' ) ), $sql, 'change' );
  577. return true;
  578. }
  579. }
  580. /**
  581.  * Checks if an index exists and has the type of the parameters below:
  582.  * @access private
  583.  *
  584.  * @param  string              $tableName        Name of table (for error strings)
  585.  * @param  CBSimpleXMLElement  $rows             <rows...>
  586.  * @param  CBSimpleXMLElement  $row              <row> to change
  587.  * @param  array               $allColumns       From $this->getAllTableColumns( $table ) : columns which were existing before upgrading columns called before this function
  588.  * @param  string              $colNamePrefix    Prefix to add to all column names
  589.  * @param  boolean             $change           TRUE: changes row, FALSE: checks row
  590.  * @return boolean             TRUE: identical, FALSE: errors are in $this->getErrors()
  591.  */
  592. function checkOrChangeRow( $tableName, &$rows, &$row, &$allColumns, $colNamePrefix, $change = true ) {
  593. $indexName = $this->_prefixedName( $row, $colNamePrefix, 'index', 'indextype' );
  594. $indexValue = $row->attributes( 'value' );
  595. $indexValueType = $row->attributes( 'valuetype' );
  596. $rowsArray = $this->loadRows( $tableName, $indexName, $indexValue, $indexValueType );
  597. $mismatchingFields = array();
  598. if ( is_array( $rowsArray ) && ( count( $rowsArray ) > 0 ) ) {
  599. foreach ( $rowsArray as $rowData ) {
  600. foreach ( $row->children() as $field ) {
  601. if ( $field->name() == 'field' ) {
  602. $strictField = $field->attributes( 'strict' );
  603. $fieldName = $this->_prefixedName( $field, $colNamePrefix );
  604. if ( $strictField || ! isset( $allColumns[$fieldName] ) ) {
  605. // if field is strict, or if column has just been created: compare value to the should be one:
  606. $fieldValue = $field->attributes( 'value' );
  607. $fieldValueType = $field->attributes( 'valuetype' );
  608. if ( ( ! isset( $allColumns[$fieldName] ) )
  609. || ( ! array_key_exists( $fieldName, $rowData ) )
  610. || ( ( $strictField === 'true' ) && ( $this->_adjustToStrictType( $rowData->$fieldName, $fieldValueType ) !== $this->_phpCleanQuote( $fieldValue, $fieldValueType ) ) )
  611. || ( ( $strictField === 'notnull' ) && ( $this->_adjustToStrictType( $rowData->$fieldName, $fieldValueType ) === null ) && ( $this->_phpCleanQuote( $fieldValue, $fieldValueType ) !== null ) )
  612. || ( ( $strictField === 'notzero' ) && ( ( ( $this->_adjustToStrictType( $rowData->$fieldName, $fieldValueType ) === null ) || ( $this->_adjustToStrictType( $rowData->$fieldName, $fieldValueType ) == 0 ) )
  613.  && ( ! ( ( $this->_phpCleanQuote( $fieldValue, $fieldValueType ) === null ) || ( $this->_phpCleanQuote( $fieldValue, $fieldValueType ) === 0 ) ) ) ) )
  614. || ( ( $strictField === 'notempty' ) && ( ( ( $this->_adjustToStrictType( $rowData->$fieldName, $fieldValueType ) === null ) || ( $this->_adjustToStrictType( $rowData->$fieldName, $fieldValueType ) == '' ) )
  615.  && ( ! ( ( $this->_phpCleanQuote( $fieldValue, $fieldValueType ) === null ) || ( $this->_phpCleanQuote( $fieldValue, $fieldValueType ) === '' ) ) ) ) )
  616.    )
  617. {
  618. $mismatchingFields[$fieldName] = $this->_sqlCleanQuote( $fieldValue, $fieldValueType );
  619. }
  620. }
  621. }
  622. }
  623. foreach ( $row->children() as $field ) {
  624. if ( $field->name() == 'field' ) {
  625. $strictField = $field->attributes( 'strict' );
  626. if ( $strictField == 'updatewithfield' ) {
  627. // if field should be updated same time than another field: check if the field is in the list to be upgraded:
  628. $strictSameAsField = $field->attributes( 'strictsameasfield' );
  629. if ( isset( $mismatchingFields[$strictSameAsField] ) ) {
  630. $fieldName = $this->_prefixedName( $field, $colNamePrefix );
  631. $fieldValue = $field->attributes( 'value' );
  632. $fieldValueType = $field->attributes( 'valuetype' );
  633. if ( ( ! array_key_exists( $fieldName, $rowData ) )
  634. || ( $this->_adjustToStrictType( $rowData->$fieldName, $fieldValueType ) !== $this->_phpCleanQuote( $fieldValue, $fieldValueType ) )
  635.    )
  636. {
  637. $mismatchingFields[$fieldName] = $this->_sqlCleanQuote( $fieldValue, $fieldValueType );
  638. }
  639. }
  640. }
  641. }
  642. }
  643. }
  644. if ( count( $mismatchingFields ) > 0 ) {
  645. if ( $change === true ) {
  646. return $this->setFields( $tableName, $row, $mismatchingFields, $colNamePrefix );
  647. } else {
  648. $texts = array();
  649. foreach ($mismatchingFields as $name => $val ) {
  650. $texts[] = sprintf( 'Field %s = %s instead of %s', $name, ( isset( $rowData->$name ) ? $rowData->$name : '""' ), $val );
  651. }
  652. $this->_setError( sprintf( 'Table %s Rows %s = %s : %s', $tableName, $indexName, $indexValue, implode( ', ', $texts ) ) );
  653. return false;
  654. }
  655. } else {
  656. if ( $change === false ) {
  657. $this->_setLog( sprintf( 'Table %s Rows %s = %s are up-to-date.', $tableName, $indexName, $this->_sqlCleanQuote( $indexValue, $indexValueType ) ), null, 'ok' );
  658. }
  659. return true;
  660. }
  661. } else {
  662. if ( $change === true ) {
  663. return $this->insertRow( $tableName, $row, $colNamePrefix );
  664. } else {
  665. $this->_setError( sprintf( 'Table %s Rows %s = %s do not exist', $tableName, $indexName, $this->_sqlCleanQuote( $indexValue, $indexValueType ) ), null );
  666. }
  667. return false;
  668. }
  669. }
  670. /**
  671.  * Load rows from table $tableName
  672.  * @access private
  673.  *
  674.  * @param  string   $tableName        Name of table (for error strings)
  675.  * @param  string   $indexName
  676.  * @param  string   $indexValue
  677.  * @param  string   $indexValueType
  678.  * @return boolean
  679.  */
  680. function loadRows( $tableName, $indexName, $indexValue, $indexValueType ) {
  681. $sql = 'SELECT * FROM ' . $this->_db->NameQuote( $tableName );
  682. if ( $indexName ) {
  683. $sql .= "n WHERE " . $this->_db->NameQuote( $indexName )
  684. . ' = '
  685. . $this->_sqlCleanQuote( $indexValue, $indexValueType )
  686. ;
  687. }
  688. $this->_db->setQuery( $sql );
  689. $result = $this->_db->loadObjectList();
  690. if ( $this->_db->getErrorMsg() ) {
  691. $this->_setError( sprintf( '%s::loadRows of Table %s Rows %s = %s failed with SQL error: %s', get_class( $this ), $tableName, $columnName, $this->_sqlCleanQuote( $indexValue, $indexValueType ), $this->_db->getErrorMsg() ), $sql );
  692. return false;
  693. } else {
  694. // $this->_setLog( sprintf( 'Table %s Rows %s = %s successfully loaded', $tableName, $columnName, $this->_sqlCleanQuote( $indexValue, $indexValueType ) ), $sql, 'change' );
  695. return $result;
  696. }
  697. }
  698. /**
  699.  * Drop rows from table $tableName matching $selection
  700.  * @access private
  701.  *
  702.  * @param  string   $tableName
  703.  * @param  array    $selection        array( 'columnName' => array( 'columnValue' => 'columnValueType' ) )
  704.  * @param  boolean  $positiveSelect   TRUE: select corresponding to selection, FALSE: Select NOT the selection
  705.  * @return boolean                    TRUE: no error, FALSE: error (logged)
  706.  */
  707. function dropRows( $tableName, &$selection, $positiveSelect ) {
  708. $where = $this->_sqlBuiildSelectionWhere( $selection, $positiveSelect );
  709. $sql = 'DELETE FROM ' . $this->_db->NameQuote( $tableName )
  710. . "n WHERE " . $where
  711. ;
  712. if ( ! $this->_doQuery( $sql ) ) {
  713. $this->_setError( sprintf( '%s::dropRows of Table %s Row(s) %s failed with SQL error: %s', get_class( $this ), $tableName, $where, $this->_db->getErrorMsg() ), $sql );
  714. return false;
  715. } else {
  716. $this->_setLog( sprintf( 'Table %s Row(s) %s successfully dropped', $tableName, $where ), $sql, 'change' );
  717. return true;
  718. }
  719. }
  720. /**
  721.  * Counts rows from table $tableName matching $selection
  722.  * @access private
  723.  *
  724.  * @param  string   $tableName
  725.  * @param  array    $selection        array( 'columnName' => array( 'columnValue' => 'columnValueType' ) )
  726.  * @param  boolean  $positiveSelect   TRUE: select corresponding to selection, FALSE: Select NOT the selection
  727.  * @return boolean                    TRUE: no error, FALSE: error (logged)
  728.  */
  729. function countRows( $tableName, &$selection, $positiveSelect ) {
  730. $where = $this->_sqlBuiildSelectionWhere( $selection, $positiveSelect );
  731. $sql = 'SELECT COUNT(*) FROM ' . $this->_db->NameQuote( $tableName )
  732. . "n WHERE " . $where
  733. ;
  734. $this->_db->setQuery( $sql );
  735. $result = $this->_db->loadResult();
  736. if ( $result === null ) {
  737. $this->_setError( sprintf( '%s::countRows of Table %s Row(s) %s failed with SQL error: %s', get_class( $this ), $tableName, $where, $this->_db->getErrorMsg() ), $sql );
  738. }
  739. return $result;
  740. }
  741. /**
  742.  * Counts rows from table $tableName matching $selection
  743.  * @access private
  744.  *
  745.  * @param  string              $tableName
  746.  * @param  CBSimpleXMLElement  $row                <row index="columnname" indextype="prefixname" value="123" valuetype="sql:int" /> to delete
  747.  * @param  array               $mismatchingFields  array( 'columnName' => 'SQL-safe value' )
  748.  * @param  string              $colNamePrefix    Prefix to add to all column names
  749.  * @return boolean                                 TRUE: no error, FALSE: error (logged)
  750.  */
  751. function setFields( $tableName, &$row, &$mismatchingFields, $colNamePrefix ) {
  752. $indexName = $this->_prefixedName( $row, $colNamePrefix, 'index', 'indextype' );
  753. $indexValue = $row->attributes( 'value' );
  754. $indexValueType = $row->attributes( 'valuetype' );
  755. $selection = array( $indexName => array( $indexValue => $indexValueType ) );
  756. $where = $this->_sqlBuiildSelectionWhere( $selection, true );
  757. $setFields = array();
  758. foreach ( $mismatchingFields as $name => $quotedValue ) {
  759. $setFields[] = $this->_db->NameQuote( $name ) . ' = ' . $quotedValue;
  760. }
  761. $setFieldsText = implode( ', ', $setFields );
  762. $sql = 'UPDATE ' . $this->_db->NameQuote( $tableName )
  763. . "n SET " . $setFieldsText
  764. . "n WHERE " . $where
  765. ;
  766. if ( ! $this->_doQuery( $sql ) ) {
  767. $this->_setError( sprintf( '%s::setFields of Table %s Row %s Fields %s failed with SQL error: %s', get_class( $this ), $tableName, $where, $setFieldsText, $this->_db->getErrorMsg() ), $sql );
  768. return false;
  769. } else {
  770. $this->_setLog( sprintf( 'Table %s Row %s successfully updated', $tableName, $where ), $sql, 'change' );
  771. return true;
  772. }
  773. }
  774. /**
  775.  * Checks if an index exists and has the type of the parameters below:
  776.  * @access private
  777.  *
  778.  * @param  string              $tableName        Name of table (for error strings)
  779.  * @param  CBSimpleXMLElement  $row              <row> to change
  780.  * @param  string              $colNamePrefix    Prefix to add to all column names
  781.  * @return boolean             TRUE: success, FALSE: errors are in $this->getErrors()
  782.  */
  783. function insertRow( $tableName, &$row, $colNamePrefix ) {
  784. $indexName = $this->_prefixedName( $row, $colNamePrefix, 'index', 'indextype' );
  785. $indexValue = $row->attributes( 'value' );
  786. $indexValueType = $row->attributes( 'valuetype' );
  787. if ( $row->name() == 'row' ) {
  788. $sqlFieldNames = array();
  789. $sqlFieldValues = array();
  790. foreach ( $row->children() as $field ) {
  791. if ( $field->name() == 'field' ) {
  792. $fieldName = $this->_prefixedName( $field, $colNamePrefix );
  793. $fieldValue = $field->attributes( 'value' );
  794. $fieldValueType = $field->attributes( 'valuetype' );
  795. if ( ( $fieldName == $indexName ) && ( ( $fieldValue != $indexValue ) || ( $fieldValueType != $indexValueType ) ) ) {
  796. $this->_setError( sprintf( '%s::insertRow Error in XML: Table %s Row %s = %s (type %s) trying to insert different Field Value or type: %s = %s (type %s)', get_class( $this ), $tableName, $indexName, $indexValue, $indexValueType, $fieldName, $fieldValue, $fieldValueType ), null );
  797. return false;
  798. }
  799. if ( isset( $sqlFieldNames[$fieldName] ) ) {
  800. $this->_setError( sprintf( '%s::insertRow Error in XML: Table %s Row %s = %s : Field %s is defined twice in XML', get_class( $this ), $tableName, $indexName, $indexValue, $fieldName ), null );
  801. return false;
  802. }
  803. $sqlFieldNames[$fieldName] = $this->_db->NameQuote( $fieldName );
  804. $sqlFieldValues[$fieldName] = $this->_sqlCleanQuote( $fieldValue, $fieldValueType );
  805. }
  806. }
  807. if ( ! isset( $sqlFieldNames[$indexName] ) ) {
  808. $sqlFieldNames[$indexName] = $this->_db->NameQuote( $indexName );
  809. $sqlFieldValues[$indexName] = $this->_sqlCleanQuote( $indexValue, $indexValueType );
  810. }
  811. if ( count( $sqlFieldNames ) > 0 ) {
  812. $sqlColumnsText = '(' . implode( ',', $sqlFieldNames ) . ')';
  813. $sqlColumnsValues = array();
  814. $sqlColumnsValues[] = '(' . implode( ',', $sqlFieldValues ) . ')';
  815. } elseif ( $indexName ) {
  816. $sqlColumnsText = '(' . $this->_db->NameQuote( $indexName ) . ')';
  817. $sqlColumnsValues = '(' . $this->_sqlCleanQuote( $indexValue, $indexValueType );
  818. } else {
  819. $sqlColumnsText = null;
  820. }
  821. if ( $sqlColumnsText != null ) {
  822. $sql = 'INSERT INTO ' . $this->_db->NameQuote( $tableName )
  823. . "n " . $sqlColumnsText
  824. . "n VALUES " . implode( ",n        ", $sqlColumnsValues )
  825. ;
  826. if ( ! $this->_doQuery( $sql ) ) {
  827. $this->_setError( sprintf( '%s::insertRow of Table %s Row %s = %s Fields %s = %s failed with SQL error: %s', get_class( $this ), $tableName, $indexName, $indexValue, $sqlColumnsText, $sqlColumnsValues, $this->_db->getErrorMsg() ), $sql );
  828. return false;
  829. } else {
  830. $this->_setLog( sprintf( 'Table %s Row %s = %s successfully updated', $tableName, $indexName, $indexValue ), $sql, 'change' );
  831. return true;
  832. }
  833. }
  834. }
  835. $this->_setError( sprintf( '%s::insertRow : Error in SQL: No values to insert Row %s = %s (type %s)', $tableName, $indexName, $indexValue, $indexValueType ), $sql );
  836. return true;
  837. }
  838. /**
  839.  * Builds SQL WHERE statement (without WHERE) based on array $selection
  840.  * @access private
  841.  *
  842.  * @param  array    $selection        array( 'columnName' => array( 'columnValue' => 'columnValueType' ) )
  843.  * @param  boolean  $positiveSelect   TRUE: select corresponding to selection, FALSE: Select NOT the selection
  844.  * @return boolean  True: no error, False: error (logged)
  845.  */
  846. function _sqlBuiildSelectionWhere( &$selection, $positiveSelect ) {
  847. $where = array();
  848. foreach ( $selection as $colName => $valuesArray ) {
  849. $values = array();
  850. foreach ( $valuesArray as $colValue => $colValueType ) {
  851. $values[] = $this->_sqlCleanQuote( $colValue, $colValueType );
  852. }
  853. if ( count( $values ) > 0 ) {
  854. if ( count( $values ) > 1 ) {
  855. $where[] = $this->_db->NameQuote( $colName ) . ' IN (' .implode( ',', $values ) . ')';
  856. } else {
  857. $where[] = $this->_db->NameQuote( $colName ) . ' = ' . $values[0];
  858. }
  859. }
  860. }
  861. $positiveWhere = '(' . implode( ') OR (', $where ) . ')';
  862. if ( $positiveSelect ) {
  863. return $positiveWhere;
  864. } else {
  865. return 'NOT(' . $positiveWhere . ')';
  866. }
  867. }
  868. /**
  869.  * Drops column $ColumnName from table $tableName
  870.  * @access private
  871.  *
  872.  * @param  string   $tableName        Name of table (for error strings)
  873.  * @param  string   $columnName       Old name of column to change
  874.  * @return boolean                    TRUE: no error, FALSE: errors are in $this->getErrors()
  875.  */
  876. function dropColumn( $tableName, $columnName ) {
  877. $sql = 'ALTER TABLE ' . $this->_db->NameQuote( $tableName )
  878. . "n DROP COLUMN " . $this->_db->NameQuote( $columnName )
  879. ;
  880. if ( ! $this->_doQuery( $sql ) ) {
  881. $this->_setError( sprintf( '%s::dropColumn of Table %s Column %s failed with SQL error: %s', get_class( $this ), $tableName, $columnName, $this->_db->getErrorMsg() ), $sql );
  882. return false;
  883. } else {
  884. $this->_setLog( sprintf( 'Table %s Column %s successfully dropped', $tableName, $columnName ), $sql, 'change' );
  885. return true;
  886. }
  887. }
  888. /**
  889.  * Drops INDEX $indexName from table $tableName
  890.  * @access private
  891.  *
  892.  * @param  string   $tableName        Name of table (for error strings)
  893.  * @param  string   $indexName       Old name of column to change
  894.  * @return boolean                    TRUE: no error, FALSE: errors are in $this->getErrors()
  895.  */
  896. function dropIndex( $tableName, $indexName ) {
  897. $sql = 'ALTER TABLE ' . $this->_db->NameQuote( $tableName );
  898. if ( $indexName == 'PRIMARY' ) {
  899. $sql .= "n DROP PRIMARY KEY";
  900. } else {
  901. $sql .= "n DROP KEY " . $this->_db->NameQuote( $indexName );
  902. }
  903. if ( ! $this->_doQuery( $sql ) ) {
  904. $this->_setError( sprintf( '%s::dropIndex of Table %s Index %s failed with SQL error: %s', get_class( $this ), $tableName, $indexName, $this->_db->getErrorMsg() ), $sql );
  905. return false;
  906. } else {
  907. $this->_setLog( sprintf( 'Table %s Index %s successfully dropped', $tableName, $indexName ), $sql, 'change' );
  908. return true;
  909. }
  910. }
  911. /**
  912.  * Drops table $tableName
  913.  * @access private
  914.  *
  915.  * @param  string   $tableName        Name of table (for error strings)
  916.  * @return boolean                    TRUE: no error, FALSE: errors are in $this->getErrors()
  917.  */
  918. function dropTable( $tableName ) {
  919. $sql = 'DROP TABLE ' . $this->_db->NameQuote( $tableName )
  920. ;
  921. if ( ! $this->_doQuery( $sql ) ) {
  922. $this->_setError( sprintf( '%s::dropTable of Table %s failed with SQL error: %s', get_class( $this ), $tableName, $this->_db->getErrorMsg() ), $sql );
  923. return false;
  924. } else {
  925. $this->_setLog( sprintf( 'Table %s successfully dropped', $tableName ), $sql, 'change' );
  926. return true;
  927. }
  928. }
  929. /**
  930.  * Creates a new table
  931.  * @access private
  932.  *
  933.  * @param  CBSimpleXMLElement  $table  Table
  934.  * @param  string              $colNamePrefix    Prefix to add to all column names
  935.  * @return boolean                               True: success, False: failure
  936.  */
  937. function createTable( &$table, $colNamePrefix ) {
  938. if ( $table->name() == 'table' ) {
  939. $tableName = $this->_prefixedName( $table, $colNamePrefix );
  940. $columns =& $table->getElementByPath( 'columns' );
  941. if ( $tableName && ( $columns !== false ) ) {
  942. $sqlColumns = array();
  943. $auto_increment_initial_value = '';
  944. foreach ( $columns->children() as $column ) {
  945. if ( $column->name() == 'column' ) {
  946. $colNamePrefixed = $this->_prefixedName( $column, $colNamePrefix );
  947. $sqlColumns[] = "n " . $this->_db->NameQuote( $colNamePrefixed )
  948. . ' ' . $this->_fullColumnType( $column )
  949. ;
  950. if ( (int) $column->attributes( 'auto_increment' ) ) {
  951. $auto_increment_initial_value = ' AUTO_INCREMENT=' . (int) $column->attributes( 'auto_increment' );
  952. }
  953. }
  954. }
  955. $indexes =& $table->getElementByPath( 'indexes' );
  956. if ( $indexes !== false ) {
  957. foreach ( $indexes->children() as $index ) {
  958. if ( $index->name() == 'index' ) {
  959. $sqlIndexText = $this->_fullIndexType( $index, $colNamePrefix );
  960. if ( $sqlIndexText ) {
  961. $sqlColumns[] = "n " . $sqlIndexText;
  962. }
  963. }
  964. }
  965. }
  966. $sql = 'CREATE TABLE ' . $this->_db->NameQuote( $tableName )
  967. . ' ('
  968. . implode( ',', $sqlColumns )
  969. . "n) ENGINE=MyISAM"
  970. . $auto_increment_initial_value
  971. ;
  972. if ( ! $this->_doQuery( $sql ) ) {
  973. $this->_setError( sprintf( '%s::createTableof Table %s failed with SQL error: %s', get_class( $this ), $tableName, $this->_db->getErrorMsg() ), $sql );
  974. return false;
  975. } else {
  976. $this->_setLog( sprintf( 'Table %s successfully created', $tableName ), $sql, 'change' );
  977. return true;
  978. }
  979. }
  980. }
  981. return false;
  982. }
  983. /**
  984.  * UTILITY FUNCTIONS:
  985.  */
  986. /**
  987.  * Sets modifying query and performs it, IF NOT in dry run mode.
  988.  * If in dry run mode, returns true
  989.  * @access private
  990.  *
  991.  * @param  string  $sql
  992.  * @return boolean
  993.  */
  994. function _doQuery( $sql ) {
  995. if ( $this->_dryRun ) {
  996. return true;
  997. } else {
  998. $this->_db->SetQuery( $sql );
  999. return $this->_db->query();
  1000. }
  1001. }
  1002. /**
  1003.  * Utility: Checks if $needle is the $attribute of a child of $xml
  1004.  * @access private
  1005.  *
  1006.  * @param  string              $needle
  1007.  * @param  CBSimpleXMLElement  $xml
  1008.  * @param  string              $attribute
  1009.  * @param  string              $colNamePrefix    Prefix to add to all column names
  1010.  * @return boolean
  1011.  */
  1012. function _inXmlChildrenAttribute( $needle, &$xml, $name, $attribute, $colNamePrefix) {
  1013. foreach ( $xml->children() as $chld ) {
  1014. if ( $chld->name() == $name ) {
  1015. $colNamePrefixed = $this->_prefixedName( $chld, $colNamePrefix, $attribute );
  1016. if ( $needle == $colNamePrefixed ) {
  1017. return true;
  1018. }
  1019. }
  1020. }
  1021. return false;
  1022. }
  1023. /**
  1024.  * Converts a XML description of a SQL column into a full SQL type
  1025.  *
  1026.  * <column name="_rate" nametype="namesuffix" type="sql:decimal(16,8)" unsigned="true" null="true" default="NULL" auto_increment="100" />
  1027.  *
  1028.  * Returns: $fulltype: 'decimal(16,8) unsigned NULL DEFAULT NULL'
  1029.  * @access private
  1030.  *
  1031.  * @param  CBSimpleXMLElement  $column
  1032.  * @return string|boolean              Full SQL creation type or FALSE in case of error
  1033.  */
  1034. function _fullColumnType( &$column ) {
  1035. $fullType = false;
  1036. if ( $column->name() == 'column' ) {
  1037. // $colName = $column->attributes( 'name' );
  1038. // $colNameType = $column->attributes( 'nametype' );
  1039. // if ( $colNameType == 'namesuffix' ) {
  1040. // $colName = $colNamePrefix . $colName;
  1041. // }
  1042. $type = $column->attributes( 'type' );
  1043. $unsigned = $column->attributes( 'unsigned' );
  1044. $null = $column->attributes( 'null' );
  1045. $default = $column->attributes( 'default' );
  1046. $auto_increment = $column->attributes( 'auto_increment' );
  1047. if ( cbStartOfStringMatch( $type, 'sql:' ) ) {
  1048. $type = trim( substr( $type, 4 ) ); // remove 'sql:'
  1049. if ( $type ) {
  1050. $notQuoted = array( 'int', 'float', 'tinyint', 'bigint', 'decimal', 'boolean', 'bit', 'serial', 'smallint', 'mediumint', 'double', 'year' );
  1051. $isInt = false;
  1052. foreach ( $notQuoted as $n ) {
  1053. if ( cbStartOfStringMatch( $type, $n ) ) {
  1054. $isInt = true;
  1055. break;
  1056. }
  1057. }
  1058. $fullType = $type;
  1059. if ( $unsigned == 'true' ) {
  1060. $fullType .= ' unsigned';
  1061. }
  1062. if ( $null !== 'true' ) {
  1063. $fullType .= ' NOT NULL';
  1064. }
  1065. if ( ! in_array( $type, array( 'text', 'blob', 'tinytext', 'mediumtext', 'longtext', 'tinyblob', 'mediumblob', 'longblob' ))) {
  1066. // BLOB and TEXT columns cannot have DEFAULT values. http://dev.mysql.com/doc/refman/5.0/en/blob.html
  1067. if ( $default !== null ) {
  1068. $fullType .= ' DEFAULT ' . ( ( $isInt || ( $default === 'NULL' ) ) ? $default : $this->_db->Quote( $default ) );
  1069. } elseif ( ! $auto_increment ) {
  1070. // MySQL 5.0.51a and b have a bug: they need a default value always to be able to return it correctly in SHOW COLUMNS FROM ...:
  1071. if ( $null === 'true' ) {
  1072. $default = 'NULL';
  1073. } elseif ( $isInt ) {
  1074. $default = 0;
  1075. } elseif ( in_array( $type, array( 'datetime', 'date', 'time' ) ) ) {
  1076. $default = $this->_db->getNullDate( $type );
  1077. } else {
  1078. $default = '';
  1079. }
  1080. $fullType .= ' DEFAULT ' . ( ( $isInt || ( $default === 'NULL' ) ) ? $default : $this->_db->Quote( $default ) );
  1081. }
  1082. }
  1083. if ( $auto_increment ) {
  1084. $fullType .= ' auto_increment';
  1085. }
  1086. }
  1087. }
  1088. }
  1089. return $fullType;
  1090. }
  1091. /**
  1092.  * Converts a mysql type with 'sql:' prefix to a xmlsql sql:/const: type (without prefix).
  1093.  *
  1094.  * @param  string  $type   MySql type, E.g.: 'sql:varchar(255)' (with 'sql:' prefix)
  1095.  * @return string          Xmlsql type, E.g.: 'string' (without 'sql:' or 'const:' prefix)
  1096.  */
  1097. function mysqlToXmlsql( $type ) {
  1098. $mysqlTypes = array( 'varchar' => 'string',
  1099. 'character' => 'string',
  1100. 'char' => 'string',
  1101. 'binary' => 'string',
  1102. 'varbinary' => 'string',
  1103. 'tinyblob' => 'string',
  1104. 'blob' => 'string',
  1105. 'mediumblob' => 'string',
  1106. 'longblob' => 'string',
  1107. 'tinytext' => 'string',
  1108. 'mediumtext' => 'string',
  1109. 'longtext' => 'string',
  1110. 'text' => 'string',
  1111. 'tinyint' => 'int',
  1112. 'smallint' => 'int',
  1113. 'mediumint' => 'int',
  1114. 'bigint' => 'int',
  1115. 'integer' => 'int',
  1116. 'int' => 'int',
  1117. 'bit' => 'int',
  1118. 'boolean' => 'int',
  1119. 'year' => 'int',
  1120. 'float' => 'float',
  1121. 'double' => 'float',
  1122. 'decimal' => 'float',
  1123. 'date' => 'date',
  1124. 'datetime' => 'datetime',
  1125. 'timestamp' => 'datetime',
  1126. 'time' => 'time',
  1127. 'enum' => 'string'
  1128. // missing since not in SQL standard: SET, and ENUM above is a little simplified since only partly supported.
  1129.  );
  1130. $cleanedType = preg_replace( '/^sql:([^\(]*)\(?.*/', '$1', $type );
  1131. if ( isset( $mysqlTypes[$cleanedType] ) ) {
  1132. return $mysqlTypes[$cleanedType];
  1133. } else {
  1134. trigger_error( sprintf( 'mysqlToXmlsql: Unknown SQL type %s (i am extracting "%s" from type)', $type, $cleanedType ), E_USER_WARNING );
  1135. return $type;
  1136. }
  1137. }
  1138. /**
  1139.  * Returns the possible default default values for that type
  1140.  *
  1141.  * @param  string $type
  1142.  * @return array  of string
  1143.  */
  1144. function defaultValuesOfTypes( $type ) {
  1145. $defaultNulls = array( 'string' => array( ''  ),
  1146. 'int' => array( '', '0' ),
  1147. 'float' => array( '', '0' ),
  1148. 'date' => array( '', '0000-00-00' ),
  1149. 'datetime' => array( '', '0000-00-00 00:00:00' ),
  1150. 'time' => array( '', '00:00:00' ),
  1151. 'enum' => array( '' )
  1152.  );
  1153. if ( isset( $defaultNulls[$type] ) ) {
  1154. return $defaultNulls[$type];
  1155. } else {
  1156. trigger_error( sprintf( 'defaultValuesOfTypes: Unknown SQL type %s', $type ), E_USER_WARNING );
  1157. return array( '', 0 );
  1158. }
  1159. }
  1160. /**
  1161.  * Cleans and makes a value SQL safe depending on the type that is enforced.
  1162.  * @access private
  1163.  *
  1164.  * @param  mixed   $fieldValue
  1165.  * @param  string  $type
  1166.  * @return string
  1167.  */
  1168. function _sqlCleanQuote( $fieldValue, $type ) {
  1169. $typeArray = explode( ':', $type, 3 );
  1170. if ( count( $typeArray ) < 2 ) {
  1171. $typeArray = array( 'const' , $type );
  1172. }
  1173. switch ( $typeArray[1] ) {
  1174. case 'int':
  1175. $value = (int) $fieldValue;
  1176. break;
  1177. case 'float':
  1178. $value = (float) $fieldValue;
  1179. break;
  1180. case 'formula':
  1181. $value = $fieldValue;
  1182. break;
  1183. case 'field': // this is temporarly handled here
  1184. $value = $this->_db->NameQuote( $fieldValue );
  1185. break;
  1186. case 'datetime':
  1187. if ( preg_match( '/^[0-9]{4}-[01][0-9]-[0-3][0-9] [0-2][0-9](:[0-5][0-9]){2}$/', $fieldValue ) ) {
  1188. $value = $this->_db->Quote( $fieldValue );
  1189. } else {
  1190. $value = "''";
  1191. }
  1192. break;
  1193. case 'date':
  1194. if ( preg_match( '/^[0-9]{4}-[01][0-9]-[0-3][0-9]$/', $fieldValue ) ) {
  1195. $value = $this->_db->Quote( $fieldValue );
  1196. } else {
  1197. $value = "''";
  1198. }
  1199. break;
  1200. case 'string':
  1201. $value = $this->_db->Quote( $fieldValue );
  1202. break;
  1203. case 'null':
  1204. if ( $fieldValue != 'NULL' ) {
  1205. trigger_error( sprintf( 'CBSQLUpgrader::_sqlCleanQuote: ERROR: field type sql:null has not NULL value' ) );
  1206. }
  1207. $value = 'NULL';
  1208. break;
  1209. default:
  1210. trigger_error( 'CBSQLUpgrader::_sqlQuoteValueType: ERROR_UNKNOWN_TYPE: ' . htmlspecialchars( $type ), E_USER_NOTICE );
  1211. $value = $this->_db->Quote( $fieldValue ); // false;
  1212. break;
  1213. }
  1214. return (string) $value;
  1215. }
  1216. /**
  1217.  * Cleans and makes a value comparable to the SQL stored value in a comprofilerDBTable object, depending on the type that is enforced.
  1218.  * @access private
  1219.  *
  1220.  * @param  mixed   $fieldValue
  1221.  * @param  string  $type
  1222.  * @return string
  1223.  */
  1224. function _phpCleanQuote( $fieldValue, $type ) {
  1225. $typeArray = explode( ':', $type, 3 );
  1226. if ( count( $typeArray ) < 2 ) {
  1227. $typeArray = array( 'const' , $type );
  1228. }
  1229. switch ( $typeArray[1] ) {
  1230. case 'int':
  1231. $value = (int) $fieldValue;
  1232. break;
  1233. case 'float':
  1234. $value = (float) $fieldValue;
  1235. break;
  1236. case 'formula':
  1237. $value = $fieldValue; // this is temporarly done so
  1238. break;
  1239. case 'field': // this is temporarly handled here
  1240. $value = $fieldValue;
  1241. break;
  1242. case 'datetime':
  1243. if ( preg_match( '/^[0-9]{4}-[01][0-9]-[0-3][0-9] [0-2][0-9](:[0-5][0-9]){2}$/', $fieldValue ) ) {
  1244. $value = (string) $fieldValue;
  1245. } else {
  1246. $value = '';
  1247. }
  1248. break;
  1249. case 'date':
  1250. if ( preg_match( '/^[0-9]{4}-[01][0-9]-[0-3][0-9]$/', $fieldValue ) ) {
  1251. $value = (string) $fieldValue;
  1252. } else {
  1253. $value = '';
  1254. }
  1255. break;
  1256. case 'string':
  1257. $value = (string) $fieldValue;
  1258. break;
  1259. case 'null':
  1260. if ( $fieldValue != 'NULL' ) {
  1261. trigger_error( sprintf( 'CBSQLUpgrader::_phpCleanQuote: ERROR: field type sql:null has not NULL value' ) );
  1262. }
  1263. $value = null;
  1264. break;
  1265. default:
  1266. trigger_error( 'CBSQLUpgrader::_sqlQuoteValueType: ERROR_UNKNOWN_TYPE: ' . htmlspecialchars( $type ), E_USER_NOTICE );
  1267. $value = (string) $fieldValue; // false;
  1268. break;
  1269. }
  1270. return $value;
  1271. }
  1272. /**
  1273.  * Cleans and makes a value comparable to the SQL stored value in a comprofilerDBTable object, depending on the type that is enforced.
  1274.  * @access private
  1275.  *
  1276.  * @param  string|null  $fieldValue
  1277.  * @param  string  $type
  1278.  * @return mixed
  1279.  */
  1280. function _adjustToStrictType( $fieldValue, $type ) {
  1281. $typeArray = explode( ':', $type, 3 );
  1282. if ( count( $typeArray ) < 2 ) {
  1283. $typeArray = array( 'const' , $type );
  1284. }
  1285. $value = $fieldValue;
  1286. if ( $fieldValue !== null ) {
  1287. switch ( $typeArray[1] ) {
  1288. case 'int':
  1289. if ( is_int( $fieldValue ) || preg_match( '/^d++$/', $fieldValue ) ) {
  1290. $value = (int) $fieldValue;
  1291. }
  1292. break;
  1293. case 'float':
  1294. if ( is_float( $fieldValue ) || ( preg_match( '/^(((+|-)?d+(.d*)?([Ee](+|-)?d+)?)|((+|-)?(d*.)?d+([Ee](+|-)?d+)?))$/', $fieldValue ) ) ) {
  1295. $value = (float) $fieldValue;
  1296. }
  1297. break;
  1298. case 'formula':
  1299. $value = $fieldValue; // this is temporarly done so
  1300. break;
  1301. case 'field': // this is temporarly handled here
  1302. $value = $fieldValue;
  1303. break;
  1304. case 'datetime':
  1305. if ( preg_match( '/^[0-9]{4}-[01][0-9]-[0-3][0-9] [0-2][0-9](:[0-5][0-9]){2}$/', $fieldValue ) ) {
  1306. $value = (string) $fieldValue;
  1307. } else {
  1308. $value = '';
  1309. }
  1310. break;
  1311. case 'date':
  1312. if ( preg_match( '/^[0-9]{4}-[01][0-9]-[0-3][0-9]$/', $fieldValue ) ) {
  1313. $value = (string) $fieldValue;
  1314. } else {
  1315. $value = '';
  1316. }
  1317. break;
  1318. case 'string':
  1319. if ( is_string( $fieldValue ) ) {
  1320. $value = (string) $fieldValue;
  1321. }
  1322. break;
  1323. case 'null':
  1324. if ( $fieldValue === null ) {
  1325. $value = null;
  1326. }
  1327. break;
  1328. default:
  1329. trigger_error( 'CBSQLUpgrader::_sqlQuoteValueType: ERROR_UNKNOWN_TYPE: ' . htmlspecialchars( $type ), E_USER_NOTICE );
  1330. $value = (string) $fieldValue; // false;
  1331. break;
  1332. }
  1333. }
  1334. return $value;
  1335. }
  1336. /**
  1337.  * Converts a XML description of a SQL index into a full SQL type
  1338.  *
  1339.  * <index name="PRIMARY" type="primary">
  1340.  * <column name="id" />
  1341.  * </index>
  1342.  * <index name="rate_chars">
  1343.  * <column name="rate" />
  1344.  * <column name="_mychars" nametype="namesuffix" size="8" ordering="DESC" />
  1345.  * </index>
  1346.  * <index name="myrate" type="unique" using="btree">
  1347.  * <column name="rate" />
  1348.  * </index>
  1349.  *
  1350.  * Returns: $fulltype: 'decimal(16,8) unsigned NULL DEFAULT NULL'
  1351.  * @access private
  1352.  *
  1353.  * @param  CBSimpleXMLElement  $index
  1354.  * @param  string              $colNamePrefix    Prefix to add to all column names
  1355.  * @return string|boolean                        Full SQL creation type or NULL in case of no index/error
  1356.  */
  1357. function _fullIndexType( &$index, $colNamePrefix ) {
  1358. $sqlIndexText = null;
  1359. if ( $index->name() == 'index' ) {
  1360. // first collect all columns of this index:
  1361. $indexColumns = array();
  1362. foreach ( $index->children() as $column ) {
  1363. if ( $column->name() == 'column' ) {
  1364. $colNamePrefixed = $this->_prefixedName( $column, $colNamePrefix );
  1365. $indexColText = $this->_db->NameQuote( $colNamePrefixed );
  1366. if ( $column->attributes( 'size' ) ) {
  1367. $indexColText .= ' (' . (int) $column->attributes( 'size' ) . ')';
  1368. }
  1369. if ( $column->attributes( 'ordering' ) ) {
  1370. $indexColText .= ' ' . $this->_db->getEscaped( $column->attributes( 'ordering' ) );
  1371. }
  1372. $indexColumns[] = $indexColText;
  1373. }
  1374. }
  1375. if ( count( $indexColumns ) > 0 ) {
  1376. // then build the index creation SQL:
  1377. if ( $index->attributes( 'type' ) ) {
  1378. // PRIMARY, UNIQUE, FULLTEXT, SPATIAL:
  1379. $sqlIndexText .= $this->_db->getEscaped( strtoupper( $index->attributes( 'type' ) ) ) . ' ';
  1380. }
  1381. $sqlIndexText .= 'KEY ';
  1382. if ( $index->attributes( 'type' ) !== 'primary' ) {
  1383. $sqlIndexText .= $this->_db->NameQuote( $this->_prefixedName( $index, $colNamePrefix ) ) . ' ';
  1384. }
  1385. if ( $index->attributes( 'using' ) ) {
  1386. // BTREE, HASH, RTREE:
  1387. $sqlIndexText .= 'USING ' . $this->_db->getEscaped( $index->attributes( 'using' ) ) . ' ';
  1388. }
  1389. $sqlIndexText .= '(' . implode( ', ', $indexColumns ) . ')';
  1390. }
  1391. }
  1392. return $sqlIndexText;
  1393. }
  1394. /**
  1395.  * Prefixes the $attribute of $column (or table or other xml element) with
  1396.  * $colNamePrefix if $column->attributes( 'nametype' ) == 'namesuffix' or 'nameprefix'
  1397.  * @access private
  1398.  *
  1399.  * @param  CBSimpleXMLElement  $column
  1400.  * @param  string              $colNamePrefix
  1401.  * @param  string              $attribute      
  1402.  * @param  string              $modifyingAttr
  1403.  * @return string
  1404.  */
  1405. function _prefixedName( &$column, $colNamePrefix, $attribute = 'name', $modifyingAttr = 'nametype' ) {
  1406. $colName = $column->attributes( $attribute );
  1407. $colNameType = $column->attributes( $modifyingAttr );
  1408. switch ( $colNameType ) {
  1409. case 'nameprefix':
  1410. $colName .= $colNamePrefix;
  1411. break;
  1412. case 'namesuffix':
  1413. $colName = $colNamePrefix . $colName;
  1414. break;
  1415. default:
  1416. break;
  1417. }
  1418. return $colName;
  1419. }
  1420. /**
  1421.  * Checks if all columns of a xml description of all tables of a database matches the database
  1422.  *
  1423.  * Warning: if ( $change && $strictlyColumns ) it will DROP not described columns !!!
  1424.  * @access private
  1425.  *
  1426.  * @param  CBSimpleXMLElement  $table
  1427.  * @param  string              $colNamePrefix    Prefix to add to all column names
  1428.  * @param  boolean             $change           FALSE: only check, TRUE: change database to match description (deleting columns if $strictlyColumns == true)
  1429.  * @param  boolean|null        $strictlyColumns  FALSE: allow for other columns, TRUE: doesn't allow for other columns, NULL: checks for attribute 'strict' in table
  1430.  * @return boolean             TRUE: matches, FALSE: don't match
  1431.  */
  1432. function checkXmlTableDescription( &$table, $colNamePrefix = '', $change = false, $strictlyColumns = false ) {
  1433. $isMatching = false;
  1434. if ( $table->name() == 'table' ) {
  1435. $tableName = $this->_prefixedName( $table, $colNamePrefix );
  1436. $columns =& $table->getElementByPath( 'columns' );
  1437. if ( $tableName && ( $columns !== false ) ) {
  1438. if ( $strictlyColumns === null ) {
  1439. $strictlyColumns = ( $table->attributes( 'strict' ) == 'true' );
  1440. }
  1441. $isMatching = true;
  1442. $allColumns = $this->getAllTableColumns( $tableName );
  1443. if ( $allColumns === false ) {
  1444. // table doesn't exist:
  1445. if ( $change ) {
  1446. if ( $this->createTable( $table, $colNamePrefix ) ) {
  1447. $allColumns = $this->getAllTableColumns( $tableName );
  1448. } else {
  1449. $isMatching = false;
  1450. }
  1451. } else {
  1452. $this->_setError( sprintf( 'Table %s does not exist', $tableName ), null );
  1453. $isMatching = false;
  1454. }
  1455. } else {
  1456. // Table exists:
  1457. // 1) Check columns:
  1458. if ( $strictlyColumns ) {
  1459. $columnBefore = 1;
  1460. } else {
  1461. $columnBefore = null;
  1462. }
  1463. foreach ( $columns->children() as $column ) {
  1464. if ( $column->name() == 'column' ) {
  1465. if ( ! $this->checkColumnExistsType( $tableName, $allColumns, $column, $colNamePrefix, $change ) ) {
  1466. if ( ( ! $change ) || ( ! $this->changeColumn( $tableName, $allColumns, $column, $colNamePrefix, $columnBefore ) ) ) {
  1467. $isMatching = false;
  1468. }
  1469. }
  1470. $columnBefore = $column;
  1471. }
  1472. }
  1473. if ( $strictlyColumns && ( $columns->attributes( 'strict' ) !== 'false' ) && ! $this->checkOtherColumnsExist( $tableName, $allColumns, $columns, $colNamePrefix, $change ) ) {
  1474. $isMatching = false;
  1475. }
  1476. // 2) Check indexes:
  1477. $indexes =& $table->getElementByPath( 'indexes' );
  1478. if ( $indexes !== false ) {
  1479. $allIndexes = $this->getAllTableIndexes( $tableName );
  1480. foreach ( $indexes->children() as $index ) {
  1481. if ( $index->name() == 'index' ) {
  1482. if ( ! $this->checkIndexExistsType( $tableName, $allIndexes, $index, $colNamePrefix, $change ) ) {
  1483. if ( ( ! $change ) || ( ! $this->changeIndex( $tableName, $allIndexes, $index, $colNamePrefix ) ) ) {
  1484. $isMatching = false;
  1485. }
  1486. }
  1487. }
  1488. }
  1489. if ( $strictlyColumns && ( $indexes->attributes( 'strict' ) !== 'false' ) && ! $this->checkOtherIndexesExist( $tableName, $allIndexes, $indexes, $colNamePrefix, $change ) ) {
  1490. $isMatching = false;
  1491. }
  1492. }
  1493. }
  1494. // 3) Now that indexed table is checked (exists or has been created), Check rows:
  1495. if ( $allColumns !== false ) {
  1496. $rows =& $table->getElementByPath( 'rows' );
  1497. if ( $rows !== false ) {
  1498. foreach ( $rows->children() as $row ) {
  1499. if ( $row->name() == 'row' ) {
  1500. $rowArray = null;
  1501. if ( ! $this->checkOrChangeRow( $tableName, $rows, $row, $allColumns, $colNamePrefix, $change ) ) {
  1502. $isMatching = false;
  1503. }
  1504. }
  1505. }
  1506. if ( $strictlyColumns && ( $rows->attributes( 'strict' ) !== 'false' ) && ! $this->checkOtherRowsExist( $tableName, $rows, $colNamePrefix, $change ) ) {
  1507. $isMatching = false;
  1508. }
  1509. }
  1510. }
  1511. }
  1512. }
  1513. return $isMatching;
  1514. }
  1515. /**
  1516.  * Checks if all columns of a xml description of all tables of a database matches the database
  1517.  *
  1518.  * Warning: removes columns tables and columns which would be added by the changes to XML !!!
  1519.  * @access private
  1520.  *
  1521.  * @param  CBSimpleXMLElement  $table
  1522.  * @param  string              $colNamePrefix    Prefix to add to all column names
  1523.  * @param  string              $change           'drop': uninstalls columns/tables
  1524.  * @param  boolean|null        $strictlyColumns  FALSE: allow for other columns, TRUE: doesn't allow for other columns, NULL: checks for attribute 'strict' in table
  1525.  * @return boolean             TRUE: matches, FALSE: don't match
  1526.  */
  1527. function dropXmlTableDescription( &$table, $colNamePrefix = '', $change = 'drop', $strictlyColumns = false ) {
  1528. $isMatching = false;
  1529. if ( $table->name() == 'table' ) {
  1530. $tableName = $this->_prefixedName( $table, $colNamePrefix );
  1531. $columns =& $table->getElementByPath( 'columns' );
  1532. if ( $tableName && ( $columns !== false ) ) {
  1533. if ( $strictlyColumns === null ) {
  1534. $strictlyColumns = ( $table->attributes( 'strict' ) === 'true' );
  1535. }
  1536. $neverDropTable = ( $table->attributes( 'drop' ) === 'never' );
  1537. $isMatching = true;
  1538. $allColumns = $this->getAllTableColumns( $tableName );
  1539. if ( $allColumns === false ) {
  1540. // table doesn't exist: do nothing
  1541. } else {
  1542. if ( $strictlyColumns && ( ! $neverDropTable ) ) {
  1543. if ( in_array( $tableName, array( '#__comprofiler', '#_users', '#__comprofiler_fields' ) ) ) {
  1544. // Safeguard against fatal error in XML file !
  1545. $errorMsg = sprintf( 'Fatal error: Trying to delete core CB table %s not allowed.', $tableName );
  1546. echo $errorMsg;
  1547. trigger_error( $errorMsg, E_USER_ERROR );
  1548. exit;
  1549. }
  1550. $this->dropTable( $tableName );
  1551. } else {
  1552. // 1) Drop rows:
  1553. $rows =& $table->getElementByPath( 'rows' );
  1554. if ( $rows !== false ) {
  1555. $neverDropRows = ( $rows->attributes( 'drop' ) === 'never' );
  1556. if ( ! $neverDropRows ) {
  1557. $strictRows = ( ( $rows->attributes( 'strict' ) === 'true' ) );
  1558. foreach ( $rows->children() as $row ) {
  1559. if ( $row->name() == 'row' ) {
  1560. $neverDropRow = ( $row->attributes( 'drop' ) === 'never' );
  1561. if ( ( $strictRows && ! $neverDropRow ) ) {
  1562. if ( ! $this->dropRow( $tableName, $row, $colNamePrefix ) ) {
  1563. $isMatching = false;
  1564. }
  1565. }
  1566. }
  1567. }
  1568. }
  1569. }
  1570. // 2) Drop indexes:
  1571. $indexes =& $table->getElementByPath( 'indexes' );
  1572. if ( $indexes !== false ) {
  1573. $neverDropIndexes = ( $indexes->attributes( 'drop' ) === 'never' );
  1574. if ( ! $neverDropIndexes ) {
  1575. $allIndexes = $this->getAllTableIndexes( $tableName );
  1576. foreach ( $indexes->children() as $index ) {
  1577. if ( $index->name() == 'index' ) {
  1578. $indexName = $this->_prefixedName( $index, $colNamePrefix );
  1579. if ( $indexName == 'PRIMARY' ) {
  1580. $neverDropIndex = ( $index->attributes( 'drop' ) !== 'always' );
  1581. } else {
  1582. $neverDropIndex = ( $index->attributes( 'drop' ) === 'never' );
  1583. }
  1584. if ( isset( $allIndexes[$indexName] ) && ! $neverDropIndex ) {
  1585. if ( ! $this->dropIndex( $tableName, $indexName ) ) {
  1586. $isMatching = false;
  1587. }
  1588. }
  1589. }
  1590. }
  1591. }
  1592. }
  1593. // 3) Drop columns:
  1594. $neverDropColumns = ( $columns->attributes( 'drop' ) === 'never' );
  1595. if ( ! $neverDropColumns ) {
  1596. foreach ( $columns->children() as $column ) {
  1597. if ( $column->name() == 'column' ) {
  1598. $neverDropColumn = ( $column->attributes( 'drop' ) === 'never' );
  1599. $colNamePrefixed = $this->_prefixedName( $column, $colNamePrefix );
  1600. if ( isset( $allColumns[$colNamePrefixed] ) && ! $neverDropColumn ) {
  1601. if ( ! $this->dropColumn( $tableName, $colNamePrefixed ) ) {
  1602. $isMatching = false;
  1603. }
  1604. }
  1605. }
  1606. }
  1607. }
  1608. }
  1609. }
  1610. }
  1611. }
  1612. return $isMatching;
  1613. }
  1614. /**
  1615.  * Checks if all columns of a xml description of all tables of a database matches the database
  1616.  *
  1617.  * Warning: if ( $change && $strictlyColumns ) it will DROP not described columns !!!
  1618.  * @access private
  1619.  *
  1620.  * @param  string|array        $tablesNames      Name(s) of tables to dump
  1621.  * @param  boolean             $withContent      FALSE: only structure, TRUE: also content
  1622.  * @return CBSimpleXMLElement
  1623.  */
  1624. function dumpTableToXml( $tablesNames, $withContent = true ) {
  1625. $db = new CBSimpleXMLElement( '<?xml version="1.0" encoding="UTF-8"?><database version="1" />' );
  1626. foreach ( (array) $tablesNames as $tableName ) {
  1627. $table = $db->addChild( 'table' );
  1628. $table->addAttribute( 'name', $tableName );
  1629. $table->addAttribute( 'class', '' );
  1630. $table->addAttribute( 'strict', 'false' );
  1631. $table->addAttribute( 'drop', 'never' );
  1632. // Columns:
  1633. $allColumns = $this->getAllTableColumns( $tableName );
  1634. $columns = $table->addChild( 'columns' );
  1635. foreach ( $allColumns as $colEntry ) {
  1636. $colTypeUnsigned = explode( ' ', $colEntry->Type );
  1637. if ( count( $colTypeUnsigned ) == 1 ) {
  1638. $colTypeUnsigned[1] = null;
  1639. }
  1640. $column = $columns->addChild( 'column' );
  1641. $column->addAttribute( 'name',  $colEntry->Field );
  1642. $column->addAttribute( 'type', 'sql:' . $colTypeUnsigned[0] );
  1643. if ( $colTypeUnsigned[1] === 'unsigned' ) {
  1644. $column->addAttribute( 'unsigned', ( $colTypeUnsigned[1] === 'unsigned' ? 'true' : 'false' ) );
  1645. }
  1646. if ( $colEntry->Null === 'YES' ) {
  1647. $column->addAttribute( 'null', ( $colEntry->Null === 'YES' ? 'true' : 'false' ) );
  1648. }
  1649. if ( $colEntry->Default !== null ) {
  1650. if ( $colEntry->Null === 'YES' ) {
  1651. $column->addAttribute( 'default', $colEntry->Default );
  1652. } else {
  1653. $defaultDefaultTypes = $this->defaultValuesOfTypes( $this->mysqlToXmlsql( 'sql:' . $colTypeUnsigned[0] ) );
  1654. if ( ! in_array( $colEntry->Default, $defaultDefaultTypes ) ) {
  1655. $column->addAttribute( 'default', $colEntry->Default );
  1656. }
  1657. }
  1658. }
  1659. if ( strpos( $colEntry->Extra, 'auto_increment' ) !== false ) {
  1660. $tableStatus = $this->_db->getTableStatus( $tableName );
  1661. if ( isset( $tableStatus[0]->Auto_increment ) ) {
  1662. $lastAuto_increment = $tableStatus[0]->Auto_increment;
  1663. } else {
  1664. $lastAuto_increment = '100';
  1665. }
  1666. $column->addAttribute( 'auto_increment', $lastAuto_increment );
  1667. }
  1668. }
  1669. // Indexes:
  1670. $indexes = $table->addChild( 'indexes' );
  1671. $primaryIndex = null;
  1672. $allIndexes = $this->getAllTableIndexes( $tableName );
  1673. foreach ( $allIndexes as $indexName => $sequenceInIndexArray ) {
  1674. $type = $sequenceInIndexArray[1]['type'];
  1675. $using = $sequenceInIndexArray[1]['using'];
  1676. $index = $indexes->addChild( 'index' );
  1677. $index->addAttribute( 'name', $indexName );
  1678. if ( $type != '' ) {
  1679. $index->addAttribute( 'type', $type );
  1680. }
  1681. if ( $using != 'btree' ) {
  1682. $index->addAttribute( 'using', $using );
  1683. }
  1684. foreach ( $sequenceInIndexArray as $sequenceInIndex => $indexAttributes ) {
  1685. $column = $index->addChild( 'column' );
  1686. $column->addAttribute( 'name', $indexAttributes['name'] );
  1687. if ( $indexAttributes['size'] ) {
  1688. $column->addAttribute( 'size', $indexAttributes['size'] );
  1689. }
  1690. if ( $indexAttributes['ordering'] != 'A' ) {
  1691. $column->addAttribute( 'ordering', $indexAttributes['ordering'] );
  1692. }
  1693. }
  1694. if ( $type == 'primary' ) {
  1695. $primaryIndex = $index;
  1696. }
  1697. }
  1698. // Content:
  1699. if ( $withContent ) {
  1700. $allRows = $this->loadRows( $tableName, null, null, null );
  1701. if ( count( $allRows ) > 0 ) {
  1702. $rows = $table->addChild( 'rows' );
  1703. $primaryNames = null;
  1704. if ( $primaryIndex !== null ) {
  1705. foreach ( $primaryIndex->children() as $column ) {
  1706. if ( $column->name() == 'column' ) {
  1707. $primaryNames[] = $column->attributes( 'name' );
  1708. }
  1709. }
  1710. }
  1711. foreach ( $allRows as $rowData ) {
  1712. $row = $rows->addChild( 'row' );
  1713. // missing primary key here:
  1714. $rowIndexName = array();
  1715. $rowIndexValue = array();
  1716. $rowIndexValueType = array();
  1717. foreach ( get_object_vars( $rowData ) as $fieldDataName => $fieldDataValue ) {
  1718. if ( $fieldDataName[0] != '_' ) {
  1719. $typeColumn = $columns->getChildByNameAttributes( 'column', array( 'name' => $fieldDataName ) );
  1720. $fieldDataValueType = 'const:' . $this->mysqlToXmlsql( $typeColumn->attributes( 'type' ) );
  1721. $field = $row->addChild( 'field' );
  1722. if ( $fieldDataValue === null ) {
  1723. $fieldDataValue = 'NULL';
  1724. $fieldDataValueType = 'const:null';
  1725. }
  1726. $field->addAttribute( 'name', $fieldDataName );
  1727. $field->addAttribute( 'value', $fieldDataValue );
  1728. $field->addAttribute( 'valuetype', $fieldDataValueType );
  1729. if ( in_array( $fieldDataName, $primaryNames ) ) {
  1730. $field->addAttribute( 'strict', 'true' );
  1731. $rowIndexName[] = $fieldDataName;
  1732. $rowIndexValue[] = $fieldDataValue;
  1733. $rowIndexValueType[] = $fieldDataValueType;
  1734. }
  1735. }
  1736. }
  1737. $row->addAttribute( 'index',  implode( ' ', $rowIndexName ) );
  1738. $row->addAttribute( 'value',  implode( ' ', $rowIndexValue ) );
  1739. $row->addAttribute( 'valuetype', implode( ' ', $rowIndexValueType ) );
  1740. }
  1741. }
  1742. }
  1743. }
  1744. return $db;
  1745. }
  1746. /**
  1747.  * Main function FOR CB INTERNAL USE ONLY:
  1748.  */
  1749. /**
  1750.  * Checks if all columns of a xml description of all tables of a database matches the database
  1751.  *
  1752.  * Warning: if ( $change && $strictlyColumns ) it will DROP not described columns !!!
  1753.  *
  1754.  *  <database version="1">
  1755.  * <table name="#__comprofiler" class="moscomprofiler">
  1756.  * <columns>
  1757.  * <column name="_rate" nametype="namesuffix" type="sql:decimal(16,8)" unsigned="true" null="true" default="NULL" auto_increment="100" />
  1758.  * <table name="#__comprofiler_hf2_" nametype="nameprefix" class="myClass" strict="true">
  1759.  * <indexes>
  1760.  * <index name="primary" type="primary">
  1761.  * <column name="id" />
  1762.  * </index>
  1763.  * <index name="rate_chars">
  1764.  * <column name="rate" />
  1765.  * <column name="_mychars" nametype="namesuffix" size="8" ordering="ASC" />
  1766.  * </index>
  1767.  * <index name="chars_rate_id" type="unique" using="btree">
  1768.  *
  1769.  * @param  CBSimpleXMLElement  $xml
  1770.  * @param  string              $colNamePrefix    Prefix to add to all column names
  1771.  * @param  boolean|string      $change           FALSE: only check, TRUE: change database to match description (deleting non-matching columns if $strictlyColumns == true), 'drop': uninstalls columns/tables
  1772.  * @param  boolean             $strictlyColumns  FALSE: allow for other columns, TRUE: doesn't allow for other columns
  1773.  * @return boolean             TRUE: matches, FALSE: don't match
  1774.  */
  1775. function checkXmlDatabaseDescription( &$db, $colNamePrefix = '', $change = false, $strictlyColumns = false ) {
  1776. $isMatching = false;
  1777. if ( $db->name() == 'database' ) {
  1778. $isMatching = true;
  1779. foreach ( $db->children() as $table ) {
  1780. if ( $table->name() == 'table' ) {
  1781. if ( is_bool( $change ) ) {
  1782. $isMatching = $this->checkXmlTableDescription( $table, $colNamePrefix, $change, $strictlyColumns ) && $isMatching;
  1783. } else {
  1784. $isMatching = $this->dropXmlTableDescription( $table, $colNamePrefix, $change, $strictlyColumns ) && $isMatching;
  1785. }
  1786. }
  1787. }
  1788. }
  1789. return $isMatching;
  1790. }
  1791. /**
  1792.  * Returns main table name (pre/post/fixed with $colNamePrefix)
  1793.  *
  1794.  * <database>
  1795.  *  <table name="xyz" nametype="nameprefix" maintable="true">
  1796.  *
  1797.  * @param  CBSimpleXMLElement  $db
  1798.  * @param  string              $colNamePrefix    Prefix to add to all column names
  1799.  * @param  string|null         $default          Default table result to return if table not found in xml
  1800.  * @return string
  1801.  */
  1802. function getMainTableName( &$db, $colNamePrefix = '', $default = null ) {
  1803. $maintable =& $db->getChildByNameAttr( 'table', 'maintable', 'true' );
  1804. if ( $maintable !== false ) {
  1805. return $this->_prefixedName( $maintable, $colNamePrefix );
  1806. }
  1807. return $default;
  1808. }
  1809. /**
  1810.  * Returns array of column names (pre/post/fixed with $colNamePrefix) of $table
  1811.  *
  1812.  * @param  CBSimpleXMLElement  $db
  1813.  * @param  string              $colNamePrefix    Prefix to add to all column names
  1814.  * @return array|boolean                         False if not found
  1815.  */
  1816. function getMainTableColumnsNames( &$db, $colNamePrefix = '' ) {
  1817. $table =& $db->getChildByNameAttr( 'table', 'maintable', 'true' );
  1818. if ( $table !== false ) {
  1819. $columns =& $table->getElementByPath( 'columns' );
  1820. if ( $columns !== false ) {
  1821. $columnNamesArray = array();
  1822. foreach ( $columns->children() as $column ) {
  1823. if ( $column->name() == 'column' ) {
  1824. $columnNamesArray[] = $this->_prefixedName( $column, $colNamePrefix );
  1825. }
  1826. }
  1827. return $columnNamesArray;
  1828. }
  1829. }
  1830. return false;
  1831. }
  1832. } // class CBSQLupgrader
  1833. ?>