Merge branch 'QA_4_9-security' into QA_5_1-security
Signed-off-by: Maurício Meneghini Fauth <mauricio@fauth.dev>
This commit is contained in:
commit
02e8588404
@ -67,7 +67,7 @@
|
||||
"pragmarx/google2fa-qrcode": "<1.0.1"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-openssl": "Cookie encryption",
|
||||
"ext-openssl": "For encryption performance",
|
||||
"ext-curl": "Updates checking",
|
||||
"ext-opcache": "Better performance",
|
||||
"ext-zlib": "For gz import and export",
|
||||
@ -77,9 +77,12 @@
|
||||
"ext-mbstring": "For best performance",
|
||||
"tecnickcom/tcpdf": "For PDF support",
|
||||
"pragmarx/google2fa-qrcode": "For 2FA authentication",
|
||||
"code-lts/u2f-php-server": "For FIDO U2F authentication"
|
||||
"code-lts/u2f-php-server": "For FIDO U2F authentication",
|
||||
"paragonie/sodium_compat": "For modern encryption support"
|
||||
},
|
||||
"require-dev": {
|
||||
"code-lts/u2f-php-server": "^1.2",
|
||||
"paragonie/sodium_compat": "^1.17",
|
||||
"php-webdriver/webdriver": "^1.11",
|
||||
"phpmyadmin/coding-standard": "^2.1.1",
|
||||
"phpstan/extension-installer": "^1.1",
|
||||
@ -87,7 +90,6 @@
|
||||
"phpstan/phpstan-phpunit": "^1.0",
|
||||
"phpunit/phpunit": "^7.5 || ^8.0 || ^9.0",
|
||||
"pragmarx/google2fa-qrcode": "^1.0.1",
|
||||
"code-lts/u2f-php-server": "^1.2",
|
||||
"symfony/console": "^4.4",
|
||||
"symfony/finder": "^4.4",
|
||||
"symfony/twig-bridge": "^4.4",
|
||||
|
||||
@ -147,6 +147,13 @@ $cfg['SaveDir'] = '';
|
||||
*/
|
||||
//$cfg['SendErrorReports'] = 'always';
|
||||
|
||||
/**
|
||||
* 'URLQueryEncryption' defines whether phpMyAdmin will encrypt sensitive data from the URL query string.
|
||||
* 'URLQueryEncryptionSecretKey' is a 32 bytes long secret key used to encrypt/decrypt the URL query string.
|
||||
*/
|
||||
//$cfg['URLQueryEncryption'] = true;
|
||||
//$cfg['URLQueryEncryptionSecretKey'] = '';
|
||||
|
||||
/**
|
||||
* You can find more configuration options in the documentation
|
||||
* in the doc/ folder or at <https://docs.phpmyadmin.net/>.
|
||||
|
||||
@ -1498,6 +1498,20 @@ Server connection settings
|
||||
after logout (doesn't affect config authentication method). Should be
|
||||
absolute including protocol.
|
||||
|
||||
.. config:option:: $cfg['Servers'][$i]['hide_connection_errors']
|
||||
|
||||
:type: boolean
|
||||
:default: false
|
||||
|
||||
.. versionadded:: 4.9.8
|
||||
|
||||
Whether to show or hide detailed MySQL/MariaDB connection errors on the login page.
|
||||
|
||||
.. note::
|
||||
|
||||
This error message can contain the target database server hostname or IP address,
|
||||
which may reveal information about your network to an attacker.
|
||||
|
||||
Generic settings
|
||||
----------------
|
||||
|
||||
@ -1787,6 +1801,27 @@ Generic settings
|
||||
When enabled, a user can drag a file in to their browser and phpMyAdmin will
|
||||
attempt to import the file.
|
||||
|
||||
.. config:option:: $cfg['URLQueryEncryption']
|
||||
|
||||
:type: boolean
|
||||
:default: false
|
||||
|
||||
.. versionadded:: 4.9.8
|
||||
|
||||
Define whether phpMyAdmin will encrypt sensitive data (like database name
|
||||
and table name) from the URL query string. Default is to not encrypt the URL
|
||||
query string.
|
||||
|
||||
.. config:option:: $cfg['URLQueryEncryptionSecretKey']
|
||||
|
||||
:type: string
|
||||
:default: ``''``
|
||||
|
||||
.. versionadded:: 4.9.8
|
||||
|
||||
A secret key used to encrypt/decrypt the URL query string.
|
||||
Should be 32 bytes long.
|
||||
|
||||
Cookie authentication options
|
||||
-----------------------------
|
||||
|
||||
|
||||
@ -163,7 +163,7 @@ Navigation.loadChildNodes = function (isNode, $expandElem, callback) {
|
||||
};
|
||||
}
|
||||
|
||||
$.get('index.php?route=/navigation&ajax_request=1', params, function (data) {
|
||||
$.post('index.php?route=/navigation&ajax_request=1', params, function (data) {
|
||||
if (typeof data !== 'undefined' && data.success === true) {
|
||||
$destination.find('div.list_container').remove(); // FIXME: Hack, there shouldn't be a list container there
|
||||
if (isNode) {
|
||||
|
||||
@ -71,6 +71,7 @@ use function trigger_error;
|
||||
use function unserialize;
|
||||
use function urldecode;
|
||||
use function vsprintf;
|
||||
use function json_decode;
|
||||
|
||||
/**
|
||||
* Core class
|
||||
@ -1426,4 +1427,37 @@ class Core
|
||||
|
||||
return $containerBuilder;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public static function populateRequestWithEncryptedQueryParams()
|
||||
{
|
||||
if (
|
||||
(! isset($_GET['eq']) || ! is_string($_GET['eq']))
|
||||
&& (! isset($_POST['eq']) || ! is_string($_POST['eq']))
|
||||
) {
|
||||
unset($_GET['eq'], $_POST['eq'], $_REQUEST['eq']);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$isFromPost = isset($_POST['eq']);
|
||||
$decryptedQuery = Url::decryptQuery($isFromPost ? $_POST['eq'] : $_GET['eq']);
|
||||
unset($_GET['eq'], $_POST['eq'], $_REQUEST['eq']);
|
||||
if ($decryptedQuery === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$urlQueryParams = (array) json_decode($decryptedQuery);
|
||||
foreach ($urlQueryParams as $urlQueryParamKey => $urlQueryParamValue) {
|
||||
if ($isFromPost) {
|
||||
$_POST[$urlQueryParamKey] = $urlQueryParamValue;
|
||||
} else {
|
||||
$_GET[$urlQueryParamKey] = $urlQueryParamValue;
|
||||
}
|
||||
|
||||
$_REQUEST[$urlQueryParamKey] = $urlQueryParamValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
175
libraries/classes/Crypto/Crypto.php
Normal file
175
libraries/classes/Crypto/Crypto.php
Normal file
@ -0,0 +1,175 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Crypto;
|
||||
|
||||
use phpseclib\Crypt\AES;
|
||||
use phpseclib\Crypt\Random;
|
||||
use Throwable;
|
||||
use function is_callable;
|
||||
use function defined;
|
||||
use const SODIUM_CRYPTO_SECRETBOX_KEYBYTES;
|
||||
use function is_string;
|
||||
use function mb_strlen;
|
||||
use function random_bytes;
|
||||
use function hash_hmac;
|
||||
use function mb_substr;
|
||||
use function hash_equals;
|
||||
use const SODIUM_CRYPTO_SECRETBOX_NONCEBYTES;
|
||||
use function sodium_crypto_secretbox;
|
||||
use function sodium_crypto_secretbox_open;
|
||||
|
||||
final class Crypto
|
||||
{
|
||||
/** @var bool */
|
||||
private $hasRandomBytesSupport;
|
||||
|
||||
/** @var bool */
|
||||
private $hasSodiumSupport;
|
||||
|
||||
/**
|
||||
* @param bool $forceFallback Force the usage of the fallback functions.
|
||||
*/
|
||||
public function __construct($forceFallback = false)
|
||||
{
|
||||
$this->hasRandomBytesSupport = ! $forceFallback && is_callable('random_bytes');
|
||||
$this->hasSodiumSupport = ! $forceFallback
|
||||
&& $this->hasRandomBytesSupport
|
||||
&& is_callable('sodium_crypto_secretbox')
|
||||
&& is_callable('sodium_crypto_secretbox_open')
|
||||
&& defined('SODIUM_CRYPTO_SECRETBOX_NONCEBYTES')
|
||||
&& defined('SODIUM_CRYPTO_SECRETBOX_KEYBYTES');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $plaintext
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function encrypt($plaintext)
|
||||
{
|
||||
if ($this->hasSodiumSupport) {
|
||||
return $this->encryptWithSodium($plaintext);
|
||||
}
|
||||
|
||||
return $this->encryptWithPhpseclib($plaintext);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $ciphertext
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function decrypt($ciphertext)
|
||||
{
|
||||
if ($this->hasSodiumSupport) {
|
||||
return $this->decryptWithSodium($ciphertext);
|
||||
}
|
||||
|
||||
return $this->decryptWithPhpseclib($ciphertext);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
private function getEncryptionKey()
|
||||
{
|
||||
global $PMA_Config;
|
||||
|
||||
$keyLength = $this->hasSodiumSupport ? SODIUM_CRYPTO_SECRETBOX_KEYBYTES : 32;
|
||||
|
||||
$key = $PMA_Config->get('URLQueryEncryptionSecretKey');
|
||||
if (is_string($key) && mb_strlen($key, '8bit') === $keyLength) {
|
||||
return $key;
|
||||
}
|
||||
|
||||
$key = $_SESSION['URLQueryEncryptionSecretKey'] ?? null;
|
||||
if (is_string($key) && mb_strlen($key, '8bit') === $keyLength) {
|
||||
return $key;
|
||||
}
|
||||
|
||||
$key = $this->hasRandomBytesSupport ? random_bytes($keyLength) : Random::string($keyLength);
|
||||
$_SESSION['URLQueryEncryptionSecretKey'] = $key;
|
||||
|
||||
return $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $plaintext
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function encryptWithPhpseclib($plaintext)
|
||||
{
|
||||
$key = $this->getEncryptionKey();
|
||||
$cipher = new AES(AES::MODE_CBC);
|
||||
$iv = $this->hasRandomBytesSupport ? random_bytes(16) : Random::string(16);
|
||||
$cipher->setIV($iv);
|
||||
$cipher->setKey($key);
|
||||
$ciphertext = $cipher->encrypt($plaintext);
|
||||
$hmac = hash_hmac('sha256', $iv . $ciphertext, $key, true);
|
||||
|
||||
return $hmac . $iv . $ciphertext;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $encrypted
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
private function decryptWithPhpseclib($encrypted)
|
||||
{
|
||||
$key = $this->getEncryptionKey();
|
||||
$hmac = mb_substr($encrypted, 0, 32, '8bit');
|
||||
$iv = mb_substr($encrypted, 32, 16, '8bit');
|
||||
$ciphertext = mb_substr($encrypted, 48, null, '8bit');
|
||||
$calculatedHmac = hash_hmac('sha256', $iv . $ciphertext, $key, true);
|
||||
if (! hash_equals($hmac, $calculatedHmac)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$cipher = new AES(AES::MODE_CBC);
|
||||
$cipher->setIV($iv);
|
||||
$cipher->setKey($key);
|
||||
|
||||
return $cipher->decrypt($ciphertext);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $plaintext
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function encryptWithSodium($plaintext)
|
||||
{
|
||||
$key = $this->getEncryptionKey();
|
||||
$nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
|
||||
$ciphertext = sodium_crypto_secretbox($plaintext, $nonce, $key);
|
||||
|
||||
return $nonce . $ciphertext;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $encrypted
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
private function decryptWithSodium($encrypted)
|
||||
{
|
||||
$key = $this->getEncryptionKey();
|
||||
$nonce = mb_substr($encrypted, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, '8bit');
|
||||
$ciphertext = mb_substr($encrypted, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, null, '8bit');
|
||||
try {
|
||||
$decrypted = sodium_crypto_secretbox_open($ciphertext, $nonce, $key);
|
||||
} catch (Throwable $e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($decrypted === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $decrypted;
|
||||
}
|
||||
}
|
||||
@ -75,6 +75,8 @@ use function mysqli_init;
|
||||
use function stripos;
|
||||
use function trigger_error;
|
||||
use function mysqli_get_client_info;
|
||||
use function sprintf;
|
||||
use const E_USER_ERROR;
|
||||
|
||||
/**
|
||||
* Interface to the MySQL Improved extension (MySQLi)
|
||||
@ -163,15 +165,27 @@ class DbiMysqli implements DbiExtension
|
||||
$host = $server['host'];
|
||||
}
|
||||
|
||||
$return_value = $mysqli->real_connect(
|
||||
$host,
|
||||
$user,
|
||||
$password,
|
||||
'',
|
||||
$server['port'],
|
||||
(string) $server['socket'],
|
||||
$client_flags
|
||||
);
|
||||
if ($server['hide_connection_errors']) {
|
||||
$return_value = @$mysqli->real_connect(
|
||||
$host,
|
||||
$user,
|
||||
$password,
|
||||
'',
|
||||
$server['port'],
|
||||
(string) $server['socket'],
|
||||
$client_flags
|
||||
);
|
||||
} else {
|
||||
$return_value = $mysqli->real_connect(
|
||||
$host,
|
||||
$user,
|
||||
$password,
|
||||
'',
|
||||
$server['port'],
|
||||
(string) $server['socket'],
|
||||
$client_flags
|
||||
);
|
||||
}
|
||||
|
||||
if ($return_value === false || $return_value === null) {
|
||||
/*
|
||||
@ -198,6 +212,20 @@ class DbiMysqli implements DbiExtension
|
||||
return self::connect($user, $password, $server);
|
||||
}
|
||||
|
||||
if ($error_number === 1045 && $server['hide_connection_errors']) {
|
||||
trigger_error(
|
||||
sprintf(
|
||||
__(
|
||||
'Error 1045: Access denied for user. Additional error information'
|
||||
. ' may be available, but is being hidden by the %s configuration directive.'
|
||||
),
|
||||
'[code][doc@cfg_Servers_hide_connection_errors]'
|
||||
. '$cfg[\'Servers\'][$i][\'hide_connection_errors\'][/doc][/code]'
|
||||
),
|
||||
E_USER_ERROR
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@ -1559,9 +1559,8 @@ class Results
|
||||
|
||||
$tmp_image = '<img class="fulltext" src="' . $tmp_image_file . '" alt="'
|
||||
. $tmp_txt . '" title="' . $tmp_txt . '">';
|
||||
$tmp_url = Url::getFromRoute('/sql', $url_params_full_text);
|
||||
|
||||
return Generator::linkOrButton($tmp_url, $tmp_image);
|
||||
return Generator::linkOrButton(Url::getFromRoute('/sql'), $url_params_full_text, $tmp_image);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1677,16 +1676,14 @@ class Results
|
||||
'session_max_rows' => $session_max_rows,
|
||||
'is_browse_distinct' => $this->properties['is_browse_distinct'],
|
||||
];
|
||||
$single_order_url = Url::getFromRoute('/sql', $_single_url_params);
|
||||
$multi_order_url = Url::getFromRoute('/sql', $_multi_url_params);
|
||||
|
||||
// Displays the sorting URL
|
||||
// enable sort order swapping for image
|
||||
$order_link = $this->getSortOrderLink(
|
||||
$order_img,
|
||||
$fields_meta,
|
||||
$single_order_url,
|
||||
$multi_order_url
|
||||
$_single_url_params,
|
||||
$_multi_url_params
|
||||
);
|
||||
|
||||
$order_link .= $this->getSortOrderHiddenInputs(
|
||||
@ -1996,10 +1993,10 @@ class Results
|
||||
*
|
||||
* @see getTableHeaders()
|
||||
*
|
||||
* @param string $order_img the sort order image
|
||||
* @param stdClass $fields_meta set of field properties
|
||||
* @param string $order_url the url for sort
|
||||
* @param string $multi_order_url the url for sort
|
||||
* @param string $order_img the sort order image
|
||||
* @param stdClass $fields_meta set of field properties
|
||||
* @param array $order_url_params the url params for sort
|
||||
* @param array $multi_order_url_params the url params for sort
|
||||
*
|
||||
* @return string the sort order link
|
||||
*
|
||||
@ -2008,17 +2005,21 @@ class Results
|
||||
private function getSortOrderLink(
|
||||
$order_img,
|
||||
$fields_meta,
|
||||
$order_url,
|
||||
$multi_order_url
|
||||
$order_url_params,
|
||||
$multi_order_url_params
|
||||
) {
|
||||
$order_link_params = ['class' => 'sortlink'];
|
||||
|
||||
$order_link_content = htmlspecialchars($fields_meta->name ?? '');
|
||||
$inner_link_content = $order_link_content . $order_img
|
||||
. '<input type="hidden" value="' . $multi_order_url . '">';
|
||||
. '<input type="hidden" value="'
|
||||
. Url::getFromRoute('/sql')
|
||||
. Url::getCommon($multi_order_url_params, '?', false)
|
||||
. '">';
|
||||
|
||||
return Generator::linkOrButton(
|
||||
$order_url,
|
||||
Url::getFromRoute('/sql'),
|
||||
$order_url_params,
|
||||
$inner_link_content,
|
||||
$order_link_params
|
||||
);
|
||||
@ -2519,6 +2520,8 @@ class Results
|
||||
$copy_url = null;
|
||||
$copy_str = null;
|
||||
$edit_url = null;
|
||||
$editCopyUrlParams = null;
|
||||
$delUrlParams = null;
|
||||
|
||||
// 1.2 Defines the URLs for the modify/delete link(s)
|
||||
|
||||
@ -2559,6 +2562,7 @@ class Results
|
||||
$copy_url,
|
||||
$edit_str,
|
||||
$copy_str,
|
||||
$editCopyUrlParams,
|
||||
]
|
||||
= $this->getModifiedLinks(
|
||||
$where_clause,
|
||||
@ -2568,7 +2572,7 @@ class Results
|
||||
}
|
||||
|
||||
// 1.2.2 Delete/Kill link(s)
|
||||
[$del_url, $del_str, $js_conf]
|
||||
[$del_url, $del_str, $js_conf, $delUrlParams]
|
||||
= $this->getDeleteAndKillLinks(
|
||||
$where_clause,
|
||||
$clause_is_unique,
|
||||
@ -2584,9 +2588,18 @@ class Results
|
||||
$table_body_html .= $this->template->render('display/results/checkbox_and_links', [
|
||||
'position' => self::POSITION_LEFT,
|
||||
'has_checkbox' => ! empty($del_url) && $displayParts['del_lnk'] !== self::KILL_PROCESS,
|
||||
'edit' => ['url' => $edit_url, 'string' => $edit_str, 'clause_is_unique' => $clause_is_unique],
|
||||
'copy' => ['url' => $copy_url, 'string' => $copy_str],
|
||||
'delete' => ['url' => $del_url, 'string' => $del_str],
|
||||
'edit' => [
|
||||
'url' => $edit_url,
|
||||
'params' => $editCopyUrlParams + ['default_action' => 'update'],
|
||||
'string' => $edit_str,
|
||||
'clause_is_unique' => $clause_is_unique,
|
||||
],
|
||||
'copy' => [
|
||||
'url' => $copy_url,
|
||||
'params' => $editCopyUrlParams + ['default_action' => 'insert'],
|
||||
'string' => $copy_str,
|
||||
],
|
||||
'delete' => ['url' => $del_url, 'params' => $delUrlParams, 'string' => $del_str],
|
||||
'row_number' => $row_no,
|
||||
'where_clause' => $where_clause,
|
||||
'condition' => json_encode($condition_array),
|
||||
@ -2597,9 +2610,18 @@ class Results
|
||||
$table_body_html .= $this->template->render('display/results/checkbox_and_links', [
|
||||
'position' => self::POSITION_NONE,
|
||||
'has_checkbox' => ! empty($del_url) && $displayParts['del_lnk'] !== self::KILL_PROCESS,
|
||||
'edit' => ['url' => $edit_url, 'string' => $edit_str, 'clause_is_unique' => $clause_is_unique],
|
||||
'copy' => ['url' => $copy_url, 'string' => $copy_str],
|
||||
'delete' => ['url' => $del_url, 'string' => $del_str],
|
||||
'edit' => [
|
||||
'url' => $edit_url,
|
||||
'params' => $editCopyUrlParams + ['default_action' => 'update'],
|
||||
'string' => $edit_str,
|
||||
'clause_is_unique' => $clause_is_unique,
|
||||
],
|
||||
'copy' => [
|
||||
'url' => $copy_url,
|
||||
'params' => $editCopyUrlParams + ['default_action' => 'insert'],
|
||||
'string' => $copy_str,
|
||||
],
|
||||
'delete' => ['url' => $del_url, 'params' => $delUrlParams, 'string' => $del_str],
|
||||
'row_number' => $row_no,
|
||||
'where_clause' => $where_clause,
|
||||
'condition' => json_encode($condition_array),
|
||||
@ -2637,11 +2659,16 @@ class Results
|
||||
'has_checkbox' => ! empty($del_url) && $displayParts['del_lnk'] !== self::KILL_PROCESS,
|
||||
'edit' => [
|
||||
'url' => $edit_url,
|
||||
'params' => $editCopyUrlParams + ['default_action' => 'update'],
|
||||
'string' => $edit_str,
|
||||
'clause_is_unique' => $clause_is_unique ?? true,
|
||||
],
|
||||
'copy' => ['url' => $copy_url, 'string' => $copy_str],
|
||||
'delete' => ['url' => $del_url, 'string' => $del_str],
|
||||
'copy' => [
|
||||
'url' => $copy_url,
|
||||
'params' => $editCopyUrlParams + ['default_action' => 'insert'],
|
||||
'string' => $copy_str,
|
||||
],
|
||||
'delete' => ['url' => $del_url, 'params' => $delUrlParams, 'string' => $del_str],
|
||||
'row_number' => $row_no,
|
||||
'where_clause' => $where_clause ?? '',
|
||||
'condition' => json_encode($condition_array ?? []),
|
||||
@ -3231,7 +3258,7 @@ class Results
|
||||
* @param bool $clause_is_unique the unique condition of clause
|
||||
* @param string $url_sql_query the analyzed sql query
|
||||
*
|
||||
* @return array<int,string> 5 element array - $edit_url, $copy_url,
|
||||
* @return array<int,string|array> 5 element array - $edit_url, $copy_url,
|
||||
* $edit_str, $copy_str
|
||||
*
|
||||
* @access private
|
||||
@ -3250,15 +3277,9 @@ class Results
|
||||
'goto' => Url::getFromRoute('/sql'),
|
||||
];
|
||||
|
||||
$edit_url = Url::getFromRoute(
|
||||
'/table/change',
|
||||
$_url_params + ['default_action' => 'update']
|
||||
);
|
||||
$edit_url = Url::getFromRoute('/table/change');
|
||||
|
||||
$copy_url = Url::getFromRoute(
|
||||
'/table/change',
|
||||
$_url_params + ['default_action' => 'insert']
|
||||
);
|
||||
$copy_url = Url::getFromRoute('/table/change');
|
||||
|
||||
$edit_str = $this->getActionLinkContent(
|
||||
'b_edit',
|
||||
@ -3274,6 +3295,7 @@ class Results
|
||||
$copy_url,
|
||||
$edit_str,
|
||||
$copy_str,
|
||||
$_url_params,
|
||||
];
|
||||
}
|
||||
|
||||
@ -3327,7 +3349,7 @@ class Results
|
||||
'message_to_show' => __('The row has been deleted.'),
|
||||
'goto' => $lnk_goto,
|
||||
];
|
||||
$del_url = Url::getFromRoute('/sql', $_url_params);
|
||||
$del_url = Url::getFromRoute('/sql');
|
||||
|
||||
$js_conf = 'DELETE FROM ' . $this->properties['table']
|
||||
. ' WHERE ' . $where_clause
|
||||
@ -3352,20 +3374,21 @@ class Results
|
||||
'goto' => $lnk_goto,
|
||||
];
|
||||
|
||||
$del_url = Url::getFromRoute('/sql', $_url_params);
|
||||
$del_url = Url::getFromRoute('/sql');
|
||||
$js_conf = $kill;
|
||||
$del_str = Generator::getIcon(
|
||||
'b_drop',
|
||||
__('Kill')
|
||||
);
|
||||
} else {
|
||||
$del_url = $del_str = $js_conf = null;
|
||||
$del_url = $del_str = $js_conf = $_url_params = null;
|
||||
}
|
||||
|
||||
return [
|
||||
$del_url,
|
||||
$del_str,
|
||||
$js_conf,
|
||||
$_url_params,
|
||||
];
|
||||
}
|
||||
|
||||
@ -5067,7 +5090,8 @@ class Results
|
||||
$tag_params['class'] = 'ajax';
|
||||
}
|
||||
$result .= Generator::linkOrButton(
|
||||
Url::getFromRoute('/sql', $_url_params),
|
||||
Url::getFromRoute('/sql'),
|
||||
$_url_params,
|
||||
$displayedData,
|
||||
$tag_params
|
||||
);
|
||||
|
||||
@ -222,6 +222,7 @@ class ErrorReport
|
||||
if (isset($components['query'])) {
|
||||
parse_str($components['query'], $queryArray);
|
||||
unset($queryArray['db'], $queryArray['table'], $queryArray['token'], $queryArray['server']);
|
||||
unset($queryArray['eq']);
|
||||
$query = http_build_query($queryArray);
|
||||
} else {
|
||||
$query = '';
|
||||
|
||||
@ -565,16 +565,18 @@ class Export
|
||||
*/
|
||||
$back_button = '<p id="export_back_button">[ <a href="';
|
||||
if ($export_type === 'server') {
|
||||
$back_button .= Url::getFromRoute('/server/export') . '" data-post="' . Url::getCommon([], '');
|
||||
$back_button .= Url::getFromRoute('/server/export') . '" data-post="' . Url::getCommon([], '', false);
|
||||
} elseif ($export_type === 'database') {
|
||||
$back_button .= Url::getFromRoute('/database/export') . '" data-post="' . Url::getCommon(['db' => $db], '');
|
||||
$back_button .= Url::getFromRoute('/database/export') . '" data-post="' . Url::getCommon(
|
||||
['db' => $db],
|
||||
'',
|
||||
false
|
||||
);
|
||||
} else {
|
||||
$back_button .= Url::getFromRoute('/table/export') . '" data-post="' . Url::getCommon(
|
||||
[
|
||||
'db' => $db,
|
||||
'table' => $table,
|
||||
],
|
||||
''
|
||||
['db' => $db, 'table' => $table],
|
||||
'',
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -653,7 +653,8 @@ class Generator
|
||||
$explain_params['sql_query'] = 'EXPLAIN ' . $sql_query;
|
||||
$explain_link = ' [ '
|
||||
. self::linkOrButton(
|
||||
Url::getFromRoute('/import', $explain_params),
|
||||
Url::getFromRoute('/import'),
|
||||
$explain_params,
|
||||
__('Explain SQL')
|
||||
) . ' ]';
|
||||
} elseif (preg_match(
|
||||
@ -664,7 +665,8 @@ class Generator
|
||||
= mb_substr($sql_query, 8);
|
||||
$explain_link = ' [ '
|
||||
. self::linkOrButton(
|
||||
Url::getFromRoute('/import', $explain_params),
|
||||
Url::getFromRoute('/import'),
|
||||
$explain_params,
|
||||
__('Skip Explain SQL')
|
||||
) . ']';
|
||||
$url = 'https://mariadb.org/explain_analyzer/analyze/'
|
||||
@ -673,6 +675,7 @@ class Generator
|
||||
$explain_link .= ' ['
|
||||
. self::linkOrButton(
|
||||
htmlspecialchars('url.php?url=' . urlencode($url)),
|
||||
null,
|
||||
sprintf(__('Analyze Explain at %s'), 'mariadb.org'),
|
||||
[],
|
||||
'_blank',
|
||||
@ -689,9 +692,8 @@ class Generator
|
||||
if (! empty($cfg['SQLQuery']['Edit'])
|
||||
&& empty($GLOBALS['show_as_php'])
|
||||
) {
|
||||
$edit_link .= Url::getCommon($url_params, '&');
|
||||
$edit_link = ' [ '
|
||||
. self::linkOrButton($edit_link, __('Edit'))
|
||||
. self::linkOrButton($edit_link, $url_params, __('Edit'))
|
||||
. ' ]';
|
||||
} else {
|
||||
$edit_link = '';
|
||||
@ -703,14 +705,16 @@ class Generator
|
||||
if (! empty($GLOBALS['show_as_php'])) {
|
||||
$php_link = ' [ '
|
||||
. self::linkOrButton(
|
||||
Url::getFromRoute('/import', $url_params),
|
||||
Url::getFromRoute('/import'),
|
||||
$url_params,
|
||||
__('Without PHP code')
|
||||
)
|
||||
. ' ]';
|
||||
|
||||
$php_link .= ' [ '
|
||||
. self::linkOrButton(
|
||||
Url::getFromRoute('/import', $url_params),
|
||||
Url::getFromRoute('/import'),
|
||||
$url_params,
|
||||
__('Submit query')
|
||||
)
|
||||
. ' ]';
|
||||
@ -719,7 +723,8 @@ class Generator
|
||||
$php_params['show_as_php'] = 1;
|
||||
$php_link = ' [ '
|
||||
. self::linkOrButton(
|
||||
Url::getFromRoute('/import', $php_params),
|
||||
Url::getFromRoute('/import'),
|
||||
$php_params,
|
||||
__('Create PHP code')
|
||||
)
|
||||
. ' ]';
|
||||
@ -733,9 +738,9 @@ class Generator
|
||||
&& ! isset($GLOBALS['show_as_php']) // 'Submit query' does the same
|
||||
&& preg_match('@^(SELECT|SHOW)[[:space:]]+@i', $sql_query)
|
||||
) {
|
||||
$refresh_link = Url::getFromRoute('/sql', $url_params);
|
||||
$refresh_link = Url::getFromRoute('/sql');
|
||||
$refresh_link = ' [ '
|
||||
. self::linkOrButton($refresh_link, __('Refresh')) . ' ]';
|
||||
. self::linkOrButton($refresh_link, $url_params, __('Refresh')) . ' ]';
|
||||
} else {
|
||||
$refresh_link = '';
|
||||
}
|
||||
@ -770,6 +775,7 @@ class Generator
|
||||
$inline_edit_link = ' [ '
|
||||
. self::linkOrButton(
|
||||
'#',
|
||||
null,
|
||||
_pgettext('Inline edit query', 'Edit inline'),
|
||||
['class' => 'inline_edit_sql']
|
||||
)
|
||||
@ -1089,21 +1095,27 @@ class Generator
|
||||
* - URL components are over Suhosin limits
|
||||
* - There is SQL query in the parameters
|
||||
*
|
||||
* @param string $url the URL
|
||||
* @param string $message the link message
|
||||
* @param mixed $tag_params string: js confirmation; array: additional tag
|
||||
* params (f.e. style="")
|
||||
* @param string $target target
|
||||
* @param string $urlPath the URL
|
||||
* @param array|null $urlParams URL parameters
|
||||
* @param string $message the link message
|
||||
* @param mixed $tag_params string: js confirmation; array: additional tag params (f.e. style="")
|
||||
* @param string $target target
|
||||
*
|
||||
* @return string the results to be echoed or saved in an array
|
||||
*/
|
||||
public static function linkOrButton(
|
||||
$url,
|
||||
$urlPath,
|
||||
$urlParams,
|
||||
$message,
|
||||
$tag_params = [],
|
||||
$target = '',
|
||||
bool $respectUrlLengthLimit = true
|
||||
): string {
|
||||
$url = $urlPath;
|
||||
if (is_array($urlParams)) {
|
||||
$url = $urlPath . Url::getCommon($urlParams, '?', false);
|
||||
}
|
||||
|
||||
$url_length = strlen($url);
|
||||
|
||||
if (! is_array($tag_params)) {
|
||||
@ -1165,6 +1177,11 @@ class Generator
|
||||
) {
|
||||
$url .= '?' . explode('&', $parts[1], 2)[0];
|
||||
}
|
||||
} else {
|
||||
$url = $urlPath;
|
||||
if (is_array($urlParams)) {
|
||||
$url = $urlPath . Url::getCommon($urlParams);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($tag_params as $par_name => $par_value) {
|
||||
|
||||
@ -329,13 +329,13 @@ class InsertEdit
|
||||
|
||||
if (! $is_show) {
|
||||
return ' : <a href="' . Url::getFromRoute('/table/change') . '" data-post="'
|
||||
. Url::getCommon($this_url_params, '') . '">'
|
||||
. Url::getCommon($this_url_params, '', false) . '">'
|
||||
. $this->showTypeOrFunctionLabel($which)
|
||||
. '</a>';
|
||||
}
|
||||
|
||||
return '<th><a href="' . Url::getFromRoute('/table/change') . '" data-post="'
|
||||
. Url::getCommon($this_url_params, '')
|
||||
. Url::getCommon($this_url_params, '', false)
|
||||
. '" title="' . __('Hide') . '">'
|
||||
. $this->showTypeOrFunctionLabel($which)
|
||||
. '</a></th>';
|
||||
@ -920,7 +920,8 @@ class InsertEdit
|
||||
'rownumber' => $rownumber,
|
||||
'data' => $data,
|
||||
],
|
||||
''
|
||||
'',
|
||||
false
|
||||
) . '">'
|
||||
. Generator::getIcon('b_browse', __('Browse foreign values')) . '</a>';
|
||||
|
||||
@ -1788,6 +1789,7 @@ class InsertEdit
|
||||
return '<span class="open_gis_editor" data-row-id="' . $rowId . '">'
|
||||
. Generator::linkOrButton(
|
||||
'#',
|
||||
null,
|
||||
$edit_str,
|
||||
[],
|
||||
'_blank'
|
||||
|
||||
@ -52,6 +52,9 @@ use function trim;
|
||||
use function urlencode;
|
||||
use function usort;
|
||||
use function vsprintf;
|
||||
use function parse_url;
|
||||
use function parse_str;
|
||||
use function htmlspecialchars_decode;
|
||||
|
||||
/**
|
||||
* Displays a collapsible of database objects in the navigation frame
|
||||
@ -145,13 +148,13 @@ class NavigationTree
|
||||
$this->pos = $this->getNavigationDbPos();
|
||||
}
|
||||
// Get the active node
|
||||
if (isset($_REQUEST['aPath'])) {
|
||||
$this->aPath[0] = $this->parsePath($_REQUEST['aPath']);
|
||||
$this->pos2Name[0] = $_REQUEST['pos2_name'] ?? '';
|
||||
$this->pos2Value[0] = (int) ($_REQUEST['pos2_value'] ?? 0);
|
||||
if (isset($_REQUEST['pos3_name'])) {
|
||||
$this->pos3Name[0] = $_REQUEST['pos3_name'] ?? '';
|
||||
$this->pos3Value[0] = (int) $_REQUEST['pos3_value'];
|
||||
if (isset($_POST['aPath'])) {
|
||||
$this->aPath[0] = $this->parsePath($_POST['aPath']);
|
||||
$this->pos2Name[0] = $_POST['pos2_name'] ?? '';
|
||||
$this->pos2Value[0] = (int) ($_POST['pos2_value'] ?? 0);
|
||||
if (isset($_POST['pos3_name'])) {
|
||||
$this->pos3Name[0] = $_POST['pos3_name'] ?? '';
|
||||
$this->pos3Value[0] = (int) $_POST['pos3_value'];
|
||||
}
|
||||
} else {
|
||||
if (isset($_POST['n0_aPath'])) {
|
||||
@ -172,8 +175,8 @@ class NavigationTree
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isset($_REQUEST['vPath'])) {
|
||||
$this->vPath[0] = $this->parsePath($_REQUEST['vPath']);
|
||||
if (isset($_POST['vPath'])) {
|
||||
$this->vPath[0] = $this->parsePath($_POST['vPath']);
|
||||
} else {
|
||||
if (isset($_POST['n0_vPath'])) {
|
||||
$count = 0;
|
||||
@ -185,11 +188,11 @@ class NavigationTree
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isset($_REQUEST['searchClause'])) {
|
||||
$this->searchClause = $_REQUEST['searchClause'];
|
||||
if (isset($_POST['searchClause'])) {
|
||||
$this->searchClause = $_POST['searchClause'];
|
||||
}
|
||||
if (isset($_REQUEST['searchClause2'])) {
|
||||
$this->searchClause2 = $_REQUEST['searchClause2'];
|
||||
if (isset($_POST['searchClause2'])) {
|
||||
$this->searchClause2 = $_POST['searchClause2'];
|
||||
}
|
||||
// Initialize the tree by creating a root node
|
||||
$node = NodeFactory::getInstance('NodeDatabaseContainer', 'root');
|
||||
@ -1193,7 +1196,7 @@ class NavigationTree
|
||||
}
|
||||
|
||||
foreach ($icons as $key => $icon) {
|
||||
$link = vsprintf($iconLinks[$key], $args);
|
||||
$link = $this->encryptQueryParams(vsprintf($iconLinks[$key], $args));
|
||||
if ($linkClass != '') {
|
||||
$retval .= "<a class='" . $linkClass . "' href='" . $link . "'>";
|
||||
$retval .= '' . $icon . '</a>';
|
||||
@ -1211,7 +1214,7 @@ class NavigationTree
|
||||
foreach ($node->parents(true) as $parent) {
|
||||
$args[] = urlencode($parent->realName);
|
||||
}
|
||||
$link = vsprintf($node->links['text'], $args);
|
||||
$link = $this->encryptQueryParams(vsprintf($node->links['text'], $args));
|
||||
$title = $node->links['title'] ?? $node->title ?? '';
|
||||
if ($nodeIsContainer) {
|
||||
$retval .= " <a class='hover_show_full' href='" . $link . "'>";
|
||||
@ -1601,4 +1604,23 @@ class NavigationTree
|
||||
|
||||
return $retval;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $link
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function encryptQueryParams($link)
|
||||
{
|
||||
global $PMA_Config;
|
||||
|
||||
if (! $PMA_Config->get('URLQueryEncryption')) {
|
||||
return $link;
|
||||
}
|
||||
|
||||
$url = parse_url($link);
|
||||
parse_str(htmlspecialchars_decode($url['query']), $query);
|
||||
|
||||
return $url['path'] . '?' . htmlspecialchars(Url::buildHttpQuery($query));
|
||||
}
|
||||
}
|
||||
|
||||
@ -702,7 +702,7 @@ class NodeDatabase extends Node
|
||||
];
|
||||
$ret = '<span class="dbItemControls">'
|
||||
. '<a href="' . Url::getFromRoute('/navigation') . '" data-post="'
|
||||
. Url::getCommon($params, '') . '"'
|
||||
. Url::getCommon($params, '', false) . '"'
|
||||
. ' class="showUnhide ajax">'
|
||||
. Generator::getImage(
|
||||
'show',
|
||||
|
||||
@ -46,7 +46,7 @@ abstract class NodeDatabaseChild extends Node
|
||||
|
||||
$ret = '<span class="navItemControls">'
|
||||
. '<a href="' . Url::getFromRoute('/navigation') . '" data-post="'
|
||||
. Url::getCommon($params, '') . '"'
|
||||
. Url::getCommon($params, '', false) . '"'
|
||||
. ' class="hideNavItem ajax">'
|
||||
. Generator::getImage('hide', __('Hide'))
|
||||
. '</a></span>';
|
||||
|
||||
@ -2223,7 +2223,8 @@ class Relation
|
||||
);
|
||||
}
|
||||
$message->addParamHtml(
|
||||
'<a href="' . Url::getFromRoute('/check-relations') . '" data-post="' . Url::getCommon($params, '') . '">'
|
||||
'<a href="' . Url::getFromRoute('/check-relations')
|
||||
. '" data-post="' . Url::getCommon($params, '', false) . '">'
|
||||
);
|
||||
$message->addParamHtml('</a>');
|
||||
|
||||
|
||||
@ -140,7 +140,7 @@ class ReplicationGui
|
||||
}
|
||||
|
||||
$urlParams['sr_slave_control_parm'] = 'IO_THREAD';
|
||||
$slaveControlIoLink = Url::getCommon($urlParams, '');
|
||||
$slaveControlIoLink = Url::getCommon($urlParams, '', false);
|
||||
|
||||
if ($serverSlaveReplication[0]['Slave_SQL_Running'] === 'No') {
|
||||
$urlParams['sr_slave_action'] = 'start';
|
||||
@ -149,7 +149,7 @@ class ReplicationGui
|
||||
}
|
||||
|
||||
$urlParams['sr_slave_control_parm'] = 'SQL_THREAD';
|
||||
$slaveControlSqlLink = Url::getCommon($urlParams, '');
|
||||
$slaveControlSqlLink = Url::getCommon($urlParams, '', false);
|
||||
|
||||
if ($serverSlaveReplication[0]['Slave_IO_Running'] === 'No'
|
||||
|| $serverSlaveReplication[0]['Slave_SQL_Running'] === 'No'
|
||||
@ -160,21 +160,21 @@ class ReplicationGui
|
||||
}
|
||||
|
||||
$urlParams['sr_slave_control_parm'] = null;
|
||||
$slaveControlFullLink = Url::getCommon($urlParams, '');
|
||||
$slaveControlFullLink = Url::getCommon($urlParams, '', false);
|
||||
|
||||
$urlParams['sr_slave_action'] = 'reset';
|
||||
$slaveControlResetLink = Url::getCommon($urlParams, '');
|
||||
$slaveControlResetLink = Url::getCommon($urlParams, '', false);
|
||||
|
||||
$urlParams = $GLOBALS['url_params'];
|
||||
$urlParams['sr_take_action'] = true;
|
||||
$urlParams['sr_slave_skip_error'] = true;
|
||||
$slaveSkipErrorLink = Url::getCommon($urlParams, '');
|
||||
$slaveSkipErrorLink = Url::getCommon($urlParams, '', false);
|
||||
|
||||
$urlParams = $GLOBALS['url_params'];
|
||||
$urlParams['sl_configure'] = true;
|
||||
$urlParams['repl_clear_scr'] = true;
|
||||
|
||||
$reconfigureMasterLink = Url::getCommon($urlParams, '');
|
||||
$reconfigureMasterLink = Url::getCommon($urlParams, '', false);
|
||||
|
||||
$slaveStatusTable = $this->getHtmlForReplicationStatusTable('slave', true, false);
|
||||
|
||||
|
||||
@ -1606,7 +1606,7 @@ class Privileges
|
||||
|
||||
$html .= ' href="' . Url::getFromRoute('/server/privileges');
|
||||
if ($linktype === 'revoke') {
|
||||
$html .= '" data-post="' . Url::getCommon($params, '');
|
||||
$html .= '" data-post="' . Url::getCommon($params, '', false);
|
||||
} else {
|
||||
$html .= Url::getCommon($params, '&');
|
||||
}
|
||||
|
||||
@ -111,7 +111,8 @@ class UserGroups
|
||||
'viewUsers' => 1,
|
||||
'userGroup' => $groupName,
|
||||
],
|
||||
''
|
||||
'',
|
||||
false
|
||||
);
|
||||
$userGroupVal['viewUsersIcon'] = Generator::getIcon('b_usrlist', __('View users'));
|
||||
|
||||
@ -120,7 +121,8 @@ class UserGroups
|
||||
'editUserGroup' => 1,
|
||||
'userGroup' => $groupName,
|
||||
],
|
||||
''
|
||||
'',
|
||||
false
|
||||
);
|
||||
$userGroupVal['editUsersIcon'] = Generator::getIcon('b_edit', __('Edit'));
|
||||
|
||||
@ -129,7 +131,8 @@ class UserGroups
|
||||
'deleteUserGroup' => 1,
|
||||
'userGroup' => $groupName,
|
||||
],
|
||||
''
|
||||
'',
|
||||
false
|
||||
);
|
||||
$userGroupVal['deleteUsersIcon'] = Generator::getIcon('b_drop', __('Delete'));
|
||||
$userGroupsValues[] = $userGroupVal;
|
||||
|
||||
@ -7,6 +7,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin;
|
||||
|
||||
use PhpMyAdmin\Crypto\Crypto;
|
||||
|
||||
use function htmlentities;
|
||||
use function htmlspecialchars;
|
||||
use function http_build_query;
|
||||
@ -14,6 +16,11 @@ use function ini_get;
|
||||
use function is_array;
|
||||
use function mb_strpos;
|
||||
use function strlen;
|
||||
use function in_array;
|
||||
use function json_encode;
|
||||
use function strtr;
|
||||
use function base64_encode;
|
||||
use function base64_decode;
|
||||
|
||||
/**
|
||||
* Static methods for URL/hidden inputs generating
|
||||
@ -166,14 +173,15 @@ class Url
|
||||
*
|
||||
* @param array<string,int|string|bool> $params optional, Contains an associative array with url params
|
||||
* @param string $divider optional character to use instead of '?'
|
||||
* @param bool $encrypt whether to encrypt URL params
|
||||
*
|
||||
* @return string string with URL parameters
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public static function getCommon(array $params = [], $divider = '?')
|
||||
public static function getCommon(array $params = [], $divider = '?', $encrypt = true)
|
||||
{
|
||||
return self::getCommonRaw($params, $divider);
|
||||
return self::getCommonRaw($params, $divider, $encrypt);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -201,17 +209,16 @@ class Url
|
||||
*
|
||||
* @param array<string|int,int|string|bool> $params optional, Contains an associative array with url params
|
||||
* @param string $divider optional character to use instead of '?'
|
||||
* @param bool $encrypt whether to encrypt URL params
|
||||
*
|
||||
* @return string string with URL parameters
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public static function getCommonRaw(array $params = [], $divider = '?')
|
||||
public static function getCommonRaw(array $params = [], $divider = '?', $encrypt = true)
|
||||
{
|
||||
global $PMA_Config;
|
||||
|
||||
$separator = self::getArgSeparator();
|
||||
|
||||
// avoid overwriting when creating navigation panel links to servers
|
||||
if (isset($GLOBALS['server'])
|
||||
&& $GLOBALS['server'] != $GLOBALS['cfg']['ServerDefault']
|
||||
@ -227,7 +234,7 @@ class Url
|
||||
$params['lang'] = $GLOBALS['lang'];
|
||||
}
|
||||
|
||||
$query = http_build_query($params, '', $separator);
|
||||
$query = self::buildHttpQuery($params, $encrypt);
|
||||
|
||||
if (($divider !== '?' && $divider !== '&') || strlen($query) > 0) {
|
||||
return $divider . $query;
|
||||
@ -236,6 +243,81 @@ class Url
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $params
|
||||
* @param bool $encrypt whether to encrypt URL params
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function buildHttpQuery($params, $encrypt = true)
|
||||
{
|
||||
global $PMA_Config;
|
||||
|
||||
$separator = self::getArgSeparator();
|
||||
|
||||
if (! $encrypt || ! $PMA_Config->get('URLQueryEncryption')) {
|
||||
return http_build_query($params, '', $separator);
|
||||
}
|
||||
|
||||
$data = $params;
|
||||
$keys = [
|
||||
'db',
|
||||
'table',
|
||||
'field',
|
||||
'sql_query',
|
||||
'sql_signature',
|
||||
'where_clause',
|
||||
'goto',
|
||||
'back',
|
||||
'message_to_show',
|
||||
'username',
|
||||
'hostname',
|
||||
'dbname',
|
||||
'tablename',
|
||||
'checkprivsdb',
|
||||
'checkprivstable',
|
||||
];
|
||||
$paramsToEncrypt = [];
|
||||
foreach ($params as $paramKey => $paramValue) {
|
||||
if (! in_array($paramKey, $keys)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$paramsToEncrypt[$paramKey] = $paramValue;
|
||||
unset($data[$paramKey]);
|
||||
}
|
||||
|
||||
if ($paramsToEncrypt !== []) {
|
||||
$data['eq'] = self::encryptQuery(json_encode($paramsToEncrypt));
|
||||
}
|
||||
|
||||
return http_build_query($data, '', $separator);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $query
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function encryptQuery($query)
|
||||
{
|
||||
$crypto = new Crypto();
|
||||
|
||||
return strtr(base64_encode($crypto->encrypt($query)), '+/', '-_');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $query
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public static function decryptQuery($query)
|
||||
{
|
||||
$crypto = new Crypto();
|
||||
|
||||
return $crypto->decrypt(base64_decode(strtr($query, '-_', '+/')));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns url separator
|
||||
*
|
||||
|
||||
@ -3171,9 +3171,9 @@ class Util
|
||||
$urlParams['tbl_group'] = $_REQUEST['tbl_group'];
|
||||
}
|
||||
|
||||
$url = Url::getFromRoute('/database/structure', $urlParams);
|
||||
$url = Url::getFromRoute('/database/structure');
|
||||
|
||||
return Generator::linkOrButton($url, $title . $orderImg, $orderLinkParams);
|
||||
return Generator::linkOrButton($url, $urlParams, $title . $orderImg, $orderLinkParams);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -161,6 +161,8 @@ if (! defined('PMA_NO_SESSION')) {
|
||||
Session::setUp($PMA_Config, $error_handler);
|
||||
}
|
||||
|
||||
Core::populateRequestWithEncryptedQueryParams();
|
||||
|
||||
/**
|
||||
* init some variables LABEL_variables_init
|
||||
*/
|
||||
|
||||
@ -606,6 +606,13 @@ $cfg['Servers'][$i]['tracking_add_drop_table'] = true;
|
||||
*/
|
||||
$cfg['Servers'][$i]['tracking_add_drop_database'] = true;
|
||||
|
||||
/**
|
||||
* Whether to show or hide detailed MySQL/MariaDB connection errors on the login page.
|
||||
*
|
||||
* @global bool $cfg['Servers'][$i]['hide_connection_errors']
|
||||
*/
|
||||
$cfg['Servers'][$i]['hide_connection_errors'] = false;
|
||||
|
||||
/**
|
||||
* Default server (0 = no default server)
|
||||
*
|
||||
@ -839,6 +846,20 @@ $cfg['UseDbSearch'] = true;
|
||||
*/
|
||||
$cfg['IgnoreMultiSubmitErrors'] = false;
|
||||
|
||||
/**
|
||||
* Define whether phpMyAdmin will encrypt sensitive data from the URL query string.
|
||||
*
|
||||
* @global bool $cfg['URLQueryEncryption']
|
||||
*/
|
||||
$cfg['URLQueryEncryption'] = false;
|
||||
|
||||
/**
|
||||
* A secret key used to encrypt/decrypt the URL query string. Should be 32 bytes long.
|
||||
*
|
||||
* @global string $cfg['URLQueryEncryptionSecretKey']
|
||||
*/
|
||||
$cfg['URLQueryEncryptionSecretKey'] = '';
|
||||
|
||||
/**
|
||||
* allow login to any user entered server in cookie based authentication
|
||||
*
|
||||
|
||||
@ -507,7 +507,7 @@ parameters:
|
||||
|
||||
-
|
||||
message: "#^Cannot call method real_connect\\(\\) on mysqli\\|false\\.$#"
|
||||
count: 1
|
||||
count: 2
|
||||
path: libraries/classes/Dbal/DbiMysqli.php
|
||||
|
||||
-
|
||||
@ -525,6 +525,16 @@ parameters:
|
||||
count: 2
|
||||
path: libraries/classes/Dbal/DbiMysqli.php
|
||||
|
||||
-
|
||||
message: "#^Binary operation \"\\+\" between array\\|string\\|null and array\\{default_action\\: 'insert'\\} results in an error\\.$#"
|
||||
count: 3
|
||||
path: libraries/classes/Display/Results.php
|
||||
|
||||
-
|
||||
message: "#^Binary operation \"\\+\" between array\\|string\\|null and array\\{default_action\\: 'update'\\} results in an error\\.$#"
|
||||
count: 3
|
||||
path: libraries/classes/Display/Results.php
|
||||
|
||||
-
|
||||
message: "#^Call to an undefined method object\\:\\:getMIMESubtype\\(\\)\\.$#"
|
||||
count: 1
|
||||
@ -1220,6 +1230,16 @@ parameters:
|
||||
count: 1
|
||||
path: libraries/classes/Menu.php
|
||||
|
||||
-
|
||||
message: "#^Cannot access offset 'path' on array\\{scheme\\?\\: string, host\\?\\: string, port\\?\\: int, user\\?\\: string, pass\\?\\: string, path\\?\\: string, query\\?\\: string, fragment\\?\\: string\\}\\|false\\.$#"
|
||||
count: 1
|
||||
path: libraries/classes/Navigation/NavigationTree.php
|
||||
|
||||
-
|
||||
message: "#^Cannot access offset 'query' on array\\{scheme\\?\\: string, host\\?\\: string, port\\?\\: int, user\\?\\: string, pass\\?\\: string, path\\?\\: string, query\\?\\: string, fragment\\?\\: string\\}\\|false\\.$#"
|
||||
count: 1
|
||||
path: libraries/classes/Navigation/NavigationTree.php
|
||||
|
||||
-
|
||||
message: "#^Property PhpMyAdmin\\\\Navigation\\\\NavigationTree\\:\\:\\$pos \\(int\\) in isset\\(\\) is not nullable\\.$#"
|
||||
count: 1
|
||||
@ -2545,6 +2565,16 @@ parameters:
|
||||
count: 1
|
||||
path: libraries/classes/Types.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$params of static method PhpMyAdmin\\\\Url\\:\\:buildHttpQuery\\(\\) expects array\\<string, mixed\\>, array\\<int\\|string, mixed\\> given\\.$#"
|
||||
count: 1
|
||||
path: libraries/classes/Url.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$query of static method PhpMyAdmin\\\\Url\\:\\:encryptQuery\\(\\) expects string, string\\|false given\\.$#"
|
||||
count: 1
|
||||
path: libraries/classes/Url.php
|
||||
|
||||
-
|
||||
message: "#^Cannot use array destructuring on array\\|null\\.$#"
|
||||
count: 1
|
||||
@ -2620,6 +2650,16 @@ parameters:
|
||||
count: 1
|
||||
path: test/classes/Controllers/ExportTemplateControllerTest.php
|
||||
|
||||
-
|
||||
message: "#^Method PhpMyAdmin\\\\Tests\\\\CoreTest\\:\\:providerForTestPopulateRequestWithEncryptedQueryParamsWithInvalidParam\\(\\) should return array\\<array\\<array\\<string\\>\\>\\> but returns array\\<int, array\\<int, array\\<string, array\\|string\\>\\>\\>\\.$#"
|
||||
count: 1
|
||||
path: test/classes/CoreTest.php
|
||||
|
||||
-
|
||||
message: "#^Call to method PHPUnit\\\\Framework\\\\Assert\\:\\:assertArrayHasKey\\(\\) with 'URLQueryEncryptionS…' and array\\<string, mixed\\> will always evaluate to false\\.$#"
|
||||
count: 1
|
||||
path: test/classes/Crypto/CryptoTest.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$result of method PhpMyAdmin\\\\Dbal\\\\DbiMysqli\\:\\:numRows\\(\\) expects mysqli_result, false given\\.$#"
|
||||
count: 1
|
||||
@ -2690,6 +2730,16 @@ parameters:
|
||||
count: 1
|
||||
path: test/classes/LanguageTest.php
|
||||
|
||||
-
|
||||
message: "#^Cannot access offset 'query' on array\\{scheme\\?\\: string, host\\?\\: string, port\\?\\: int, user\\?\\: string, pass\\?\\: string, path\\?\\: string, query\\?\\: string, fragment\\?\\: string\\}\\|false\\.$#"
|
||||
count: 1
|
||||
path: test/classes/Navigation/NavigationTreeTest.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$actualJson of method PHPUnit\\\\Framework\\\\Assert\\:\\:assertJson\\(\\) expects string, string\\|null given\\.$#"
|
||||
count: 1
|
||||
path: test/classes/Navigation/NavigationTreeTest.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#2 \\$name of static method PhpMyAdmin\\\\Navigation\\\\NodeFactory\\:\\:getInstance\\(\\) expects string, array\\<string, string\\> given\\.$#"
|
||||
count: 1
|
||||
@ -2805,6 +2855,11 @@ parameters:
|
||||
count: 1
|
||||
path: test/classes/TrackingTest.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$actualJson of method PHPUnit\\\\Framework\\\\Assert\\:\\:assertJson\\(\\) expects string, string\\|null given\\.$#"
|
||||
count: 1
|
||||
path: test/classes/UrlTest.php
|
||||
|
||||
-
|
||||
message: "#^Property PhpMyAdmin\\\\Tests\\\\Selenium\\\\ChangePasswordTest\\:\\:\\$verificationErrors is never written, only read\\.$#"
|
||||
count: 1
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<files psalm-version="4.17.0@6f4707aa41c9174353a6434bba3fc8840f981d9c">
|
||||
<files psalm-version="4.18.1@dda05fa913f4dc6eb3386f2f7ce5a45d37a71bcb">
|
||||
<file src="libraries/classes/Bookmark.php">
|
||||
<RedundantCastGivenDocblockType occurrences="1">
|
||||
<code>(int) $this->id</code>
|
||||
@ -593,6 +593,14 @@
|
||||
<code>$_POST['field_extra'][$i] ?? false</code>
|
||||
</PossiblyFalseArgument>
|
||||
</file>
|
||||
<file src="libraries/classes/Crypto/Crypto.php">
|
||||
<InvalidReturnStatement occurrences="1">
|
||||
<code>$decrypted</code>
|
||||
</InvalidReturnStatement>
|
||||
<InvalidReturnType occurrences="1">
|
||||
<code>string|null</code>
|
||||
</InvalidReturnType>
|
||||
</file>
|
||||
<file src="libraries/classes/Database/CentralColumns.php">
|
||||
<InvalidScalarArgument occurrences="1">
|
||||
<code>$tn_pageNow</code>
|
||||
@ -812,6 +820,14 @@
|
||||
<code>$col_visib</code>
|
||||
<code>$sortExpressionNoDirection</code>
|
||||
</PossiblyInvalidArgument>
|
||||
<PossiblyInvalidOperand occurrences="6">
|
||||
<code>$editCopyUrlParams</code>
|
||||
<code>$editCopyUrlParams</code>
|
||||
<code>$editCopyUrlParams</code>
|
||||
<code>$editCopyUrlParams</code>
|
||||
<code>$editCopyUrlParams</code>
|
||||
<code>$editCopyUrlParams</code>
|
||||
</PossiblyInvalidOperand>
|
||||
<PossiblyNullArgument occurrences="4">
|
||||
<code>$col_visib_current</code>
|
||||
<code>$col_visib_current</code>
|
||||
@ -1312,6 +1328,10 @@
|
||||
<PossiblyNullReference occurrences="1">
|
||||
<code>addChild</code>
|
||||
</PossiblyNullReference>
|
||||
<PossiblyUndefinedArrayOffset occurrences="2">
|
||||
<code>$url['path']</code>
|
||||
<code>$url['query']</code>
|
||||
</PossiblyUndefinedArrayOffset>
|
||||
<RedundantCastGivenDocblockType occurrences="5">
|
||||
<code>(string) $child->name</code>
|
||||
<code>(string) $child->name</code>
|
||||
@ -2752,6 +2772,9 @@
|
||||
</RedundantCastGivenDocblockType>
|
||||
</file>
|
||||
<file src="libraries/classes/Url.php">
|
||||
<InvalidScalarArgument occurrences="1">
|
||||
<code>$params</code>
|
||||
</InvalidScalarArgument>
|
||||
<RedundantCastGivenDocblockType occurrences="2">
|
||||
<code>(string) $db</code>
|
||||
<code>(string) $table</code>
|
||||
|
||||
@ -61,11 +61,12 @@
|
||||
<td>
|
||||
{% if has_privilege %}
|
||||
{{ link_or_button(
|
||||
url('/sql', {
|
||||
url('/sql'),
|
||||
{
|
||||
'db': db,
|
||||
'sql_query': 'DROP EVENT IF EXISTS %s'|format(backquote(event.name)),
|
||||
'goto': url('/database/events', {'db': db})
|
||||
}),
|
||||
},
|
||||
get_icon('b_drop', 'Drop'|trans),
|
||||
{'class': 'ajax drop_anchor'}
|
||||
) }}
|
||||
|
||||
@ -36,12 +36,13 @@
|
||||
<td>
|
||||
{% if has_privilege %}
|
||||
{{ link_or_button(
|
||||
url('/sql', {
|
||||
url('/sql'),
|
||||
{
|
||||
'db': db,
|
||||
'table': table,
|
||||
'sql_query': sql_drop,
|
||||
'goto': url('/database/events', {'db': db})
|
||||
}),
|
||||
},
|
||||
get_icon('b_drop', 'Drop'|trans),
|
||||
{'class': 'ajax drop_anchor'}
|
||||
) }}
|
||||
|
||||
@ -90,7 +90,8 @@
|
||||
<div class="card-body">
|
||||
<div class="card-text">
|
||||
{{ link_or_button(
|
||||
url('/sql', {
|
||||
url('/sql'),
|
||||
{
|
||||
'sql_query': 'DROP DATABASE ' ~ backquote(db),
|
||||
'back': url('/database/operations'),
|
||||
'goto': url('/'),
|
||||
@ -98,7 +99,7 @@
|
||||
'purge': true,
|
||||
'message_to_show': 'Database %s has been dropped.'|trans|format(backquote(db))|e,
|
||||
'db': null
|
||||
}),
|
||||
},
|
||||
'Drop the database (DROP)'|trans,
|
||||
{
|
||||
'id': 'drop_db_anchor',
|
||||
|
||||
@ -63,12 +63,13 @@
|
||||
</td>
|
||||
<td>
|
||||
{{ link_or_button(
|
||||
url('/sql', {
|
||||
url('/sql'),
|
||||
{
|
||||
'db': db,
|
||||
'table': table,
|
||||
'sql_query': sql_drop,
|
||||
'goto': url('/database/routines', {'db': db})
|
||||
}),
|
||||
},
|
||||
get_icon('b_drop', 'Drop'|trans),
|
||||
{'class': 'ajax drop_anchor'}
|
||||
) }}
|
||||
|
||||
@ -89,7 +89,7 @@
|
||||
'back': url('/database/tracking'),
|
||||
'table': version.table_name,
|
||||
'delete_tracking': true
|
||||
}, '') }}">
|
||||
}, '', false) }}">
|
||||
{{ get_icon('b_drop', 'Delete tracking'|trans) }}
|
||||
</a>
|
||||
</td>
|
||||
@ -100,7 +100,7 @@
|
||||
'goto': url('/table/tracking'),
|
||||
'back': url('/database/tracking'),
|
||||
'table': version.table_name
|
||||
}, '') }}">
|
||||
}, '', false) }}">
|
||||
{{ get_icon('b_versions', 'Versions'|trans) }}
|
||||
</a>
|
||||
<a href="{{ url('/table/tracking') }}" data-post="
|
||||
@ -111,7 +111,7 @@
|
||||
'table': version.table_name,
|
||||
'report': true,
|
||||
'version': version.version
|
||||
}, '') }}">
|
||||
}, '', false) }}">
|
||||
{{ get_icon('b_report', 'Tracking report'|trans) }}
|
||||
</a>
|
||||
<a href="{{ url('/table/tracking') }}" data-post="
|
||||
@ -122,7 +122,7 @@
|
||||
'table': version.table_name,
|
||||
'snapshot': true,
|
||||
'version': version.version
|
||||
}, '') }}">
|
||||
}, '', false) }}">
|
||||
{{ get_icon('b_props', 'Structure snapshot'|trans) }}
|
||||
</a>
|
||||
</td>
|
||||
|
||||
@ -38,12 +38,13 @@
|
||||
<td>
|
||||
{% if has_drop_privilege %}
|
||||
{{ link_or_button(
|
||||
url('/sql', {
|
||||
url('/sql'),
|
||||
{
|
||||
'db': db,
|
||||
'table': table,
|
||||
'sql_query': trigger.drop,
|
||||
'goto': url('/database/triggers', {'db': db})
|
||||
}),
|
||||
},
|
||||
get_icon('b_drop', 'Drop'|trans),
|
||||
{'class': 'ajax drop_anchor'}
|
||||
) }}
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
{% if edit.url is not empty %}
|
||||
<td class="text-center print_ignore edit_row_anchor{{ not edit.clause_is_unique ? ' nonunique' }}">
|
||||
<span class="nowrap">
|
||||
{{ link_or_button(edit.url, edit.string) }}
|
||||
{{ link_or_button(edit.url, edit.params, edit.string) }}
|
||||
{% if where_clause is not empty %}
|
||||
<input type="hidden" class="where_clause" value="{{ where_clause }}">
|
||||
{% endif %}
|
||||
@ -21,7 +21,7 @@
|
||||
{% if copy.url is not empty %}
|
||||
<td class="text-center print_ignore">
|
||||
<span class="nowrap">
|
||||
{{ link_or_button(copy.url, copy.string) }}
|
||||
{{ link_or_button(copy.url, copy.params, copy.string) }}
|
||||
{% if where_clause is not empty %}
|
||||
<input type="hidden" class="where_clause" value="{{ where_clause }}">
|
||||
{% endif %}
|
||||
@ -32,7 +32,7 @@
|
||||
{% if delete.url is not empty %}
|
||||
<td class="text-center print_ignore{{ is_ajax ? ' ajax' }}">
|
||||
<span class="nowrap">
|
||||
{{ link_or_button(delete.url, delete.string, {'class': 'delete_row requireConfirm' ~ (is_ajax ? ' ajax') }) }}
|
||||
{{ link_or_button(delete.url, delete.params, delete.string, {'class': 'delete_row requireConfirm' ~ (is_ajax ? ' ajax') }) }}
|
||||
{% if js_conf is not empty %}
|
||||
<div class="hide">{{ js_conf }}</div>
|
||||
{% endif %}
|
||||
@ -43,7 +43,7 @@
|
||||
{% if delete.url is not empty %}
|
||||
<td class="text-center print_ignore{{ is_ajax ? ' ajax' }}">
|
||||
<span class="nowrap">
|
||||
{{ link_or_button(delete.url, delete.string, {'class': 'delete_row requireConfirm' ~ (is_ajax ? ' ajax') }) }}
|
||||
{{ link_or_button(delete.url, delete.params, delete.string, {'class': 'delete_row requireConfirm' ~ (is_ajax ? ' ajax') }) }}
|
||||
{% if js_conf is not empty %}
|
||||
<div class="hide">{{ js_conf }}</div>
|
||||
{% endif %}
|
||||
@ -54,7 +54,7 @@
|
||||
{% if copy.url is not empty %}
|
||||
<td class="text-center print_ignore">
|
||||
<span class="nowrap">
|
||||
{{ link_or_button(copy.url, copy.string) }}
|
||||
{{ link_or_button(copy.url, copy.params, copy.string) }}
|
||||
{% if where_clause is not empty %}
|
||||
<input type="hidden" class="where_clause" value="{{ where_clause }}">
|
||||
{% endif %}
|
||||
@ -65,7 +65,7 @@
|
||||
{% if edit.url is not empty %}
|
||||
<td class="text-center print_ignore edit_row_anchor{{ not edit.clause_is_unique ? ' nonunique' }}">
|
||||
<span class="nowrap">
|
||||
{{ link_or_button(edit.url, edit.string) }}
|
||||
{{ link_or_button(edit.url, edit.params, edit.string) }}
|
||||
{% if where_clause is not empty %}
|
||||
<input type="hidden" class="where_clause" value="{{ where_clause }}">
|
||||
{% endif %}
|
||||
|
||||
@ -240,6 +240,7 @@
|
||||
{% if operations.has_print_link %}
|
||||
{{ link_or_button(
|
||||
'#',
|
||||
null,
|
||||
get_icon('b_print', 'Print'|trans, true),
|
||||
{'id': 'printView', 'class': 'btn'},
|
||||
'print_view'
|
||||
@ -247,6 +248,7 @@
|
||||
|
||||
{{ link_or_button(
|
||||
'#',
|
||||
null,
|
||||
get_icon('b_insrow', 'Copy to clipboard'|trans, true),
|
||||
{'id': 'copyToClipBoard', 'class': 'btn'}
|
||||
) }}
|
||||
@ -255,20 +257,23 @@
|
||||
{% if not operations.has_procedure %}
|
||||
{% if operations.has_export_link %}
|
||||
{{ link_or_button(
|
||||
url('/table/export', operations.url_params),
|
||||
url('/table/export'),
|
||||
operations.url_params,
|
||||
get_icon('b_tblexport', 'Export'|trans, true),
|
||||
{'class': 'btn'}
|
||||
) }}
|
||||
|
||||
{{ link_or_button(
|
||||
url('/table/chart', operations.url_params),
|
||||
url('/table/chart'),
|
||||
operations.url_params,
|
||||
get_icon('b_chart', 'Display chart'|trans, true),
|
||||
{'class': 'btn'}
|
||||
) }}
|
||||
|
||||
{% if operations.has_geometry %}
|
||||
{{ link_or_button(
|
||||
url('/table/gis-visualization', operations.url_params),
|
||||
url('/table/gis-visualization'),
|
||||
operations.url_params,
|
||||
get_icon('b_globe', 'Visualize GIS data'|trans, true),
|
||||
{'class': 'btn'}
|
||||
) }}
|
||||
@ -277,7 +282,8 @@
|
||||
|
||||
<span>
|
||||
{{ link_or_button(
|
||||
url('/view/create', {'db': db, 'table': table, 'sql_query': sql_query, 'printview': true}),
|
||||
url('/view/create'),
|
||||
{'db': db, 'table': table, 'sql_query': sql_query, 'printview': true},
|
||||
get_icon('b_view_add', 'Create view'|trans, true),
|
||||
{'class': 'btn create_view ajax'}
|
||||
) }}
|
||||
|
||||
@ -29,12 +29,12 @@
|
||||
{% set columns_count = index.getColumnCount() %}
|
||||
<tr class="noclick">
|
||||
<td rowspan="{{ columns_count }}" class="edit_index print_ignore ajax">
|
||||
<a class="ajax" href="{{ url('/table/indexes') }}" data-post="{{ get_common(url_params|merge({'index': index.getName()}), '') }}">
|
||||
<a class="ajax" href="{{ url('/table/indexes') }}" data-post="{{ get_common(url_params|merge({'index': index.getName()}), '', false) }}">
|
||||
{{ get_icon('b_edit', 'Edit'|trans) }}
|
||||
</a>
|
||||
</td>
|
||||
<td rowspan="{{ columns_count }}" class="rename_index print_ignore ajax" >
|
||||
<a class="ajax" href="{{ url('/table/indexes/rename') }}" data-post="{{ get_common(url_params|merge({'index': index.getName()}), '') }}">
|
||||
<a class="ajax" href="{{ url('/table/indexes/rename') }}" data-post="{{ get_common(url_params|merge({'index': index.getName()}), '', false) }}">
|
||||
{{ get_icon('b_rename', 'Rename'|trans) }}
|
||||
</a>
|
||||
</td>
|
||||
@ -53,7 +53,8 @@
|
||||
|
||||
<input type="hidden" class="drop_primary_key_index_msg" value="{{ index_params.sql_query }}">
|
||||
{{ link_or_button(
|
||||
url('/sql', url_params|merge(index_params)),
|
||||
url('/sql'),
|
||||
url_params|merge(index_params),
|
||||
get_icon('b_drop', 'Drop'|trans),
|
||||
{'class': 'drop_primary_key_index_anchor ajax'}
|
||||
) }}
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
'itemType': type,
|
||||
'itemName': item,
|
||||
'dbName': database
|
||||
}, '') }}">{{ get_icon('show', 'Unhide'|trans) }}</a>
|
||||
}, '', false) }}">{{ get_icon('show', 'Unhide'|trans) }}</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
@ -43,12 +43,12 @@
|
||||
<td colspan="6" class="text-center">
|
||||
{% if has_previous %}
|
||||
{% if has_icons %}
|
||||
<a href="{{ url('/server/binlog') }}" data-post="{{ get_common(previous_params, '') }}" title="
|
||||
<a href="{{ url('/server/binlog') }}" data-post="{{ get_common(previous_params, '', false) }}" title="
|
||||
{%- trans %}Previous{% context %}Previous page{% endtrans %}">
|
||||
«
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="{{ url('/server/binlog') }}" data-post="{{ get_common(previous_params, '') }}">
|
||||
<a href="{{ url('/server/binlog') }}" data-post="{{ get_common(previous_params, '', false) }}">
|
||||
{% trans %}Previous{% context %}Previous page{% endtrans %} «
|
||||
</a>
|
||||
{% endif %}
|
||||
@ -56,11 +56,11 @@
|
||||
{% endif %}
|
||||
|
||||
{% if is_full_query %}
|
||||
<a href="{{ url('/server/binlog') }}" data-post="{{ get_common(full_queries_params, '') }}" title="{% trans 'Truncate shown queries' %}">
|
||||
<a href="{{ url('/server/binlog') }}" data-post="{{ get_common(full_queries_params, '', false) }}" title="{% trans 'Truncate shown queries' %}">
|
||||
<img src="{{ image_path }}s_partialtext.png" alt="{% trans 'Truncate shown queries' %}">
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="{{ url('/server/binlog') }}" data-post="{{ get_common(full_queries_params, '') }}" title="{% trans 'Show full queries' %}">
|
||||
<a href="{{ url('/server/binlog') }}" data-post="{{ get_common(full_queries_params, '', false) }}" title="{% trans 'Show full queries' %}">
|
||||
<img src="{{ image_path }}s_fulltext.png" alt="{% trans 'Show full queries' %}">
|
||||
</a>
|
||||
{% endif %}
|
||||
@ -68,12 +68,12 @@
|
||||
{% if has_next %}
|
||||
-
|
||||
{% if has_icons %}
|
||||
<a href="{{ url('/server/binlog') }}" data-post="{{ get_common(next_params, '') }}" title="
|
||||
<a href="{{ url('/server/binlog') }}" data-post="{{ get_common(next_params, '', false) }}" title="
|
||||
{%- trans %}Next{% context %}Next page{% endtrans %}">
|
||||
»
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="{{ url('/server/binlog') }}" data-post="{{ get_common(next_params, '') }}">
|
||||
<a href="{{ url('/server/binlog') }}" data-post="{{ get_common(next_params, '', false) }}">
|
||||
{% trans %}Next{% context %}Next page{% endtrans %} »
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
@ -315,7 +315,7 @@
|
||||
<div class="row">
|
||||
<ul>
|
||||
<li class="li_switch_dbstats">
|
||||
<a href="{{ url('/server/databases') }}" data-post="{{ get_common({'statistics': '1'}, '') }}" title="{% trans 'Enable statistics' %}">
|
||||
<a href="{{ url('/server/databases') }}" data-post="{{ get_common({'statistics': '1'}, '', false) }}" title="{% trans 'Enable statistics' %}">
|
||||
<strong>{% trans 'Enable statistics' %}</strong>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
<div class="card mb-2">
|
||||
<div class="card-header">{% trans 'Master replication' %}</div>
|
||||
<div class="card-body">
|
||||
{% apply format('<a href="' ~ url('/server/replication') ~ '" data-post="' ~ get_common(url_params|merge({'mr_configure': true}), '') ~ '">', '</a>')|raw %}
|
||||
{% apply format('<a href="' ~ url('/server/replication') ~ '" data-post="' ~ get_common(url_params|merge({'mr_configure': true}), '', false) ~ '">', '</a>')|raw %}
|
||||
{% trans %}
|
||||
This server is not configured as master in a replication process. Would you like to %sconfigure%s it?
|
||||
{% endtrans %}
|
||||
|
||||
@ -40,7 +40,7 @@
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ url('/server/replication') }}" data-post="{{ get_common(url_params, '') }}" id="master_addslaveuser_href">
|
||||
<a href="{{ url('/server/replication') }}" data-post="{{ get_common(url_params, '', false) }}" id="master_addslaveuser_href">
|
||||
{% trans 'Add slave replication user' %}
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@ -103,7 +103,7 @@
|
||||
{% apply format('<a href="' ~ url('/server/replication') ~ '" data-post="' ~ get_common(url_params|merge({
|
||||
'sl_configure': true,
|
||||
'repl_clear_scr': true
|
||||
})) ~ '">', '</a>')|raw %}
|
||||
}), '', false) ~ '">', '</a>')|raw %}
|
||||
{% trans 'This server is not configured as slave in a replication process. Would you like to %sconfigure%s it?' %}
|
||||
{% endapply %}
|
||||
{% endif %}
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<th>{% trans 'Processes' %}</th>
|
||||
{% for column in columns %}
|
||||
<th scope="col">
|
||||
<a href="{{ url('/server/status/processes') }}" data-post="{{ get_common(column.params) }}" class="sortlink">
|
||||
<a href="{{ url('/server/status/processes') }}" data-post="{{ get_common(column.params, '', false) }}" class="sortlink">
|
||||
{{ column.name }}
|
||||
{% if column.is_sorted %}
|
||||
<img class="icon ic_s_desc soimg" alt="
|
||||
@ -15,7 +15,7 @@
|
||||
{% endif %}
|
||||
</a>
|
||||
{% if column.has_full_query %}
|
||||
<a href="{{ url('/server/status/processes') }}" data-post="{{ get_common(refresh_params, '') }}">
|
||||
<a href="{{ url('/server/status/processes') }}" data-post="{{ get_common(refresh_params, '', false) }}">
|
||||
{% if column.is_full %}
|
||||
{{ get_image(
|
||||
's_partialtext',
|
||||
@ -40,7 +40,7 @@
|
||||
{% for row in rows %}
|
||||
<tr>
|
||||
<td>
|
||||
<a class="ajax kill_process" href="{{ url('/server/status/processes/kill/' ~ row.id) }}" data-post="{{ get_common({'kill': row.id}, '') }}">
|
||||
<a class="ajax kill_process" href="{{ url('/server/status/processes/kill/' ~ row.id) }}" data-post="{{ get_common({'kill': row.id}, '', false) }}">
|
||||
{% trans 'Kill' %}
|
||||
</a>
|
||||
</td>
|
||||
|
||||
@ -58,7 +58,7 @@
|
||||
</a>
|
||||
|
|
||||
<a class="delete-server" href="{{ get_common(server.params.remove) }}" data-post="
|
||||
{{- get_common({ token: server.params.token }, '') }}">
|
||||
{{- get_common({ token: server.params.token }, '', false) }}">
|
||||
{% trans 'Delete' %}
|
||||
</a>
|
||||
</small>
|
||||
|
||||
@ -9,7 +9,8 @@
|
||||
<legend>{% trans 'Query results operations' %}</legend>
|
||||
<span>
|
||||
{{ link_or_button(
|
||||
url('/view/create', {'db': db, 'table': table, 'printview': '1', 'sql_query': sql_query}),
|
||||
url('/view/create'),
|
||||
{'db': db, 'table': table, 'printview': '1', 'sql_query': sql_query},
|
||||
get_icon('b_view_add', 'Create view'|trans, true),
|
||||
{'class': 'create_view ajax btn'}
|
||||
) }}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<span class="curr_value">{{ current_value }}</span>
|
||||
<a href="{{ url('/browse-foreigners') }}" data-post="{{ get_common(params, '') }}" class="ajax browse_foreign">
|
||||
<a href="{{ url('/browse-foreigners') }}" data-post="{{ get_common(params, '', false) }}" class="ajax browse_foreign">
|
||||
{% trans 'Browse foreign values' %}
|
||||
</a>
|
||||
|
||||
@ -313,7 +313,7 @@
|
||||
<ul class="list-group list-group-flush" id="tbl_maintenance">
|
||||
{% if storage_engine in ['MYISAM', 'ARIA', 'INNODB', 'BERKELEYDB', 'TOKUDB'] %}
|
||||
<li class="list-group-item">
|
||||
<a href="{{ url('/table/maintenance/analyze') }}" data-post="{{ get_common({'db': db, 'table': table, 'selected_tbl': [table]}) }}">
|
||||
<a href="{{ url('/table/maintenance/analyze') }}" data-post="{{ get_common({'db': db, 'table': table, 'selected_tbl': [table]}, '', false) }}">
|
||||
{% trans 'Analyze table' %}
|
||||
</a>
|
||||
{{ show_mysql_docu('ANALYZE_TABLE') }}
|
||||
@ -322,7 +322,7 @@
|
||||
|
||||
{% if storage_engine in ['MYISAM', 'ARIA', 'INNODB', 'TOKUDB'] %}
|
||||
<li class="list-group-item">
|
||||
<a href="{{ url('/table/maintenance/check') }}" data-post="{{ get_common({'db': db, 'table': table, 'selected_tbl': [table]}) }}">
|
||||
<a href="{{ url('/table/maintenance/check') }}" data-post="{{ get_common({'db': db, 'table': table, 'selected_tbl': [table]}, '', false) }}">
|
||||
{% trans 'Check table' %}
|
||||
</a>
|
||||
{{ show_mysql_docu('CHECK_TABLE') }}
|
||||
@ -330,7 +330,7 @@
|
||||
{% endif %}
|
||||
|
||||
<li class="list-group-item">
|
||||
<a href="{{ url('/table/maintenance/checksum') }}" data-post="{{ get_common({'db': db, 'table': table, 'selected_tbl': [table]}) }}">
|
||||
<a href="{{ url('/table/maintenance/checksum') }}" data-post="{{ get_common({'db': db, 'table': table, 'selected_tbl': [table]}, '', false) }}">
|
||||
{% trans 'Checksum table' %}
|
||||
</a>
|
||||
{{ show_mysql_docu('CHECKSUM_TABLE') }}
|
||||
@ -338,7 +338,7 @@
|
||||
|
||||
{% if storage_engine == 'INNODB' %}
|
||||
<li class="list-group-item">
|
||||
<a class="maintain_action ajax" href="{{ url('/sql') }}" data-post="{{ get_common(url_params|merge({'sql_query': 'ALTER TABLE ' ~ backquote(table) ~ ' ENGINE = InnoDB;'})) }}">
|
||||
<a class="maintain_action ajax" href="{{ url('/sql') }}" data-post="{{ get_common(url_params|merge({'sql_query': 'ALTER TABLE ' ~ backquote(table) ~ ' ENGINE = InnoDB;'}), '', false) }}">
|
||||
{% trans 'Defragment table' %}
|
||||
</a>
|
||||
{{ show_mysql_docu('InnoDB_File_Defragmenting') }}
|
||||
@ -350,7 +350,7 @@
|
||||
'sql_query': 'FLUSH TABLE ' ~ backquote(table),
|
||||
'message_to_show': 'Table %s has been flushed.'|trans|format(table|e),
|
||||
'reload': true
|
||||
})) }}">
|
||||
}), '', false) }}">
|
||||
{% trans 'Flush the table (FLUSH)' %}
|
||||
</a>
|
||||
{{ show_mysql_docu('FLUSH') }}
|
||||
@ -358,7 +358,7 @@
|
||||
|
||||
{% if storage_engine in ['MYISAM', 'ARIA', 'INNODB', 'BERKELEYDB', 'TOKUDB'] %}
|
||||
<li class="list-group-item">
|
||||
<a href="{{ url('/table/maintenance/optimize') }}" data-post="{{ get_common({'db': db, 'table': table, 'selected_tbl': [table]}) }}">
|
||||
<a href="{{ url('/table/maintenance/optimize') }}" data-post="{{ get_common({'db': db, 'table': table, 'selected_tbl': [table]}, '', false) }}">
|
||||
{% trans 'Optimize table' %}
|
||||
</a>
|
||||
{{ show_mysql_docu('OPTIMIZE_TABLE') }}
|
||||
@ -367,7 +367,7 @@
|
||||
|
||||
{% if storage_engine in ['MYISAM', 'ARIA'] %}
|
||||
<li class="list-group-item">
|
||||
<a href="{{ url('/table/maintenance/repair') }}" data-post="{{ get_common({'db': db, 'table': table, 'selected_tbl': [table]}) }}">
|
||||
<a href="{{ url('/table/maintenance/repair') }}" data-post="{{ get_common({'db': db, 'table': table, 'selected_tbl': [table]}, '', false) }}">
|
||||
{% trans 'Repair table' %}
|
||||
</a>
|
||||
{{ show_mysql_docu('REPAIR_TABLE') }}
|
||||
@ -383,12 +383,13 @@
|
||||
{% if not is_view %}
|
||||
<li class="list-group-item">
|
||||
{{ link_or_button(
|
||||
url('/sql', url_params|merge({
|
||||
url('/sql'),
|
||||
url_params|merge({
|
||||
'sql_query': 'TRUNCATE TABLE ' ~ backquote(db) ~ '.' ~ backquote(table),
|
||||
'goto': url('/table/structure'),
|
||||
'reload': true,
|
||||
'message_to_show': 'Table %s has been emptied.'|trans|format(table)|e
|
||||
})),
|
||||
}),
|
||||
'Empty the table (TRUNCATE)'|trans,
|
||||
{
|
||||
'id': 'truncate_tbl_anchor',
|
||||
@ -400,14 +401,15 @@
|
||||
{% endif %}
|
||||
<li class="list-group-item">
|
||||
{{ link_or_button(
|
||||
url('/sql', url_params|merge({
|
||||
url('/sql'),
|
||||
url_params|merge({
|
||||
'sql_query': 'DROP TABLE ' ~ backquote(db) ~ '.' ~ backquote(table),
|
||||
'goto': url('/database/operations'),
|
||||
'reload': true,
|
||||
'purge': true,
|
||||
'message_to_show': is_view ? 'View %s has been dropped.'|trans|format(table)|e : 'Table %s has been dropped.'|trans|format(table)|e,
|
||||
'table': table
|
||||
})),
|
||||
}),
|
||||
'Delete the table (DROP)'|trans,
|
||||
{
|
||||
'id': 'drop_tbl_anchor',
|
||||
|
||||
@ -23,14 +23,15 @@
|
||||
<div class="card-body">
|
||||
<div class="card-text">
|
||||
{{ link_or_button(
|
||||
url('/sql', url_params|merge({
|
||||
url('/sql'),
|
||||
url_params|merge({
|
||||
'sql_query': 'DROP VIEW ' ~ backquote(table),
|
||||
'goto': url('/table/structure'),
|
||||
'reload': true,
|
||||
'purge': true,
|
||||
'message_to_show': 'View %s has been dropped.'|trans|format(table)|e,
|
||||
'table': table
|
||||
})),
|
||||
}),
|
||||
'Delete the view (DROP)'|trans,
|
||||
{
|
||||
'id': 'drop_view_anchor',
|
||||
|
||||
@ -22,9 +22,8 @@
|
||||
{% if one_key['constraint'] is defined %}
|
||||
<input type="hidden" class="drop_foreign_key_msg" value="
|
||||
{{- js_msg }}">
|
||||
{% set drop_url = url('/sql', this_params) %}
|
||||
{% set drop_str = get_icon('b_drop', 'Drop'|trans) %}
|
||||
{{ link_or_button(drop_url, drop_str, {'class': 'drop_foreign_key_anchor ajax'}) }}
|
||||
{{ link_or_button(url('/sql'), this_params, drop_str, {'class': 'drop_foreign_key_anchor ajax'}) }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
value="{{ criteria_values[column_index] }}"
|
||||
{% endif %}>
|
||||
<a class="ajax browse_foreign" href="{{ url('/browse-foreigners') }}" data-post="
|
||||
{{- get_common({'db': db, 'table': table}, '') -}}
|
||||
{{- get_common({'db': db, 'table': table}, '', false) -}}
|
||||
&field={{ column_name|url_encode }}&fieldkey=
|
||||
{{- column_index }}&fromsearch=1">
|
||||
{{ get_icon('b_browse', 'Browse foreign values'|trans) }}
|
||||
@ -36,7 +36,7 @@
|
||||
{% if in_fbs %}
|
||||
{% set edit_str = get_icon('b_edit', 'Edit/Insert'|trans) %}
|
||||
<span class="open_search_gis_editor">
|
||||
{{ link_or_button(url('/gis-data-editor'), edit_str, [], '_blank') }}
|
||||
{{ link_or_button(url('/gis-data-editor'), [], edit_str, [], '_blank') }}
|
||||
</span>
|
||||
{% endif %}
|
||||
{% elseif column_type starts with 'enum'
|
||||
|
||||
@ -80,7 +80,7 @@
|
||||
'db': db,
|
||||
'table': table,
|
||||
'partition_name': partition.getName(),
|
||||
}) }}">
|
||||
}, '', false) }}">
|
||||
{{ get_icon('b_search', 'Analyze'|trans) }}
|
||||
</a>
|
||||
</td>
|
||||
@ -90,7 +90,7 @@
|
||||
'db': db,
|
||||
'table': table,
|
||||
'partition_name': partition.getName(),
|
||||
}) }}">
|
||||
}, '', false) }}">
|
||||
{{ get_icon('eye', 'Check'|trans) }}
|
||||
</a>
|
||||
</td>
|
||||
@ -100,7 +100,7 @@
|
||||
'db': db,
|
||||
'table': table,
|
||||
'partition_name': partition.getName(),
|
||||
}) }}">
|
||||
}, '', false) }}">
|
||||
{{ get_icon('normalize', 'Optimize'|trans) }}
|
||||
</a>
|
||||
</td>
|
||||
@ -110,7 +110,7 @@
|
||||
'db': db,
|
||||
'table': table,
|
||||
'partition_name': partition.getName(),
|
||||
}) }}">
|
||||
}, '', false) }}">
|
||||
{{ get_icon('s_tbl', 'Rebuild'|trans) }}
|
||||
</a>
|
||||
</td>
|
||||
@ -120,7 +120,7 @@
|
||||
'db': db,
|
||||
'table': table,
|
||||
'partition_name': partition.getName(),
|
||||
}) }}">
|
||||
}, '', false) }}">
|
||||
{{ get_icon('b_tblops', 'Repair'|trans) }}
|
||||
</a>
|
||||
</td>
|
||||
@ -130,7 +130,7 @@
|
||||
'db': db,
|
||||
'table': table,
|
||||
'partition_name': partition.getName(),
|
||||
}) }}">
|
||||
}, '', false) }}">
|
||||
{{ get_icon('b_empty', 'Truncate'|trans) }}
|
||||
</a>
|
||||
</td>
|
||||
@ -141,7 +141,7 @@
|
||||
'db': db,
|
||||
'table': table,
|
||||
'partition_name': partition.getName(),
|
||||
}) }}">
|
||||
}, '', false) }}">
|
||||
{{ get_icon('b_drop', 'Drop'|trans) }}
|
||||
</a>
|
||||
</td>
|
||||
@ -194,11 +194,12 @@
|
||||
<input class="btn btn-secondary" type="submit" value="{% trans 'Partition table' %}">
|
||||
{% else %}
|
||||
{{ link_or_button(
|
||||
url('/sql', {
|
||||
url('/sql'),
|
||||
{
|
||||
'db': db,
|
||||
'table': table,
|
||||
'sql_query': 'ALTER TABLE ' ~ backquote(table) ~ ' REMOVE PARTITIONING'
|
||||
}),
|
||||
},
|
||||
'Remove partitioning'|trans, {
|
||||
'class': 'btn btn-secondary ajax',
|
||||
'id': 'remove_partitioning'
|
||||
|
||||
@ -117,7 +117,7 @@
|
||||
'dropped_column': row['Field'],
|
||||
'purge': true,
|
||||
'message_to_show': 'Column %s has been dropped.'|trans|format(row['Field']|e)
|
||||
}) }}">
|
||||
}, '', false) }}">
|
||||
{{ get_icon('b_drop', 'Drop'|trans) }}
|
||||
</a>
|
||||
</td>
|
||||
@ -142,7 +142,7 @@
|
||||
'table': table,
|
||||
'sql_query': 'ALTER TABLE ' ~ backquote(table) ~ (primary ? ' DROP PRIMARY KEY,') ~ ' ADD PRIMARY KEY(' ~ backquote(row['Field']) ~ ');',
|
||||
'message_to_show': 'A primary key has been added on %s.'|trans|format(row['Field']|e)
|
||||
}) }}">
|
||||
}, '', false) }}">
|
||||
{{ get_icon('b_primary', 'Primary'|trans) }}
|
||||
</a>
|
||||
{% endif %}
|
||||
@ -157,7 +157,7 @@
|
||||
'table': table,
|
||||
'sql_query': 'ALTER TABLE ' ~ backquote(table) ~ ' ADD UNIQUE(' ~ backquote(row['Field']) ~ ');',
|
||||
'message_to_show': 'An index has been added on %s.'|trans|format(row['Field']|e)
|
||||
}) }}">
|
||||
}, '', false) }}">
|
||||
{{ get_icon('b_unique', 'Unique'|trans) }}
|
||||
</a>
|
||||
{% endif %}
|
||||
@ -172,7 +172,7 @@
|
||||
'table': table,
|
||||
'sql_query': 'ALTER TABLE ' ~ backquote(table) ~ ' ADD INDEX(' ~ backquote(row['Field']) ~ ');',
|
||||
'message_to_show': 'An index has been added on %s.'|trans|format(row['Field']|e)
|
||||
}) }}">
|
||||
}, '', false) }}">
|
||||
{{ get_icon('b_index', 'Index'|trans) }}
|
||||
</a>
|
||||
{% endif %}
|
||||
@ -197,7 +197,7 @@
|
||||
'table': table,
|
||||
'sql_query': 'ALTER TABLE ' ~ backquote(table) ~ ' ADD SPATIAL(' ~ backquote(row['Field']) ~ ');',
|
||||
'message_to_show': 'An index has been added on %s.'|trans|format(row['Field']|e)
|
||||
}) }}">
|
||||
}, '', false) }}">
|
||||
{{ get_icon('b_spatial', 'Spatial'|trans) }}
|
||||
</a>
|
||||
{% endif %}
|
||||
@ -216,7 +216,7 @@
|
||||
'table': table,
|
||||
'sql_query': 'ALTER TABLE ' ~ backquote(table) ~ ' ADD FULLTEXT(' ~ backquote(row['Field']) ~ ');',
|
||||
'message_to_show': 'An index has been added on %s.'|trans|format(row['Field']|e)
|
||||
}) }}">
|
||||
}, '', false) }}">
|
||||
{{ get_icon('b_ftext', 'Fulltext'|trans) }}
|
||||
</a>
|
||||
{% else %}
|
||||
@ -235,7 +235,7 @@
|
||||
~ ' GROUP BY ' ~ backquote(row['Field'])
|
||||
~ ' ORDER BY ' ~ backquote(row['Field']),
|
||||
'is_browse_distinct': true
|
||||
}) }}">
|
||||
}, '', false) }}">
|
||||
{{ get_icon('b_browse', 'Distinct values'|trans) }}
|
||||
</a>
|
||||
</li>
|
||||
@ -326,7 +326,8 @@
|
||||
<div id="structure-action-links">
|
||||
{% if tbl_is_view and not db_is_system_schema %}
|
||||
{{ link_or_button(
|
||||
url('/view/create', {'db': db, 'table': table}),
|
||||
url('/view/create'),
|
||||
{'db': db, 'table': table},
|
||||
get_icon('b_edit', 'Edit view'|trans, true)
|
||||
) }}
|
||||
{% endif %}
|
||||
@ -339,7 +340,7 @@
|
||||
'table': table,
|
||||
'sql_query': 'SELECT * FROM ' ~ backquote(table) ~ ' PROCEDURE ANALYSE()',
|
||||
'session_max_rows': 'all'
|
||||
}) }}">
|
||||
}, '', false) }}">
|
||||
{{ get_icon(
|
||||
'b_tblanalyse',
|
||||
'Propose table structure'|trans,
|
||||
@ -432,7 +433,7 @@
|
||||
'db': db,
|
||||
'table': table,
|
||||
'index': index.getName()
|
||||
}, '') }}">
|
||||
}, '', false) }}">
|
||||
{{ get_icon('b_edit', 'Edit'|trans) }}
|
||||
</a>
|
||||
</td>
|
||||
@ -441,7 +442,7 @@
|
||||
'db': db,
|
||||
'table': table,
|
||||
'index': index.getName()
|
||||
}, '') }}">
|
||||
}, '', false) }}">
|
||||
{{ get_icon('b_rename', 'Rename'|trans) }}
|
||||
</a>
|
||||
</td>
|
||||
@ -460,7 +461,8 @@
|
||||
|
||||
<input type="hidden" class="drop_primary_key_index_msg" value="{{ index_params.sql_query }}">
|
||||
{{ link_or_button(
|
||||
url('/sql', index_params|merge({'db': db, 'table': table})),
|
||||
url('/sql'),
|
||||
index_params|merge({'db': db, 'table': table}),
|
||||
get_icon('b_drop', 'Drop'|trans),
|
||||
{'class': 'drop_primary_key_index_anchor ajax'}
|
||||
) }}
|
||||
|
||||
@ -60,7 +60,7 @@
|
||||
{{- get_common(url_params|merge({
|
||||
'version': version['version'],
|
||||
'submit_delete_version': true
|
||||
}), '') }}">
|
||||
}), '', false) }}">
|
||||
{{ get_icon('b_drop', 'Delete version'|trans) }}
|
||||
</a>
|
||||
</td>
|
||||
@ -69,14 +69,14 @@
|
||||
{{- get_common(url_params|merge({
|
||||
'version': version['version'],
|
||||
'report': 'true'
|
||||
}), '') }}">
|
||||
}), '', false) }}">
|
||||
{{ get_icon('b_report', 'Tracking report'|trans) }}
|
||||
</a>
|
||||
<a href="{{ url('/table/tracking') }}" data-post="
|
||||
{{- get_common(url_params|merge({
|
||||
'version': version['version'],
|
||||
'snapshot': 'true'
|
||||
}), '') }}">
|
||||
}), '', false) }}">
|
||||
{{ get_icon('b_props', 'Structure snapshot'|trans) }}
|
||||
</a>
|
||||
</td>
|
||||
|
||||
@ -200,6 +200,7 @@ abstract class AbstractTestCase extends TestCase
|
||||
global $PMA_Config;
|
||||
$PMA_Config = new Config();
|
||||
$PMA_Config->set('environment', 'development');
|
||||
$PMA_Config->set('URLQueryEncryption', false);
|
||||
}
|
||||
|
||||
protected function setTheme(): void
|
||||
|
||||
@ -6,6 +6,7 @@ namespace PhpMyAdmin\Tests;
|
||||
|
||||
use PhpMyAdmin\Core;
|
||||
use PhpMyAdmin\Sanitize;
|
||||
use PhpMyAdmin\Url;
|
||||
use stdClass;
|
||||
use function hash;
|
||||
use function htmlspecialchars;
|
||||
@ -15,6 +16,7 @@ use function ob_get_contents;
|
||||
use function ob_start;
|
||||
use function preg_quote;
|
||||
use function serialize;
|
||||
use function str_repeat;
|
||||
|
||||
class CoreTest extends AbstractNetworkTestCase
|
||||
{
|
||||
@ -33,6 +35,7 @@ class CoreTest extends AbstractNetworkTestCase
|
||||
$GLOBALS['db'] = '';
|
||||
$GLOBALS['table'] = '';
|
||||
$GLOBALS['PMA_PHP_SELF'] = 'http://example.net/';
|
||||
$GLOBALS['PMA_Config']->set('URLQueryEncryption', false);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1437,4 +1440,61 @@ class CoreTest extends AbstractNetworkTestCase
|
||||
$this->assertArrayHasKey('test', $_POST);
|
||||
$this->assertEquals('test', $_POST['test']);
|
||||
}
|
||||
|
||||
public function testPopulateRequestWithEncryptedQueryParams(): void
|
||||
{
|
||||
global $PMA_Config;
|
||||
|
||||
$_SESSION = [];
|
||||
$PMA_Config->set('URLQueryEncryption', true);
|
||||
$PMA_Config->set('URLQueryEncryptionSecretKey', str_repeat('a', 32));
|
||||
|
||||
$_GET = ['pos' => '0', 'eq' => Url::encryptQuery('{"db":"test_db","table":"test_table"}')];
|
||||
$_REQUEST = $_GET;
|
||||
|
||||
Core::populateRequestWithEncryptedQueryParams();
|
||||
|
||||
$expected = ['pos' => '0', 'db' => 'test_db', 'table' => 'test_table'];
|
||||
|
||||
$this->assertEquals($expected, $_GET);
|
||||
$this->assertEquals($expected, $_REQUEST);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $encrypted
|
||||
* @param string[] $decrypted
|
||||
*
|
||||
* @dataProvider providerForTestPopulateRequestWithEncryptedQueryParamsWithInvalidParam
|
||||
*/
|
||||
public function testPopulateRequestWithEncryptedQueryParamsWithInvalidParam(
|
||||
array $encrypted,
|
||||
array $decrypted
|
||||
): void {
|
||||
global $PMA_Config;
|
||||
|
||||
$_SESSION = [];
|
||||
$PMA_Config->set('URLQueryEncryption', true);
|
||||
$PMA_Config->set('URLQueryEncryptionSecretKey', str_repeat('a', 32));
|
||||
|
||||
$_GET = $encrypted;
|
||||
$_REQUEST = $encrypted;
|
||||
|
||||
Core::populateRequestWithEncryptedQueryParams();
|
||||
|
||||
$this->assertEquals($decrypted, $_GET);
|
||||
$this->assertEquals($decrypted, $_REQUEST);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[][][]
|
||||
*/
|
||||
public function providerForTestPopulateRequestWithEncryptedQueryParamsWithInvalidParam(): array
|
||||
{
|
||||
return [
|
||||
[[], []],
|
||||
[['eq' => []], []],
|
||||
[['eq' => ''], []],
|
||||
[['eq' => 'invalid'], []],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
106
test/classes/Crypto/CryptoTest.php
Normal file
106
test/classes/Crypto/CryptoTest.php
Normal file
@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Tests\Crypto;
|
||||
|
||||
use PhpMyAdmin\Crypto\Crypto;
|
||||
use PhpMyAdmin\Tests\AbstractTestCase;
|
||||
use function str_repeat;
|
||||
use function mb_strlen;
|
||||
|
||||
/**
|
||||
* @covers \PhpMyAdmin\Crypto\Crypto
|
||||
*/
|
||||
class CryptoTest extends AbstractTestCase
|
||||
{
|
||||
public function testWithValidKeyFromConfig(): void
|
||||
{
|
||||
global $PMA_Config;
|
||||
|
||||
$_SESSION = [];
|
||||
$PMA_Config->set('URLQueryEncryptionSecretKey', str_repeat('a', 32));
|
||||
|
||||
$cryptoWithSodium = new Crypto();
|
||||
$encrypted = $cryptoWithSodium->encrypt('test');
|
||||
$this->assertNotSame('test', $encrypted);
|
||||
$this->assertSame('test', $cryptoWithSodium->decrypt($encrypted));
|
||||
$this->assertArrayNotHasKey('URLQueryEncryptionSecretKey', $_SESSION);
|
||||
|
||||
$cryptoWithPhpseclib = new Crypto(true);
|
||||
$encrypted = $cryptoWithPhpseclib->encrypt('test');
|
||||
$this->assertNotSame('test', $encrypted);
|
||||
$this->assertSame('test', $cryptoWithPhpseclib->decrypt($encrypted));
|
||||
$this->assertArrayNotHasKey('URLQueryEncryptionSecretKey', $_SESSION);
|
||||
}
|
||||
|
||||
public function testWithValidKeyFromSession(): void
|
||||
{
|
||||
global $PMA_Config;
|
||||
|
||||
$_SESSION = ['URLQueryEncryptionSecretKey' => str_repeat('a', 32)];
|
||||
$PMA_Config->set('URLQueryEncryptionSecretKey', '');
|
||||
|
||||
$cryptoWithSodium = new Crypto();
|
||||
$encrypted = $cryptoWithSodium->encrypt('test');
|
||||
$this->assertNotSame('test', $encrypted);
|
||||
$this->assertSame('test', $cryptoWithSodium->decrypt($encrypted));
|
||||
$this->assertArrayHasKey('URLQueryEncryptionSecretKey', $_SESSION);
|
||||
|
||||
$cryptoWithPhpseclib = new Crypto(true);
|
||||
$encrypted = $cryptoWithPhpseclib->encrypt('test');
|
||||
$this->assertNotSame('test', $encrypted);
|
||||
$this->assertSame('test', $cryptoWithPhpseclib->decrypt($encrypted));
|
||||
$this->assertArrayHasKey('URLQueryEncryptionSecretKey', $_SESSION);
|
||||
}
|
||||
|
||||
public function testWithNewSessionKey(): void
|
||||
{
|
||||
global $PMA_Config;
|
||||
|
||||
$_SESSION = [];
|
||||
$PMA_Config->set('URLQueryEncryptionSecretKey', '');
|
||||
|
||||
$cryptoWithSodium = new Crypto();
|
||||
$encrypted = $cryptoWithSodium->encrypt('test');
|
||||
$this->assertNotSame('test', $encrypted);
|
||||
$this->assertSame('test', $cryptoWithSodium->decrypt($encrypted));
|
||||
$this->assertArrayHasKey('URLQueryEncryptionSecretKey', $_SESSION);
|
||||
$this->assertEquals(32, mb_strlen($_SESSION['URLQueryEncryptionSecretKey'], '8bit'));
|
||||
|
||||
$cryptoWithPhpseclib = new Crypto(true);
|
||||
$encrypted = $cryptoWithPhpseclib->encrypt('test');
|
||||
$this->assertNotSame('test', $encrypted);
|
||||
$this->assertSame('test', $cryptoWithPhpseclib->decrypt($encrypted));
|
||||
$this->assertArrayHasKey('URLQueryEncryptionSecretKey', $_SESSION);
|
||||
$this->assertEquals(32, mb_strlen($_SESSION['URLQueryEncryptionSecretKey'], '8bit'));
|
||||
}
|
||||
|
||||
public function testDecryptWithInvalidKey(): void
|
||||
{
|
||||
global $PMA_Config;
|
||||
|
||||
$_SESSION = [];
|
||||
$PMA_Config->set('URLQueryEncryptionSecretKey', str_repeat('a', 32));
|
||||
|
||||
$cryptoWithSodium = new Crypto();
|
||||
$encrypted = $cryptoWithSodium->encrypt('test');
|
||||
$this->assertNotSame('test', $encrypted);
|
||||
$this->assertSame('test', $cryptoWithSodium->decrypt($encrypted));
|
||||
|
||||
$PMA_Config->set('URLQueryEncryptionSecretKey', str_repeat('b', 32));
|
||||
|
||||
$cryptoWithSodium = new Crypto();
|
||||
$this->assertNull($cryptoWithSodium->decrypt($encrypted));
|
||||
|
||||
$cryptoWithPhpseclib = new Crypto(true);
|
||||
$encrypted = $cryptoWithPhpseclib->encrypt('test');
|
||||
$this->assertNotSame('test', $encrypted);
|
||||
$this->assertSame('test', $cryptoWithPhpseclib->decrypt($encrypted));
|
||||
|
||||
$PMA_Config->set('URLQueryEncryptionSecretKey', str_repeat('a', 32));
|
||||
|
||||
$cryptoWithPhpseclib = new Crypto(true);
|
||||
$this->assertNull($cryptoWithPhpseclib->decrypt($encrypted));
|
||||
}
|
||||
}
|
||||
@ -221,6 +221,7 @@ class GeneratorTest extends AbstractTestCase
|
||||
[
|
||||
[
|
||||
'index.php',
|
||||
null,
|
||||
'text',
|
||||
],
|
||||
1000,
|
||||
@ -228,15 +229,17 @@ class GeneratorTest extends AbstractTestCase
|
||||
],
|
||||
[
|
||||
[
|
||||
'index.php?some=parameter',
|
||||
'index.php',
|
||||
['some' => 'parameter'],
|
||||
'text',
|
||||
],
|
||||
20,
|
||||
'<a href="index.php" data-post="some=parameter">text</a>',
|
||||
'<a href="index.php" data-post="some=parameter&lang=en">text</a>',
|
||||
],
|
||||
[
|
||||
[
|
||||
'index.php',
|
||||
null,
|
||||
'text',
|
||||
[],
|
||||
'target',
|
||||
@ -247,6 +250,7 @@ class GeneratorTest extends AbstractTestCase
|
||||
[
|
||||
[
|
||||
'https://mariadb.org/explain_analyzer/analyze/?client=phpMyAdmin&raw_explain=%2B---%2B',
|
||||
null,
|
||||
'text',
|
||||
[],
|
||||
'target',
|
||||
@ -259,6 +263,7 @@ class GeneratorTest extends AbstractTestCase
|
||||
[
|
||||
[
|
||||
'https://mariadb.org/explain_analyzer/analyze/?client=phpMyAdmin&raw_explain=%2B---%2B',
|
||||
null,
|
||||
'text',
|
||||
[],
|
||||
'target',
|
||||
@ -271,6 +276,7 @@ class GeneratorTest extends AbstractTestCase
|
||||
[
|
||||
[
|
||||
'url.php?url=http://phpmyadmin.net/',
|
||||
null,
|
||||
'text',
|
||||
[],
|
||||
'_blank',
|
||||
|
||||
@ -7,6 +7,12 @@ namespace PhpMyAdmin\Tests\Navigation;
|
||||
use PhpMyAdmin\Navigation\NavigationTree;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Tests\AbstractTestCase;
|
||||
use PhpMyAdmin\Url;
|
||||
use ReflectionMethod;
|
||||
use function str_repeat;
|
||||
use function parse_url;
|
||||
use function parse_str;
|
||||
use function htmlspecialchars_decode;
|
||||
|
||||
class NavigationTreeTest extends AbstractTestCase
|
||||
{
|
||||
@ -80,4 +86,37 @@ class NavigationTreeTest extends AbstractTestCase
|
||||
$result = $this->object->renderDbSelect();
|
||||
$this->assertStringContainsString('pma_navigation_select_database', $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testEncryptQueryParams()
|
||||
{
|
||||
global $PMA_Config;
|
||||
|
||||
$_SESSION = [];
|
||||
$PMA_Config->set('URLQueryEncryption', false);
|
||||
$PMA_Config->set('URLQueryEncryptionSecretKey', str_repeat('a', 32));
|
||||
|
||||
$method = new ReflectionMethod($this->object, 'encryptQueryParams');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$link = 'tbl_structure.php?server=1&db=test_db&table=test_table&pos=0';
|
||||
|
||||
$actual = $method->invoke($this->object, $link);
|
||||
$this->assertEquals($link, $actual);
|
||||
|
||||
$PMA_Config->set('URLQueryEncryption', true);
|
||||
|
||||
$actual = $method->invoke($this->object, $link);
|
||||
$this->assertStringStartsWith('tbl_structure.php?server=1&pos=0&eq=', $actual);
|
||||
|
||||
$url = parse_url($actual);
|
||||
parse_str(htmlspecialchars_decode($url['query']), $query);
|
||||
|
||||
$this->assertRegExp('/^[a-zA-Z0-9-_=]+$/', $query['eq']);
|
||||
$decrypted = Url::decryptQuery($query['eq']);
|
||||
$this->assertJson($decrypted);
|
||||
$this->assertSame('{"db":"test_db","table":"test_table"}', $decrypted);
|
||||
}
|
||||
}
|
||||
|
||||
@ -118,7 +118,8 @@ class UserGroupsTest extends AbstractTestCase
|
||||
'viewUsers' => 1,
|
||||
'userGroup' => htmlspecialchars('usergroup'),
|
||||
],
|
||||
''
|
||||
'',
|
||||
false
|
||||
);
|
||||
$this->assertStringContainsString(
|
||||
$url_tag,
|
||||
@ -130,7 +131,8 @@ class UserGroupsTest extends AbstractTestCase
|
||||
'editUserGroup' => 1,
|
||||
'userGroup' => htmlspecialchars('usergroup'),
|
||||
],
|
||||
''
|
||||
'',
|
||||
false
|
||||
);
|
||||
$this->assertStringContainsString(
|
||||
$url_tag,
|
||||
@ -142,7 +144,8 @@ class UserGroupsTest extends AbstractTestCase
|
||||
'deleteUserGroup' => 1,
|
||||
'userGroup' => htmlspecialchars('usergroup'),
|
||||
],
|
||||
''
|
||||
'',
|
||||
false
|
||||
);
|
||||
$this->assertStringContainsString(
|
||||
$url_tag,
|
||||
|
||||
@ -6,6 +6,9 @@ namespace PhpMyAdmin\Tests;
|
||||
|
||||
use PhpMyAdmin\Url;
|
||||
use function urldecode;
|
||||
use function str_repeat;
|
||||
use function parse_str;
|
||||
use function is_string;
|
||||
|
||||
class UrlTest extends AbstractTestCase
|
||||
{
|
||||
@ -20,6 +23,7 @@ class UrlTest extends AbstractTestCase
|
||||
parent::setUp();
|
||||
parent::setLanguage();
|
||||
unset($_COOKIE['pma_lang']);
|
||||
$GLOBALS['PMA_Config']->set('URLQueryEncryption', false);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -179,4 +183,61 @@ class UrlTest extends AbstractTestCase
|
||||
Url::getHiddenFields([])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testBuildHttpQueryWithUrlQueryEncryptionDisabled()
|
||||
{
|
||||
global $PMA_Config;
|
||||
|
||||
$PMA_Config->set('URLQueryEncryption', false);
|
||||
$params = ['db' => 'test_db', 'table' => 'test_table', 'pos' => 0];
|
||||
$this->assertEquals('db=test_db&table=test_table&pos=0', Url::buildHttpQuery($params));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testBuildHttpQueryWithUrlQueryEncryptionEnabled()
|
||||
{
|
||||
global $PMA_Config;
|
||||
|
||||
$_SESSION = [];
|
||||
$PMA_Config->set('URLQueryEncryption', true);
|
||||
$PMA_Config->set('URLQueryEncryptionSecretKey', str_repeat('a', 32));
|
||||
|
||||
$params = ['db' => 'test_db', 'table' => 'test_table', 'pos' => 0];
|
||||
$query = Url::buildHttpQuery($params);
|
||||
$this->assertStringStartsWith('pos=0&eq=', $query);
|
||||
parse_str($query, $queryParams);
|
||||
$this->assertCount(2, $queryParams);
|
||||
$this->assertSame('0', $queryParams['pos']);
|
||||
$this->assertTrue(is_string($queryParams['eq']));
|
||||
$this->assertNotSame('', $queryParams['eq']);
|
||||
$this->assertRegExp('/^[a-zA-Z0-9-_=]+$/', $queryParams['eq']);
|
||||
$decrypted = Url::decryptQuery($queryParams['eq']);
|
||||
$this->assertJson($decrypted);
|
||||
$this->assertSame('{"db":"test_db","table":"test_table"}', $decrypted);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testQueryEncryption()
|
||||
{
|
||||
global $PMA_Config;
|
||||
|
||||
$_SESSION = [];
|
||||
$PMA_Config->set('URLQueryEncryption', true);
|
||||
$PMA_Config->set('URLQueryEncryptionSecretKey', str_repeat('a', 32));
|
||||
|
||||
$query = '{"db":"test_db","table":"test_table"}';
|
||||
$encrypted = Url::encryptQuery($query);
|
||||
$this->assertNotSame($query, $encrypted);
|
||||
$this->assertNotSame('', $encrypted);
|
||||
$this->assertRegExp('/^[a-zA-Z0-9-_=]+$/', $encrypted);
|
||||
$decrypted = Url::decryptQuery($encrypted);
|
||||
$this->assertSame($query, $decrypted);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user