1. Check existence of mb_string, mysql and xml extensions before installation.
[openemr.git] / phpmyadmin / libraries / Tracker.class.php
blobfa9d1368afecc459de9a051b769b6059309d8d7b
1 <?php
2 /* vim: set expandtab sw=4 ts=4 sts=4: */
3 /**
4 * Tracking changes on databases, tables and views
6 * @package PhpMyAdmin
7 */
8 if (! defined('PHPMYADMIN')) {
9 exit;
12 /**
13 * This class tracks changes on databases, tables and views.
15 * @package PhpMyAdmin
17 * @todo use stristr instead of strstr
19 class PMA_Tracker
21 /**
22 * Whether tracking is ready.
24 static protected $enabled = false;
26 /**
27 * Flags copied from `tracking` column definition in `pma_tracking` table.
28 * Used for column type conversion in Drizzle.
30 * @var array
32 static private $_tracking_set_flags = array(
33 'UPDATE','REPLACE','INSERT','DELETE','TRUNCATE','CREATE DATABASE',
34 'ALTER DATABASE','DROP DATABASE','CREATE TABLE','ALTER TABLE',
35 'RENAME TABLE','DROP TABLE','CREATE INDEX','DROP INDEX',
36 'CREATE VIEW','ALTER VIEW','DROP VIEW'
39 /**
40 * Actually enables tracking. This needs to be done after all
41 * underlaying code is initialized.
43 * @static
45 * @return void
47 static public function enable()
49 self::$enabled = true;
52 /**
53 * Gets the on/off value of the Tracker module, starts initialization.
55 * @static
57 * @return boolean (true=on|false=off)
59 static public function isActive()
61 if (! self::$enabled) {
62 return false;
64 /* We need to avoid attempt to track any queries
65 * from PMA_getRelationsParam
67 self::$enabled = false;
68 $cfgRelation = PMA_getRelationsParam();
69 /* Restore original state */
70 self::$enabled = true;
71 if (! $cfgRelation['trackingwork']) {
72 return false;
75 $pma_table = self::_getTrackingTable();
76 if (isset($pma_table)) {
77 return true;
78 } else {
79 return false;
83 /**
84 * Parses the name of a table from a SQL statement substring.
86 * @param string $string part of SQL statement
88 * @static
90 * @return string the name of table
92 static protected function getTableName($string)
94 if (/*overload*/mb_strstr($string, '.')) {
95 $temp = explode('.', $string);
96 $tablename = $temp[1];
97 } else {
98 $tablename = $string;
101 $str = explode("\n", $tablename);
102 $tablename = $str[0];
104 $tablename = str_replace(';', '', $tablename);
105 $tablename = str_replace('`', '', $tablename);
106 $tablename = trim($tablename);
108 return $tablename;
113 * Gets the tracking status of a table, is it active or deactive ?
115 * @param string $dbname name of database
116 * @param string $tablename name of table
118 * @static
120 * @return boolean true or false
122 static public function isTracked($dbname, $tablename)
124 if (! self::$enabled) {
125 return false;
127 /* We need to avoid attempt to track any queries
128 * from PMA_getRelationsParam
130 self::$enabled = false;
131 $cfgRelation = PMA_getRelationsParam();
132 /* Restore original state */
133 self::$enabled = true;
134 if (! $cfgRelation['trackingwork']) {
135 return false;
138 $sql_query = " SELECT tracking_active FROM " . self::_getTrackingTable() .
139 " WHERE db_name = '" . PMA_Util::sqlAddSlashes($dbname) . "' " .
140 " AND table_name = '" . PMA_Util::sqlAddSlashes($tablename) . "' " .
141 " ORDER BY version DESC";
143 $row = $GLOBALS['dbi']->fetchArray(PMA_queryAsControlUser($sql_query));
145 if (isset($row['tracking_active']) && $row['tracking_active'] == 1) {
146 return true;
147 } else {
148 return false;
153 * Returns the comment line for the log.
155 * @return string Comment, contains date and username
157 static public function getLogComment()
159 $date = date('Y-m-d H:i:s');
161 return "# log " . $date . " " . $GLOBALS['cfg']['Server']['user'] . "\n";
165 * Creates tracking version of a table / view
166 * (in other words: create a job to track future changes on the table).
168 * @param string $dbname name of database
169 * @param string $tablename name of table
170 * @param string $version version
171 * @param string $tracking_set set of tracking statements
172 * @param bool $is_view if table is a view
174 * @static
176 * @return int result of version insertion
178 static public function createVersion($dbname, $tablename, $version,
179 $tracking_set = '', $is_view = false
181 global $sql_backquotes, $export_type;
183 if ($tracking_set == '') {
184 $tracking_set
185 = $GLOBALS['cfg']['Server']['tracking_default_statements'];
188 // get Export SQL instance
189 include_once "libraries/plugin_interface.lib.php";
190 /* @var $export_sql_plugin ExportSql */
191 $export_sql_plugin = PMA_getPlugin(
192 "export",
193 "sql",
194 'libraries/plugins/export/',
195 array(
196 'export_type' => $export_type,
197 'single_table' => false,
201 $sql_backquotes = true;
203 $date = date('Y-m-d H:i:s');
205 // Get data definition snapshot of table
207 $columns = $GLOBALS['dbi']->getColumns($dbname, $tablename, null, true);
208 // int indices to reduce size
209 $columns = array_values($columns);
210 // remove Privileges to reduce size
211 for ($i = 0, $nb = count($columns); $i < $nb; $i++) {
212 unset($columns[$i]['Privileges']);
215 $indexes = $GLOBALS['dbi']->getTableIndexes($dbname, $tablename);
217 $snapshot = array('COLUMNS' => $columns, 'INDEXES' => $indexes);
218 $snapshot = serialize($snapshot);
220 // Get DROP TABLE / DROP VIEW and CREATE TABLE SQL statements
221 $sql_backquotes = true;
223 $create_sql = "";
225 if ($GLOBALS['cfg']['Server']['tracking_add_drop_table'] == true
226 && $is_view == false
228 $create_sql .= self::getLogComment()
229 . 'DROP TABLE IF EXISTS ' . PMA_Util::backquote($tablename) . ";\n";
233 if ($GLOBALS['cfg']['Server']['tracking_add_drop_view'] == true
234 && $is_view == true
236 $create_sql .= self::getLogComment()
237 . 'DROP VIEW IF EXISTS ' . PMA_Util::backquote($tablename) . ";\n";
240 $create_sql .= self::getLogComment() .
241 $export_sql_plugin->getTableDef($dbname, $tablename, "\n", "");
243 // Save version
245 $sql_query = "/*NOTRACK*/\n" .
246 "INSERT INTO " . self::_getTrackingTable() . " (" .
247 "db_name, " .
248 "table_name, " .
249 "version, " .
250 "date_created, " .
251 "date_updated, " .
252 "schema_snapshot, " .
253 "schema_sql, " .
254 "data_sql, " .
255 "tracking " .
256 ") " .
257 "values (
258 '" . PMA_Util::sqlAddSlashes($dbname) . "',
259 '" . PMA_Util::sqlAddSlashes($tablename) . "',
260 '" . PMA_Util::sqlAddSlashes($version) . "',
261 '" . PMA_Util::sqlAddSlashes($date) . "',
262 '" . PMA_Util::sqlAddSlashes($date) . "',
263 '" . PMA_Util::sqlAddSlashes($snapshot) . "',
264 '" . PMA_Util::sqlAddSlashes($create_sql) . "',
265 '" . PMA_Util::sqlAddSlashes("\n") . "',
266 '" . PMA_Util::sqlAddSlashes(self::_transformTrackingSet($tracking_set))
267 . "' )";
269 $result = PMA_queryAsControlUser($sql_query);
271 if ($result) {
272 // Deactivate previous version
273 self::deactivateTracking($dbname, $tablename, ($version - 1));
276 return $result;
281 * Removes all tracking data for a table or a version of a table
283 * @param string $dbname name of database
284 * @param string $tablename name of table
285 * @param string $version version
287 * @static
289 * @return int result of version insertion
291 static public function deleteTracking($dbname, $tablename, $version = '')
293 $sql_query = "/*NOTRACK*/\n"
294 . "DELETE FROM " . self::_getTrackingTable()
295 . " WHERE `db_name` = '"
296 . PMA_Util::sqlAddSlashes($dbname) . "'"
297 . " AND `table_name` = '"
298 . PMA_Util::sqlAddSlashes($tablename) . "'";
299 if ($version) {
300 $sql_query .= " AND `version` = '"
301 . PMA_Util::sqlAddSlashes($version) . "'";
303 $result = PMA_queryAsControlUser($sql_query);
305 return $result;
309 * Creates tracking version of a database
310 * (in other words: create a job to track future changes on the database).
312 * @param string $dbname name of database
313 * @param string $version version
314 * @param string $query query
315 * @param string $tracking_set set of tracking statements
317 * @static
319 * @return int result of version insertion
321 static public function createDatabaseVersion($dbname, $version, $query,
322 $tracking_set = 'CREATE DATABASE,ALTER DATABASE,DROP DATABASE'
324 $date = date('Y-m-d H:i:s');
326 if ($tracking_set == '') {
327 $tracking_set
328 = $GLOBALS['cfg']['Server']['tracking_default_statements'];
331 $create_sql = "";
333 if ($GLOBALS['cfg']['Server']['tracking_add_drop_database'] == true) {
334 $create_sql .= self::getLogComment()
335 . 'DROP DATABASE IF EXISTS ' . PMA_Util::backquote($dbname) . ";\n";
338 $create_sql .= self::getLogComment() . $query;
340 // Save version
341 $sql_query = "/*NOTRACK*/\n" .
342 "INSERT INTO " . self::_getTrackingTable() . " (" .
343 "db_name, " .
344 "table_name, " .
345 "version, " .
346 "date_created, " .
347 "date_updated, " .
348 "schema_snapshot, " .
349 "schema_sql, " .
350 "data_sql, " .
351 "tracking " .
352 ") " .
353 "values (
354 '" . PMA_Util::sqlAddSlashes($dbname) . "',
355 '" . PMA_Util::sqlAddSlashes('') . "',
356 '" . PMA_Util::sqlAddSlashes($version) . "',
357 '" . PMA_Util::sqlAddSlashes($date) . "',
358 '" . PMA_Util::sqlAddSlashes($date) . "',
359 '" . PMA_Util::sqlAddSlashes('') . "',
360 '" . PMA_Util::sqlAddSlashes($create_sql) . "',
361 '" . PMA_Util::sqlAddSlashes("\n") . "',
362 '" . PMA_Util::sqlAddSlashes(self::_transformTrackingSet($tracking_set))
363 . "' )";
365 $result = PMA_queryAsControlUser($sql_query);
367 return $result;
373 * Changes tracking of a table.
375 * @param string $dbname name of database
376 * @param string $tablename name of table
377 * @param string $version version
378 * @param integer $new_state the new state of tracking
380 * @static
382 * @return int result of SQL query
384 static private function _changeTracking($dbname, $tablename,
385 $version, $new_state
388 $sql_query = " UPDATE " . self::_getTrackingTable() .
389 " SET `tracking_active` = '" . $new_state . "' " .
390 " WHERE `db_name` = '" . PMA_Util::sqlAddSlashes($dbname) . "' " .
391 " AND `table_name` = '" . PMA_Util::sqlAddSlashes($tablename) . "' " .
392 " AND `version` = '" . PMA_Util::sqlAddSlashes($version) . "' ";
394 $result = PMA_queryAsControlUser($sql_query);
396 return $result;
400 * Changes tracking data of a table.
402 * @param string $dbname name of database
403 * @param string $tablename name of table
404 * @param string $version version
405 * @param string $type type of data(DDL || DML)
406 * @param string|array $new_data the new tracking data
408 * @static
410 * @return bool result of change
412 static public function changeTrackingData($dbname, $tablename,
413 $version, $type, $new_data
415 if ($type == 'DDL') {
416 $save_to = 'schema_sql';
417 } elseif ($type == 'DML') {
418 $save_to = 'data_sql';
419 } else {
420 return false;
422 $date = date('Y-m-d H:i:s');
424 $new_data_processed = '';
425 if (is_array($new_data)) {
426 foreach ($new_data as $data) {
427 $new_data_processed .= '# log ' . $date . ' ' . $data['username']
428 . PMA_Util::sqlAddSlashes($data['statement']) . "\n";
430 } else {
431 $new_data_processed = $new_data;
434 $sql_query = " UPDATE " . self::_getTrackingTable() .
435 " SET `" . $save_to . "` = '" . $new_data_processed . "' " .
436 " WHERE `db_name` = '" . PMA_Util::sqlAddSlashes($dbname) . "' " .
437 " AND `table_name` = '" . PMA_Util::sqlAddSlashes($tablename) . "' " .
438 " AND `version` = '" . PMA_Util::sqlAddSlashes($version) . "' ";
440 $result = PMA_queryAsControlUser($sql_query);
442 return (boolean) $result;
446 * Activates tracking of a table.
448 * @param string $dbname name of database
449 * @param string $tablename name of table
450 * @param string $version version
452 * @static
454 * @return int result of SQL query
456 static public function activateTracking($dbname, $tablename, $version)
458 return self::_changeTracking($dbname, $tablename, $version, 1);
463 * Deactivates tracking of a table.
465 * @param string $dbname name of database
466 * @param string $tablename name of table
467 * @param string $version version
469 * @static
471 * @return int result of SQL query
473 static public function deactivateTracking($dbname, $tablename, $version)
475 return self::_changeTracking($dbname, $tablename, $version, 0);
480 * Gets the newest version of a tracking job
481 * (in other words: gets the HEAD version).
483 * @param string $dbname name of database
484 * @param string $tablename name of table
485 * @param string $statement tracked statement
487 * @static
489 * @return int (-1 if no version exists | > 0 if a version exists)
491 static public function getVersion($dbname, $tablename, $statement = null)
493 $sql_query = " SELECT MAX(version) FROM " . self::_getTrackingTable() .
494 " WHERE `db_name` = '" . PMA_Util::sqlAddSlashes($dbname) . "' " .
495 " AND `table_name` = '" . PMA_Util::sqlAddSlashes($tablename) . "' ";
497 if ($statement != "") {
498 if (PMA_DRIZZLE) {
499 $sql_query .= ' AND tracking & '
500 . self::_transformTrackingSet($statement) . ' <> 0';
501 } else {
502 $sql_query .= " AND FIND_IN_SET('"
503 . $statement . "',tracking) > 0" ;
506 $row = $GLOBALS['dbi']->fetchArray(PMA_queryAsControlUser($sql_query));
507 return isset($row[0])
508 ? $row[0]
509 : -1;
514 * Gets the record of a tracking job.
516 * @param string $dbname name of database
517 * @param string $tablename name of table
518 * @param string $version version number
520 * @static
522 * @return mixed record DDM log, DDL log, structure snapshot, tracked
523 * statements.
525 static public function getTrackedData($dbname, $tablename, $version)
527 $sql_query = " SELECT * FROM " . self::_getTrackingTable() .
528 " WHERE `db_name` = '" . PMA_Util::sqlAddSlashes($dbname) . "' ";
529 if (! empty($tablename)) {
530 $sql_query .= " AND `table_name` = '"
531 . PMA_Util::sqlAddSlashes($tablename) . "' ";
533 $sql_query .= " AND `version` = '" . PMA_Util::sqlAddSlashes($version)
534 . "' " . " ORDER BY `version` DESC LIMIT 1";
536 $mixed = $GLOBALS['dbi']->fetchAssoc(PMA_queryAsControlUser($sql_query));
538 // Parse log
539 $log_schema_entries = explode('# log ', $mixed['schema_sql']);
540 $log_data_entries = explode('# log ', $mixed['data_sql']);
542 $ddl_date_from = $date = date('Y-m-d H:i:s');
544 $ddlog = array();
545 $first_iteration = true;
547 // Iterate tracked data definition statements
548 // For each log entry we want to get date, username and statement
549 foreach ($log_schema_entries as $log_entry) {
550 if (trim($log_entry) != '') {
551 $date = /*overload*/mb_substr($log_entry, 0, 19);
552 $username = /*overload*/mb_substr(
553 $log_entry, 20, /*overload*/mb_strpos($log_entry, "\n") - 20
555 if ($first_iteration) {
556 $ddl_date_from = $date;
557 $first_iteration = false;
559 $statement = rtrim(/*overload*/mb_strstr($log_entry, "\n"));
561 $ddlog[] = array( 'date' => $date,
562 'username'=> $username,
563 'statement' => $statement );
567 $date_from = $ddl_date_from;
568 $ddl_date_to = $date;
570 $dml_date_from = $date_from;
572 $dmlog = array();
573 $first_iteration = true;
575 // Iterate tracked data manipulation statements
576 // For each log entry we want to get date, username and statement
577 foreach ($log_data_entries as $log_entry) {
578 if (trim($log_entry) != '') {
579 $date = /*overload*/mb_substr($log_entry, 0, 19);
580 $username = /*overload*/mb_substr(
581 $log_entry, 20, /*overload*/mb_strpos($log_entry, "\n") - 20
583 if ($first_iteration) {
584 $dml_date_from = $date;
585 $first_iteration = false;
587 $statement = rtrim(/*overload*/mb_strstr($log_entry, "\n"));
589 $dmlog[] = array( 'date' => $date,
590 'username' => $username,
591 'statement' => $statement );
595 $dml_date_to = $date;
597 // Define begin and end of date range for both logs
598 $data = array();
599 if (strtotime($ddl_date_from) <= strtotime($dml_date_from)) {
600 $data['date_from'] = $ddl_date_from;
601 } else {
602 $data['date_from'] = $dml_date_from;
604 if (strtotime($ddl_date_to) >= strtotime($dml_date_to)) {
605 $data['date_to'] = $ddl_date_to;
606 } else {
607 $data['date_to'] = $dml_date_to;
609 $data['ddlog'] = $ddlog;
610 $data['dmlog'] = $dmlog;
611 $data['tracking'] = self::_transformTrackingSet($mixed['tracking']);
612 $data['schema_snapshot'] = $mixed['schema_snapshot'];
614 return $data;
619 * Parses a query. Gets
620 * - statement identifier (UPDATE, ALTER TABLE, ...)
621 * - type of statement, is it part of DDL or DML ?
622 * - tablename
624 * @param string $query query
626 * @static
627 * @todo: using PMA SQL Parser when possible
628 * @todo: support multi-table/view drops
630 * @return mixed Array containing identifier, type and tablename.
633 static public function parseQuery($query)
635 // Usage of PMA_SQP does not work here
637 // require_once("libraries/sqlparser.lib.php");
638 // $parsed_sql = PMA_SQP_parse($query);
639 // $sql_info = PMA_SQP_analyze($parsed_sql);
641 $query = str_replace("\n", " ", $query);
642 $query = str_replace("\r", " ", $query);
644 $query = trim($query);
645 $query = trim($query, ' -');
647 $tokens = explode(" ", $query);
648 foreach ($tokens as $key => $value) {
649 $tokens[$key] = /*overload*/mb_strtoupper($value);
652 // Parse USE statement, need it for SQL dump imports
653 if (/*overload*/mb_substr($query, 0, 4) == 'USE ') {
654 $prefix = explode('USE ', $query);
655 $GLOBALS['db'] = self::getTableName($prefix[1]);
659 * DDL statements
662 $result = array();
663 $result['type'] = 'DDL';
665 // Parse CREATE VIEW statement
666 if (in_array('CREATE', $tokens) == true
667 && in_array('VIEW', $tokens) == true
668 && in_array('AS', $tokens) == true
670 $result['identifier'] = 'CREATE VIEW';
672 $index = array_search('VIEW', $tokens);
674 $result['tablename'] = /*overload*/mb_strtolower(
675 self::getTableName($tokens[$index + 1])
679 // Parse ALTER VIEW statement
680 if (in_array('ALTER', $tokens) == true
681 && in_array('VIEW', $tokens) == true
682 && in_array('AS', $tokens) == true
683 && ! isset($result['identifier'])
685 $result['identifier'] = 'ALTER VIEW';
687 $index = array_search('VIEW', $tokens);
689 $result['tablename'] = /*overload*/mb_strtolower(
690 self::getTableName($tokens[$index + 1])
694 // Parse DROP VIEW statement
695 if (! isset($result['identifier'])
696 && substr($query, 0, 10) == 'DROP VIEW '
698 $result['identifier'] = 'DROP VIEW';
700 $prefix = explode('DROP VIEW ', $query);
701 $str = str_replace('IF EXISTS', '', $prefix[1]);
702 $result['tablename'] = self::getTableName($str);
705 // Parse CREATE DATABASE statement
706 if (! isset($result['identifier'])
707 && substr($query, 0, 15) == 'CREATE DATABASE'
709 $result['identifier'] = 'CREATE DATABASE';
710 $str = str_replace('CREATE DATABASE', '', $query);
711 $str = str_replace('IF NOT EXISTS', '', $str);
713 $prefix = explode('DEFAULT ', $str);
715 $result['tablename'] = '';
716 $GLOBALS['db'] = self::getTableName($prefix[0]);
719 // Parse ALTER DATABASE statement
720 if (! isset($result['identifier'])
721 && substr($query, 0, 14) == 'ALTER DATABASE'
723 $result['identifier'] = 'ALTER DATABASE';
724 $result['tablename'] = '';
727 // Parse DROP DATABASE statement
728 if (! isset($result['identifier'])
729 && substr($query, 0, 13) == 'DROP DATABASE'
731 $result['identifier'] = 'DROP DATABASE';
732 $str = str_replace('DROP DATABASE', '', $query);
733 $str = str_replace('IF EXISTS', '', $str);
734 $GLOBALS['db'] = self::getTableName($str);
735 $result['tablename'] = '';
738 // Parse CREATE TABLE statement
739 if (! isset($result['identifier'])
740 && substr($query, 0, 12) == 'CREATE TABLE'
742 $result['identifier'] = 'CREATE TABLE';
743 $query = str_replace('IF NOT EXISTS', '', $query);
744 $prefix = explode('CREATE TABLE ', $query);
745 $suffix = explode('(', $prefix[1]);
746 $result['tablename'] = self::getTableName($suffix[0]);
749 // Parse ALTER TABLE statement
750 if (! isset($result['identifier'])
751 && substr($query, 0, 12) == 'ALTER TABLE '
753 $result['identifier'] = 'ALTER TABLE';
755 $prefix = explode('ALTER TABLE ', $query);
756 $suffix = explode(' ', $prefix[1]);
757 $result['tablename'] = self::getTableName($suffix[0]);
760 // Parse DROP TABLE statement
761 if (! isset($result['identifier'])
762 && substr($query, 0, 11) == 'DROP TABLE '
764 $result['identifier'] = 'DROP TABLE';
766 $prefix = explode('DROP TABLE ', $query);
767 $str = str_replace('IF EXISTS', '', $prefix[1]);
768 $result['tablename'] = self::getTableName($str);
771 // Parse CREATE INDEX statement
772 if (! isset($result['identifier'])
773 && (substr($query, 0, 12) == 'CREATE INDEX'
774 || substr($query, 0, 19) == 'CREATE UNIQUE INDEX'
775 || substr($query, 0, 20) == 'CREATE SPATIAL INDEX')
777 $result['identifier'] = 'CREATE INDEX';
778 $prefix = explode('ON ', $query);
779 $suffix = explode('(', $prefix[1]);
780 $result['tablename'] = self::getTableName($suffix[0]);
783 // Parse DROP INDEX statement
784 if (! isset($result['identifier'])
785 && substr($query, 0, 10) == 'DROP INDEX'
787 $result['identifier'] = 'DROP INDEX';
788 $prefix = explode('ON ', $query);
789 $result['tablename'] = self::getTableName($prefix[1]);
792 // Parse RENAME TABLE statement
793 if (! isset($result['identifier'])
794 && substr($query, 0, 13) == 'RENAME TABLE '
796 $result['identifier'] = 'RENAME TABLE';
797 $prefix = explode('RENAME TABLE ', $query);
798 $names = explode(' TO ', $prefix[1]);
799 $result['tablename'] = self::getTableName($names[0]);
800 $result["tablename_after_rename"] = self::getTableName($names[1]);
804 * DML statements
807 if (! isset($result['identifier'])) {
808 $result["type"] = 'DML';
810 // Parse UPDATE statement
811 if (! isset($result['identifier'])
812 && substr($query, 0, 6) == 'UPDATE'
814 $result['identifier'] = 'UPDATE';
815 $prefix = explode('UPDATE ', $query);
816 $suffix = explode(' ', $prefix[1]);
817 $result['tablename'] = self::getTableName($suffix[0]);
820 // Parse INSERT INTO statement
821 if (! isset($result['identifier'])
822 && substr($query, 0, 11) == 'INSERT INTO'
824 $result['identifier'] = 'INSERT';
825 $prefix = explode('INSERT INTO', $query);
826 $suffix = explode('(', $prefix[1]);
827 $result['tablename'] = self::getTableName($suffix[0]);
830 // Parse DELETE statement
831 if (! isset($result['identifier'])
832 && substr($query, 0, 6) == 'DELETE'
834 $result['identifier'] = 'DELETE';
835 $prefix = explode('FROM ', $query);
836 $suffix = explode(' ', $prefix[1]);
837 $result['tablename'] = self::getTableName($suffix[0]);
840 // Parse TRUNCATE statement
841 if (! isset($result['identifier'])
842 && substr($query, 0, 8) == 'TRUNCATE'
844 $result['identifier'] = 'TRUNCATE';
845 $prefix = explode('TRUNCATE', $query);
846 $result['tablename'] = self::getTableName($prefix[1]);
849 return $result;
854 * Analyzes a given SQL statement and saves tracking data.
856 * @param string $query a SQL query
858 * @static
860 * @return void
862 static public function handleQuery($query)
864 // If query is marked as untouchable, leave
865 if (/*overload*/mb_strstr($query, "/*NOTRACK*/")) {
866 return;
869 if (! (substr($query, -1) == ';')) {
870 $query = $query . ";\n";
872 // Get some information about query
873 $result = self::parseQuery($query);
875 // Get database name
876 $dbname = trim(isset($GLOBALS['db']) ? $GLOBALS['db'] : '', '`');
877 // $dbname can be empty, for example when coming from Synchronize
878 // and this is a query for the remote server
879 if (empty($dbname)) {
880 return;
883 // If we found a valid statement
884 if (isset($result['identifier'])) {
885 $version = self::getVersion(
886 $dbname, $result['tablename'], $result['identifier']
889 // If version not exists and auto-creation is enabled
890 if ($GLOBALS['cfg']['Server']['tracking_version_auto_create'] == true
891 && self::isTracked($dbname, $result['tablename']) == false
892 && $version == -1
894 // Create the version
896 switch ($result['identifier']) {
897 case 'CREATE TABLE':
898 self::createVersion($dbname, $result['tablename'], '1');
899 break;
900 case 'CREATE VIEW':
901 self::createVersion(
902 $dbname, $result['tablename'], '1', '', true
904 break;
905 case 'CREATE DATABASE':
906 self::createDatabaseVersion($dbname, '1', $query);
907 break;
908 } // end switch
911 // If version exists
912 if (self::isTracked($dbname, $result['tablename']) && $version != -1) {
913 if ($result['type'] == 'DDL') {
914 $save_to = 'schema_sql';
915 } elseif ($result['type'] == 'DML') {
916 $save_to = 'data_sql';
917 } else {
918 $save_to = '';
920 $date = date('Y-m-d H:i:s');
922 // Cut off `dbname`. from query
923 $query = preg_replace(
924 '/`' . preg_quote($dbname) . '`\s?\./',
926 $query
929 // Add log information
930 $query = self::getLogComment() . $query ;
932 // Mark it as untouchable
933 $sql_query = " /*NOTRACK*/\n"
934 . " UPDATE " . self::_getTrackingTable()
935 . " SET " . PMA_Util::backquote($save_to)
936 . " = CONCAT( " . PMA_Util::backquote($save_to) . ",'\n"
937 . PMA_Util::sqlAddSlashes($query) . "') ,"
938 . " `date_updated` = '" . $date . "' ";
940 // If table was renamed we have to change
941 // the tablename attribute in pma_tracking too
942 if ($result['identifier'] == 'RENAME TABLE') {
943 $sql_query .= ', `table_name` = \''
944 . PMA_Util::sqlAddSlashes($result['tablename_after_rename'])
945 . '\' ';
948 // Save the tracking information only for
949 // 1. the database
950 // 2. the table / view
951 // 3. the statements
952 // we want to track
953 $sql_query .=
954 " WHERE FIND_IN_SET('" . $result['identifier'] . "',tracking) > 0" .
955 " AND `db_name` = '" . PMA_Util::sqlAddSlashes($dbname) . "' " .
956 " AND `table_name` = '"
957 . PMA_Util::sqlAddSlashes($result['tablename']) . "' " .
958 " AND `version` = '" . PMA_Util::sqlAddSlashes($version) . "' ";
960 PMA_queryAsControlUser($sql_query);
966 * Transforms tracking set for Drizzle, which has no SET type
968 * Converts int<>string for Drizzle, does nothing for MySQL
970 * @param int|string $tracking_set Set to convert
972 * @return int|string
974 static private function _transformTrackingSet($tracking_set)
976 if (!PMA_DRIZZLE) {
977 return $tracking_set;
980 // init conversion array (key 3 doesn't exist in calculated array)
981 if (isset(self::$_tracking_set_flags[3])) {
982 // initialize flags
983 $set = self::$_tracking_set_flags;
984 $array = array();
985 for ($i = 0, $nb = count($set); $i < $nb; $i++) {
986 $flag = 1 << $i;
987 $array[$flag] = $set[$i];
988 $array[$set[$i]] = $flag;
990 self::$_tracking_set_flags = $array;
993 if (is_numeric($tracking_set)) {
994 // int > string conversion
995 $aflags = array();
996 // count/2 - conversion table has both int > string
997 // and string > int values
998 for ($i = 0, $nb = count(self::$_tracking_set_flags)/2; $i < $nb; $i++) {
999 $flag = 1 << $i;
1000 if ($tracking_set & $flag) {
1001 $aflags[] = self::$_tracking_set_flags[$flag];
1004 $flags = implode(',', $aflags);
1005 } else {
1006 // string > int conversion
1007 $flags = 0;
1008 foreach (explode(',', $tracking_set) as $strflag) {
1009 if ($strflag == '') {
1010 continue;
1012 $flags |= self::$_tracking_set_flags[$strflag];
1016 return $flags;
1020 * Returns the tracking table
1022 * @return string tracking table
1024 private static function _getTrackingTable()
1026 $cfgRelation = PMA_getRelationsParam();
1027 return PMA_Util::backquote($cfgRelation['db'])
1028 . '.' . PMA_Util::backquote($cfgRelation['tracking']);