From 3f5b683456ad1eab9083f3384bf02bc040a59c2a Mon Sep 17 00:00:00 2001 From: jakubvrana Date: Wed, 21 Apr 2010 12:01:32 +0000 Subject: [PATCH] Reintegrate sqlite branch git-svn-id: https://adminer.svn.sourceforge.net/svnroot/adminer/trunk@1466 7c3ca157-0c34-0410-bff1-cbf682f78f5c --- adminer/call.inc.php | 9 +- adminer/create.inc.php | 77 ++-- adminer/database.inc.php | 52 +-- adminer/db.inc.php | 120 +++-- adminer/download.inc.php | 2 +- adminer/drivers/mssql.inc.php | 406 +++++++++++++++++ adminer/drivers/mysql.inc.php | 713 ++++++++++++++++++++++++++++++ adminer/drivers/pgsql.inc.php | 443 +++++++++++++++++++ adminer/drivers/sqlite.inc.php | 479 ++++++++++++++++++++ adminer/dump.inc.php | 78 ++-- adminer/edit.inc.php | 29 +- adminer/foreign.inc.php | 10 +- adminer/include/adminer.inc.php | 132 +++--- adminer/include/auth.inc.php | 104 +++-- adminer/include/bootstrap.inc.php | 33 +- adminer/include/connect.inc.php | 30 +- adminer/include/design.inc.php | 57 ++- adminer/include/editing.inc.php | 45 +- adminer/include/export.inc.php | 21 +- adminer/include/functions.inc.php | 151 +++++-- adminer/include/mysql.inc.php | 406 ----------------- adminer/include/pdo.inc.php | 20 +- adminer/include/version.inc.php | 2 +- adminer/index.php | 2 +- adminer/indexes.inc.php | 20 +- adminer/lang/cs.inc.php | 12 +- adminer/lang/de.inc.php | 9 +- adminer/lang/es.inc.php | 9 +- adminer/lang/et.inc.php | 9 +- adminer/lang/fr.inc.php | 9 +- adminer/lang/it.inc.php | 9 +- adminer/lang/nl.inc.php | 9 +- adminer/lang/ru.inc.php | 9 +- adminer/lang/sk.inc.php | 9 +- adminer/lang/zh-tw.inc.php | 9 +- adminer/lang/zh.inc.php | 9 +- adminer/privileges.inc.php | 3 +- adminer/schema.inc.php | 4 +- adminer/select.inc.php | 114 +++-- adminer/sql.inc.php | 27 +- adminer/static/default.css | 14 +- adminer/static/editing.js | 5 +- adminer/static/favicon.ico | Bin 318 -> 318 bytes adminer/static/functions.js | 17 +- adminer/table.inc.php | 24 +- adminer/trigger.inc.php | 7 +- adminer/user.inc.php | 2 +- adminer/view.inc.php | 2 +- changes.txt | 10 + compile.php | 49 +- coverage.php | 2 +- editor/db.inc.php | 4 +- editor/include/adminer.inc.php | 142 +++--- editor/include/editing.inc.php | 40 +- editor/index.php | 3 +- lang.php | 1 + tests/0-login.html | 4 +- tests/1-create-database.html | 2 +- tests/10-clone.html | 2 +- tests/11-reference.html | 2 +- tests/12-update.html | 2 +- tests/13-delete.html | 2 +- tests/14-truncate.html | 2 +- tests/15-privileges.html | 2 +- tests/16-processlist.html | 2 +- tests/17-export.html | 2 +- tests/18-events.html | 2 +- tests/19-procedures.html | 2 +- tests/2-create-table.html | 2 +- tests/20-partitioning.html | 2 +- tests/21-variables.html | 2 +- tests/22-history.html | 2 +- tests/23-editor.html | 2 +- tests/24-explain.html | 2 +- tests/3-create-index.html | 2 +- tests/4-create-table-2.html | 2 +- tests/5-foreign-key.html | 2 +- tests/6-alter-table.html | 2 +- tests/7-create-trigger.html | 2 +- tests/8-create-view.html | 2 +- tests/9-insert.html | 4 +- tests/logout.html | 4 +- todo.txt | 32 +- version.js | 2 +- 84 files changed, 3010 insertions(+), 1095 deletions(-) create mode 100644 adminer/drivers/mssql.inc.php create mode 100644 adminer/drivers/mysql.inc.php create mode 100644 adminer/drivers/pgsql.inc.php create mode 100644 adminer/drivers/sqlite.inc.php delete mode 100644 adminer/include/mysql.inc.php diff --git a/adminer/call.inc.php b/adminer/call.inc.php index 039868d8..2c781968 100644 --- a/adminer/call.inc.php +++ b/adminer/call.inc.php @@ -28,7 +28,9 @@ if (!$error && $_POST) { } $call[] = (isset($out[$key]) ? "@" . idf_escape($field["field"]) : $val); } - if (!$connection->multi_query((isset($_GET["callf"]) ? "SELECT" : "CALL") . " " . idf_escape($PROCEDURE) . "(" . implode(", ", $call) . ")")) { + $query = (isset($_GET["callf"]) ? "SELECT" : "CALL") . " " . idf_escape($PROCEDURE) . "(" . implode(", ", $call) . ")"; + echo "

" . h($query) . " " . lang('Edit') . "\n"; + if (!$connection->multi_query($query)) { echo "

" . error() . "\n"; } else { do { @@ -52,8 +54,9 @@ if ($in) { echo "\n"; foreach ($in as $key) { $field = $routine["fields"][$key]; - echo "\n"; - foreach (get_databases() as $db) { - if (!information_schema($db)) { - $prefix = ereg_replace("_.*", "", $db); - echo "
" . h($field["field"]); - $value = $_POST["fields"][$key]; + $name = $field["field"]; + echo "
" . h($name); + $value = $_POST["fields"][$name]; if ($value != "" && ereg("enum|set", $field["type"])) { $value = intval($value); } diff --git a/adminer/create.inc.php b/adminer/create.inc.php index e9fd1d0d..b1d4d4b9 100644 --- a/adminer/create.inc.php +++ b/adminer/create.inc.php @@ -19,20 +19,8 @@ if ($_POST && !$error && !$_POST["add"] && !$_POST["drop_col"] && !$_POST["up"] if ($_POST["drop"]) { query_redirect("DROP TABLE " . idf_escape($_GET["create"]), substr(ME, 0, -1), lang('Table has been dropped.')); } else { - $auto_increment_index = " PRIMARY KEY"; - // don't overwrite primary key by auto_increment - if ($TABLE != "" && $_POST["auto_increment_col"]) { - foreach (indexes($TABLE) as $index) { - if (in_array($_POST["fields"][$_POST["auto_increment_col"]]["orig"], $index["columns"], true)) { - $auto_increment_index = ""; - break; - } - if ($index["type"] == "PRIMARY") { - $auto_increment_index = " UNIQUE"; - } - } - } - $fields = ""; + $fields = array(); + $foreign = array(); ksort($_POST["fields"]); $orig_field = reset($orig_fields); $after = "FIRST"; @@ -48,33 +36,26 @@ if ($_POST && !$error && !$_POST["add"] && !$_POST["drop_col"] && !$_POST["up"] $field["on_update"] = "CURRENT_TIMESTAMP"; $field["default"] = $default; } + if ($key == $_POST["auto_increment_col"]) { + $field["auto_increment"] = true; + } $process_field = process_field($field, $type_field); - $auto_increment = ($key == $_POST["auto_increment_col"]); - if ($process_field != process_field($orig_field, $orig_field) || $orig_field["auto_increment"] != $auto_increment) { - $fields .= "\n" . ($TABLE != "" ? ($field["orig"] != "" ? "CHANGE " . idf_escape($field["orig"]) : "ADD") : " ") - . " $process_field" - . ($auto_increment ? " AUTO_INCREMENT$auto_increment_index" : "") - . ($TABLE != "" ? " $after" : "") . "," - ; + if ($process_field != process_field($orig_field, $orig_field)) { + $fields[] = array($field["orig"], $process_field, $after); } if (!isset($types[$field["type"]])) { - $fields .= ($TABLE != "" ? "\nADD" : "") . " FOREIGN KEY (" . idf_escape($field["field"]) . ") REFERENCES " . idf_escape($foreign_keys[$field["type"]]) . " (" . idf_escape($type_field["field"]) . "),"; + $foreign[] = ($TABLE != "" ? "ADD " : " ") . "FOREIGN KEY (" . idf_escape($field["field"]) . ") REFERENCES " . idf_escape($foreign_keys[$field["type"]]) . " (" . idf_escape($type_field["field"]) . ")"; } } $after = "AFTER " . idf_escape($field["field"]); - //! drop and create foreign keys with renamed columns } elseif ($field["orig"] != "") { - $fields .= "\nDROP " . idf_escape($field["orig"]) . ","; + $fields[] = array($field["orig"]); } if ($field["orig"] != "") { $orig_field = next($orig_fields); } } - $status = "COMMENT=" . $connection->quote($_POST["Comment"]) - . ($_POST["Engine"] && $_POST["Engine"] != $orig_status["Engine"] ? " ENGINE=" . $connection->quote($_POST["Engine"]) : "") - . ($_POST["Collation"] && $_POST["Collation"] != $orig_status["Collation"] ? " COLLATE " . $connection->quote($_POST["Collation"]) : "") - . ($_POST["Auto_increment"] != "" ? " AUTO_INCREMENT=" . preg_replace('~[^0-9]+~', '', $_POST["Auto_increment"]) : "") - ; + $partitioning = ""; if (in_array($_POST["partition_by"], $partition_by)) { $partitions = array(); if ($_POST["partition_by"] == 'RANGE' || $_POST["partition_by"] == 'LIST') { @@ -83,20 +64,29 @@ if ($_POST && !$error && !$_POST["add"] && !$_POST["drop_col"] && !$_POST["up"] $partitions[] = "\nPARTITION " . idf_escape($val) . " VALUES " . ($_POST["partition_by"] == 'RANGE' ? "LESS THAN" : "IN") . ($value != "" ? " ($value)" : " MAXVALUE"); //! SQL injection } } - $status .= "\nPARTITION BY $_POST[partition_by]($_POST[partition])" . ($partitions // $_POST["partition"] can be expression, not only column + $partitioning .= "\nPARTITION BY $_POST[partition_by]($_POST[partition])" . ($partitions // $_POST["partition"] can be expression, not only column ? " (" . implode(",", $partitions) . "\n)" : ($_POST["partitions"] ? " PARTITIONS " . intval($_POST["partitions"]) : "") ); - } elseif ($connection->server_info >= 5.1 && $TABLE != "") { - $status .= "\nREMOVE PARTITIONING"; + } elseif ($TABLE != "") { + $partitioning .= "\nREMOVE PARTITIONING"; } - $location = ME . "table=" . urlencode($_POST["name"]); - if ($TABLE != "") { - query_redirect("ALTER TABLE " . idf_escape($TABLE) . "$fields\nRENAME TO " . idf_escape($_POST["name"]) . ",\n$status", $location, lang('Table has been altered.')); - } else { + $message = lang('Table has been altered.'); + if ($TABLE == "") { cookie("adminer_engine", $_POST["Engine"]); - query_redirect("CREATE TABLE " . idf_escape($_POST["name"]) . " (" . substr($fields, 0, -1) . "\n) $status", $location, lang('Table has been created.')); + $message = lang('Table has been created.'); } + queries_redirect(ME . "table=" . urlencode($_POST["name"]), $message, alter_table( + $TABLE, + $_POST["name"], + $fields, + $foreign, + $_POST["Comment"], + ($_POST["Engine"] && $_POST["Engine"] != $orig_status["Engine"] ? $_POST["Engine"] : ""), + ($_POST["Collation"] && $_POST["Collation"] != $orig_status["Collation"] ? $_POST["Collation"] : ""), + ($_POST["Auto_increment"] != "" ? preg_replace('~[^0-9]+~', '', $_POST["Auto_increment"]) : ""), + $partitioning + )); } } @@ -127,9 +117,9 @@ if ($_POST) { } $row["fields"][] = $field; } - if ($connection->server_info >= 5.1) { + if (support("partitioning")) { $from = "FROM information_schema.PARTITIONS WHERE TABLE_SCHEMA = " . $connection->quote(DB) . " AND TABLE_NAME = " . $connection->quote($TABLE); - $result = $connection->query("SELECT PARTITION_METHOD, PARTITION_ORDINAL_POSITION, PARTITION_EXPRESSION $from ORDER BY PARTITION_ORDINAL_POSITION DESC LIMIT 1"); + $result = $connection->query("SELECT" . limit("PARTITION_METHOD, PARTITION_ORDINAL_POSITION, PARTITION_EXPRESSION $from ORDER BY PARTITION_ORDINAL_POSITION", 1)); list($row["partition_by"], $row["partitions"], $row["partition"]) = $result->fetch_row(); $row["partition_names"] = array(); $row["partition_values"] = array(); @@ -162,24 +152,23 @@ foreach ($engines as $engine) {

: "> "(" . lang('engine') . ")") + $engines, $row["Engine"]) : ""); ?> - "(" . lang('collation') . ")") + $collations, $row["Collation"]); ?> + "(" . lang('collation') . ")") + $collations, $row["Collation"]) : ""); ?> - +

: "> -: " maxlength="60"> +' : ''); ?>

