Upgraded phpmyadmin to 4.0.4 (All Languages) - No modifications yet
[openemr.git] / phpmyadmin / libraries / Tracker.class.php
blob4e67c08a21e07833dcc613d7ef9ec543296d352a
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 * Defines the internal PMA table which contains tracking data.
29 * @access protected
30 * @var string
32 static protected $pma_table;
34 /**
35 * Defines the usage of DROP TABLE statment in SQL dumps.
37 * @access protected
38 * @var boolean
40 static protected $add_drop_table;
42 /**
43 * Defines the usage of DROP VIEW statment in SQL dumps.
45 * @access protected
46 * @var boolean
48 static protected $add_drop_view;
50 /**
51 * Defines the usage of DROP DATABASE statment in SQL dumps.
53 * @access protected
54 * @var boolean
56 static protected $add_drop_database;
58 /**
59 * Defines auto-creation of tracking versions.
61 * @var boolean
63 static protected $version_auto_create;
65 /**
66 * Defines the default set of tracked statements.
68 * @var string
70 static protected $default_tracking_set;
72 /**
73 * Flags copied from `tracking` column definition in `pma_tracking` table.
74 * Used for column type conversion in Drizzle.
76 * @var array
78 static private $_tracking_set_flags = array(
79 'UPDATE','REPLACE','INSERT','DELETE','TRUNCATE','CREATE DATABASE',
80 'ALTER DATABASE','DROP DATABASE','CREATE TABLE','ALTER TABLE',
81 'RENAME TABLE','DROP TABLE','CREATE INDEX','DROP INDEX',
82 'CREATE VIEW','ALTER VIEW','DROP VIEW'
86 /**
87 * Initializes settings.
89 * @static
91 * @return void
93 static protected function init()
95 self::$pma_table = PMA_Util::backquote($GLOBALS['cfg']['Server']['pmadb']) .".".
96 PMA_Util::backquote($GLOBALS['cfg']['Server']['tracking']);
98 self::$add_drop_table = $GLOBALS['cfg']['Server']['tracking_add_drop_table'];
100 self::$add_drop_view = $GLOBALS['cfg']['Server']['tracking_add_drop_view'];
102 self::$add_drop_database
103 = $GLOBALS['cfg']['Server']['tracking_add_drop_database'];
105 self::$default_tracking_set
106 = $GLOBALS['cfg']['Server']['tracking_default_statements'];
108 self::$version_auto_create
109 = $GLOBALS['cfg']['Server']['tracking_version_auto_create'];
113 * Actually enables tracking. This needs to be done after all
114 * underlaying code is initialized.
116 * @static
118 * @return void
120 static public function enable()
122 self::$enabled = true;
126 * Gets the on/off value of the Tracker module, starts initialization.
128 * @static
130 * @return boolean (true=on|false=off)
132 static public function isActive()
134 if (! self::$enabled) {
135 return false;
137 /* We need to avoid attempt to track any queries
138 * from PMA_getRelationsParam
140 self::$enabled = false;
141 $cfgRelation = PMA_getRelationsParam();
142 /* Restore original state */
143 self::$enabled = true;
144 if (! $cfgRelation['trackingwork']) {
145 return false;
147 self::init();
149 if (isset(self::$pma_table)) {
150 return true;
151 } else {
152 return false;
157 * Parses the name of a table from a SQL statement substring.
159 * @param string $string part of SQL statement
161 * @static
163 * @return string the name of table
165 static protected function getTableName($string)
167 if (strstr($string, '.')) {
168 $temp = explode('.', $string);
169 $tablename = $temp[1];
170 } else {
171 $tablename = $string;
174 $str = explode("\n", $tablename);
175 $tablename = $str[0];
177 $tablename = str_replace(';', '', $tablename);
178 $tablename = str_replace('`', '', $tablename);
179 $tablename = trim($tablename);
181 return $tablename;
186 * Gets the tracking status of a table, is it active or deactive ?
188 * @param string $dbname name of database
189 * @param string $tablename name of table
191 * @static
193 * @return boolean true or false
195 static public function isTracked($dbname, $tablename)
197 if (! self::$enabled) {
198 return false;
200 /* We need to avoid attempt to track any queries
201 * from PMA_getRelationsParam
203 self::$enabled = false;
204 $cfgRelation = PMA_getRelationsParam();
205 /* Restore original state */
206 self::$enabled = true;
207 if (! $cfgRelation['trackingwork']) {
208 return false;
211 $sql_query = " SELECT tracking_active FROM " . self::$pma_table .
212 " WHERE db_name = '" . PMA_Util::sqlAddSlashes($dbname) . "' " .
213 " AND table_name = '" . PMA_Util::sqlAddSlashes($tablename) . "' " .
214 " ORDER BY version DESC";
216 $row = PMA_DBI_fetch_array(PMA_queryAsControlUser($sql_query));
218 if (isset($row['tracking_active']) && $row['tracking_active'] == 1) {
219 return true;
220 } else {
221 return false;
226 * Returns the comment line for the log.
228 * @return string Comment, contains date and username
230 static public function getLogComment()
232 $date = date('Y-m-d H:i:s');
234 return "# log " . $date . " " . $GLOBALS['cfg']['Server']['user'] . "\n";
238 * Creates tracking version of a table / view
239 * (in other words: create a job to track future changes on the table).
241 * @param string $dbname name of database
242 * @param string $tablename name of table
243 * @param string $version version
244 * @param string $tracking_set set of tracking statements
245 * @param bool $is_view if table is a view
247 * @static
249 * @return int result of version insertion
251 static public function createVersion($dbname, $tablename, $version,
252 $tracking_set = '', $is_view = false
254 global $sql_backquotes, $export_type;
256 if ($tracking_set == '') {
257 $tracking_set = self::$default_tracking_set;
260 // get Export SQL instance
261 include_once "libraries/plugin_interface.lib.php";
262 $export_sql_plugin = PMA_getPlugin(
263 "export",
264 "sql",
265 'libraries/plugins/export/',
266 array(
267 'export_type' => $export_type,
268 'single_table' => isset($single_table)
272 $sql_backquotes = true;
274 $date = date('Y-m-d H:i:s');
276 // Get data definition snapshot of table
278 $columns = PMA_DBI_get_columns($dbname, $tablename, null, true);
279 // int indices to reduce size
280 $columns = array_values($columns);
281 // remove Privileges to reduce size
282 for ($i = 0; $i < count($columns); $i++) {
283 unset($columns[$i]['Privileges']);
286 $indexes = PMA_DBI_get_table_indexes($dbname, $tablename);
288 $snapshot = array('COLUMNS' => $columns, 'INDEXES' => $indexes);
289 $snapshot = serialize($snapshot);
291 // Get DROP TABLE / DROP VIEW and CREATE TABLE SQL statements
292 $sql_backquotes = true;
294 $create_sql = "";
296 if (self::$add_drop_table == true && $is_view == false) {
297 $create_sql .= self::getLogComment()
298 . 'DROP TABLE IF EXISTS ' . PMA_Util::backquote($tablename) . ";\n";
302 if (self::$add_drop_view == true && $is_view == true) {
303 $create_sql .= self::getLogComment()
304 . 'DROP VIEW IF EXISTS ' . PMA_Util::backquote($tablename) . ";\n";
307 $create_sql .= self::getLogComment() .
308 $export_sql_plugin->getTableDef($dbname, $tablename, "\n", "");
310 // Save version
312 $sql_query = "/*NOTRACK*/\n" .
313 "INSERT INTO" . self::$pma_table . " (" .
314 "db_name, " .
315 "table_name, " .
316 "version, " .
317 "date_created, " .
318 "date_updated, " .
319 "schema_snapshot, " .
320 "schema_sql, " .
321 "data_sql, " .
322 "tracking " .
323 ") " .
324 "values (
325 '" . PMA_Util::sqlAddSlashes($dbname) . "',
326 '" . PMA_Util::sqlAddSlashes($tablename) . "',
327 '" . PMA_Util::sqlAddSlashes($version) . "',
328 '" . PMA_Util::sqlAddSlashes($date) . "',
329 '" . PMA_Util::sqlAddSlashes($date) . "',
330 '" . PMA_Util::sqlAddSlashes($snapshot) . "',
331 '" . PMA_Util::sqlAddSlashes($create_sql) . "',
332 '" . PMA_Util::sqlAddSlashes("\n") . "',
333 '" . PMA_Util::sqlAddSlashes(self::_transformTrackingSet($tracking_set)) . "' )";
335 $result = PMA_queryAsControlUser($sql_query);
337 if ($result) {
338 // Deactivate previous version
339 self::deactivateTracking($dbname, $tablename, ($version - 1));
342 return $result;
347 * Removes all tracking data for a table
349 * @param string $dbname name of database
350 * @param string $tablename name of table
352 * @static
354 * @return int result of version insertion
356 static public function deleteTracking($dbname, $tablename)
358 $sql_query = "/*NOTRACK*/\n"
359 . "DELETE FROM " . self::$pma_table
360 . " WHERE `db_name` = '"
361 . PMA_Util::sqlAddSlashes($dbname) . "'"
362 . " AND `table_name` = '"
363 . PMA_Util::sqlAddSlashes($tablename) . "'";
364 $result = PMA_queryAsControlUser($sql_query);
366 return $result;
370 * Creates tracking version of a database
371 * (in other words: create a job to track future changes on the database).
373 * @param string $dbname name of database
374 * @param string $version version
375 * @param string $query query
376 * @param string $tracking_set set of tracking statements
378 * @static
380 * @return int result of version insertion
382 static public function createDatabaseVersion($dbname, $version, $query,
383 $tracking_set = 'CREATE DATABASE,ALTER DATABASE,DROP DATABASE'
385 $date = date('Y-m-d H:i:s');
387 if ($tracking_set == '') {
388 $tracking_set = self::$default_tracking_set;
391 include_once './libraries/export/sql.php';
393 $create_sql = "";
395 if (self::$add_drop_database == true) {
396 $create_sql .= self::getLogComment()
397 . 'DROP DATABASE IF EXISTS ' . PMA_Util::backquote($dbname) . ";\n";
400 $create_sql .= self::getLogComment() . $query;
402 // Save version
403 $sql_query = "/*NOTRACK*/\n" .
404 "INSERT INTO" . self::$pma_table . " (" .
405 "db_name, " .
406 "table_name, " .
407 "version, " .
408 "date_created, " .
409 "date_updated, " .
410 "schema_snapshot, " .
411 "schema_sql, " .
412 "data_sql, " .
413 "tracking " .
414 ") " .
415 "values (
416 '" . PMA_Util::sqlAddSlashes($dbname) . "',
417 '" . PMA_Util::sqlAddSlashes('') . "',
418 '" . PMA_Util::sqlAddSlashes($version) . "',
419 '" . PMA_Util::sqlAddSlashes($date) . "',
420 '" . PMA_Util::sqlAddSlashes($date) . "',
421 '" . PMA_Util::sqlAddSlashes('') . "',
422 '" . PMA_Util::sqlAddSlashes($create_sql) . "',
423 '" . PMA_Util::sqlAddSlashes("\n") . "',
424 '" . PMA_Util::sqlAddSlashes(self::_transformTrackingSet($tracking_set)) . "' )";
426 $result = PMA_queryAsControlUser($sql_query);
428 return $result;
434 * Changes tracking of a table.
436 * @param string $dbname name of database
437 * @param string $tablename name of table
438 * @param string $version version
439 * @param integer $new_state the new state of tracking
441 * @static
443 * @return int result of SQL query
445 static private function _changeTracking($dbname, $tablename,
446 $version, $new_state
449 $sql_query = " UPDATE " . self::$pma_table .
450 " SET `tracking_active` = '" . $new_state . "' " .
451 " WHERE `db_name` = '" . PMA_Util::sqlAddSlashes($dbname) . "' " .
452 " AND `table_name` = '" . PMA_Util::sqlAddSlashes($tablename) . "' " .
453 " AND `version` = '" . PMA_Util::sqlAddSlashes($version) . "' ";
455 $result = PMA_queryAsControlUser($sql_query);
457 return $result;
461 * Changes tracking data of a table.
463 * @param string $dbname name of database
464 * @param string $tablename name of table
465 * @param string $version version
466 * @param string $type type of data(DDL || DML)
467 * @param string|array $new_data the new tracking data
469 * @static
471 * @return bool result of change
473 static public function changeTrackingData($dbname, $tablename,
474 $version, $type, $new_data
476 if ($type == 'DDL') {
477 $save_to = 'schema_sql';
478 } elseif ($type == 'DML') {
479 $save_to = 'data_sql';
480 } else {
481 return false;
483 $date = date('Y-m-d H:i:s');
485 $new_data_processed = '';
486 if (is_array($new_data)) {
487 foreach ($new_data as $data) {
488 $new_data_processed .= '# log ' . $date . ' ' . $data['username']
489 . PMA_Util::sqlAddSlashes($data['statement']) . "\n";
491 } else {
492 $new_data_processed = $new_data;
495 $sql_query = " UPDATE " . self::$pma_table .
496 " SET `" . $save_to . "` = '" . $new_data_processed . "' " .
497 " WHERE `db_name` = '" . PMA_Util::sqlAddSlashes($dbname) . "' " .
498 " AND `table_name` = '" . PMA_Util::sqlAddSlashes($tablename) . "' " .
499 " AND `version` = '" . PMA_Util::sqlAddSlashes($version) . "' ";
501 $result = PMA_queryAsControlUser($sql_query);
503 return $result;
507 * Activates tracking of a table.
509 * @param string $dbname name of database
510 * @param string $tablename name of table
511 * @param string $version version
513 * @static
515 * @return int result of SQL query
517 static public function activateTracking($dbname, $tablename, $version)
519 return self::_changeTracking($dbname, $tablename, $version, 1);
524 * Deactivates tracking of a table.
526 * @param string $dbname name of database
527 * @param string $tablename name of table
528 * @param string $version version
530 * @static
532 * @return int result of SQL query
534 static public function deactivateTracking($dbname, $tablename, $version)
536 return self::_changeTracking($dbname, $tablename, $version, 0);
541 * Gets the newest version of a tracking job
542 * (in other words: gets the HEAD version).
544 * @param string $dbname name of database
545 * @param string $tablename name of table
546 * @param string $statement tracked statement
548 * @static
550 * @return int (-1 if no version exists | > 0 if a version exists)
552 static public function getVersion($dbname, $tablename, $statement = null)
554 $sql_query = " SELECT MAX(version) FROM " . self::$pma_table .
555 " WHERE `db_name` = '" . PMA_Util::sqlAddSlashes($dbname) . "' " .
556 " AND `table_name` = '" . PMA_Util::sqlAddSlashes($tablename) . "' ";
558 if ($statement != "") {
559 $sql_query .= PMA_DRIZZLE
560 ? ' AND tracking & ' . self::_transformTrackingSet($statement) . ' <> 0'
561 : " AND FIND_IN_SET('" . $statement . "',tracking) > 0" ;
563 $row = PMA_DBI_fetch_array(PMA_queryAsControlUser($sql_query));
564 return isset($row[0])
565 ? $row[0]
566 : -1;
571 * Gets the record of a tracking job.
573 * @param string $dbname name of database
574 * @param string $tablename name of table
575 * @param string $version version number
577 * @static
579 * @return mixed record DDM log, DDL log, structure snapshot, tracked statements.
581 static public function getTrackedData($dbname, $tablename, $version)
583 if (! isset(self::$pma_table)) {
584 self::init();
586 $sql_query = " SELECT * FROM " . self::$pma_table .
587 " WHERE `db_name` = '" . PMA_Util::sqlAddSlashes($dbname) . "' ";
588 if (! empty($tablename)) {
589 $sql_query .= " AND `table_name` = '"
590 . PMA_Util::sqlAddSlashes($tablename) ."' ";
592 $sql_query .= " AND `version` = '" . PMA_Util::sqlAddSlashes($version) ."' ".
593 " ORDER BY `version` DESC LIMIT 1";
595 $mixed = PMA_DBI_fetch_assoc(PMA_queryAsControlUser($sql_query));
597 // Parse log
598 $log_schema_entries = explode('# log ', $mixed['schema_sql']);
599 $log_data_entries = explode('# log ', $mixed['data_sql']);
601 $ddl_date_from = $date = date('Y-m-d H:i:s');
603 $ddlog = array();
604 $i = 0;
606 // Iterate tracked data definition statements
607 // For each log entry we want to get date, username and statement
608 foreach ($log_schema_entries as $log_entry) {
609 if (trim($log_entry) != '') {
610 $date = substr($log_entry, 0, 19);
611 $username = substr($log_entry, 20, strpos($log_entry, "\n") - 20);
612 if ($i == 0) {
613 $ddl_date_from = $date;
615 $statement = rtrim(strstr($log_entry, "\n"));
617 $ddlog[] = array( 'date' => $date,
618 'username'=> $username,
619 'statement' => $statement );
620 $i++;
624 $date_from = $ddl_date_from;
625 $date_to = $ddl_date_to = $date;
627 $dml_date_from = $date_from;
629 $dmlog = array();
630 $i = 0;
632 // Iterate tracked data manipulation statements
633 // For each log entry we want to get date, username and statement
634 foreach ($log_data_entries as $log_entry) {
635 if (trim($log_entry) != '') {
636 $date = substr($log_entry, 0, 19);
637 $username = substr($log_entry, 20, strpos($log_entry, "\n") - 20);
638 if ($i == 0) {
639 $dml_date_from = $date;
641 $statement = rtrim(strstr($log_entry, "\n"));
643 $dmlog[] = array( 'date' => $date,
644 'username' => $username,
645 'statement' => $statement );
646 $i++;
650 $dml_date_to = $date;
652 // Define begin and end of date range for both logs
653 if (strtotime($ddl_date_from) <= strtotime($dml_date_from)) {
654 $data['date_from'] = $ddl_date_from;
655 } else {
656 $data['date_from'] = $dml_date_from;
658 if (strtotime($ddl_date_to) >= strtotime($dml_date_to)) {
659 $data['date_to'] = $ddl_date_to;
660 } else {
661 $data['date_to'] = $dml_date_to;
663 $data['ddlog'] = $ddlog;
664 $data['dmlog'] = $dmlog;
665 $data['tracking'] = self::_transformTrackingSet($mixed['tracking']);
666 $data['schema_snapshot'] = $mixed['schema_snapshot'];
668 return $data;
673 * Parses a query. Gets
674 * - statement identifier (UPDATE, ALTER TABLE, ...)
675 * - type of statement, is it part of DDL or DML ?
676 * - tablename
678 * @param string $query query
680 * @static
681 * @todo: using PMA SQL Parser when possible
682 * @todo: support multi-table/view drops
684 * @return mixed Array containing identifier, type and tablename.
687 static public function parseQuery($query)
690 // Usage of PMA_SQP does not work here
692 // require_once("libraries/sqlparser.lib.php");
693 // $parsed_sql = PMA_SQP_parse($query);
694 // $sql_info = PMA_SQP_analyze($parsed_sql);
696 $query = str_replace("\n", " ", $query);
697 $query = str_replace("\r", " ", $query);
699 $query = trim($query);
700 $query = trim($query, ' -');
702 $tokens = explode(" ", $query);
703 foreach ($tokens as $key => $value) {
704 $tokens[$key] = strtoupper($value);
707 // Parse USE statement, need it for SQL dump imports
708 if (substr($query, 0, 4) == 'USE ') {
709 $prefix = explode('USE ', $query);
710 $GLOBALS['db'] = self::getTableName($prefix[1]);
714 * DDL statements
717 $result['type'] = 'DDL';
719 // Parse CREATE VIEW statement
720 if (in_array('CREATE', $tokens) == true
721 && in_array('VIEW', $tokens) == true
722 && in_array('AS', $tokens) == true
724 $result['identifier'] = 'CREATE VIEW';
726 $index = array_search('VIEW', $tokens);
728 $result['tablename'] = strtolower(
729 self::getTableName($tokens[$index + 1])
733 // Parse ALTER VIEW statement
734 if (in_array('ALTER', $tokens) == true
735 && in_array('VIEW', $tokens) == true
736 && in_array('AS', $tokens) == true
737 && ! isset($result['identifier'])
739 $result['identifier'] = 'ALTER VIEW';
741 $index = array_search('VIEW', $tokens);
743 $result['tablename'] = strtolower(
744 self::getTableName($tokens[$index + 1])
748 // Parse DROP VIEW statement
749 if (! isset($result['identifier'])
750 && substr($query, 0, 10) == 'DROP VIEW '
752 $result['identifier'] = 'DROP VIEW';
754 $prefix = explode('DROP VIEW ', $query);
755 $str = strstr($prefix[1], 'IF EXISTS');
757 if ($str == false ) {
758 $str = $prefix[1];
760 $result['tablename'] = self::getTableName($str);
763 // Parse CREATE DATABASE statement
764 if (! isset($result['identifier'])
765 && substr($query, 0, 15) == 'CREATE DATABASE'
767 $result['identifier'] = 'CREATE DATABASE';
768 $str = str_replace('CREATE DATABASE', '', $query);
769 $str = str_replace('IF NOT EXISTS', '', $str);
771 $prefix = explode('DEFAULT ', $str);
773 $result['tablename'] = '';
774 $GLOBALS['db'] = self::getTableName($prefix[0]);
777 // Parse ALTER DATABASE statement
778 if (! isset($result['identifier'])
779 && substr($query, 0, 14) == 'ALTER DATABASE'
781 $result['identifier'] = 'ALTER DATABASE';
782 $result['tablename'] = '';
785 // Parse DROP DATABASE statement
786 if (! isset($result['identifier'])
787 && substr($query, 0, 13) == 'DROP DATABASE'
789 $result['identifier'] = 'DROP DATABASE';
790 $str = str_replace('DROP DATABASE', '', $query);
791 $str = str_replace('IF EXISTS', '', $str);
792 $GLOBALS['db'] = self::getTableName($str);
793 $result['tablename'] = '';
796 // Parse CREATE TABLE statement
797 if (! isset($result['identifier'])
798 && substr($query, 0, 12) == 'CREATE TABLE'
800 $result['identifier'] = 'CREATE TABLE';
801 $query = str_replace('IF NOT EXISTS', '', $query);
802 $prefix = explode('CREATE TABLE ', $query);
803 $suffix = explode('(', $prefix[1]);
804 $result['tablename'] = self::getTableName($suffix[0]);
807 // Parse ALTER TABLE statement
808 if (! isset($result['identifier'])
809 && substr($query, 0, 12) == 'ALTER TABLE '
811 $result['identifier'] = 'ALTER TABLE';
813 $prefix = explode('ALTER TABLE ', $query);
814 $suffix = explode(' ', $prefix[1]);
815 $result['tablename'] = self::getTableName($suffix[0]);
818 // Parse DROP TABLE statement
819 if (! isset($result['identifier'])
820 && substr($query, 0, 11) == 'DROP TABLE '
822 $result['identifier'] = 'DROP TABLE';
824 $prefix = explode('DROP TABLE ', $query);
825 $str = strstr($prefix[1], 'IF EXISTS');
827 if ($str == false ) {
828 $str = $prefix[1];
830 $result['tablename'] = self::getTableName($str);
833 // Parse CREATE INDEX statement
834 if (! isset($result['identifier'])
835 && (substr($query, 0, 12) == 'CREATE INDEX'
836 || substr($query, 0, 19) == 'CREATE UNIQUE INDEX'
837 || substr($query, 0, 20) == 'CREATE SPATIAL INDEX')
839 $result['identifier'] = 'CREATE INDEX';
840 $prefix = explode('ON ', $query);
841 $suffix = explode('(', $prefix[1]);
842 $result['tablename'] = self::getTableName($suffix[0]);
845 // Parse DROP INDEX statement
846 if (! isset($result['identifier'])
847 && substr($query, 0, 10) == 'DROP INDEX'
849 $result['identifier'] = 'DROP INDEX';
850 $prefix = explode('ON ', $query);
851 $result['tablename'] = self::getTableName($prefix[1]);
854 // Parse RENAME TABLE statement
855 if (! isset($result['identifier'])
856 && substr($query, 0, 13) == 'RENAME TABLE '
858 $result['identifier'] = 'RENAME TABLE';
859 $prefix = explode('RENAME TABLE ', $query);
860 $names = explode(' TO ', $prefix[1]);
861 $result['tablename'] = self::getTableName($names[0]);
862 $result["tablename_after_rename"] = self::getTableName($names[1]);
866 * DML statements
869 if (! isset($result['identifier'])) {
870 $result["type"] = 'DML';
872 // Parse UPDATE statement
873 if (! isset($result['identifier'])
874 && substr($query, 0, 6) == 'UPDATE'
876 $result['identifier'] = 'UPDATE';
877 $prefix = explode('UPDATE ', $query);
878 $suffix = explode(' ', $prefix[1]);
879 $result['tablename'] = self::getTableName($suffix[0]);
882 // Parse INSERT INTO statement
883 if (! isset($result['identifier'])
884 && substr($query, 0, 11) == 'INSERT INTO'
886 $result['identifier'] = 'INSERT';
887 $prefix = explode('INSERT INTO', $query);
888 $suffix = explode('(', $prefix[1]);
889 $result['tablename'] = self::getTableName($suffix[0]);
892 // Parse DELETE statement
893 if (! isset($result['identifier'])
894 && substr($query, 0, 6) == 'DELETE'
896 $result['identifier'] = 'DELETE';
897 $prefix = explode('FROM ', $query);
898 $suffix = explode(' ', $prefix[1]);
899 $result['tablename'] = self::getTableName($suffix[0]);
902 // Parse TRUNCATE statement
903 if (! isset($result['identifier'])
904 && substr($query, 0, 8) == 'TRUNCATE'
906 $result['identifier'] = 'TRUNCATE';
907 $prefix = explode('TRUNCATE', $query);
908 $result['tablename'] = self::getTableName($prefix[1]);
911 return $result;
916 * Analyzes a given SQL statement and saves tracking data.
918 * @param string $query a SQL query
920 * @static
922 * @return void
924 static public function handleQuery($query)
926 // If query is marked as untouchable, leave
927 if (strstr($query, "/*NOTRACK*/")) {
928 return;
931 if (! (substr($query, -1) == ';')) {
932 $query = $query . ";\n";
934 // Get some information about query
935 $result = self::parseQuery($query);
937 // Get database name
938 $dbname = trim(isset($GLOBALS['db']) ? $GLOBALS['db'] : '', '`');
939 // $dbname can be empty, for example when coming from Synchronize
940 // and this is a query for the remote server
941 if (empty($dbname)) {
942 return;
944 // Remove null bytes (preg_replace() is vulnerable in some
945 // PHP versions)
946 $dbname = str_replace("\0", "", $dbname);
948 // If we found a valid statement
949 if (isset($result['identifier'])) {
950 $version = self::getVersion(
951 $dbname, $result['tablename'], $result['identifier']
954 // If version not exists and auto-creation is enabled
955 if (self::$version_auto_create == true
956 && self::isTracked($dbname, $result['tablename']) == false
957 && $version == -1
959 // Create the version
961 switch ($result['identifier']) {
962 case 'CREATE TABLE':
963 self::createVersion($dbname, $result['tablename'], '1');
964 break;
965 case 'CREATE VIEW':
966 self::createVersion(
967 $dbname, $result['tablename'], '1', '', true
969 break;
970 case 'CREATE DATABASE':
971 self::createDatabaseVersion($dbname, '1', $query);
972 break;
973 } // end switch
976 // If version exists
977 if (self::isTracked($dbname, $result['tablename']) && $version != -1) {
978 if ($result['type'] == 'DDL') {
979 $save_to = 'schema_sql';
980 } elseif ($result['type'] == 'DML') {
981 $save_to = 'data_sql';
982 } else {
983 $save_to = '';
985 $date = date('Y-m-d H:i:s');
987 // Cut off `dbname`. from query
988 $query = preg_replace('/`' . $dbname . '`\s?\./', '', $query);
990 // Add log information
991 $query = self::getLogComment() . $query ;
993 // Mark it as untouchable
994 $sql_query = " /*NOTRACK*/\n"
995 . " UPDATE " . self::$pma_table
996 . " SET " . PMA_Util::backquote($save_to)
997 . " = CONCAT( " . PMA_Util::backquote($save_to) . ",'\n"
998 . PMA_Util::sqlAddSlashes($query) . "') ,"
999 . " `date_updated` = '" . $date . "' ";
1001 // If table was renamed we have to change
1002 // the tablename attribute in pma_tracking too
1003 if ($result['identifier'] == 'RENAME TABLE') {
1004 $sql_query .= ', `table_name` = \''
1005 . PMA_Util::sqlAddSlashes($result['tablename_after_rename'])
1006 . '\' ';
1009 // Save the tracking information only for
1010 // 1. the database
1011 // 2. the table / view
1012 // 3. the statements
1013 // we want to track
1014 $sql_query .=
1015 " WHERE FIND_IN_SET('" . $result['identifier'] . "',tracking) > 0" .
1016 " AND `db_name` = '" . PMA_Util::sqlAddSlashes($dbname) . "' " .
1017 " AND `table_name` = '" . PMA_Util::sqlAddSlashes($result['tablename']) . "' " .
1018 " AND `version` = '" . PMA_Util::sqlAddSlashes($version) . "' ";
1020 $result = PMA_queryAsControlUser($sql_query);
1026 * Transforms tracking set for Drizzle, which has no SET type
1028 * Converts int<>string for Drizzle, does nothing for MySQL
1030 * @param int|string $tracking_set
1032 * @return int|string
1034 static private function _transformTrackingSet($tracking_set)
1036 if (!PMA_DRIZZLE) {
1037 return $tracking_set;
1040 // init conversion array (key 3 doesn't exist in calculated array)
1041 if (isset(self::$_tracking_set_flags[3])) {
1042 // initialize flags
1043 $set = self::$_tracking_set_flags;
1044 $array = array();
1045 for ($i = 0; $i < count($set); $i++) {
1046 $flag = 1 << $i;
1047 $array[$flag] = $set[$i];
1048 $array[$set[$i]] = $flag;
1050 self::$_tracking_set_flags = $array;
1053 if (is_numeric($tracking_set)) {
1054 // int > string conversion
1055 $aflags = array();
1056 // count/2 - conversion table has both int > string
1057 // and string > int values
1058 for ($i = 0; $i < count(self::$_tracking_set_flags)/2; $i++) {
1059 $flag = 1 << $i;
1060 if ($tracking_set & $flag) {
1061 $aflags[] = self::$_tracking_set_flags[$flag];
1064 $flags = implode(',', $aflags);
1065 } else {
1066 // string > int conversion
1067 $flags = 0;
1068 foreach (explode(',', $tracking_set) as $strflag) {
1069 if ($strflag == '') {
1070 continue;
1072 $flags |= self::$_tracking_set_flags[$strflag];
1076 return $flags;