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

Compare commits

...

29 Commits

Author SHA1 Message Date
Peter Knut
507f335371 Release 4.9.3 2024-10-02 17:07:09 +02:00
Peter Knut
ea314b8103 Hide invalid edit form if table record is not found 2024-10-02 09:35:59 +02:00
Peter Knut
e250470768 PostgreSQL: Fix editing record that contains a field with GENERATED ALWAYS default value
Fields with GENERATED ALWAYS default values are also disabled.

Thanks to PurpleTape (https://github.com/adminerevo/adminerevo/issues/201).
2024-10-02 00:29:24 +02:00
Peter Knut
2fa42d50eb Fix using undefined Min_DB::info property 2024-10-01 23:37:07 +02:00
Peter Knut
a366b7af09 MySQL: Fix editing user's proxy privilege, refactoring
- Uncheck all other priviledges if 'All privileges' is checked.
- Refactor related functions.
2024-10-01 23:22:26 +02:00
Peter Knut
b039a39e4d Bigger font size for code blocks 2024-10-01 09:07:21 +02:00
SeaEagle
08e48c8641 MySQL: Fix where clause for JSON column
Issue: https://github.com/adminerevo/adminerevo/issues/175
2024-10-01 09:06:20 +02:00
Peter Knut
78c2041cfd Fix background color of <pre> used as edit field 2024-10-01 00:33:47 +02:00
Peter Knut
5d7c5fa268 Do not include unchanged PARTITION BY definition into ALTER TABLE query 2024-09-22 00:33:55 +02:00
Peter Knut
8f1db4cf6f Add helper methods for dumping variable to the output 2024-09-21 22:34:38 +02:00
Peter Knut
9daa88acca MariaDB: Fix comparing CURRENT_TIMESTAMP definition while altering a table 2024-09-21 22:20:08 +02:00
Peter Knut
aa519b78ca MySQL, PostgreSQL: Fix queries splitting and string constants
Thanks to alxivnov (https://github.com/vrana/adminer/pull/490).
2024-09-21 09:28:50 +02:00
Michael Graß
aee800efed Do not limit unlimited memory, fix number conversion warning 2024-09-20 22:28:46 +02:00
Peter Knut
06d0f957d5 Bump version to 4.9.3-dev 2024-09-18 10:57:48 +02:00
Peter Knut
3f0bc24e01 Release 4.9.2 2024-09-18 09:50:35 +02:00
Peter Knut
4863f48d33 Basic JS code cleanup 2024-09-18 09:39:17 +02:00
Peter Knut
9ea8f44919 Fix undefined property in error message if driver does not support error number 2024-09-18 00:46:34 +02:00
khoazero123
fa791b5461 PostgreSQL: Fix exporting CREATE TABLE with sequence default value 2024-09-18 00:31:15 +02:00
Roy-Orbison
203162b203 Function to retrieve driver name
Plugins cannot access $drivers global after compilation.
2024-09-18 00:27:02 +02:00
Peter Knut
e4e76b6384 PostgreSQL: Allow to set connection's sslmode with AdminerLoginSsl plugin
Thanks to wodka (https://github.com/vrana/adminer/pull/427/files)
2024-09-18 00:27:02 +02:00
Peter Knut
353cd452a3 PostgreSQL: Fix exporting CREATE TABLE query with GENERATED default values
Thanks to GottfriedCP (https://github.com/adminerevo/adminerevo/issues/157)
2024-09-18 00:27:02 +02:00
Peter Knut
5bc4ac6c18 Merge branch 'editor-search-fix' 2024-09-17 15:46:51 +02:00
Peter Knut
aec8275502 Editor: Fix searching in tables
Thanks to ytetsuro (https://github.com/vrana/adminer/pull/473)
2024-09-17 15:46:36 +02:00
Peter Knut
7d5077e687 Cleanup the code for searching 2024-09-17 15:36:02 +02:00
Peter Knut
91d0d8538f MySQL: Do not show 'empty' enum value in strict mode 2024-09-10 23:47:06 +02:00
Peter Knut
2439369143 PostreSQL: Fix search condition for network address types, add macaddr8 type
This fixes issue https://github.com/adminerevo/adminerevo/issues/115
2024-09-10 10:27:59 +02:00
Peter Knut
d5bce9b3e9 PostreSQL: Fix search fields configuration
Regression from 4.9.
2024-09-10 08:28:18 +02:00
Peter Knut
b42762e4dc Remove hardcoded textarea height 2024-09-09 23:54:51 +02:00
Peter Knut
695a720403 Bump version to 4.9.2-dev 2024-09-09 23:53:50 +02:00
23 changed files with 584 additions and 259 deletions

View File

@@ -82,20 +82,39 @@ if ($_POST && !process_fields($row["fields"]) && !$error) {
}
$partitioning = "";
if ($partition_by[$row["partition_by"]]) {
$partitions = array();
if ($row["partition_by"] == 'RANGE' || $row["partition_by"] == 'LIST') {
foreach (array_filter($row["partition_names"]) as $key => $val) {
$value = $row["partition_values"][$key];
$partitions[] = "\n PARTITION " . idf_escape($val) . " VALUES " . ($row["partition_by"] == 'RANGE' ? "LESS THAN" : "IN") . ($value != "" ? " ($value)" : " MAXVALUE"); //! SQL injection
if (support("partitioning")) {
if (isset($partition_by[$row["partition_by"]])) {
$params = array_filter($row, function ($key) {
return preg_match('~^partition~', $key);
}, ARRAY_FILTER_USE_KEY);
foreach ($params["partition_names"] as $key => $name) {
if ($name === "") {
unset($params["partition_names"][$key]);
unset($params["partition_values"][$key]);
}
}
if ($params != get_partitions_info($TABLE)) {
$partitions = [];
if ($params["partition_by"] == 'RANGE' || $params["partition_by"] == 'LIST') {
foreach ($params["partition_names"] as $key => $name) {
$value = $params["partition_values"][$key];
$partitions[] = "\n PARTITION " . idf_escape($name) . " VALUES " . ($params["partition_by"] == 'RANGE' ? "LESS THAN" : "IN") . ($value != "" ? " ($value)" : " MAXVALUE"); //! SQL injection
}
}
// $params["partition"] can be expression, not only column
$partitioning .= "\nPARTITION BY {$params["partition_by"]}({$params["partition"]})";
if ($partitions) {
$partitioning .= " (" . implode(",", $partitions) . "\n)";
} elseif ($params["partitions"]) {
$partitioning .= " PARTITIONS " . (int)$params["partitions"];
}
}
} elseif (preg_match("~partitioned~", $table_status["Create_options"])) {
$partitioning .= "\nREMOVE PARTITIONING";
}
$partitioning .= "\nPARTITION BY $row[partition_by]($row[partition])" . ($partitions // $row["partition"] can be expression, not only column
? " (" . implode(",", $partitions) . "\n)"
: ($row["partitions"] ? " PARTITIONS " . (+$row["partitions"]) : "")
);
} elseif (support("partitioning") && preg_match("~partitioned~", $table_status["Create_options"])) {
$partitioning .= "\nREMOVE PARTITIONING";
}
$message = lang('Table has been altered.');
@@ -141,13 +160,9 @@ if (!$_POST) {
}
if (support("partitioning")) {
$from = "FROM information_schema.PARTITIONS WHERE TABLE_SCHEMA = " . q(DB) . " AND TABLE_NAME = " . q($TABLE);
$result = $connection->query("SELECT PARTITION_METHOD, PARTITION_ORDINAL_POSITION, PARTITION_EXPRESSION $from ORDER BY PARTITION_ORDINAL_POSITION DESC LIMIT 1");
list($row["partition_by"], $row["partitions"], $row["partition"]) = $result->fetch_row();
$partitions = get_key_vals("SELECT PARTITION_NAME, PARTITION_DESCRIPTION $from AND PARTITION_NAME != '' ORDER BY PARTITION_ORDINAL_POSITION");
$partitions[""] = "";
$row["partition_names"] = array_keys($partitions);
$row["partition_values"] = array_values($partitions);
$row += get_partitions_info($TABLE);
$row["partition_names"][] = "";
$row["partition_values"][] = "";
}
}
}

View File

@@ -630,6 +630,17 @@ WHERE sys1.xtype = 'TR' AND sys2.name = " . q($table)
return array();
}
/**
* @return bool
*/
function is_strict_mode() {
return false;
}
function is_c_style_escapes() {
return true;
}
function show_status() {
return array();
}

View File

@@ -16,10 +16,17 @@ if (!defined("DRIVER")) {
global $adminer;
mysqli_report(MYSQLI_REPORT_OFF); // stays between requests, not required since PHP 5.3.4
list($host, $port) = explode(":", $server, 2); // part after : is used for port or socket
$ssl = $adminer->connectSsl();
if ($ssl) {
$this->ssl_set($ssl['key'], $ssl['cert'], $ssl['ca'], '', '');
if (isset($ssl['key']) || isset($ssl['cert']) || isset($ssl['ca'])) {
$this->ssl_set(
isset($ssl['key']) ? $ssl['key'] : null,
isset($ssl['cert']) ? $ssl['cert'] : null,
isset($ssl['ca']) ? $ssl['ca'] : null,
null, null
);
}
$return = @$this->real_connect(
($server != "" ? $host : ini_get("mysqli.default_host")),
($server . $username != "" ? $username : ini_get("mysqli.default_user")),
@@ -234,25 +241,23 @@ if (!defined("DRIVER")) {
function connect($server, $username, $password) {
global $adminer;
$options = array(PDO::MYSQL_ATTR_LOCAL_INFILE => false);
$dsn = "mysql:charset=utf8;host=" . str_replace(":", ";unix_socket=", preg_replace('~:(\d)~', ';port=\1', $server));
$options = [PDO::MYSQL_ATTR_LOCAL_INFILE => false];
$ssl = $adminer->connectSsl();
if ($ssl) {
if (!empty($ssl['key'])) {
$options[PDO::MYSQL_ATTR_SSL_KEY] = $ssl['key'];
}
if (!empty($ssl['cert'])) {
$options[PDO::MYSQL_ATTR_SSL_CERT] = $ssl['cert'];
}
if (!empty($ssl['ca'])) {
$options[PDO::MYSQL_ATTR_SSL_CA] = $ssl['ca'];
}
if (isset($ssl['key'])) {
$options[PDO::MYSQL_ATTR_SSL_KEY] = $ssl['key'];
}
$this->dsn(
"mysql:charset=utf8;host=" . str_replace(":", ";unix_socket=", preg_replace('~:(\d)~', ';port=\1', $server)),
$username,
$password,
$options
);
if (isset($ssl['cert'])) {
$options[PDO::MYSQL_ATTR_SSL_CERT] = $ssl['cert'];
}
if (isset($ssl['ca'])) {
$options[PDO::MYSQL_ATTR_SSL_CA] = $ssl['ca'];
}
$this->dsn($dsn, $username, $password, $options);
return true;
}
@@ -316,8 +321,8 @@ if (!defined("DRIVER")) {
}
}
function convertSearch($idf, $val, $field) {
return (preg_match('~char|text|enum|set~', $field["type"]) && !preg_match("~^utf8~", $field["collation"]) && preg_match('~[\x80-\xFF]~', $val['val'])
function convertSearch($idf, array $where, array $field) {
return (preg_match('~char|text|enum|set~', $field["type"]) && !preg_match("~^utf8~", $field["collation"]) && preg_match('~[\x80-\xFF]~', $where['val'])
? "CONVERT($idf USING " . charset($this->_conn) . ")"
: $idf
);
@@ -1056,6 +1061,29 @@ if (!defined("DRIVER")) {
return get_key_vals("SHOW VARIABLES");
}
/**
* @return bool
*/
function is_strict_mode() {
static $strictMode = null;
if ($strictMode === null) {
$strictMode = (bool)preg_match('~STRICT_(TRANS|ALL)_TABLES~', get_key_vals("SHOW VARIABLES LIKE 'sql_mode'")["sql_mode"]);
}
return $strictMode;
}
function is_c_style_escapes() {
static $c_style = null;
if ($c_style === null) {
$c_style = strpos(get_key_vals("SHOW VARIABLES LIKE 'sql_mode'")["sql_mode"], 'NO_BACKSLASH_ESCAPES') === false;
}
return $c_style;
}
/** Get process list
* @return array ($row)
*/

View File

@@ -486,6 +486,17 @@ AND c_src.TABLE_NAME = " . q($table);
return get_key_vals('SELECT name, display_value FROM v$parameter');
}
/**
* @return bool
*/
function is_strict_mode() {
return false;
}
function is_c_style_escapes() {
return true;
}
function process_list() {
return get_rows('SELECT sess.process AS "process", sess.username AS "user", sess.schemaname AS "schema", sess.status AS "status", sess.wait_class AS "wait_class", sess.seconds_in_wait AS "seconds_in_wait", sql.sql_text AS "sql_text", sess.machine AS "machine", sess.port AS "port"
FROM v$session sess LEFT OUTER JOIN v$sql sql

View File

@@ -19,7 +19,14 @@ if (isset($_GET["pgsql"])) {
global $adminer;
$db = $adminer->database();
set_error_handler(array($this, '_error'));
$this->_string = "host='" . str_replace(":", "' port='", addcslashes($server, "'\\")) . "' user='" . addcslashes($username, "'\\") . "' password='" . addcslashes($password, "'\\") . "'";
$ssl = $adminer->connectSsl();
if (isset($ssl["mode"])) {
$this->_string .= " sslmode='" . $ssl["mode"] . "'";
}
$this->_link = @pg_connect("$this->_string dbname='" . ($db != "" ? addcslashes($db, "'\\") : "postgres") . "'", PGSQL_CONNECT_FORCE_NEW);
if (!$this->_link && $db != "") {
// try to connect directly with database for performance
@@ -36,7 +43,7 @@ if (isset($_GET["pgsql"])) {
}
function quote($string) {
return "'" . pg_escape_string($this->_link, $string) . "'";
return pg_escape_literal($this->_link, $string);
}
function value($val, $field) {
@@ -148,9 +155,19 @@ if (isset($_GET["pgsql"])) {
function connect($server, $username, $password) {
global $adminer;
$db = $adminer->database();
$this->dsn("pgsql:host='" . str_replace(":", "' port='", addcslashes($server, "'\\")) . "' client_encoding=utf8 dbname='" . ($db != "" ? addcslashes($db, "'\\") : "postgres") . "'", $username, $password); //! client_encoding is supported since 9.1 but we can't yet use min_version here
//! connect without DB in case of an error
//! client_encoding is supported since 9.1, but we can't yet use min_version here
$dsn = "pgsql:host='" . str_replace(":", "' port='", addcslashes($server, "'\\")) . "' client_encoding=utf8 dbname='" . ($db != "" ? addcslashes($db, "'\\") : "postgres") . "'";
$ssl = $adminer->connectSsl();
if (isset($ssl["mode"])) {
$dsn .= " sslmode='" . $ssl["mode"] . "'";
}
$this->dsn($dsn, $username, $password);
return true;
}
@@ -212,13 +229,13 @@ if (isset($_GET["pgsql"])) {
return $query;
}
function convertSearch($idf, $val, $field) {
return (preg_match('~char|text'
. (!preg_match('~LIKE~', $val["op"]) ? '|date|time(stamp)?|boolean|uuid|' . number_type() : '')
. '~', $field["type"])
? $idf
: "CAST($idf AS text)"
);
function convertSearch($idf, array $where, array $field) {
$textTypes = "char|text";
if (strpos($where["op"], "LIKE") === false) {
$textTypes .= "|date|time(stamp)?|boolean|uuid|inet|cidr|macaddr|" . number_type();
}
return (preg_match("~$textTypes~", $field["type"]) ? $idf : "CAST($idf AS text)");
}
function quoteBinary($s) {
@@ -378,7 +395,7 @@ ORDER BY a.attnum"
}
$row["null"] = !$row["attnotnull"];
$row["auto_increment"] = $row['attidentity'] || preg_match('~^nextval\(~i', $row["default"]);
$row["privileges"] = array("insert" => 1, "select" => 1, "update" => 1);
$row["privileges"] = array("insert" => 1, "select" => 1, "update" => 1, "where" => 1, "order" => 1);
if (preg_match('~(.+)::[^,)]+(.*)~', $row["default"], $match)) {
$row["default"] = ($match[1] == "NULL" ? null : idf_unescape($match[1]) . $match[2]);
}
@@ -775,7 +792,7 @@ AND typelem = 0"
$return = "CREATE TABLE " . idf_escape($status['nspname']) . "." . idf_escape($status['Name']) . " (\n ";
// fields' definitions
foreach ($fields as $field_name => $field) {
foreach ($fields as $field) {
$part = idf_escape($field['field']) . ' ' . $field['full_type']
. default_value($field)
. ($field['attnotnull'] ? " NOT NULL" : "");
@@ -784,10 +801,11 @@ AND typelem = 0"
// sequences for fields
if (preg_match('~nextval\(\'([^\']+)\'\)~', $field['default'], $matches)) {
$sequence_name = $matches[1];
$sq = reset(get_rows(min_version(10)
? "SELECT *, cache_size AS cache_value FROM pg_sequences WHERE schemaname = current_schema() AND sequencename = " . q($sequence_name)
$rows = get_rows(min_version(10)
? "SELECT *, cache_size AS cache_value FROM pg_sequences WHERE schemaname = current_schema() AND sequencename = " . q(idf_unescape($sequence_name))
: "SELECT * FROM $sequence_name"
));
);
$sq = reset($rows);
$sequences[] = ($style == "DROP+CREATE" ? "DROP SEQUENCE IF EXISTS $sequence_name;\n" : "")
. "CREATE SEQUENCE $sequence_name INCREMENT $sq[increment_by] MINVALUE $sq[min_value] MAXVALUE $sq[max_value]" . ($auto_increment && $sq['last_value'] ? " START $sq[last_value]" : "") . " CACHE $sq[cache_value];";
}
@@ -860,6 +878,23 @@ AND typelem = 0"
return get_key_vals("SHOW ALL");
}
/**
* @return bool
*/
function is_strict_mode() {
return false;
}
function is_c_style_escapes() {
static $c_style = null;
if ($c_style === null) {
$c_style = get_vals("SHOW standard_conforming_strings")[0] == "off";
}
return $c_style;
}
function process_list() {
return get_rows("SELECT * FROM pg_stat_activity ORDER BY " . (min_version(9.2) ? "pid" : "procpid"));
}
@@ -899,7 +934,7 @@ AND typelem = 0"
lang('Date and time') => array("date" => 13, "time" => 17, "timestamp" => 20, "timestamptz" => 21, "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('Network') => array("cidr" => 43, "inet" => 43, "macaddr" => 17, "macaddr8" => 23, "txid_snapshot" => 0),
lang('Geometry') => array("box" => 0, "circle" => 0, "line" => 0, "lseg" => 0, "path" => 0, "point" => 0, "polygon" => 0),
) as $key => $val) { //! can be retrieved from pg_type
$types += $val;
@@ -924,6 +959,7 @@ AND typelem = 0"
"char|text" => "||",
)
),
'c_style_escapes' => true,
);
}
}

View File

@@ -764,6 +764,17 @@ if (isset($_GET["sqlite"]) || isset($_GET["sqlite2"])) {
return $return;
}
/**
* @return bool
*/
function is_strict_mode() {
return false;
}
function is_c_style_escapes() {
return true;
}
function show_status() {
$return = array();
foreach (get_vals("PRAGMA compile_options") as $option) {

View File

@@ -537,50 +537,60 @@ class Adminer {
* @return array expressions to join by AND
*/
function selectSearchProcess($fields, $indexes) {
global $connection, $driver;
$return = array();
global $driver;
$return = [];
foreach ($indexes as $i => $index) {
if ($index["type"] == "FULLTEXT" && $_GET["fulltext"][$i] != "") {
$return[] = "MATCH (" . implode(", ", array_map('idf_escape', $index["columns"])) . ") AGAINST (" . q($_GET["fulltext"][$i]) . (isset($_GET["boolean"][$i]) ? " IN BOOLEAN MODE" : "") . ")";
}
}
foreach ((array) $_GET["where"] as $key => $val) {
if ("$val[col]$val[val]" != "" && in_array($val["op"], $this->operators)) {
foreach ((array) $_GET["where"] as $where) {
$col = $where["col"];
$op = $where["op"];
$val = $where["val"];
if ("$col$val" != "" && in_array($op, $this->operators)) {
$prefix = "";
$cond = " $val[op]";
if (preg_match('~IN$~', $val["op"])) {
$in = process_length($val["val"]);
$cond = " $op";
if (preg_match('~IN$~', $op)) {
$in = process_length($val);
$cond .= " " . ($in != "" ? $in : "(NULL)");
} elseif ($val["op"] == "SQL") {
$cond = " $val[val]"; // SQL injection
} elseif ($val["op"] == "LIKE %%") {
$cond = " LIKE " . $this->processInput($fields[$val["col"]], "%$val[val]%");
} elseif ($val["op"] == "ILIKE %%") {
$cond = " ILIKE " . $this->processInput($fields[$val["col"]], "%$val[val]%");
} elseif ($val["op"] == "FIND_IN_SET") {
$prefix = "$val[op](" . q($val["val"]) . ", ";
} elseif ($op == "SQL") {
$cond = " $val"; // SQL injection
} elseif ($op == "LIKE %%") {
$cond = " LIKE " . $this->processInput($fields[$col], "%$val%");
} elseif ($op == "ILIKE %%") {
$cond = " ILIKE " . $this->processInput($fields[$col], "%$val%");
} elseif ($op == "FIND_IN_SET") {
$prefix = "$op(" . q($val) . ", ";
$cond = ")";
} elseif (!preg_match('~NULL$~', $val["op"])) {
$cond .= " " . $this->processInput($fields[$val["col"]], $val["val"]);
} elseif (!preg_match('~NULL$~', $op)) {
$cond .= " " . $this->processInput($fields[$col], $val);
}
if ($val["col"] != "") {
$return[] = $prefix . $driver->convertSearch(idf_escape($val["col"]), $val, $fields[$val["col"]]) . $cond;
if ($col != "") {
$return[] = $prefix . $driver->convertSearch(idf_escape($col), $where, $fields[$col]) . $cond;
} else {
// find anywhere
$cols = array();
foreach ($fields as $name => $field) {
if (isset($field["privileges"]["where"])
&& (preg_match('~^[-\d.' . (preg_match('~IN$~', $val["op"]) ? ',' : '') . ']+$~', $val["val"]) || !preg_match('~' . number_type() . '|bit~', $field["type"]))
&& (!preg_match("~[\x80-\xFF]~", $val["val"]) || preg_match('~char|text|enum|set~', $field["type"]))
&& (!preg_match('~date|timestamp~', $field["type"]) || preg_match('~^\d+-\d+-\d+~', $val["val"]))
&& (preg_match('~^[-\d.' . (preg_match('~IN$~', $op) ? ',' : '') . ']+$~', $val) || !preg_match('~' . number_type() . '|bit~', $field["type"]))
&& (!preg_match("~[\x80-\xFF]~", $val) || preg_match('~char|text|enum|set~', $field["type"]))
&& (!preg_match('~date|timestamp~', $field["type"]) || preg_match('~^\d+-\d+-\d+~', $val))
) {
$cols[] = $prefix . $driver->convertSearch(idf_escape($name), $val, $field) . $cond;
$cols[] = $prefix . $driver->convertSearch(idf_escape($name), $where, $field) . $cond;
}
}
$return[] = ($cols ? "(" . implode(" OR ", $cols) . ")" : "1 = 0");
}
}
}
return $return;
}

View File

@@ -6,6 +6,7 @@ function adminer_errors($errno, $errstr) {
error_reporting(6135); // errors and warnings
set_error_handler('adminer_errors', E_WARNING);
include "../adminer/include/debug.inc.php";
include "../adminer/include/coverage.inc.php";
// disable filter.default

View File

@@ -0,0 +1,14 @@
<?php
function dump($value)
{
echo "<pre>";
var_export($value);
echo "</pre>\n";
}
function dumpe($value)
{
dump($value);
exit;
}

View File

@@ -11,6 +11,15 @@ function add_driver($id, $name) {
$drivers[$id] = $name;
}
/** Get driver name
* @param string
* @return string
*/
function get_driver($id) {
global $drivers;
return $drivers[$id];
}
/*abstract*/ class Min_SQL {
var $_conn;
@@ -138,7 +147,7 @@ function add_driver($id, $name) {
* @param array
* @return string
*/
function convertSearch($idf, $val, $field) {
function convertSearch($idf, array $where, array $field) {
return $idf;
}

View File

@@ -221,12 +221,17 @@ function process_type($field, $collate = "COLLATE") {
* @return array array("field", "type", "NULL", "DEFAULT", "ON UPDATE", "COMMENT", "AUTO_INCREMENT")
*/
function process_field($field, $type_field) {
// MariaDB exports CURRENT_TIMESTAMP as a function.
if ($field["on_update"]) {
$field["on_update"] = str_ireplace("current_timestamp()", "CURRENT_TIMESTAMP", $field["on_update"]);
}
return array(
idf_escape(trim($field["field"])),
process_type($type_field),
($field["null"] ? " NULL" : " NOT NULL"), // NULL for timestamp
default_value($field),
(preg_match('~timestamp|datetime~', $field["type"]) && $field["on_update"] ? " ON UPDATE $field[on_update]" : ""),
(preg_match('~timestamp|datetime~', $field["type"]) && $field["on_update"] ? " ON UPDATE " . $field["on_update"] : ""),
(support("comment") && $field["comment"] != "" ? " COMMENT " . q($field["comment"]) : ""),
($field["auto_increment"] ? auto_increment() : null),
);
@@ -238,7 +243,18 @@ function process_field($field, $type_field) {
*/
function default_value($field) {
$default = $field["default"];
return ($default === null ? "" : " DEFAULT " . (preg_match('~char|binary|text|enum|set~', $field["type"]) || preg_match('~^(?![a-z])~i', $default) ? q($default) : $default));
if ($default === null) return "";
if (stripos($default, "GENERATED ") === 0) {
return " $default";
}
// MariaDB exports CURRENT_TIMESTAMP as a function.
$default = str_ireplace("current_timestamp()", "CURRENT_TIMESTAMP", $default);
$quote = preg_match('~char|binary|text|enum|set~', $field["type"]) || preg_match('~^(?![a-z])~i', $default);
return " DEFAULT " . ($quote ? q($default) : $default);
}
/** Get type class to use in CSS
@@ -368,25 +384,43 @@ function normalize_enum($match) {
return "'" . str_replace("'", "''", addcslashes(stripcslashes(str_replace($match[0][0] . $match[0][0], $match[0][0], substr($match[0], 1, -1))), '\\')) . "'";
}
/** Issue grant or revoke commands
* @param string GRANT or REVOKE
* @param array
* @param string
* @param string
* @return bool
*/
function grant($grant, $privileges, $columns, $on) {
if (!$privileges) {
return true;
/**
* Issue grant or revoke commands.
*
* @param bool $grant
* @param array $privileges
* @param string $columns
* @param string $on
* @param string $user
*
* @return bool
*/
function grant($grant, array $privileges, $columns, $on, $user) {
if (!$privileges) return true;
if ($privileges == ["ALL PRIVILEGES", "GRANT OPTION"]) {
if ($grant) {
return (bool) queries("GRANT ALL PRIVILEGES ON $on TO $user WITH GRANT OPTION");
} else {
return queries("REVOKE ALL PRIVILEGES ON $on FROM $user") &&
queries("REVOKE GRANT OPTION ON $on FROM $user");
}
}
if ($privileges == array("ALL PRIVILEGES", "GRANT OPTION")) {
// can't be granted or revoked together
return ($grant == "GRANT"
? queries("$grant ALL PRIVILEGES$on WITH GRANT OPTION")
: queries("$grant ALL PRIVILEGES$on") && queries("$grant GRANT OPTION$on")
);
if ($privileges == ["GRANT OPTION", "PROXY"]) {
if ($grant) {
return (bool) queries("GRANT PROXY ON $on TO $user WITH GRANT OPTION");
} else {
return (bool) queries("REVOKE PROXY ON $on FROM $user");
}
}
return queries("$grant " . preg_replace('~(GRANT OPTION)\([^)]*\)~', '\1', implode("$columns, ", $privileges) . $columns) . $on);
return (bool) queries(
($grant ? "GRANT " : "REVOKE ") .
preg_replace('~(GRANT OPTION)\([^)]*\)~', '$1', implode("$columns, ", $privileges) . $columns) .
" ON $on " .
($grant ? "TO " : "FROM ") . $user
);
}
/** Drop old object and create a new one
@@ -515,9 +549,9 @@ function tar_file($filename, $tmp_file) {
function ini_bytes($ini) {
$val = ini_get($ini);
switch (strtolower(substr($val, -1))) {
case 'g': $val *= 1024; // no break
case 'm': $val *= 1024; // no break
case 'k': $val *= 1024;
case 'g': $val = (int)$val * 1024; // no break
case 'm': $val = (int)$val * 1024; // no break
case 'k': $val = (int)$val * 1024;
}
return $val;
}

View File

@@ -477,24 +477,36 @@ function escape_key($key) {
*/
function where($where, $fields = array()) {
global $connection, $jush;
$return = array();
$conditions = [];
foreach ((array) $where["where"] as $key => $val) {
$key = bracket_escape($key, 1); // 1 - back
$column = escape_key($key);
$return[] = $column
. ($jush == "sql" && is_numeric($val) && preg_match('~\.~', $val) ? " LIKE " . q($val) // LIKE because of floats but slow with ints
: ($jush == "mssql" ? " LIKE " . q(preg_replace('~[_%[]~', '[\0]', $val)) // LIKE because of text
: " = " . unconvert_field($fields[$key], q($val))
))
; //! enum and set
if ($jush == "sql" && preg_match('~char|text~', $fields[$key]["type"]) && preg_match("~[^ -@]~", $val)) { // not just [a-z] to catch non-ASCII characters
$return[] = "$column = " . q($val) . " COLLATE " . charset($connection) . "_bin";
if ($jush == "sql" && $fields[$key]["type"] == "json") {
$conditions[] = "$column = CAST(" . q($val) . " AS JSON)";
} elseif ($jush == "sql" && is_numeric($val) && strpos($val, ".") !== false) {
// LIKE because of floats but slow with ints.
$conditions[] = "$column LIKE " . q($val);
} elseif ($jush == "mssql") {
// LIKE because of text.
$conditions[] = "$column LIKE " . q(preg_replace('~[_%[]~', '[\0]', $val));
} else {
$conditions[] = "$column = " . unconvert_field($fields[$key], q($val));
}
// Not just [a-z] to catch non-ASCII characters.
if ($jush == "sql" && preg_match('~char|text~', $fields[$key]["type"]) && preg_match("~[^ -@]~", $val)) {
$conditions[] = "$column = " . q($val) . " COLLATE " . charset($connection) . "_bin";
}
}
foreach ((array) $where["null"] as $key) {
$return[] = escape_key($key) . " IS NULL";
$conditions[] = escape_key($key) . " IS NULL";
}
return implode(" AND ", $return);
return implode(" AND ", $conditions);
}
/** Create SQL condition from query string
@@ -914,13 +926,16 @@ function column_foreign_keys($table) {
*/
function enum_input($type, $attrs, $field, $value, $empty = null) {
global $adminer;
$return = ($empty !== null && !is_strict_mode() ? "<label><input type='$type'$attrs value='$empty'" . ((is_array($value) ? in_array($empty, $value) : $value === 0) ? " checked" : "") . "><i>" . lang('empty') . "</i></label>" : "");
preg_match_all("~'((?:[^']|'')*)'~", $field["length"], $matches);
$return = ($empty !== null ? "<label><input type='$type'$attrs value='$empty'" . ((is_array($value) ? in_array($empty, $value) : $value === 0) ? " checked" : "") . "><i>" . lang('empty') . "</i></label>" : "");
foreach ($matches[1] as $i => $val) {
$val = stripcslashes(str_replace("''", "'", $val));
$checked = (is_int($value) ? $value == $i+1 : (is_array($value) ? in_array($i+1, $value) : $value === $val));
$return .= " <label><input type='$type'$attrs value='" . ($i+1) . "'" . ($checked ? ' checked' : '') . '>' . h($adminer->editVal($val, $field)) . '</label>';
}
return $return;
}
@@ -932,8 +947,9 @@ function enum_input($type, $attrs, $field, $value, $empty = null) {
*/
function input($field, $value, $function) {
global $types, $adminer, $jush;
$name = h(bracket_escape($field["field"]));
echo "<td class='function'>";
if (is_array($value) && !$function) {
$args = array($value);
if (version_compare(PHP_VERSION, 5.4) >= 0) {
@@ -947,13 +963,18 @@ function input($field, $value, $function) {
$function = null;
}
$functions = (isset($_GET["select"]) || $reset ? array("orig" => lang('original')) : array()) + $adminer->editFunctions($field);
$attrs = " name='fields[$name]'";
$disabled = stripos($field["default"], "GENERATED ALWAYS AS ") === 0 ? " disabled=''" : "";
$attrs = " name='fields[$name]' $disabled";
echo "<td class='function'>";
if ($field["type"] == "enum") {
echo h($functions[""]) . "<td>" . $adminer->editInput($_GET["edit"], $field, $attrs, $value);
} else {
$has_function = (in_array($function, $functions) || isset($functions[$function]));
echo (count($functions) > 1
? "<select name='function[$name]'>" . optionlist($functions, $function === null || $has_function ? $function : "") . "</select>"
? "<select name='function[$name]' $disabled>" . optionlist($functions, $function === null || $has_function ? $function : "") . "</select>"
. on_help("getTarget(event).value.replace(/^SQL\$/, '')", 1)
. script("qsl('select').onchange = functionChange;", "")
: h(reset($functions))
@@ -978,7 +999,7 @@ function input($field, $value, $function) {
$attrs .= " cols='50' rows='12'";
} else {
$rows = min(12, substr_count($value, "\n") + 1);
$attrs .= " cols='30' rows='$rows'" . ($rows == 1 ? " style='height: 1.2em;'" : ""); // 1.2em - line-height
$attrs .= " cols='30' rows='$rows'";
}
echo "<textarea$attrs>" . h($value) . '</textarea>';
} elseif ($function == "json" || preg_match('~^jsonb?$~', $field["type"])) {
@@ -1018,6 +1039,11 @@ function input($field, $value, $function) {
*/
function process_input($field) {
global $adminer, $driver;
if (stripos($field["default"], "GENERATED ALWAYS AS ") === 0) {
return null;
}
$idf = bracket_escape($field["field"]);
$function = $_POST["function"][$idf];
$value = $_POST["fields"][$idf];
@@ -1108,6 +1134,27 @@ function search_tables() {
echo ($sep ? "<p class='message'>" . lang('No tables.') : "</ul>") . "\n";
}
/**
* @param string $table
* @return array
*/
function get_partitions_info($table) {
global $connection;
$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");
$info = [];
list($info["partition_by"], $info["partition"], $info["partitions"]) = $result->fetch_row();
$partitions = get_key_vals("SELECT PARTITION_NAME, PARTITION_DESCRIPTION $from AND PARTITION_NAME != '' ORDER BY PARTITION_ORDINAL_POSITION");
$info["partition_names"] = array_keys($partitions);
$info["partition_values"] = array_values($partitions);
return $info;
}
/** Send headers for export
* @param string
* @param bool
@@ -1438,6 +1485,7 @@ function edit_form($table, $fields, $row, $update) {
$adminer->editRowPrint($table, $fields, $row, $update);
if ($row === false) {
echo "<p class='error'>" . lang('No rows.') . "\n";
return;
}
?>
<form action="" method="post" enctype="multipart/form-data" id="form">

View File

@@ -1,2 +1,2 @@
<?php
$VERSION = "4.9.1";
$VERSION = "4.9.3";

View File

@@ -32,8 +32,8 @@ if (!$error && $_POST) {
}
if (is_string($query)) { // get_file() returns error as number, fread() as false
if (function_exists('memory_get_usage')) {
@ini_set("memory_limit", max(ini_bytes("memory_limit"), 2 * strlen($query) + memory_get_usage() + 8e6)); // @ - may be disabled, 2 - substr and trim, 8e6 - other variables
if (function_exists('memory_get_usage') && ($memory_limit = ini_bytes("memory_limit")) != "-1") {
@ini_set("memory_limit", max($memory_limit, 2 * strlen($query) + memory_get_usage() + 8e6)); // @ - may be disabled, 2 - substr and trim, 8e6 - other variables
}
if ($query != "" && strlen($query) < 1e6) { // don't add big queries
@@ -81,7 +81,21 @@ if (!$error && $_POST) {
$offset = $pos + strlen($found);
if ($found && rtrim($found) != $delimiter) { // find matching quote or comment end
while (preg_match('(' . ($found == '/*' ? '\*/' : ($found == '[' ? ']' : (preg_match('~^-- |^#~', $found) ? "\n" : preg_quote($found) . "|\\\\."))) . '|$)s', $query, $match, PREG_OFFSET_CAPTURE, $offset)) { //! respect sql_mode NO_BACKSLASH_ESCAPES
$c_style_escapes = is_c_style_escapes() || ($jush == "pgsql" && ($pos > 0 && strtolower($query[$pos - 1]) == "e"));
$pattern = '(';
if ($found == '/*') {
$pattern .= '\*/';
} elseif ($found == '[') {
$pattern .= ']';
} elseif (preg_match('~^-- |^#~', $found)) {
$pattern .= "\n";
} else {
$pattern .= preg_quote($found) . ($c_style_escapes ? "|\\\\." : "");
}
$pattern .= '|$)s';
while (preg_match($pattern, $query, $match, PREG_OFFSET_CAPTURE, $offset)) {
$s = $match[0][0];
if (!$s && $fp && !feof($fp)) {
$query .= fread($fp, 1e5);
@@ -123,7 +137,7 @@ if (!$error && $_POST) {
if ($connection->error) {
echo ($_POST["only_errors"] ? $print : "");
echo "<p class='error'>" . lang('Error in query') . ($connection->errno ? " ($connection->errno)" : "") . ": " . error() . "\n";
echo "<p class='error'>" . lang('Error in query') . (!empty($connection->errno) ? " ($connection->errno)" : "") . ": " . error() . "\n";
$errors[] = " <a href='#sql-$commands'>$commands</a>";
if ($_POST["error_stops"]) {
break 2;
@@ -169,7 +183,8 @@ if (!$error && $_POST) {
stop_session();
}
if (!$_POST["only_errors"]) {
echo "<p class='message' title='" . h($connection->info) . "'>" . lang('Query executed OK, %d row(s) affected.', $affected) . "$time\n";
$title = isset($connection->info) ? "title='" . h($connection->info) . "'" : "";
echo "<p class='message' $title>" . lang('Query executed OK, %d row(s) affected.', $affected) . "$time\n";
}
}
echo ($warnings ? "<div id='$warnings_id' class='hidden'>\n$warnings</div>\n" : "");

View File

@@ -19,15 +19,18 @@ fieldset { display: inline; vertical-align: top; padding: .5em .8em; margin: .8e
p { margin: .8em 20px 0 0; }
img { vertical-align: middle; border: 0; }
td img { max-width: 200px; max-height: 200px; }
code { background: #eee; }
tbody tr:hover td, tbody tr:hover th { background: #eee; }
code { font-size: 110%; padding: 1px 2px; background: #eee; }
pre { margin: 1em 0 0; }
pre, textarea { font: 100%/1.25 monospace; }
pre code { display: block; font-size: 100%; }
pre, textarea { font: 110%/1.25 monospace; }
pre.jush { background: #fff; }
input, select { vertical-align: middle; }
input.default { box-shadow: 1px 1px 1px #777; }
input.required { box-shadow: 1px 1px 1px red; }
input.maxlength { box-shadow: 1px 1px 1px red; }
input.wayoff { left: -1000px; position: absolute; }
.center { text-align: center; }
.block { display: block; }
.version { color: #777; font-size: 67%; }
.js .hidden, .nojs .jsonly { display: none; }

View File

@@ -54,7 +54,7 @@ function bodyLoad(version, maria) {
function formField(form, name) {
// required in IE < 8, form.elements[name] doesn't work
for (var i=0; i < form.length; i++) {
if (form[i].name == name) {
if (form[i].name === name) {
return form[i];
}
}
@@ -104,7 +104,7 @@ var dbPrevious = {};
*/
function dbMouseDown(event) {
dbCtrl = isCtrl(event);
if (dbPrevious[this.name] == undefined) {
if (dbPrevious[this.name] === undefined) {
dbPrevious[this.name] = this.value;
}
}
@@ -118,7 +118,7 @@ function dbChange() {
}
this.form.submit();
this.form.target = '';
if (dbCtrl && dbPrevious[this.name] != undefined) {
if (dbCtrl && dbPrevious[this.name] !== undefined) {
this.value = dbPrevious[this.name];
dbPrevious[this.name] = undefined;
}
@@ -145,23 +145,23 @@ function selectFieldChange() {
for (var i=0; i < selects.length; i++) {
var select = selects[i];
var col = selectValue(select);
var match = /^(where.+)col\]/.exec(select.name);
var match = /^(where.+)col]/.exec(select.name);
if (match) {
var op = selectValue(form[match[1] + 'op]']);
var val = form[match[1] + 'val]'].value;
if (col in indexColumns && (!/LIKE|REGEXP/.test(op) || (op == 'LIKE' && val.charAt(0) != '%'))) {
if (col in indexColumns && (!/LIKE|REGEXP/.test(op) || (op === 'LIKE' && val.charAt(0) !== '%'))) {
return true;
} else if (col || val) {
ok = false;
}
}
if ((match = /^(columns.+)fun\]/.exec(select.name))) {
if ((match = /^(columns.+)fun]/.exec(select.name))) {
if (/^(avg|count|count distinct|group_concat|max|min|sum)$/.test(col)) {
group = true;
}
var val = selectValue(form[match[1] + 'col]']);
if (val) {
columns[col && col != 'count' ? '' : val] = 1;
columns[col && col !== 'count' ? '' : val] = 1;
}
}
if (col && /^order/.test(select.name)) {
@@ -194,7 +194,7 @@ var added = '.', rowCount;
* @return boolean
*/
function delimiterEqual(val, a, b) {
return (val == a + '_' + b || val == a + b || val == a + b.charAt(0).toUpperCase() + b.substr(1));
return (val === a + '_' + b || val === a + b || val === a + b.charAt(0).toUpperCase() + b.substr(1));
}
/** Escape string to use as identifier
@@ -255,7 +255,7 @@ function editingClick(event) {
} else if (/^drop_col\[/.test(name)) {
editingRemoveRow.call(el, 'fields\$1[field]');
} else {
if (name == 'auto_increment_col') {
if (name === 'auto_increment_col') {
var field = el.form['fields[' + el.value + '][field]'];
if (!field.value) {
field.value = 'id';
@@ -273,7 +273,7 @@ function editingClick(event) {
*/
function editingInput(event) {
var el = getTarget(event);
if (/\[default\]$/.test(el.name)) {
if (/\[default]$/.test(el.name)) {
el.previousSibling.checked = true;
}
}
@@ -290,7 +290,7 @@ function editingNameChange() {
for (var i = opts.length; i--; ) {
var match = /(.+)`(.+)/.exec(opts[i].value);
if (!match) { // common type
if (candidate && i == opts.length - 2 && val == opts[candidate].value.replace(/.+`/, '') && name == 'fields[1]') { // single target table, link to column, first field - probably `id`
if (candidate && i === opts.length - 2 && val === opts[candidate].value.replace(/.+`/, '') && name === 'fields[1]') { // single target table, link to column, first field - probably `id`
return;
}
break;
@@ -300,7 +300,7 @@ function editingNameChange() {
var tables = [ table, table.replace(/s$/, ''), table.replace(/es$/, '') ];
for (var j=0; j < tables.length; j++) {
table = tables[j];
if (val == column || val == table || delimiterEqual(val, table, column) || delimiterEqual(val, column, table)) {
if (val === column || val === table || delimiterEqual(val, table, column) || delimiterEqual(val, column, table)) {
if (candidate) {
return;
}
@@ -335,7 +335,7 @@ function editingAddRow(focus) {
tags2 = qsa('input', row2);
var input = tags2[0]; // IE loose tags2 after insertBefore()
for (var i=0; i < tags.length; i++) {
if (tags[i].name == 'auto_increment_col') {
if (tags[i].name === 'auto_increment_col') {
tags2[i].value = x;
tags2[i].checked = false;
}
@@ -397,7 +397,7 @@ function editingTypeChange() {
var text = selectValue(type);
for (var i=0; i < type.form.elements.length; i++) {
var el = type.form.elements[i];
if (el.name == name + '[length]') {
if (el.name === name + '[length]') {
if (!(
(/(char|binary)$/.test(lastType) && /(char|binary)$/.test(text))
|| (/(enum|set)$/.test(lastType) && /(enum|set)$/.test(text))
@@ -406,19 +406,19 @@ function editingTypeChange() {
}
el.oninput.apply(el);
}
if (lastType == 'timestamp' && el.name == name + '[has_default]' && /timestamp/i.test(formField(type.form, name + '[default]').value)) {
if (lastType === 'timestamp' && el.name == name + '[has_default]' && /timestamp/i.test(formField(type.form, name + '[default]').value)) {
el.checked = false;
}
if (el.name == name + '[collation]') {
if (el.name === name + '[collation]') {
alterClass(el, 'hidden', !/(char|text|enum|set)$/.test(text));
}
if (el.name == name + '[unsigned]') {
if (el.name === name + '[unsigned]') {
alterClass(el, 'hidden', !/(^|[^o])int(?!er)|numeric|real|float|double|decimal|money/.test(text));
}
if (el.name == name + '[on_update]') {
if (el.name === name + '[on_update]') {
alterClass(el, 'hidden', !/timestamp|datetime/.test(text)); // MySQL supports datetime since 5.6.5
}
if (el.name == name + '[on_delete]') {
if (el.name === name + '[on_delete]') {
alterClass(el, 'hidden', !/`/.test(text));
}
}
@@ -457,13 +457,13 @@ function enumValues(s) {
var offset = 0;
var match;
while (match = re.exec(s)) {
if (offset != match.index) {
if (offset !== match.index) {
break;
}
result.push(match[2].replace(/'(')|\\(.)/g, '$1$2'));
offset += match[0].length;
}
return (offset == s.length ? result.join('\n') : s);
return (offset === s.length ? result.join('\n') : s);
}
/** Finish editing of enum or set
@@ -531,7 +531,7 @@ function dumpClick(event) {
var el = parentTag(getTarget(event), 'label');
if (el) {
el = qs('input', el);
var match = /(.+)\[\]$/.exec(el.name);
var match = /(.+)\[]$/.exec(el.name);
if (match) {
checkboxClick.call(el, event);
formUncheck('check-' + match[1]);
@@ -549,7 +549,7 @@ function foreignAddRow() {
this.onchange = function () { };
var selects = qsa('select', row);
for (var i=0; i < selects.length; i++) {
selects[i].name = selects[i].name.replace(/\]/, '1$&');
selects[i].name = selects[i].name.replace(']', '1]');
selects[i].selectedIndex = 0;
}
parentTag(this, 'table').appendChild(row);
@@ -585,7 +585,7 @@ function indexesChangeColumn(prefix) {
for (var tag in { 'select': 1, 'input': 1 }) {
var columns = qsa(tag, parentTag(this, 'td'));
for (var i=0; i < columns.length; i++) {
if (/\[columns\]/.test(columns[i].name)) {
if (/\[columns]/.test(columns[i].name)) {
var value = selectValue(columns[i]);
if (value) {
names.push(value);
@@ -593,7 +593,7 @@ function indexesChangeColumn(prefix) {
}
}
}
this.form[this.name.replace(/\].*/, '][name]')].value = prefix + names.join('_');
this.form[this.name.replace(/].*/, '][name]')].value = prefix + names.join('_');
}
/** Add column for index
@@ -602,9 +602,9 @@ function indexesChangeColumn(prefix) {
*/
function indexesAddColumn(prefix) {
var field = this;
var select = field.form[field.name.replace(/\].*/, '][type]')];
var select = field.form[field.name.replace(/].*/, '][type]')];
if (!select.selectedIndex) {
while (selectValue(select) != "INDEX" && select.selectedIndex < select.options.length) {
while (selectValue(select) !== "INDEX" && select.selectedIndex < select.options.length) {
select.selectedIndex++;
}
select.onchange();
@@ -613,15 +613,15 @@ function indexesAddColumn(prefix) {
var selects = qsa('select', column);
for (var i = 0; i < selects.length; i++) {
select = selects[i];
select.name = select.name.replace(/\]\[\d+/, '$&1');
select.name = select.name.replace(/]\[\d+/, '$&1');
select.selectedIndex = 0;
}
field.onchange = partial(indexesChangeColumn, prefix);
var inputs = qsa('input', column);
for (var i = 0; i < inputs.length; i++) {
var input = inputs[i];
input.name = input.name.replace(/\]\[\d+/, '$&1');
if (input.type != 'checkbox') {
input.name = input.name.replace(/]\[\d+/, '$&1');
if (input.type !== 'checkbox') {
input.value = '';
}
}
@@ -670,7 +670,7 @@ var that, x, y; // em and tablePos defined in schema.inc.php
* @this HTMLElement
*/
function schemaMousedown(event) {
if ((event.which ? event.which : event.button) == 1) {
if ((event.which ? event.which : event.button) === 1) {
that = this;
x = event.clientX - this.offsetLeft;
y = event.clientY - this.offsetTop;
@@ -687,12 +687,12 @@ function schemaMousemove(event) {
var divs = qsa('div', that);
var lineSet = { };
for (var i=0; i < divs.length; i++) {
if (divs[i].className == 'references') {
if (divs[i].className === 'references') {
var div2 = qs('[id="' + (/^refs/.test(divs[i].id) ? 'refd' : 'refs') + divs[i].id.substr(4) + '"]');
var ref = (tablePos[divs[i].title] ? tablePos[divs[i].title] : [ div2.parentNode.offsetTop / em, 0 ]);
var left1 = -1;
var id = divs[i].id.replace(/^ref.(.+)-.+/, '$1');
if (divs[i].parentNode != div2.parentNode) {
if (divs[i].parentNode !== div2.parentNode) {
left1 = Math.min(0, ref[1] - left) - 1;
divs[i].style.left = left1 + 'em';
divs[i].querySelector('div').style.width = -left1 + 'em';
@@ -704,7 +704,7 @@ function schemaMousemove(event) {
var line = qs('[id="' + divs[i].id.replace(/^....(.+)-.+$/, 'refl$1') + '"]');
var top1 = top + divs[i].offsetTop / em;
var top2 = top + div2.offsetTop / em;
if (divs[i].parentNode != div2.parentNode) {
if (divs[i].parentNode !== div2.parentNode) {
top2 += ref[0] - top;
line.querySelector('div').style.height = Math.abs(top1 - top2) + 'em';
}
@@ -752,7 +752,7 @@ function helpMouseover(event, text, side) {
var target = getTarget(event);
if (!text) {
helpClose();
} else if (window.jush && (!helpIgnore || this != target)) {
} else if (window.jush && (!helpIgnore || this !== target)) {
helpOpen = 1;
var help = qs('#help');
help.innerHTML = text;
@@ -771,7 +771,7 @@ function helpMouseover(event, text, side) {
*/
function helpMouseout(event) {
helpOpen = 0;
helpIgnore = (this != getTarget(event));
helpIgnore = (this !== getTarget(event));
setTimeout(function () {
if (!helpOpen) {
helpClose();

View File

@@ -4,7 +4,7 @@
* @param [HTMLElement] defaults to document
* @return HTMLElement
*/
function qs(selector, context) {
function qs(selector, context = null) {
return (context || document).querySelector(selector);
}
@@ -13,7 +13,7 @@ function qs(selector, context) {
* @param [HTMLElement] defaults to document
* @return HTMLElement
*/
function qsl(selector, context) {
function qsl(selector, context = null) {
var els = qsa(selector, context);
return els[els.length - 1];
}
@@ -23,7 +23,7 @@ function qsl(selector, context) {
* @param [HTMLElement] defaults to document
* @return NodeList
*/
function qsa(selector, context) {
function qsa(selector, context = null) {
return (context || document).querySelectorAll(selector);
}
@@ -79,7 +79,7 @@ function alterClass(el, className, enable) {
*/
function toggle(id) {
var el = qs('#' + id);
el.className = (el.className == 'hidden' ? '' : 'hidden');
el.className = (el.className === 'hidden' ? '' : 'hidden');
return false;
}
@@ -111,7 +111,7 @@ function verifyVersion(current, url, token) {
if (window.postMessage && window.addEventListener) {
iframe.style.display = 'none';
addEventListener('message', function (event) {
if (event.origin == 'https://www.adminer.org') {
if (event.origin === 'https://www.adminer.org') {
var match = /version=(.+)/.exec(event.data);
if (match) {
cookie('adminer_version=' + match[1], 1);
@@ -139,7 +139,7 @@ function selectValue(select) {
/** Verify if element has a specified tag name
* @param HTMLElement
* @param string regular expression
* @return bool
* @return boolean
*/
function isTag(el, tag) {
var re = new RegExp('^(' + tag + ')$', 'i');
@@ -181,8 +181,8 @@ function selectCount(id, count) {
var inputs = qsa('input', el.parentNode.parentNode);
for (var i = 0; i < inputs.length; i++) {
var input = inputs[i];
if (input.type == 'submit') {
input.disabled = (count == '0');
if (input.type === 'submit') {
input.disabled = (count === '0');
}
}
}
@@ -211,13 +211,21 @@ function tableCheck() {
}
}
/** Uncheck single element
* @param string
*/
/**
* Uncheck single element.
*/
function formUncheck(id) {
var el = qs('#' + id);
el.checked = false;
trCheck(el);
formUncheckAll("#" + id);
}
/**
* Uncheck elements matched by selector.
*/
function formUncheckAll(selector) {
for (const element of qsa(selector)) {
element.checked = false;
trCheck(element);
}
}
/** Get number of checked elements matching given name
@@ -252,7 +260,7 @@ function tableClick(event, click) {
var el = getTarget(event);
while (!isTag(el, 'tr')) {
if (isTag(el, 'table|a|input|textarea')) {
if (el.type != 'checkbox') {
if (el.type !== 'checkbox') {
return;
}
checkboxClick.call(el, event);
@@ -268,7 +276,7 @@ function tableClick(event, click) {
el.checked = !el.checked;
el.onclick && el.onclick();
}
if (el.name == 'check[]') {
if (el.name === 'check[]') {
el.form['all'].checked = false;
formUncheck('all-page');
}
@@ -288,7 +296,7 @@ function checkboxClick(event) {
if (!this.name) {
return;
}
if (event.shiftKey && (!lastChecked || lastChecked.name == this.name)) {
if (event.shiftKey && (!lastChecked || lastChecked.name === this.name)) {
var checked = (lastChecked ? lastChecked.checked : true);
var inputs = qsa('input', parentTag(this, 'table'));
var checking = !lastChecked;
@@ -333,7 +341,7 @@ function setHtml(id, html) {
*/
function nodePosition(el) {
var pos = 0;
while (el = el.previousSibling) {
while ((el = el.previousSibling)) {
pos++;
}
return pos;
@@ -345,7 +353,7 @@ function nodePosition(el) {
*/
function pageClick(href, page) {
if (!isNaN(page) && page) {
location.href = href + (page != 1 ? '&page=' + (page - 1) : '');
location.href = href + (page !== 1 ? '&page=' + (page - 1) : '');
}
}
@@ -388,7 +396,7 @@ function selectAddRow() {
for (var i=0; i < inputs.length; i++) {
inputs[i].name = inputs[i].name.replace(/[a-z]\[\d+/, '$&1');
inputs[i].className = '';
if (inputs[i].type == 'checkbox') {
if (inputs[i].type === 'checkbox') {
inputs[i].checked = false;
} else {
inputs[i].value = '';
@@ -402,7 +410,7 @@ function selectAddRow() {
* @this HTMLInputElement
*/
function selectSearchKeydown(event) {
if (event.keyCode == 13 || event.keyCode == 10) {
if (event.keyCode === 13 || event.keyCode === 10) {
this.onsearch = function () {
};
}
@@ -445,11 +453,11 @@ function selectSearch(name) {
for (var i=0; i < divs.length; i++) {
var div = divs[i];
var el = qs('[name$="[col]"]', div);
if (el && selectValue(el) == name) {
if (el && selectValue(el) === name) {
break;
}
}
if (i == divs.length) {
if (i === divs.length) {
div.firstChild.value = name;
div.firstChild.onchange();
}
@@ -487,7 +495,7 @@ function bodyKeydown(event, button) {
if (target.jushTextarea) {
target = target.jushTextarea;
}
if (isCtrl(event) && (event.keyCode == 13 || event.keyCode == 10) && isTag(target, 'select|textarea|input')) { // 13|10 - Enter
if (isCtrl(event) && (event.keyCode === 13 || event.keyCode === 10) && isTag(target, 'select|textarea|input')) { // 13|10 - Enter
target.blur();
if (button) {
target.form[button].click();
@@ -508,7 +516,7 @@ function bodyKeydown(event, button) {
*/
function bodyClick(event) {
var target = getTarget(event);
if ((isCtrl(event) || event.shiftKey) && target.type == 'submit' && isTag(target, 'input')) {
if ((isCtrl(event) || event.shiftKey) && target.type === 'submit' && isTag(target, 'input')) {
target.form.target = '_blank';
setTimeout(function () {
// if (isCtrl(event)) { focus(); } doesn't work
@@ -524,9 +532,9 @@ function bodyClick(event) {
* @return boolean
*/
function editingKeydown(event) {
if ((event.keyCode == 40 || event.keyCode == 38) && isCtrl(event)) { // 40 - Down, 38 - Up
if ((event.keyCode === 40 || event.keyCode === 38) && isCtrl(event)) { // 40 - Down, 38 - Up
var target = getTarget(event);
var sibling = (event.keyCode == 40 ? 'nextSibling' : 'previousSibling');
var sibling = (event.keyCode === 40 ? 'nextSibling' : 'previousSibling');
var el = target.parentNode.parentNode[sibling];
if (el && (isTag(el, 'tr') || (el = el[sibling])) && isTag(el, 'tr') && (el = el.childNodes[nodePosition(target.parentNode)]) && (el = el.childNodes[nodePosition(target)])) {
el.focus();
@@ -614,7 +622,7 @@ function ajax(url, callback, data, message) {
}
request.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
request.onreadystatechange = function () {
if (request.readyState == 4) {
if (request.readyState === 4) {
if (/^2/.test(request.status)) {
callback(request);
} else {
@@ -656,7 +664,7 @@ function ajaxForm(form, message, button) {
if (/^file$/i.test(el.type) && el.value) {
return false;
}
if (!/^(checkbox|radio|submit|file)$/i.test(el.type) || el.checked || el == button) {
if (!/^(checkbox|radio|submit|file)$/i.test(el.type) || el.checked || el === button) {
data.push(encodeURIComponent(el.name) + '=' + encodeURIComponent(isTag(el, 'select') ? selectValue(el) : el.value));
}
}
@@ -703,7 +711,7 @@ function selectClick(event, text, warning) {
if (!event) {
event = window.event;
}
if (event.keyCode == 27 && !event.shiftKey && !event.altKey && !isCtrl(event)) { // 27 - Esc
if (event.keyCode === 27 && !event.shiftKey && !event.altKey && !isCtrl(event)) { // 27 - Esc
inputBlur.apply(input);
td.innerHTML = original;
}
@@ -733,7 +741,7 @@ function selectClick(event, text, warning) {
td.appendChild(input);
setupSubmitHighlight(td);
input.focus();
if (text == 2) { // long text
if (text === 2) { // long text
return ajax(location.href + '&' + encodeURIComponent(td.id) + '=', function (request) {
if (request.responseText) {
input.value = request.responseText;
@@ -855,7 +863,7 @@ function findDefaultSubmit(el) {
var inputs = qsa('input', el.form);
for (var i = 0; i < inputs.length; i++) {
var input = inputs[i];
if (input.type == 'submit' && !input.style.zIndex) {
if (input.type === 'submit' && !input.style.zIndex) {
return input;
}
}

View File

@@ -85,8 +85,8 @@ if ($_POST && !$error) {
unset($grants[$object]);
}
if (preg_match('~^(.+)\s*(\(.*\))?$~U', $object, $match) && (
!grant("REVOKE", $revoke, $match[2], " ON $match[1] FROM $new_user") //! SQL injection
|| !grant("GRANT", $grant, $match[2], " ON $match[1] TO $new_user")
!grant(false, $revoke, $match[2], $match[1], $new_user) //! SQL injection
|| !grant(true, $grant, $match[2], $match[1], $new_user)
)) {
$error = true;
break;
@@ -100,7 +100,7 @@ if ($_POST && !$error) {
} elseif (!isset($_GET["grant"])) {
foreach ($grants as $object => $revoke) {
if (preg_match('~^(.+)(\(.*\))?$~U', $object, $match)) {
grant("REVOKE", array_keys($revoke), $match[2], " ON $match[1] FROM $new_user");
grant(false, array_keys($revoke), $match[2], $match[1], $new_user);
}
}
}
@@ -150,33 +150,63 @@ foreach ($grants as $object => $grant) {
}
echo "</thead>\n";
foreach (array(
foreach ([
"" => "",
"Server Admin" => lang('Server'),
"Databases" => lang('Database'),
"Tables" => lang('Table'),
"Columns" => lang('Column'),
"Procedures" => lang('Routine'),
) as $context => $desc) {
] as $context => $desc) {
foreach ((array) $privileges[$context] as $privilege => $comment) {
echo "<tr" . odd() . "><td" . ($desc ? ">$desc<td" : " colspan='2'") . ' lang="en" title="' . h($comment) . '">' . h($privilege);
echo "<tr" . odd() . ">";
if ($desc) {
echo "<td>$desc</td>";
}
echo "<td" . (!$desc ? " colspan='2'" : "") . ' lang="en" title="' . h($comment) . '">' . h($privilege) . "</td>";
$i = 0;
foreach ($grants as $object => $grant) {
$name = "'grants[$i][" . h(strtoupper($privilege)) . "]'";
$value = $grant[strtoupper($privilege)];
if ($context == "Server Admin" && $object != (isset($grants["*.*"]) ? "*.*" : ".*")) {
echo "<td>";
$proxiedUser = strpos($object, "@") !== false;
$newObject = $object == ".*";
$allPrivileges = $privilege == "All privileges";
$grantOption = $privilege == "Grant option";
if ($object == "*.*" && $privilege == "Proxy") {
echo "<td></td>";
} elseif ($proxiedUser && $privilege != "Proxy" && !$grantOption) {
echo "<td></td>";
} elseif ($context == "Server Admin" && $object != (isset($grants["*.*"]) ? "*.*" : ".*") && !(($proxiedUser || $newObject) && $privilege == "Proxy")) {
echo "<td></td>";
} elseif (isset($_GET["grant"])) {
echo "<td><select name=$name><option><option value='1'" . ($value ? " selected" : "") . ">" . lang('Grant') . "<option value='0'" . ($value == "0" ? " selected" : "") . ">" . lang('Revoke') . "</select>";
echo "<td><select name=$name>" .
"<option></option>" .
"<option value='1'" . ($value ? " selected" : "") . ">" . lang('Grant') . "</option>" .
"<option value='0'" . ($value == "0" ? " selected" : "") . ">" . lang('Revoke') . "</option>" .
"</select></td>";
} else {
echo "<td align='center'><label class='block'>";
echo "<input type='checkbox' name=$name value='1'" . ($value ? " checked" : "") . ($privilege == "All privileges"
? " id='grants-$i-all'>" //! uncheck all except grant if all is checked
: ">" . ($privilege == "Grant option" ? "" : script("qsl('input').onclick = function () { if (this.checked) formUncheck('grants-$i-all'); };")));
echo "<td class='center'><label class='block'>";
echo "<input type='checkbox' name=$name value='1'" .
($value ? " checked" : "") .
($allPrivileges ? " id='grants-$i-all'" : (!$grantOption ? " class='grants-$i'" : "")) .
">";
if ($allPrivileges) {
echo script("qsl('input').onclick = function () { if (this.checked) formUncheckAll('.grants-$i'); };");
} elseif (!$grantOption) {
echo script("qsl('input').onclick = function () { if (this.checked) formUncheck('grants-$i-all'); };");
}
echo "</label>";
}
$i++;
}
echo "</tr>";
}
}

View File

@@ -1,3 +1,29 @@
Adminer 4.9.3 (released 2024-10-02):
- MySQL, PostgreSQL: Fix queries splitting and string constants.
- MySQL: Fix where clause for JSON column.
- MySQL: Fix editing user's proxy privilege, refactoring.
- MariaDB: Fix comparing CURRENT_TIMESTAMP definition while altering a table.
- PostgreSQL: Fix editing record that contains a field with GENERATED ALWAYS default value.
- Fix using undefined Min_DB::info property.
- Do not include unchanged PARTITION BY definition into ALTER TABLE query.
- Do not limit unlimited memory while executing queries.
- Fix number conversion warning while reading INI settings.
- Hide invalid edit form if table record is not found.
- CSS: Fix background color of <pre> used as edit field.
- CSS: Bigger font size for code blocks.
Adminer 4.9.2 (released 2024-09-18):
- Fix textarea height for single-line inputs (used typically for SQLite text field).
- Fix undefined property in error message if driver does not support error number (e.g. PostgreSQL).
- PostgreSQL: Fix search fields configuration (regression from 4.9).
- PostgreSQL: Fix search condition for network address types, add macaddr8 type.
- PostgreSQL: Fix exporting CREATE TABLE query with GENERATED default values.
- PostgreSQL: Fix exporting CREATE TABLE query with sequence default value.
- PostgreSQL: Allow to set connection's sslmode with AdminerLoginSsl plugin.
- MySQL: Do not show 'empty' enum value in strict mode.
- Editor: Fix searching in tables.
- Function to retrieve driver name that can be used in plugins.
Adminer 4.9.1 (released 2024-09-09):
- Compatibility with PHP 8.3.
- Fix compiling jush external files.

View File

@@ -342,35 +342,44 @@ ORDER BY ORDINAL_POSITION", null, "") as $row) { //! requires MySQL 5
function selectSearchProcess($fields, $indexes) {
global $driver;
$return = array();
$return = [];
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) || !preg_match(number_type(), $field["type"])) {
$name = idf_escape($name);
if ($col != "" && $field["type"] == "enum") {
$conds[] = (in_array(0, $val) ? "$name IS NULL OR " : "") . "$name IN (" . implode(", ", array_map('intval', $val)) . ")";
} else {
$text_type = preg_match('~char|text|enum|set~', $field["type"]);
$value = $this->processInput($field, (!$op && $text_type && preg_match('~^[^%]+$~', $val) ? "%$val%" : $val));
$conds[] = $driver->convertSearch($name, $val, $field) . ($value == "NULL" ? " IS" . ($op == ">=" ? " NOT" : "") . " $value"
$conds[] = $driver->convertSearch($name, $where, $field) . ($value == "NULL" ? " IS" . ($op == ">=" ? " NOT" : "") . " $value"
: (in_array($op, $this->operators) || $op == "=" ? " $op $value"
: ($text_type ? " LIKE $value"
: " IN (" . str_replace(",", "', '", $value) . ")"
)));
if ($key < 0 && $val == "0") {
$conds[] = "$name IS NULL";
}
}
}
}
$return[] = ($conds ? "(" . implode(" OR ", $conds) . ")" : "1 = 0");
}
}
return $return;
}

View File

@@ -10,7 +10,7 @@ class AdminerEditTextarea {
function editInput($table, $field, $attrs, $value) {
if (preg_match('~char~', $field["type"])) {
return "<textarea cols='30' rows='1' style='height: 1.2em;'$attrs>" . h($value) . '</textarea>';
return "<textarea cols='30' rows='1' $attrs>" . h($value) . '</textarea>';
}
}

View File

@@ -24,7 +24,9 @@ class AdminerEnumOption {
$selected = "";
}
}
$options[0] = lang('empty');
if (!is_strict_mode()) {
$options[0] = lang('empty');
}
preg_match_all("~'((?:[^']|'')*)'~", $field["length"], $matches);
foreach ($matches[1] as $i => $val) {
$val = stripcslashes(str_replace("''", "'", $val));

View File

@@ -1,24 +1,28 @@
<?php
/** Connect to MySQL using SSL
* @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 AdminerLoginSsl {
/** @access protected */
var $ssl;
/**
* Connect to MySQL using SSL
*
* @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 AdminerLoginSsl
{
private var $ssl;
/**
* @param array array("key" => filename, "cert" => filename, "ca" => filename)
*/
function __construct($ssl) {
* MySQL: ["key" => filename, "cert" => filename, "ca" => filename]
* PostgresSQL: ["mode" => sslmode] (https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNECT-SSLMODE)
*/
function __construct(array $ssl)
{
$this->ssl = $ssl;
}
function connectSsl() {
function connectSsl()
{
return $this->ssl;
}
}