1
0
mirror of https://github.com/vrana/adminer.git synced 2025-08-30 09:39:51 +02:00

Compare commits

...

55 Commits

Author SHA1 Message Date
Jakub Vrana
dcf11d8fc9 PgSQL <11: Fix error in table list (fix #1128) 2025-08-04 07:46:14 +02:00
Jakub Vrana
0de6a057d3 PgSQL <10: Don't try partitions 2025-08-04 07:44:46 +02:00
Jakub Vrana
18b62aba38 Fix typo (fix #1137) 2025-08-02 08:24:00 +02:00
Jakub Vrana
10ff74552d PostgreSQL: Quote edit value with interval operator 2025-07-29 16:47:47 +02:00
Jakub Vrana
f5c7ab54f7 Update JUSH 2025-07-29 11:05:25 +02:00
Jakub Vrana
cc110c448c Plugins: PHP 5 compatibility (fix #1125) 2025-07-28 22:46:31 +02:00
Jakub Vrana
6f766f8c52 PostgreSQL: Limit dollar-quoted strings 2025-07-16 11:16:58 +02:00
Jakub Vrana
9a60d158f1 Increase routine textarea size 2025-07-07 15:22:46 +02:00
Jakub Vrana
6dec0d63b0 New plugin: Specify query timeout 2025-07-07 15:21:02 +02:00
Jakub Vrana
c9a52cd28c Link routines from syntax highlighting 2025-07-07 15:06:41 +02:00
Jakub Vrana
9424b7431e Elastic: Avoid extra newline 2025-07-07 15:06:41 +02:00
Jakub Vrana
35b2b969be Save vertical real estate 2025-06-28 18:04:30 +02:00
Jakub Vrana
165f1b241c AdminerBackwardKeys: Work with null ns in non-PgSQL 2025-06-28 11:31:05 +02:00
Jakub Vrana
4d75f822e9 PostgreSQL: Shorten values in hstore columns 2025-06-25 18:39:32 +02:00
Jakub Vrana
5e9c185596 Display data length and index length for materialized views 2025-06-25 18:38:34 +02:00
Jakub Vrana
cfd69dfd8c Trigger script=db sooner 2025-06-25 17:15:34 +02:00
Jakub Vrana
1fc5acb389 AdminerBackwardKeys: Skip relations without selected column 2025-06-25 14:57:35 +02:00
Jakub Vrana
8ca0e36970 AdminerBackwardKeys: Link only tables in the same schema 2025-06-25 14:52:12 +02:00
Jakub Vrana
9e8de24e3d Fix a typo 2025-06-25 14:39:55 +02:00
Jakub Vrana
4cb09852e3 Display @ after username without server in existing logins 2025-06-23 19:08:55 +02:00
Jakub Vrana
e115dccdae MariaDB: Parse COLLATE in routine definition (fix #1104) 2025-06-19 10:03:39 +02:00
Jakub Vrana
1ba410cad1 MySQL: Simplify routines() 2025-06-19 09:56:36 +02:00
Jakub Vrana
0764a20a19 New plugin: Display row numbers in select (fix #1106) 2025-06-19 09:35:01 +02:00
Jakub Vrana
b2c4574325 AdminerBackwardKeys: Strip table prefix 2025-06-19 09:25:03 +02:00
Jakub Vrana
466eceff40 Rename highlight plugins 2025-06-19 09:12:48 +02:00
Jakub Vrana
27c5f6d21b AdminerBackwardKeys: Support PostgreSQL 2025-06-18 18:21:38 +02:00
Jakub Vrana
2744538c8c Fix type error in multiple database export (fix #1109) 2025-06-18 10:40:06 +02:00
Matthaiks
41456c9eb7 Update Polish translation 2025-06-18 10:04:38 +02:00
Jakub Vrana
9745c12769 AdminerSqlGemini: Remove extra comments (fix #1075) 2025-06-18 09:36:17 +02:00
Jakub Vrana
f24f72ac51 Allow exporting SQL in SQL command (fix #1092) 2025-06-18 09:19:15 +02:00
Jakub Vrana
2b52a9b653 PostgreSQL: Allow comparing json columns (fix #1107) 2025-06-18 09:00:34 +02:00
Jakub Vrana
9ba4b86916 AdminerTablesFilter: Add title 2025-06-18 08:08:54 +02:00
Jakub Vrana
1cc3fdf915 Add section links in database overview 2025-06-11 19:33:07 +02:00
Jakub Vrana
b960c41d63 Elastic: Display indexes in alias (unused for now) 2025-06-11 15:16:19 +02:00
Jakub Vrana
3ec750ef7e Hide view links without view support 2025-06-11 15:16:17 +02:00
Jakub Vrana
737b631dda Elasticsearch: Support dropping aliases 2025-06-11 08:45:21 +02:00
Jakub Vrana
8fc450946c Driver plugins: Readme 2025-06-11 08:16:57 +02:00
Jakub Vrana
e282d6eb89 PostgreSQL: Display index expressions 2025-06-06 10:42:20 +02:00
Jakub Vrana
86ffeb2a1e PostgreSQL: Add SQL operator to select 2025-06-06 10:19:24 +02:00
Jakub Vrana
31530ba03e Do not order descending in GROUP BY select 2025-06-05 10:26:11 +02:00
Jakub Vrana
146d3539d8 Warn about exceeded max_file_uploads in import 2025-06-04 14:04:24 +02:00
Jakub Vrana
04068a631e Allow null value in where_link() 2025-06-03 16:17:55 +02:00
Jakub Vrana
4698686232 PostgreSQL: Hide only partitions, not all inherited tables from menu 2025-06-03 15:05:12 +02:00
Jakub Vrana
57b6afc8cb Fix heading of inherited tables 2025-06-03 14:40:08 +02:00
Jakub Vrana
e589c80f42 PostgreSQL: Show structure of inherited tables 2025-06-03 14:33:30 +02:00
Ludek Benedik
acfebf1788 Plugins pretty-json-column: Encode value to JSON without white spaces 2025-05-30 14:02:18 +02:00
Jakub Vrana
246c3c489b MySQL 5.0-: Do not load partitioning info in alter table (fix #1099) 2025-05-30 13:28:28 +02:00
yamamoto
92b95606c1 Updates Japanese translation (#1096) 2025-05-27 16:28:59 +02:00
Jakub Vrana
ac8318f387 PostgreSQL 11-: Avoid duplicate oid in table status (fix #1089) 2025-05-27 13:58:22 +02:00
Maxim Milovanov
d1c2679acd Add Win98-style design theme 2025-05-27 13:58:19 +02:00
Jakub Vrana
caf6e495dc Update externals 2025-05-27 10:37:12 +02:00
salacr
ec5ad85470 Make kill_process / process_list extendable 2025-05-07 08:29:43 +02:00
Jakub Vrana
307fabaf22 Allow specifying operator in search anywhere 2025-05-05 09:56:59 +02:00
schucan
1862b84612 Mute chmod warning 2025-05-05 08:05:36 +02:00
Jakub Vrana
e76be9f890 Develop 2025-05-05 08:04:55 +02:00
46 changed files with 689 additions and 197 deletions

View File

@@ -1,3 +1,27 @@
## Adminer dev
- Allow specifying operator in search anywhere
- Do not order descending in GROUP BY select
- Allow exporting SQL in SQL command (bug #1092)
- Add section links in database overview
- Warn about exceeded max_file_uploads in import
- Display @ after username without server in existing logins
- Display data length and index length for materialized views
- Link routines from syntax highlighting
- MySQL 5.0-: Do not load partitioning info in alter table (bug #1099)
- MariaDB: Parse COLLATE in routine definition (bug #1104)
- PostgreSQL: Show structure of inherited tables
- PostgreSQL: Display index expressions
- PostgreSQL: Add SQL operator to select
- PostgreSQL: Hide only partitions, not all inherited tables from menu
- PostgreSQL: Allow comparing json columns (bug #1107)
- PostgreSQL: Shorten values in hstore columns
- PostgreSQL: Quote edit value with interval operator
- PostgreSQL 11-: Avoid duplicate oid in table status (bug #1089)
- Elasticsearch: Support dropping aliases
- Plugins: Methods afterConnect(), processList() and killProcess()
- New plugin: Display row numbers in select (bug #1106)
- New plugin: Specify query timeout
## Adminer 5.3.0 (released 2025-05-04)
- Align numeric functions right
- Autocomplete: Support table aliases

View File

@@ -3,7 +3,7 @@ namespace Adminer;
$TABLE = $_GET["create"];
$partition_by = driver()->partitionBy;
$partitions_info = driver()->partitionsInfo($TABLE);
$partitions_info = ($partition_by ? driver()->partitionsInfo($TABLE) : array());
$referencable_primary = referencable_primary($TABLE);
$foreign_keys = array();

View File

@@ -64,12 +64,13 @@ if (adminer()->homepage()) {
echo "<form action='' method='post'>\n";
if (support("table")) {
echo "<fieldset><legend>" . lang('Search data in tables') . " <span id='selected2'></span></legend><div>";
echo "<input type='search' name='query' value='" . h($_POST["query"]) . "'>";
echo html_select("op", adminer()->operators(), idx($_POST, "op", JUSH == "elastic" ? "should" : "LIKE %%"));
echo " <input type='search' name='query' value='" . h($_POST["query"]) . "'>";
echo script("qsl('input').onkeydown = partialArg(bodyKeydown, 'search');", "");
echo " <input type='submit' name='search' value='" . lang('Search') . "'>\n";
echo "</div></fieldset>\n";
if ($_POST["search"] && $_POST["query"] != "") {
$_GET["where"][0]["op"] = driver()->convertOperator("LIKE %%");
$_GET["where"][0]["op"] = $_POST["op"];
search_tables();
}
}
@@ -95,8 +96,9 @@ if (adminer()->homepage()) {
$id = h("Table-" . $name);
echo '<tr><td>' . checkbox(($view ? "views[]" : "tables[]"), $name, in_array("$name", $tables_views, true), "", "", "", $id); // "$name" to check numeric table names
echo '<th>' . (support("table") || support("indexes") ? "<a href='" . h(ME) . "table=" . urlencode($name) . "' title='" . lang('Show structure') . "' id='$id'>" . h($name) . '</a>' : h($name));
if ($view) {
echo '<td colspan="6"><a href="' . h(ME) . "view=" . urlencode($name) . '" title="' . lang('Alter view') . '">' . (preg_match('~materialized~i', $type) ? lang('Materialized view') : lang('View')) . '</a>';
if ($view && !preg_match('~materialized~i', $type)) {
$title = lang('View');
echo '<td colspan="6">' . (support("view") ? "<a href='" . h(ME) . "view=" . urlencode($name) . "' title='" . lang('Alter view') . "'>$title</a>" : $title);
echo '<td align="right"><a href="' . h(ME) . "select=" . urlencode($name) . '" title="' . lang('Select data') . '">?</a>';
} else {
foreach (
@@ -131,6 +133,7 @@ if (adminer()->homepage()) {
echo "\n";
echo "</table>\n";
echo script("ajaxSetHtml('" . js_escape(ME) . "script=db');");
echo "</div>\n";
if (!information_schema(DB)) {
echo "<div class='footer'><div>\n";
@@ -147,18 +150,23 @@ if (adminer()->homepage()) {
. "<input type='submit' name='truncate' value='" . lang('Truncate') . "'> " . on_help(JUSH == "sqlite" ? "'DELETE'" : "'TRUNCATE" . (JUSH == "pgsql" ? "'" : " TABLE'")) . confirm()
. "<input type='submit' name='drop' value='" . lang('Drop') . "'>" . on_help("'DROP TABLE'") . confirm() . "\n";
$databases = (support("scheme") ? adminer()->schemas() : adminer()->databases());
echo "</div></fieldset>\n";
$script = "";
if (count($databases) != 1 && JUSH != "sqlite") {
echo "<fieldset><legend>" . lang('Move to other database') . " <span id='selected3'></span></legend><div>";
$db = (isset($_POST["target"]) ? $_POST["target"] : (support("scheme") ? $_GET["ns"] : DB));
echo "<p><label>" . lang('Move to other database') . ": ";
echo ($databases ? html_select("target", $databases, $db) : '<input name="target" value="' . h($db) . '" autocapitalize="off">');
echo "</label> <input type='submit' name='move' value='" . lang('Move') . "'>";
echo (support("copy") ? " <input type='submit' name='copy' value='" . lang('Copy') . "'> " . checkbox("overwrite", 1, $_POST["overwrite"], lang('overwrite')) : "");
echo "\n";
echo "</div></fieldset>\n";
$script = " selectCount('selected3', formChecked(this, /^(tables|views)\[/));";
}
echo "<input type='hidden' name='all' value=''>"; // used by trCheck()
echo script("qsl('input').onclick = function () { selectCount('selected', formChecked(this, /^(tables|views)\[/));" . (support("table") ? " selectCount('selected2', formChecked(this, /^tables\[/) || $tables);" : "") . " }");
echo script("qsl('input').onclick = function () { selectCount('selected', formChecked(this, /^(tables|views)\[/));"
. (support("table") ? " selectCount('selected2', formChecked(this, /^tables\[/) || $tables);" : "")
. "$script }"
);
echo input_token();
echo "</div></fieldset>\n";
echo "</div></div>\n";
}
echo "</form>\n";
@@ -239,9 +247,5 @@ if (adminer()->homepage()) {
}
echo '<p class="links"><a href="' . h(ME) . 'event=">' . lang('Create event') . "</a>\n";
}
if ($tables_list) {
echo script("ajaxSetHtml('" . js_escape(ME) . "script=db');");
}
}
}

View File

@@ -340,7 +340,7 @@ if (!defined('Adminer\DRIVER')) {
function partitionsInfo(string $table): array {
$from = "FROM information_schema.PARTITIONS WHERE TABLE_SCHEMA = " . q(DB) . " AND TABLE_NAME = " . q($table);
$result = connection()->query("SELECT PARTITION_METHOD, PARTITION_EXPRESSION, PARTITION_ORDINAL_POSITION $from ORDER BY PARTITION_ORDINAL_POSITION DESC LIMIT 1");
$result = $this->conn->query("SELECT PARTITION_METHOD, PARTITION_EXPRESSION, PARTITION_ORDINAL_POSITION $from ORDER BY PARTITION_ORDINAL_POSITION DESC LIMIT 1");
$return = array();
list($return["partition_by"], $return["partition"], $return["partitions"]) = $result->fetch_row();
$partitions = get_key_vals("SELECT PARTITION_NAME, PARTITION_DESCRIPTION $from AND PARTITION_NAME != '' ORDER BY PARTITION_ORDINAL_POSITION");
@@ -896,7 +896,7 @@ if (!defined('Adminer\DRIVER')) {
$space = "(?:\\s|/\\*[\s\S]*?\\*/|(?:#|-- )[^\n]*\n?|--\r?\n)";
$enum = driver()->enumLength;
$type_pattern = "((" . implode("|", array_merge(array_keys(driver()->types()), $aliases)) . ")\\b(?:\\s*\\(((?:[^'\")]|$enum)++)\\))?"
. "\\s*(zerofill\\s*)?(unsigned(?:\\s+zerofill)?)?)(?:\\s*(?:CHARSET|CHARACTER\\s+SET)\\s*['\"]?([^'\"\\s,]+)['\"]?)?";
. "\\s*(zerofill\\s*)?(unsigned(?:\\s+zerofill)?)?)(?:\\s*(?:CHARSET|CHARACTER\\s+SET)\\s*['\"]?([^'\"\\s,]+)['\"]?)?(?:\\s*COLLATE\\s*['\"]?([^'\"\\s,]+)['\"]?)?"; //! store COLLATE
$pattern = "$space*(" . ($type == "FUNCTION" ? "" : driver()->inout) . ")?\\s*(?:`((?:[^`]|``)*)`\\s*|\\b(\\S+)\\s+)$type_pattern";
$create = get_val("SHOW CREATE $type " . idf_escape($name), 2);
preg_match("~\\(((?:$pattern\\s*,?)*)\\)\\s*" . ($type == "FUNCTION" ? "RETURNS\\s+$type_pattern\\s+" : "") . "(.*)~is", $create, $match);
@@ -928,7 +928,7 @@ if (!defined('Adminer\DRIVER')) {
* @return list<string[]> ["SPECIFIC_NAME" => , "ROUTINE_NAME" => , "ROUTINE_TYPE" => , "DTD_IDENTIFIER" => ]
*/
function routines(): array {
return get_rows("SELECT ROUTINE_NAME AS SPECIFIC_NAME, ROUTINE_NAME, ROUTINE_TYPE, DTD_IDENTIFIER FROM information_schema.ROUTINES WHERE ROUTINE_SCHEMA = DATABASE()");
return get_rows("SELECT SPECIFIC_NAME, ROUTINE_NAME, ROUTINE_TYPE, DTD_IDENTIFIER FROM information_schema.ROUTINES WHERE ROUTINE_SCHEMA = DATABASE()");
}
/** Get list of available routine languages
@@ -1067,11 +1067,11 @@ if (!defined('Adminer\DRIVER')) {
}
/** Kill a process
* @param numeric-string $val
* @param numeric-string $id
* @return Result|bool
*/
function kill_process(string $val) {
return queries("KILL " . number($val));
function kill_process(string $id) {
return queries("KILL " . number($id));
}
/** Return query to get connection ID */

View File

@@ -60,6 +60,10 @@ if (isset($_GET["oracle"])) {
}
return $return;
}
function timeout(int $ms): bool {
return oci_set_call_timeout($this->link, $ms);
}
}
class Result {

View File

@@ -204,7 +204,7 @@ if (isset($_GET["pgsql"])) {
static $extensions = array("PgSQL", "PDO_PgSQL");
static $jush = "pgsql";
public $operators = array("=", "<", ">", "<=", ">=", "!=", "~", "!~", "LIKE", "LIKE %%", "ILIKE", "ILIKE %%", "IN", "IS NULL", "NOT LIKE", "NOT ILIKE", "NOT IN", "IS NOT NULL"); // no "SQL" to avoid CSRF
public $operators = array("=", "<", ">", "<=", ">=", "!=", "~", "!~", "LIKE", "LIKE %%", "ILIKE", "ILIKE %%", "IN", "IS NULL", "NOT LIKE", "NOT ILIKE", "NOT IN", "IS NOT NULL", "SQL"); //! SQL - same-site CSRF
public $functions = array("char_length", "lower", "round", "to_hex", "to_timestamp", "upper");
public $grouping = array("avg", "count", "count distinct", "max", "min", "sum");
@@ -340,7 +340,7 @@ if (isset($_GET["pgsql"])) {
}
function partitionsInfo(string $table): array {
$row = connection()->query("SELECT * FROM pg_partitioned_table WHERE partrelid = " . driver()->tableOid($table))->fetch_assoc();
$row = (min_version(10) ? connection()->query("SELECT * FROM pg_partitioned_table WHERE partrelid = " . $this->tableOid($table))->fetch_assoc() : null);
if ($row) {
$attrs = get_vals("SELECT attname FROM pg_attribute WHERE attrelid = $row[partrelid] AND attnum IN (" . str_replace(" ", ", ", $row["partattrs"]) . ")"); //! ordering
$by = array('h' => 'HASH', 'l' => 'LIST', 'r' => 'RANGE');
@@ -448,15 +448,14 @@ ORDER BY 1";
get_rows("SELECT
relname AS \"Name\",
CASE relkind WHEN 'v' THEN 'view' WHEN 'm' THEN 'materialized view' ELSE 'table' END AS \"Engine\"" . ($has_size ? ",
pg_table_size(oid) AS \"Data_length\",
pg_indexes_size(oid) AS \"Index_length\"" : "") . ",
obj_description(oid, 'pg_class') AS \"Comment\",
pg_table_size(c.oid) AS \"Data_length\",
pg_indexes_size(c.oid) AS \"Index_length\"" : "") . ",
obj_description(c.oid, 'pg_class') AS \"Comment\",
" . (min_version(12) ? "''" : "CASE WHEN relhasoids THEN 'oid' ELSE '' END") . " AS \"Oid\",
reltuples as \"Rows\",
inhparent AS inherited,
reltuples AS \"Rows\",
" . (min_version(10) ? "relispartition::int AS partition," : "") . "
current_schema() AS nspname
FROM pg_class
LEFT JOIN pg_inherits ON inhrelid = oid
FROM pg_class c
WHERE relkind IN ('r', 'm', 'v', 'f', 'p')
AND relnamespace = " . driver()->nsOid . "
" . ($name != "" ? "AND relname = " . q($name) : "ORDER BY relname")) as $row //! Auto_increment
@@ -530,7 +529,7 @@ ORDER BY a.attnum") as $row
$table_oid = driver()->tableOid($table);
$columns = get_key_vals("SELECT attnum, attname FROM pg_attribute WHERE attrelid = $table_oid AND attnum > 0", $connection2);
foreach (
get_rows("SELECT relname, indisunique::int, indisprimary::int, indkey, indoption, (indpred IS NOT NULL)::int as indispartial, pg_am.amname as algorithm, pg_get_expr(pg_index.indpred, pg_index.indrelid, true) AS partial
get_rows("SELECT relname, indisunique::int, indisprimary::int, indkey, indoption, amname, pg_get_expr(indpred, indrelid, true) AS partial, pg_get_expr(indexprs, indrelid) AS indexpr
FROM pg_index
JOIN pg_class ON indexrelid = oid
JOIN pg_am ON pg_am.oid = pg_class.relam
@@ -538,18 +537,17 @@ WHERE indrelid = $table_oid
ORDER BY indisprimary DESC, indisunique DESC", $connection2) as $row
) {
$relname = $row["relname"];
$return[$relname]["type"] = ($row["indispartial"] ? "INDEX" : ($row["indisprimary"] ? "PRIMARY" : ($row["indisunique"] ? "UNIQUE" : "INDEX")));
$return[$relname]["type"] = ($row["partial"] ? "INDEX" : ($row["indisprimary"] ? "PRIMARY" : ($row["indisunique"] ? "UNIQUE" : "INDEX")));
$return[$relname]["columns"] = array();
$return[$relname]["descs"] = array();
$return[$relname]["algorithm"] = $row["algorithm"];
$return[$relname]["algorithm"] = $row["amname"];
$return[$relname]["partial"] = $row["partial"];
if ($row["indkey"]) {
foreach (explode(" ", $row["indkey"]) as $indkey) {
$return[$relname]["columns"][] = $columns[$indkey];
}
foreach (explode(" ", $row["indoption"]) as $indoption) {
$return[$relname]["descs"][] = (intval($indoption) & 1 ? '1' : null); // 1 - INDOPTION_DESC
}
$indexpr = preg_split('~(?<=\)), (?=\()~', $row["indexpr"]); //! '), (' used in expression
foreach (explode(" ", $row["indkey"]) as $indkey) {
$return[$relname]["columns"][] = ($indkey ? $columns[$indkey] : array_shift($indexpr));
}
foreach (explode(" ", $row["indoption"]) as $indoption) {
$return[$relname]["descs"][] = (intval($indoption) & 1 ? '1' : null); // 1 - INDOPTION_DESC
}
$return[$relname]["lengths"] = array();
}

View File

@@ -85,6 +85,10 @@ class Adminer {
return 2;
}
/** Called after connecting and selecting a database */
function afterConnect(): void {
}
/** Headers to send before HTML output */
function headers(): void {
}
@@ -197,10 +201,10 @@ class Adminer {
$is_view = false;
if (support("table")) {
$is_view = is_view($tableStatus);
if ($is_view) {
$links["view"] = lang('Alter view');
} else {
if (!$is_view) {
$links["create"] = lang('Alter table');
} elseif (support("view")) {
$links["view"] = lang('Alter view');
}
}
if ($set !== null) {
@@ -750,7 +754,7 @@ class Adminer {
} elseif (preg_match('~^([+-]|\|\|)$~', $function)) {
$return = idf_escape($name) . " $function $return";
} elseif (preg_match('~^[+-] interval$~', $function)) {
$return = idf_escape($name) . " $function " . (preg_match("~^(\\d+|'[0-9.: -]') [A-Z_]+\$~i", $value) ? $value : $return);
$return = idf_escape($name) . " $function " . (preg_match("~^(\\d+|'[0-9.: -]') [A-Z_]+\$~i", $value) && JUSH != "pgsql" ? $value : $return);
} elseif (preg_match('~^(addtime|subtime|concat)$~', $function)) {
$return = "$function(" . idf_escape($name) . ", $return)";
} elseif (preg_match('~^(md5|sha1|password|encrypt)$~', $function)) {
@@ -962,6 +966,12 @@ class Adminer {
echo (support("scheme") ? "<a href='" . h(ME) . "scheme='>" . ($_GET["ns"] != "" ? lang('Alter schema') : lang('Create schema')) . "</a>\n" : "");
echo ($_GET["ns"] !== "" ? '<a href="' . h(ME) . 'schema=">' . lang('Database schema') . "</a>\n" : "");
echo (support("privileges") ? "<a href='" . h(ME) . "privileges='>" . lang('Privileges') . "</a>\n" : "");
if ($_GET["ns"] !== "") {
echo (support("routine") ? "<a href='#routines'>" . lang('Routines') . "</a>\n" : "");
echo (support("sequence") ? "<a href='#sequences'>" . lang('Sequences') . "</a>\n" : "");
echo (support("type") ? "<a href='#user-types'>" . lang('User types') . "</a>\n" : "");
echo (support("event") ? "<a href='#events'>" . lang('Events') . "</a>\n" : "");
}
return true;
}
@@ -984,7 +994,7 @@ class Adminer {
if ($password !== null) {
$dbs = $_SESSION["db"][$vendor][$server][$username];
foreach (($dbs ? array_keys($dbs) : array("")) as $db) {
$output .= "<li><a href='" . h(auth_url($vendor, $server, $username, $db)) . "'>($name) " . h($username . ($server != "" ? "@" . adminer()->serverName($server) : "") . ($db != "" ? " - $db" : "")) . "</a>\n";
$output .= "<li><a href='" . h(auth_url($vendor, $server, $username, $db)) . "'>($name) " . h("$username@" . ($server != "" ? adminer()->serverName($server) : "") . ($db != "" ? " - $db" : "")) . "</a>\n";
}
}
}
@@ -1042,7 +1052,15 @@ class Adminer {
foreach ($tables as $table => $type) {
$links[] = preg_quote($table, '/');
}
echo "var jushLinks = { " . JUSH . ": [ '" . js_escape(ME) . (support("table") ? "table=" : "select=") . "\$&', /\\b(" . implode("|", $links) . ")\\b/g ] };\n";
echo "var jushLinks = { " . JUSH . ":";
json_row(js_escape(ME) . (support("table") ? "table" : "select") . '=$&', '/\b(' . implode('|', $links) . ')\b/g', false);
if (support('routine')) {
foreach (routines() as $row) {
json_row(js_escape(ME) . 'function=' . urlencode($row["SPECIFIC_NAME"]) . '&name=$&', '/\b' . preg_quote($row["ROUTINE_NAME"], '/') . '(?=["`]?\()/g', false);
}
}
json_row('');
echo "};\n";
foreach (array("bac", "bra", "sqlite_quo", "mssql_bra") as $val) {
echo "jushLinks.$val = jushLinks." . JUSH . ";\n";
}
@@ -1100,7 +1118,7 @@ class Adminer {
foreach ($tables as $table => $status) {
$table = "$table"; // do not highlight "0" as active everywhere
$name = adminer()->tableName($status);
if ($name != "" && !$status["inherited"]) {
if ($name != "" && !$status["partition"]) {
echo '<li><a href="' . h(ME) . 'select=' . urlencode($table) . '"'
. bold($_GET["select"] == $table || $_GET["edit"] == $table, "select")
. " title='" . lang('Select data') . "'>" . lang('select') . "</a> "
@@ -1115,4 +1133,19 @@ class Adminer {
}
echo "</ul>\n";
}
/** Get process list
* @return list<string[]> [$row]
*/
function processList(): array {
return process_list();
}
/** Kill a process
* @param numeric-string $id
* @return Result|bool
*/
function killProcess(string $id) {
return kill_process($id);
}
}

View File

@@ -92,7 +92,7 @@ include "../adminer/drivers/mysql.inc.php"; // must be included as last driver
define('Adminer\JUSH', Driver::$jush);
define('Adminer\SERVER', $_GET[DRIVER]); // read from pgsql=localhost, '' means default server, null means no server
define('Adminer\DB', $_GET["db"]); // for the sake of speed and size
define('Adminer\DB', "$_GET[db]"); // for the sake of speed and size
define(
'Adminer\ME',
preg_replace('~\?.*~', '', relative_uri()) . '?'
@@ -108,3 +108,5 @@ include "../adminer/include/xxtea.inc.php";
include "../adminer/include/auth.inc.php";
include "./include/editing.inc.php";
include "./include/connect.inc.php";
adminer()->afterConnect();

View File

@@ -189,11 +189,6 @@ abstract class SqlDriver {
return $idf;
}
/** Convert operator so it can be used in search */
function convertOperator(string $operator): string {
return $operator;
}
/** Convert value returned by database to actual value
* @param Field $field
*/
@@ -258,8 +253,7 @@ abstract class SqlDriver {
return !is_view($table_status);
}
/**
* Return list of supported index algorithms, first one is default
/** Return list of supported index algorithms, first one is default
* @param TableStatus $tableStatus
* @return list<string>
*/

View File

@@ -150,13 +150,13 @@ function select_input(string $attrs, array $options, ?string $value = "", string
* @param string $key or "" to close the object
* @param string|int $val
*/
function json_row(string $key, $val = null): void {
function json_row(string $key, $val = null, bool $escape = true): void {
static $first = true;
if ($first) {
echo "{";
}
if ($key != "") {
echo ($first ? "" : ",") . "\n\t\"" . addcslashes($key, "\r\n\t\"\\/") . '": ' . ($val !== null ? '"' . addcslashes($val, "\r\n\"\\/") . '"' : 'null');
echo ($first ? "" : ",") . "\n\t\"" . addcslashes($key, "\r\n\t\"\\/") . '": ' . ($val !== null ? ($escape ? '"' . addcslashes($val, "\r\n\"\\/") . '"' : $val) : 'null');
$first = false;
} else {
echo "\n}\n";

View File

@@ -264,9 +264,10 @@ function where(array $where, array $fields = array()): string {
$field_type = $field["type"];
$return[] = $column
. (JUSH == "sql" && $field_type == "json" ? " = CAST(" . q($val) . " AS JSON)"
: (JUSH == "pgsql" && preg_match('~^json~', $field_type) ? "::jsonb = " . q($val) . "::jsonb"
: (JUSH == "sql" && is_numeric($val) && preg_match('~\.~', $val) ? " LIKE " . q($val) // LIKE because of floats but slow with ints
: (JUSH == "mssql" && strpos($field_type, "datetime") === false ? " LIKE " . q(preg_replace('~[_%[]~', '[\0]', $val)) // LIKE because of text but it does not work with datetime
: " = " . unconvert_field($field, q($val)))))
: " = " . unconvert_field($field, q($val))))))
; //! enum and set
if (JUSH == "sql" && preg_match('~char|text~', $field_type) && preg_match("~[^ -@]~", $val)) { // not just [a-z] to catch non-ASCII characters
$return[] = "$column = " . q($val) . " COLLATE " . charset(connection()) . "_bin";
@@ -291,7 +292,7 @@ function where_check(string $val, array $fields = array()): string {
* @param int $i condition order
* @param string $column column identifier
*/
function where_link(int $i, string $column, string $value, string $operator = "="): string {
function where_link(int $i, string $column, ?string $value, string $operator = "="): string {
return "&where%5B$i%5D%5Bcol%5D=" . urlencode($column) . "&where%5B$i%5D%5Bop%5D=" . urlencode(($value !== null ? $operator : "IS NULL")) . "&where%5B$i%5D%5Bval%5D=" . urlencode($value);
}
@@ -337,11 +338,11 @@ function get_settings(string $cookie): array {
}
/** Get setting stored in a cookie
* @param mixed $default
* @return mixed
*/
function get_setting(string $key, string $cookie = "adminer_settings") {
$settings = get_settings($cookie);
return $settings[$key];
function get_setting(string $key, string $cookie = "adminer_settings", $default = null) {
return idx(get_settings($cookie), $key, $default);
}
/** Store settings to a cookie
@@ -685,7 +686,7 @@ function file_open_lock(string $filename) {
if (!$fp) {
return;
}
chmod($filename, 0660);
@chmod($filename, 0660); // @ - may not be permitted
if (!flock($fp, LOCK_EX)) {
fclose($fp);
return;
@@ -808,7 +809,7 @@ function is_url(?string $string): bool {
* @param Field $field
*/
function is_shortable(array $field): bool {
return preg_match('~char|text|json|lob|geometry|point|linestring|polygon|string|bytea~', $field["type"]);
return preg_match('~char|text|json|lob|geometry|point|linestring|polygon|string|bytea|hstore~', $field["type"]);
}
/** Get query to compute number of found rows

View File

@@ -67,7 +67,7 @@ class Plugins {
function __call(string $name, array $params) {
$args = array();
foreach ($params as $key => $val) {
// some plugins accept params by reference - we don't need to propage it outside, just to the other plugins
// some plugins accept params by reference - we don't need to propagate it outside, just to the other plugins
$args[] = &$params[$key];
}
$return = null;

View File

@@ -1,4 +1,4 @@
<?php
namespace Adminer;
const VERSION = "5.3.0";
const VERSION = "5.3.1-dev";

View File

@@ -12,6 +12,7 @@ if (preg_match('~MyISAM|M?aria' . (min_version(5.7, '10.2.2') ? '|InnoDB' : '')
$index_types[] = "SPATIAL";
}
$indexes = indexes($TABLE);
$fields = fields($TABLE);
$primary = array();
if (JUSH == "mongo") { // doesn't support primary key
$primary = $indexes["_id_"];
@@ -38,7 +39,7 @@ if ($_POST && !$error && !$_POST["add"] && !$_POST["drop_col"]) {
if ($column != "") {
$length = idx($index["lengths"], $key);
$desc = idx($index["descs"], $key);
$set[] = idf_escape($column) . ($length ? "(" . (+$length) . ")" : "") . ($desc ? " DESC" : "");
$set[] = ($fields[$column] ? idf_escape($column) : $column) . ($length ? "(" . (+$length) . ")" : "") . ($desc ? " DESC" : "");
$columns[] = $column;
$lengths[] = ($length ?: null);
$descs[] = $desc;
@@ -81,7 +82,7 @@ if ($_POST && !$error && !$_POST["add"] && !$_POST["drop_col"]) {
page_header(lang('Indexes'), $error, array("table" => $TABLE), h($TABLE));
$fields = array_keys(fields($TABLE));
$fields_keys = array_keys($fields);
if ($_POST["add"]) {
foreach ($row["indexes"] as $key => $index) {
if ($index["columns"][count($index["columns"])] != "") {
@@ -138,7 +139,7 @@ if (support("partial_indexes")) {
if ($primary) {
echo "<tr><td>PRIMARY<td>";
foreach ($primary["columns"] as $key => $column) {
echo select_input(" disabled", $fields, $column);
echo select_input(" disabled", $fields_keys, $column);
echo "<label><input disabled type='checkbox'>" . lang('descending') . "</label> ";
}
echo "<td><td>\n";
@@ -158,7 +159,7 @@ foreach ($row["indexes"] as $index) {
foreach ($index["columns"] as $key => $column) {
echo "<span>" . select_input(
" name='indexes[$j][columns][$i]' title='" . lang('Column') . "'",
($fields ? array_combine($fields, $fields) : $fields),
($fields && ($column == "" || $fields[$column]) ? array_combine($fields_keys, $fields_keys) : array()),
$column,
"partial(" . ($i == count($index["columns"]) ? "indexesAddColumn" : "indexesChangeColumn") . ", '" . js_escape(JUSH == "sql" ? "" : $_GET["indexes"] . "_") . "')"
);

View File

@@ -27,7 +27,7 @@ Lang::$translations = array(
'Connecting to privileged ports is not allowed.' => 'Připojování k privilegovaným portům není povoleno.',
'Disable %s or enable %s or %s extensions.' => 'Zakažte %s nebo povolte rozšíření %s nebo %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.',
'Session expired, please login again.' => 'Session vypršela, přihlaste se prosím znovu.',
'The action will be performed after successful login with the same credentials.' => 'Akce bude provedena po úspěšném přihlášení se stejnými přihlašovacími údaji.',
'%s version: %s through PHP extension %s' => 'Verze %s: %s přes PHP rozšíření %s',
'Refresh' => 'Obnovit',
@@ -76,6 +76,7 @@ Lang::$translations = array(
'Webserver file %s' => 'Soubor %s na webovém serveru',
'Run file' => 'Spustit soubor',
'File does not exist.' => 'Soubor neexistuje.',
'Increase %s.' => 'Zvyšte %s.',
'File uploads are disabled.' => 'Nahrávání souborů není povoleno.',
'Unable to upload a file.' => 'Nepodařilo se nahrát soubor.',
'Maximum allowed file size is %sB.' => 'Maximální povolená velikost souboru je %sB.',
@@ -195,6 +196,7 @@ Lang::$translations = array(
'Partition name' => 'Název oddílu',
'Values' => 'Hodnoty',
'Inherits from' => 'Zděděná z',
'Inherited by' => 'Zděděné',
'View' => 'Pohled',
'Materialized view' => 'Materializovaný pohled',

View File

@@ -5,8 +5,8 @@ Lang::$translations = array(
'Login' => 'ログイン',
'Logout successful.' => 'ログアウトしました。',
'Invalid credentials.' => '不正なログインです。',
'Server' => 'サーバ',
'Username' => 'ユーザ名',
'Server' => 'サーバ',
'Username' => 'ユーザ名',
'Password' => 'パスワード',
'Loaded plugins' => '読込済プラグイン',
'Thanks for using Adminer, consider <a href="https://www.adminer.org/en/donation/">donating</a>.' => 'Adminerのご利用ありがとうございました。(寄付は<a href="https://www.adminer.org/en/donation/">こちら</a>)',
@@ -20,14 +20,14 @@ Lang::$translations = array(
'Select database' => 'データベースを選択してください',
'Invalid database.' => '不正なデータベースです。',
'Table has been dropped.' => 'テーブルを削除しました。',
'Table has been altered.' => 'テーブルを変更しました。',
'Table has been altered.' => 'テーブルの設定を変更しました。',
'Table has been created.' => 'テーブルを作成しました。',
'Alter table' => 'テーブルを変更',
'Alter table' => 'テーブルの設定を変更',
'Create table' => 'テーブルを作成',
'Table name' => 'テーブル名',
'engine' => 'エンジン',
'collation' => '照合順序',
'Column name' => '名',
'collation' => 'コレーション',
'Column name' => 'カラム名',
'Type' => '型',
'Length' => '長さ',
'Auto Increment' => '連番',
@@ -38,16 +38,16 @@ Lang::$translations = array(
'Database has been dropped.' => 'データベースを削除しました。',
'Database has been created.' => 'データベースを作成しました。',
'Database has been renamed.' => 'データベースの名前を変えました。',
'Database has been altered.' => 'データベースを変更しました。',
'Alter database' => 'データベースを変更',
'Database has been altered.' => 'データベースの設定を変更しました。',
'Alter database' => 'データベースの設定を変更',
'Create database' => 'データベースを作成',
'SQL command' => 'SQLコマンド',
'Logout' => 'ログアウト',
'Use' => '使用',
'No tables.' => 'テーブルがありません。',
'select' => '選択',
'Item has been deleted.' => '項目を削除しました。',
'Item has been updated.' => '項目を更新しました。',
'Item has been deleted.' => 'レコードを削除しました。',
'Item has been updated.' => 'レコードを更新しました。',
'Edit' => '編集',
'Insert' => '挿入',
'Save and insert next' => '保存/追加',
@@ -56,18 +56,18 @@ Lang::$translations = array(
'Database' => 'データベース',
'DB' => 'DB',
'Routines' => 'ルーチン',
'Indexes have been altered.' => '索引を変更しました。',
'Indexes' => '索引',
'Alter indexes' => '索引の変更',
'Indexes have been altered.' => 'インデックスを変更しました。',
'Indexes' => 'インデックス',
'Alter indexes' => 'インデックスを変更',
'Add next' => '追加',
'Language' => '言語',
'Select' => '選択',
'New item' => '項目の作成',
'New item' => '新規レコードを挿入',
'Search' => '検索',
'Sort' => 'ソート',
'descending' => '降順',
'Limit' => '制約',
'Limit rows' => '行数の制約',
'Limit rows' => '表示行数を制限',
'No rows.' => '行がありません。',
'Action' => '動作',
'edit' => '編集',
@@ -82,17 +82,17 @@ Lang::$translations = array(
'Foreign keys' => '外部キー',
'Triggers' => 'トリガー',
'View' => 'ビュー',
'Materialized view' => 'マテビュー',
'Full table scan' => 'テーブル全スキャン',
'Materialized view' => 'マテリアライズドビュー',
'Full table scan' => 'テーブル全スキャン',
'Unable to select the table' => 'テーブルを選択できません',
'Invalid CSRF token. Send the form again.' => '不正なCSRFトークン再送信してください。',
'If you did not send this request from Adminer then close this page.' => 'Adminerからのリクエストを送信しない場合はこのページを閉じてください。',
'Invalid CSRF token. Send the form again.' => '不正なCSRFトークンです。フォームを再送信してください。',
'If you did not send this request from Adminer then close this page.' => 'Adminerからのリクエストない場合はこのページを閉じてください。',
'Comment' => 'コメント',
'Default values' => '定値',
'Default values' => '定値',
'%d byte(s)' => '%d バイト',
'No commands to execute.' => '実行するコマンドがありません。',
'Unable to upload a file.' => 'ファイルをアップロードできません。',
'File upload' => 'ファイルをアップロード',
'File upload' => 'アップロード',
'File uploads are disabled.' => 'ファイルのアップロードが無効です。',
'Routine has been called, %d row(s) affected.' => 'ルーチンを呼びました。%d 行を変更しました。',
'Call' => '呼出し',
@@ -103,7 +103,7 @@ Lang::$translations = array(
'Session support must be enabled.' => 'セッションを有効にしてください。',
'Session expired, please login again.' => 'セッションの期限切れ。ログインし直してください。',
'The action will be performed after successful login with the same credentials.' => '同じアカウントで正しくログインすると作業を実行します。',
'Text length' => '文字列の長さ',
'Text length' => '文字数を丸める',
'Foreign key has been dropped.' => '外部キーを削除しました。',
'Foreign key has been altered.' => '外部キーを変更しました。',
'Foreign key has been created.' => '外部キーを作成しました。',
@@ -112,12 +112,12 @@ Lang::$translations = array(
'Change' => '変更',
'Source' => 'ソース',
'Target' => 'ターゲット',
'Add column' => 'を追加',
'Add column' => 'カラムを追加',
'Alter' => '変更',
'Add foreign key' => '外部キーを追加',
'ON DELETE' => 'ON DELETE',
'ON UPDATE' => 'ON UPDATE',
'Index Type' => '索引の型',
'Index Type' => 'インデックスの型',
'length' => '長さ',
'View has been dropped.' => 'ビューを削除しました。',
'View has been altered.' => 'ビューを変更しました。',
@@ -126,25 +126,25 @@ Lang::$translations = array(
'Create view' => 'ビューを作成',
'Name' => '名称',
'Process list' => 'プロセス一覧',
'%d process(es) have been killed.' => '%d プロセスを強制終了しました。',
'Kill' => '強制終了',
'Parameter name' => '参数名',
'Database schema' => '構造',
'Create procedure' => 'プロシージャ作成',
'Create function' => '関数作成',
'Routine has been dropped.' => 'ルーチンを作成しました。',
'%d process(es) have been killed.' => '%d プロセスを終了しました。',
'Kill' => 'プロセスを終了',
'Parameter name' => 'パラメータ名',
'Database schema' => 'スキーマ',
'Create procedure' => 'プロシージャ作成',
'Create function' => '関数作成',
'Routine has been dropped.' => 'ルーチンを削除しました。',
'Routine has been altered.' => 'ルーチンを変更しました。',
'Routine has been created.' => 'ルーチンを作成しました。',
'Alter function' => '関数変更',
'Alter procedure' => 'プロシージャ変更',
'Alter function' => '関数変更',
'Alter procedure' => 'プロシージャ変更',
'Return type' => '戻り値の型',
'Add trigger' => 'トリガー追加',
'Add trigger' => 'トリガー追加',
'Trigger has been dropped.' => 'トリガーを削除しました。',
'Trigger has been altered.' => 'トリガーを変更しました。',
'Trigger has been created.' => 'トリガーを追加しました。',
'Alter trigger' => 'トリガー変更',
'Create trigger' => 'トリガー作成',
'Time' => '時間',
'Alter trigger' => 'トリガー変更',
'Create trigger' => 'トリガー作成',
'Time' => 'タイミング',
'Event' => 'イベント',
'%s version: %s through PHP extension %s' => '%sバージョン%s、 PHP拡張機能 %s',
'%d / ' => '%d / ',
@@ -157,11 +157,11 @@ Lang::$translations = array(
'User has been altered.' => 'ユーザを変更しました。',
'User has been created.' => 'ユーザを作成しました。',
'Hashed' => 'Hashed',
'Column' => '',
'Columns' => '',
'Column' => 'カラム',
'Columns' => 'カラム',
'Routine' => 'ルーチン',
'Grant' => '権限付与',
'Revoke' => '権限の取消し',
'Grant' => '権限付与',
'Revoke' => '権限を取り消す',
'Logged as: %s' => 'ログ:%s',
'Too big POST data. Reduce the data or increase the %s configuration directive.' => 'POSTデータが大きすぎます。データサイズを小さくするか %s 設定を大きくしてください。',
'You can upload a big SQL file via FTP and import it from server.' => '大きなSQLファイルは、FTP経由でアップロードしてサーバからインポートしてください。',
@@ -172,16 +172,16 @@ Lang::$translations = array(
'Tables' => 'テーブル',
'Data' => 'データ',
'Output' => '出力',
'open' => '開く',
'open' => 'ブラウザに表示',
'save' => '保存',
'Format' => '形式',
'Functions' => '関数',
'Aggregation' => '集',
'Aggregation' => '集約関数',
'Event has been dropped.' => 'イベントを削除しました。',
'Event has been altered.' => 'イベントを変更しました。',
'Event has been created.' => 'イベントを作成しました。',
'Alter event' => '変更',
'Create event' => '作成',
'Alter event' => 'イベントを変更',
'Create event' => 'イベントを作成',
'Start' => '開始',
'End' => '終了',
'Every' => '毎回',
@@ -194,24 +194,24 @@ Lang::$translations = array(
'Tables have been moved.' => 'テーブルを移動しました。',
'Tables and views' => 'テーブルとビュー',
'Engine' => 'エンジン',
'Collation' => '照合順序',
'Collation' => 'コレーション',
'Data Length' => 'データ長',
'Index Length' => '索引長',
'Index Length' => 'インデックス長',
'Data Free' => '空き',
'Rows' => '行数',
',' => ',',
'0123456789' => '0123456789',
'Analyze' => '分析',
'Optimize' => '最適化',
'Vacuum' => '不要領域回収',
'Check' => 'チェック',
'Vacuum' => '不要領域回収(Vacuum)',
'Check' => '検査',
'Repair' => '修復',
'Truncate' => '空にする',
'Move to other database' => 'のデータベースへ移動',
'Move to other database' => 'のデータベースへ移動',
'Move' => '移動',
'Save and continue edit' => '保存して継続',
'original' => '元',
'%d item(s) have been affected.' => '%d を更新しました。',
'%d item(s) have been affected.' => '%d レコードを更新しました。',
'Whole result' => '全結果',
'Tables have been dropped.' => 'テーブルを削除しました。',
'Tables have been optimized.' => 'テーブルを最適化しました。',
@@ -223,7 +223,7 @@ Lang::$translations = array(
'Values' => '値',
'%d row(s) have been imported.' => '%d 行をインポートしました。',
'File must be in UTF-8 encoding.' => 'ファイルをUTF-8で保存してください。',
'Show structure' => '構造',
'Show structure' => 'スキーマ',
'anywhere' => '任意',
'Import' => 'インポート',
'Stop on error' => 'エラーの場合は停止',
@@ -233,9 +233,9 @@ Lang::$translations = array(
'[yyyy]-mm-dd' => '[yyyy]/mm/dd',
'History' => '履歴',
'Variables' => '変数',
'Source and target columns must have the same data type, there must be an index on the target columns and referenced data must exist.' => 'ソースとターゲットのは同じデータ型でなければなりません。ターゲット列に索引があり、データが存在しなければなりません。',
'Source and target columns must have the same data type, there must be an index on the target columns and referenced data must exist.' => 'ソースとターゲットのカラムは同じデータ型でなければなりません。ターゲットカラムにインデックスがあり、データが存在しなければなりません。',
'Relations' => '関係',
'Run file' => 'ファイルを実行',
'Run file' => '実行',
'Clear' => '消去',
'Maximum allowed file size is %sB.' => '最大ファイルサイズは %sB です。',
'Numbers' => '数字',
@@ -244,7 +244,7 @@ Lang::$translations = array(
'Binary' => 'バイナリ',
'Lists' => 'リスト',
'Editor' => 'エディタ',
'Webserver file %s' => 'Webサーバファイル %s',
'Webserver file %s' => 'ファイル %s',
'File does not exist.' => 'ファイルは存在しません。',
'%d in total' => '合計 %d',
'Permanent login' => '永続的にログイン',
@@ -268,15 +268,15 @@ Lang::$translations = array(
'Type has been dropped.' => 'ユーザー定義型を削除しました。',
'Type has been created.' => 'ユーザー定義型を追加しました。',
'Ctrl+click on a value to modify it.' => 'Ctrl+クリックで値を修正します。',
'Use edit link to modify this value.' => 'この値を修正するリンクを編集します。',
'Use edit link to modify this value.' => 'この値を修正するにはリンクを使用してください。',
'last' => '最終',
'From server' => 'サーバーから実行',
'From server' => 'サーバー上のファイル',
'System' => 'データベース種類',
'empty' => '空',
'Network' => 'ネットワーク型',
'Geometry' => 'ジオメトリ型',
'File exists.' => 'ファイルが既に存在します。',
'Item%s has been inserted.' => '%s項目を挿入しました。',
'Item%s has been inserted.' => '%sレコードを挿入しました。',
'now' => '現在の日時',
'%d query(s) executed OK.' => '%d クエリーを実行しました。',
'Show only errors' => 'エラーのみ表示',
@@ -287,29 +287,30 @@ Lang::$translations = array(
'Tables have been copied.' => 'テーブルをコピーしました。',
'Copy' => 'コピー',
'overwrite' => '上書き',
'Permanent link' => 'パーマネントリンク',
'Edit all' => 'すべて編集',
'Permanent link' => '固定リンク',
'Edit all' => '一括編集',
'Selected' => '選択中',
'Modify' => '編集',
'Load more data' => 'さらにデータを表示',
'Compute' => '再計算',
'Saving' => '保存しています...',
'Checks' => 'CHECK制約',
'Create check' => 'CHECK制約を追加',
'Alter check' => 'CHECK制約を編集',
'Check has been created.' => 'CHECK制約を追加しました。',
'Check has been altered.' => 'CHECK制約を編集しました。',
'Check has been dropped.' => 'CHECK制約を削除しました。',
'screenshot' => 'スクリーンショット',
'Algorithm' => 'アルゴリズム',
'Condition' => '条件',
'Inherits from' => '継承元',
'HH:MM:SS' => '時:分:秒',
'Selected' => '選択済',
'Modify' => '修正',
'Load more data' => '続きを読み込み',
'Loading' => '読み込み中',
'Size' => 'サイズ',
'Compute' => '算出',
'Saving' => '保存中',
'yes' => 'はい',
'no' => 'いいえ',
'Default value' => '既定値',
// Table check constraints
'Checks' => 'チェック',
'Create check' => 'チェックを作成',
'Alter check' => 'チェックを変更',
'Check has been created.' => 'チェックを作成しました。',
'Check has been altered.' => 'チェックを変更しました。',
'Check has been dropped.' => 'チェックを削除しました。',
'screenshot' => 'スクリーンショット',
);
// run `php ../../lang.php ja` to update this file

View File

@@ -76,6 +76,7 @@ Lang::$translations = array(
'Webserver file %s' => 'Plik %s na serwerze',
'Run file' => 'Uruchom z pliku',
'File does not exist.' => 'Plik nie istnieje.',
'Increase %s.' => 'Zwiększ %s.',
'File uploads are disabled.' => 'Wgrywanie plików jest wyłączone.',
'Unable to upload a file.' => 'Wgranie pliku było niemożliwe.',
'Maximum allowed file size is %sB.' => 'Maksymalna wielkość pliku to %sB.',
@@ -196,6 +197,7 @@ Lang::$translations = array(
'Partition name' => 'Nazwa partycji',
'Values' => 'Wartości',
'Inherits from' => 'Dziedziczy po',
'Inherited by' => 'Odziedziczone przez',
'View' => 'Perspektywa',
'Materialized view' => 'Zmaterializowana perspektywa',

View File

@@ -77,6 +77,7 @@ Lang::$translations = array(
'Webserver file %s' => 'Xx %s',
'Run file' => 'Xx',
'File does not exist.' => 'Xx.',
'Increase %s.' => 'Xx %s.',
'File uploads are disabled.' => 'Xx.',
'Unable to upload a file.' => 'Xx.',
'Maximum allowed file size is %sB.' => 'Xx %sB.',
@@ -197,6 +198,7 @@ Lang::$translations = array(
'Partition name' => 'Xx',
'Values' => 'Xx',
'Inherits from' => 'Xx',
'Inherited by' => 'Xx',
'View' => 'Xx',
'Materialized view' => 'Xx',

View File

@@ -62,7 +62,7 @@ if (isset($_GET["function"])) {
</table>
<?php echo script("editFields();"); ?>
</div>
<p><?php textarea("definition", $row["definition"]); ?>
<p><?php textarea("definition", $row["definition"], 20); ?>
<p>
<input type="submit" value="<?php echo lang('Save'); ?>">
<?php if ($PROCEDURE != "") { ?>

View File

@@ -5,7 +5,7 @@ if (support("kill")) {
if ($_POST && !$error) {
$killed = 0;
foreach ((array) $_POST["kill"] as $val) {
if (kill_process($val)) {
if (adminer()->killProcess($val)) {
$killed++;
}
}
@@ -23,7 +23,7 @@ page_header(lang('Process list'), $error);
echo script("mixin(qsl('table'), {onclick: tableClick, ondblclick: partialArg(tableClick, true)});");
// HTML valid because there is always at least one process
$i = -1;
foreach (process_list() as $i => $row) {
foreach (adminer()->processList() as $i => $row) {
if (!$i) {
echo "<thead><tr lang='en'>" . (support("kill") ? "<th>" : "");
foreach ($row as $key => $val) {

View File

@@ -7,7 +7,7 @@ if ($_GET["script"] == "db") {
$sums = array("Data_length" => 0, "Index_length" => 0, "Data_free" => 0);
foreach (table_status() as $name => $table_status) {
json_row("Comment-$name", h($table_status["Comment"]));
if (!is_view($table_status)) {
if (!is_view($table_status) || preg_match('~materialized~i', $table_status["Engine"])) {
foreach (array("Engine", "Collation") as $key) {
json_row("$key-$name", h($table_status[$key]));
}

View File

@@ -354,7 +354,7 @@ if (!$columns && support("table")) {
echo "<th id='th[" . h(bracket_escape($key)) . "]'>" . script("mixin(qsl('th'), {onmouseover: partial(columnMouse), onmouseout: partial(columnMouse, ' hidden')});", "");
$fun = apply_sql_function($val["fun"], $name); //! columns looking like functions
$sortable = isset($field["privileges"]["order"]) || $fun;
echo ($sortable ? "<a href='" . h($href . ($order[0] == $column || $order[0] == $key || (!$order && $is_group && $group[0] == $column) ? $desc : '')) . "'>$fun</a>" : $fun); // $order[0] == $key - COUNT(*)
echo ($sortable ? "<a href='" . h($href . ($order[0] == $column || $order[0] == $key ? $desc : '')) . "'>$fun</a>" : $fun); // $order[0] == $key - COUNT(*)
echo "<span class='column hidden'>";
if ($sortable) {
echo "<a href='" . h($href . $desc) . "' title='" . lang('descending') . "' class='text'> ↓</a>";

View File

@@ -4,9 +4,13 @@ namespace Adminer;
if (!$error && $_POST["export"]) {
save_settings(array("output" => $_POST["output"], "format" => $_POST["format"]), "adminer_import");
dump_headers("sql");
adminer()->dumpTable("", "");
adminer()->dumpData("", "table", $_POST["query"]);
adminer()->dumpFooter();
if ($_POST["format"] == "sql") {
echo "$_POST[query]\n";
} else {
adminer()->dumpTable("", "");
adminer()->dumpData("", "table", $_POST["query"]);
adminer()->dumpFooter();
}
exit;
}
@@ -65,11 +69,9 @@ if (!$error && $_POST) {
}
$commands = 0;
$errors = array();
$parse = '[\'"' . (JUSH == "sql" ? '`#' : (JUSH == "sqlite" ? '`[' : (JUSH == "mssql" ? '[' : ''))) . ']|/\*|' . $line_comment . '|$' . (JUSH == "pgsql" ? '|\$[^$]*\$' : '');
$parse = '[\'"' . (JUSH == "sql" ? '`#' : (JUSH == "sqlite" ? '`[' : (JUSH == "mssql" ? '[' : ''))) . ']|/\*|' . $line_comment . '|$' . (JUSH == "pgsql" ? '|\$([a-zA-Z]\w*)?\$' : '');
$total_start = microtime(true);
$adminer_export = get_settings("adminer_import"); // this doesn't offer SQL export so we match the import/export style at select
$dump_format = adminer()->dumpFormat();
unset($dump_format["sql"]);
while ($query != "") {
if (!$offset && preg_match("~^$space*+DELIMITER\\s+(\\S+)~i", $query, $match)) {
@@ -174,7 +176,7 @@ if (!$error && $_POST) {
$id = "export-$commands";
echo ", <a href='#$id'>" . lang('Export') . "</a>" . script("qsl('a').onclick = partial(toggle, '$id');", "") . "<span id='$id' class='hidden'>: "
. html_select("output", adminer()->dumpOutput(), $adminer_export["output"]) . " "
. html_select("format", $dump_format, $adminer_export["format"])
. html_select("format", adminer()->dumpFormat(), $adminer_export["format"])
. input_hidden("query", $q)
. "<input type='submit' name='export' value='" . lang('Export') . "'>" . input_token() . "</span>\n"
. "</form>\n"
@@ -250,8 +252,11 @@ if (!isset($_GET["import"])) {
} else {
echo "<fieldset><legend>" . lang('File upload') . "</legend><div>";
$gz = (extension_loaded("zlib") ? "[.gz]" : "");
$max_file_uploads = "max_file_uploads";
$max_file_uploads_value = ini_get($max_file_uploads);
echo (ini_bool("file_uploads")
? "SQL$gz (&lt; " . ini_get("upload_max_filesize") . "B): <input type='file' name='sql_file[]' multiple>\n$execute" // ignore post_max_size because it is for all form fields together and bytes computing would be necessary
? "SQL$gz (&lt; " . ini_get("upload_max_filesize") . "B): <input type='file' name='sql_file[]' multiple>\n" // ignore post_max_size because it is for all form fields together and bytes computing would be necessary
. script("qsl('input').onchange = partialArg(fileChange, $max_file_uploads_value, '" . lang('Increase %s.', "$max_file_uploads = $max_file_uploads_value") . "')") . $execute
: lang('File uploads are disabled.')
);
echo "</div></fieldset>\n";

View File

@@ -665,6 +665,17 @@ function sqlSubmit(form, root) {
}
}
/** Check if PHP can handle the uploaded files
* @param number
* @param string
* @param Event
*/
function fileChange(event, maxFileUploads, message) {
if (event.target.files.length > maxFileUploads) {
alert(message);
}
}
/** Handle changing trigger time or event

View File

@@ -22,6 +22,10 @@ if ($comment != "") {
echo "<p class='nowrap'>" . lang('Comment') . ": " . h($comment) . "\n";
}
if ($fields) {
adminer()->tableStructurePrint($fields, $table_status);
}
function tables_links($tables) {
echo "<ul>\n";
foreach ($tables as $table) {
@@ -34,8 +38,6 @@ $inherits = driver()->inheritsFrom($TABLE);
if ($inherits) {
echo "<h3>" . lang('Inherits from') . "</h3>\n";
tables_links($inherits);
} elseif ($fields) {
adminer()->tableStructurePrint($fields, $table_status);
}
if (support("indexes") && driver()->supportsIndex($table_status)) {
@@ -110,7 +112,7 @@ if (support(is_view($table_status) ? "view_trigger" : "trigger")) {
$inherited = driver()->inheritedTables($TABLE);
if ($inherited) {
echo "<h3 id='partitions'>" . lang('Partitions') . "</h3>\n";
echo "<h3 id='partitions'>" . lang('Inherited by') . "</h3>\n";
$partition = driver()->partitionsInfo($TABLE);
if ($partition) {
echo "<p><code class='jush-" . JUSH . "'>BY " . h("$partition[partition_by]($partition[partition])") . "</code>\n";

2
designs/win98/README.md Normal file
View File

@@ -0,0 +1,2 @@
## Screenshot
![screenshot](https://www.adminer.org/static/designs/win98/screenshot.png)

298
designs/win98/adminer.css Normal file
View File

@@ -0,0 +1,298 @@
/* inspired by https://github.com/jdan/98.css */
:root {
--text-color: #222222;
--button-face: #dfdfdf;
--window-frame: #0a0a0a;
--dialog-blue-light: #1084d0;
--link-blue: #0000ff;
}
body {
background: teal;
color: var(--text-color);
font-family: Arial, Helvetica, sans-serif;
}
button, input[type=reset], input[type=submit] {
background: silver;
border: none;
border-radius: 0;
box-shadow: inset -1px -1px var(--window-frame), inset 1px 1px white, inset -2px -2px grey, inset 2px 2px var(--button-face);
box-sizing: border-box;
color: transparent;
font-size: 12px;
min-height: 23px;
min-width: 75px;
padding: 0 12px;
text-shadow: 0 0 var(--text-color);
}
button.default, input[type=reset].default, input[type=submit].default {
box-shadow: inset -2px -2px var(--window-frame), inset 1px 1px var(--window-frame), inset 2px 2px white, inset -3px -3px grey, inset 3px 3px var(--button-face);
}
button:not(:disabled):active, input[type=reset]:not(:disabled):active, input[type=submit]:not(:disabled):active {
box-shadow: inset -1px -1px white, inset 1px 1px var(--window-frame), inset -2px -2px var(--button-face), inset 2px 2px grey;
text-shadow: 1px 1px var(--text-color);
}
button.default:not(:disabled):active, input[type=reset].default:not(:disabled):active, input[type=submit].default:not(:disabled):active {
box-shadow: inset 2px 2px var(--window-frame), inset -1px -1px var(--window-frame), inset -2px -2px white, inset 3px 3px grey, inset -3px -3px var(--button-face);
}
button:focus, input[type=reset]:focus, input[type=submit]:focus {
outline: 1px dotted black;
outline-offset: -4px;
}
button::-moz-focus-inner, input[type=reset]::-moz-focus-inner, input[type=submit]::-moz-focus-inner {
border: 0;
}
:disabled, :disabled + label, input[readonly], input[readonly] + label, input[type=submit]:disabled, input[type=reset]:disabled, input[type=submit]:disabled {
color: grey;
}
:disabled + label, button:disabled, input[type=reset]:disabled, input[type=submit]:disabled {
text-shadow: 1px 1px 0 white;
}
#content, #menu, .footer, #breadcrumb {
background: silver;
box-shadow: inset -1px -1px var(--window-frame), inset 1px 1px var(--button-face), inset -2px -2px grey, inset 2px 2px white;
padding: 16px;
}
#content h2 {
align-items: center;
background: linear-gradient(90deg, navy, var(--dialog-blue-light));
color: white;
display: flex;
font-size: 120%;
font-weight: normal;
justify-content: space-between;
letter-spacing: 0;
margin: -13px -13px 16px -13px;
padding: 3px 2px 3px 3px;
}
fieldset {
border-image: url("data:image/svg+xml;charset=utf-8,%3Csvg width='5' height='5' fill='gray' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M0 0h5v5H0V2h2v1h1V2H0' fill='%23fff'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M0 0h4v4H0V1h1v2h2V1H0'/%3E%3C/svg%3E") 2;
margin: 0;
padding: 10px;
padding-block-start: 8px;
}
legend {
background: silver;
}
input[type=email], input[type=number], input[type=password], input[type=search], input[type=tel], input[type=text], input:not([type]) {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
border: none;
border-radius: 0;
}
input[type=email], input[type=number], input[type=password], input[type=search], input[type=tel], input[type=text], select, input:not([type]) {
background-color: white;
box-shadow: inset -1px -1px white, inset 1px 1px grey, inset -2px -2px var(--button-face), inset 2px 2px var(--window-frame);
box-sizing: border-box;
padding: 3px 4px;
}
select, textarea {
border: none;
}
textarea {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
background-color: white;
border-radius: 0;
box-shadow: inset -1px -1px white, inset 1px 1px grey, inset -2px -2px var(--button-face), inset 2px 2px var(--window-frame);
box-sizing: border-box;
padding: 3px 4px;
}
input[type=email], input[type=password], input[type=search], input[type=tel], input[type=text], input[type=number], select {
min-height: 21px;
}
input[type=search]::-ms-clear, input[type=search]::-ms-reveal {
display: none;
height: 0;
width: 0;
}
input[type=search]::-webkit-search-cancel-button, input[type=search]::-webkit-search-decoration, input[type=search]::-webkit-search-results-button, input[type=search]::-webkit-search-results-decoration {
display: none;
}
input[type=email]:disabled, input[type=email]:read-only, input[type=number]:disabled, input[type=number]:read-only, input[type=password]:disabled, input[type=password]:read-only, input[type=search]:disabled, input[type=search]:read-only, input[type=tel]:disabled, input[type=tel]:read-only, input[type=text]:disabled, input[type=text]:read-only, textarea:disabled {
background-color: silver;
}
select {
appearance: none;
-webkit-appearance: none;
-moz-appearance: none;
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg width='16' height='17' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M15 0H0v16h1V1h14V0z' fill='%23DFDFDF'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M2 1H1v14h1V2h12V1H2z' fill='%23fff'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M16 17H0v-1h15V0h1v17z' fill='%23000'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M15 1h-1v14H1v1h14V1z' fill='gray'/%3E%3Cpath fill='silver' d='M2 2h12v13H2z'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M11 6H4v1h1v1h1v1h1v1h1V9h1V8h1V7h1V6z' fill='%23000'/%3E%3C/svg%3E");
background-position: top 2px right 2px;
background-repeat: no-repeat;
border-radius: 0;
padding-right: 32px;
position: relative;
}
input[type=email]:focus, input[type=number]:focus, input[type=password]:focus, input[type=search]:focus, input[type=tel]:focus, input[type=text]:focus, select:focus, textarea:focus {
outline: none;
}
select:focus {
background-color: navy;
color: white;
}
select:focus option {
background-color: white;
color: black;
}
select:active {
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg width='16' height='17' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M0 0h16v17H0V0zm1 16h14V1H1v15z' fill='gray'/%3E%3Cpath fill='silver' d='M1 1h14v15H1z'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M12 7H5v1h1v1h1v1h1v1h1v-1h1V9h1V8h1V7z' fill='%23000'/%3E%3C/svg%3E")
}
a {
color: var(--link-blue);
}
a:focus {
outline: 1px dotted var(--link-blue);
}
pre {
border-image: url("data:image/svg+xml;charset=utf-8,%3Csvg width='5' height='5' fill='gray' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M0 0h5v5H0V2h2v1h1V2H0' fill='%23fff'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M0 0h4v4H0V1h1v2h2V1H0'/%3E%3C/svg%3E") 2;
border-style: solid;
border-width: 1px;
margin: 0;
padding: 12px 8px;
}
table {
background-color: white;
}
table > thead > tr > * {
background: silver;
box-shadow: inset -1px -1px var(--window-frame), inset 1px 1px white, inset -2px -2px grey, inset 2px 2px var(--button-face);
}
table.interactive > tbody > tr {
cursor: pointer;
}
#fieldset-sort > div:not(:last-of-type), #fieldset-search > div:not(:last-of-type), #fieldset-select > div:not(:last-of-type) {
margin-bottom: .5em;
}
#fieldset-partition p {
margin: 0;
}
.icon {
min-height: 0;
min-width: 0;
}
.links {
margin: 1em 0;
}
#logins a, #tables a, #tables span {
background: none;
}
.jush-autocomplete, .jush-autocomplete:active {
background: none;
height: auto;
}
.logout {
box-shadow: none;
margin-top: .8em;
}
#breadcrumb {
height: auto;
margin: 0;
padding: 2px 16px;
top: calc(1.5em - 14px);
}
code {
background: none;
}
#menu {
margin: 0;
top: 3em;
width: 18em;
}
#menu h1 {
background: none;
margin: 0;
padding: 0;
}
#menu h1 a {
color: var(--text-color);
}
#menu .links a {
display: block;
}
#lang {
top: -2.8em;
}
#lang label {
color: white;
}
#content {
margin-top: 3em;
}
.footer {
margin: 1em 0;
}
.footer > div {
background: none;
}
.js .column {
background: silver;
border: none;
border-radius: 0;
box-shadow: inset -1px -1px var(--window-frame), inset 1px 1px var(--button-face), inset -2px -2px grey, inset 2px 2px white;
margin-top: -.4em;
padding: 4px;
}
@media all and (max-width: 800px) {
.js .logout {
box-shadow: none;
top: 0;
}
#lang label {
color: initial;
}
}

View File

@@ -59,6 +59,9 @@ class Adminer {
return 5;
}
function afterConnect() {
}
function headers() {
}

2
externals/jush vendored

View File

@@ -61,7 +61,7 @@ parameters:
max: 80499
typeAliases:
TableStatus: "array{Name:string, Engine?:?string, Comment?:string, Oid?:numeric-string, Rows?:?numeric-string, Collation?:string, Auto_increment?:?numeric-string, Data_length?:numeric-string, Index_length?:numeric-string, Data_free?:numeric-string, Create_options?:string, inherited?:numeric-string, nspname?:string}"
TableStatus: "array{Name:string, Engine?:?string, Comment?:string, Oid?:numeric-string, Rows?:?numeric-string, Collation?:string, Auto_increment?:?numeric-string, Data_length?:numeric-string, Index_length?:numeric-string, Data_free?:numeric-string, Create_options?:string, partition?:numeric-string, nspname?:string}"
Field: "array{field?:string, full_type:string, type:string, length:numeric-string, unsigned:string, default?:string, null:bool, auto_increment:bool, collation:string, privileges:int[], comment:string, primary:bool, generated:string, orig?:string, on_update?:string, on_delete?:string, default_constraint?: string}"
FieldType: "array{type:string, length:numeric-string, unsigned:string, collation:string}" # subset of RoutineField and Field
RoutineField: "array{field:string, type:string, length:numeric-string, unsigned:string, null:bool, full_type:string, collation:string, inout?:string}"

View File

@@ -11,15 +11,26 @@ class AdminerBackwardKeys extends Adminer\Plugin {
function backwardKeys($table, $tableName) {
$return = array();
// we couldn't use the same query in MySQL and PostgreSQL because unique_constraint_name is not table-specific in MySQL and referenced_table_name is not available in PostgreSQL
foreach (
Adminer\get_rows($q = "SELECT TABLE_NAME, CONSTRAINT_NAME, COLUMN_NAME, REFERENCED_COLUMN_NAME
FROM information_schema.KEY_COLUMN_USAGE
WHERE TABLE_SCHEMA = " . Adminer\q(Adminer\DB) . "
AND REFERENCED_TABLE_SCHEMA = " . Adminer\q(Adminer\DB) . "
AND REFERENCED_TABLE_NAME = " . Adminer\q($table) . "
ORDER BY ORDINAL_POSITION", null, "") as $row
Adminer\get_rows("SELECT s.table_name table_name, s.constraint_name constraint_name, s.column_name column_name, " . (Adminer\JUSH == "sql" ? "referenced_column_name" : "t.column_name") . " referenced_column_name
FROM information_schema.key_column_usage s" . (Adminer\JUSH == "sql" ? "
WHERE table_schema = " . Adminer\q(Adminer\DB) . "
AND referenced_table_schema = " . Adminer\q(Adminer\DB) . "
AND referenced_table_name" : "
JOIN information_schema.referential_constraints r USING (constraint_catalog, constraint_schema, constraint_name)
JOIN information_schema.key_column_usage t ON r.unique_constraint_catalog = t.constraint_catalog
AND r.unique_constraint_schema = t.constraint_schema
AND r.unique_constraint_name = t.constraint_name
AND r.constraint_catalog = t.constraint_catalog
AND r.constraint_schema = t.constraint_schema
AND r.unique_constraint_name = t.constraint_name
AND s.position_in_unique_constraint = t.ordinal_position
WHERE t.table_catalog = " . Adminer\q(Adminer\DB) . " AND t.table_schema = " . Adminer\q("$_GET[ns]") . "
AND t.table_name") . " = " . Adminer\q($table) . "
ORDER BY s.ordinal_position", null, "") as $row
) {
$return[$row["TABLE_NAME"]]["keys"][$row["CONSTRAINT_NAME"]][$row["COLUMN_NAME"]] = $row["REFERENCED_COLUMN_NAME"];
$return[$row["table_name"]]["keys"][$row["constraint_name"]][$row["column_name"]] = $row["referenced_column_name"];
}
foreach ($return as $key => $val) {
$name = Adminer\adminer()->tableName(Adminer\table_status1($key, true));
@@ -40,9 +51,12 @@ ORDER BY ORDINAL_POSITION", null, "") as $row
$link = Adminer\ME . 'select=' . urlencode($table);
$i = 0;
foreach ($cols as $column => $val) {
if (!isset($row[$val])) {
continue 2;
}
$link .= Adminer\where_link($i++, $column, $row[$val]);
}
echo "<a href='" . Adminer\h($link) . "'>" . Adminer\h($backwardKey["name"]) . "</a>";
echo "<a href='" . Adminer\h($link) . "'>" . Adminer\h(preg_replace('(^' . preg_quote($_GET["select"]) . (substr($_GET["select"], -1) == 's' ? '?' : '') . '_)', '_', $backwardKey["name"])) . "</a>";
$link = Adminer\ME . 'edit=' . urlencode($table);
foreach ($cols as $column => $val) {
$link .= "&set" . urlencode("[" . Adminer\bracket_escape($column) . "]") . "=" . urlencode($row[$val]);

View File

@@ -1,3 +1,5 @@
Using drivers: https://www.adminer.org/plugins/#use
Developing drivers: https://www.adminer.org/en/drivers/
The type declarations must be compatible both with source codes and the compiled version (where PHP5-incompatible types are stripped). It means:

View File

@@ -287,10 +287,6 @@ if (isset($_GET["elastic"])) {
return !!$this->conn->affected_rows;
}
function convertOperator($operator): string {
return $operator == "LIKE %%" ? "should" : $operator;
}
}
function support($feature) {
@@ -412,6 +408,11 @@ if (isset($_GET["elastic"])) {
return $table_status["Engine"] == "view";
}
function view(string $name): array {
$return = connection()->rootQuery("_alias/" . urlencode($name) . "");
return array("select" => implode("\n", array_keys($return)));
}
function error() {
return h(connection()->error);
}
@@ -533,15 +534,18 @@ if (isset($_GET["elastic"])) {
}
}
/** Drop types
* @param list<string> $tables
*/
function drop_views(array $tables): bool {
$return = connection()->rootQuery('_aliases', array('actions' => array_map(function ($table) {
return array('remove' => array('index' => '*', 'alias' => $table));
}, $tables)), 'POST');
return $return && !$return['errors'];
}
function drop_tables(array $tables): bool {
$return = true;
foreach ($tables as $table) { //! convert to bulk api
$return = $return && connection()->rootQuery(urlencode($table), null, 'DELETE');
}
return $return;
}

View File

@@ -7,7 +7,7 @@
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
* @license https://www.gnu.org/licenses/gpl-2.0.html GNU General Public License, version 2 (one or other)
*/
class AdminerCodemirror extends Adminer\Plugin {
class AdminerHighlightCodemirror extends Adminer\Plugin {
private $root;
private $minified;

View File

@@ -7,7 +7,7 @@
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
* @license https://www.gnu.org/licenses/gpl-2.0.html GNU General Public License, version 2 (one or other)
*/
class AdminerMonaco extends Adminer\Plugin {
class AdminerHighlightMonaco extends Adminer\Plugin {
private $root;
function __construct($root = "https://cdn.jsdelivr.net/npm/monaco-editor@0.52/min/vs") {

View File

@@ -7,7 +7,7 @@
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
* @license https://www.gnu.org/licenses/gpl-2.0.html GNU General Public License, version 2 (one or other)
*/
class AdminerPrism extends Adminer\Plugin {
class AdminerHighlightPrism extends Adminer\Plugin {
private $editorRoot;
private $minified;
private $theme;

View File

@@ -12,7 +12,7 @@ class AdminerLoginOtp extends Adminer\Plugin {
/**
* @param string $secret decoded secret, e.g. base64_decode("SECRET")
*/
function __construct(string $secret) {
function __construct($secret) {
$this->secret = $secret;
if ($_POST["auth"]) {
$_SESSION["otp"] = (string) $_POST["auth"]["otp"];

View File

@@ -12,7 +12,7 @@ class AdminerLoginPasswordLess extends Adminer\Plugin {
/** Set allowed password
* @param string $password_hash result of password_hash()
*/
function __construct(string $password_hash) {
function __construct($password_hash) {
$this->password_hash = $password_hash;
}

View File

@@ -19,7 +19,7 @@ class AdminerLoginTable extends Adminer\Plugin {
protected $database;
/** Set database of login table */
function __construct(string $database) {
function __construct($database) {
$this->database = $database;
}

View File

@@ -21,12 +21,12 @@ class AdminerMenuLinks extends Adminer\Plugin {
'' => $this->lang('Both'),
'auto' => $this->lang('Auto (select on select page, structure otherwise)'),
);
$menu = Adminer\get_setting("menu", "adminer_config") ?: $this->menu;
$menu = Adminer\get_setting("menu", "adminer_config", $this->menu);
return array($this->lang('Menu table links') => Adminer\html_radios('config[menu]', $options, $menu, "<br>"));
}
function tablesPrint(array $tables) {
$menu = Adminer\get_setting("menu", "adminer_config") ?: $this->menu;
$menu = Adminer\get_setting("menu", "adminer_config", $this->menu);
$titles = array(
'select' => $this->lang('Select data'),
'table' => $this->lang('Show structure'),
@@ -36,7 +36,7 @@ class AdminerMenuLinks extends Adminer\Plugin {
foreach ($tables as $table => $status) {
$table = "$table"; // do not highlight "0" as active everywhere
$name = Adminer\adminer()->tableName($status);
if ($name != "" && !$status["inherited"]) {
if ($name != "" && !$status["partition"]) {
echo '<li>';
if (!$menu) {
echo '<a href="' . Adminer\h(Adminer\ME) . 'select=' . urlencode($table) . '"'

View File

@@ -26,7 +26,7 @@ class AdminerPrettyJsonColumn extends Adminer\Plugin {
if ($function === '') {
$json = $this->testJson($value);
if ($json !== $value) {
$value = json_encode($json);
return Adminer\q(json_encode($json));
}
}
}

24
plugins/row-numbers.php Normal file
View File

@@ -0,0 +1,24 @@
<?php
/** Display row numbers in select
* @link https://www.adminer.org/plugins/#use
* @author Jakub Vrana, https://www.vrana.cz/
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
* @license https://www.gnu.org/licenses/gpl-2.0.html GNU General Public License, version 2 (one or other)
*/
class AdminerRowNumbers extends Adminer\Plugin {
function backwardKeys($table, $tableName) {
return array(1);
}
function backwardKeysPrint($backwardKeys, $row) {
static $n = $_GET["page"] * Adminer\adminer()->selectLimitProcess();
$n++;
echo "$n.\n";
}
protected $translations = array(
'cs' => array('' => 'Zobrazí čísla řádek ve výpisu'),
);
}

View File

@@ -41,8 +41,8 @@ class AdminerSqlGemini extends Adminer\Plugin {
echo "-- " . $response->error->message;
} else {
$text = $response->candidates[0]->content->parts[0]->text;
$text = preg_replace('~(\n|^)```sql\n(.+)\n```(\n|$)~sU', "*/\n\n\\2\n\n/*", "/*\n$text*/\n");
echo preg_replace('~/\*\s*\*/\n*~', '', $text);
$text2 = preg_replace('~(\n|^)```sql\n(.+)\n```(\n|$)~sU', "*/\n\n\\2\n\n/*", "/*\n$text\n*/", -1, $count);
echo ($count ? preg_replace('~/\*\s*\*/\n*~', '', $text2) : $text);
}
exit;
}

View File

@@ -64,15 +64,26 @@ sessionStorage && document.addEventListener('DOMContentLoaded', () => {
sessionStorage.setItem('adminer_tables_filter_db', db);
});
</script>
<p class="jsonly"><input id="filter-field" autocomplete="off" type="search"><?php echo Adminer\script("qs('#filter-field').oninput = tablesFilterInput;"); ?>
<p class="jsonly"><?php echo $this->lang('Filter'); ?>: <input id="filter-field" autocomplete="off" type="search"><?php echo Adminer\script("qs('#filter-field').oninput = tablesFilterInput;"); ?>
<?php
}
protected $translations = array(
'cs' => array('' => 'Filtruje názvy v seznamu tabulek'),
'de' => array('' => 'Filtern Sie Namen in der Tabellenliste'),
'pl' => array('' => 'Filtruj nazwy na liście tabel'),
'ro' => array('' => 'Nume de filtre în lista de tabele'),
'ja' => array('' => 'テーブル一覧をテーブル名でフィルタリング'),
'cs' => array(
'' => 'Filtruje názvy v seznamu tabulek',
'Filter' => 'Filtr',
),
'de' => array(
'' => 'Filtern Sie Namen in der Tabellenliste',
),
'pl' => array(
'' => 'Filtruj nazwy na liście tabel',
),
'ro' => array(
'' => 'Nume de filtre în lista de tabele',
),
'ja' => array(
'' => 'テーブル一覧をテーブル名でフィルタリング',
),
);
}

48
plugins/timeout.php Normal file
View File

@@ -0,0 +1,48 @@
<?php
/** Specify timeout for running every query
* @link https://www.adminer.org/plugins/#use
* @author Jakub Vrana, https://www.vrana.cz/
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
* @license https://www.gnu.org/licenses/gpl-2.0.html GNU General Public License, version 2 (one or other)
*/
class AdminerTimeout extends Adminer\Plugin {
private $seconds;
/**
* @param int $seconds
*/
function __construct($seconds = 5) {
$this->seconds = $seconds;
}
function afterConnect() {
$seconds = Adminer\get_setting("timeout", "adminer_config", $this->seconds);
if ($seconds != '') {
$ms = $seconds * 1000;
$conn = Adminer\connection();
switch (Adminer\JUSH) {
case 'sql': $conn->query("SET max_execution_time = $ms"); break;
case 'pgsql': $conn->query("SET statement_timeout = $ms"); break;
case 'mssql': $conn->query("SET LOCK_TIMEOUT $ms"); break;
default:
if (method_exists(connection(), 'timeout')) {
$conn->timeout($ms);
}
}
}
}
function config() {
$seconds = Adminer\get_setting("timeout", "adminer_config", $this->seconds);
return array($this->lang('Queries timeout') => '<input type="number" name="config[timeout]" min="0" value="' . Adminer\h($seconds) . '" class="size"> ' . $this->lang('seconds'));
}
protected $translations = array(
'cs' => array(
'' => 'Nastaví timeout pro spouštění každého dotazu',
'Queries timeout' => 'Timeout dotazů',
'seconds' => 'sekund',
),
);
}