Tracker.class.php 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080
  1. <?php
  2. /* vim: set expandtab sw=4 ts=4 sts=4: */
  3. /**
  4. * Tracking changes on databases, tables and views
  5. *
  6. * @package PhpMyAdmin
  7. */
  8. if (! defined('PHPMYADMIN')) {
  9. exit;
  10. }
  11. /**
  12. * This class tracks changes on databases, tables and views.
  13. *
  14. * @package PhpMyAdmin
  15. *
  16. * @todo use stristr instead of strstr
  17. */
  18. class PMA_Tracker
  19. {
  20. /**
  21. * Whether tracking is ready.
  22. */
  23. static protected $enabled = false;
  24. /**
  25. * Defines the internal PMA table which contains tracking data.
  26. *
  27. * @access protected
  28. * @var string
  29. */
  30. static protected $pma_table;
  31. /**
  32. * Defines the usage of DROP TABLE statment in SQL dumps.
  33. *
  34. * @access protected
  35. * @var boolean
  36. */
  37. static protected $add_drop_table;
  38. /**
  39. * Defines the usage of DROP VIEW statment in SQL dumps.
  40. *
  41. * @access protected
  42. * @var boolean
  43. */
  44. static protected $add_drop_view;
  45. /**
  46. * Defines the usage of DROP DATABASE statment in SQL dumps.
  47. *
  48. * @access protected
  49. * @var boolean
  50. */
  51. static protected $add_drop_database;
  52. /**
  53. * Defines auto-creation of tracking versions.
  54. *
  55. * @var boolean
  56. */
  57. static protected $version_auto_create;
  58. /**
  59. * Defines the default set of tracked statements.
  60. *
  61. * @var string
  62. */
  63. static protected $default_tracking_set;
  64. /**
  65. * Flags copied from `tracking` column definition in `pma_tracking` table.
  66. * Used for column type conversion in Drizzle.
  67. *
  68. * @var array
  69. */
  70. static private $_tracking_set_flags = array(
  71. 'UPDATE','REPLACE','INSERT','DELETE','TRUNCATE','CREATE DATABASE',
  72. 'ALTER DATABASE','DROP DATABASE','CREATE TABLE','ALTER TABLE',
  73. 'RENAME TABLE','DROP TABLE','CREATE INDEX','DROP INDEX',
  74. 'CREATE VIEW','ALTER VIEW','DROP VIEW'
  75. );
  76. /**
  77. * Initializes settings.
  78. *
  79. * @static
  80. *
  81. * @return void
  82. */
  83. static protected function init()
  84. {
  85. self::$pma_table = PMA_Util::backquote($GLOBALS['cfg']['Server']['pmadb']) .".".
  86. PMA_Util::backquote($GLOBALS['cfg']['Server']['tracking']);
  87. self::$add_drop_table = $GLOBALS['cfg']['Server']['tracking_add_drop_table'];
  88. self::$add_drop_view = $GLOBALS['cfg']['Server']['tracking_add_drop_view'];
  89. self::$add_drop_database
  90. = $GLOBALS['cfg']['Server']['tracking_add_drop_database'];
  91. self::$default_tracking_set
  92. = $GLOBALS['cfg']['Server']['tracking_default_statements'];
  93. self::$version_auto_create
  94. = $GLOBALS['cfg']['Server']['tracking_version_auto_create'];
  95. }
  96. /**
  97. * Actually enables tracking. This needs to be done after all
  98. * underlaying code is initialized.
  99. *
  100. * @static
  101. *
  102. * @return void
  103. */
  104. static public function enable()
  105. {
  106. self::$enabled = true;
  107. }
  108. /**
  109. * Gets the on/off value of the Tracker module, starts initialization.
  110. *
  111. * @static
  112. *
  113. * @return boolean (true=on|false=off)
  114. */
  115. static public function isActive()
  116. {
  117. if (! self::$enabled) {
  118. return false;
  119. }
  120. /* We need to avoid attempt to track any queries
  121. * from PMA_getRelationsParam
  122. */
  123. self::$enabled = false;
  124. $cfgRelation = PMA_getRelationsParam();
  125. /* Restore original state */
  126. self::$enabled = true;
  127. if (! $cfgRelation['trackingwork']) {
  128. return false;
  129. }
  130. self::init();
  131. if (isset(self::$pma_table)) {
  132. return true;
  133. } else {
  134. return false;
  135. }
  136. }
  137. /**
  138. * Parses the name of a table from a SQL statement substring.
  139. *
  140. * @param string $string part of SQL statement
  141. *
  142. * @static
  143. *
  144. * @return string the name of table
  145. */
  146. static protected function getTableName($string)
  147. {
  148. if (strstr($string, '.')) {
  149. $temp = explode('.', $string);
  150. $tablename = $temp[1];
  151. } else {
  152. $tablename = $string;
  153. }
  154. $str = explode("\n", $tablename);
  155. $tablename = $str[0];
  156. $tablename = str_replace(';', '', $tablename);
  157. $tablename = str_replace('`', '', $tablename);
  158. $tablename = trim($tablename);
  159. return $tablename;
  160. }
  161. /**
  162. * Gets the tracking status of a table, is it active or deactive ?
  163. *
  164. * @param string $dbname name of database
  165. * @param string $tablename name of table
  166. *
  167. * @static
  168. *
  169. * @return boolean true or false
  170. */
  171. static public function isTracked($dbname, $tablename)
  172. {
  173. if (! self::$enabled) {
  174. return false;
  175. }
  176. /* We need to avoid attempt to track any queries
  177. * from PMA_getRelationsParam
  178. */
  179. self::$enabled = false;
  180. $cfgRelation = PMA_getRelationsParam();
  181. /* Restore original state */
  182. self::$enabled = true;
  183. if (! $cfgRelation['trackingwork']) {
  184. return false;
  185. }
  186. $sql_query = " SELECT tracking_active FROM " . self::$pma_table .
  187. " WHERE db_name = '" . PMA_Util::sqlAddSlashes($dbname) . "' " .
  188. " AND table_name = '" . PMA_Util::sqlAddSlashes($tablename) . "' " .
  189. " ORDER BY version DESC";
  190. $row = PMA_DBI_fetch_array(PMA_queryAsControlUser($sql_query));
  191. if (isset($row['tracking_active']) && $row['tracking_active'] == 1) {
  192. return true;
  193. } else {
  194. return false;
  195. }
  196. }
  197. /**
  198. * Returns the comment line for the log.
  199. *
  200. * @return string Comment, contains date and username
  201. */
  202. static public function getLogComment()
  203. {
  204. $date = date('Y-m-d H:i:s');
  205. $user = preg_replace('/\s+/', ' ', $GLOBALS['cfg']['Server']['user']);
  206. return "# log " . $date . " " . $user . "\n";
  207. }
  208. /**
  209. * Creates tracking version of a table / view
  210. * (in other words: create a job to track future changes on the table).
  211. *
  212. * @param string $dbname name of database
  213. * @param string $tablename name of table
  214. * @param string $version version
  215. * @param string $tracking_set set of tracking statements
  216. * @param bool $is_view if table is a view
  217. *
  218. * @static
  219. *
  220. * @return int result of version insertion
  221. */
  222. static public function createVersion($dbname, $tablename, $version,
  223. $tracking_set = '', $is_view = false
  224. ) {
  225. global $sql_backquotes, $export_type;
  226. if ($tracking_set == '') {
  227. $tracking_set = self::$default_tracking_set;
  228. }
  229. // get Export SQL instance
  230. include_once "libraries/plugin_interface.lib.php";
  231. $export_sql_plugin = PMA_getPlugin(
  232. "export",
  233. "sql",
  234. 'libraries/plugins/export/',
  235. array(
  236. 'export_type' => $export_type,
  237. 'single_table' => isset($single_table)
  238. )
  239. );
  240. $sql_backquotes = true;
  241. $date = date('Y-m-d H:i:s');
  242. // Get data definition snapshot of table
  243. $columns = PMA_DBI_get_columns($dbname, $tablename, null, true);
  244. // int indices to reduce size
  245. $columns = array_values($columns);
  246. // remove Privileges to reduce size
  247. for ($i = 0; $i < count($columns); $i++) {
  248. unset($columns[$i]['Privileges']);
  249. }
  250. $indexes = PMA_DBI_get_table_indexes($dbname, $tablename);
  251. $snapshot = array('COLUMNS' => $columns, 'INDEXES' => $indexes);
  252. $snapshot = serialize($snapshot);
  253. // Get DROP TABLE / DROP VIEW and CREATE TABLE SQL statements
  254. $sql_backquotes = true;
  255. $create_sql = "";
  256. if (self::$add_drop_table == true && $is_view == false) {
  257. $create_sql .= self::getLogComment()
  258. . 'DROP TABLE IF EXISTS ' . PMA_Util::backquote($tablename) . ";\n";
  259. }
  260. if (self::$add_drop_view == true && $is_view == true) {
  261. $create_sql .= self::getLogComment()
  262. . 'DROP VIEW IF EXISTS ' . PMA_Util::backquote($tablename) . ";\n";
  263. }
  264. $create_sql .= self::getLogComment() .
  265. $export_sql_plugin->getTableDef($dbname, $tablename, "\n", "");
  266. // Save version
  267. $sql_query = "/*NOTRACK*/\n" .
  268. "INSERT INTO" . self::$pma_table . " (" .
  269. "db_name, " .
  270. "table_name, " .
  271. "version, " .
  272. "date_created, " .
  273. "date_updated, " .
  274. "schema_snapshot, " .
  275. "schema_sql, " .
  276. "data_sql, " .
  277. "tracking " .
  278. ") " .
  279. "values (
  280. '" . PMA_Util::sqlAddSlashes($dbname) . "',
  281. '" . PMA_Util::sqlAddSlashes($tablename) . "',
  282. '" . PMA_Util::sqlAddSlashes($version) . "',
  283. '" . PMA_Util::sqlAddSlashes($date) . "',
  284. '" . PMA_Util::sqlAddSlashes($date) . "',
  285. '" . PMA_Util::sqlAddSlashes($snapshot) . "',
  286. '" . PMA_Util::sqlAddSlashes($create_sql) . "',
  287. '" . PMA_Util::sqlAddSlashes("\n") . "',
  288. '" . PMA_Util::sqlAddSlashes(self::_transformTrackingSet($tracking_set)) . "' )";
  289. $result = PMA_queryAsControlUser($sql_query);
  290. if ($result) {
  291. // Deactivate previous version
  292. self::deactivateTracking($dbname, $tablename, ($version - 1));
  293. }
  294. return $result;
  295. }
  296. /**
  297. * Removes all tracking data for a table
  298. *
  299. * @param string $dbname name of database
  300. * @param string $tablename name of table
  301. *
  302. * @static
  303. *
  304. * @return int result of version insertion
  305. */
  306. static public function deleteTracking($dbname, $tablename)
  307. {
  308. $sql_query = "/*NOTRACK*/\n"
  309. . "DELETE FROM " . self::$pma_table
  310. . " WHERE `db_name` = '"
  311. . PMA_Util::sqlAddSlashes($dbname) . "'"
  312. . " AND `table_name` = '"
  313. . PMA_Util::sqlAddSlashes($tablename) . "'";
  314. $result = PMA_queryAsControlUser($sql_query);
  315. return $result;
  316. }
  317. /**
  318. * Creates tracking version of a database
  319. * (in other words: create a job to track future changes on the database).
  320. *
  321. * @param string $dbname name of database
  322. * @param string $version version
  323. * @param string $query query
  324. * @param string $tracking_set set of tracking statements
  325. *
  326. * @static
  327. *
  328. * @return int result of version insertion
  329. */
  330. static public function createDatabaseVersion($dbname, $version, $query,
  331. $tracking_set = 'CREATE DATABASE,ALTER DATABASE,DROP DATABASE'
  332. ) {
  333. $date = date('Y-m-d H:i:s');
  334. if ($tracking_set == '') {
  335. $tracking_set = self::$default_tracking_set;
  336. }
  337. include_once './libraries/export/sql.php';
  338. $create_sql = "";
  339. if (self::$add_drop_database == true) {
  340. $create_sql .= self::getLogComment()
  341. . 'DROP DATABASE IF EXISTS ' . PMA_Util::backquote($dbname) . ";\n";
  342. }
  343. $create_sql .= self::getLogComment() . $query;
  344. // Save version
  345. $sql_query = "/*NOTRACK*/\n" .
  346. "INSERT INTO" . self::$pma_table . " (" .
  347. "db_name, " .
  348. "table_name, " .
  349. "version, " .
  350. "date_created, " .
  351. "date_updated, " .
  352. "schema_snapshot, " .
  353. "schema_sql, " .
  354. "data_sql, " .
  355. "tracking " .
  356. ") " .
  357. "values (
  358. '" . PMA_Util::sqlAddSlashes($dbname) . "',
  359. '" . PMA_Util::sqlAddSlashes('') . "',
  360. '" . PMA_Util::sqlAddSlashes($version) . "',
  361. '" . PMA_Util::sqlAddSlashes($date) . "',
  362. '" . PMA_Util::sqlAddSlashes($date) . "',
  363. '" . PMA_Util::sqlAddSlashes('') . "',
  364. '" . PMA_Util::sqlAddSlashes($create_sql) . "',
  365. '" . PMA_Util::sqlAddSlashes("\n") . "',
  366. '" . PMA_Util::sqlAddSlashes(self::_transformTrackingSet($tracking_set)) . "' )";
  367. $result = PMA_queryAsControlUser($sql_query);
  368. return $result;
  369. }
  370. /**
  371. * Changes tracking of a table.
  372. *
  373. * @param string $dbname name of database
  374. * @param string $tablename name of table
  375. * @param string $version version
  376. * @param integer $new_state the new state of tracking
  377. *
  378. * @static
  379. *
  380. * @return int result of SQL query
  381. */
  382. static private function _changeTracking($dbname, $tablename,
  383. $version, $new_state
  384. ) {
  385. $sql_query = " UPDATE " . self::$pma_table .
  386. " SET `tracking_active` = '" . $new_state . "' " .
  387. " WHERE `db_name` = '" . PMA_Util::sqlAddSlashes($dbname) . "' " .
  388. " AND `table_name` = '" . PMA_Util::sqlAddSlashes($tablename) . "' " .
  389. " AND `version` = '" . PMA_Util::sqlAddSlashes($version) . "' ";
  390. $result = PMA_queryAsControlUser($sql_query);
  391. return $result;
  392. }
  393. /**
  394. * Changes tracking data of a table.
  395. *
  396. * @param string $dbname name of database
  397. * @param string $tablename name of table
  398. * @param string $version version
  399. * @param string $type type of data(DDL || DML)
  400. * @param string|array $new_data the new tracking data
  401. *
  402. * @static
  403. *
  404. * @return bool result of change
  405. */
  406. static public function changeTrackingData($dbname, $tablename,
  407. $version, $type, $new_data
  408. ) {
  409. if ($type == 'DDL') {
  410. $save_to = 'schema_sql';
  411. } elseif ($type == 'DML') {
  412. $save_to = 'data_sql';
  413. } else {
  414. return false;
  415. }
  416. $date = date('Y-m-d H:i:s');
  417. $new_data_processed = '';
  418. if (is_array($new_data)) {
  419. foreach ($new_data as $data) {
  420. $new_data_processed .= '# log ' . $date . ' ' . $data['username']
  421. . PMA_Util::sqlAddSlashes($data['statement']) . "\n";
  422. }
  423. } else {
  424. $new_data_processed = $new_data;
  425. }
  426. $sql_query = " UPDATE " . self::$pma_table .
  427. " SET `" . $save_to . "` = '" . $new_data_processed . "' " .
  428. " WHERE `db_name` = '" . PMA_Util::sqlAddSlashes($dbname) . "' " .
  429. " AND `table_name` = '" . PMA_Util::sqlAddSlashes($tablename) . "' " .
  430. " AND `version` = '" . PMA_Util::sqlAddSlashes($version) . "' ";
  431. $result = PMA_queryAsControlUser($sql_query);
  432. return $result;
  433. }
  434. /**
  435. * Activates tracking of a table.
  436. *
  437. * @param string $dbname name of database
  438. * @param string $tablename name of table
  439. * @param string $version version
  440. *
  441. * @static
  442. *
  443. * @return int result of SQL query
  444. */
  445. static public function activateTracking($dbname, $tablename, $version)
  446. {
  447. return self::_changeTracking($dbname, $tablename, $version, 1);
  448. }
  449. /**
  450. * Deactivates tracking of a table.
  451. *
  452. * @param string $dbname name of database
  453. * @param string $tablename name of table
  454. * @param string $version version
  455. *
  456. * @static
  457. *
  458. * @return int result of SQL query
  459. */
  460. static public function deactivateTracking($dbname, $tablename, $version)
  461. {
  462. return self::_changeTracking($dbname, $tablename, $version, 0);
  463. }
  464. /**
  465. * Gets the newest version of a tracking job
  466. * (in other words: gets the HEAD version).
  467. *
  468. * @param string $dbname name of database
  469. * @param string $tablename name of table
  470. * @param string $statement tracked statement
  471. *
  472. * @static
  473. *
  474. * @return int (-1 if no version exists | > 0 if a version exists)
  475. */
  476. static public function getVersion($dbname, $tablename, $statement = null)
  477. {
  478. $sql_query = " SELECT MAX(version) FROM " . self::$pma_table .
  479. " WHERE `db_name` = '" . PMA_Util::sqlAddSlashes($dbname) . "' " .
  480. " AND `table_name` = '" . PMA_Util::sqlAddSlashes($tablename) . "' ";
  481. if ($statement != "") {
  482. $sql_query .= PMA_DRIZZLE
  483. ? ' AND tracking & ' . self::_transformTrackingSet($statement) . ' <> 0'
  484. : " AND FIND_IN_SET('" . $statement . "',tracking) > 0" ;
  485. }
  486. $row = PMA_DBI_fetch_array(PMA_queryAsControlUser($sql_query));
  487. return isset($row[0])
  488. ? $row[0]
  489. : -1;
  490. }
  491. /**
  492. * Gets the record of a tracking job.
  493. *
  494. * @param string $dbname name of database
  495. * @param string $tablename name of table
  496. * @param string $version version number
  497. *
  498. * @static
  499. *
  500. * @return mixed record DDM log, DDL log, structure snapshot, tracked statements.
  501. */
  502. static public function getTrackedData($dbname, $tablename, $version)
  503. {
  504. if (! isset(self::$pma_table)) {
  505. self::init();
  506. }
  507. $sql_query = " SELECT * FROM " . self::$pma_table .
  508. " WHERE `db_name` = '" . PMA_Util::sqlAddSlashes($dbname) . "' ";
  509. if (! empty($tablename)) {
  510. $sql_query .= " AND `table_name` = '"
  511. . PMA_Util::sqlAddSlashes($tablename) ."' ";
  512. }
  513. $sql_query .= " AND `version` = '" . PMA_Util::sqlAddSlashes($version) ."' ".
  514. " ORDER BY `version` DESC LIMIT 1";
  515. $mixed = PMA_DBI_fetch_assoc(PMA_queryAsControlUser($sql_query));
  516. // Parse log
  517. $log_schema_entries = explode('# log ', $mixed['schema_sql']);
  518. $log_data_entries = explode('# log ', $mixed['data_sql']);
  519. $ddl_date_from = $date = date('Y-m-d H:i:s');
  520. $ddlog = array();
  521. $i = 0;
  522. // Iterate tracked data definition statements
  523. // For each log entry we want to get date, username and statement
  524. foreach ($log_schema_entries as $log_entry) {
  525. if (trim($log_entry) != '') {
  526. $date = substr($log_entry, 0, 19);
  527. $username = substr($log_entry, 20, strpos($log_entry, "\n") - 20);
  528. if ($i == 0) {
  529. $ddl_date_from = $date;
  530. }
  531. $statement = rtrim(strstr($log_entry, "\n"));
  532. $ddlog[] = array( 'date' => $date,
  533. 'username'=> $username,
  534. 'statement' => $statement );
  535. $i++;
  536. }
  537. }
  538. $date_from = $ddl_date_from;
  539. $date_to = $ddl_date_to = $date;
  540. $dml_date_from = $date_from;
  541. $dmlog = array();
  542. $i = 0;
  543. // Iterate tracked data manipulation statements
  544. // For each log entry we want to get date, username and statement
  545. foreach ($log_data_entries as $log_entry) {
  546. if (trim($log_entry) != '') {
  547. $date = substr($log_entry, 0, 19);
  548. $username = substr($log_entry, 20, strpos($log_entry, "\n") - 20);
  549. if ($i == 0) {
  550. $dml_date_from = $date;
  551. }
  552. $statement = rtrim(strstr($log_entry, "\n"));
  553. $dmlog[] = array( 'date' => $date,
  554. 'username' => $username,
  555. 'statement' => $statement );
  556. $i++;
  557. }
  558. }
  559. $dml_date_to = $date;
  560. // Define begin and end of date range for both logs
  561. if (strtotime($ddl_date_from) <= strtotime($dml_date_from)) {
  562. $data['date_from'] = $ddl_date_from;
  563. } else {
  564. $data['date_from'] = $dml_date_from;
  565. }
  566. if (strtotime($ddl_date_to) >= strtotime($dml_date_to)) {
  567. $data['date_to'] = $ddl_date_to;
  568. } else {
  569. $data['date_to'] = $dml_date_to;
  570. }
  571. $data['ddlog'] = $ddlog;
  572. $data['dmlog'] = $dmlog;
  573. $data['tracking'] = self::_transformTrackingSet($mixed['tracking']);
  574. $data['schema_snapshot'] = $mixed['schema_snapshot'];
  575. return $data;
  576. }
  577. /**
  578. * Parses a query. Gets
  579. * - statement identifier (UPDATE, ALTER TABLE, ...)
  580. * - type of statement, is it part of DDL or DML ?
  581. * - tablename
  582. *
  583. * @param string $query query
  584. *
  585. * @static
  586. * @todo: using PMA SQL Parser when possible
  587. * @todo: support multi-table/view drops
  588. *
  589. * @return mixed Array containing identifier, type and tablename.
  590. *
  591. */
  592. static public function parseQuery($query)
  593. {
  594. // Usage of PMA_SQP does not work here
  595. //
  596. // require_once("libraries/sqlparser.lib.php");
  597. // $parsed_sql = PMA_SQP_parse($query);
  598. // $sql_info = PMA_SQP_analyze($parsed_sql);
  599. $query = str_replace("\n", " ", $query);
  600. $query = str_replace("\r", " ", $query);
  601. $query = trim($query);
  602. $query = trim($query, ' -');
  603. $tokens = explode(" ", $query);
  604. foreach ($tokens as $key => $value) {
  605. $tokens[$key] = strtoupper($value);
  606. }
  607. // Parse USE statement, need it for SQL dump imports
  608. if (substr($query, 0, 4) == 'USE ') {
  609. $prefix = explode('USE ', $query);
  610. $GLOBALS['db'] = self::getTableName($prefix[1]);
  611. }
  612. /*
  613. * DDL statements
  614. */
  615. $result['type'] = 'DDL';
  616. // Parse CREATE VIEW statement
  617. if (in_array('CREATE', $tokens) == true
  618. && in_array('VIEW', $tokens) == true
  619. && in_array('AS', $tokens) == true
  620. ) {
  621. $result['identifier'] = 'CREATE VIEW';
  622. $index = array_search('VIEW', $tokens);
  623. $result['tablename'] = strtolower(
  624. self::getTableName($tokens[$index + 1])
  625. );
  626. }
  627. // Parse ALTER VIEW statement
  628. if (in_array('ALTER', $tokens) == true
  629. && in_array('VIEW', $tokens) == true
  630. && in_array('AS', $tokens) == true
  631. && ! isset($result['identifier'])
  632. ) {
  633. $result['identifier'] = 'ALTER VIEW';
  634. $index = array_search('VIEW', $tokens);
  635. $result['tablename'] = strtolower(
  636. self::getTableName($tokens[$index + 1])
  637. );
  638. }
  639. // Parse DROP VIEW statement
  640. if (! isset($result['identifier'])
  641. && substr($query, 0, 10) == 'DROP VIEW '
  642. ) {
  643. $result['identifier'] = 'DROP VIEW';
  644. $prefix = explode('DROP VIEW ', $query);
  645. $str = strstr($prefix[1], 'IF EXISTS');
  646. if ($str == false ) {
  647. $str = $prefix[1];
  648. }
  649. $result['tablename'] = self::getTableName($str);
  650. }
  651. // Parse CREATE DATABASE statement
  652. if (! isset($result['identifier'])
  653. && substr($query, 0, 15) == 'CREATE DATABASE'
  654. ) {
  655. $result['identifier'] = 'CREATE DATABASE';
  656. $str = str_replace('CREATE DATABASE', '', $query);
  657. $str = str_replace('IF NOT EXISTS', '', $str);
  658. $prefix = explode('DEFAULT ', $str);
  659. $result['tablename'] = '';
  660. $GLOBALS['db'] = self::getTableName($prefix[0]);
  661. }
  662. // Parse ALTER DATABASE statement
  663. if (! isset($result['identifier'])
  664. && substr($query, 0, 14) == 'ALTER DATABASE'
  665. ) {
  666. $result['identifier'] = 'ALTER DATABASE';
  667. $result['tablename'] = '';
  668. }
  669. // Parse DROP DATABASE statement
  670. if (! isset($result['identifier'])
  671. && substr($query, 0, 13) == 'DROP DATABASE'
  672. ) {
  673. $result['identifier'] = 'DROP DATABASE';
  674. $str = str_replace('DROP DATABASE', '', $query);
  675. $str = str_replace('IF EXISTS', '', $str);
  676. $GLOBALS['db'] = self::getTableName($str);
  677. $result['tablename'] = '';
  678. }
  679. // Parse CREATE TABLE statement
  680. if (! isset($result['identifier'])
  681. && substr($query, 0, 12) == 'CREATE TABLE'
  682. ) {
  683. $result['identifier'] = 'CREATE TABLE';
  684. $query = str_replace('IF NOT EXISTS', '', $query);
  685. $prefix = explode('CREATE TABLE ', $query);
  686. $suffix = explode('(', $prefix[1]);
  687. $result['tablename'] = self::getTableName($suffix[0]);
  688. }
  689. // Parse ALTER TABLE statement
  690. if (! isset($result['identifier'])
  691. && substr($query, 0, 12) == 'ALTER TABLE '
  692. ) {
  693. $result['identifier'] = 'ALTER TABLE';
  694. $prefix = explode('ALTER TABLE ', $query);
  695. $suffix = explode(' ', $prefix[1]);
  696. $result['tablename'] = self::getTableName($suffix[0]);
  697. }
  698. // Parse DROP TABLE statement
  699. if (! isset($result['identifier'])
  700. && substr($query, 0, 11) == 'DROP TABLE '
  701. ) {
  702. $result['identifier'] = 'DROP TABLE';
  703. $prefix = explode('DROP TABLE ', $query);
  704. $str = strstr($prefix[1], 'IF EXISTS');
  705. if ($str == false ) {
  706. $str = $prefix[1];
  707. }
  708. $result['tablename'] = self::getTableName($str);
  709. }
  710. // Parse CREATE INDEX statement
  711. if (! isset($result['identifier'])
  712. && (substr($query, 0, 12) == 'CREATE INDEX'
  713. || substr($query, 0, 19) == 'CREATE UNIQUE INDEX'
  714. || substr($query, 0, 20) == 'CREATE SPATIAL INDEX')
  715. ) {
  716. $result['identifier'] = 'CREATE INDEX';
  717. $prefix = explode('ON ', $query);
  718. $suffix = explode('(', $prefix[1]);
  719. $result['tablename'] = self::getTableName($suffix[0]);
  720. }
  721. // Parse DROP INDEX statement
  722. if (! isset($result['identifier'])
  723. && substr($query, 0, 10) == 'DROP INDEX'
  724. ) {
  725. $result['identifier'] = 'DROP INDEX';
  726. $prefix = explode('ON ', $query);
  727. $result['tablename'] = self::getTableName($prefix[1]);
  728. }
  729. // Parse RENAME TABLE statement
  730. if (! isset($result['identifier'])
  731. && substr($query, 0, 13) == 'RENAME TABLE '
  732. ) {
  733. $result['identifier'] = 'RENAME TABLE';
  734. $prefix = explode('RENAME TABLE ', $query);
  735. $names = explode(' TO ', $prefix[1]);
  736. $result['tablename'] = self::getTableName($names[0]);
  737. $result["tablename_after_rename"] = self::getTableName($names[1]);
  738. }
  739. /*
  740. * DML statements
  741. */
  742. if (! isset($result['identifier'])) {
  743. $result["type"] = 'DML';
  744. }
  745. // Parse UPDATE statement
  746. if (! isset($result['identifier'])
  747. && substr($query, 0, 6) == 'UPDATE'
  748. ) {
  749. $result['identifier'] = 'UPDATE';
  750. $prefix = explode('UPDATE ', $query);
  751. $suffix = explode(' ', $prefix[1]);
  752. $result['tablename'] = self::getTableName($suffix[0]);
  753. }
  754. // Parse INSERT INTO statement
  755. if (! isset($result['identifier'])
  756. && substr($query, 0, 11) == 'INSERT INTO'
  757. ) {
  758. $result['identifier'] = 'INSERT';
  759. $prefix = explode('INSERT INTO', $query);
  760. $suffix = explode('(', $prefix[1]);
  761. $result['tablename'] = self::getTableName($suffix[0]);
  762. }
  763. // Parse DELETE statement
  764. if (! isset($result['identifier'])
  765. && substr($query, 0, 6) == 'DELETE'
  766. ) {
  767. $result['identifier'] = 'DELETE';
  768. $prefix = explode('FROM ', $query);
  769. $suffix = explode(' ', $prefix[1]);
  770. $result['tablename'] = self::getTableName($suffix[0]);
  771. }
  772. // Parse TRUNCATE statement
  773. if (! isset($result['identifier'])
  774. && substr($query, 0, 8) == 'TRUNCATE'
  775. ) {
  776. $result['identifier'] = 'TRUNCATE';
  777. $prefix = explode('TRUNCATE', $query);
  778. $result['tablename'] = self::getTableName($prefix[1]);
  779. }
  780. return $result;
  781. }
  782. /**
  783. * Analyzes a given SQL statement and saves tracking data.
  784. *
  785. * @param string $query a SQL query
  786. *
  787. * @static
  788. *
  789. * @return void
  790. */
  791. static public function handleQuery($query)
  792. {
  793. // If query is marked as untouchable, leave
  794. if (strstr($query, "/*NOTRACK*/")) {
  795. return;
  796. }
  797. if (! (substr($query, -1) == ';')) {
  798. $query = $query . ";\n";
  799. }
  800. // Get some information about query
  801. $result = self::parseQuery($query);
  802. // Get database name
  803. $dbname = trim(isset($GLOBALS['db']) ? $GLOBALS['db'] : '', '`');
  804. // $dbname can be empty, for example when coming from Synchronize
  805. // and this is a query for the remote server
  806. if (empty($dbname)) {
  807. return;
  808. }
  809. // Remove null bytes (preg_replace() is vulnerable in some
  810. // PHP versions)
  811. $dbname = str_replace("\0", "", $dbname);
  812. // If we found a valid statement
  813. if (isset($result['identifier'])) {
  814. $version = self::getVersion(
  815. $dbname, $result['tablename'], $result['identifier']
  816. );
  817. // If version not exists and auto-creation is enabled
  818. if (self::$version_auto_create == true
  819. && self::isTracked($dbname, $result['tablename']) == false
  820. && $version == -1
  821. ) {
  822. // Create the version
  823. switch ($result['identifier']) {
  824. case 'CREATE TABLE':
  825. self::createVersion($dbname, $result['tablename'], '1');
  826. break;
  827. case 'CREATE VIEW':
  828. self::createVersion(
  829. $dbname, $result['tablename'], '1', '', true
  830. );
  831. break;
  832. case 'CREATE DATABASE':
  833. self::createDatabaseVersion($dbname, '1', $query);
  834. break;
  835. } // end switch
  836. }
  837. // If version exists
  838. if (self::isTracked($dbname, $result['tablename']) && $version != -1) {
  839. if ($result['type'] == 'DDL') {
  840. $save_to = 'schema_sql';
  841. } elseif ($result['type'] == 'DML') {
  842. $save_to = 'data_sql';
  843. } else {
  844. $save_to = '';
  845. }
  846. $date = date('Y-m-d H:i:s');
  847. // Cut off `dbname`. from query
  848. $query = preg_replace('/`' . $dbname . '`\s?\./', '', $query);
  849. // Add log information
  850. $query = self::getLogComment() . $query ;
  851. // Mark it as untouchable
  852. $sql_query = " /*NOTRACK*/\n"
  853. . " UPDATE " . self::$pma_table
  854. . " SET " . PMA_Util::backquote($save_to)
  855. . " = CONCAT( " . PMA_Util::backquote($save_to) . ",'\n"
  856. . PMA_Util::sqlAddSlashes($query) . "') ,"
  857. . " `date_updated` = '" . $date . "' ";
  858. // If table was renamed we have to change
  859. // the tablename attribute in pma_tracking too
  860. if ($result['identifier'] == 'RENAME TABLE') {
  861. $sql_query .= ', `table_name` = \''
  862. . PMA_Util::sqlAddSlashes($result['tablename_after_rename'])
  863. . '\' ';
  864. }
  865. // Save the tracking information only for
  866. // 1. the database
  867. // 2. the table / view
  868. // 3. the statements
  869. // we want to track
  870. $sql_query .=
  871. " WHERE FIND_IN_SET('" . $result['identifier'] . "',tracking) > 0" .
  872. " AND `db_name` = '" . PMA_Util::sqlAddSlashes($dbname) . "' " .
  873. " AND `table_name` = '" . PMA_Util::sqlAddSlashes($result['tablename']) . "' " .
  874. " AND `version` = '" . PMA_Util::sqlAddSlashes($version) . "' ";
  875. $result = PMA_queryAsControlUser($sql_query);
  876. }
  877. }
  878. }
  879. /**
  880. * Transforms tracking set for Drizzle, which has no SET type
  881. *
  882. * Converts int<>string for Drizzle, does nothing for MySQL
  883. *
  884. * @param int|string $tracking_set
  885. *
  886. * @return int|string
  887. */
  888. static private function _transformTrackingSet($tracking_set)
  889. {
  890. if (!PMA_DRIZZLE) {
  891. return $tracking_set;
  892. }
  893. // init conversion array (key 3 doesn't exist in calculated array)
  894. if (isset(self::$_tracking_set_flags[3])) {
  895. // initialize flags
  896. $set = self::$_tracking_set_flags;
  897. $array = array();
  898. for ($i = 0; $i < count($set); $i++) {
  899. $flag = 1 << $i;
  900. $array[$flag] = $set[$i];
  901. $array[$set[$i]] = $flag;
  902. }
  903. self::$_tracking_set_flags = $array;
  904. }
  905. if (is_numeric($tracking_set)) {
  906. // int > string conversion
  907. $aflags = array();
  908. // count/2 - conversion table has both int > string
  909. // and string > int values
  910. for ($i = 0; $i < count(self::$_tracking_set_flags)/2; $i++) {
  911. $flag = 1 << $i;
  912. if ($tracking_set & $flag) {
  913. $aflags[] = self::$_tracking_set_flags[$flag];
  914. }
  915. }
  916. $flags = implode(',', $aflags);
  917. } else {
  918. // string > int conversion
  919. $flags = 0;
  920. foreach (explode(',', $tracking_set) as $strflag) {
  921. if ($strflag == '') {
  922. continue;
  923. }
  924. $flags |= self::$_tracking_set_flags[$strflag];
  925. }
  926. }
  927. return $flags;
  928. }
  929. }
  930. ?>