> server_info >= 5.1) { +if (support("partitioning")) { $partition_table = ereg('RANGE|LIST', $row["partition_by"]); print_fieldset("partition", lang('Partition by'), $row["partition_by"]); ?> diff --git a/adminer/database.inc.php b/adminer/database.inc.php index 80e4b65e..ece59158 100644 --- a/adminer/database.inc.php +++ b/adminer/database.inc.php @@ -2,35 +2,26 @@ if ($_POST && !$error && !isset($_POST["add_x"])) { // add is an image and PHP changes add.x to add_x restart_session(); if ($_POST["drop"]) { - unset($_SESSION["databases"][$_GET["server"]]); + set_session("databases", null); query_redirect("DROP DATABASE " . idf_escape(DB), remove_from_uri("db|database"), lang('Database has been dropped.')); } elseif (DB !== $_POST["name"]) { // create or rename database - unset($_SESSION["databases"][$_GET["server"]]); // clear cache - $dbs = explode("\n", str_replace("\r", "", $_POST["name"])); - $failed = false; - $last = ""; - foreach ($dbs as $db) { - if (count($dbs) == 1 || $db != "") { // ignore empty lines but always try to create single database - if (!queries("CREATE DATABASE " . idf_escape($db) . ($_POST["collation"] ? " COLLATE " . $connection->quote($_POST["collation"]) : ""))) { - $failed = true; - } - $last = $db; - } - } - if (query_redirect(queries(), ME . "db=" . urlencode($last), lang('Database has been created.'), DB == "", false, $failed)) { - //! move triggers - $result = $connection->query("SHOW TABLES"); - while ($row = $result->fetch_row()) { - if (!queries("RENAME TABLE " . idf_escape($row[0]) . " TO " . idf_escape($_POST["name"]) . "." . idf_escape($row[0]))) { - break; + set_session("databases", null); // clear cache + if (DB != "") { + queries_redirect(preg_replace('~db=[^&]*&~', '', ME) . "db=" . urlencode($_POST["name"]), lang('Database has been renamed.'), rename_database($_POST["name"], $_POST["collation"])); + } else { + $dbs = explode("\n", str_replace("\r", "", $_POST["name"])); + $success = true; + $last = ""; + foreach ($dbs as $db) { + if (count($dbs) == 1 || $db != "") { // ignore empty lines but always try to create single database + if (!queries("CREATE DATABASE " . idf_escape($db) . ($_POST["collation"] ? " COLLATE " . $connection->quote($_POST["collation"]) : ""))) { + $success = false; + } + $last = $db; } } - if (!$row) { - queries("DROP DATABASE " . idf_escape(DB)); - //! saved to history of removed database - } - queries_redirect(preg_replace('~db=[^&]*&~', '', ME) . "db=" . urlencode($_POST["name"]), lang('Database has been renamed.'), !$row); + queries_redirect(ME . "db=" . urlencode($last), lang('Database has been created.'), $success); } } else { // alter database @@ -49,17 +40,16 @@ $collate = null; if ($_POST) { $name = $_POST["name"]; $collate = $_POST["collation"]; -} elseif (DB == "") { +} elseif (DB != "") { + $collate = db_collation(DB, $collations); +} elseif ($driver == "sql") { // propose database name with limited privileges - $result = $connection->query("SHOW GRANTS"); - while ($row = $result->fetch_row()) { - if (preg_match('~ ON (`(([^\\\\`]|``|\\\\.)*)%`\\.\\*)?~', $row[0], $match) && $match[1]) { - $name = stripcslashes(idf_unescape($match[2])); + foreach (get_vals("SHOW GRANTS") as $grant) { + if (preg_match('~ ON (`(([^\\\\`]|``|\\\\.)*)%`\\.\\*)?~', $grant, $match) && $match[1]) { + $name = stripcslashes(idf_unescape("`$match[2]`")); break; } } -} else { - $collate = db_collation(DB, $collations); } ?> diff --git a/adminer/db.inc.php b/adminer/db.inc.php index 6f22c207..ef197347 100644 --- a/adminer/db.inc.php +++ b/adminer/db.inc.php @@ -4,18 +4,15 @@ $tables_views = array_merge((array) $_POST["tables"], (array) $_POST["views"]); if ($tables_views && !$error && !$_POST["search"]) { $result = true; $message = ""; - if (count($_POST["tables"]) > 1 && ($_POST["drop"] || $_POST["truncate"])) { + if ($driver == "sql" && count($_POST["tables"]) > 1 && ($_POST["drop"] || $_POST["truncate"])) { queries("SET foreign_key_checks = 0"); // allows to truncate or drop several tables at once } - if (isset($_POST["truncate"])) { - foreach ((array) $_POST["tables"] as $table) { - if (!queries("TRUNCATE " . idf_escape($table))) { - $result = false; - break; - } + if ($_POST["truncate"]) { + if ($_POST["tables"]) { + $result = truncate_tables($_POST["tables"]); } $message = lang('Tables have been truncated.'); - } elseif (isset($_POST["move"])) { + } elseif ($_POST["move"]) { $rename = array(); foreach ($tables_views as $table) { $rename[] = idf_escape($table) . " TO " . idf_escape($_POST["target"]) . "." . idf_escape($table); @@ -23,66 +20,65 @@ if ($tables_views && !$error && !$_POST["search"]) { $result = queries("RENAME TABLE " . implode(", ", $rename)); //! move triggers $message = lang('Tables have been moved.'); - } elseif ((!isset($_POST["drop"]) || !$_POST["views"] || queries("DROP VIEW " . implode(", ", array_map('idf_escape', $_POST["views"])))) - && (!$_POST["tables"] || ($result = queries((isset($_POST["optimize"]) ? "OPTIMIZE" : (isset($_POST["check"]) ? "CHECK" : (isset($_POST["repair"]) ? "REPAIR" : (isset($_POST["drop"]) ? "DROP" : "ANALYZE")))) . " TABLE " . implode(", ", array_map('idf_escape', $_POST["tables"]))))) - ) { - if (isset($_POST["drop"])) { - $message = lang('Tables have been dropped.'); - } else { - while ($row = $result->fetch_assoc()) { - $message .= h("$row[Table]: $row[Msg_text]") . "
"; - } + } elseif ($_POST["drop"]) { + if ($_POST["views"]) { + $result = drop_views($_POST["views"]); + } + if ($result && $_POST["tables"]) { + $result = drop_tables($_POST["tables"]); + } + $message = lang('Tables have been dropped.'); + } elseif ($_POST["tables"] && ($result = queries(($_POST["optimize"] ? "OPTIMIZE" : ($_POST["check"] ? "CHECK" : ($_POST["repair"] ? "REPAIR" : "ANALYZE"))) . " TABLE " . implode(", ", array_map('idf_escape', $_POST["tables"]))))) { + while ($row = $result->fetch_assoc()) { + $message .= h("$row[Table]: $row[Msg_text]") . "
"; } } queries_redirect(substr(ME, 0, -1), $message, $result); } -page_header(lang('Database') . ": " . h(DB), $error, false); +page_header(lang('Database') . ": " . h(DB), $error, true); echo '

' . lang('Alter database') . "\n"; echo '' . lang('Database schema') . "\n"; +$sums = array("Data_length" => 0, "Index_length" => 0, "Data_free" => 0); echo "

" . lang('Tables and views') . "

\n"; -$table_status = table_status(); -if (!$table_status) { +$tables_list = tables_list(); +if (!$tables_list) { echo "

" . lang('No tables.') . "\n"; } else { echo "

\n"; echo "

\n"; if ($_POST["search"] && $_POST["query"] != "") { - $_GET["where"][0]["op"] = "LIKE"; - $_GET["where"][0]["val"] = "%$_POST[query]%"; + $_GET["where"][0]["op"] = "LIKE %%"; + $_GET["where"][0]["val"] = $_POST["query"]; search_tables(); } echo "\n"; - echo '\n"; - $sums = array(); - foreach ($table_status as $row) { - $name = $row["Name"]; - echo '\n"; + foreach ($tables_list as $name => $type) { + $view = (isset($type) && !eregi("table", $type)); + echo '
' . lang('Table') . '' . lang('Engine') . '' . lang('Collation') . '' . lang('Data Length') . '' . lang('Index Length') . '' . lang('Data Free') . '' . lang('Auto Increment') . '' . lang('Rows') . '' . lang('Comment') . "
' . checkbox((isset($row["Rows"]) ? "tables[]" : "views[]"), $name, in_array($name, $tables_views, true), "", "formUncheck('check-all');"); + echo '
' . lang('Table') . '' . lang('Engine') . '' . lang('Collation') . '' . lang('Data Length') . '' . lang('Index Length') . '' . lang('Data Free') . '' . lang('Auto Increment') . '' . lang('Rows') . (support("comment") ? '' . lang('Comment') : '') . "
' . checkbox(($view ? "views[]" : "tables[]"), $name, in_array($name, $tables_views, true), "", "formUncheck('check-all');"); echo '' . h($name) . ''; - if (isset($row["Rows"])) { - echo "$row[Engine]$row[Collation]"; - foreach (array("Data_length" => "create", "Index_length" => "indexes", "Data_free" => "edit", "Auto_increment" => "auto_increment=1&create", "Rows" => "select") as $key => $link) { - $val = number_format($row[$key], 0, '.', lang(',')); - echo '' . ($row[$key] != "" ? '' . str_replace(" ", " ", ($key == "Rows" && $row["Engine"] == "InnoDB" && $val ? lang('~ %s', $val) : $val)) . '' : ' '); - $sums[$link] += ($row["Engine"] != "InnoDB" || $link != "edit" ? $row[$key] : 0); - } - echo "" . nbsp($row["Comment"]); - } else { + if ($view) { echo '' . lang('View') . ''; echo '?'; - echo ' '; + } else { + echo "  "; + foreach (array("Data_length" => "create", "Index_length" => "indexes", "Data_free" => "edit", "Auto_increment" => "auto_increment=1&create", "Rows" => "select") as $key => $link) { + echo "?"; + } } + echo (support("comment") ? " " : ""); } - echo "
 " . lang('%d in total', count($table_status)); - echo "" . $connection->result($connection->query("SELECT @@storage_engine")); + echo "
 " . lang('%d in total', count($tables_list)); + echo "" . $connection->result("SELECT @@storage_engine"); echo "" . db_collation(DB, collations()); - foreach (array("create", "indexes", "edit") as $val) { - echo "" . number_format($sums[$val], 0, '.', lang(',')); + foreach ($sums as $key => $val) { + echo " "; } echo "
\n"; if (!information_schema(DB)) { - echo "

\n"; + echo "

" . ($driver == "sql" ? " " : "") . " \n"; $dbs = get_databases(); if (count($dbs) != 1) { $db = (isset($_POST["target"]) ? $_POST["target"] : DB); @@ -93,8 +89,10 @@ if (!$table_status) { } echo '

' . lang('Create table') . "\n"; -if ($connection->server_info >= 5) { +if (support("view")) { echo '' . lang('Create view') . "\n"; +} +if (support("routine")) { echo "

" . lang('Routines') . "

\n"; $result = $connection->query("SELECT * FROM information_schema.ROUTINES WHERE ROUTINE_SCHEMA = " . $connection->quote(DB)); if ($result->num_rows) { @@ -110,9 +108,10 @@ if ($connection->server_info >= 5) { echo '

' . lang('Create procedure') . ' ' . lang('Create function') . "\n"; } -if ($connection->server_info >= 5.1 && ($result = $connection->query("SHOW EVENTS"))) { +if (support("event")) { echo "

" . lang('Events') . "

\n"; - if ($result->num_rows) { + $result = $connection->query("SHOW EVENTS"); + if ($result && $result->num_rows) { echo "\n"; echo "\n"; while ($row = $result->fetch_assoc()) { @@ -125,3 +124,34 @@ if ($connection->server_info >= 5.1 && ($result = $connection->query("SHOW EVENT } echo '

' . lang('Create event') . "\n"; } + +page_footer(); +$table_status = table_status(); +if ($table_status) { + echo "\n"; +} +exit; // page_footer() already called diff --git a/adminer/download.inc.php b/adminer/download.inc.php index dcf39415..faa28ce4 100644 --- a/adminer/download.inc.php +++ b/adminer/download.inc.php @@ -2,5 +2,5 @@ $TABLE = $_GET["download"]; header("Content-Type: application/octet-stream"); header("Content-Disposition: attachment; filename=" . friendly_url("$TABLE-" . implode("_", $_GET["where"])) . "." . friendly_url($_GET["field"])); -echo $connection->result($connection->query("SELECT " . idf_escape($_GET["field"]) . " FROM " . idf_escape($TABLE) . " WHERE " . where($_GET) . " LIMIT 1")); +echo $connection->result("SELECT" . limit(idf_escape($_GET["field"]) . " FROM " . idf_escape($TABLE) . " WHERE " . where($_GET), 1)); exit; // don't output footer diff --git a/adminer/drivers/mssql.inc.php b/adminer/drivers/mssql.inc.php new file mode 100644 index 00000000..98f38e99 --- /dev/null +++ b/adminer/drivers/mssql.inc.php @@ -0,0 +1,406 @@ +error = ""; + foreach (sqlsrv_errors() as $error) { + $this->error .= "$error[message]\n"; + } + $this->error = rtrim($this->error); + } + + function connect($server, $username, $password) { + $this->_link = @sqlsrv_connect($server, array("UID" => $username, "PWD" => $password)); + if ($this->_link) { + $info = sqlsrv_server_info($this->_link); + $this->server_info = $info['SQLServerVersion']; + } else { + $this->_get_error(); + } + return (bool) $this->_link; + } + + function quote($string) { + return "'" . str_replace("'", "''", $string) . "'"; + } + + function select_db($database) { + return $this->query("USE $database"); + } + + + function query($query, $unbuffered = false) { + $result = sqlsrv_query($this->_link, $query); //! , array(), ($unbuffered ? array() : array("Scrollable" => "keyset")) + if (!$result) { + $this->_get_error(); + return false; + } + return $this->store_result($result); + } + + function multi_query($query) { + $this->_result = sqlsrv_query($this->_link, $query); + if (!$this->_result) { + $this->_get_error(); + return false; + } + return true; + } + + function store_result($result = null) { + if (!$result) { + $result = $this->_result; + } + if (sqlsrv_field_metadata($result)) { + return new Min_Result($result); + } + $this->affected_rows = sqlsrv_rows_affected($result); + return true; + } + + function next_result() { + return sqlsrv_next_result($this->_result); + } + + function result($query, $field = 0) { + $result = $this->query($query); + if (!is_object($result)) { + return false; + } + $row = $result->fetch_row(); + return $row[$field]; + } + } + + class Min_Result { + var $_result, $_offset = 0, $_fields, $num_rows; + + function Min_Result($result) { + $this->_result = $result; + $this->num_rows = sqlsrv_has_rows($result); //! sqlsrv_num_rows($result) + } + + function _convert($row) { + foreach ((array) $row as $key => $val) { + if (is_a($val, 'DateTime')) { + $row[$key] = $val->format("Y-m-d H:i:s"); + } + //! stream + } + return $row; + } + + function fetch_assoc() { + return $this->_convert(sqlsrv_fetch_array($this->_result, SQLSRV_FETCH_ASSOC, SQLSRV_SCROLL_NEXT)); + } + + function fetch_row() { + return $this->_convert(sqlsrv_fetch_array($this->_result, SQLSRV_FETCH_NUMERIC, SQLSRV_SCROLL_NEXT)); + } + + function fetch_field() { + if (!$this->_fields) { + $this->_fields = sqlsrv_field_metadata($this->_result); + } + $field = $this->_fields[$this->_offset++]; + $return = new stdClass; + $return->name = $field["Name"]; + $return->orgname = $field["Name"]; + $return->type = ($field["Type"] == 1 ? 254 : 0); + return $return; + } + + function __destruct() { + sqlsrv_free_stmt($this->_result); + } + } + + } elseif (extension_loaded("mssql")) { + class Min_DB { + var $extension = "MSSQL", $_link, $_result, $server_info, $affected_rows, $error; + + function connect($server, $username, $password) { + $this->_link = @mssql_connect($server, $username, $password); + if ($this->_link) { + $result = $this->query("SELECT SERVERPROPERTY('ProductLevel'), SERVERPROPERTY('Edition')"); + $row = $result->fetch_row(); + $this->server_info = $this->result("sp_server_info 2", 2)." [$row[0]] $row[1]"; + } else { + $this->error = mssql_get_last_message(); + } + return (bool) $this->_link; + } + + function quote($string) { + return "'" . str_replace("'", "''", $string) . "'"; + } + + function select_db($database) { + return mssql_select_db($database); + } + + function query($query, $unbuffered = false) { + $result = mssql_query($query, $this->_link); //! $unbuffered + if (!$result) { + $this->error = mssql_get_last_message(); + return false; + } + if ($result === true) { + $this->affected_rows = mssql_rows_affected($this->_link); + return true; + } + return new Min_Result($result); + } + + function multi_query($query) { + return $this->_result = $this->query($query); + } + + function store_result() { + return $this->_result; + } + + function next_result() { + return mssql_next_result($this->_result); + } + + function result($query, $field = 0) { + $result = $this->query($query); + if (!is_object($result)) { + return false; + } + return mssql_result($result->_result, 0, $field); + } + } + + class Min_Result { + var $_result, $_offset = 0, $_fields, $num_rows; + + function Min_Result($result) { + $this->_result = $result; + $this->num_rows = mssql_num_rows($result); + } + + function fetch_assoc() { + return mssql_fetch_assoc($this->_result); + } + + function fetch_row() { + return mssql_fetch_row($this->_result); + } + + function num_rows() { + return mssql_num_rows($this->_result); + } + + function fetch_field() { + $return = mssql_fetch_field($this->_result); + $return->orgtable = $return->table; + $return->orgname = $return->name; + return $return; + } + + function __destruct() { + mssql_free_result($this->_result); + } + } + + } + + function idf_escape($idf) { + return "[" . str_replace("]", "]]", $idf) . "]"; + } + + function connect() { + global $adminer; + $connection = new Min_DB; + $credentials = $adminer->credentials(); + if ($connection->connect($credentials[0], $credentials[1], $credentials[2])) { + return $connection; + } + return $connection->error; + } + + function get_databases() { + return get_vals("EXEC sp_databases"); + } + + function limit($query, $limit, $offset = 0) { + return (isset($limit) ? " TOP ($limit)" : "") . " $query"; //! offset + } + + function limit1($query, $limit, $offset = 0) { + return limit($query, 1); + } + + function db_collation($db, $collations) { + global $connection; + return $connection->result("SELECT collation_name FROM sys.databases WHERE name = " . $connection->quote($db)); + } + + function engines() { + return array(); + } + + function logged_user() { + global $connection; + return $connection->result("SELECT SUSER_NAME()"); + } + + function tables_list() { + return get_key_vals("SELECT TABLE_NAME, TABLE_TYPE FROM information_schema.TABLES"); + } + + function count_tables($databases) { + global $connection; + $return = array(); + foreach ($databases as $db) { + $connection->select_db($db); + $return[$db] = $connection->result("SELECT COUNT(*) FROM information_schema.TABLES"); + } + return $return; + } + + function table_status($name = "") { + global $connection; + $return = array(); + $result = $connection->query("SELECT TABLE_NAME AS Name, TABLE_TYPE AS Engine FROM information_schema.TABLES" . ($name != "" ? " WHERE TABLE_NAME = " . $connection->quote($name) : "")); + while ($row = $result->fetch_assoc()) { + if ($name != "") { + return $row; + } + $return[$row["Name"]] = $row; + } + return $return; + } + + function fk_support($table_status) { + return true; + } + + function fields($table) { + global $connection; + $return = array(); + $result = $connection->query("SELECT * FROM information_schema.COLUMNS WHERE TABLE_NAME = " . $connection->quote($table)); + while ($row = $result->fetch_assoc()) { + $return[$row["COLUMN_NAME"]] = array( + "field" => $row["COLUMN_NAME"], + "full_type" => $row["DATA_TYPE"], + "type" => $row["DATA_TYPE"], + "length" => $row["CHARACTER_MAXIMUM_LENGTH"], //! NUMERIC_, DATETIME_? + "default" => $row["COLUMN_DEFAULT"], + "null" => ($row["IS_NULLABLE"] == "YES"), + "collation" => $row["COLLATION_NAME"], + "privileges" => array("insert" => 1, "select" => 1, "update" => 1), + //! primary - is_identity in sys.columns + ); + } + return $return; + } + + function indexes($table, $connection2 = null) { + global $connection; + if (!is_object($connection2)) { + $connection2 = $connection; + } + $return = array(); + // sp_statistics doesn't return information about primary key + $result = $connection2->query("SELECT indexes.name, key_ordinal, is_unique, is_primary_key, columns.name AS column_name +FROM sys.indexes +INNER JOIN sys.index_columns ON indexes.object_id = index_columns.object_id AND indexes.index_id = index_columns.index_id +INNER JOIN sys.columns ON index_columns.object_id = columns.object_id AND index_columns.column_id = columns.column_id +WHERE OBJECT_NAME(indexes.object_id) = " . $connection2->quote($table)); + if ($result) { + while ($row = $result->fetch_assoc()) { + $return[$row["name"]]["type"] = ($row["is_primary_key"] ? "PRIMARY" : ($row["is_unique"] ? "UNIQUE" : "INDEX")); + $return[$row["name"]]["columns"][$row["key_ordinal"]] = $row["column_name"]; + } + } + return $return; + } + + function collations() { + $return = array(); + foreach (get_vals("SELECT name FROM fn_helpcollations()") as $collation) { + $return[ereg_replace("_.*", "", $collation)][] = $collation; + } + return $return; + } + + function information_schema($db) { + return false; + } + + function error() { + global $connection; + return nl_br(h(ereg_replace("^(\\[[^]]*])+", "", $connection->error))); + } + + function exact_value($val) { + global $connection; + return $connection->quote($val); + } + + function rename_database($name, $collation) { + if ($collation) { + queries("ALTER DATABASE " . idf_escape(DB) . " COLLATE " . idf_escape($collation)); + } + return queries("ALTER DATABASE " . idf_escape(DB) . " MODIFY NAME = " . idf_escape($name)); //! false negative "The database name 'test2' has been set." + } + + function auto_increment() { + return " IDENTITY"; + } + + function explain($connection, $query) { + $connection->query("SET SHOWPLAN_ALL ON"); + $return = $connection->query($query); + $connection->query("SET SHOWPLAN_ALL OFF"); // connection is used also for indexes + return $return; + } + + function support($feature) { + return ereg('^(view|routine|trigger)$', $feature); + } + + $driver = "mssql"; + $types = array(); + $structured_types = array(); + foreach (array( + lang('Numbers') => array("tinyint" => 3, "smallint" => 5, "int" => 10, "bigint" => 20, "bit" => 1, "decimal" => 0, "real" => 12, "float" => 53, "smallmoney" => 10, "money" => 20), + lang('Date and time') => array("date" => 10, "smalldatetime" => 19, "datetime" => 19, "datetime2" => 19, "time" => 8, "datetimeoffset" => 10), + lang('Strings') => array("char" => 8000, "varchar" => 8000, "text" => 2147483647, "nchar" => 4000, "nvarchar" => 4000, "ntext" => 1073741823), + lang('Binary') => array("binary" => 8000, "varbinary" => 8000, "image" => 2147483647), + ) as $key => $val) { + $types += $val; + $structured_types[$key] = array_keys($val); + } + $unsigned = array(); + $operators = array("=", "<", ">", "<=", ">=", "!=", "LIKE", "LIKE %%", "IN", "IS NULL", "NOT LIKE", "NOT IN", "IS NOT NULL"); + $functions = array("len", "lower", "round", "upper"); + $grouping = array("avg", "count", "count distinct", "max", "min", "sum"); + $edit_functions = array( + array( + "date|time" => "getdate", + ), array( + "int|decimal|real|float|money|datetime" => "+/-", + "char|text" => "+", + ) + ); +} diff --git a/adminer/drivers/mysql.inc.php b/adminer/drivers/mysql.inc.php new file mode 100644 index 00000000..f6006744 --- /dev/null +++ b/adminer/drivers/mysql.inc.php @@ -0,0 +1,713 @@ + "MySQL") + $drivers; +} + +if (!defined("DRIVER")) { + define("DRIVER", "server"); // server - backwards compatibility + // MySQLi supports everything, MySQL doesn't support multiple result sets, PDO_MySQL doesn't support orgtable + if (extension_loaded("mysqli")) { + class Min_DB extends MySQLi { + var $extension = "MySQLi"; + + function Min_DB() { + parent::init(); + } + + function connect($server, $username, $password) { + list($host, $port) = explode(":", $server, 2); // part after : is used for port or socket + return @$this->real_connect( + ($server != "" ? $host : ini_get("mysqli.default_host")), + ("$server$username" != "" ? $username : ini_get("mysqli.default_user")), + ("$server$username$password" != "" ? $password : ini_get("mysqli.default_pw")), + null, + (is_numeric($port) ? $port : ini_get("mysqli.default_port")), + (!is_numeric($port) ? $port : null) + ); + } + + function result($query, $field = 0) { + $result = $this->query($query); + if (!$result) { + return false; + } + $row = $result->fetch_array(); + return $row[$field]; + } + + function quote($string) { + return "'" . $this->escape_string($string) . "'"; + } + } + + } elseif (extension_loaded("mysql")) { + class Min_DB { + var + $extension = "MySQL", ///< @var string extension name + $server_info, ///< @var string server version + $affected_rows, ///< @var int number of affected rows + $error, ///< @var string last error message + $_link, $_result ///< @access private + ; + + /** Connect to server + * @param string + * @param string + * @param string + * @return bool + */ + function connect($server, $username, $password) { + $this->_link = @mysql_connect( + ($server != "" ? $server : ini_get("mysql.default_host")), + ("$server$username" != "" ? $username : ini_get("mysql.default_user")), + ("$server$username$password" != "" ? $password : ini_get("mysql.default_password")), + true, + 131072 // CLIENT_MULTI_RESULTS for CALL + ); + if ($this->_link) { + $this->server_info = mysql_get_server_info($this->_link); + } else { + $this->error = mysql_error(); + } + return (bool) $this->_link; + } + + /** Quote string to use in SQL + * @param string + * @return string escaped string enclosed in ' + */ + function quote($string) { + return "'" . mysql_real_escape_string($string, $this->_link) . "'"; + } + + /** Select database + * @param string + * @return bool + */ + function select_db($database) { + return mysql_select_db($database, $this->_link); + } + + /** Send query + * @param string + * @param bool + * @return mixed bool or Min_Result + */ + function query($query, $unbuffered = false) { + $result = @($unbuffered ? mysql_unbuffered_query($query, $this->_link) : mysql_query($query, $this->_link)); // @ - mute mysql.trace_mode + if (!$result) { + $this->error = mysql_error($this->_link); + return false; + } + if ($result === true) { + $this->affected_rows = mysql_affected_rows($this->_link); + $this->info = mysql_info($this->_link); + return true; + } + return new Min_Result($result); + } + + /** Send query with more resultsets + * @param string + * @return bool + */ + function multi_query($query) { + return $this->_result = $this->query($query); + } + + /** Get current resultset + * @return Min_Result + */ + function store_result() { + return $this->_result; + } + + /** Fetch next resultset + * @return bool + */ + function next_result() { + // MySQL extension doesn't support multiple results + return false; + } + + /** Get single field from result + * @param string + * @param int + * @return string + */ + function result($query, $field = 0) { + $result = $this->query($query); + if (!$result) { + return false; + } + return mysql_result($result->_result, 0, $field); + } + } + + class Min_Result { + var + $num_rows, ///< @var int number of rows in the result + $_result ///< @access private + ; + + /** Constructor + * @param resource + */ + function Min_Result($result) { + $this->_result = $result; + $this->num_rows = mysql_num_rows($result); + } + + /** Fetch next row as associative array + * @return array + */ + function fetch_assoc() { + return mysql_fetch_assoc($this->_result); + } + + /** Fetch next row as numbered array + * @return array + */ + function fetch_row() { + return mysql_fetch_row($this->_result); + } + + /** Fetch next field + * @return object properties: name, type, orgtable, orgname, charsetnr + */ + function fetch_field() { + $return = mysql_fetch_field($this->_result); + $return->orgtable = $return->table; + $return->orgname = $return->name; + $return->charsetnr = ($return->blob ? 63 : 0); + return $return; + } + + /** Free result set + */ + function __destruct() { + mysql_free_result($this->_result); //! not called in PHP 4 which is a problem with mysql.trace_mode + } + } + + } elseif (extension_loaded("pdo_mysql")) { + class Min_DB extends Min_PDO { + var $extension = "PDO_MySQL"; + + function connect($server, $username, $password) { + $this->dsn("mysql:host=" . str_replace(":", ";unix_socket=", preg_replace('~:([0-9])~', ';port=\\1', $server)), $username, $password); + return true; + } + + function select_db($database) { + // database selection is separated from the connection so dbname in DSN can't be used + return $this->query("USE " . idf_escape($database)); + } + + function query($query, $unbuffered = false) { + $this->setAttribute(1000, !$unbuffered); // 1000 - PDO::MYSQL_ATTR_USE_BUFFERED_QUERY + return parent::query($query, $unbuffered); + } + } + + } + + /** Escape database identifier + * @param string + * @return string + */ + function idf_escape($idf) { + return "`" . str_replace("`", "``", $idf) . "`"; + } + + /** Connect to the database + * @return mixed Min_DB or string for error + */ + function connect() { + global $adminer; + $connection = new Min_DB; + $credentials = $adminer->credentials(); + if ($connection->connect($credentials[0], $credentials[1], $credentials[2])) { + $connection->query("SET SQL_QUOTE_SHOW_CREATE=1"); + $connection->query("SET NAMES utf8"); + return $connection; + } + return $connection->error; + } + + /** Get cached list of databases + * @param bool + * @return array + */ + function get_databases($flush = true) { + // SHOW DATABASES can take a very long time so it is cached + $return = &get_session("databases"); + if (!isset($return)) { + if ($flush) { + restart_session(); + ob_flush(); + flush(); + } + $return = get_vals("SHOW DATABASES"); + } + return $return; + } + + /** Formulate SQL query with limit + * @param string everything after SELECT + * @param int + * @param int + * @return string + */ + function limit($query, $limit, $offset = 0) { + return " $query" . (isset($limit) ? "\nLIMIT $limit" . ($offset ? " OFFSET $offset" : "") : ""); + } + + /** Formulate SQL modification query with limit 1 + * @param string everything after UPDATE or DELETE + * @return string + */ + function limit1($query) { + return limit($query, 1); + } + + /** Get database collation + * @param string + * @param array result of collations() + * @return string + */ + function db_collation($db, $collations) { + global $connection; + $return = null; + $create = $connection->result("SHOW CREATE DATABASE " . idf_escape($db), 1); + if (preg_match('~ COLLATE ([^ ]+)~', $create, $match)) { + $return = $match[1]; + } elseif (preg_match('~ CHARACTER SET ([^ ]+)~', $create, $match)) { + // default collation + $return = $collations[$match[1]][0]; + } + return $return; + } + + /** Get supported engines + * @return array + */ + function engines() { + global $connection; + $return = array(); + $result = $connection->query("SHOW ENGINES"); + while ($row = $result->fetch_assoc()) { + if (ereg("YES|DEFAULT", $row["Support"])) { + $return[] = $row["Engine"]; + } + } + return $return; + } + + /** Get logged user + * @return string + */ + function logged_user() { + global $connection; + return $connection->result("SELECT USER()"); + } + + /** Get tables list + * @return array + */ + function tables_list() { + global $connection; + return get_key_vals("SHOW" . ($connection->server_info >= 5 ? " FULL" : "") . " TABLES"); + } + + /** Count tables in all databases + * @param array + * @return array array($db => $tables) + */ + function count_tables($databases) { + $return = array(); + foreach ($databases as $db) { + $return[$db] = count(get_vals("SHOW TABLES IN " . idf_escape($db))); + } + return $return; + } + + /** Get table status + * @param string + * @return array + */ + function table_status($name = "") { + global $connection; + $return = array(); + $result = $connection->query("SHOW TABLE STATUS" . ($name != "" ? " LIKE " . $connection->quote(addcslashes($name, "%_")) : "")); + while ($row = $result->fetch_assoc()) { + if ($row["Engine"] == "InnoDB") { + // ignore internal comment, unnecessary since MySQL 5.1.21 + $row["Comment"] = preg_replace('~(?:(.+); )?InnoDB free: .*~', '\\1', $row["Comment"]); + } + if (!isset($row["Rows"])) { + $row["Engine"] = "VIEW"; + $row["Comment"] = ""; + } + if ($name != "") { + return $row; + } + $return[$row["Name"]] = $row; + } + return $return; + } + + /** Check if table supports foreign keys + * @param array result of table_status + * @return bool + */ + function fk_support($table_status) { + return ($table_status["Engine"] == "InnoDB"); + } + + /** Get information about fields + * @param string + * @return array array($name => array("field" => , "full_type" => , "type" => , "length" => , "unsigned" => , "default" => , "null" => , "auto_increment" => , "on_update" => , "collation" => , "privileges" => , "comment" => , "primary" => )) + */ + function fields($table) { + global $connection; + $return = array(); + $result = $connection->query("SHOW FULL COLUMNS FROM " . idf_escape($table)); + if ($result) { + while ($row = $result->fetch_assoc()) { + preg_match('~^([^( ]+)(?:\\((.+)\\))?( unsigned)?( zerofill)?$~', $row["Type"], $match); + $return[$row["Field"]] = array( + "field" => $row["Field"], + "full_type" => $row["Type"], + "type" => $match[1], + "length" => $match[2], + "unsigned" => ltrim($match[3] . $match[4]), + "default" => ($row["Default"] != "" || ereg("char", $match[1]) ? $row["Default"] : null), + "null" => ($row["Null"] == "YES"), + "auto_increment" => ($row["Extra"] == "auto_increment"), + "on_update" => (eregi('^on update (.+)', $row["Extra"], $match) ? $match[1] : ""), //! available since MySQL 5.1.23 + "collation" => $row["Collation"], + "privileges" => array_flip(explode(",", $row["Privileges"])), + "comment" => $row["Comment"], + "primary" => ($row["Key"] == "PRI"), + ); + } + } + return $return; + } + + /** Get table indexes + * @param string + * @param string Min_DB to use + * @return array array($key_name => array("type" => , "columns" => array(), "lengths" => array())) + */ + function indexes($table, $connection2 = null) { + global $connection; + if (!is_object($connection2)) { // use the main connection if the separate connection is unavailable + $connection2 = $connection; + } + $return = array(); + $result = $connection2->query("SHOW INDEX FROM " . idf_escape($table)); + if ($result) { + while ($row = $result->fetch_assoc()) { + $return[$row["Key_name"]]["type"] = ($row["Key_name"] == "PRIMARY" ? "PRIMARY" : ($row["Index_type"] == "FULLTEXT" ? "FULLTEXT" : ($row["Non_unique"] ? "INDEX" : "UNIQUE"))); + $return[$row["Key_name"]]["columns"][] = $row["Column_name"]; + $return[$row["Key_name"]]["lengths"][] = $row["Sub_part"]; + } + } + return $return; + } + + /** Get foreign keys in table + * @param string + * @return array array($name => array("db" => , "table" => , "source" => array(), "target" => array(), "on_delete" => , "on_update" => )) + */ + function foreign_keys($table) { + global $connection, $on_actions; + static $pattern = '`(?:[^`]|``)+`'; + $return = array(); + $create_table = $connection->result("SHOW CREATE TABLE " . idf_escape($table), 1); + if ($create_table) { + preg_match_all("~CONSTRAINT ($pattern) FOREIGN KEY \\(((?:$pattern,? ?)+)\\) REFERENCES ($pattern)(?:\\.($pattern))? \\(((?:$pattern,? ?)+)\\)(?: ON DELETE (" . implode("|", $on_actions) . "))?(?: ON UPDATE (" . implode("|", $on_actions) . "))?~", $create_table, $matches, PREG_SET_ORDER); + foreach ($matches as $match) { + preg_match_all("~$pattern~", $match[2], $source); + preg_match_all("~$pattern~", $match[5], $target); + $return[idf_unescape($match[1])] = array( + "db" => idf_unescape($match[4] != "" ? $match[3] : $match[4]), + "table" => idf_unescape($match[4] != "" ? $match[4] : $match[3]), + "source" => array_map('idf_unescape', $source[0]), + "target" => array_map('idf_unescape', $target[0]), + "on_delete" => $match[6], + "on_update" => $match[7], + ); + } + } + return $return; + } + + /** Get view SELECT + * @param string + * @return array array("select" => ) + */ + function view($name) { + global $connection; + return array("select" => preg_replace('~^(?:[^`]|`[^`]*`)* AS ~U', '', $connection->result("SHOW CREATE VIEW " . idf_escape($name), 1))); + } + + /** Get sorted grouped list of collations + * @return array + */ + function collations() { + global $connection; + $return = array(); + $result = $connection->query("SHOW COLLATION"); + while ($row = $result->fetch_assoc()) { + $return[$row["Charset"]][] = $row["Collation"]; + } + ksort($return); + foreach ($return as $key => $val) { + sort($return[$key]); + } + return $return; + } + + /** Find out if database is information_schema + * @param string + * @return bool + */ + function information_schema($db) { + global $connection; + return ($connection->server_info >= 5 && $db == "information_schema"); + } + + /** Get escaped error message + * @return string + */ + function error() { + global $connection; + return h(preg_replace('~^You have an error.*syntax to use~U', "Syntax error", $connection->error)); + } + + /** Return expression for binary comparison + * @param string + * @return string + */ + function exact_value($val) { + global $connection; + return "BINARY " . $connection->quote($val); + } + + /** Rename database from DB + * @param string new name + * @return string + * @return bool + */ + function rename_database($name, $collation) { + global $connection; + $return = false; + if (queries("CREATE DATABASE " . idf_escape($name) . ($collation ? " COLLATE " . $connection->quote($collation) : ""))) { + //! move triggers + $return = true; // table list may by empty + foreach (tables_list() as $table) { + if (!queries("RENAME TABLE " . idf_escape($table) . " TO " . idf_escape($name) . "." . idf_escape($table))) { + $return = false; + break; + } + } + if ($return) { + queries("DROP DATABASE " . idf_escape(DB)); + //! saved to history of removed database + } + } + return $return; + } + + /** Generate modifier for auto increment column + * @return string + */ + function auto_increment() { + $auto_increment_index = " PRIMARY KEY"; + // don't overwrite primary key by auto_increment + if ($_GET["create"] != "" && $_POST["auto_increment_col"]) { + foreach (indexes($_GET["create"]) as $index) { + if (in_array($_POST["fields"][$_POST["auto_increment_col"]]["orig"], $index["columns"], true)) { + $auto_increment_index = ""; + break; + } + if ($index["type"] == "PRIMARY") { + $auto_increment_index = " UNIQUE"; + } + } + } + return " AUTO_INCREMENT$auto_increment_index"; + } + + /** Run commands to create or alter table + * @param string "" to create + * @param string new name + * @param array of array($orig, $process_field, $after) + * @param array of strings + * @param string + * @param string + * @param string + * @param int + * @param string + * @return bool + */ + function alter_table($table, $name, $fields, $foreign, $comment, $engine, $collation, $auto_increment, $partitioning) { + global $connection; + $alter = array(); + foreach ($fields as $field) { + $alter[] = ($field[1] + ? ($table != "" ? ($field[0] != "" ? "CHANGE " . idf_escape($field[0]) : "ADD") : " ") . " " . implode("", $field[1]) . ($table != "" ? " $field[2]" : "") + : "DROP " . idf_escape($field[0]) + ); + } + $alter = array_merge($alter, $foreign); + $status = "COMMENT=" . $connection->quote($comment) + . ($engine ? " ENGINE=" . $connection->quote($engine) : "") + . ($collation ? " COLLATE " . $connection->quote($collation) : "") + . ($auto_increment != "" ? " AUTO_INCREMENT=$auto_increment" : "") + . $partitioning + ; + if ($table == "") { + return queries("CREATE TABLE " . idf_escape($name) . " (\n" . implode(",\n", $alter) . "\n) $status"); + } + if ($table != $name) { + $alter[] = "RENAME TO " . idf_escape($name); + } + $alter[] = $status; + return queries("ALTER TABLE " . idf_escape($table) . "\n" . implode(",\n", $alter)); + } + + /** Run commands to alter indexes + * @param string escaped table name + * @param array of array("index type", "(columns definition)") or array("index type", "escaped name", "DROP") + * @return bool + */ + function alter_indexes($table, $alter) { + foreach ($alter as $key => $val) { + $alter[$key] = ($val[2] ? "\nDROP INDEX " : "\nADD $val[0] " . ($val[0] == "PRIMARY" ? "KEY " : "")) . $val[1]; + } + return queries("ALTER TABLE " . idf_escape($table) . implode(",", $alter)); + } + + /** Run commands to truncate tables + * @param array + * @return bool + */ + function truncate_tables($tables) { + foreach ($tables as $table) { + if (!queries("TRUNCATE TABLE " . idf_escape($table))) { + return false; + } + } + return true; + } + + /** Drop views + * @param array + * @return bool + */ + function drop_views($views) { + return queries("DROP VIEW " . implode(", ", array_map('idf_escape', $views))); + } + + /** Drop tables + * @param array + * @return bool + */ + function drop_tables($tables) { + return queries("DROP TABLE " . implode(", ", array_map('idf_escape', $tables))); + } + + /** Get information about trigger + * @param string trigger name + * @return array array("Trigger" => , "Timing" => , "Event" => , "Statement" => ) + */ + function trigger($name) { + global $connection; + $result = $connection->query("SHOW TRIGGERS WHERE `Trigger` = " . $connection->quote($name)); + return $result->fetch_assoc(); + } + + /** Get defined triggers + * @param string + * @return array array($name => array($timing, $event)) + */ + function triggers($table) { + global $connection; + $return = array(); + $result = $connection->query("SHOW TRIGGERS LIKE " . $connection->quote(addcslashes($table, "%_"))); + while ($row = $result->fetch_assoc()) { + $return[$row["Trigger"]] = array($row["Timing"], $row["Event"]); + } + return $return; + } + + /** Explain select + * @param Min_DB + * @param string + * @return Min_Result + */ + function explain($connection, $query) { + return $connection->query("EXPLAIN $query"); + } + + /** Get SQL command to create table + * @param string + * @return string + */ + function create_sql($table) { + global $connection; + return $connection->result("SHOW CREATE TABLE " . idf_escape($table), 1); + } + + /** Check whether a feature is supported + * @param string + * @return bool + */ + function support($feature) { + global $connection; + $features = array( + "view" => ($connection->server_info >= 5), + "routine" => ($connection->server_info >= 5), + "trigger" => ($connection->server_info >= 5), + "event" => ($connection->server_info >= 5.1), + "partitioning" => ($connection->server_info >= 5.1), + ); + return (isset($features[$feature]) ? $features[$feature] : true); + } + + $driver = "sql"; ///< @var string JUSH identifier + $types = array(); ///< @var array ($type => $maximum_unsigned_length, ...) + $structured_types = array(); ///< @var array ($description => array($type, ...), ...) + foreach (array( + lang('Numbers') => array("tinyint" => 3, "smallint" => 5, "mediumint" => 8, "int" => 10, "bigint" => 20, "decimal" => 66, "float" => 12, "double" => 21), + lang('Date and time') => array("date" => 10, "datetime" => 19, "timestamp" => 19, "time" => 10, "year" => 4), + lang('Strings') => array("char" => 255, "varchar" => 65535, "tinytext" => 255, "text" => 65535, "mediumtext" => 16777215, "longtext" => 4294967295), + lang('Binary') => array("binary" => 255, "varbinary" => 65535, "tinyblob" => 255, "blob" => 65535, "mediumblob" => 16777215, "longblob" => 4294967295), + lang('Lists') => array("enum" => 65535, "set" => 64), + ) as $key => $val) { + $types += $val; + $structured_types[$key] = array_keys($val); + } + $unsigned = array("unsigned", "zerofill", "unsigned zerofill"); ///< @var array number variants + $operators = array("=", "<", ">", "<=", ">=", "!=", "LIKE", "LIKE %%", "REGEXP", "IN", "IS NULL", "NOT LIKE", "NOT REGEXP", "NOT IN", "IS NOT NULL"); ///< @var array operators used in select + $functions = array("char_length", "from_unixtime", "hex", "lower", "round", "sec_to_time", "time_to_sec", "upper"); ///< @var array functions used in select + $grouping = array("avg", "count", "count distinct", "group_concat", "max", "min", "sum"); ///< @var array grouping functions used in select + $edit_functions = array( ///< @var array of array("$type|$type2" => "$function/$function2") functions used in editing, [0] - edit and insert, [1] - edit only + array( + "char" => "md5/sha1/password/encrypt/uuid", //! JavaScript for disabling maxlength + "date|time" => "now", + ), array( + "int|float|double|decimal" => "+/-", + "date" => "+ interval/- interval", + "time" => "addtime/subtime", + "char|text" => "concat", + ) + ); +} diff --git a/adminer/drivers/pgsql.inc.php b/adminer/drivers/pgsql.inc.php new file mode 100644 index 00000000..a42a21e6 --- /dev/null +++ b/adminer/drivers/pgsql.inc.php @@ -0,0 +1,443 @@ +error = $error; + } + + function connect($server, $username, $password) { + set_error_handler(array($this, '_error')); + $this->_string = "host='" . str_replace(":", "' port='", addcslashes($server, "'\\")) . "' user='" . addcslashes($username, "'\\") . "' password='" . addcslashes($password, "'\\") . "'"; + $this->_link = @pg_connect($this->_string . (DB != "" ? " dbname='" . addcslashes(DB, "'\\") . "'" : ""), PGSQL_CONNECT_FORCE_NEW); + if (!$this->_link && DB != "") { + // try to connect directly with database for performance + $this->_database = false; + $this->_link = @pg_connect($this->_string, PGSQL_CONNECT_FORCE_NEW); + } + restore_error_handler(); + if ($this->_link) { + $version = pg_version($this->_link); + $this->server_info = $version["server"]; + pg_set_client_encoding($this->_link, "UTF8"); + } + return (bool) $this->_link; + } + + function quote($string) { + return "'" . pg_escape_string($this->_link, $string) . "'"; //! bytea + } + + function select_db($database) { + if ($database == DB) { + return $this->_database; + } + $link = @pg_connect($this->_connection . " dbname='" . addcslashes($database, "'\\") . "'", PGSQL_CONNECT_FORCE_NEW); + if ($link) { + $this->_link = $link; + } + return $link; + } + + function query($query, $unbuffered = false) { + $result = @pg_query($this->_link, $query); + if (!$result) { + $this->error = pg_last_error($this->_link); + return false; + } elseif (!pg_num_fields($result)) { + $this->affected_rows = pg_affected_rows($result); + return true; + } + return new Min_Result($result); + } + + function multi_query($query) { + return $this->_result = $this->query($query); + } + + function store_result() { + return $this->_result; + } + + function next_result() { + // PgSQL extension doesn't support multiple results + return false; + } + + function result($query, $field = 0) { + $result = $this->query($query); + if (!$result) { + return false; + } + return pg_fetch_result($result->_result, 0, $field); + } + } + + class Min_Result { + var $_result, $_offset = 0, $num_rows; + + function Min_Result($result) { + $this->_result = $result; + $this->num_rows = pg_num_rows($result); + } + + function fetch_assoc() { + return pg_fetch_assoc($this->_result); + } + + function fetch_row() { + return pg_fetch_row($this->_result); + } + + function fetch_field() { + $column = $this->_offset++; + $row = new stdClass; + $row->orgtable = pg_field_table($this->_result, $column); + $row->name = pg_field_name($this->_result, $column); + $row->orgname = $row->name; + $row->type = pg_field_type($this->_result, $column); + $row->charsetnr = ($row->type == "bytea" ? 63 : 0); + return $row; + } + + function __destruct() { + pg_free_result($this->_result); + } + } + + } elseif (extension_loaded("pdo_pgsql")) { + class Min_DB extends Min_PDO { + var $extension = "PDO_PgSQL"; + + function connect($server, $username, $password) { + $string = "pgsql:host='" . str_replace(":", "' port='", addcslashes($server, "'\\")) . "' options='-c client_encoding=utf8'"; + $this->dsn($string . (DB != "" ? " dbname='" . addcslashes(DB, "'\\") . "'" : ""), $username, $password); + //! connect without DB in case of an error + return true; + } + + function select_db($database) { + return (DB == $database); + } + } + + } + + function idf_escape($idf) { + return '"' . str_replace('"', '""', $idf) . '"'; + } + + function connect() { + global $adminer; + $connection = new Min_DB; + $credentials = $adminer->credentials(); + if ($connection->connect($credentials[0], $credentials[1], $credentials[2])) { + return $connection; + } + return $connection->error; + } + + function get_databases() { + return get_vals("SELECT datname FROM pg_database"); + } + + function limit($query, $limit, $offset = 0) { + return " $query" . (isset($limit) ? "\nLIMIT $limit" . ($offset ? " OFFSET $offset" : "") : ""); + } + + function limit1($query) { + return " $query"; + } + + function db_collation($db, $collations) { + global $connection; + return $connection->result("SHOW LC_COLLATE"); //! respect $db + } + + function engines() { + return array(); + } + + function logged_user() { + global $connection; + return $connection->result("SELECT user"); + } + + function tables_list() { + global $connection; + return get_key_vals("SELECT table_name, table_type FROM information_schema.tables WHERE table_schema = 'public' ORDER BY table_name"); + } + + function count_tables($databases) { + return array(); // would require reconnect + } + + function table_status($name = "") { + global $connection; + $return = array(); + $result = $connection->query("SELECT relname AS \"Name\", CASE relkind WHEN 'r' THEN '' ELSE 'view' END AS \"Engine\", pg_relation_size(oid) AS \"Data_length\", pg_catalog.obj_description(oid, 'pg_class') AS \"Comment\" FROM pg_catalog.pg_class WHERE relkind IN ('r','v') AND relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'public')" . ($name != "" ? " AND relname = " . $connection->quote($name) : "")); //! Index_length, Auto_increment + while ($row = $result->fetch_assoc()) { + $return[$row["Name"]] = $row; + } + return ($name != "" ? $return[$name] : $return); + } + + function fk_support($table_status) { + return true; + } + + function fields($table) { + global $connection; + $return = array(); + $table_oid = $connection->result("SELECT oid FROM pg_class WHERE relname = " . $connection->quote($table)); + $result = $connection->query("SELECT *, col_description($table_oid, ordinal_position) AS comment FROM information_schema.columns WHERE table_name = " . $connection->quote($table) . " ORDER BY ordinal_position"); + if ($result) { + while ($row = $result->fetch_assoc()) { + $length = $row["character_maximum_length"]; + $return[$row["column_name"]] = array( + "field" => $row["column_name"], + "full_type" => $row["data_type"] . ($length ? "($length)" : ""), + "type" => $row["data_type"], + "length" => $length, + "default" => $row["column_default"], + "null" => ($row["is_nullable"] == "YES"), + "auto_increment" => eregi("^nextval\\(", $row["column_default"]), + "on_update" => "", //! + "collation" => $row["collation_name"], + "privileges" => array("insert" => 1, "select" => 1, "update" => 1), //! is_updatable + "primary" => false, //! + "comment" => $row["comment"], + ); + } + } + return $return; + } + + function indexes($table, $connection2 = null) { + global $connection; + if (!is_object($connection2)) { + $connection2 = $connection; + } + $return = array(); + $table_oid = $connection2->result("SELECT oid FROM pg_class WHERE relname = " . $connection2->quote($table)); + $columns = get_key_vals("SELECT attnum, attname FROM pg_attribute WHERE attrelid = $table_oid AND attnum > 0", $connection2); + $result = $connection2->query("SELECT relname, indisunique, indisprimary, indkey FROM pg_index i, pg_class ci WHERE i.indrelid = $table_oid AND ci.oid = i.indexrelid"); + while ($row = $result->fetch_assoc()) { + $return[$row["relname"]]["type"] = ($row["indisprimary"] == "t" ? "PRIMARY" : ($row["indisunique"] == "t" ? "UNIQUE" : "INDEX")); + $return[$row["relname"]]["columns"] = array(); + foreach (explode(" ", $row["indkey"]) as $indkey) { + $return[$row["relname"]]["columns"][] = $columns[$indkey]; + } + $return[$row["relname"]]["lengths"] = array(); + } + return $return; + } + + function foreign_keys($table) { + global $connection; + $return = array(); + $result = $connection->query("SELECT tc.constraint_name, kcu.column_name, rc.update_rule AS on_update, rc.delete_rule AS on_delete, ccu.table_name AS table, ccu.column_name AS ref +FROM information_schema.table_constraints tc +LEFT JOIN information_schema.key_column_usage kcu USING (constraint_catalog, constraint_schema, constraint_name) +LEFT JOIN information_schema.referential_constraints rc USING (constraint_catalog, constraint_schema, constraint_name) +LEFT JOIN information_schema.constraint_column_usage ccu ON rc.unique_constraint_catalog = ccu.constraint_catalog AND rc.unique_constraint_schema = ccu.constraint_schema AND rc.unique_constraint_name = ccu.constraint_name +WHERE tc.constraint_type = 'FOREIGN KEY' AND tc.table_name = " . $connection->quote($table)); //! there can be more unique_constraint_name + while ($row = $result->fetch_assoc()) { + $foreign_key = &$return[$row["constraint_name"]]; + if (!$foreign_key) { + $foreign_key = $row; + } + $foreign_key["source"][] = $row["column_name"]; + $foreign_key["target"][] = $row["ref"]; + } + return $return; + } + + function view($name) { + global $connection; + return array("select" => $connection->result("SELECT pg_get_viewdef(" . $connection->quote($name) . ")")); + } + + function collations() { + //! supported in CREATE DATABASE + return array(); + } + + function information_schema($db) { + return ($db == "information_schema"); + } + + function error() { + global $connection; + $return = h($connection->error); + if (preg_match('~^(.*\\n)?([^\\n]*)\\n( *)\\^(\\n.*)?$~s', $return, $match)) { + $return = $match[1] . preg_replace('~((?:[^&]|&[^;]*;){' . strlen($match[3]) . '})(.*)~', '\\1\\2', $match[2]) . $match[4]; + } + return nl_br($return); + } + + function exact_value($val) { + global $connection; + return $connection->quote($val); + } + + function rename_database($name, $collation) { + //! current database cannot be renamed + return queries("ALTER DATABASE " . idf_escape(DB) . " RENAME TO " . idf_escape($name)); + } + + function auto_increment() { + return true; + } + + function alter_table($table, $name, $fields, $foreign, $comment, $engine, $collation, $auto_increment, $partitioning) { + global $connection; + $alter = array(); + $queries = array(); + foreach ($fields as $field) { + $column = idf_escape($field[0]); + $val = $field[1]; + if (!$val) { + $alter[] = "DROP $column"; + } else { + $val5 = $val[5]; + unset($val[5]); + if ($val[6]) { // auto_increment + $val = array($val[0], ($val[1] == "bigint" ? "big" : "") . "serial"); + } + if ($field[0] == "") { + $alter[] = ($table != "" ? "ADD " : " ") . implode("", $val); + } else { + if ($column != $val[0]) { + $queries[] = "ALTER TABLE " . idf_escape($table) . " RENAME $column TO $val[0]"; + } + $alter[] = "ALTER $column TYPE $val[1]"; + if (!$val[6]) { + $alter[] = "ALTER $column" . ($val[3] ? " SET$val[3]" : " DROP DEFAULT"); //! quoting + $alter[] = "ALTER $column " . ($val[2] == " NULL" ? "DROP NOT" : "SET") . $val[2]; + } + } + if ($table != "" || $val5 != "") { + $queries[] = "COMMENT ON COLUMN " . idf_escape($table) . ".$val[0] IS " . substr($val5, 9); + } + } + } + $alter = array_merge($alter, $foreign); + if ($table == "") { + array_unshift($queries, "CREATE TABLE " . idf_escape($name) . " (\n" . implode(",\n", $alter) . "\n)"); + } elseif ($alter) { + array_unshift($queries, "ALTER TABLE " . idf_escape($table) . "\n" . implode(",\n", $alter)); + } + if ($table != "" && $table != $name) { + $queries[] = "ALTER TABLE " . idf_escape($table) . " RENAME TO " . idf_escape($name); + } + if ($table != "" || $comment != "") { + $queries[] = "COMMENT ON TABLE " . idf_escape($name) . " IS " . $connection->quote($comment); + } + if ($auto_increment != "") { + //! $queries[] = "SELECT setval(pg_get_serial_sequence(" . $connection->quote($name) . ", ), $auto_increment)"; + } + foreach ($queries as $query) { + if (!queries($query)) { + return false; + } + } + return true; + } + + function alter_indexes($table, $alter) { + $create = array(); + $drop = array(); + foreach ($alter as $val) { + if ($val[0] != "INDEX") { + $create[] = ($val[2] ? "\nDROP CONSTRAINT " : "\nADD $val[0] " . ($val[0] == "PRIMARY" ? "KEY " : "")) . $val[1]; + } elseif ($val[2]) { + $drop[] = $val[1]; + } elseif (!queries("CREATE INDEX " . idf_escape(uniqid($table . "_")) . " ON " . idf_escape($table) . " $val[1]")) { + return false; + } + } + return ((!$create || queries("ALTER TABLE " . idf_escape($table) . implode(",", $create))) + && (!$drop || queries("DROP INDEX " . implode(", ", $drop))) + ); + } + + function truncate_tables($tables) { + return queries("TRUNCATE " . implode(", ", array_map('idf_escape', $tables))); + return true; + } + + function drop_views($views) { + return queries("DROP VIEW " . implode(", ", array_map('idf_escape', $views))); + } + + function drop_tables($tables) { + return queries("DROP TABLE " . implode(", ", array_map('idf_escape', $tables))); + } + + function trigger($name) { + global $connection; + $result = $connection->query('SELECT trigger_name AS "Trigger", condition_timing AS "Timing", event_manipulation AS "Event", action_statement AS "Statement" FROM information_schema.triggers WHERE event_object_table = ' . $connection->quote($_GET["trigger"]) . ' AND trigger_name = ' . $connection->quote($name)); + return $result->fetch_assoc(); + } + + function triggers($table) { + global $connection; + $return = array(); + $result = $connection->query("SELECT * FROM information_schema.triggers WHERE event_object_table = " . $connection->quote($table)); + while ($row = $result->fetch_assoc()) { + $return[$row["trigger_name"]] = array($row["condition_timing"], $row["event_manipulation"]); + } + return $return; + } + + function explain($connection, $query) { + return $connection->query("EXPLAIN $query"); + } + + function support($feature) { + return ereg('^(comment|view|routine|trigger)$', $feature); + } + + $driver = "pgsql"; + $types = array(); + $structured_types = array(); + foreach (array( //! arrays + lang('Numbers') => array("smallint" => 5, "integer" => 10, "bigint" => 19, "boolean" => 1, "numeric" => 0, "real" => 7, "double precision" => 16, "money" => 20), + lang('Date and time') => array("date" => 13, "time" => 17, "timestamp" => 20, "interval" => 0), + lang('Strings') => array("character" => 0, "character varying" => 0, "text" => 0, "tsquery" => 0, "tsvector" => 0, "uuid" => 0, "xml" => 0), + lang('Binary') => array("bit" => 0, "bit varying" => 0, "bytea" => 0), + lang('Network') => array("cidr" => 43, "inet" => 43, "macaddr" => 17, "txid_snapshot" => 0), + lang('Geometry') => array("box" => 0, "circle" => 0, "line" => 0, "lseg" => 0, "path" => 0, "point" => 0, "polygon" => 0), + ) as $key => $val) { + $types += $val; + $structured_types[$key] = array_keys($val); + } + $unsigned = array(); + $operators = array("=", "<", ">", "<=", ">=", "!=", "~", "!~", "LIKE", "LIKE %%", "IN", "IS NULL", "NOT LIKE", "NOT IN", "IS NOT NULL"); + $functions = array("char_length", "lower", "round", "to_hex", "to_timestamp", "upper"); + $grouping = array("avg", "count", "count distinct", "max", "min", "sum"); + $edit_functions = array( + array( + "char" => "md5", + "date|time" => "now", + ), array( + "int|numeric|real|money" => "+/-", + "date|time" => "+ interval/- interval", //! escape + "char|text" => "||", + ) + ); +} diff --git a/adminer/drivers/sqlite.inc.php b/adminer/drivers/sqlite.inc.php new file mode 100644 index 00000000..48ee0460 --- /dev/null +++ b/adminer/drivers/sqlite.inc.php @@ -0,0 +1,479 @@ +server_info = sqlite_libversion(); + } + + function open($filename) { + $this->_connection = new SQLiteDatabase($filename); + } + + function query($query, $unbuffered = false) { + $method = ($unbuffered ? "unbufferedQuery" : "query"); + $result = @$this->_connection->$method($query, SQLITE_BOTH, $error); + if (!$result) { + $this->error = $error; + return false; + } elseif ($result === true) { + $this->affected_rows = $this->changes(); + return true; + } + return new Min_Result($result); + } + + function quote($string) { + return "'" . sqlite_escape_string($string) . "'"; + } + + function result($query, $field = 0) { + $result = $this->query($query); + if (!$result) { + return false; + } + $row = $result->_result->fetch(); + return $row[$field]; + } + } + + class Min_Result { + var $_result, $_offset = 0, $num_rows; + + function __construct($result) { + $this->_result = $result; + if (method_exists($result, 'numRows')) { // not available in unbuffered query + $this->num_rows = $result->numRows(); + } + } + + function fetch_assoc() { + $row = $this->_result->fetch(SQLITE_ASSOC); + if (!$row) { + return false; + } + $return = array(); + foreach ($row as $key => $val) { + $return[($key[0] == '"' ? idf_unescape($key) : $key)] = $val; + } + return $return; + } + + function fetch_row() { + return $this->_result->fetch(SQLITE_NUM); + } + + function fetch_field() { + return (object) array( + "name" => $this->_result->fieldName($this->_offset++), + //! type, orgtable, charsetnr + ); + } + + } + + } else { + + class Min_SQLite extends SQLite3 { + var $extension = "SQLite3", $server_info, $affected_rows, $error; + + function __construct() { + $version = $this->version(); + $this->server_info = $version["versionString"]; + } + + function open($filename) { + parent::__construct($filename); + } + + function query($query) { + $result = @parent::query($query); + if (!$result) { + $this->error = $this->lastErrorMsg(); + return false; + } elseif ($result->numColumns()) { + return new Min_Result($result); + } + $this->affected_rows = $this->changes(); + return true; + } + + function quote($string) { + return "'" . $this->escapeString($string) . "'"; + } + + function result($query, $field = 0) { + $result = $this->query($query); + if (!$result) { + return false; + } + $row = $result->_result->fetchArray(); + return $row[$field]; + } + } + + class Min_Result { + var $_result, $_offset = 0, $num_rows; + + function __construct($result) { + $this->_result = $result; + $this->num_rows = 1; //! + } + + function fetch_assoc() { + return $this->_result->fetchArray(SQLITE3_ASSOC); + } + + function fetch_row() { + return $this->_result->fetchArray(SQLITE3_NUM); + } + + function fetch_field() { + $column = $this->_offset++; + return (object) array( + "name" => $this->_result->columnName($column), + "type" => $this->_result->columnType($column), + //! orgtable, charsetnr + ); + } + + function __desctruct() { + return $this->_result->finalize(); + } + } + + } + + class Min_DB extends Min_SQLite { + + function select_db($filename) { + static $connected = false; + if ($connected) { + return true; + } + set_exception_handler('connect_error'); // try/catch is not compatible with PHP 4 + $this->open($filename); + $connected = true; + restore_exception_handler(); + return true; + } + + function multi_query($query) { + return $this->_result = $this->query($query); + } + + function store_result() { + return $this->_result; + } + + function next_result() { + return false; + } + } + + } elseif (extension_loaded("pdo_sqlite")) { + class Min_DB extends Min_PDO { + var $extension = "PDO_SQLite"; + + function select_db($filename) { + static $connected = false; + if ($connected) { + return true; + } + $connected = true; + $this->dsn(DRIVER . ":$filename", "", "", "connect_error"); + //! $this->server_info needs to be filled in __construct() + return true; + } + } + + } + + function idf_escape($idf) { + return '"' . str_replace('"', '""', $idf) . '"'; + } + + function connect() { + global $connection; + if ($connection) { + return $connection; // can connect only once, function to get number of rows doesn't exist anyway + } + return new Min_DB; + } + + function get_databases() { + return array(); + } + + function limit($query, $limit, $offset = 0) { + return " $query" . (isset($limit) ? "\nLIMIT $limit" . ($offset ? " OFFSET $offset" : "") : ""); + } + + function limit1($query) { + global $connection; + return ($connection->result("SELECT sqlite_compileoption_used('ENABLE_UPDATE_DELETE_LIMIT')") ? limit($query, 1) : " $query"); + } + + function db_collation($db, $collations) { + return null; + } + + function engines() { + return array(); + } + + function logged_user() { + return ""; //! OS user + } + + function tables_list() { + return get_key_vals("SELECT name, type FROM sqlite_master WHERE type IN ('table', 'view')", 1); + } + + function count_tables($databases) { + return array(); + } + + function table_status($name = "") { + global $connection; + $return = array(); + $result = $connection->query("SELECT name AS Name, type AS Engine FROM sqlite_master WHERE type IN ('table', 'view')" . ($name != "" ? " AND name = " . $connection->quote($name) : "")); + while ($row = $result->fetch_assoc()) { + $return[$row["Name"]] = $row; + } + $result = $connection->query("SELECT * FROM sqlite_sequence"); + if ($result) { + while ($row = $result->fetch_assoc()) { + $return[$row["name"]]["Auto_increment"] = $row["seq"]; + } + } + return ($name != "" ? $return[$name] : $return); + } + + function fk_support($table_status) { + global $connection; + return !$connection->result("SELECT sqlite_compileoption_used('OMIT_FOREIGN_KEY')"); + } + + function fields($table) { + global $connection; + $return = array(); + $result = $connection->query("PRAGMA table_info(" . idf_escape($table) . ")"); + if (is_object($result)) { + while ($row = $result->fetch_assoc()) { + $type = strtolower($row["type"]); + $return[$row["name"]] = array( + "field" => $row["name"], + "type" => (eregi("int", $type) ? "integer" : (eregi("char|clob|text", $type) ? "text" : (eregi("blob", $type) ? "blob" : (eregi("real|floa|doub", $type) ? "real" : "numeric")))), + "full_type" => $type, + "default" => $row["dflt_value"], + "null" => !$row["notnull"], + "auto_increment" => eregi('^integer$', $type) && $row["pk"], //! possible false positive + "collation" => null, //! + "privileges" => array("select" => 1, "insert" => 1, "update" => 1), + "primary" => $row["pk"], + ); + } + } + return $return; + } + + function indexes($table, $connection2 = null) { + global $connection; + $return = array(); + $primary = array(); + foreach (fields($table) as $field) { + if ($field["primary"]) { + $primary[] = $field["field"]; + } + } + if ($primary) { + $return[""] = array("type" => "PRIMARY", "columns" => $primary, "lengths" => array()); + } + $result = $connection->query("PRAGMA index_list(" . idf_escape($table) . ")"); + if (is_object($result)) { + while ($row = $result->fetch_assoc()) { + $return[$row["name"]]["type"] = ($row["unique"] ? "UNIQUE" : "INDEX"); + $return[$row["name"]]["lengths"] = array(); + $result1 = $connection->query("PRAGMA index_info(" . idf_escape($row["name"]) . ")"); + while ($row1 = $result1->fetch_assoc()) { + $return[$row["name"]]["columns"][] = $row1["name"]; + } + } + } + return $return; + } + + function foreign_keys($table) { + global $connection; + $return = array(); + $result = $connection->query("PRAGMA foreign_key_list(" . idf_escape($table) . ")"); + if (is_object($result)) { + while ($row = $result->fetch_assoc()) { + $foreign_key = &$return[$row["id"]]; + if (!$foreign_key) { + $foreign_key = $row; + } + $foreign_key["source"][] = $row["from"]; + $foreign_key["target"][] = $row["to"]; + } + } + return $return; + } + + function view($name) { + global $connection; + return array("select" => preg_replace('~^(?:[^`"[]+|`[^`]*`|"[^"]*")* AS\\s+~iU', '', $connection->result("SELECT sql FROM sqlite_master WHERE name = " . $connection->quote($name)))); //! identifiers may be inside [] + } + + function collations() { + return get_vals("PRAGMA collation_list", 1); + } + + function information_schema($db) { + return false; + } + + function error() { + global $connection; + return h($connection->error); + } + + function exact_value($val) { + global $connection; + return $connection->quote($val); + } + + function rename_database($name, $collation) { + global $connection; + $connection->close(); //! not available with all extensions + return rename(DB, $name); + } + + function auto_increment() { + return " PRIMARY KEY" . (DRIVER == "sqlite" ? " AUTOINCREMENT" : ""); + } + + function alter_table($table, $name, $fields, $foreign, $comment, $engine, $collation, $auto_increment, $partitioning) { + global $connection; + $alter = array(); + foreach ($fields as $field) { + $alter[] = ($table != "" && $field[0] == "" ? "ADD " : " ") . implode("", $field[1]); + } + $alter = array_merge($alter, $foreign); + if ($table != "") { + foreach ($alter as $val) { + if (!queries("ALTER TABLE " . idf_escape($table) . " $val")) { + return false; + } + } + if ($table != $name && !queries("ALTER TABLE " . idf_escape($table) . " RENAME TO " . idf_escape($name))) { + return false; + } + } elseif (!queries("CREATE TABLE " . idf_escape($name) . " (\n" . implode(",\n", $alter) . "\n)")) { + return false; + } + if ($auto_increment) { + return queries("UPDATE sqlite_sequence SET seq = $auto_increment WHERE name = " . $connection->quote($name) . ""); + } + return true; + } + + function alter_indexes($table, $alter) { + foreach ($alter as $val) { + if (!queries(($val[2] ? "DROP INDEX" : "CREATE" . ($val[0] != "INDEX" ? " UNIQUE" : "") . " INDEX " . idf_escape(uniqid($table . "_")) . " ON " . idf_escape($table)) . " $val[1]")) { //! primary key must be created in CREATE TABLE + return false; + } + } + return true; + } + + function truncate_tables($tables) { + foreach ($tables as $table) { + if (!queries("DELETE FROM " . idf_escape($table))) { + return false; + } + } + return true; + } + + function drop_views($views) { + foreach ($views as $view) { + if (!queries("DROP VIEW " . idf_escape($view))) { + return false; + } + } + return true; + } + + function drop_tables($tables) { + foreach ($tables as $table) { + if (!queries("DROP TABLE " . idf_escape($table))) { + return false; + } + } + return true; + } + + function trigger($name) { + global $connection; + preg_match('~^CREATE\\s+TRIGGER\\s*(?:[^`"\\s]+|`[^`]*`|"[^"]*")+\\s*([a-z]+)\\s+([a-z]+)\\s+ON\\s*(?:[^`"\\s]+|`[^`]*`|"[^"]*")+\\s*(?:FOR\\s*EACH\\s*ROW\\s)?(.*)~is', $connection->result("SELECT sql FROM sqlite_master WHERE name = " . $connection->quote($name)), $match); + return array("Timing" => strtoupper($match[1]), "Event" => strtoupper($match[2]), "Trigger" => $name, "Statement" => $match[3]); + } + + function triggers($table) { + global $connection; + $return = array(); + $result = $connection->query("SELECT * FROM sqlite_master WHERE type = 'trigger' AND tbl_name = " . $connection->quote($table)); + while ($row = $result->fetch_assoc()) { + preg_match('~^CREATE\\s+TRIGGER\\s*(?:[^`"\\s]+|`[^`]*`|"[^"]*")+\\s*([a-z]+)\\s*([a-z]+)~i', $row["sql"], $match); + $return[$row["name"]] = array($match[1], $match[2]); + } + return $return; + } + + function explain($connection, $query) { + return $connection->query("EXPLAIN $query"); + } + + function create_sql($table) { + global $connection; + return $connection->result("SELECT sql FROM sqlite_master WHERE name = " . $connection->quote($table)); + } + + function support($feature) { + return ereg('^(view|trigger)$', $feature); + } + + $driver = "sqlite"; + $types = array("integer" => 0, "real" => 0, "numeric" => 0, "text" => 0, "blob" => 0); + $structured_types = array_keys($types); + $unsigned = array(); + $operators = array("=", "<", ">", "<=", ">=", "!=", "LIKE", "LIKE %%", "IN", "IS NULL", "NOT LIKE", "NOT IN", "IS NOT NULL"); // REGEXP can be user defined function + $functions = array("hex", "length", "lower", "round", "unixepoch", "upper"); + $grouping = array("avg", "count", "count distinct", "group_concat", "max", "min", "sum"); + $edit_functions = array( + array( + // "text" => "date('now')/time('now')/datetime('now')", + ), array( + "integer|real|numeric" => "+/-", + // "text" => "date/time/datetime", + "text" => "||", + ) + ); +} diff --git a/adminer/dump.inc.php b/adminer/dump.inc.php index 12336c56..4f455ac1 100644 --- a/adminer/dump.inc.php +++ b/adminer/dump.inc.php @@ -2,25 +2,37 @@ $TABLE = $_GET["dump"]; if ($_POST) { + $cookie = ""; + foreach (array("output", "format", "db_style", "table_style", "data_style") as $key) { + $cookie .= "&$key=" . urlencode($_POST[$key]); + } + cookie("adminer_export", substr($cookie, 1)); $ext = dump_headers(($TABLE != "" ? $TABLE : DB), (DB == "" || count((array) $_POST["tables"] + (array) $_POST["data"]) > 1)); if ($_POST["format"] == "sql") { - echo "-- Adminer $VERSION dump -SET NAMES utf8; + echo "-- Adminer $VERSION " . $drivers[DRIVER] . " dump + +" . ($driver != "sql" ? "" : "SET NAMES utf8; SET foreign_key_checks = 0; -SET time_zone = " . $connection->quote($connection->result($connection->query("SELECT @@time_zone"))) . "; +SET time_zone = " . $connection->quote($connection->result("SELECT @@time_zone")) . "; SET sql_mode = 'NO_AUTO_VALUE_ON_ZERO'; -"; +"); } $style = $_POST["db_style"]; - foreach ((DB != "" ? array(DB) : (array) $_POST["databases"]) as $db) { + $databases = array(DB); + if (DB == "") { + $databases = $_POST["databases"]; + if (is_string($databases)) { + $databases = explode("\n", rtrim(str_replace("\r", "", $databases), "\n")); + } + } + foreach ((array) $databases as $db) { if ($connection->select_db($db)) { - if ($_POST["format"] == "sql" && ereg('CREATE', $style) && ($result = $connection->query("SHOW CREATE DATABASE " . idf_escape($db)))) { + if ($_POST["format"] == "sql" && ereg('CREATE', $style) && ($create = $connection->result("SHOW CREATE DATABASE " . idf_escape($db), 1))) { if ($style == "DROP+CREATE") { echo "DROP DATABASE IF EXISTS " . idf_escape($db) . ";\n"; } - $create = $connection->result($result, 1); echo ($style == "CREATE+ALTER" ? preg_replace('~^CREATE DATABASE ~', '\\0IF NOT EXISTS ', $create) : $create) . ";\n"; } if ($_POST["format"] == "sql") { @@ -36,7 +48,7 @@ SET sql_mode = 'NO_AUTO_VALUE_ON_ZERO'; $result = $connection->query("SHOW $routine STATUS WHERE Db = " . $connection->quote($db)); while ($row = $result->fetch_assoc()) { $out .= ($style != 'DROP+CREATE' ? "DROP $routine IF EXISTS " . idf_escape($row["Name"]) . ";;\n" : "") - . $connection->result($connection->query("SHOW CREATE $routine " . idf_escape($row["Name"])), 2) . ";;\n\n"; + . $connection->result("SHOW CREATE $routine " . idf_escape($row["Name"]), 2) . ";;\n\n"; } } } @@ -45,7 +57,7 @@ SET sql_mode = 'NO_AUTO_VALUE_ON_ZERO'; if ($result) { while ($row = $result->fetch_assoc()) { $out .= ($style != 'DROP+CREATE' ? "DROP EVENT IF EXISTS " . idf_escape($row["Name"]) . ";;\n" : "") - . $connection->result($connection->query("SHOW CREATE EVENT " . idf_escape($row["Name"])), 3) . ";;\n\n"; + . $connection->result("SHOW CREATE EVENT " . idf_escape($row["Name"]), 3) . ";;\n\n"; } } } @@ -56,6 +68,7 @@ SET sql_mode = 'NO_AUTO_VALUE_ON_ZERO'; if ($_POST["table_style"] || $_POST["data_style"]) { $views = array(); + //! defer number of rows to JavaScript foreach (table_status() as $row) { $table = (DB == "" || in_array($row["Name"], (array) $_POST["tables"])); $data = (DB == "" || in_array($row["Name"], (array) $_POST["data"])); @@ -143,22 +156,26 @@ page_header(lang('Export'), "", ($_GET["export"] != "" ? array("table" => $_GET[ $db_style = array('', 'USE', 'DROP+CREATE', 'CREATE'); $table_style = array('', 'DROP+CREATE', 'CREATE'); $data_style = array('', 'TRUNCATE+INSERT', 'INSERT', 'INSERT+UPDATE'); -if ($connection->server_info >= 5) { +if (support("routine")) { $db_style[] = 'CREATE+ALTER'; $table_style[] = 'CREATE+ALTER'; } -echo "

" . lang('Name') . "" . lang('Schedule') . "" . lang('Start') . "" . lang('End') . "
" . lang('Output') . "" . $adminer->dumpOutput(0) . "\n"; // token is not needed but checked in bootstrap for all POST data //! read from cookie -echo "
" . lang('Format') . "" . $adminer->dumpFormat(0) . "\n"; -echo "
" . lang('Database') . "" . html_select('db_style', $db_style, (DB != "" ? '' : 'CREATE')); -if ($connection->server_info >= 5) { - $checked = $_GET["dump"] == ""; - echo checkbox("routines", 1, $checked, lang('Routines')); - if ($connection->server_info >= 5.1) { - echo checkbox("events", 1, $checked, lang('Events')); - } +parse_str($_COOKIE["adminer_export"], $row); +if (!$row) { + $row = array("output" => "text", "format" => "sql", "db_style" => (DB != "" ? "" : "CREATE"), "table_style" => "DROP+CREATE", "data_style" => "INSERT"); } -echo "
" . lang('Tables') . "" . html_select('table_style', $table_style, 'DROP+CREATE'); -echo "
" . lang('Data') . "" . html_select('data_style', $data_style, 'INSERT'); +echo "
" . lang('Output') . "" . $adminer->dumpOutput(0, $row["output"]) . "\n"; +echo "
" . lang('Format') . "" . $adminer->dumpFormat(0, $row["format"]) . "\n"; +echo "
" . lang('Database') . "" . html_select('db_style', $db_style, $row["db_style"]); +$checked = ($_GET["dump"] == ""); +if (support("routine")) { + echo checkbox("routines", 1, $checked, lang('Routines')); +} +if (support("event")) { + echo checkbox("events", 1, $checked, lang('Events')); +} +echo "
" . lang('Tables') . "" . html_select('table_style', $table_style, $row["table_style"]); +echo "
" . lang('Data') . "" . html_select('data_style', $data_style, $row["data_style"]); ?>

@@ -178,22 +195,27 @@ if (DB != "") { $prefix = ereg_replace("_.*", "", $name); $checked = ($TABLE == "" || $TABLE == (substr($TABLE, -1) == "%" ? "$prefix%" : $name)); //! % may be part of table name $print = "

" . checkbox("tables[]", $name, $checked, $name, "formUncheck('check-tables');"); - if (!$row["Engine"]) { + if (eregi("view", $row["Engine"])) { $views .= "$print\n"; } else { - echo "$print\n"; + echo "$print\n"; } $prefixes[$prefix]++; } echo $views; } else { echo "
" . checkbox("databases[]", $db, $TABLE == "" || $TABLE == "$prefix%", $db, "formUncheck('check-databases');") . "\n"; - $prefixes[$prefix]++; + $databases = get_databases(); + if ($databases) { + foreach ($databases as $db) { + if (!information_schema($db)) { + $prefix = ereg_replace("_.*", "", $db); + echo "
" . checkbox("databases[]", $db, $TABLE == "" || $TABLE == "$prefix%", $db, "formUncheck('check-databases');") . "\n"; + $prefixes[$prefix]++; + } } + } else { + echo "
"; } } ?> diff --git a/adminer/edit.inc.php b/adminer/edit.inc.php index f0fb7781..a85587bf 100644 --- a/adminer/edit.inc.php +++ b/adminer/edit.inc.php @@ -14,30 +14,22 @@ if ($_POST && !$error && !isset($_GET["select"])) { $location = ($update ? null : $_SERVER["REQUEST_URI"]); } elseif (!ereg('^.+&select=.+$', $location)) { $location = ME . "select=" . urlencode($TABLE); - $i = 0; // append &set converted to &where - foreach ((array) $_GET["set"] as $key => $val) { - if ($val == $_POST["fields"][$key]) { - $location .= where_link($i++, bracket_escape($key, "back"), $val); - } - } } if (isset($_POST["delete"])) { - query_redirect("DELETE FROM " . idf_escape($_GET["edit"]) . " WHERE $where LIMIT 1", $location, lang('Item has been deleted.')); + query_redirect("DELETE" . limit1("FROM " . idf_escape($_GET["edit"]) . "\nWHERE $where"), $location, lang('Item has been deleted.')); } else { $set = array(); foreach ($fields as $name => $field) { $val = process_input($field); - if (!$update) { - $set[idf_escape($name)] = ($val !== false ? $val : "''"); - } elseif ($val !== false) { - $set[] = "\n" . idf_escape($name) . " = $val"; + if ($val !== false && $val !== null) { + $set[idf_escape($name)] = ($update ? "\n" . idf_escape($name) . " = $val" : $val); } } - if (!$set) { - redirect($location); - } if ($update) { - query_redirect("UPDATE " . idf_escape($TABLE) . " SET" . implode(",", $set) . "\nWHERE $where\nLIMIT 1", $location, lang('Item has been updated.')); + if (!$set) { + redirect($location); + } + query_redirect("UPDATE" . limit1(idf_escape($TABLE) . " SET" . implode(",", $set) . "\nWHERE $where"), $location, lang('Item has been updated.')); } else { query_redirect("INSERT INTO " . idf_escape($TABLE) . " (" . implode(", ", array_keys($set)) . ")\nVALUES (" . implode(", ", $set) . ")", $location, lang('Item has been inserted.')); } @@ -64,8 +56,11 @@ if ($_POST["save"]) { } $row = array(); if ($select) { - $result = $connection->query("SELECT " . implode(", ", $select) . " FROM " . idf_escape($TABLE) . " WHERE $where " . (isset($_GET["select"]) ? "HAVING COUNT(*) = 1" : "LIMIT 1")); + $result = $connection->query("SELECT" . limit(implode(", ", $select) . " FROM " . idf_escape($TABLE) . " WHERE $where", (isset($_GET["select"]) ? 2 : 1))); $row = $result->fetch_assoc(); + if (isset($_GET["select"]) && $result->fetch_assoc()) { + $row = false; + } } } ?> @@ -80,7 +75,7 @@ if ($fields) { $default = $_GET["set"][bracket_escape($name)]; $value = (isset($row) ? ($row[$name] != "" && ereg("enum|set", $field["type"]) ? intval($row[$name]) : $row[$name]) - : ($_POST["clone"] && $field["auto_increment"] ? "" : (isset($_GET["select"]) ? false : (isset($default) ? $default : $field["default"]))) + : (!$update && $field["auto_increment"] ? "" : (isset($_GET["select"]) ? false : (isset($default) ? $default : $field["default"]))) ); if (!$_POST["save"] && is_string($value)) { $value = $adminer->editVal($value, $field); diff --git a/adminer/foreign.inc.php b/adminer/foreign.inc.php index bc117a1c..200d3a03 100644 --- a/adminer/foreign.inc.php +++ b/adminer/foreign.inc.php @@ -2,7 +2,7 @@ $TABLE = $_GET["foreign"]; if ($_POST && !$error && !$_POST["add"] && !$_POST["change"] && !$_POST["change-js"]) { if ($_POST["drop"]) { - query_redirect("ALTER TABLE " . idf_escape($TABLE) . "\nDROP FOREIGN KEY " . idf_escape($_GET["name"]), ME . "table=" . urlencode($TABLE), lang('Foreign key has been dropped.')); + query_redirect("ALTER TABLE " . idf_escape($TABLE) . "\nDROP " . ($driver == "sql" ? "FOREIGN KEY " : "CONSTRAINT ") . idf_escape($_GET["name"]), ME . "table=" . urlencode($TABLE), lang('Foreign key has been dropped.')); } else { $source = array_filter($_POST["source"], 'strlen'); ksort($source); // enforce input order @@ -39,13 +39,19 @@ if ($_POST) { $source = array_keys(fields($TABLE)); //! no text and blob $target = ($TABLE === $row["table"] ? $source : array_keys(fields($row["table"]))); +$referencable = array(); +foreach (table_status() as $name => $table_status) { + if (fk_support($table_status)) { + $referencable[] = $name; + } +} ?>

: - +

diff --git a/adminer/include/adminer.inc.php b/adminer/include/adminer.inc.php index ccbbe775..895793df 100644 --- a/adminer/include/adminer.inc.php +++ b/adminer/include/adminer.inc.php @@ -1,13 +1,7 @@ ", "<=", ">=", "!=", "LIKE", "REGEXP", "IN", "IS NULL", "NOT LIKE", "NOT REGEXP", "NOT IN", "IS NOT NULL"); + /** @var array operators used in select, null for all operators */ + var $operators; /** Name in title and navigation * @return string @@ -20,7 +14,7 @@ class Adminer { * @return array ($server, $username, $password) */ function credentials() { - return array($_GET["server"], $_SESSION["usernames"][$_GET["server"]], $_SESSION["passwords"][$_GET["server"]]); + return array(SERVER, $_GET["username"], get_session("passwords")); } /** Get key used for permanent login @@ -39,14 +33,15 @@ class Adminer { } /** Print login form - * @param string * @return null */ - function loginForm($username) { + function loginForm() { + global $drivers, $possible_drivers; ?>
- +
"> -
+
3 ? html_select("driver", $drivers, DRIVER) : "" . reset($drivers)); ?>
+
">
'; $links = array("select" => lang('Select data'), "table" => lang('Show structure')); - if (isset($tableStatus["Rows"])) { - $links["create"] = lang('Alter table'); - } else { + if (eregi("view", $tableStatus["Engine"])) { $links["view"] = lang('Alter view'); + } else { + $links["create"] = lang('Alter table'); } if (isset($set)) { $links["edit"] = lang('New item'); @@ -126,7 +121,8 @@ class Adminer { * @return string */ function selectQuery($query) { - return "

" . h(str_replace("\n", " ", $query)) . " " . lang('Edit') . "\n"; + global $driver; + return "

" . h(str_replace("\n", " ", $query)) . " " . lang('Edit') . "\n"; } /** Description of a row in a table @@ -154,7 +150,7 @@ class Adminer { */ function selectVal($val, $link, $field) { $return = ($val != "NULL" && $field["type"] == "char" ? "$val" : $val); - if (ereg('blob|binary', $field["type"]) && !is_utf8($val)) { + if (ereg('binary|blob|bytea', $field["type"]) && !is_utf8($val)) { $return = lang('%d byte(s)', strlen($val)); } return ($link ? "$return" : $return); @@ -175,17 +171,18 @@ class Adminer { * @return null */ function selectColumnsPrint($select, $columns) { + global $functions, $grouping; print_fieldset("select", lang('Select'), $select); $i = 0; - $fun_group = array(lang('Functions') => $this->functions, lang('Aggregation') => $this->grouping); + $fun_group = array(lang('Functions') => $functions, lang('Aggregation') => $grouping); foreach ($select as $key => $val) { $val = $_GET["columns"][$key]; echo "

" . html_select("columns[$i][fun]", array(-1 => "") + $fun_group, $val["fun"]); - echo "
\n"; + echo "()\n"; $i++; } - echo "
" . html_select("columns[$i][fun]", array(-1 => "") + $fun_group, "", "this.nextSibling.onchange();"); - echo "
\n"; + echo "
" . html_select("columns[$i][fun]", array(-1 => "") + $fun_group, "", "this.nextSibling.nextSibling.onchange();"); + echo "()
\n"; echo "\n"; } @@ -208,13 +205,13 @@ class Adminer { $i = 0; foreach ((array) $_GET["where"] as $val) { if ("$val[col]$val[val]" != "" && in_array($val["op"], $this->operators)) { - echo "
"; + echo "
"; echo html_select("where[$i][op]", $this->operators, $val["op"]); echo "
\n"; $i++; } } - echo "
"; + echo "
"; echo html_select("where[$i][op]", $this->operators); echo "
\n"; echo "
\n"; @@ -286,12 +283,13 @@ class Adminer { * @return array (array(select_expressions), array(group_expressions)) */ function selectColumnsProcess($columns, $indexes) { + global $functions, $grouping; $select = array(); // select expressions, empty for * $group = array(); // expressions without aggregation - will be used for GROUP BY if an aggregation function is used foreach ((array) $_GET["columns"] as $key => $val) { - if ($val["fun"] == "count" || (isset($columns[$val["col"]]) && (!$val["fun"] || in_array($val["fun"], $this->functions) || in_array($val["fun"], $this->grouping)))) { + if ($val["fun"] == "count" || (isset($columns[$val["col"]]) && (!$val["fun"] || in_array($val["fun"], $functions) || in_array($val["fun"], $grouping)))) { $select[$key] = apply_sql_function($val["fun"], (isset($columns[$val["col"]]) ? idf_escape($val["col"]) : "*")); - if (!in_array($val["fun"], $this->grouping)) { + if (!in_array($val["fun"], $grouping)) { $group[] = $select[$key]; } } @@ -314,8 +312,15 @@ class Adminer { } foreach ((array) $_GET["where"] as $val) { if ("$val[col]$val[val]" != "" && in_array($val["op"], $this->operators)) { - $in = process_length($val["val"]); - $cond = " $val[op]" . (ereg('NULL$', $val["op"]) ? "" : (ereg('IN$', $val["op"]) ? " (" . ($in != "" ? $in : "NULL") . ")" : " " . $this->processInput($fields[$val["col"]], $val["val"]))); + $cond = " $val[op]"; + if (ereg('IN$', $val["op"])) { + $in = process_length($val["val"]); + $cond .= " (" . ($in != "" ? $in : "NULL") . ")"; + } elseif ($val["op"] == "LIKE %%") { + $cond = " LIKE " . $this->processInput($fields[$val["col"]], "%$val[val]%"); + } elseif (!ereg('NULL$', $val["op"])) { + $cond .= " " . $this->processInput($fields[$val["col"]], $val["val"]); + } if ($val["col"] != "") { $return[] = idf_escape($val["col"]) . $cond; } else { @@ -342,8 +347,8 @@ class Adminer { function selectOrderProcess($fields, $indexes) { $return = array(); foreach ((array) $_GET["order"] as $key => $val) { - if (isset($fields[$val]) || preg_match('~^((COUNT\\(DISTINCT |[A-Z0-9_]+\\()`(?:[^`]|``)+`\\)|COUNT\\(\\*\\))$~', $val)) { - $return[] = idf_escape($val) . (isset($_GET["desc"][$key]) ? " DESC" : ""); + if (isset($fields[$val]) || preg_match('~^((COUNT\\(DISTINCT |[A-Z0-9_]+\\()(`(?:[^`]|``)+`|"(?:[^"]|"")+")\\)|COUNT\\(\\*\\))$~', $val)) { //! MS SQL uses [] + $return[] = (isset($fields[$val]) ? idf_escape($val) : $val) . (isset($_GET["desc"][$key]) ? " DESC" : ""); } } return $return; @@ -377,10 +382,12 @@ class Adminer { * @return string */ function messageQuery($query) { + global $driver; restart_session(); $id = "sql-" . count($_SESSION["messages"]); - $_SESSION["history"][$_GET["server"]][DB][] = (strlen($query) > 1e6 ? ereg_replace('[\x80-\xFF]+$', '', substr($query, 0, 1e6)) . "\n..." : $query); // [\x80-\xFF] - valid UTF-8, \n - can end by one-line comment - return " " . lang('SQL command') . "'; + $history = &get_session("history"); + $history[DB][] = (strlen($query) > 1e6 ? ereg_replace('[\x80-\xFF]+$', '', substr($query, 0, 1e6)) . "\n..." : $query); // [\x80-\xFF] - valid UTF-8, \n - can end by one-line comment + return " " . lang('SQL command') . "'; } /** Functions displayed in edit form @@ -388,31 +395,18 @@ class Adminer { * @return array */ function editFunctions($field) { - $return = array(""); - if (ereg('char|date|time', $field["type"])) { - $return = (ereg('char', $field["type"]) ? array("", "md5", "sha1", "password", "encrypt", "uuid") : array("", "now")); //! JavaScript for disabling maxlength - } - if (!isset($_GET["call"]) && (isset($_GET["select"]) || where($_GET))) { - // relative functions - if (ereg('int|float|double|decimal', $field["type"])) { - $return = array("", "+", "-"); - } - if (ereg('date', $field["type"])) { - $return[] = "+ interval"; - $return[] = "- interval"; - } - if (ereg('time', $field["type"])) { - $return[] = "addtime"; - $return[] = "subtime"; - } - if (ereg('char|text', $field["type"])) { - $return[] = "concat"; + global $edit_functions; + $return = ($field["null"] ? "/NULL" : ""); + foreach ($edit_functions as $key => $functions) { + if (!$key || (!isset($_GET["call"]) && (isset($_GET["select"]) || where($_GET)))) { // relative functions + foreach ($functions as $pattern => $val) { + if (!$pattern || ereg($pattern, $field["type"])) { + $return .= "/$val"; + } + } } } - if ($field["null"]) { - array_unshift($return, "NULL"); - } - return $return; + return explode("/", $return); } /** Get options to display edit field @@ -441,9 +435,9 @@ class Adminer { global $connection; $name = $field["field"]; $return = $connection->quote($value); - if (ereg('^(now|uuid)$', $function)) { + if (ereg('^(now|getdate|uuid)$', $function)) { $return = "$function()"; - } elseif (ereg('^[+-]$', $function)) { + } elseif (ereg('^([+-]|\\|\\|)$', $function)) { $return = idf_escape($name) . " $function $return"; } elseif (ereg('^[+-] interval$', $function)) { $return = idf_escape($name) . " $function " . (preg_match("~^([0-9]+|'[0-9.: -]') [A-Z_]+$~i", $value) ? $value : $return); @@ -457,9 +451,10 @@ class Adminer { /** Returns export output options * @param bool generate select (otherwise radio) + * @param string * @return string */ - function dumpOutput($select) { + function dumpOutput($select, $value = "") { $return = array('text' => lang('open'), 'file' => lang('save')); if (function_exists('gzencode')) { $return['gz'] = 'gzip'; @@ -468,15 +463,16 @@ class Adminer { $return['bz2'] = 'bzip2'; } // ZipArchive requires temporary file, ZIP can be created by gzcompress - see PEAR File_Archive - return html_select("output", $return, "text", $select); + return html_select("output", $return, $value, $select); } /** Returns export format options * @param bool generate select (otherwise radio) + * @param string * @return string */ - function dumpFormat($select) { - return html_select("format", array('sql' => 'SQL', 'csv' => 'CSV'), "sql", $select); + function dumpFormat($select, $value = "") { + return html_select("format", array('sql' => 'SQL', 'csv' => 'CSV,', 'csv;' => 'CSV;'), $value, $select); } /** Prints navigation after Adminer title @@ -484,7 +480,7 @@ class Adminer { * @return null */ function navigation($missing) { - global $VERSION, $connection; + global $VERSION, $connection, $token; ?>

name(); ?> @@ -499,19 +495,18 @@ class Adminer {

"> -"> +

- -"> + "(" . lang('database') . ")") + $databases, DB, "this.form.submit();") : ''); ?> - > +>

\n"; - foreach ($tables as $table) { + foreach ($tables as $table => $type) { echo '' . bold(lang('select'), $_GET["select"] == $table) . ' '; echo '' . bold($this->tableName(array("Name" => $table)), $_GET["table"] == $table) . "
\n"; //! Adminer::tableName may work with full table status } @@ -542,3 +537,6 @@ class Adminer { } $adminer = (function_exists('adminer_object') ? adminer_object() : new Adminer); +if (!isset($adminer->operators)) { + $adminer->operators = $operators; +} diff --git a/adminer/include/auth.inc.php b/adminer/include/auth.inc.php index bc6bb550..592cbce4 100644 --- a/adminer/include/auth.inc.php +++ b/adminer/include/auth.inc.php @@ -1,88 +1,100 @@ permanentLogin())) + . ":" . base64_encode($_POST["driver"]) ); } - if (count($_POST) == ($_POST["permanent"] ? 4 : 3)) { // 3 - server, username, password - $location = ((string) $_GET["server"] === $_POST["server"] ? remove_from_uri(session_name()) : preg_replace('~^([^?]*).*~', '\\1', ME) . ($_POST["server"] != "" ? '?server=' . urlencode($_POST["server"]) : '')); - if (SID_FORM) { - $pos = strpos($location, '?'); - $location = ($pos ? substr_replace($location, SID . "&", $pos + 1, 0) : "$location?" . SID); - } - redirect($location); + if (count($_POST) == ($_POST["permanent"] ? 5 : 4) // 4 - driver, server, username, password + || DRIVER != $_POST["driver"] + || SERVER != $_POST["server"] + || $_GET["username"] !== $_POST["username"] // "0" == "00" + ) { + preg_match('~([^?]*)\\??(.*)~', remove_from_uri(implode("|", array_keys($drivers)) . "|username|" . session_name()), $match); + redirect("$match[1]?" + . (SID ? SID . "&" : "") + . ($_POST["driver"] != "server" || $_POST["server"] != "" ? urlencode($_POST["driver"]) . "=" . urlencode($_POST["server"]) . "&" : "") + . "username=" . urlencode($_POST["username"]) + . ($match[2] ? "&$match[2]" : "") + ); } - $_GET["server"] = $_POST["server"]; //! used also in ME } elseif ($_POST["logout"]) { - $token = $_SESSION["tokens"][$_GET["server"]]; if ($token && $_POST["token"] != $token) { page_header(lang('Logout'), lang('Invalid CSRF token. Send the form again.')); page_footer("db"); exit; } else { - foreach (array("usernames", "passwords", "databases", "tokens", "history") as $val) { - unset($_SESSION[$val][$_GET["server"]]); - } - if (!isset($_SESSION["passwords"])) { // don't require login to logout - $_SESSION["passwords"] = array(); + foreach (array("passwords", "databases", "history") as $key) { + set_session($key, null); } cookie("adminer_permanent", ""); - redirect(substr(preg_replace('~db=[^&]*&~', '', ME), 0, -1), lang('Logout successful.')); + redirect(substr(preg_replace('~(username|db)=[^&]*&~', '', ME), 0, -1), lang('Logout successful.')); } -} elseif ($_COOKIE["adminer_permanent"] && !isset($_SESSION["usernames"][$_GET["server"]])) { - list($server, $username, $cipher) = array_map('base64_decode', explode(":", $_COOKIE["adminer_permanent"])); - if (($_GET["server"] == "" && !$_POST) || $server == $_GET["server"]) { +} elseif ($_COOKIE["adminer_permanent"]) { + list($server, $username, $cipher, $system) = array_map('base64_decode', explode(":", $_COOKIE["adminer_permanent"])); // $driver is a global variable + if ($server == SERVER && $username === $_GET["username"] && $system == DRIVER) { session_regenerate_id(); // defense against session fixation - $_SESSION["usernames"][$server] = $username; - $_SESSION["passwords"][$server] = decrypt_string($cipher, $adminer->permanentLogin()); - if ($server != $_GET["server"]) { - redirect(preg_replace('~^([^?]*).*~', '\\1', ME) . '?server=' . urlencode($server)); - } + set_session("passwords", decrypt_string($cipher, $adminer->permanentLogin())); } + //! redirect ?select=tab } function auth_error($exception = null) { - global $connection, $adminer; + global $connection, $adminer, $token; $session_name = session_name(); - $username = $_SESSION["usernames"][$_GET["server"]]; - unset($_SESSION["usernames"][$_GET["server"]]); - page_header(lang('Login'), (isset($username) ? h($exception ? $exception->getMessage() : (is_string($connection) ? $connection : lang('Invalid credentials.'))) - : (!$_COOKIE[$session_name] && $_GET[$session_name] && ini_get("session.use_only_cookies") ? lang('Session support must be enabled.') - : (($_COOKIE[$session_name] || $_GET[$session_name]) && !isset($_SESSION["passwords"]) ? lang('Session expired, please login again.') - : ""))), null); + $error = ""; + if (!$_COOKIE[$session_name] && $_GET[$session_name] && ini_bool("session.use_only_cookies")) { + $error = lang('Session support must be enabled.'); + } elseif (isset($_GET["username"])) { + if (($_COOKIE[$session_name] || $_GET[$session_name]) && !$token) { + $error = lang('Session expired, please login again.'); + } else { + $password = get_session("passwords"); + if (isset($password)) { + $error = h($exception ? $exception->getMessage() : (is_string($connection) ? $connection : lang('Invalid credentials.'))); + } + } + } + page_header(lang('Login'), $error, null); echo "
\n"; - $adminer->loginForm($username); + $adminer->loginForm(); echo "
"; - hidden_fields($_POST, array("server", "username", "password")); // expired session + hidden_fields($_POST, array("driver", "server", "username", "password", "permanent")); // expired session echo "
\n"; echo "
\n"; page_footer("auth"); } -$username = &$_SESSION["usernames"][$_GET["server"]]; -if (!isset($username)) { - $username = $_GET["username"]; // default username can be passed in URL +if (isset($_GET["username"]) && class_exists("Min_DB")) { // doesn't exists with passing wrong driver + $connection = connect(); } -$connection = (isset($username) ? connect() : ''); -if (is_string($connection) || !$adminer->login($username, $_SESSION["passwords"][$_GET["server"]])) { +if (is_string($connection) || !$adminer->login($_GET["username"], get_session("passwords"))) { auth_error(); exit; } -unset($username); -if (!$_SESSION["tokens"][$_GET["server"]]) { - $_SESSION["tokens"][$_GET["server"]] = rand(1, 1e6); // defense against cross-site request forgery -} +$token = $_SESSION["token"]; ///< @var string CSRF protection if (isset($_POST["server"]) && $_POST["token"]) { - $_POST["token"] = $_SESSION["tokens"][$_GET["server"]]; + $_POST["token"] = $token; // reset token after explicit login } -$token = $_SESSION["tokens"][$_GET["server"]]; ///< @var string CSRF protection $error = ($_POST ///< @var string ? ($_POST["token"] == $token ? "" : lang('Invalid CSRF token. Send the form again.')) : ($_SERVER["REQUEST_METHOD"] != "POST" ? "" : lang('Too big POST data. Reduce the data or increase the %s configuration directive.', '"post_max_size"')) // posted form with no data means that post_max_size exceeded because Adminer always sends token at least diff --git a/adminer/include/bootstrap.inc.php b/adminer/include/bootstrap.inc.php index 38668f4c..6614718d 100644 --- a/adminer/include/bootstrap.inc.php +++ b/adminer/include/bootstrap.inc.php @@ -39,12 +39,14 @@ if (isset($_GET["file"])) { exit; } +include "../adminer/include/functions.inc.php"; + if (!isset($_SERVER["REQUEST_URI"])) { - $_SERVER["REQUEST_URI"] = $_SERVER["ORIG_PATH_INFO"] . ($_SERVER["QUERY_STRING"] != "" ? "?$_SERVER[QUERY_STRING]" : ""); + $_SERVER["REQUEST_URI"] = $_SERVER["ORIG_PATH_INFO"] . ($_SERVER["QUERY_STRING"] != "" ? "?$_SERVER[QUERY_STRING]" : ""); // IIS 5 compatibility } @ini_set("session.use_trans_sid", false); // protect links in export, @ - may be disabled -if (!ini_get("session.auto_start")) { +if (!ini_bool("session.auto_start")) { session_name("adminer_sid"); // use specific session name to get own namespace $params = array(0, preg_replace('~\\?.*~', '', $_SERVER["REQUEST_URI"]), "", $_SERVER["HTTPS"] && strcasecmp($_SERVER["HTTPS"], "off")); if (version_compare(PHP_VERSION, '5.2.0') >= 0) { @@ -75,19 +77,26 @@ if (function_exists("set_magic_quotes_runtime")) { } @set_time_limit(0); // @ - can be disabled -include "../adminer/include/version.inc.php"; -include "../adminer/include/functions.inc.php"; - -define("DB", $_GET["db"]); // for the sake of speed and size -define("SID_FORM", SID && !ini_get("session.use_only_cookies") ? '' : ''); -define("ME", preg_replace('~^[^?]*/([^?]*).*~', '\\1', $_SERVER["REQUEST_URI"]) . '?' . (SID_FORM ? SID . '&' : '') . ($_GET["server"] != "" ? 'server=' . urlencode($_GET["server"]) . '&' : '') . (DB != "" ? 'db=' . urlencode(DB) . '&' : '')); - include "../adminer/include/lang.inc.php"; include "../adminer/lang/$LANG.inc.php"; +include "../adminer/include/pdo.inc.php"; +include "../adminer/drivers/sqlite.inc.php"; +include "../adminer/drivers/pgsql.inc.php"; +include "../adminer/drivers/mssql.inc.php"; +include "../adminer/drivers/mysql.inc.php"; // must be included as last driver + +define("SERVER", $_GET[DRIVER]); // read from pgsql=localhost +define("DB", $_GET["db"]); // for the sake of speed and size +define("ME", preg_replace('~^[^?]*/([^?]*).*~', '\\1', $_SERVER["REQUEST_URI"]) . '?' + . (SID && !$_COOKIE ? SID . '&' : '') // !$_COOKIE - don't pass SID with permanent login + . (SERVER !== null ? DRIVER . "=" . urlencode(SERVER) . '&' : '') + . (isset($_GET["username"]) ? "username=" . urlencode($_GET["username"]) . '&' : '') + . (DB != "" ? 'db=' . urlencode(DB) . '&' : '') +); + +include "../adminer/include/version.inc.php"; include "./include/adminer.inc.php"; include "../adminer/include/design.inc.php"; -include "../adminer/include/pdo.inc.php"; -include "../adminer/include/mysql.inc.php"; include "../adminer/include/xxtea.inc.php"; include "../adminer/include/auth.inc.php"; include "./include/connect.inc.php"; @@ -95,7 +104,7 @@ include "./include/editing.inc.php"; include "./include/export.inc.php"; session_cache_limiter(""); // to allow restarting session -if (!ini_get("session.use_cookies") || @ini_set("session.use_cookies", false) !== false) { // @ - may be disabled +if (!ini_bool("session.use_cookies") || @ini_set("session.use_cookies", false) !== false) { // @ - may be disabled session_write_close(); // improves concurrency if a user opens several pages at once, may be restarted later } diff --git a/adminer/include/connect.inc.php b/adminer/include/connect.inc.php index 7ebdbf93..9bab9fc8 100644 --- a/adminer/include/connect.inc.php +++ b/adminer/include/connect.inc.php @@ -1,11 +1,12 @@ error); } - page_header(lang('Select database'), $error, null); - echo "

"; + page_header(lang('Select database'), $error, false); + echo "

" . lang('Create new database') . "\n"; foreach (array( - 'database' => lang('Create new database'), 'privileges' => lang('Privileges'), 'processlist' => lang('Process list'), 'variables' => lang('Variables'), 'status' => lang('Status'), ) as $key => $val) { - echo "$val\n"; + if (support($key)) { + echo "$val\n"; + } } - echo "

" . lang('MySQL version: %s through PHP extension %s', "server_info < 4.1 ? " class='binary'" : "") . ">$connection->server_info", "$connection->extension") . "\n"; - echo "

" . lang('Logged as: %s', "" . h($connection->result($connection->query("SELECT USER()"))) . "") . "\n"; + echo "

" . lang('%s version: %s through PHP extension %s', $drivers[DRIVER], "$connection->server_info", "$connection->extension") . "\n"; + echo "

" . lang('Logged as: %s', "" . h(logged_user()) . "") . "\n"; $databases = get_databases(); if ($databases) { $collations = collations(); echo "

\n"; echo "\n"; - echo "\n"; + echo "\n"; foreach ($databases as $db) { $root = h(ME) . "db=" . urlencode($db); echo "
 " . lang('Database') . "" . lang('Collation') . "
 " . lang('Database') . "" . lang('Collation') . "" . lang('Tables') . "
" . checkbox("db[]", $db, in_array($db, (array) $_POST["db"])); echo "" . h($db) . ""; echo "" . nbsp(db_collation($db, $collations)) . ""; + echo "?"; echo "\n"; } echo "
\n"; @@ -46,6 +49,11 @@ function connect_error() { } } page_footer("db"); + echo "\n"; } if (isset($_GET["status"])) { @@ -53,7 +61,7 @@ if (isset($_GET["status"])) { } if (!(DB != "" ? $connection->select_db(DB) : isset($_GET["sql"]) || isset($_GET["dump"]) || isset($_GET["database"]) || isset($_GET["processlist"]) || isset($_GET["privileges"]) || isset($_GET["user"]) || isset($_GET["variables"]))) { if (DB != "") { - unset($_SESSION["databases"][$_GET["server"]]); + set_session("databases", null); } connect_error(); // separate function to catch SQLite error exit; diff --git a/adminer/include/design.inc.php b/adminer/include/design.inc.php index 09c41048..a0b39477 100644 --- a/adminer/include/design.inc.php +++ b/adminer/include/design.inc.php @@ -1,8 +1,15 @@ "link=desc", "key2" => array("link", "desc")), null for nothing, false for driver only, true for driver and server +* @param string used after colon in title and heading +* @return null +*/ function page_header($title, $error = "", $breadcrumb = array(), $title2 = "") { - global $LANG, $VERSION, $adminer, $connection; + global $LANG, $VERSION, $adminer, $connection, $drivers; header("Content-Type: text/html; charset=utf-8"); - header("X-Frame-Options: deny"); // ClickJacking protection in IE8, Safari 4, Chrome 2, NoScript plugin + header("X-Frame-Options: deny"); // ClickJacking protection in IE8, Safari 4, Chrome 2, Firefox NoScript plugin $title_all = $title . ($title2 != "" ? ": " . h($title2) : ""); ?> @@ -10,34 +17,41 @@ function page_header($title, $error = "", $breadcrumb = array(), $title2 = "") { -<?php echo $title_all . ($_GET["server"] != "" && $_GET["server"] != "localhost" ? h(" - $_GET[server]") : "") . " - " . $adminer->name(); ?> +<?php echo $title_all . (SERVER != "" && SERVER != "localhost" ? h(" - " . SERVER) : "") . " - " . $adminer->name(); ?> -"> +');">
' . $drivers[DRIVER] . ' » '; $link = substr(preg_replace('~db=[^&]*&~', '', ME), 0, -1); - echo '

$title_all

\n"; restart_session(); @@ -45,10 +59,7 @@ function page_header($title, $error = "", $breadcrumb = array(), $title2 = "") { echo "
" . implode("
\n
", $_SESSION["messages"]) . "
\n"; $_SESSION["messages"] = array(); } - if (!$_POST && !isset($_SESSION["passwords"])) { // used in auth - $_SESSION["passwords"] = array(); - } - $databases = &$_SESSION["databases"][$_GET["server"]]; + $databases = &get_session("databases"); if (DB != "" && $databases && !in_array(DB, $databases, true)) { $databases = null; } @@ -57,7 +68,11 @@ function page_header($title, $error = "", $breadcrumb = array(), $title2 = "") { } } -function page_footer($missing = false) { +/** Print HTML footer +* @param string auth|db +* @return null +*/ +function page_footer($missing = "") { global $adminer; ?>
@@ -67,4 +82,8 @@ function page_footer($missing = false) { navigation($missing); ?>

field - foreach (table_status_referencable() as $table_name => $table) { - if ($table_name != $self) { + foreach (table_status() as $table_name => $table) { + if ($table_name != $self && fk_support($table)) { foreach (fields($table_name) as $field) { if ($field["primary"]) { if ($return[$table_name]) { // multi column primary key @@ -142,16 +142,19 @@ function process_type($field, $collate = "COLLATE") { /** Create SQL string from field * @param array basic field information * @param array information about field type -* @return string +* @return array array("field", "type", "NULL", "DEFAULT", "ON UPDATE", "COMMENT", "AUTO_INCREMENT") */ function process_field($field, $type_field) { global $connection; - return idf_escape($field["field"]) . process_type($type_field) - . ($field["null"] ? " NULL" : " NOT NULL") // NULL for timestamp - . (!isset($field["default"]) ? "" : " DEFAULT " . ($field["type"] == "timestamp" && eregi("^CURRENT_TIMESTAMP$", $field["default"]) ? $field["default"] : $connection->quote($field["default"]))) - . ($field["on_update"] ? " ON UPDATE $field[on_update]" : "") - . " COMMENT " . $connection->quote($field["comment"]) - ; + return array( + idf_escape($field["field"]), + process_type($type_field), + ($field["null"] ? " NULL" : " NOT NULL"), // NULL for timestamp + (isset($field["default"]) ? " DEFAULT " . ($field["type"] == "timestamp" && eregi("^CURRENT_TIMESTAMP$", $field["default"]) ? $field["default"] : $connection->quote($field["default"])) : ""), + ($field["on_update"] ? " ON UPDATE $field[on_update]" : ""), + (support("comment") && $field["comment"] != "" ? " COMMENT " . $connection->quote($field["comment"]) : ""), + ($field["auto_increment"] ? auto_increment() : ""), + ); } /** Get type class to use in CSS @@ -179,12 +182,11 @@ function type_class($type) { * @param array returned by referencable_primary() * @return bool column comments used */ -function edit_fields($fields, $collations, $type = "TABLE", $allowed = 0, $foreign_keys = array()) { +function edit_fields($fields, $collations, $type = "TABLE", $allowed = 0, $foreign_keys = array(), $comments = false) { global $inout; - $column_comments = false; foreach ($fields as $field) { if ($field["comment"] != "") { - $column_comments = true; + $comments = true; break; } } @@ -197,9 +199,9 @@ function edit_fields($fields, $collations, $type = "TABLE", $allowed = 0, $forei
NULL -A_I +AI "; ?> @@ -209,27 +211,24 @@ function edit_fields($fields, $collations, $type = "TABLE", $allowed = 0, $forei $display = (isset($_POST["add"][$i-1]) || (isset($field["field"]) && !$_POST["drop_col"][$i])); ?> > -" . html_select("fields[$i][inout]", $inout, $field["inout"]); -} -?> +" . html_select("fields[$i][inout]", $inout, $field["inout"]) : ""); ?> " onchange=" 1 ? "" : "editingAddRow(this, $allowed); "); ?>editingNameChange(this);" maxlength="64">"> checked> "; $functions = (isset($_GET["select"]) ? array("orig" => lang('original')) : array()) + $adminer->editFunctions($field); if ($field["type"] == "enum") { echo nbsp($functions[""]) . "" . ($functions["orig"] ? " " : ""); echo $adminer->editInput($_GET["edit"], $field, " name='fields[$name]'", $value); - preg_match_all("~'((?:[^']|'')*)'~", $field["length"], $matches); - foreach ($matches[1] as $i => $val) { - $val = stripcslashes(str_replace("''", "'", $val)); - $checked = (is_int($value) ? $value == $i+1 : $value === $val); - echo " '; - } + enum_input("radio", "fields[$name]", $field, $value); } else { $first = 0; foreach ($functions as $key => $val) { @@ -440,10 +511,10 @@ function input($field, $value, $function) { $checked = (is_int($value) ? ($value >> $i) & 1 : in_array($val, explode(",", $value), true)); echo " '; } - } elseif (ereg('binary|blob', $field["type"]) && ini_get("file_uploads")) { + } elseif (ereg('binary|blob|bytea', $field["type"]) && ini_bool("file_uploads")) { echo ""; } elseif (ereg('text|blob', $field["type"])) { - echo "'; + echo "'; } else { // int(3) is only a display hint $maxlength = (!ereg('int', $field["type"]) && preg_match('~^([0-9]+)(,([0-9]+))?$~', $field["length"], $match) ? ($match[1] + ($match[3] ? 1 : 0) + ($match[2] && !$field["unsigned"] ? 1 : 0)) : ($types[$field["type"]] ? $types[$field["type"]] + ($field["unsigned"] ? 0 : 1) : 0)); @@ -461,23 +532,35 @@ function process_input($field) { $idf = bracket_escape($field["field"]); $function = $_POST["function"][$idf]; $value = $_POST["fields"][$idf]; - if ($field["type"] == "enum" ? $value == -1 : $function == "orig") { - return false; - } elseif ($field["type"] == "enum" || $field["auto_increment"] ? $value == "" : $function == "NULL") { - return "NULL"; - } elseif ($field["type"] == "enum") { + if ($field["type"] == "enum") { + if ($value == -1) { + return false; + } + if ($value == "") { + return "NULL"; + } return intval($value); - } elseif ($field["type"] == "set") { + } + if ($field["auto_increment"] && $value == "") { + return null; + } + if ($function == "orig") { + return false; + } + if ($function == "NULL") { + return "NULL"; + } + if ($field["type"] == "set") { return array_sum((array) $value); - } elseif (ereg('binary|blob', $field["type"]) && ini_get("file_uploads")) { + } + if (ereg('binary|blob|bytea', $field["type"]) && ini_bool("file_uploads")) { $file = get_file("fields-$idf"); if (!is_string($file)) { return false; //! report errors } return $connection->quote($file); - } else { - return $adminer->processInput($field, $value, $function); } + return $adminer->processInput($field, $value, $function); } /** Print results of search in all tables @@ -491,7 +574,7 @@ function search_tables() { foreach (table_status() as $table => $table_status) { $name = $adminer->tableName($table_status); if (isset($table_status["Engine"]) && $name != "" && (!$_POST["tables"] || in_array($table, $_POST["tables"]))) { - $result = $connection->query("SELECT 1 FROM " . idf_escape($table) . " WHERE " . implode(" AND ", $adminer->selectSearchProcess(fields($table), array())) . " LIMIT 1"); + $result = $connection->query("SELECT" . limit("1 FROM " . idf_escape($table) . " WHERE " . implode(" AND ", $adminer->selectSearchProcess(fields($table), array())), 1)); if ($result->num_rows) { if (!$found) { echo "
    \n"; @@ -510,11 +593,11 @@ function search_tables() { */ function dump_csv($row) { foreach ($row as $key => $val) { - if (preg_match("~[\"\n,]~", $val) || $val === "") { + if (preg_match("~[\"\n,;]~", $val) || $val === "") { $row[$key] = '"' . str_replace('"', '""', $val) . '"'; } } - echo implode(",", $row) . "\n"; + echo implode(($_POST["format"] == "csv;" ? ";" : ","), $row) . "\n"; } /** Apply SQL function @@ -523,7 +606,7 @@ function dump_csv($row) { * @return string */ function apply_sql_function($function, $column) { - return ($function ? ($function == "count distinct" ? "COUNT(DISTINCT " : strtoupper("$function(")) . "$column)" : $column); + return ($function ? ($function == "unixepoch" ? "DATETIME($column, '$function')" : ($function == "count distinct" ? "COUNT(DISTINCT " : strtoupper("$function(")) . "$column)") : $column); } /** Check whether the string is e-mail address diff --git a/adminer/include/mysql.inc.php b/adminer/include/mysql.inc.php deleted file mode 100644 index 36499922..00000000 --- a/adminer/include/mysql.inc.php +++ /dev/null @@ -1,406 +0,0 @@ -real_connect( - ($server != "" ? $host : ini_get("mysqli.default_host")), - ("$server$username" != "" ? $username : ini_get("mysqli.default_user")), - ("$server$username$password" != "" ? $password : ini_get("mysqli.default_pw")), - null, - (is_numeric($port) ? $port : ini_get("mysqli.default_port")), - (!is_numeric($port) ? $port : null) - ); - } - - function result($result, $field = 0) { - if (!$result) { - return false; - } - $row = $result->fetch_array(); - return $row[$field]; - } - - function quote($string) { - return "'" . $this->escape_string($string) . "'"; - } - } - -} elseif (extension_loaded("mysql")) { - class Min_DB { - var $extension = "MySQL", $_link, $_result, $server_info, $affected_rows, $error; - - function connect($server, $username, $password) { - $this->_link = @mysql_connect( - ($server != "" ? $server : ini_get("mysql.default_host")), - ("$server$username" != "" ? $username : ini_get("mysql.default_user")), - ("$server$username$password" != "" ? $password : ini_get("mysql.default_password")), - true, - 131072 // CLIENT_MULTI_RESULTS for CALL - ); - if ($this->_link) { - $this->server_info = mysql_get_server_info($this->_link); - } else { - $this->error = mysql_error(); - } - return (bool) $this->_link; - } - - function quote($string) { - return "'" . mysql_real_escape_string($string, $this->_link) . "'"; - } - - function select_db($database) { - return mysql_select_db($database, $this->_link); - } - - function query($query, $unbuffered = false) { - $result = @($unbuffered ? mysql_unbuffered_query($query, $this->_link) : mysql_query($query, $this->_link)); // @ - mute mysql.trace_mode - if (!$result) { - $this->error = mysql_error($this->_link); - return false; - } - if ($result === true) { - $this->affected_rows = mysql_affected_rows($this->_link); - $this->info = mysql_info($this->_link); - return true; - } - return new Min_Result($result); - } - - function multi_query($query) { - return $this->_result = $this->query($query); - } - - function store_result() { - return $this->_result; - } - - function next_result() { - // MySQL extension doesn't support multiple results - return false; - } - - function result($result, $field = 0) { - if (!$result) { - return false; - } - return mysql_result($result->_result, 0, $field); - } - } - - class Min_Result { - var $_result, $_offset = 0, $num_rows; - - function Min_Result($result) { - $this->_result = $result; - $this->num_rows = mysql_num_rows($result); - } - - function fetch_assoc() { - return mysql_fetch_assoc($this->_result); - } - - function fetch_row() { - return mysql_fetch_row($this->_result); - } - - function fetch_field() { - $row = mysql_fetch_field($this->_result, $this->_offset++); - $row->orgtable = $row->table; - $row->orgname = $row->name; - $row->charsetnr = ($row->blob ? 63 : 0); - return $row; - } - - function __destruct() { - mysql_free_result($this->_result); //! is not called in PHP 4 which is a problem with mysql.trace_mode - } - } - -} elseif (extension_loaded("pdo_mysql")) { - class Min_DB extends Min_PDO { - var $extension = "PDO_MySQL"; - - function connect($server, $username, $password) { - $this->dsn("mysql:host=" . str_replace(":", ";unix_socket=", preg_replace('~:([0-9])~', ';port=\\1', $server)), $username, $password); - $this->server_info = $this->result($this->query("SELECT VERSION()")); - return true; - } - - function query($query, $unbuffered = false) { - $this->setAttribute(1000, !$unbuffered); // 1000 - PDO::MYSQL_ATTR_USE_BUFFERED_QUERY - return parent::query($query, $unbuffered); - } - } - -} else { - page_header(lang('No MySQL extension'), lang('None of the supported PHP extensions (%s) are available.', 'MySQLi, MySQL, PDO_MySQL'), null); - page_footer("auth"); - exit; -} - -/** Connect to the database -* @return mixed Min_DB or string for error -*/ -function connect() { - global $adminer; - $connection = new Min_DB; - $credentials = $adminer->credentials(); - if ($connection->connect($credentials[0], $credentials[1], $credentials[2])) { - $connection->query("SET SQL_QUOTE_SHOW_CREATE=1"); - $connection->query("SET NAMES utf8"); - return $connection; - } - return $connection->error; -} - -/** Get cached list of databases -* @param bool -* @return array -*/ -function get_databases($flush = true) { - // SHOW DATABASES can take a very long time so it is cached - $return = &$_SESSION["databases"][$_GET["server"]]; - if (!isset($return)) { - restart_session(); - $return = get_vals("SHOW DATABASES"); - if ($flush) { - ob_flush(); - flush(); - } - } - return $return; -} - -/** Get database collation -* @param string -* @param array result of collations() -* @return array -*/ -function db_collation($db, $collations) { - global $connection; - $return = null; - $result = $connection->query("SHOW CREATE DATABASE " . idf_escape($db)); - if ($result) { - $create = $connection->result($result, 1); - if (preg_match('~ COLLATE ([^ ]+)~', $create, $match)) { - $return = $match[1]; - } elseif (preg_match('~ CHARACTER SET ([^ ]+)~', $create, $match)) { - // default collation - $return = $collations[$match[1]][0]; - } - } - return $return; -} - -/**Get supported engines -* @return array -*/ -function engines() { - global $connection; - $return = array(); - $result = $connection->query("SHOW ENGINES"); - while ($row = $result->fetch_assoc()) { - if (ereg("YES|DEFAULT", $row["Support"])) { - $return[] = $row["Engine"]; - } - } - return $return; -} - -/** Get tables list -* @return array -*/ -function tables_list() { - return get_vals("SHOW TABLES"); -} - -/** Get table status -* @param string -* @return array -*/ -function table_status($name = "") { - global $connection; - $return = array(); - $result = $connection->query("SHOW TABLE STATUS" . ($name != "" ? " LIKE " . $connection->quote(addcslashes($name, "%_")) : "")); - while ($row = $result->fetch_assoc()) { - if ($row["Engine"] == "InnoDB") { - // ignore internal comment, unnecessary since MySQL 5.1.21 - $row["Comment"] = preg_replace('~(?:(.+); )?InnoDB free: .*~', '\\1', $row["Comment"]); - } - if ($name != "") { - return $row; - } - $return[$row["Name"]] = $row; - } - return $return; -} - -/** Get status of referencable tables -* @return array -*/ -function table_status_referencable() { - $return = array(); - foreach (table_status() as $name => $row) { - if ($row["Engine"] == "InnoDB") { - $return[$name] = $row; - } - } - return $return; -} - -/** Get information about fields -* @param string -* @return array array($name => array("field" => , "full_type" => , "type" => , "length" => , "unsigned" => , "default" => , "null" => , "auto_increment" => , "on_update" => , "collation" => , "privileges" => , "comment" => , "primary" => )) -*/ -function fields($table) { - global $connection; - $return = array(); - $result = $connection->query("SHOW FULL COLUMNS FROM " . idf_escape($table)); - if ($result) { - while ($row = $result->fetch_assoc()) { - preg_match('~^([^( ]+)(?:\\((.+)\\))?( unsigned)?( zerofill)?$~', $row["Type"], $match); - $return[$row["Field"]] = array( - "field" => $row["Field"], - "full_type" => $row["Type"], - "type" => $match[1], - "length" => $match[2], - "unsigned" => ltrim($match[3] . $match[4]), - "default" => ($row["Default"] != "" || ereg("char", $match[1]) ? $row["Default"] : null), - "null" => ($row["Null"] == "YES"), - "auto_increment" => ($row["Extra"] == "auto_increment"), - "on_update" => (eregi('^on update (.+)', $row["Extra"], $match) ? $match[1] : ""), //! available since MySQL 5.1.23 - "collation" => $row["Collation"], - "privileges" => array_flip(explode(",", $row["Privileges"])), - "comment" => $row["Comment"], - "primary" => ($row["Key"] == "PRI"), - ); - } - } - return $return; -} - -/** Get table indexes -* @param string -* @param string Min_DB to use -* @return array array($key_name => array("type" => , "columns" => array(), "lengths" => array())) -*/ -function indexes($table, $connection2 = null) { - global $connection; - if (!is_object($connection2)) { // use the main connection if the separate connection is unavailable - $connection2 = $connection; - } - $return = array(); - $result = $connection2->query("SHOW INDEX FROM " . idf_escape($table)); - if ($result) { - while ($row = $result->fetch_assoc()) { - $return[$row["Key_name"]]["type"] = ($row["Key_name"] == "PRIMARY" ? "PRIMARY" : ($row["Index_type"] == "FULLTEXT" ? "FULLTEXT" : ($row["Non_unique"] ? "INDEX" : "UNIQUE"))); - $return[$row["Key_name"]]["columns"][$row["Seq_in_index"]] = $row["Column_name"]; - $return[$row["Key_name"]]["lengths"][$row["Seq_in_index"]] = $row["Sub_part"]; - } - } - return $return; -} - -/** Get foreign keys in table -* @param string -* @return array array($name => array("db" => , "table" => , "source" => array(), "target" => array(), "on_delete" => , "on_update" => )) -*/ -function foreign_keys($table) { - global $connection, $on_actions; - static $pattern = '(?:[^`]|``)+'; - $return = array(); - $result = $connection->query("SHOW CREATE TABLE " . idf_escape($table)); - if ($result) { - $create_table = $connection->result($result, 1); - preg_match_all("~CONSTRAINT `($pattern)` FOREIGN KEY \\(((?:`$pattern`,? ?)+)\\) REFERENCES `($pattern)`(?:\\.`($pattern)`)? \\(((?:`$pattern`,? ?)+)\\)(?: ON DELETE (" . implode("|", $on_actions) . "))?(?: ON UPDATE (" . implode("|", $on_actions) . "))?~", $create_table, $matches, PREG_SET_ORDER); - foreach ($matches as $match) { - preg_match_all("~`($pattern)`~", $match[2], $source); - preg_match_all("~`($pattern)`~", $match[5], $target); - $return[$match[1]] = array( - "db" => idf_unescape($match[4] != "" ? $match[3] : $match[4]), - "table" => idf_unescape($match[4] != "" ? $match[4] : $match[3]), - "source" => array_map('idf_unescape', $source[1]), - "target" => array_map('idf_unescape', $target[1]), - "on_delete" => $match[6], - "on_update" => $match[7], - ); - } - } - return $return; -} - -/** Get view SELECT -* @param string -* @return array array("select" => ) -*/ -function view($name) { - global $connection; - return array("select" => preg_replace('~^(?:[^`]|`[^`]*`)* AS ~U', '', $connection->result($connection->query("SHOW CREATE VIEW " . idf_escape($name)), 1))); -} - -/** Get sorted grouped list of collations -* @return array -*/ -function collations() { - global $connection; - $return = array(); - $result = $connection->query("SHOW COLLATION"); - while ($row = $result->fetch_assoc()) { - $return[$row["Charset"]][] = $row["Collation"]; - } - ksort($return); - foreach ($return as $key => $val) { - sort($return[$key]); - } - return $return; -} - -/** Find out if database is information_schema -* @param string -* @return bool -*/ -function information_schema($db) { - global $connection; - return ($connection->server_info >= 5 && $db == "information_schema"); -} - -/** Get escaped error message -* @return string -*/ -function error() { - global $connection; - return h(preg_replace('~^You have an error.*syntax to use~U', "Syntax error", $connection->error)); -} - -/** Return expression for binary comparison -* @param string -* @return string -*/ -function exact_value($val) { - global $connection; - return "BINARY " . $connection->quote($val); -} - -// value means maximum unsigned length -$types = array(); -$structured_types = array(); -foreach (array( - lang('Numbers') => array("tinyint" => 3, "smallint" => 5, "mediumint" => 8, "int" => 10, "bigint" => 20, "decimal" => 66, "float" => 12, "double" => 21), - lang('Date and time') => array("date" => 10, "datetime" => 19, "timestamp" => 19, "time" => 10, "year" => 4), - lang('Strings') => array("char" => 255, "varchar" => 65535, "tinytext" => 255, "text" => 65535, "mediumtext" => 16777215, "longtext" => 4294967295), - lang('Binary') => array("binary" => 255, "varbinary" => 65535, "tinyblob" => 255, "blob" => 65535, "mediumblob" => 16777215, "longblob" => 4294967295), - lang('Lists') => array("enum" => 65535, "set" => 64), -) as $key => $val) { - $types += $val; - $structured_types[$key] = array_keys($val); -} -$unsigned = array("unsigned", "zerofill", "unsigned zerofill"); diff --git a/adminer/include/pdo.inc.php b/adminer/include/pdo.inc.php index a8d949ec..359e5a8a 100644 --- a/adminer/include/pdo.inc.php +++ b/adminer/include/pdo.inc.php @@ -1,23 +1,21 @@ setAttribute(13, array('Min_PDOStatement')); // PDO::ATTR_STATEMENT_CLASS + $this->setAttribute(13, array('Min_PDOStatement')); // 13 - PDO::ATTR_STATEMENT_CLASS + $this->server_info = $this->getAttribute(4); // 4 - PDO::ATTR_SERVER_VERSION } - function select_db($database) { - // database selection is separated from the connection so dbname in DSN can't be used - return $this->query("USE " . idf_escape($database)); - } + /*abstract function select_db($database);*/ function query($query, $unbuffered = false) { $result = parent::query($query); @@ -50,7 +48,8 @@ if (extension_loaded('pdo')) { return $this->_result->nextRowset(); } - function result($result, $field = 0) { + function result($query, $field = 0) { + $result = $this->query($query); if (!$result) { return false; } @@ -79,3 +78,6 @@ if (extension_loaded('pdo')) { } } } + +$possible_drivers = array(); +$drivers = array(); diff --git a/adminer/include/version.inc.php b/adminer/include/version.inc.php index 8464b966..ce425176 100644 --- a/adminer/include/version.inc.php +++ b/adminer/include/version.inc.php @@ -1,2 +1,2 @@ $existing) { ksort($existing["columns"]); ksort($existing["lengths"]); - if ($index["type"] == $existing["type"] && $existing["columns"] === $columns && $existing["lengths"] === $lengths) { + if ($index["type"] == $existing["type"] && array_values($existing["columns"]) === $columns && (!$existing["lengths"] || array_values($existing["lengths"]) === $lengths)) { // skip existing index unset($indexes[$name]); continue 2; } } - $alter[] = "\nADD $index[type]" . ($index["type"] == "PRIMARY" ? " KEY" : "") . " (" . implode(", ", $set) . ")"; + $alter[] = array($index["type"], "(" . implode(", ", $set) . ")"); } } } // drop removed indexes foreach ($indexes as $name => $existing) { - $alter[] = "\nDROP INDEX " . idf_escape($name); + $alter[] = array($existing["type"], idf_escape($name), "DROP"); } if (!$alter) { redirect(ME . "table=" . urlencode($TABLE)); } - query_redirect("ALTER TABLE " . idf_escape($TABLE) . implode(",", $alter), ME . "table=" . urlencode($TABLE), lang('Indexes have been altered.')); + queries_redirect(ME . "table=" . urlencode($TABLE), lang('Indexes have been altered.'), alter_indexes($TABLE, $alter)); } page_header(lang('Indexes'), $error, array("table" => $TABLE), $TABLE); @@ -77,7 +81,7 @@ foreach ($row["indexes"] as $index) { ksort($index["columns"]); foreach ($index["columns"] as $i => $column) { echo "" . html_select("indexes[$j][columns][$i]", array(-1 => "") + $fields, $column, ($i == count($index["columns"]) ? "indexesAddColumn(this);" : 1)); - echo " \n"; + echo " \n"; //! hide for non-MySQL drivers, add ASC|DESC } echo "\n"; $j++; diff --git a/adminer/lang/cs.inc.php b/adminer/lang/cs.inc.php index 8ca5dfe6..0d5ad61e 100644 --- a/adminer/lang/cs.inc.php +++ b/adminer/lang/cs.inc.php @@ -81,7 +81,7 @@ $translations = array( 'File uploads are disabled.' => 'Nahrávání souborů není povoleno.', 'Routine has been called, %d row(s) affected.' => array('Procedura byla zavolána, byl změněn %d záznam.', 'Procedura byla zavolána, byly změněny %d záznamy.', 'Procedura byla zavolána, bylo změněno %d záznamů.'), 'Call' => 'Zavolat', - 'No MySQL extension' => 'Žádná MySQL extenze', + 'No extension' => 'Žádná extenze', 'None of the supported PHP extensions (%s) are available.' => 'Není dostupná žádná z podporovaných PHP extenzí (%s).', 'Session support must be enabled.' => 'Session proměnné musí být povolené.', 'Session expired, please login again.' => 'Session vypršela, přihlašte se prosím znovu.', @@ -128,9 +128,8 @@ $translations = array( 'Create trigger' => 'Vytvořit trigger', 'Time' => 'Čas', 'Event' => 'Událost', - 'MySQL version: %s through PHP extension %s' => 'Verze MySQL: %s přes PHP extenzi %s', + '%s version: %s through PHP extension %s' => 'Verze %s: %s přes PHP extenzi %s', '%d row(s)' => array('%d řádek', '%d řádky', '%d řádků'), - '~ %s' => '~ %s', 'Remove' => 'Odebrat', 'Are you sure?' => 'Opravdu?', 'Privileges' => 'Oprávnění', @@ -203,7 +202,7 @@ $translations = array( 'Select data' => 'Vypsat data', 'Stop on error' => 'Zastavit při chybě', 'Maximum number of allowed fields exceeded. Please increase %s and %s.' => 'Byl překročen maximální povolený počet polí. Zvyšte prosím %s a %s.', - '(anywhere)' => '(kdekoliv)', + 'anywhere' => 'kdekoliv', '%.3f s' => '%.3f s', '$1-$3-$5' => '$6.$4.$1', '[yyyy]-mm-dd' => 'd.m.[rrrr]', @@ -229,4 +228,9 @@ $translations = array( 'File does not exist.' => 'Soubor neexistuje.', 'Permanent login' => 'Trvalé přihlášení', '%d in total' => '%d celkem', + 'Attachments' => 'Přílohy', + 'System' => 'Systém', + 'last' => 'poslední', + 'Network' => 'Síť', + 'Geometry' => 'Geometrie', ); diff --git a/adminer/lang/de.inc.php b/adminer/lang/de.inc.php index cca37cb7..2b2a695a 100644 --- a/adminer/lang/de.inc.php +++ b/adminer/lang/de.inc.php @@ -81,7 +81,7 @@ $translations = array( 'File uploads are disabled.' => 'Importieren von Dateien abgeschaltet.', 'Routine has been called, %d row(s) affected.' => array('Kommando SQL ausgeführt, %d Datensatz betroffen.', 'Kommando SQL ausgeführt, %d Datensätze betroffen.'), 'Call' => 'Aufrufen', - 'No MySQL extension' => 'Keine MySQL-Erweiterungen installiert', + 'No extension' => 'Keine Erweiterungen installiert', 'None of the supported PHP extensions (%s) are available.' => 'Keine der unterstützten PHP-Erweiterungen (%s) ist vorhanden.', 'Session support must be enabled.' => 'Sitzungen müssen aktiviert sein.', 'Session expired, please login again.' => 'Sitzungsdauer abgelaufen, bitte erneut anmelden.', @@ -128,9 +128,8 @@ $translations = array( 'Create trigger' => 'Trigger hinzufügen', 'Time' => 'Zeitpunkt', 'Event' => 'Ereignis', - 'MySQL version: %s through PHP extension %s' => 'Version MySQL: %s, mit PHP-Erweiterung %s', + '%s version: %s through PHP extension %s' => 'Version %s: %s, mit PHP-Erweiterung %s', '%d row(s)' => array('%d Datensatz', '%d Datensätze'), - '~ %s' => '~ %s', 'Remove' => 'Entfernen', 'Are you sure?' => 'Sind Sie sicher ?', 'Privileges' => 'Rechte', @@ -198,12 +197,10 @@ $translations = array( 'Partition name' => 'Name der Partition', 'Values' => 'Werte', '%d row(s) have been imported.' => array('%d Datensatz importiert.', '%d Datensätze wurden importiert.'), - 'Show structure' => 'Tabellenstruktur', - '(anywhere)' => '(beliebig)', + 'anywhere' => 'beliebig', 'CSV Import' => 'Importiere CSV', 'Import' => 'Importieren', 'Stop on error' => 'Bei Fehler anhaltan', - 'Select data' => 'Tabelle auswählen', '%.3f s' => '%.3f s', '$1-$3-$5' => '$6.$4.$1', '[yyyy]-mm-dd' => 't.m.[jjjj]', diff --git a/adminer/lang/es.inc.php b/adminer/lang/es.inc.php index c539c432..e3945294 100644 --- a/adminer/lang/es.inc.php +++ b/adminer/lang/es.inc.php @@ -81,7 +81,7 @@ $translations = array( 'File uploads are disabled.' => 'Importación de archivos deshablilitado.', 'Routine has been called, %d row(s) affected.' => array('Consulta ejecutada, %d registro afectado.', 'Consulta ejecutada, %d registros afectados.'), 'Call' => 'Llamar', - 'No MySQL extension' => 'No hay extension MySQL', + 'No extension' => 'No hay extension', 'None of the supported PHP extensions (%s) are available.' => 'Ninguna de las extensiones PHP soportadas (%s) está disponible.', 'Session support must be enabled.' => 'Deben estar habilitadas las sesiones.', 'Session expired, please login again.' => 'Sesión expirada, favor loguéese de nuevo.', @@ -128,9 +128,8 @@ $translations = array( 'Create trigger' => 'Agregar Trigger', 'Time' => 'Tiempo', 'Event' => 'Evento', - 'MySQL version: %s through PHP extension %s' => 'Versión MySQL: %s a través de extensión PHP %s', + '%s version: %s through PHP extension %s' => 'Versión %s: %s a través de extensión PHP %s', '%d row(s)' => array('%d registro', '%d registros'), - '~ %s' => '~ %s', 'Remove' => 'Eliminar', 'Are you sure?' => 'Está seguro?', 'Privileges' => 'Privilegios', @@ -198,12 +197,10 @@ $translations = array( 'Partition name' => 'Nombre de Partición', 'Values' => 'Valores', '%d row(s) have been imported.' => array('%d registro importado.', '%d registros importados.'), - 'Show structure' => 'Información de la Tabla', - '(anywhere)' => '(donde sea)', + 'anywhere' => 'donde sea', 'CSV Import' => 'Importar CSV', 'Import' => 'Importar', 'Stop on error' => 'Parar en caso de error', - 'Select data' => 'Mostrar datos', '%.3f s' => '%.3f s', '$1-$3-$5' => '$5/$3/$1', '[yyyy]-mm-dd' => 'dd/mm/[aaaa]', diff --git a/adminer/lang/et.inc.php b/adminer/lang/et.inc.php index 6f582c8f..5790f9a9 100644 --- a/adminer/lang/et.inc.php +++ b/adminer/lang/et.inc.php @@ -81,7 +81,7 @@ $translations = array( 'File uploads are disabled.' => 'Failide üleslaadimine on keelatud.', 'Routine has been called, %d row(s) affected.' => array('Protseduur täideti edukalt, mõjutatud ridu: %d.', 'Protseduur täideti edukalt, mõjutatud ridu: %d.'), 'Call' => 'Käivita', - 'No MySQL extension' => 'Ei leitud MySQL laiendust', + 'No extension' => 'Ei leitud laiendust', 'None of the supported PHP extensions (%s) are available.' => 'Serveris pole ühtegi toetatud PHP laiendustest (%s).', 'Session support must be enabled.' => 'Sessioonid peavad olema lubatud.', 'Session expired, please login again.' => 'Sessioon on aegunud, palun logige uuesti sisse.', @@ -129,7 +129,6 @@ $translations = array( 'Time' => 'Aeg', 'Event' => 'Sündmus', '%d row(s)' => array('%d rida', '%d rida'), - '~ %s' => '~ %s', 'Remove' => 'Eemalda', 'Are you sure?' => 'Kas oled kindel?', 'Privileges' => 'Õigused', @@ -142,7 +141,7 @@ $translations = array( 'Routine' => 'Protseduur', 'Grant' => 'Anna', 'Revoke' => 'Eemalda', - 'MySQL version: %s through PHP extension %s' => 'MySQL versioon: %s, kasutatud PHP moodul: %s', + '%s version: %s through PHP extension %s' => '%s versioon: %s, kasutatud PHP moodul: %s', 'Logged as: %s' => 'Sisse logitud: %s', 'Too big POST data. Reduce the data or increase the %s configuration directive.' => 'POST-andmete maht on liialt suur. Palun vähendage andmeid või suurendage %s php-seadet.', 'Move up' => 'Liiguta ülespoole', @@ -198,12 +197,10 @@ $translations = array( 'Partition name' => 'Partitsiooni nimi', 'Values' => 'Väärtused', '%d row(s) have been imported.' => array('Imporditi %d rida.', 'Imporditi %d rida.'), - 'Show structure' => 'Tabeli struktuur', - '(anywhere)' => '(vahet pole)', + 'anywhere' => 'vahet pole', 'CSV Import' => 'Impordi CSV', 'Import' => 'Impordi', 'Stop on error' => 'Peatuda vea esinemisel', - 'Select data' => 'Vali tabel', '%.3f s' => '%.3f s', '$1-$3-$5' => '$6.$4.$1', '[yyyy]-mm-dd' => 'd.m.[yyyy]', diff --git a/adminer/lang/fr.inc.php b/adminer/lang/fr.inc.php index e6dd1030..84a89c51 100644 --- a/adminer/lang/fr.inc.php +++ b/adminer/lang/fr.inc.php @@ -81,7 +81,7 @@ $translations = array( 'File uploads are disabled.' => 'Importation de fichier désactivé.', 'Routine has been called, %d row(s) affected.' => array('Routine exécutée, %d ligne modifiée.', 'Routine exécutée, %d lignes modifiées.'), 'Call' => 'Appeler', - 'No MySQL extension' => 'Extension MySQL introuvable', + 'No extension' => 'Extension introuvable', 'None of the supported PHP extensions (%s) are available.' => 'Aucune des extensions PHP supportées (%s) n\'est disponible.', 'Session support must be enabled.' => 'Veuillez activer les sessions.', 'Session expired, please login again.' => 'Session expirée, veuillez vous authentifier à nouveau.', @@ -129,7 +129,6 @@ $translations = array( 'Time' => 'Temps', 'Event' => 'Évènement', '%d row(s)' => array('%d ligne', '%d lignes'), - '~ %s' => '~ %s', 'Remove' => 'Effacer', 'Are you sure?' => 'Êtes-vous certain?', 'Privileges' => 'Privilège', @@ -142,7 +141,7 @@ $translations = array( 'Routine' => 'Routine', 'Grant' => 'Grant', 'Revoke' => 'Revoke', - 'MySQL version: %s through PHP extension %s' => 'Version de MySQL: %s utilisant l\'extension %s', + '%s version: %s through PHP extension %s' => 'Version de %s: %s utilisant l\'extension %s', 'Logged as: %s' => 'Authentifié en tant que %s', 'Too big POST data. Reduce the data or increase the %s configuration directive.' => 'Donnée POST trop grande . Réduire la taille des données ou modifier le %s dans la configuration de PHP.', 'Move up' => 'Déplacer vers le haut', @@ -198,12 +197,10 @@ $translations = array( 'Partition name' => 'Nom de la partition', 'Values' => 'Valeurs', '%d row(s) have been imported.' => array('%d ligne a été importé.','%d lignes ont été importé.'), - 'Show structure' => 'Structure de la table', - '(anywhere)' => '(n\'importe où)', + 'anywhere' => 'n\'importe où', 'CSV Import' => 'Importation CVS', 'Import' => 'Importer', 'Stop on error' => 'Arrêt sur erreur', - 'Select data' => 'Selectionner la table', '%.3f s' => '%.3f s', '$1-$3-$5' => '$5/$3/$1', '[yyyy]-mm-dd' => 'jj/mm/[aaaa]', diff --git a/adminer/lang/it.inc.php b/adminer/lang/it.inc.php index 77727504..7eb551b0 100644 --- a/adminer/lang/it.inc.php +++ b/adminer/lang/it.inc.php @@ -81,7 +81,7 @@ $translations = array( 'File uploads are disabled.' => 'Caricamento file disabilitato.', 'Routine has been called, %d row(s) affected.' => array('Routine chiamata, %d riga interessata.', 'Routine chiamata, %d righe interessate.'), 'Call' => 'Chiama', - 'No MySQL extension' => 'Estensioni MySQL non presenti', + 'No extension' => 'Estensioni non presenti', 'None of the supported PHP extensions (%s) are available.' => 'Nessuna delle estensioni PHP supportate (%s) disponibile.', 'Session support must be enabled.' => 'Le sessioni devono essere abilitate.', 'Session expired, please login again.' => 'Sessione scaduta, autenticarsi di nuovo.', @@ -128,9 +128,8 @@ $translations = array( 'Create trigger' => 'Crea trigger', 'Time' => 'Orario', 'Event' => 'Evento', - 'MySQL version: %s through PHP extension %s' => 'Versione MySQL: %s via estensione PHP %s', + '%s version: %s through PHP extension %s' => 'Versione %s: %s via estensione PHP %s', '%d row(s)' => array('%d riga', '%d righe'), - '~ %s' => '~ %s', 'Remove' => 'Rimuovi', 'Are you sure?' => 'Sicuro?', 'Privileges' => 'Privilegi', @@ -198,12 +197,10 @@ $translations = array( 'Partition name' => 'Nome partizione', 'Values' => 'Valori', '%d row(s) have been imported.' => array('%d riga importata.','%d righe importate.'), - 'Show structure' => 'Struttura tabella', - '(anywhere)' => '(ovunque)', + 'anywhere' => 'ovunque', 'CSV Import' => 'Importa da CSV', 'Import' => 'Importa', 'Stop on error' => 'Stop su errore', - 'Select data' => 'Scegli tabella', '%.3f s' => '%.3f s', '$1-$3-$5' => '$5/$3/$1', '[yyyy]-mm-dd' => 'dd/mm/[yyyy]', diff --git a/adminer/lang/nl.inc.php b/adminer/lang/nl.inc.php index 6f3b6a81..d319fbb0 100644 --- a/adminer/lang/nl.inc.php +++ b/adminer/lang/nl.inc.php @@ -81,7 +81,7 @@ $translations = array( 'File uploads are disabled.' => 'Bestanden uploaden is uitgeschakeld.', 'Routine has been called, %d row(s) affected.' => array('Procedure uitgevoerd, %d rij geraakt.', 'Procedure uitgevoerd, %d rijen geraakt.'), 'Call' => 'Uitvoeren', - 'No MySQL extension' => 'Geen MySQL extensie', + 'No extension' => 'Geen extensie', 'None of the supported PHP extensions (%s) are available.' => 'Geen geldige PHP extensies beschikbaar (%s).', 'Session support must be enabled.' => 'Sessies moeten geactiveerd zijn.', 'Session expired, please login again.' => 'Uw sessie is verlopen. Gelieve opnieuw in te loggen.', @@ -128,9 +128,8 @@ $translations = array( 'Create trigger' => 'Trigger aanmaken', 'Time' => 'Time', 'Event' => 'Event', - 'MySQL version: %s through PHP extension %s' => 'MySQL versie: %s met PHP extensie %s', + '%s version: %s through PHP extension %s' => '%s versie: %s met PHP extensie %s', '%d row(s)' => array('%d rij', '%d rijen'), - '~ %s' => '~ %s', 'Remove' => 'Verwijderen', 'Are you sure?' => 'Weet u het zeker?', 'Privileges' => 'Rechten', @@ -198,12 +197,10 @@ $translations = array( 'Partition name' => 'Partitie naam', 'Values' => 'Waarden', '%d row(s) have been imported.' => array('%d rij werd geïmporteerd.', '%d rijen werden geïmporteerd.'), - 'Show structure' => 'Tabelstructuur', - '(anywhere)' => '(overal)', + 'anywhere' => 'overal', 'CSV Import' => 'CSV Import', 'Import' => 'Importeren', 'Stop on error' => 'Stoppen bij fout', - 'Select data' => 'Selecteer tabel', '%.3f s' => '%.3f s', '$1-$3-$5' => '$5-$3-$1', '[yyyy]-mm-dd' => 'dd-mm-[jjjj]', diff --git a/adminer/lang/ru.inc.php b/adminer/lang/ru.inc.php index 52c40404..01877c61 100644 --- a/adminer/lang/ru.inc.php +++ b/adminer/lang/ru.inc.php @@ -81,7 +81,7 @@ $translations = array( 'File uploads are disabled.' => 'Загрузка файлов на сервер запрещена.', 'Routine has been called, %d row(s) affected.' => array('Была вызвана процедура, %d запись была изменена.', 'Была вызвана процедура, %d записи было изменено.', 'Была вызвана процедура, %d записей было изменено.'), 'Call' => 'Вызвать', - 'No MySQL extension' => 'Нет MySQL расширений', + 'No extension' => 'Нет расширений', 'None of the supported PHP extensions (%s) are available.' => 'Не доступно ни одного расширения из поддерживаемых (%s).', 'Session support must be enabled.' => 'Сессии должны быть включены.', 'Session expired, please login again.' => 'Срок действия сесси истек, нужно снова войти в систему.', @@ -128,9 +128,8 @@ $translations = array( 'Create trigger' => 'Создать триггер', 'Time' => 'Время', 'Event' => 'Событие', - 'MySQL version: %s through PHP extension %s' => 'Версия MySQL: %s с PHP-расширением %s', + '%s version: %s through PHP extension %s' => 'Версия %s: %s с PHP-расширением %s', '%d row(s)' => array('%d строка', '%d строки', '%d строк'), - '~ %s' => '~ %s', 'Remove' => 'Удалить', 'Are you sure?' => 'Вы уверены?', 'Privileges' => 'Полномочия', @@ -199,11 +198,9 @@ $translations = array( '%d row(s) have been imported.' => array('Импортирована %d строка.', 'Импортировано %d строки.', 'Импортировано %d строк.'), 'CSV Import' => 'Импорт CSV', 'Import' => 'Импорт', - 'Show structure' => 'Структура таблицы', - 'Select data' => 'Выбрать данные из таблицы', 'Stop on error' => 'Остановить при ошибке', 'Maximum number of allowed fields exceeded. Please increase %s and %s.' => 'Достигнуто максимальное значение количества доступных полей. Увеличьте %s и %s.', - '(anywhere)' => '(в любом месте)', + 'anywhere' => 'в любом месте', '%.3f s' => '%.3f s', '$1-$3-$5' => '$5.$3.$1', '[yyyy]-mm-dd' => 'дд.мм.[гггг]', diff --git a/adminer/lang/sk.inc.php b/adminer/lang/sk.inc.php index cd492153..5a6acd3d 100644 --- a/adminer/lang/sk.inc.php +++ b/adminer/lang/sk.inc.php @@ -81,7 +81,7 @@ $translations = array( 'File uploads are disabled.' => 'Nahrávánie súborov nie je povolené.', 'Routine has been called, %d row(s) affected.' => array('Procedúra bola zavolaná, bol zmenený %d záznam.', 'Procedúra bola zavolaná, boli zmenené %d záznamy.', 'Procedúra bola zavolaná, bolo zmenených %d záznamov.'), 'Call' => 'Zavolať', - 'No MySQL extension' => 'Žiadne MySQL rozšírenie', + 'No extension' => 'Žiadne rozšírenie', 'None of the supported PHP extensions (%s) are available.' => 'Nie je dostupné žiadne z podporovaných rozšírení (%s).', 'Session support must be enabled.' => 'Session premenné musia byť povolené.', 'Session expired, please login again.' => 'Session vypršala, prihláste sa prosím znova.', @@ -128,9 +128,8 @@ $translations = array( 'Create trigger' => 'Vytvoriť trigger', 'Time' => 'Čas', 'Event' => 'Udalosť', - 'MySQL version: %s through PHP extension %s' => 'Verzia MySQL: %s cez PHP rozšírenie %s', + '%s version: %s through PHP extension %s' => 'Verzia %s: %s cez PHP rozšírenie %s', '%d row(s)' => array('%d riadok', '%d riadky', '%d riadkov'), - '~ %s' => '~ %s', 'Remove' => 'Odobrať', 'Are you sure?' => 'Naozaj?', 'Privileges' => 'Oprávnenia', @@ -199,11 +198,9 @@ $translations = array( '%d row(s) have been imported.' => array('Bol importovaný %d záznam.', 'Boli importované %d záznamy.', 'Bolo importovaných %d záznamov.'), 'CSV Import' => 'Import CSV', 'Import' => 'Import', - 'Show structure' => 'Štruktúra tabuľky', - 'Select data' => 'Vypísať tabuľku', 'Stop on error' => 'Zastaviť pri chybe', 'Maximum number of allowed fields exceeded. Please increase %s and %s.' => 'Bol prekročený maximálny počet povolených polí. Zvýšte prosím %s a %s.', - '(anywhere)' => '(kdekoľvek)', + 'anywhere' => 'kdekoľvek', '%.3f s' => '%.3f s', '$1-$3-$5' => '$6.$4.$1', '[yyyy]-mm-dd' => 'd.m.[rrrr]', diff --git a/adminer/lang/zh-tw.inc.php b/adminer/lang/zh-tw.inc.php index fa4f6320..50a8a831 100644 --- a/adminer/lang/zh-tw.inc.php +++ b/adminer/lang/zh-tw.inc.php @@ -79,7 +79,7 @@ $translations = array( 'File uploads are disabled.' => '檔案上傳被禁用。', 'Routine has been called, %d row(s) affected.' => '程序已被執行,%d行被影響', 'Call' => '呼叫', - 'No MySQL extension' => '沒有 MySQL 擴充模組', + 'No extension' => '沒有 擴充模組', 'None of the supported PHP extensions (%s) are available.' => '沒有任何支援的PHP擴充模組(%s)。', 'Session support must be enabled.' => 'Session 必須被啟用。', 'Session expired, please login again.' => 'Session 已過期,請重新登入。', @@ -126,9 +126,8 @@ $translations = array( 'Create trigger' => '建立觸發器', 'Time' => '時間', 'Event' => '事件', - 'MySQL version: %s through PHP extension %s' => 'MySQL版本:%s 透過PHP擴充模組 %s', + '%s version: %s through PHP extension %s' => '%s版本:%s 透過PHP擴充模組 %s', '%d row(s)' => '%d行', - '~ %s' => '~ %s', 'Remove' => '移除', 'Are you sure?' => '你確定嗎?', 'Privileges' => '權限', @@ -196,12 +195,10 @@ $translations = array( 'Partition name' => '分區名', 'Values' => '值', '%d row(s) have been imported.' => '%d行已導入。', - 'Show structure' => '資料表結構', - '(anywhere)' => '(任意位置)', + 'anywhere' => '任意位置', 'CSV Import' => '匯入 CSV', 'Import' => '匯入', 'Stop on error' => '出錯時停止', - 'Select data' => '選擇資料表', '%.3f s' => '%.3f秒', '$1-$3-$5' => '$1.$3.$5', '[yyyy]-mm-dd' => '[yyyy].mm.dd', diff --git a/adminer/lang/zh.inc.php b/adminer/lang/zh.inc.php index e86396bc..7472eb72 100644 --- a/adminer/lang/zh.inc.php +++ b/adminer/lang/zh.inc.php @@ -81,7 +81,7 @@ $translations = array( 'File uploads are disabled.' => '文件上传被禁用。', 'Routine has been called, %d row(s) affected.' => '子程序被调用,%d 行被影响', 'Call' => '调用', - 'No MySQL extension' => '没有MySQL扩展', + 'No extension' => '没有扩展', 'None of the supported PHP extensions (%s) are available.' => '没有支持的 PHP 扩展可用(%s)。', 'Session support must be enabled.' => '会话必须被启用。', 'Session expired, please login again.' => '会话已过期,请重新登录。', @@ -128,9 +128,8 @@ $translations = array( 'Create trigger' => '创建触发器', 'Time' => '时间', 'Event' => '事件', - 'MySQL version: %s through PHP extension %s' => 'MySQL 版本:%s 通过 PHP 扩展 %s', + '%s version: %s through PHP extension %s' => '%s 版本:%s 通过 PHP 扩展 %s', '%d row(s)' => '%d 行', - '~ %s' => '~ %s', 'Remove' => '移除', 'Are you sure?' => '你确定吗?', 'Privileges' => '权限', @@ -198,12 +197,10 @@ $translations = array( 'Partition name' => '分区名', 'Values' => '值', '%d row(s) have been imported.' => '%d 行已导入。', - 'Show structure' => '表结构', - '(anywhere)' => '(任意位置)', + 'anywhere' => '任意位置', 'CSV Import' => 'CSV 导入', 'Import' => '导入', 'Stop on error' => '出错时停止', - 'Select data' => '选择表', '%.3f s' => '%.3f 秒', '$1-$3-$5' => '$1.$3.$5', '[yyyy]-mm-dd' => '[yyyy].mm.dd', diff --git a/adminer/privileges.inc.php b/adminer/privileges.inc.php index 77155a37..7bbf3616 100644 --- a/adminer/privileges.inc.php +++ b/adminer/privileges.inc.php @@ -5,8 +5,7 @@ $result = $connection->query("SELECT User, Host FROM mysql.user ORDER BY Host, U if (!$result) { ?>

    - -"> + : : diff --git a/adminer/schema.inc.php b/adminer/schema.inc.php index 00252bfd..316abdf0 100644 --- a/adminer/schema.inc.php +++ b/adminer/schema.inc.php @@ -7,7 +7,7 @@ $table_pos_js = array(); preg_match_all('~([^:]+):([-0-9.]+)x([-0-9.]+)(_|$)~', $_COOKIE["adminer_schema"], $matches, PREG_SET_ORDER); //! ':' in table name foreach ($matches as $i => $match) { $table_pos[$match[1]] = array($match[2], $match[3]); - $table_pos_js[] = "\n\t'" . addcslashes($match[1], "\r\n'\\") . "': [ $match[2], $match[3] ]"; + $table_pos_js[] = "\n\t'" . addcslashes($match[1], "\r\n'\\/") . "': [ $match[2], $match[3] ]"; } $top = 0; @@ -27,7 +27,7 @@ foreach (table_status() as $row) { $schema[$row["Name"]]["fields"][$name] = $field; } $schema[$row["Name"]]["pos"] = ($table_pos[$row["Name"]] ? $table_pos[$row["Name"]] : array($top, 0)); - if ($row["Engine"] == "InnoDB") { + if (fk_support($row)) { foreach (foreign_keys($row["Name"]) as $val) { if (!$val["db"]) { $left = $base_left; diff --git a/adminer/select.inc.php b/adminer/select.inc.php index 76604d3f..eaab9b97 100644 --- a/adminer/select.inc.php +++ b/adminer/select.inc.php @@ -26,6 +26,12 @@ $limit = $adminer->selectLimitProcess(); $from = ($select ? implode(", ", $select) : "*") . "\nFROM " . idf_escape($TABLE) . ($where ? "\nWHERE " . implode(" AND ", $where) : ""); $group_by = ($group && count($group) < count($select) ? "\nGROUP BY " . implode(", ", $group) : "") . ($order ? "\nORDER BY " . implode(", ", $order) : ""); +if ($_GET["page"] == "last") { + session_write_close(); + $found_rows = $connection->result("SELECT COUNT(*) FROM " . idf_escape($TABLE) . ($where ? " WHERE " . implode(" AND ", $where) : "")); + redirect(remove_from_uri("page") . ($found_rows > $limit ? "&page=" . floor(($found_rows - 1) / $limit) : "")); +} + if ($_POST && !$error) { $where_check = "(" . implode(") OR (", array_map('where_check', (array) $_POST["check"])) . ")"; $primary = ($indexes["PRIMARY"] ? ($select ? array_flip($indexes["PRIMARY"]["columns"]) : array()) : null); // empty array means that all primary fields are selected @@ -43,7 +49,7 @@ if ($_POST && !$error) { if ($select) { $row = array(); foreach ($select as $val) { - $row[] = (ereg('^`(.*)`$', $val, $match) ? idf_unescape($match[1]) : $val); //! columns looking like functions + $row[] = (ereg('^`.*`$', $val) ? idf_unescape($val) : $val); //! columns looking like functions } } dump_csv($row); @@ -54,7 +60,7 @@ if ($_POST && !$error) { $union = array(); foreach ($_POST["check"] as $val) { // where is not unique so OR can't be used - $union[] = "(SELECT $from " . ($where ? "AND " : "WHERE ") . where_check($val) . $group_by . " LIMIT 1)"; + $union[] = "(SELECT" . limit("$from " . ($where ? "AND " : "WHERE ") . where_check($val) . $group_by, 1) . ")"; } dump_data($TABLE, "INSERT", implode(" UNION ALL ", $union)); } @@ -64,27 +70,38 @@ if ($_POST && !$error) { if (!$_POST["import"]) { // edit $result = true; $affected = 0; - $command = ($_POST["delete"] ? "DELETE FROM " : ($_POST["clone"] ? "INSERT INTO " : "UPDATE ")) . idf_escape($TABLE); + $query = idf_escape($TABLE); $set = array(); if (!$_POST["delete"]) { foreach ($columns as $name => $val) { //! should check also for edit or insert privileges $val = process_input($fields[$name]); - if ($_POST["clone"]) { - $set[idf_escape($name)] = ($val !== false ? $val : idf_escape($name)); - } elseif ($val !== false) { - $set[] = idf_escape($name) . " = $val"; + if ($val !== null) { + if ($_POST["clone"]) { + $set[idf_escape($name)] = ($val !== false ? $val : idf_escape($name)); + } elseif ($val !== false) { + $set[] = idf_escape($name) . " = $val"; + } } } - $command .= ($_POST["clone"] ? " (" . implode(", ", array_keys($set)) . ")\nSELECT " . implode(", ", $set) . "\nFROM " . idf_escape($TABLE) : " SET\n" . implode(",\n", $set)); + $query .= ($_POST["clone"] ? " (" . implode(", ", array_keys($set)) . ")\nSELECT " . implode(", ", $set) . "\nFROM " . idf_escape($TABLE) : " SET\n" . implode(",\n", $set)); } if ($_POST["delete"] || $set) { - if ($_POST["all"] || ($primary === array() && $_POST["check"])) { - $result = queries($command . ($_POST["all"] ? ($where ? "\nWHERE " . implode(" AND ", $where) : "") : "\nWHERE $where_check")); + $command = "UPDATE"; + if ($_POST["delete"]) { + $command = "DELETE"; + $query = "FROM $query"; + } + if ($_POST["clone"]) { + $command = "INSERT"; + $query = "INTO $query"; + } + if ($_POST["all"] || ($primary === array() && $_POST["check"]) || count($group) < count($select)) { + $result = queries($command . " $query" . ($_POST["all"] ? ($where ? "\nWHERE " . implode(" AND ", $where) : "") : "\nWHERE $where_check")); $affected = $connection->affected_rows; } else { foreach ((array) $_POST["check"] as $val) { // where is not unique so OR can't be used - $result = queries($command . "\nWHERE " . where_check($val) . (count($group) < count($select) ? "" : "\nLIMIT 1")); + $result = queries($command . limit1($query . "\nWHERE " . where_check($val))); if (!$result) { break; } @@ -101,8 +118,9 @@ if ($_POST && !$error) { preg_match_all('~(?>"[^"]*"|[^"\\r\\n]+)+~', $file, $matches); $affected = count($matches[0]); queries("START TRANSACTION"); + $separator = ($_POST["separator"] == ";" ? ";" : ","); foreach ($matches[0] as $key => $val) { - preg_match_all('~(("[^"]*")+|[^,]*),~', "$val,", $matches2); + preg_match_all("~((\"[^\"]*\")+|[^$separator]*)$separator~", $val . $separator, $matches2); if (!$key && !array_diff($matches2[1], $cols)) { //! doesn't work with column names containing ",\n // first row corresponds to column names - use it for table structure $cols = $matches2[1]; @@ -120,7 +138,7 @@ if ($_POST && !$error) { } } if ($result) { - queries("COMMIT"); + queries("COMMIT"); } queries_redirect(remove_from_uri("page"), lang('%d row(s) have been imported.', $affected), $result); queries("ROLLBACK"); @@ -147,11 +165,11 @@ if (isset($rights["insert"])) { $adminer->selectLinks($table_status, $set); if (!$columns) { - echo "

    " . lang('Unable to select the table') . ($fields ? "" : ": " . error()) . ".\n"; + echo "

    " . lang('Unable to select the table') . ($fields ? "." : ": " . error()) . "\n"; } else { echo "\n"; echo "

    "; - echo ($_GET["server"] != "" ? '' : ""); + hidden_fields_get(); echo (DB != "" ? '' : ""); // not used in Editor echo ''; echo "
    \n"; @@ -163,7 +181,7 @@ if (!$columns) { $adminer->selectActionPrint($text_length); echo "\n"; - $query = "SELECT " . (intval($limit) && $group && count($group) < count($select) ? "SQL_CALC_FOUND_ROWS " : "") . $from . $group_by . ($limit != "" ? "\nLIMIT " . intval($limit) . ($_GET["page"] ? " OFFSET " . ($limit * $_GET["page"]) : "") : ""); + $query = "SELECT" . limit((intval($limit) && $group && count($group) < count($select) && $driver == "sql" ? "SQL_CALC_FOUND_ROWS " : "") . $from . $group_by, ($limit != "" ? intval($limit) : null), ($_GET["page"] ? $limit * $_GET["page"] : 0)); echo $adminer->selectQuery($query); $result = $connection->query($query); @@ -172,19 +190,19 @@ if (!$columns) { } else { $email_fields = array(); echo "
    \n"; - if (!$result->num_rows) { + $rows = array(); + while ($row = $result->fetch_assoc()) { + $rows[] = $row; + } + // use count($rows) without LIMIT, COUNT(*) without grouping, FOUND_ROWS otherwise (slowest) + $found_rows = (intval($limit) && $group && count($group) < count($select) + ? ($driver == "sql" ? $connection->result(" SELECT FOUND_ROWS()") : $connection->result("SELECT COUNT(*) FROM ($query) x")) // space to allow mysql.trace_mode + : count($rows) + ); + + if (!$rows) { echo "

    " . lang('No rows.') . "\n"; } else { - $rows = array(); - while ($row = $result->fetch_assoc()) { - $rows[] = $row; - } - // use count($rows) without LIMIT, COUNT(*) without grouping, FOUND_ROWS otherwise (slowest) - $found_rows = (intval($limit) && $group && count($group) < count($select) - ? $connection->result($connection->query(" SELECT FOUND_ROWS()")) // space to allow mysql.trace_mode - : count($rows) - ); - $backward_keys = $adminer->backwardKeys($TABLE, $table_name); echo "\n"; @@ -205,7 +223,7 @@ if (!$columns) { } echo ($backward_keys ? "\n"; // close to allow white-space: pre } echo "
    " . lang('Relations') : "") . "\n"; foreach ($adminer->rowDescriptions($rows, $foreign_keys) as $n => $row) { - $unique_array = unique_array($row, $indexes); + $unique_array = unique_array($rows[$n], $indexes); $unique_idf = ""; foreach ($unique_array as $key => $val) { $unique_idf .= "&" . (isset($val) ? urlencode("where[" . bracket_escape($key) . "]") . "=" . urlencode($val) : "null%5B%5D=" . urlencode($key)); @@ -222,7 +240,7 @@ if (!$columns) { if (!isset($val)) { $val = "NULL"; } else { - if (ereg('blob|binary', $field["type"]) && $val != "") { + if (ereg('binary|blob|bytea', $field["type"]) && $val != "") { $link = h(ME . 'download=' . urlencode($TABLE) . '&field=' . urlencode($key) . $unique_idf); } if ($val == "") { @@ -271,33 +289,45 @@ if (!$columns) { echo "
    \n"; - - if (intval($limit) && count($group) >= count($select)) { - // slow with big tables - ob_flush(); - flush(); - $found_rows = $connection->result($connection->query("SELECT COUNT(*) FROM " . idf_escape($TABLE) . ($where ? " WHERE " . implode(" AND ", $where) : ""))); + } + + parse_str($_COOKIE["adminer_export"], $adminer_export); + + if ($rows || $_GET["page"]) { + $exact_count = true; + if (intval($limit) && count($group) >= count($select) && ($found_rows >= $limit || $_GET["page"])) { + $found_rows = $table_status["Rows"]; + if (!isset($found_rows) || $where || $_GET["page"] * $limit * 2 > $found_rows || ($table_status["Engine"] == "InnoDB" && $found_rows < 1e5)) { + // slow with big tables + ob_flush(); + flush(); + $found_rows = $connection->result("SELECT COUNT(*) FROM " . idf_escape($TABLE) . ($where ? " WHERE " . implode(" AND ", $where) : "")); + } else { + $exact_count = false; + } } echo "

    "; if (intval($limit) && $found_rows > $limit) { - // display first, previous 3, next 3 and last page + // display first, previous 5, next 5 and last page $max_page = floor(($found_rows - 1) / $limit); - echo lang('Page') . ":" . pagination(0) . ($_GET["page"] > 3 ? " ..." : ""); - for ($i = max(1, $_GET["page"] - 2); $i < min($max_page, $_GET["page"] + 3); $i++) { + echo lang('Page') . ":" . pagination(0) . ($_GET["page"] > 5 ? " ..." : ""); + for ($i = max(1, $_GET["page"] - 2); $i < min($max_page, $_GET["page"] + 5); $i++) { echo pagination($i); } - echo ($_GET["page"] + 3 < $max_page ? " ..." : "") . pagination($max_page); + echo ($_GET["page"] + 5 < $max_page ? " ..." : "") . ($exact_count ? pagination($max_page) : ' ' . lang('last') . ""); } - echo " (" . lang('%d row(s)', $found_rows) . ") " . checkbox("all", 1, 0, lang('whole result')) . "\n"; + echo " (" . ($exact_count ? "" : "~ ") . lang('%d row(s)', $found_rows) . ") " . checkbox("all", 1, 0, lang('whole result')) . "\n"; echo (information_schema(DB) ? "" : "

    " . lang('Edit') . "
    \n"); print_fieldset("export", lang('Export')); - echo $adminer->dumpOutput(1) . " " . $adminer->dumpFormat(1); // 1 - select + echo $adminer->dumpOutput(1, $adminer_export["output"]) . " " . $adminer->dumpFormat(1, $adminer_export["format"]); // 1 - select echo " \n"; echo "\n"; } print_fieldset("import", lang('CSV Import'), !$result->num_rows); - echo " \n"; + echo " "; + echo html_select("separator", array(",", ";"), ($adminer_export["format"] == "csv;" ? ";" : ","), 1); // 1 - select + echo " \n"; echo "\n"; $adminer->selectEmailPrint(array_filter($email_fields, 'strlen'), $columns); diff --git a/adminer/sql.inc.php b/adminer/sql.inc.php index 180b2721..d3abdeff 100644 --- a/adminer/sql.inc.php +++ b/adminer/sql.inc.php @@ -1,6 +1,7 @@ select_db(DB); } $queries = 0; @@ -59,7 +59,7 @@ if (!$error && $_POST) { $empty = false; $q = substr($query, 0, $match[0][1]); $queries++; - echo "
    " . shorten_utf8(trim($q), 1000) . "
    \n"; + echo "
    " . shorten_utf8(trim($q), 1000) . "
    \n"; ob_flush(); flush(); // can take a long time - show the running query $start = explode(" ", microtime()); // microtime(true) is available since PHP 5 @@ -71,6 +71,9 @@ if (!$error && $_POST) { break; } } else { + if (is_object($connection2) && preg_match("~^$space*(USE)\\b~isU", $q)) { + $connection2->query($q); + } do { $result = $connection->store_result(); $end = explode(" ", microtime()); @@ -82,18 +85,17 @@ if (!$error && $_POST) { $id = "explain-$queries"; echo ", EXPLAIN\n"; echo "\n"; } } else { - if (preg_match("~^$space*$alter_database", $query)) { + if (preg_match("~^$space*(CREATE|DROP|ALTER)$space+(DATABASE|SCHEMA)\\b~isU", $q)) { restart_session(); - $_SESSION["databases"][$_GET["server"]] = null; // clear cache + set_session("databases", null); // clear cache session_write_close(); } echo "

    " . lang('Query executed OK, %d row(s) affected.', $connection->affected_rows) . "$time\n"; } - unset($result); // free resultset $start = $end; } while ($connection->next_result()); } @@ -119,6 +121,7 @@ if (!$error && $_POST) { if ($empty) { echo "

    " . lang('No commands to execute.') . "\n"; } + //! MS SQL - SET SHOWPLAN_ALL OFF } else { echo "

    " . upload_error($query) . "\n"; } @@ -142,7 +145,7 @@ echo h($q);

    : @@ -164,7 +167,7 @@ if ($history) { print_fieldset("history", lang('History'), $_GET["history"] != ""); foreach ($history as $key => $val) { //! save and display timestamp - echo '' . lang('Edit') . ' ' . shorten_utf8(ltrim(str_replace("\n", " ", str_replace("\r", "", preg_replace('~^(#|-- ).*~m', '', $val)))), 80, "") . "
    \n"; + echo '' . lang('Edit') . " " . shorten_utf8(ltrim(str_replace("\n", " ", str_replace("\r", "", preg_replace('~^(#|-- ).*~m', '', $val)))), 80, "") . "
    \n"; } echo "\n"; echo "\n"; diff --git a/adminer/static/default.css b/adminer/static/default.css index 567e4e6d..0ccbb81a 100644 --- a/adminer/static/default.css +++ b/adminer/static/default.css @@ -4,26 +4,28 @@ a:visited { color: navy; } a:hover { color: red; } h1 { font-size: 150%; margin: 0; padding: .8em 1em; border-bottom: 1px solid #999; font-weight: normal; color: #777; background: #eee; } h2 { font-size: 150%; margin: 0 0 20px -18px; padding: .8em 1em; border-bottom: 1px solid #000; color: #000; font-weight: normal; background: #ddf; } -h3 { font-weight: normal; font-size: 130%; margin: .8em 0; } +h3 { font-weight: normal; font-size: 130%; margin: 1em 0 0; } form { margin: 0; } -table { margin: 1em 20px .8em 0; border: 0; border-top: 1px solid #999; border-left: 1px solid #999; font-size: 90%; } -td, th { margin-bottom: 1em; border: 0; border-right: 1px solid #999; border-bottom: 1px solid #999; padding: .2em .3em; } +table { margin: 1em 20px 0 0; border: 0; border-top: 1px solid #999; border-left: 1px solid #999; font-size: 90%; } +td, th { border: 0; border-right: 1px solid #999; border-bottom: 1px solid #999; padding: .2em .3em; } th { background: #eee; text-align: left; } thead th { text-align: center; } thead td, thead th { background: #ddf; } -fieldset { display: inline; vertical-align: top; padding: .5em .8em; margin: 0 .5em .5em 0; border: 1px solid #999; } -p { margin: 0 20px 1em 0; } +fieldset { display: inline; vertical-align: top; padding: .5em .8em; margin: .8em .5em 0 0; border: 1px solid #999; } +p { margin: .8em 20px 0 0; } img { vertical-align: middle; border: 0; } td img { max-width: 200px; max-height: 200px; } code { background: #eee; } tr:hover td, tr:hover th { background: #ddf; } +pre { margin: 1em 0 0; } .version { color: #777; font-size: 67%; } .js .hidden { display: none; } .nowrap td, .nowrap th, td.nowrap { white-space: pre; } .wrap td { white-space: normal; } .error { color: red; background: #fee; } +.error b { background: #fff; font-weight: normal; } .message { color: green; background: #efe; } -.error, .message { padding: .5em .8em; margin: 0 20px 1em 0; } +.error, .message { padding: .5em .8em; margin: 1em 20px 0 0; } .char { color: #007F00; } .date { color: #7F007F; } .enum { color: #007F7F; } diff --git a/adminer/static/editing.js b/adminer/static/editing.js index cadc3aa1..189d9bfb 100644 --- a/adminer/static/editing.js +++ b/adminer/static/editing.js @@ -1,7 +1,7 @@ // Adminer specific functions /** Load syntax highlighting -* @param string first three characters of MySQL version +* @param string first three characters of database system version */ function bodyLoad(version) { var jushRoot = '../externals/jush/'; @@ -10,6 +10,7 @@ function bodyLoad(version) { script.onload = function () { if (window.jush) { // IE runs in case of an error too jush.create_links = ' target="_blank"'; + jush.urls.pgsql[0] = 'http://www.postgresql.org/docs/' + version + '/static/$key'; jush.urls.sql[0] = 'http://dev.mysql.com/doc/refman/' + version + '/en/$key'; jush.urls.sqlset[0] = jush.urls.sql[0]; jush.urls.sqlstatus[0] = jush.urls.sql[0]; @@ -26,8 +27,6 @@ function bodyLoad(version) { document.body.appendChild(script); } - - /** Get value of select * @param HTMLSelectElement * @return string diff --git a/adminer/static/favicon.ico b/adminer/static/favicon.ico index ef958a094832138ff434516c1ed92d7cff12ad88..ab736cafd0ca9b3d2216b63a1f265197c2f4f971 100644 GIT binary patch literal 318 zcma)$%MF7-3`ECL3a}T6Gn*4vls?h}l>nV>WjHgy4MoDMXUpT?0>DC^Z-WU zWFXRI?eKS(nG_TA_g;IfM1*>+`%JoPQ*@6a`#I|Bk6K*wg*hnJDB5Yvt9D0ypEp#$ VjDNRA*5waI{h}dlCn+|B- zY`_t~EL@yL)7yG>yERoMv2eR?bAKtuUPh+8k)c=tmxM+1*fJTZ8S|UACTqavC3jsZ zS66TvU2Qok4lbNHG8W3}NC&4&zfd=My;jt);yvhn;@;ScgE;-rpXd|5^hp7gpdP>n D0VEib diff --git a/adminer/static/functions.js b/adminer/static/functions.js index fa7de73e..3ff592c3 100644 --- a/adminer/static/functions.js +++ b/adminer/static/functions.js @@ -27,7 +27,7 @@ function cookie(assign, days, params) { function verifyVersion() { cookie('adminer_version=0', 1); var script = document.createElement('script'); - script.src = 'https://adminer.svn.sourceforge.net/svnroot/adminer/trunk/version.js'; + script.src = 'https://www.adminer.org/version.php'; document.body.appendChild(script); } @@ -83,6 +83,21 @@ function tableClick(event) { el.onclick && el.onclick(); } +/** Set HTML code of an element +* @param string +* @param string undefined to set parentNode to   +*/ +function setHtml(id, html) { + var el = document.getElementById(id); + if (el) { + if (html == undefined) { + el.parentNode.innerHTML = ' '; + } else { + el.innerHTML = html; + } + } +} + /** Add row in select fieldset diff --git a/adminer/table.inc.php b/adminer/table.inc.php index 9f2f9afb..6f039c1e 100644 --- a/adminer/table.inc.php +++ b/adminer/table.inc.php @@ -6,21 +6,21 @@ if (!$fields) { } $table_status = ($fields ? table_status($TABLE) : array()); -page_header(($fields && !isset($table_status["Rows"]) ? lang('View') : lang('Table')) . ": " . h($TABLE), $error); +page_header(($fields && $table_status["Engine"] == "VIEW" ? lang('View') : lang('Table')) . ": " . h($TABLE), $error); $adminer->selectLinks($table_status); if ($fields) { echo "\n"; - echo "\n"; + echo "\n"; foreach ($fields as $field) { echo "
    " . lang('Column') . "" . lang('Type') . "" . lang('Comment') . "
    " . lang('Column') . "" . lang('Type') . (support("comment") ? "" . lang('Comment') : "") . "
    " . h($field["field"]); echo "" . h($field["full_type"]) . ($field["null"] ? " NULL" : "") . ($field["auto_increment"] ? " " . lang('Auto Increment') . "" : ""); - echo "" . nbsp($field["comment"]); + echo (support("comment") ? "" . nbsp($field["comment"]) : ""); echo "\n"; } echo "
    \n"; - if (isset($table_status["Rows"])) { + if ($table_status["Engine"] != "VIEW") { echo "

    " . lang('Indexes') . "

    \n"; $indexes = indexes($TABLE); if ($indexes) { @@ -37,7 +37,7 @@ if ($fields) { } echo '

    ' . lang('Alter indexes') . "\n"; - if ($table_status["Engine"] == "InnoDB") { + if (fk_support($table_status)) { echo "

    " . lang('Foreign keys') . "

    \n"; $foreign_keys = foreign_keys($TABLE); if ($foreign_keys) { @@ -52,16 +52,18 @@ if ($fields) { } echo "
\n"; } - echo '

' . lang('Add foreign key') . "\n"; + if ($driver != "sqlite") { + echo '

' . lang('Add foreign key') . "\n"; + } } - if ($connection->server_info >= 5) { + if (support("trigger")) { echo "

" . lang('Triggers') . "

\n"; - $result = $connection->query("SHOW TRIGGERS LIKE " . $connection->quote(addcslashes($TABLE, "%_"))); - if ($result->num_rows) { + $triggers = triggers($TABLE); + if ($triggers) { echo "\n"; - while ($row = $result->fetch_assoc()) { - echo "
$row[Timing]$row[Event]" . h($row["Trigger"]) . "" . lang('Alter') . "\n"; + foreach ($triggers as $key => $val) { + echo "
$val[0]$val[1]" . h($key) . "" . lang('Alter') . "\n"; } echo "
\n"; } diff --git a/adminer/trigger.inc.php b/adminer/trigger.inc.php index 784eb756..8612b1a9 100644 --- a/adminer/trigger.inc.php +++ b/adminer/trigger.inc.php @@ -6,8 +6,8 @@ $trigger_event = array("INSERT", "UPDATE", "DELETE"); $dropped = false; if ($_POST && !$error && in_array($_POST["Timing"], $trigger_time) && in_array($_POST["Event"], $trigger_event)) { $dropped = drop_create( - "DROP TRIGGER " . idf_escape($_GET["name"]), - "CREATE TRIGGER " . idf_escape($_POST["Trigger"]) . " $_POST[Timing] $_POST[Event] ON " . idf_escape($TABLE) . " FOR EACH ROW\n$_POST[Statement]", + "DROP TRIGGER " . idf_escape($_GET["name"]) . ($driver == "pgsql" ? " ON " . idf_escape($TABLE) : ""), + "CREATE TRIGGER " . idf_escape($_POST["Trigger"]) . " $_POST[Timing] $_POST[Event] ON " . idf_escape($TABLE) . " FOR EACH ROW\n$_POST[Statement]", //! FOR EACH STATEMENT ME . "table=" . urlencode($TABLE), lang('Trigger has been dropped.'), lang('Trigger has been altered.'), @@ -22,8 +22,7 @@ $row = array("Trigger" => $TABLE . "_bi"); if ($_POST) { $row = $_POST; } elseif ($_GET["name"] != "") { - $result = $connection->query("SHOW TRIGGERS WHERE `Trigger` = " . $connection->quote($_GET["name"])); - $row = $result->fetch_assoc(); + $row = trigger($_GET["name"]); } ?> diff --git a/adminer/user.inc.php b/adminer/user.inc.php index 4a868412..850dd8fa 100644 --- a/adminer/user.inc.php +++ b/adminer/user.inc.php @@ -107,7 +107,7 @@ if ($_POST) { $row = $_POST; $grants = $new_grants; } else { - $row = $_GET + array("host" => $connection->result($connection->query("SELECT SUBSTRING_INDEX(CURRENT_USER, '@', -1)"))); // create user on the same domain by default + $row = $_GET + array("host" => $connection->result("SELECT SUBSTRING_INDEX(CURRENT_USER, '@', -1)")); // create user on the same domain by default $row["pass"] = $old_pass; $row["hashed"] = true; $grants[""] = true; diff --git a/adminer/view.inc.php b/adminer/view.inc.php index b78448d5..95c805fe 100644 --- a/adminer/view.inc.php +++ b/adminer/view.inc.php @@ -5,7 +5,7 @@ if ($_POST && !$error) { $dropped = drop_create( "DROP VIEW " . idf_escape($TABLE), "CREATE VIEW " . idf_escape($_POST["name"]) . " AS\n$_POST[select]", - substr(ME, 0, -1), + ($_POST["drop"] ? substr(ME, 0, -1) : ME . "table=" . urlencode($_POST["name"])), lang('View has been dropped.'), lang('View has been altered.'), lang('View has been created.'), diff --git a/changes.txt b/changes.txt index 99b6a254..66e66ad4 100644 --- a/changes.txt +++ b/changes.txt @@ -1,3 +1,13 @@ +Adminer 3.0.0-dev: +Drivers for MS SQL, SQLite, PostgreSQL +Show number of tables in server overview +Allow concurrent logins on the same server +Operator LIKE %% +Remember export parameters in cookie +Allow semicolon as CSV separator +Defer table information in database overview to JavaScript (performance) +Big tables optimizations (performance) + Adminer 2.3.2 (released 2010-04-21): Fix COUNT(*) link Fix Save and continue edit diff --git a/compile.php b/compile.php index cf990436..230dfe79 100644 --- a/compile.php +++ b/compile.php @@ -138,7 +138,7 @@ function php_shrink($input) { } elseif ($token[0] === T_VARIABLE && !isset($special_variables[$token[1]])) { $token[1] = '$' . $short_variables[$token[1]]; } - if (isset($set[substr($output, -1)]) || isset($set[$token[1]{0}])) { + if (isset($set[substr($output, -1)]) || isset($set[$token[1][0]])) { $space = ''; } $output .= $space . $token[1]; @@ -157,10 +157,16 @@ function compile_file($match) { return call_user_func($match[2], file_get_contents(dirname(__FILE__) . "/$project/$match[1]")); } +$DRIVER = ""; +if (file_exists(dirname(__FILE__) . "/adminer/drivers/" . $_SERVER["argv"][1] . ".inc.php")) { + $DRIVER = $_SERVER["argv"][1]; + array_shift($_SERVER["argv"]); +} + unset($_COOKIE["adminer_lang"]); $_SESSION["lang"] = $_SERVER["argv"][1]; // Adminer functions read language from session +include dirname(__FILE__) . "/adminer/include/lang.inc.php"; if (isset($_SESSION["lang"])) { - include dirname(__FILE__) . "/adminer/include/lang.inc.php"; if (isset($_SERVER["argv"][2]) || !isset($langs[$_SESSION["lang"]])) { echo "Usage: php compile.php [lang]\nPurpose: Compile adminer[-lang].php and editor[-lang].php.\n"; exit(1); @@ -168,11 +174,44 @@ if (isset($_SESSION["lang"])) { include dirname(__FILE__) . "/adminer/lang/$_SESSION[lang].inc.php"; } +// check function definition in drivers +$filename = dirname(__FILE__) . "/adminer/drivers/mysql.inc.php"; +preg_match_all('~\\bfunction ([^(]+)~', file_get_contents($filename), $matches); //! respect context (extension, class) +$functions = array_combine($matches[1], $matches[0]); +unset($functions["__destruct"], $functions["Min_DB"], $functions["Min_Result"]); +foreach (glob(dirname(__FILE__) . "/adminer/drivers/" . ($DRIVER ? $DRIVER : "*") . ".inc.php") as $filename) { + if ($filename != "mysql.inc.php") { + $file = file_get_contents($filename); + foreach ($functions as $val) { + if (!strpos($file, "$val(")) { + echo "Missing $val in $filename\n"; + } + } + } +} + foreach (array("adminer", "editor") as $project) { $lang_ids = array(); // global variable simplifies usage in a callback function $file = file_get_contents(dirname(__FILE__) . "/$project/index.php"); + if ($DRIVER && $DRIVER != "mysql") { + $_GET[$DRIVER] = true; // to load the driver + include_once dirname(__FILE__) . "/adminer/drivers/$DRIVER.inc.php"; + foreach (array("view", "event", "privileges", "user", "processlist", "variables", "trigger") as $feature) { + if (!support($feature)) { + $file = str_replace("} elseif (isset(\$_GET[\"$feature\"])) {\n\tinclude \"./$feature.inc.php\";\n", "", $file); + } + } + if (!support("routine")) { + $file = str_replace("} elseif (isset(\$_GET[\"procedure\"])) {\n\tinclude \"./procedure.inc.php\";\n", "", $file); + $file = str_replace("} elseif (isset(\$_GET[\"call\"])) {\n\tinclude \"./call.inc.php\";\n", "", $file); + $file = str_replace("if (isset(\$_GET[\"callf\"])) {\n\t\$_GET[\"call\"] = \$_GET[\"callf\"];\n}\nif (isset(\$_GET[\"function\"])) {\n\t\$_GET[\"procedure\"] = \$_GET[\"function\"];\n}\n", "", $file); + } + } $file = preg_replace_callback('~\\b(include|require) "([^"]*)";~', 'put_file', $file); $file = str_replace('include "../adminer/include/coverage.inc.php";', '', $file); + if ($DRIVER) { + $file = preg_replace('(include "../adminer/drivers/(?!' . preg_quote($DRIVER) . ').*\\s*)', '', $file); + } $file = preg_replace_callback('~\\b(include|require) "([^"]*)";~', 'put_file', $file); // bootstrap.inc.php $file = preg_replace_callback("~lang\\('((?:[^\\\\']+|\\\\.)*)'([,)])~s", 'lang_ids', $file); $file = preg_replace_callback('~\\b(include|require) "([^"]*\\$LANG.inc.php)";~', 'put_file_lang', $file); @@ -187,11 +226,11 @@ foreach (array("adminer", "editor") as $project) { $replace = 'h(preg_replace("~\\\\\\\\?.*~", "", $_SERVER["REQUEST_URI"])) . "?file=\\1&version=' . $VERSION; $file = preg_replace('~\\.\\./adminer/static/(default\\.css|functions\\.js|favicon\\.ico)~', '', $file); $file = preg_replace('~\\.\\./adminer/static/([^\'"]*)~', '" . ' . $replace, $file); - $file = str_replace("../externals/jush/", "https://jush.svn.sourceforge.net/svnroot/jush/trunk/", $file); // mixed-content warning if Adminer runs on HTTPS and external files on HTTP + $file = str_replace("../externals/jush/", "https://www.adminer.org/static/", $file); $file = preg_replace("~<\\?php\\s*\\?>\n?|\\?>\n?<\\?php~", '', $file); $file = php_shrink($file); - $filename = $project . ($_SESSION["lang"] ? "-$_SESSION[lang]" : "") . ".php"; // "$project-$VERSION" + $filename = $project . ($DRIVER ? "-$DRIVER" : "") . ($_SESSION["lang"] ? "-$_SESSION[lang]" : "") . ".php"; // . "-$VERSION" fwrite(fopen($filename, "w"), $file); // file_put_contents() since PHP 5 - echo "$filename created.\n"; + echo "$filename created (" . strlen($file) . " B).\n"; } diff --git a/coverage.php b/coverage.php index 487b150e..8dc4421a 100644 --- a/coverage.php +++ b/coverage.php @@ -13,7 +13,7 @@ function xhtml_open_tags($s) { $return = array(); preg_match_all('~<([^>]+)~', $s, $matches); foreach ($matches[1] as $val) { - if ($val{0} == "/") { + if ($val[0] == "/") { array_pop($return); } elseif (substr($val, -1) != "/") { $return[] = $val; diff --git a/editor/db.inc.php b/editor/db.inc.php index ce13ce9b..43abfbda 100644 --- a/editor/db.inc.php +++ b/editor/db.inc.php @@ -1,9 +1,9 @@

- + "> diff --git a/editor/include/adminer.inc.php b/editor/include/adminer.inc.php index 79872930..f6aad529 100644 --- a/editor/include/adminer.inc.php +++ b/editor/include/adminer.inc.php @@ -1,12 +1,14 @@ ="); - var $values = array(); // protected + var $_values = array(); function name() { return lang('Editor'); } + //! driver + function credentials() { return array(); // default INI settings } @@ -19,15 +21,15 @@ class Adminer { global $connection; $dbs = get_databases(false); return (!$dbs - ? $connection->result($connection->query("SELECT SUBSTRING_INDEX(CURRENT_USER, '@', 1)")) // username without the database list + ? $connection->result("SELECT SUBSTRING_INDEX(CURRENT_USER, '@', 1)") // username without the database list : $dbs[(information_schema($dbs[0]) ? 1 : 0)] // first available database ); } - function loginForm($username) { + function loginForm() { ?> -
+
">
server_info >= 5) { //! requires MySQL 5 - $result = $connection->query("SELECT TABLE_NAME, CONSTRAINT_NAME, COLUMN_NAME, REFERENCED_COLUMN_NAME + $result = $connection->query("SELECT TABLE_NAME, CONSTRAINT_NAME, COLUMN_NAME, REFERENCED_COLUMN_NAME FROM information_schema.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA = " . $connection->quote($this->database()) . " AND REFERENCED_TABLE_SCHEMA = " . $connection->quote($this->database()) . " AND REFERENCED_TABLE_NAME = " . $connection->quote($table) . " ORDER BY ORDINAL_POSITION"); + if ($result) { //! requires MySQL 5 while ($row = $result->fetch_assoc()) { $return[$row["TABLE_NAME"]]["keys"][$row["CONSTRAINT_NAME"]][$row["COLUMN_NAME"]] = $row["REFERENCED_COLUMN_NAME"]; } @@ -93,7 +95,7 @@ ORDER BY ORDINAL_POSITION"); foreach ($cols as $column => $val) { $link .= where_link($i++, $column, $row[$val]); } - echo "$backwardKey[name]"; + echo "" . h($backwardKey["name"]) . ""; $link = ME . 'edit=' . urlencode($table); foreach ($cols as $column => $val) { $link .= "&set" . urlencode("[" . bracket_escape($column) . "]") . "=" . urlencode($row[$val]); @@ -119,7 +121,6 @@ ORDER BY ORDINAL_POSITION"); } function rowDescriptions($rows, $foreignKeys) { - global $connection; $return = $rows; foreach ($rows[0] as $key => $val) { foreach ((array) $foreignKeys[$key] as $foreignKey) { @@ -133,7 +134,7 @@ ORDER BY ORDINAL_POSITION"); $ids[$row[$key]] = exact_value($row[$key]); } // uses constant number of queries to get the descriptions, join would be complex, multiple queries would be slow - $descriptions = $this->values[$foreignKey["table"]]; + $descriptions = $this->_values[$foreignKey["table"]]; if (!$descriptions) { $descriptions = get_key_vals("SELECT $id, $name FROM " . idf_escape($foreignKey["table"]) . " WHERE $id IN (" . implode(", ", $ids) . ")"); } @@ -153,7 +154,7 @@ ORDER BY ORDINAL_POSITION"); function selectVal($val, $link, $field) { $return = ($val == "NULL" ? " " : $val); - if (ereg('blob|binary', $field["type"]) && !is_utf8($val)) { + if (ereg('binary|blob|bytea', $field["type"]) && !is_utf8($val)) { $return = lang('%d byte(s)', strlen($val)); if (ereg("^(GIF|\xFF\xD8\xFF|\x89\x50\x4E\x47\x0D\x0A\x1A\x0A)", $val)) { // GIF|JPG|PNG, getimagetype() works with filename $return = "$return"; @@ -181,39 +182,47 @@ ORDER BY ORDINAL_POSITION"); } function selectColumnsPrint($select, $columns) { - //! allow grouping functions by indexes + // can allow grouping functions by indexes } function selectSearchPrint($where, $columns, $indexes) { - //! foreign keys + $where = (array) $_GET["where"]; echo '

' . lang('Search') . "
\n"; $keys = array(); - foreach ((array) $_GET["where"] as $key => $val) { + foreach ($where as $key => $val) { $keys[$val["col"]] = $key; } - $i = -1; - foreach ($columns as $name => $desc) { - $key = $keys[$name]; - $options = $this->foreignKeyOptions($_GET["select"], $name); - if ($options) { - echo "
" . h($desc) . ":
\n"; + $i = 0; + foreach (fields($_GET["select"]) as $name => $field) { + if (ereg("enum", $field["type"])) { //! set - uses 1 << $i and FIND_IN_SET() + $desc = $columns[$name]; + $key = $keys[$name]; $i--; + echo "
" . h($desc) . ":"; + enum_input("checkbox", "where[$i][val][]", $field, (array) $where[$key]["val"]); //! impossible to search for NULL + echo "
\n"; + unset($columns[$name]); + } + } + foreach ($columns as $name => $desc) { + $options = $this->_foreignKeyOptions($_GET["select"], $name); + if ($options) { + $key = $keys[$name]; + $i--; + echo "
" . h($desc) . ":
\n"; unset($columns[$name]); - if (isset($key)) { - unset($_GET["where"][$key]); - } } } $i = 0; - foreach ((array) $_GET["where"] as $val) { - if ("$val[col]$val[val]" != "") { - echo "
"; + foreach ($where as $val) { + if ($columns[$val["col"]] && "$val[col]$val[val]" != "") { + echo "
"; echo html_select("where[$i][op]", array(-1 => "") + $this->operators, $val["op"]); echo "
\n"; $i++; } } - echo "
"; + echo "
"; echo html_select("where[$i][op]", array(-1 => "") + $this->operators); echo "
\n"; echo "
\n"; @@ -260,7 +269,7 @@ ORDER BY ORDINAL_POSITION"); echo lang('Subject') . ": \n"; echo "


\n"; echo html_select("email_addition", $columns, $_POST["email_addition"]) . "\n"; //! JavaScript - echo "

"; + echo "

" . lang('Attachments') . ": "; echo "

" . (count($emailFields) == 1 ? '' : html_select("email_field", $emailFields)); echo "\n"; echo "\n"; @@ -273,15 +282,21 @@ ORDER BY ORDINAL_POSITION"); function selectSearchProcess($fields, $indexes) { $return = array(); - foreach ((array) $_GET["where"] as $key => $val) { - $col = $val["col"]; - if (($key < 0 ? "" : $col) . $val["val"] != "") { + foreach ((array) $_GET["where"] as $key => $where) { + $col = $where["col"]; + $op = $where["op"]; + $val = $where["val"]; + if (($key < 0 ? "" : $col) . $val != "") { $conds = array(); foreach (($col != "" ? array($col => $fields[$col]) : $fields) as $name => $field) { - if ($col != "" || is_numeric($val["val"]) || !ereg('int|float|double|decimal', $field["type"])) { - $text_type = ereg('char|text|enum|set', $field["type"]); - $value = $this->processInput($field, ($text_type && ereg('^[^%]+$', $val["val"]) ? "%$val[val]%" : $val["val"])); - $conds[] = idf_escape($name) . ($value == "NULL" ? " IS" . ($val["op"] == ">=" ? " NOT" : "") : (in_array($val["op"], $this->operators) ? " $val[op]" : ($val["op"] != "=" && $text_type ? " LIKE" : " ="))) . " $value"; //! can issue "Illegal mix of collations" for columns in other character sets - solve by CONVERT($name using utf8) + if ($col != "" || is_numeric($val) || !ereg('int|float|double|decimal', $field["type"])) { + if ($col != "" && $field["type"] == "enum") { + $conds[] = idf_escape($name) . " IN (" . implode(", ", array_map('intval', $val)) . ")"; + } else { + $text_type = ereg('char|text|enum|set', $field["type"]); + $value = $this->processInput($field, ($text_type && ereg('^[^%]+$', $val) ? "%$val%" : $val)); + $conds[] = idf_escape($name) . ($value == "NULL" ? " IS" . ($op == ">=" ? " NOT" : "") : (in_array($op, $this->operators) ? " $op" : ($op != "=" && $text_type ? " LIKE" : " ="))) . " $value"; //! can issue "Illegal mix of collations" for columns in other character sets - solve by CONVERT($name using utf8) + } } } $return[] = ($conds ? "(" . implode(" OR ", $conds) . ")" : "0"); @@ -332,8 +347,7 @@ ORDER BY ORDINAL_POSITION"); if ($_POST["all"] || $_POST["check"]) { $field = idf_escape($_POST["email_field"]); $subject = $_POST["email_subject"]; - $eol = (strncasecmp(PHP_OS, "win", 3) ? "\n" : "\r\n"); - $message = str_replace("\n", $eol, wordwrap(str_replace("\r", "", "$_POST[email_message]\n"))); + $message = $_POST["email_message"]; preg_match_all('~\\{\\$([a-z0-9_]+)\\}~i', "$subject.$message", $matches); // allows {$name} in subject or message $result = $connection->query("SELECT DISTINCT $field" . ($matches[1] ? ", " . implode(", ", array_map('idf_escape', array_unique($matches[1]))) : "") . " FROM " . idf_escape($_GET["select"]) . " WHERE $field IS NOT NULL AND $field != ''" @@ -344,37 +358,14 @@ ORDER BY ORDINAL_POSITION"); while ($row = $result->fetch_assoc()) { $rows[] = $row; } - $boundary = uniqid("boundary"); - $attachments = ""; - $email_files = $_FILES["email_files"]; - foreach ($email_files["error"] as $key => $val) { - if (!$val) { - $attachments .= "--$boundary$eol" - . "Content-Type: " . str_replace("\n", "", $email_files["type"][$key]) . $eol - . "Content-Disposition: attachment; filename=\"" . preg_replace('~["\\n]~', '', $email_files["name"][$key]) . "\"$eol" - . "Content-Transfer-Encoding: base64$eol" - . $eol . chunk_split(base64_encode(file_get_contents($email_files["tmp_name"][$key])), 76, $eol) . $eol - ; - } - } - $beginning = ""; - $headers = "Content-Type: text/plain; charset=utf-8$eol" . "Content-Transfer-Encoding: 8bit"; - if ($attachments) { - $attachments .= "--$boundary--$eol"; - $beginning = "--$boundary$eol$headers$eol$eol"; - $headers = "Content-Type: multipart/mixed; boundary=\"$boundary\""; - } - $headers .= $eol . "MIME-Version: 1.0$eol" . "X-Mailer: Adminer Editor" - . ($_POST["email_from"] ? $eol . "From: " . str_replace("\n", "", $_POST["email_from"]) : "") //! should escape display name - ; $fields = fields($_GET["select"]); foreach ($this->rowDescriptions($rows, $foreignKeys) as $row) { - $replace = array(); + $replace = array('{\\' => '{'); // allow literal {$name} foreach ($matches[1] as $val) { - $replace['{$' . "$val}"] = $this->editVal($row[$val], $fields[$val]); //! allow literal {$name} + $replace['{$' . "$val}"] = $this->editVal($row[$val], $fields[$val]); } $email = $row[$_POST["email_field"]]; - if (is_email($email) && mail($email, email_header(strtr($subject, $replace)), $beginning . strtr($message, $replace) . $attachments, $headers)) { + if (is_email($email) && send_email($email, strtr($subject, $replace), strtr($message, $replace), $_POST["email_from"], $_FILES["email_files"])) { $sent++; } } @@ -391,6 +382,7 @@ ORDER BY ORDINAL_POSITION"); function editFunctions($field) { $return = array("" => ($field["null"] || $field["auto_increment"] || $field["full_type"] == "tinyint(1)" ? "" : "*")); + //! respect driver if (ereg('date|time', $field["type"])) { $return[] = "now"; } @@ -404,7 +396,7 @@ ORDER BY ORDINAL_POSITION"); if ($field["type"] == "enum") { return ($field["null"] ? "" : ""); } - $options = $this->foreignKeyOptions($table, $field["field"]); + $options = $this->_foreignKeyOptions($table, $field["field"]); if ($options) { return "" . optionlist($options, $value, true) . ""; } @@ -429,20 +421,22 @@ ORDER BY ORDINAL_POSITION"); $return = $connection->quote($return); if (!ereg('varchar|text', $field["type"]) && $field["full_type"] != "tinyint(1)" && $value == "") { $return = "NULL"; + } elseif (ereg('^(md5|sha1)$', $function)) { + $return = "$function($return)"; } return $return; } - function dumpOutput($select) { + function dumpOutput($select, $value = "") { return ""; } - function dumpFormat($select) { - return "CSV"; + function dumpFormat($select, $value = "") { + return html_select("format", array('csv' => 'CSV,', 'csv;' => 'CSV;'), $value, $select); } function navigation($missing) { - global $VERSION; + global $VERSION, $token; ?>

name(); ?> @@ -454,7 +448,7 @@ ORDER BY ORDINAL_POSITION"); ?>

-"> +

@@ -480,27 +474,25 @@ ORDER BY ORDINAL_POSITION"); } } - function foreignKeyOptions($table, $column) { // protected + function _foreignKeyOptions($table, $column) { global $connection; + $table_status = table_status($table); $foreignKeys = column_foreign_keys($table); foreach ((array) $foreignKeys[$column] as $foreignKey) { if (count($foreignKey["source"]) == 1) { $id = idf_escape($foreignKey["target"][0]); $name = $this->rowDescription($foreignKey["table"]); if ($name != "") { - $return = &$this->values[$foreignKey["table"]]; + $return = &$this->_values[$foreignKey["table"]]; if (!isset($return)) { - $return = array("" => "") + get_key_vals("SELECT $id, $name FROM " . idf_escape($foreignKey["table"]) . " ORDER BY 2 LIMIT 1001"); - if (count($return) > 1001) { - $return = array(); - } + $return = ($table_status["Rows"] > 1000 ? array() : array("" => "") + get_key_vals("SELECT $id, $name FROM " . idf_escape($foreignKey["table"]) . " ORDER BY 2")); } return $return; } } } } - + } $adminer = (function_exists('adminer_object') ? adminer_object() : new Adminer); diff --git a/editor/include/editing.inc.php b/editor/include/editing.inc.php index a1d73585..58fedbb5 100644 --- a/editor/include/editing.inc.php +++ b/editor/include/editing.inc.php @@ -8,16 +8,38 @@ function email_header($header) { return "=?UTF-8?B?" . base64_encode($header) . "?="; //! split long lines } -/** Get keys from first column and values from second +/** Send e-mail in UTF-8 * @param string -* @return array +* @param string +* @param string +* @param string +* @param array +* @return */ -function get_key_vals($query) { - global $connection; - $return = array(); - $result = $connection->query($query); - while ($row = $result->fetch_row()) { - $return[$row[0]] = $row[1]; +function send_email($email, $subject, $message, $from = "", $files = array()) { + $eol = (strncasecmp(PHP_OS, "win", 3) ? "\n" : "\r\n"); // PHP_EOL available since PHP 4.3.10 and 5.0.2 + $message = str_replace("\n", $eol, wordwrap(str_replace("\r", "", "$message\n"))); + $boundary = uniqid("boundary"); + $attachments = ""; + foreach ($files["error"] as $key => $val) { + if (!$val) { + $attachments .= "--$boundary$eol" + . "Content-Type: " . str_replace("\n", "", $files["type"][$key]) . $eol + . "Content-Disposition: attachment; filename=\"" . preg_replace('~["\\n]~', '', $files["name"][$key]) . "\"$eol" + . "Content-Transfer-Encoding: base64$eol$eol" + . chunk_split(base64_encode(file_get_contents($files["tmp_name"][$key])), 76, $eol) . $eol + ; + } } - return $return; + $beginning = ""; + $headers = "Content-Type: text/plain; charset=utf-8$eol" . "Content-Transfer-Encoding: 8bit"; + if ($attachments) { + $attachments .= "--$boundary--$eol"; + $beginning = "--$boundary$eol$headers$eol$eol"; + $headers = "Content-Type: multipart/mixed; boundary=\"$boundary\""; + } + $headers .= $eol . "MIME-Version: 1.0$eol" . "X-Mailer: Adminer Editor" + . ($from ? $eol . "From: " . str_replace("\n", "", $from) : "") //! should escape display name + ; + return mail($email, email_header($subject), $beginning . $message . $attachments, $headers); } diff --git a/editor/index.php b/editor/index.php index 112f2018..ef6d3514 100644 --- a/editor/index.php +++ b/editor/index.php @@ -1,5 +1,5 @@ open - /adminer/coverage.php?coverage=0 + coverage.php?coverage=0 open - /adminer/adminer/?lang=en&username=ODBC + adminer/?username=ODBC&lang=en diff --git a/tests/1-create-database.html b/tests/1-create-database.html index 5ff2dc98..6ed566de 100644 --- a/tests/1-create-database.html +++ b/tests/1-create-database.html @@ -13,7 +13,7 @@ open - /adminer/adminer/ + adminer/?username=ODBC diff --git a/tests/10-clone.html b/tests/10-clone.html index 2fad3a68..2dfa38f5 100644 --- a/tests/10-clone.html +++ b/tests/10-clone.html @@ -13,7 +13,7 @@ open - /adminer/adminer/?db=adminer_test&select=albums + adminer/?username=ODBC&db=adminer_test&select=albums diff --git a/tests/11-reference.html b/tests/11-reference.html index 02411c53..89ec709c 100644 --- a/tests/11-reference.html +++ b/tests/11-reference.html @@ -13,7 +13,7 @@ open - /adminer/adminer/?db=adminer_test&select=albums + adminer/?username=ODBC&db=adminer_test&select=albums diff --git a/tests/12-update.html b/tests/12-update.html index dfa19da9..0e1be973 100644 --- a/tests/12-update.html +++ b/tests/12-update.html @@ -13,7 +13,7 @@ open - /adminer/adminer/?db=adminer_test&edit=albums&where%5Bid%5D=2 + adminer/?username=ODBC&db=adminer_test&edit=albums&where%5Bid%5D=2 diff --git a/tests/13-delete.html b/tests/13-delete.html index 058f9ab1..2f9ff8f3 100644 --- a/tests/13-delete.html +++ b/tests/13-delete.html @@ -13,7 +13,7 @@ open - /adminer/adminer/?db=adminer_test&select=albums + adminer/?username=ODBC&db=adminer_test&select=albums diff --git a/tests/14-truncate.html b/tests/14-truncate.html index cc95bc2e..f93d1c63 100644 --- a/tests/14-truncate.html +++ b/tests/14-truncate.html @@ -13,7 +13,7 @@ open - /adminer/adminer/?db=adminer_test&select=albums + adminer/?username=ODBC&db=adminer_test&select=albums diff --git a/tests/15-privileges.html b/tests/15-privileges.html index ec655068..577a74f5 100644 --- a/tests/15-privileges.html +++ b/tests/15-privileges.html @@ -13,7 +13,7 @@ open - /adminer/adminer/?user= + adminer/?username=ODBC&user= diff --git a/tests/16-processlist.html b/tests/16-processlist.html index e9c58348..5f08ec33 100644 --- a/tests/16-processlist.html +++ b/tests/16-processlist.html @@ -13,7 +13,7 @@ open - /adminer/adminer/?processlist= + adminer/?username=ODBC&processlist= diff --git a/tests/17-export.html b/tests/17-export.html index 36f4010b..cf85f7ee 100644 --- a/tests/17-export.html +++ b/tests/17-export.html @@ -13,7 +13,7 @@ open - /adminer/adminer/?db=adminer_test&dump= + adminer/?username=ODBC&db=adminer_test&dump= diff --git a/tests/18-events.html b/tests/18-events.html index c66da6a6..d4b4994d 100644 --- a/tests/18-events.html +++ b/tests/18-events.html @@ -13,7 +13,7 @@ open - /adminer/adminer/?db=adminer_test&event= + adminer/?username=ODBC&db=adminer_test&event= diff --git a/tests/19-procedures.html b/tests/19-procedures.html index 0eccd296..3d3bd3e2 100644 --- a/tests/19-procedures.html +++ b/tests/19-procedures.html @@ -13,7 +13,7 @@ open - /adminer/adminer/?db=adminer_test&procedure= + adminer/?username=ODBC&db=adminer_test&procedure= diff --git a/tests/2-create-table.html b/tests/2-create-table.html index 1620815d..22cfbf80 100644 --- a/tests/2-create-table.html +++ b/tests/2-create-table.html @@ -13,7 +13,7 @@ open - /adminer/adminer/?db=adminer_test + adminer/?username=ODBC&db=adminer_test diff --git a/tests/20-partitioning.html b/tests/20-partitioning.html index b19ab1eb..b4661cf2 100644 --- a/tests/20-partitioning.html +++ b/tests/20-partitioning.html @@ -13,7 +13,7 @@ open - /adminer/adminer/?db=adminer_test&table=interprets + adminer/?username=ODBC&db=adminer_test&table=interprets diff --git a/tests/21-variables.html b/tests/21-variables.html index e043a4db..7f762835 100644 --- a/tests/21-variables.html +++ b/tests/21-variables.html @@ -13,7 +13,7 @@ open - /adminer/adminer/?variables= + adminer/?username=ODBC&variables= diff --git a/tests/22-history.html b/tests/22-history.html index e6645957..6c1142ab 100644 --- a/tests/22-history.html +++ b/tests/22-history.html @@ -13,7 +13,7 @@ open - /adminer/adminer/?sql= + adminer/?username=ODBC&sql= diff --git a/tests/23-editor.html b/tests/23-editor.html index 46f6328c..86948fc7 100644 --- a/tests/23-editor.html +++ b/tests/23-editor.html @@ -13,7 +13,7 @@ open - /adminer/editor/example.php?lang=en&username=admin + editor/example.php?lang=en&username=admin diff --git a/tests/24-explain.html b/tests/24-explain.html index 995acec3..f0b0ccaf 100644 --- a/tests/24-explain.html +++ b/tests/24-explain.html @@ -13,7 +13,7 @@ open - /adminer/adminer/?db=adminer_test&select=albums + adminer/?username=ODBC&db=adminer_test&select=albums diff --git a/tests/3-create-index.html b/tests/3-create-index.html index 72b48478..6db2ca7f 100644 --- a/tests/3-create-index.html +++ b/tests/3-create-index.html @@ -13,7 +13,7 @@ open - /adminer/adminer/?db=adminer_test&table=interprets + adminer/?username=ODBC&db=adminer_test&table=interprets diff --git a/tests/4-create-table-2.html b/tests/4-create-table-2.html index ca8437d5..5bfe609b 100644 --- a/tests/4-create-table-2.html +++ b/tests/4-create-table-2.html @@ -13,7 +13,7 @@ open - /adminer/adminer/?db=adminer_test&table=interprets&lang=en + adminer/?username=ODBC&db=adminer_test&table=interprets&lang=en diff --git a/tests/5-foreign-key.html b/tests/5-foreign-key.html index 1992e3a9..2b87ea83 100644 --- a/tests/5-foreign-key.html +++ b/tests/5-foreign-key.html @@ -13,7 +13,7 @@ open - /adminer/adminer/?db=adminer_test&table=albums + adminer/?username=ODBC&db=adminer_test&table=albums diff --git a/tests/6-alter-table.html b/tests/6-alter-table.html index d983a900..98e501b2 100644 --- a/tests/6-alter-table.html +++ b/tests/6-alter-table.html @@ -13,7 +13,7 @@ open - /adminer/adminer/?db=adminer_test&table=interprets + adminer/?username=ODBC&db=adminer_test&table=interprets diff --git a/tests/7-create-trigger.html b/tests/7-create-trigger.html index 8fae68f3..328873a2 100644 --- a/tests/7-create-trigger.html +++ b/tests/7-create-trigger.html @@ -13,7 +13,7 @@ open - /adminer/adminer/?db=adminer_test&trigger=albums + adminer/?username=ODBC&db=adminer_test&trigger=albums diff --git a/tests/8-create-view.html b/tests/8-create-view.html index ce8e4a44..37623f29 100644 --- a/tests/8-create-view.html +++ b/tests/8-create-view.html @@ -13,7 +13,7 @@ open - /adminer/adminer/?db=adminer_test&view= + adminer/?username=ODBC&db=adminer_test&view= diff --git a/tests/9-insert.html b/tests/9-insert.html index 60eebf6c..8def6caa 100644 --- a/tests/9-insert.html +++ b/tests/9-insert.html @@ -13,7 +13,7 @@ open - /adminer/adminer/?db=adminer_test&edit=interprets + adminer/?username=ODBC&db=adminer_test&edit=interprets @@ -33,7 +33,7 @@ open - /adminer/adminer/?db=adminer_test&edit=albums + adminer/?username=ODBC&db=adminer_test&edit=albums diff --git a/tests/logout.html b/tests/logout.html index 1e1480cd..e4623742 100644 --- a/tests/logout.html +++ b/tests/logout.html @@ -13,7 +13,7 @@ open - /adminer/adminer/ + adminer/?username=ODBC @@ -38,7 +38,7 @@ open - /adminer/coverage.php + coverage.php diff --git a/todo.txt b/todo.txt index 1a124759..6141fbf5 100644 --- a/todo.txt +++ b/todo.txt @@ -2,9 +2,7 @@ MySQL 5 BIT data type Transactions in export Create view and routine options Mass editation of individual rows -Offer enum and set items in search - whisperer Variables editation, especially timezone -Use event $intervals + microseconds in relative date functions Optionally check IP address Disable spell checking in SQL textareas - spellcheck="false" Accept Tab in SQL textareas, Ctrl+Enter to send form @@ -12,14 +10,40 @@ Highlight SQL textarea, then display query inside textarea in select - may use e Blob download and image display in edit form (important for Editor with hidden fields in select) Add title to Logout, edit (in select) and select (in menu) in style "hever" Shift-click in checkboxes to select range -? LIKE %% operator +Export by GET parameters +Only first part of big BZ2 export is readable ? Column and table names auto-completition in SQL textarea -? Aliasing of built-in functions can save 7 KB, function minification can save 7 KB, substitution of repetitive $a["a"] can save 4 KB, substitution of $_GET and friends can save 2 KB, JS packer can save 1 KB, not enclosing HTML attribute values can save 1.2 KB, replacing \\n by \n can save .3 KB +? Aliasing of built-in functions can save 7 KB, function minification can save 7 KB, substitution of repetitive $a["a"] can save 4 KB, substitution of $_GET and friends can save 2 KB, aliasing of $connection->query, $connection->result and $connection->quote can save ~ 3 KB, JS packer can save 1 KB, not enclosing HTML attribute values can save 1.2 KB, replacing \\n by \n can save .3 KB ? Branch binary_compile: LZW compression of translations can save 30 KB, LZW compression of all texts can save 11 KB, remove of base64_decode() + using chars 127-255 in minification can save 1 KB ? AJAX editing - select page has all data to display edit form +? MySQL geometry support +Translations - database(s) have been dropped Editor: JavaScript data validation - columns containing word email, url, ... Joining tables - PRIMARY KEY (table, joining) Rank, Tree structure Add whisperer to fields with foreign key to big table + +SQLite: +CREATE DATABASE - PRAGMA encoding = "UTF-8" +Detecion of non-existing database +DROP DATABASE by file operations +CSV import - ON DUPLICATE KEY UPDATE +Export - views, triggers +Delimiter in export and SQL command + +PostgreSQL: +Users - SELECT * FROM pg_user +ORDER BY COUNT(*) +Table schema +Export - http://www.postgresql.org/docs/8.4/static/functions-info.html +Table status - http://www.postgresql.org/docs/8.4/static/functions-admin.html +Column rights - http://www.postgresql.org/docs/8.4/static/functions-info.html +Move table - ALTER TABLE SET SCHEMA +bool in Editor + +MS SQL: +Rename by sp_rename +Detection of table collation +PDO driver diff --git a/version.js b/version.js index 86e472f4..119a4ecd 100644 --- a/version.js +++ b/version.js @@ -1,4 +1,4 @@ -// downloaded from repository by verifyVersion() +// downloaded from repository by verifyVersion() before Adminer 3.0.0 (function () { // cookie function is not defined in older versions var date = new Date(); date.setDate(date.getDate() + 7); // valid for 7 days