Update website
This commit is contained in:
parent
a0b0d3dae7
commit
ae7ef6ad45
3151 changed files with 566766 additions and 48 deletions
|
@ -0,0 +1,93 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers;
|
||||
|
||||
use PhpMyAdmin\Core;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use function strlen;
|
||||
|
||||
abstract class AbstractController
|
||||
{
|
||||
/** @var Response */
|
||||
protected $response;
|
||||
|
||||
/** @var Template */
|
||||
protected $template;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
*/
|
||||
public function __construct($response, Template $template)
|
||||
{
|
||||
$this->response = $response;
|
||||
$this->template = $template;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $templateData
|
||||
*/
|
||||
protected function render(string $templatePath, array $templateData = []): void
|
||||
{
|
||||
$this->response->addHTML($this->template->render($templatePath, $templateData));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $files
|
||||
*/
|
||||
protected function addScriptFiles(array $files): void
|
||||
{
|
||||
$header = $this->response->getHeader();
|
||||
$scripts = $header->getScripts();
|
||||
$scripts->addFiles($files);
|
||||
}
|
||||
|
||||
protected function hasDatabase(): bool
|
||||
{
|
||||
global $db, $is_db, $errno, $dbi, $message;
|
||||
|
||||
if (isset($is_db) && $is_db) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$is_db = false;
|
||||
if (strlen($db) > 0) {
|
||||
$is_db = $dbi->selectDb($db);
|
||||
// This "Command out of sync" 2014 error may happen, for example
|
||||
// after calling a MySQL procedure; at this point we can't select
|
||||
// the db but it's not necessarily wrong
|
||||
if ($dbi->getError() && $errno == 2014) {
|
||||
$is_db = true;
|
||||
unset($errno);
|
||||
}
|
||||
}
|
||||
|
||||
if (strlen($db) === 0 || ! $is_db) {
|
||||
if ($this->response->isAjax()) {
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON(
|
||||
'message',
|
||||
Message::error(__('No databases selected.'))
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Not a valid db name -> back to the welcome page
|
||||
$params = ['reload' => '1'];
|
||||
if (isset($message)) {
|
||||
$params['message'] = $message;
|
||||
}
|
||||
$uri = './index.php?route=/' . Url::getCommonRaw($params, '&');
|
||||
Core::sendHeaderLocation($uri);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return $is_db;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers;
|
||||
|
||||
use PhpMyAdmin\BrowseForeigners;
|
||||
use PhpMyAdmin\Relation;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
|
||||
/**
|
||||
* Display selection for relational field values
|
||||
*/
|
||||
class BrowseForeignersController extends AbstractController
|
||||
{
|
||||
/** @var BrowseForeigners */
|
||||
private $browseForeigners;
|
||||
|
||||
/** @var Relation */
|
||||
private $relation;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param BrowseForeigners $browseForeigners
|
||||
* @param Relation $relation
|
||||
*/
|
||||
public function __construct($response, Template $template, $browseForeigners, $relation)
|
||||
{
|
||||
parent::__construct($response, $template);
|
||||
$this->browseForeigners = $browseForeigners;
|
||||
$this->relation = $relation;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
$params = [
|
||||
'db' => $_POST['db'] ?? null,
|
||||
'table' => $_POST['table'] ?? null,
|
||||
'field' => $_POST['field'] ?? null,
|
||||
'fieldkey' => $_POST['fieldkey'] ?? null,
|
||||
'data' => $_POST['data'] ?? null,
|
||||
'foreign_showAll' => $_POST['foreign_showAll'] ?? null,
|
||||
'foreign_filter' => $_POST['foreign_filter'] ?? null,
|
||||
];
|
||||
|
||||
if (! isset($params['db'], $params['table'], $params['field'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->response->getFooter()->setMinimal();
|
||||
$header = $this->response->getHeader();
|
||||
$header->disableMenuAndConsole();
|
||||
$header->setBodyId('body_browse_foreigners');
|
||||
|
||||
$foreigners = $this->relation->getForeigners(
|
||||
$params['db'],
|
||||
$params['table']
|
||||
);
|
||||
$foreignLimit = $this->browseForeigners->getForeignLimit(
|
||||
$params['foreign_showAll']
|
||||
);
|
||||
$foreignData = $this->relation->getForeignData(
|
||||
$foreigners,
|
||||
$params['field'],
|
||||
true,
|
||||
$params['foreign_filter'] ?? '',
|
||||
$foreignLimit ?? null,
|
||||
true
|
||||
);
|
||||
|
||||
$this->response->addHTML($this->browseForeigners->getHtmlForRelationalFieldSelection(
|
||||
$params['db'],
|
||||
$params['table'],
|
||||
$params['field'],
|
||||
$foreignData,
|
||||
$params['fieldkey'] ?? '',
|
||||
$params['data'] ?? ''
|
||||
));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
<?php
|
||||
/**
|
||||
* Simple script to set correct charset for changelog
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers;
|
||||
|
||||
use function array_keys;
|
||||
use function file_get_contents;
|
||||
use function htmlspecialchars;
|
||||
use function is_readable;
|
||||
use function ob_get_clean;
|
||||
use function ob_start;
|
||||
use function preg_replace;
|
||||
use function printf;
|
||||
use function readgzfile;
|
||||
use function substr;
|
||||
|
||||
class ChangeLogController extends AbstractController
|
||||
{
|
||||
public function index(): void
|
||||
{
|
||||
$this->response->disable();
|
||||
$this->response->getHeader()->sendHttpHeaders();
|
||||
|
||||
$filename = CHANGELOG_FILE;
|
||||
|
||||
/**
|
||||
* Read changelog.
|
||||
*/
|
||||
// Check if the file is available, some distributions remove these.
|
||||
if (! @is_readable($filename)) {
|
||||
printf(
|
||||
__(
|
||||
'The %s file is not available on this system, please visit ' .
|
||||
'%s for more information.'
|
||||
),
|
||||
$filename,
|
||||
'<a href="https://www.phpmyadmin.net/">phpmyadmin.net</a>'
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Test if the if is in a compressed format
|
||||
if (substr($filename, -3) === '.gz') {
|
||||
ob_start();
|
||||
readgzfile($filename);
|
||||
$changelog = ob_get_clean();
|
||||
} else {
|
||||
$changelog = file_get_contents($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whole changelog in variable.
|
||||
*/
|
||||
$changelog = htmlspecialchars((string) $changelog);
|
||||
|
||||
$github_url = 'https://github.com/phpmyadmin/phpmyadmin/';
|
||||
$faq_url = 'https://docs.phpmyadmin.net/en/latest/faq.html';
|
||||
|
||||
$replaces = [
|
||||
'@(https?://[./a-zA-Z0-9.-_-]*[/a-zA-Z0-9_])@'
|
||||
=> '<a href="url.php?url=\\1">\\1</a>',
|
||||
|
||||
// mail address
|
||||
'/([0-9]{4}-[0-9]{2}-[0-9]{2}) (.+[^ ]) +<(.*@.*)>/i'
|
||||
=> '\\1 <a href="mailto:\\3">\\2</a>',
|
||||
|
||||
// FAQ entries
|
||||
'/FAQ ([0-9]+)\.([0-9a-z]+)/i'
|
||||
=> '<a href="url.php?url=' . $faq_url . '#faq\\1-\\2">FAQ \\1.\\2</a>',
|
||||
|
||||
// GitHub issues
|
||||
'/issue\s*#?([0-9]{4,5}) /i'
|
||||
=> '<a href="url.php?url=' . $github_url . 'issues/\\1">issue #\\1</a> ',
|
||||
|
||||
// CVE/CAN entries
|
||||
'/((CAN|CVE)-[0-9]+-[0-9]+)/'
|
||||
=> '<a href="url.php?url=https://cve.mitre.org/cgi-bin/cvename.cgi?name=\\1">\\1</a>',
|
||||
|
||||
// PMASAentries
|
||||
'/(PMASA-[0-9]+-[0-9]+)/'
|
||||
=> '<a href="url.php?url=https://www.phpmyadmin.net/security/\\1/">\\1</a>',
|
||||
|
||||
// Highlight releases (with links)
|
||||
'/([0-9]+)\.([0-9]+)\.([0-9]+)\.0 (\([0-9-]+\))/'
|
||||
=> '<a id="\\1_\\2_\\3"></a>'
|
||||
. '<a href="url.php?url=' . $github_url . 'commits/RELEASE_\\1_\\2_\\3">'
|
||||
. '\\1.\\2.\\3.0 \\4</a>',
|
||||
'/([0-9]+)\.([0-9]+)\.([0-9]+)\.([1-9][0-9]*) (\([0-9-]+\))/'
|
||||
=> '<a id="\\1_\\2_\\3_\\4"></a>'
|
||||
. '<a href="url.php?url=' . $github_url . 'commits/RELEASE_\\1_\\2_\\3_\\4">'
|
||||
. '\\1.\\2.\\3.\\4 \\5</a>',
|
||||
|
||||
// Highlight releases (not linkable)
|
||||
'/( ### )(.*)/' => '\\1<b>\\2</b>',
|
||||
|
||||
// Links target and rel
|
||||
'/a href="/' => 'a target="_blank" rel="noopener noreferrer" href="',
|
||||
|
||||
];
|
||||
|
||||
$this->response->header('Content-type: text/html; charset=utf-8');
|
||||
|
||||
echo $this->template->render('changelog', [
|
||||
'changelog' => preg_replace(array_keys($replaces), $replaces, $changelog),
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
/**
|
||||
* Displays status of phpMyAdmin configuration storage
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers;
|
||||
|
||||
use PhpMyAdmin\Relation;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
|
||||
class CheckRelationsController extends AbstractController
|
||||
{
|
||||
/** @var Relation */
|
||||
private $relation;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
*/
|
||||
public function __construct($response, Template $template, Relation $relation)
|
||||
{
|
||||
parent::__construct($response, $template);
|
||||
$this->relation = $relation;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $db;
|
||||
|
||||
$params = [
|
||||
'create_pmadb' => $_POST['create_pmadb'] ?? null,
|
||||
'fixall_pmadb' => $_POST['fixall_pmadb'] ?? null,
|
||||
'fix_pmadb' => $_POST['fix_pmadb'] ?? null,
|
||||
];
|
||||
|
||||
// If request for creating the pmadb
|
||||
if (isset($params['create_pmadb']) && $this->relation->createPmaDatabase()) {
|
||||
$this->relation->fixPmaTables('phpmyadmin');
|
||||
}
|
||||
|
||||
// If request for creating all PMA tables.
|
||||
if (isset($params['fixall_pmadb'])) {
|
||||
$this->relation->fixPmaTables($db);
|
||||
}
|
||||
|
||||
$cfgRelation = $this->relation->getRelationsParam();
|
||||
// If request for creating missing PMA tables.
|
||||
if (isset($params['fix_pmadb'])) {
|
||||
$this->relation->fixPmaTables($cfgRelation['db']);
|
||||
}
|
||||
|
||||
$this->response->addHTML($this->relation->getRelationsParamDiagnostic($cfgRelation));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers;
|
||||
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
|
||||
final class ColumnController extends AbstractController
|
||||
{
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template);
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function all(): void
|
||||
{
|
||||
if (! isset($_POST['db'], $_POST['table'])) {
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON(['message' => Message::error()]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->response->addJSON(['columns' => $this->dbi->getColumnNames($_POST['db'], $_POST['table'])]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers;
|
||||
|
||||
use PhpMyAdmin\Config;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use function json_decode;
|
||||
|
||||
final class ConfigController extends AbstractController
|
||||
{
|
||||
/** @var Config */
|
||||
private $config;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
*/
|
||||
public function __construct($response, Template $template, Config $config)
|
||||
{
|
||||
parent::__construct($response, $template);
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
public function get(): void
|
||||
{
|
||||
if (! isset($_POST['key'])) {
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON(['message' => Message::error()]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->response->addJSON(['value' => $this->config->get($_POST['key'])]);
|
||||
}
|
||||
|
||||
public function set(): void
|
||||
{
|
||||
if (! isset($_POST['key'], $_POST['value'])) {
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON(['message' => Message::error()]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$result = $this->config->setUserValue(null, $_POST['key'], json_decode($_POST['value']));
|
||||
|
||||
if ($result === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON(['message' => $result]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Database;
|
||||
|
||||
use PhpMyAdmin\Controllers\AbstractController as Controller;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
|
||||
abstract class AbstractController extends Controller
|
||||
{
|
||||
/** @var string */
|
||||
protected $db;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param string $db Database name
|
||||
*/
|
||||
public function __construct($response, Template $template, $db)
|
||||
{
|
||||
parent::__construct($response, $template);
|
||||
$this->db = $db;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,302 @@
|
|||
<?php
|
||||
/**
|
||||
* Central Columns view/edit
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Database;
|
||||
|
||||
use PhpMyAdmin\Core;
|
||||
use PhpMyAdmin\Database\CentralColumns;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use function is_bool;
|
||||
use function parse_str;
|
||||
use function sprintf;
|
||||
|
||||
class CentralColumnsController extends AbstractController
|
||||
{
|
||||
/** @var CentralColumns */
|
||||
private $centralColumns;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param string $db Database name
|
||||
* @param CentralColumns $centralColumns
|
||||
*/
|
||||
public function __construct($response, Template $template, $db, $centralColumns)
|
||||
{
|
||||
parent::__construct($response, $template, $db);
|
||||
$this->centralColumns = $centralColumns;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $cfg, $db, $message, $pos, $num_cols;
|
||||
|
||||
if (isset($_POST['edit_save'])) {
|
||||
echo $this->editSave([
|
||||
'col_name' => $_POST['col_name'] ?? null,
|
||||
'orig_col_name' => $_POST['orig_col_name'] ?? null,
|
||||
'col_default' => $_POST['col_default'] ?? null,
|
||||
'col_default_sel' => $_POST['col_default_sel'] ?? null,
|
||||
'col_extra' => $_POST['col_extra'] ?? null,
|
||||
'col_isNull' => $_POST['col_isNull'] ?? null,
|
||||
'col_length' => $_POST['col_length'] ?? null,
|
||||
'col_attribute' => $_POST['col_attribute'] ?? null,
|
||||
'col_type' => $_POST['col_type'] ?? null,
|
||||
'collation' => $_POST['collation'] ?? null,
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($_POST['add_new_column'])) {
|
||||
$tmp_msg = $this->addNewColumn([
|
||||
'col_name' => $_POST['col_name'] ?? null,
|
||||
'col_default' => $_POST['col_default'] ?? null,
|
||||
'col_default_sel' => $_POST['col_default_sel'] ?? null,
|
||||
'col_extra' => $_POST['col_extra'] ?? null,
|
||||
'col_isNull' => $_POST['col_isNull'] ?? null,
|
||||
'col_length' => $_POST['col_length'] ?? null,
|
||||
'col_attribute' => $_POST['col_attribute'] ?? null,
|
||||
'col_type' => $_POST['col_type'] ?? null,
|
||||
'collation' => $_POST['collation'] ?? null,
|
||||
]);
|
||||
}
|
||||
if (isset($_POST['getColumnList'])) {
|
||||
$this->response->addJSON('message', $this->getColumnList([
|
||||
'cur_table' => $_POST['cur_table'] ?? null,
|
||||
]));
|
||||
|
||||
return;
|
||||
}
|
||||
if (isset($_POST['add_column'])) {
|
||||
$tmp_msg = $this->addColumn([
|
||||
'table-select' => $_POST['table-select'] ?? null,
|
||||
'column-select' => $_POST['column-select'] ?? null,
|
||||
]);
|
||||
}
|
||||
|
||||
$this->addScriptFiles([
|
||||
'vendor/jquery/jquery.uitablefilter.js',
|
||||
'vendor/jquery/jquery.tablesorter.js',
|
||||
'database/central_columns.js',
|
||||
]);
|
||||
|
||||
if (isset($_POST['edit_central_columns_page'])) {
|
||||
$this->editPage([
|
||||
'selected_fld' => $_POST['selected_fld'] ?? null,
|
||||
'db' => $_POST['db'] ?? null,
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
if (isset($_POST['multi_edit_central_column_save'])) {
|
||||
$message = $this->updateMultipleColumn([
|
||||
'db' => $_POST['db'] ?? null,
|
||||
'orig_col_name' => $_POST['orig_col_name'] ?? null,
|
||||
'field_name' => $_POST['field_name'] ?? null,
|
||||
'field_default_type' => $_POST['field_default_type'] ?? null,
|
||||
'field_default_value' => $_POST['field_default_value'] ?? null,
|
||||
'field_length' => $_POST['field_length'] ?? null,
|
||||
'field_attribute' => $_POST['field_attribute'] ?? null,
|
||||
'field_type' => $_POST['field_type'] ?? null,
|
||||
'field_collation' => $_POST['field_collation'] ?? null,
|
||||
'field_null' => $_POST['field_null'] ?? null,
|
||||
'col_extra' => $_POST['col_extra'] ?? null,
|
||||
]);
|
||||
if (! is_bool($message)) {
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON('message', $message);
|
||||
}
|
||||
}
|
||||
if (isset($_POST['delete_save'])) {
|
||||
$tmp_msg = $this->deleteSave([
|
||||
'db' => $_POST['db'] ?? null,
|
||||
'col_name' => $_POST['col_name'] ?? null,
|
||||
]);
|
||||
}
|
||||
|
||||
$this->main([
|
||||
'pos' => $_POST['pos'] ?? null,
|
||||
'total_rows' => $_POST['total_rows'] ?? null,
|
||||
]);
|
||||
|
||||
$pos = 0;
|
||||
if (Core::isValid($_POST['pos'], 'integer')) {
|
||||
$pos = (int) $_POST['pos'];
|
||||
}
|
||||
$num_cols = $this->centralColumns->getColumnsCount(
|
||||
$db,
|
||||
$pos,
|
||||
(int) $cfg['MaxRows']
|
||||
);
|
||||
$message = Message::success(
|
||||
sprintf(__('Showing rows %1$s - %2$s.'), $pos + 1, $pos + $num_cols)
|
||||
);
|
||||
if (! isset($tmp_msg) || $tmp_msg === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
$message = $tmp_msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $params Request parameters
|
||||
*/
|
||||
public function main(array $params): void
|
||||
{
|
||||
global $text_dir, $PMA_Theme;
|
||||
|
||||
if (! empty($params['total_rows'])
|
||||
&& Core::isValid($params['total_rows'], 'integer')
|
||||
) {
|
||||
$totalRows = (int) $params['total_rows'];
|
||||
} else {
|
||||
$totalRows = $this->centralColumns->getCount($this->db);
|
||||
}
|
||||
|
||||
$pos = 0;
|
||||
if (Core::isValid($params['pos'], 'integer')) {
|
||||
$pos = (int) $params['pos'];
|
||||
}
|
||||
|
||||
$variables = $this->centralColumns->getTemplateVariablesForMain(
|
||||
$this->db,
|
||||
$totalRows,
|
||||
$pos,
|
||||
$PMA_Theme->getImgPath(),
|
||||
$text_dir
|
||||
);
|
||||
|
||||
$this->render('database/central_columns/main', $variables);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $params Request parameters
|
||||
*
|
||||
* @return array JSON
|
||||
*/
|
||||
public function getColumnList(array $params): array
|
||||
{
|
||||
return $this->centralColumns->getListRaw(
|
||||
$this->db,
|
||||
$params['cur_table'] ?? ''
|
||||
);
|
||||
}
|
||||
|
||||
public function populateColumns(): void
|
||||
{
|
||||
$columns = $this->centralColumns->getColumnsNotInCentralList($this->db, $_POST['selectedTable']);
|
||||
$this->render('database/central_columns/populate_columns', ['columns' => $columns]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $params Request parameters
|
||||
*
|
||||
* @return true|Message
|
||||
*/
|
||||
public function editSave(array $params)
|
||||
{
|
||||
$columnDefault = $params['col_default'];
|
||||
if ($columnDefault === 'NONE' && $params['col_default_sel'] !== 'USER_DEFINED') {
|
||||
$columnDefault = '';
|
||||
}
|
||||
|
||||
return $this->centralColumns->updateOneColumn(
|
||||
$this->db,
|
||||
$params['orig_col_name'],
|
||||
$params['col_name'],
|
||||
$params['col_type'],
|
||||
$params['col_attribute'],
|
||||
$params['col_length'],
|
||||
isset($params['col_isNull']) ? 1 : 0,
|
||||
$params['collation'],
|
||||
$params['col_extra'] ?? '',
|
||||
$columnDefault
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $params Request parameters
|
||||
*
|
||||
* @return true|Message
|
||||
*/
|
||||
public function addNewColumn(array $params)
|
||||
{
|
||||
$columnDefault = $params['col_default'];
|
||||
if ($columnDefault === 'NONE' && $params['col_default_sel'] !== 'USER_DEFINED') {
|
||||
$columnDefault = '';
|
||||
}
|
||||
|
||||
return $this->centralColumns->updateOneColumn(
|
||||
$this->db,
|
||||
'',
|
||||
$params['col_name'],
|
||||
$params['col_type'],
|
||||
$params['col_attribute'],
|
||||
$params['col_length'],
|
||||
isset($params['col_isNull']) ? 1 : 0,
|
||||
$params['collation'],
|
||||
$params['col_extra'] ?? '',
|
||||
$columnDefault
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $params Request parameters
|
||||
*
|
||||
* @return true|Message
|
||||
*/
|
||||
public function addColumn(array $params)
|
||||
{
|
||||
return $this->centralColumns->syncUniqueColumns(
|
||||
[$params['column-select']],
|
||||
false,
|
||||
$params['table-select']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $params Request parameters
|
||||
*/
|
||||
public function editPage(array $params): void
|
||||
{
|
||||
$rows = $this->centralColumns->getHtmlForEditingPage(
|
||||
$params['selected_fld'],
|
||||
$params['db']
|
||||
);
|
||||
|
||||
$this->render('database/central_columns/edit', ['rows' => $rows]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $params Request parameters
|
||||
*
|
||||
* @return true|Message
|
||||
*/
|
||||
public function updateMultipleColumn(array $params)
|
||||
{
|
||||
return $this->centralColumns->updateMultipleColumn($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $params Request parameters
|
||||
*
|
||||
* @return true|Message
|
||||
*/
|
||||
public function deleteSave(array $params)
|
||||
{
|
||||
$name = [];
|
||||
parse_str($params['col_name'], $name);
|
||||
|
||||
return $this->centralColumns->deleteColumnsFromList(
|
||||
$params['db'],
|
||||
$name['selected_fld'],
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Database;
|
||||
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Index;
|
||||
use PhpMyAdmin\Relation;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Transformations;
|
||||
use PhpMyAdmin\Util;
|
||||
use function is_array;
|
||||
use function str_replace;
|
||||
|
||||
class DataDictionaryController extends AbstractController
|
||||
{
|
||||
/** @var Relation */
|
||||
private $relation;
|
||||
|
||||
/** @var Transformations */
|
||||
private $transformations;
|
||||
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param string $db Database name
|
||||
* @param Relation $relation
|
||||
* @param Transformations $transformations
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, $db, $relation, $transformations, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template, $db);
|
||||
$this->relation = $relation;
|
||||
$this->transformations = $transformations;
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
Util::checkParameters(['db'], true);
|
||||
|
||||
$header = $this->response->getHeader();
|
||||
$header->enablePrintView();
|
||||
|
||||
$cfgRelation = $this->relation->getRelationsParam();
|
||||
|
||||
$comment = $this->relation->getDbComment($this->db);
|
||||
|
||||
$this->dbi->selectDb($this->db);
|
||||
$tablesNames = $this->dbi->getTables($this->db);
|
||||
|
||||
$tables = [];
|
||||
foreach ($tablesNames as $tableName) {
|
||||
$showComment = (string) $this->dbi->getTable(
|
||||
$this->db,
|
||||
$tableName
|
||||
)->getStatusInfo('TABLE_COMMENT');
|
||||
|
||||
[, $primaryKeys] = Util::processIndexData(
|
||||
$this->dbi->getTableIndexes($this->db, $tableName)
|
||||
);
|
||||
|
||||
[$foreigners, $hasRelation] = $this->relation->getRelationsAndStatus(
|
||||
! empty($cfgRelation['relation']),
|
||||
$this->db,
|
||||
$tableName
|
||||
);
|
||||
|
||||
$columnsComments = $this->relation->getComments($this->db, $tableName);
|
||||
|
||||
$columns = $this->dbi->getColumns($this->db, $tableName);
|
||||
$rows = [];
|
||||
foreach ($columns as $row) {
|
||||
$extractedColumnSpec = Util::extractColumnSpec($row['Type']);
|
||||
|
||||
$relation = '';
|
||||
if ($hasRelation) {
|
||||
$foreigner = $this->relation->searchColumnInForeigners(
|
||||
$foreigners,
|
||||
$row['Field']
|
||||
);
|
||||
if (is_array($foreigner) && isset($foreigner['foreign_table'], $foreigner['foreign_field'])) {
|
||||
$relation = $foreigner['foreign_table'];
|
||||
$relation .= ' -> ';
|
||||
$relation .= $foreigner['foreign_field'];
|
||||
}
|
||||
}
|
||||
|
||||
$mime = '';
|
||||
if ($cfgRelation['mimework']) {
|
||||
$mimeMap = $this->transformations->getMime(
|
||||
$this->db,
|
||||
$tableName,
|
||||
true
|
||||
);
|
||||
if (is_array($mimeMap) && isset($mimeMap[$row['Field']]['mimetype'])) {
|
||||
$mime = str_replace(
|
||||
'_',
|
||||
'/',
|
||||
$mimeMap[$row['Field']]['mimetype']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$rows[$row['Field']] = [
|
||||
'name' => $row['Field'],
|
||||
'has_primary_key' => isset($primaryKeys[$row['Field']]),
|
||||
'type' => $extractedColumnSpec['type'],
|
||||
'print_type' => $extractedColumnSpec['print_type'],
|
||||
'is_nullable' => $row['Null'] !== '' && $row['Null'] !== 'NO',
|
||||
'default' => $row['Default'] ?? null,
|
||||
'comment' => $columnsComments[$row['Field']] ?? '',
|
||||
'mime' => $mime,
|
||||
'relation' => $relation,
|
||||
];
|
||||
}
|
||||
|
||||
$tables[$tableName] = [
|
||||
'name' => $tableName,
|
||||
'comment' => $showComment,
|
||||
'has_relation' => $hasRelation,
|
||||
'has_mime' => $cfgRelation['mimework'],
|
||||
'columns' => $rows,
|
||||
'indexes' => Index::getFromTable($tableName, $this->db),
|
||||
];
|
||||
}
|
||||
|
||||
$this->render('database/data_dictionary/index', [
|
||||
'database' => $this->db,
|
||||
'comment' => $comment,
|
||||
'tables' => $tables,
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,259 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Database;
|
||||
|
||||
use PhpMyAdmin\Database\Designer;
|
||||
use PhpMyAdmin\Database\Designer\Common as DesignerCommon;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
use function htmlspecialchars;
|
||||
use function in_array;
|
||||
use function sprintf;
|
||||
|
||||
class DesignerController extends AbstractController
|
||||
{
|
||||
/** @var Designer */
|
||||
private $databaseDesigner;
|
||||
|
||||
/** @var DesignerCommon */
|
||||
private $designerCommon;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param string $db Database name
|
||||
*/
|
||||
public function __construct(
|
||||
$response,
|
||||
Template $template,
|
||||
$db,
|
||||
Designer $databaseDesigner,
|
||||
DesignerCommon $designerCommon
|
||||
) {
|
||||
parent::__construct($response, $template, $db);
|
||||
$this->databaseDesigner = $databaseDesigner;
|
||||
$this->designerCommon = $designerCommon;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $db, $script_display_field, $tab_column, $tables_all_keys, $tables_pk_or_unique_keys;
|
||||
global $success, $page, $message, $display_page, $selected_page, $tab_pos, $fullTableNames, $script_tables;
|
||||
global $script_contr, $params, $tables, $num_tables, $total_num_tables, $sub_part;
|
||||
global $tooltip_truename, $tooltip_aliasname, $pos, $classes_side_menu, $cfg, $err_url;
|
||||
|
||||
if (isset($_POST['dialog'])) {
|
||||
if ($_POST['dialog'] === 'edit') {
|
||||
$html = $this->databaseDesigner->getHtmlForEditOrDeletePages($_POST['db'], 'editPage');
|
||||
} elseif ($_POST['dialog'] === 'delete') {
|
||||
$html = $this->databaseDesigner->getHtmlForEditOrDeletePages($_POST['db'], 'deletePage');
|
||||
} elseif ($_POST['dialog'] === 'save_as') {
|
||||
$html = $this->databaseDesigner->getHtmlForPageSaveAs($_POST['db']);
|
||||
} elseif ($_POST['dialog'] === 'export') {
|
||||
$html = $this->databaseDesigner->getHtmlForSchemaExport(
|
||||
$_POST['db'],
|
||||
$_POST['selected_page']
|
||||
);
|
||||
} elseif ($_POST['dialog'] === 'add_table') {
|
||||
// Pass the db and table to the getTablesInfo so we only have the table we asked for
|
||||
$script_display_field = $this->designerCommon->getTablesInfo($_POST['db'], $_POST['table']);
|
||||
$tab_column = $this->designerCommon->getColumnsInfo($script_display_field);
|
||||
$tables_all_keys = $this->designerCommon->getAllKeys($script_display_field);
|
||||
$tables_pk_or_unique_keys = $this->designerCommon->getPkOrUniqueKeys($script_display_field);
|
||||
|
||||
$html = $this->databaseDesigner->getDatabaseTables(
|
||||
$_POST['db'],
|
||||
$script_display_field,
|
||||
[],
|
||||
-1,
|
||||
$tab_column,
|
||||
$tables_all_keys,
|
||||
$tables_pk_or_unique_keys
|
||||
);
|
||||
}
|
||||
|
||||
if (! empty($html)) {
|
||||
$this->response->addHTML($html);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($_POST['operation'])) {
|
||||
if ($_POST['operation'] === 'deletePage') {
|
||||
$success = $this->designerCommon->deletePage($_POST['selected_page']);
|
||||
$this->response->setRequestStatus($success);
|
||||
} elseif ($_POST['operation'] === 'savePage') {
|
||||
if ($_POST['save_page'] === 'same') {
|
||||
$page = $_POST['selected_page'];
|
||||
} elseif ($this->designerCommon->getPageExists($_POST['selected_value'])) {
|
||||
$this->response->addJSON(
|
||||
'message',
|
||||
/* l10n: The user tries to save a page with an existing name in Designer */
|
||||
__(
|
||||
sprintf(
|
||||
'There already exists a page named "%s" please rename it to something else.',
|
||||
htmlspecialchars($_POST['selected_value'])
|
||||
)
|
||||
)
|
||||
);
|
||||
$this->response->setRequestStatus(false);
|
||||
|
||||
return;
|
||||
} else {
|
||||
$page = $this->designerCommon->createNewPage($_POST['selected_value'], $_POST['db']);
|
||||
$this->response->addJSON('id', $page);
|
||||
}
|
||||
$success = $this->designerCommon->saveTablePositions($page);
|
||||
$this->response->setRequestStatus($success);
|
||||
} elseif ($_POST['operation'] === 'setDisplayField') {
|
||||
[
|
||||
$success,
|
||||
$message,
|
||||
] = $this->designerCommon->saveDisplayField(
|
||||
$_POST['db'],
|
||||
$_POST['table'],
|
||||
$_POST['field']
|
||||
);
|
||||
$this->response->setRequestStatus($success);
|
||||
$this->response->addJSON('message', $message);
|
||||
} elseif ($_POST['operation'] === 'addNewRelation') {
|
||||
[$success, $message] = $this->designerCommon->addNewRelation(
|
||||
$_POST['db'],
|
||||
$_POST['T1'],
|
||||
$_POST['F1'],
|
||||
$_POST['T2'],
|
||||
$_POST['F2'],
|
||||
$_POST['on_delete'],
|
||||
$_POST['on_update'],
|
||||
$_POST['DB1'],
|
||||
$_POST['DB2']
|
||||
);
|
||||
$this->response->setRequestStatus($success);
|
||||
$this->response->addJSON('message', $message);
|
||||
} elseif ($_POST['operation'] === 'removeRelation') {
|
||||
[$success, $message] = $this->designerCommon->removeRelation(
|
||||
$_POST['T1'],
|
||||
$_POST['F1'],
|
||||
$_POST['T2'],
|
||||
$_POST['F2']
|
||||
);
|
||||
$this->response->setRequestStatus($success);
|
||||
$this->response->addJSON('message', $message);
|
||||
} elseif ($_POST['operation'] === 'save_setting_value') {
|
||||
$success = $this->designerCommon->saveSetting($_POST['index'], $_POST['value']);
|
||||
$this->response->setRequestStatus($success);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Util::checkParameters(['db']);
|
||||
|
||||
$err_url = Util::getScriptNameForOption($cfg['DefaultTabDatabase'], 'database');
|
||||
$err_url .= Url::getCommon(['db' => $db], '&');
|
||||
|
||||
if (! $this->hasDatabase()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$script_display_field = $this->designerCommon->getTablesInfo();
|
||||
|
||||
$display_page = -1;
|
||||
$selected_page = null;
|
||||
|
||||
$visualBuilderMode = isset($_GET['query']);
|
||||
|
||||
if ($visualBuilderMode) {
|
||||
$display_page = $this->designerCommon->getDefaultPage($_GET['db']);
|
||||
} elseif (! empty($_GET['page'])) {
|
||||
$display_page = $_GET['page'];
|
||||
} else {
|
||||
$display_page = $this->designerCommon->getLoadingPage($_GET['db']);
|
||||
}
|
||||
if ($display_page != -1) {
|
||||
$selected_page = $this->designerCommon->getPageName($display_page);
|
||||
}
|
||||
$tab_pos = $this->designerCommon->getTablePositions($display_page);
|
||||
|
||||
$fullTableNames = [];
|
||||
|
||||
foreach ($script_display_field as $designerTable) {
|
||||
$fullTableNames[] = $designerTable->getDbTableString();
|
||||
}
|
||||
|
||||
foreach ($tab_pos as $position) {
|
||||
if (in_array($position['dbName'] . '.' . $position['tableName'], $fullTableNames)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$designerTables = $this->designerCommon->getTablesInfo($position['dbName'], $position['tableName']);
|
||||
foreach ($designerTables as $designerTable) {
|
||||
$script_display_field[] = $designerTable;
|
||||
}
|
||||
}
|
||||
|
||||
$tab_column = $this->designerCommon->getColumnsInfo($script_display_field);
|
||||
$script_tables = $this->designerCommon->getScriptTabs($script_display_field);
|
||||
$tables_pk_or_unique_keys = $this->designerCommon->getPkOrUniqueKeys($script_display_field);
|
||||
$tables_all_keys = $this->designerCommon->getAllKeys($script_display_field);
|
||||
$classes_side_menu = $this->databaseDesigner->returnClassNamesFromMenuButtons();
|
||||
|
||||
$script_contr = $this->designerCommon->getScriptContr($script_display_field);
|
||||
|
||||
$params = ['lang' => $GLOBALS['lang']];
|
||||
if (isset($_GET['db'])) {
|
||||
$params['db'] = $_GET['db'];
|
||||
}
|
||||
|
||||
$this->response->getFooter()->setMinimal();
|
||||
$header = $this->response->getHeader();
|
||||
$header->setBodyId('designer_body');
|
||||
|
||||
$this->addScriptFiles([
|
||||
'vendor/jquery/jquery.fullscreen.js',
|
||||
'designer/database.js',
|
||||
'designer/objects.js',
|
||||
'designer/page.js',
|
||||
'designer/history.js',
|
||||
'designer/move.js',
|
||||
'designer/init.js',
|
||||
]);
|
||||
|
||||
[
|
||||
$tables,
|
||||
$num_tables,
|
||||
$total_num_tables,
|
||||
$sub_part,,,
|
||||
$tooltip_truename,
|
||||
$tooltip_aliasname,
|
||||
$pos,
|
||||
] = Util::getDbInfo($db, $sub_part ?? '');
|
||||
|
||||
// Embed some data into HTML, later it will be read
|
||||
// by designer/init.js and converted to JS variables.
|
||||
$this->response->addHTML(
|
||||
$this->databaseDesigner->getHtmlForMain(
|
||||
$db,
|
||||
$_GET['db'],
|
||||
$script_display_field,
|
||||
$script_tables,
|
||||
$script_contr,
|
||||
$script_display_field,
|
||||
$display_page,
|
||||
$visualBuilderMode,
|
||||
$selected_page,
|
||||
$classes_side_menu,
|
||||
$tab_pos,
|
||||
$tab_column,
|
||||
$tables_all_keys,
|
||||
$tables_pk_or_unique_keys
|
||||
)
|
||||
);
|
||||
|
||||
$this->response->addHTML('<div id="PMA_disable_floating_menubar"></div>');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Database;
|
||||
|
||||
use PhpMyAdmin\Database\Events;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
use function strlen;
|
||||
|
||||
final class EventsController extends AbstractController
|
||||
{
|
||||
/** @var Events */
|
||||
private $events;
|
||||
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param string $db Database name.
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, $db, Events $events, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template, $db);
|
||||
$this->events = $events;
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $db, $tables, $num_tables, $total_num_tables, $sub_part, $errors, $text_dir, $PMA_Theme;
|
||||
global $tooltip_truename, $tooltip_aliasname, $pos, $cfg, $err_url;
|
||||
|
||||
if (! $this->response->isAjax()) {
|
||||
Util::checkParameters(['db']);
|
||||
|
||||
$err_url = Util::getScriptNameForOption($cfg['DefaultTabDatabase'], 'database');
|
||||
$err_url .= Url::getCommon(['db' => $db], '&');
|
||||
|
||||
if (! $this->hasDatabase()) {
|
||||
return;
|
||||
}
|
||||
|
||||
[
|
||||
$tables,
|
||||
$num_tables,
|
||||
$total_num_tables,
|
||||
$sub_part,,,
|
||||
$tooltip_truename,
|
||||
$tooltip_aliasname,
|
||||
$pos,
|
||||
] = Util::getDbInfo($db, $sub_part ?? '');
|
||||
} elseif (strlen($db) > 0) {
|
||||
$this->dbi->selectDb($db);
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep a list of errors that occurred while
|
||||
* processing an 'Add' or 'Edit' operation.
|
||||
*/
|
||||
$errors = [];
|
||||
|
||||
$this->events->handleEditor();
|
||||
$this->events->export();
|
||||
|
||||
$items = $this->dbi->getEvents($db);
|
||||
|
||||
$this->render('database/events/index', [
|
||||
'db' => $db,
|
||||
'items' => $items,
|
||||
'select_all_arrow_src' => $PMA_Theme->getImgPath() . 'arrow_' . $text_dir . '.png',
|
||||
'has_privilege' => Util::currentUserHasPrivilege('EVENT', $db),
|
||||
'scheduler_state' => $this->events->getEventSchedulerStatus(),
|
||||
'text_dir' => $text_dir,
|
||||
'theme_image_path' => $PMA_Theme->getImgPath(),
|
||||
'is_ajax' => $this->response->isAjax() && empty($_REQUEST['ajax_page_request']),
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,184 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Database;
|
||||
|
||||
use PhpMyAdmin\Config\PageSettings;
|
||||
use PhpMyAdmin\Export;
|
||||
use PhpMyAdmin\Export\Options;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\Plugins;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
use function array_merge;
|
||||
use function is_array;
|
||||
|
||||
final class ExportController extends AbstractController
|
||||
{
|
||||
/** @var Export */
|
||||
private $export;
|
||||
|
||||
/** @var Options */
|
||||
private $exportOptions;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param string $db Database name.
|
||||
*/
|
||||
public function __construct($response, Template $template, $db, Export $export, Options $exportOptions)
|
||||
{
|
||||
parent::__construct($response, $template, $db);
|
||||
$this->export = $export;
|
||||
$this->exportOptions = $exportOptions;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $db, $table, $sub_part, $url_params, $sql_query;
|
||||
global $tables, $num_tables, $total_num_tables, $tooltip_truename;
|
||||
global $tooltip_aliasname, $pos, $table_select, $unlim_num_rows, $cfg, $err_url;
|
||||
|
||||
$pageSettings = new PageSettings('Export');
|
||||
$pageSettingsErrorHtml = $pageSettings->getErrorHTML();
|
||||
$pageSettingsHtml = $pageSettings->getHTML();
|
||||
|
||||
$this->addScriptFiles(['export.js']);
|
||||
|
||||
// $sub_part is used in Util::getDbInfo() to see if we are coming from
|
||||
// /database/export, in which case we don't obey $cfg['MaxTableList']
|
||||
$sub_part = '_export';
|
||||
|
||||
Util::checkParameters(['db']);
|
||||
|
||||
$err_url = Util::getScriptNameForOption($cfg['DefaultTabDatabase'], 'database');
|
||||
$err_url .= Url::getCommon(['db' => $db], '&');
|
||||
|
||||
if (! $this->hasDatabase()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$url_params['goto'] = Url::getFromRoute('/database/export');
|
||||
|
||||
[
|
||||
$tables,
|
||||
$num_tables,
|
||||
$total_num_tables,
|
||||
$sub_part,,,
|
||||
$tooltip_truename,
|
||||
$tooltip_aliasname,
|
||||
$pos,
|
||||
] = Util::getDbInfo($db, $sub_part ?? '');
|
||||
|
||||
// exit if no tables in db found
|
||||
if ($num_tables < 1) {
|
||||
$this->response->addHTML(
|
||||
Message::error(__('No tables found in database.'))->getDisplay()
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (! empty($_POST['selected_tbl']) && empty($table_select)) {
|
||||
$table_select = $_POST['selected_tbl'];
|
||||
}
|
||||
|
||||
$tablesForMultiValues = [];
|
||||
|
||||
foreach ($tables as $each_table) {
|
||||
if (isset($_POST['table_select']) && is_array($_POST['table_select'])) {
|
||||
$is_checked = $this->export->getCheckedClause(
|
||||
$each_table['Name'],
|
||||
$_POST['table_select']
|
||||
);
|
||||
} elseif (isset($table_select)) {
|
||||
$is_checked = $this->export->getCheckedClause(
|
||||
$each_table['Name'],
|
||||
$table_select
|
||||
);
|
||||
} else {
|
||||
$is_checked = true;
|
||||
}
|
||||
if (isset($_POST['table_structure']) && is_array($_POST['table_structure'])) {
|
||||
$structure_checked = $this->export->getCheckedClause(
|
||||
$each_table['Name'],
|
||||
$_POST['table_structure']
|
||||
);
|
||||
} else {
|
||||
$structure_checked = $is_checked;
|
||||
}
|
||||
if (isset($_POST['table_data']) && is_array($_POST['table_data'])) {
|
||||
$data_checked = $this->export->getCheckedClause(
|
||||
$each_table['Name'],
|
||||
$_POST['table_data']
|
||||
);
|
||||
} else {
|
||||
$data_checked = $is_checked;
|
||||
}
|
||||
|
||||
$tablesForMultiValues[] = [
|
||||
'name' => $each_table['Name'],
|
||||
'is_checked_select' => $is_checked,
|
||||
'is_checked_structure' => $structure_checked,
|
||||
'is_checked_data' => $data_checked,
|
||||
];
|
||||
}
|
||||
|
||||
if (! isset($sql_query)) {
|
||||
$sql_query = '';
|
||||
}
|
||||
if (! isset($unlim_num_rows)) {
|
||||
$unlim_num_rows = 0;
|
||||
}
|
||||
|
||||
$isReturnBackFromRawExport = isset($_POST['export_type']) && $_POST['export_type'] === 'raw';
|
||||
if (isset($_POST['raw_query']) || $isReturnBackFromRawExport) {
|
||||
$export_type = 'raw';
|
||||
} else {
|
||||
$export_type = 'database';
|
||||
}
|
||||
|
||||
$GLOBALS['single_table'] = $_POST['single_table'] ?? $_GET['single_table'] ?? $GLOBALS['single_table'] ?? null;
|
||||
|
||||
$exportList = Plugins::getExport($export_type, isset($GLOBALS['single_table']));
|
||||
|
||||
if (empty($exportList)) {
|
||||
$this->response->addHTML(Message::error(
|
||||
__('Could not load export plugins, please check your installation!')
|
||||
)->getDisplay());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$options = $this->exportOptions->getOptions(
|
||||
$export_type,
|
||||
$db,
|
||||
$table,
|
||||
$sql_query,
|
||||
$num_tables,
|
||||
$unlim_num_rows,
|
||||
$exportList
|
||||
);
|
||||
|
||||
$this->render('database/export/index', array_merge($options, [
|
||||
'page_settings_error_html' => $pageSettingsErrorHtml,
|
||||
'page_settings_html' => $pageSettingsHtml,
|
||||
'structure_or_data_forced' => $_POST['structure_or_data_forced'] ?? 0,
|
||||
'tables' => $tablesForMultiValues,
|
||||
]));
|
||||
}
|
||||
|
||||
public function tables(): void
|
||||
{
|
||||
if (empty($_POST['selected_tbl'])) {
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON('message', __('No table selected.'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->index();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,135 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Database;
|
||||
|
||||
use PhpMyAdmin\Charsets;
|
||||
use PhpMyAdmin\Charsets\Charset;
|
||||
use PhpMyAdmin\Config\PageSettings;
|
||||
use PhpMyAdmin\Core;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Encoding;
|
||||
use PhpMyAdmin\Import;
|
||||
use PhpMyAdmin\Import\Ajax;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\Plugins;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
use function intval;
|
||||
|
||||
final class ImportController extends AbstractController
|
||||
{
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param string $db Database name.
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, $db, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template, $db);
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $db, $max_upload_size, $table, $tables, $num_tables, $total_num_tables, $cfg;
|
||||
global $tooltip_truename, $tooltip_aliasname, $pos, $sub_part, $SESSION_KEY, $PMA_Theme, $err_url;
|
||||
|
||||
$pageSettings = new PageSettings('Import');
|
||||
$pageSettingsErrorHtml = $pageSettings->getErrorHTML();
|
||||
$pageSettingsHtml = $pageSettings->getHTML();
|
||||
|
||||
$this->addScriptFiles(['import.js']);
|
||||
|
||||
Util::checkParameters(['db']);
|
||||
|
||||
$err_url = Util::getScriptNameForOption($cfg['DefaultTabDatabase'], 'database');
|
||||
$err_url .= Url::getCommon(['db' => $db], '&');
|
||||
|
||||
if (! $this->hasDatabase()) {
|
||||
return;
|
||||
}
|
||||
|
||||
[
|
||||
$tables,
|
||||
$num_tables,
|
||||
$total_num_tables,
|
||||
$sub_part,,,
|
||||
$tooltip_truename,
|
||||
$tooltip_aliasname,
|
||||
$pos,
|
||||
] = Util::getDbInfo($db, $sub_part ?? '');
|
||||
|
||||
[$SESSION_KEY, $uploadId] = Ajax::uploadProgressSetup();
|
||||
|
||||
$importList = Plugins::getImport('database');
|
||||
|
||||
if (empty($importList)) {
|
||||
$this->response->addHTML(Message::error(__(
|
||||
'Could not load import plugins, please check your installation!'
|
||||
))->getDisplay());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$offset = null;
|
||||
if (Core::isValid($_REQUEST['offset'], 'numeric')) {
|
||||
$offset = intval($_REQUEST['offset']);
|
||||
}
|
||||
|
||||
$timeoutPassed = $_REQUEST['timeout_passed'] ?? null;
|
||||
$localImportFile = $_REQUEST['local_import_file'] ?? null;
|
||||
$compressions = Import::getCompressions();
|
||||
|
||||
$allCharsets = Charsets::getCharsets($this->dbi, $cfg['Server']['DisableIS']);
|
||||
$charsets = [];
|
||||
/** @var Charset $charset */
|
||||
foreach ($allCharsets as $charset) {
|
||||
$charsets[] = [
|
||||
'name' => $charset->getName(),
|
||||
'description' => $charset->getDescription(),
|
||||
];
|
||||
}
|
||||
|
||||
$idKey = $_SESSION[$SESSION_KEY]['handler']::getIdKey();
|
||||
$hiddenInputs = [
|
||||
$idKey => $uploadId,
|
||||
'import_type' => 'database',
|
||||
'db' => $db,
|
||||
];
|
||||
|
||||
$this->render('database/import/index', [
|
||||
'page_settings_error_html' => $pageSettingsErrorHtml,
|
||||
'page_settings_html' => $pageSettingsHtml,
|
||||
'upload_id' => $uploadId,
|
||||
'handler' => $_SESSION[$SESSION_KEY]['handler'],
|
||||
'theme_image_path' => $PMA_Theme->getImgPath(),
|
||||
'hidden_inputs' => $hiddenInputs,
|
||||
'db' => $db,
|
||||
'table' => $table,
|
||||
'max_upload_size' => $max_upload_size,
|
||||
'import_list' => $importList,
|
||||
'local_import_file' => $localImportFile,
|
||||
'is_upload' => $GLOBALS['is_upload'],
|
||||
'upload_dir' => $cfg['UploadDir'] ?? null,
|
||||
'timeout_passed_global' => $GLOBALS['timeout_passed'] ?? null,
|
||||
'compressions' => $compressions,
|
||||
'is_encoding_supported' => Encoding::isSupported(),
|
||||
'encodings' => Encoding::listEncodings(),
|
||||
'import_charset' => $cfg['Import']['charset'] ?? null,
|
||||
'timeout_passed' => $timeoutPassed,
|
||||
'offset' => $offset,
|
||||
'can_convert_kanji' => Encoding::canConvertKanji(),
|
||||
'charsets' => $charsets,
|
||||
'is_foreign_key_check' => Util::isForeignKeyCheck(),
|
||||
'user_upload_dir' => Util::userDir($cfg['UploadDir'] ?? ''),
|
||||
'local_files' => Import::getLocalFiles($importList),
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Database;
|
||||
|
||||
use PhpMyAdmin\Database\MultiTableQuery;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
|
||||
/**
|
||||
* Handles database multi-table querying
|
||||
*/
|
||||
class MultiTableQueryController extends AbstractController
|
||||
{
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param string $db Database name.
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, $db, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template, $db);
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
$this->addScriptFiles([
|
||||
'vendor/jquery/jquery.md5.js',
|
||||
'database/multi_table_query.js',
|
||||
'database/query_generator.js',
|
||||
]);
|
||||
|
||||
$queryInstance = new MultiTableQuery($this->dbi, $this->template, $this->db);
|
||||
|
||||
$this->response->addHTML($queryInstance->getFormHtml());
|
||||
}
|
||||
|
||||
public function displayResults(): void
|
||||
{
|
||||
global $PMA_Theme;
|
||||
|
||||
$params = [
|
||||
'sql_query' => $_POST['sql_query'],
|
||||
'db' => $_POST['db'] ?? $_GET['db'] ?? null,
|
||||
];
|
||||
|
||||
$this->response->addHTML(MultiTableQuery::displayResults(
|
||||
$params['sql_query'],
|
||||
$params['db'],
|
||||
$PMA_Theme->getImgPath()
|
||||
));
|
||||
}
|
||||
|
||||
public function table(): void
|
||||
{
|
||||
$params = [
|
||||
'tables' => $_GET['tables'],
|
||||
'db' => $_GET['db'] ?? null,
|
||||
];
|
||||
$constrains = $this->dbi->getForeignKeyConstrains(
|
||||
$params['db'],
|
||||
$params['tables']
|
||||
);
|
||||
$this->response->addJSON(['foreignKeyConstrains' => $constrains]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,419 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Database;
|
||||
|
||||
use PhpMyAdmin\Charsets;
|
||||
use PhpMyAdmin\CheckUserPrivileges;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Html\Generator;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\Operations;
|
||||
use PhpMyAdmin\Plugins;
|
||||
use PhpMyAdmin\Plugins\Export\ExportSql;
|
||||
use PhpMyAdmin\Query\Utilities;
|
||||
use PhpMyAdmin\Relation;
|
||||
use PhpMyAdmin\RelationCleanup;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
use function count;
|
||||
use function mb_strtolower;
|
||||
use function strlen;
|
||||
|
||||
/**
|
||||
* Handles miscellaneous database operations.
|
||||
*/
|
||||
class OperationsController extends AbstractController
|
||||
{
|
||||
/** @var Operations */
|
||||
private $operations;
|
||||
|
||||
/** @var CheckUserPrivileges */
|
||||
private $checkUserPrivileges;
|
||||
|
||||
/** @var Relation */
|
||||
private $relation;
|
||||
|
||||
/** @var RelationCleanup */
|
||||
private $relationCleanup;
|
||||
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param string $db Database name
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct(
|
||||
$response,
|
||||
Template $template,
|
||||
$db,
|
||||
Operations $operations,
|
||||
CheckUserPrivileges $checkUserPrivileges,
|
||||
Relation $relation,
|
||||
RelationCleanup $relationCleanup,
|
||||
$dbi
|
||||
) {
|
||||
parent::__construct($response, $template, $db);
|
||||
$this->operations = $operations;
|
||||
$this->checkUserPrivileges = $checkUserPrivileges;
|
||||
$this->relation = $relation;
|
||||
$this->relationCleanup = $relationCleanup;
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $cfg, $db, $server, $sql_query, $move, $message, $tables_full, $err_url;
|
||||
global $export_sql_plugin, $views, $sqlConstratints, $local_query, $reload, $url_params, $tables;
|
||||
global $total_num_tables, $sub_part, $tooltip_truename;
|
||||
global $db_collation, $tooltip_aliasname, $pos, $is_information_schema, $single_table, $num_tables;
|
||||
|
||||
$this->checkUserPrivileges->getPrivileges();
|
||||
|
||||
$this->addScriptFiles(['database/operations.js']);
|
||||
|
||||
$sql_query = '';
|
||||
|
||||
/**
|
||||
* Rename/move or copy database
|
||||
*/
|
||||
if (strlen($db) > 0
|
||||
&& (! empty($_POST['db_rename']) || ! empty($_POST['db_copy']))
|
||||
) {
|
||||
if (! empty($_POST['db_rename'])) {
|
||||
$move = true;
|
||||
} else {
|
||||
$move = false;
|
||||
}
|
||||
|
||||
if (! isset($_POST['newname']) || strlen($_POST['newname']) === 0) {
|
||||
$message = Message::error(__('The database name is empty!'));
|
||||
} else {
|
||||
// lower_case_table_names=1 `DB` becomes `db`
|
||||
if ($this->dbi->getLowerCaseNames() === '1') {
|
||||
$_POST['newname'] = mb_strtolower(
|
||||
$_POST['newname']
|
||||
);
|
||||
}
|
||||
|
||||
if ($_POST['newname'] === $_REQUEST['db']) {
|
||||
$message = Message::error(
|
||||
__('Cannot copy database to the same name. Change the name and try again.')
|
||||
);
|
||||
} else {
|
||||
$_error = false;
|
||||
if ($move || ! empty($_POST['create_database_before_copying'])) {
|
||||
$this->operations->createDbBeforeCopy();
|
||||
}
|
||||
|
||||
// here I don't use DELIMITER because it's not part of the
|
||||
// language; I have to send each statement one by one
|
||||
|
||||
// to avoid selecting alternatively the current and new db
|
||||
// we would need to modify the CREATE definitions to qualify
|
||||
// the db name
|
||||
$this->operations->runProcedureAndFunctionDefinitions($db);
|
||||
|
||||
// go back to current db, just in case
|
||||
$this->dbi->selectDb($db);
|
||||
|
||||
$tables_full = $this->dbi->getTablesFull($db);
|
||||
|
||||
// remove all foreign key constraints, otherwise we can get errors
|
||||
/** @var ExportSql $export_sql_plugin */
|
||||
$export_sql_plugin = Plugins::getPlugin(
|
||||
'export',
|
||||
'sql',
|
||||
'libraries/classes/Plugins/Export/',
|
||||
[
|
||||
'single_table' => isset($single_table),
|
||||
'export_type' => 'database',
|
||||
]
|
||||
);
|
||||
|
||||
// create stand-in tables for views
|
||||
$views = $this->operations->getViewsAndCreateSqlViewStandIn(
|
||||
$tables_full,
|
||||
$export_sql_plugin,
|
||||
$db
|
||||
);
|
||||
|
||||
// copy tables
|
||||
$sqlConstratints = $this->operations->copyTables(
|
||||
$tables_full,
|
||||
$move,
|
||||
$db
|
||||
);
|
||||
|
||||
// handle the views
|
||||
if (! $_error) {
|
||||
$this->operations->handleTheViews($views, $move, $db);
|
||||
}
|
||||
unset($views);
|
||||
|
||||
// now that all tables exist, create all the accumulated constraints
|
||||
if (! $_error && count($sqlConstratints) > 0) {
|
||||
$this->operations->createAllAccumulatedConstraints($sqlConstratints);
|
||||
}
|
||||
unset($sqlConstratints);
|
||||
|
||||
if ($this->dbi->getVersion() >= 50100) {
|
||||
// here DELIMITER is not used because it's not part of the
|
||||
// language; each statement is sent one by one
|
||||
|
||||
$this->operations->runEventDefinitionsForDb($db);
|
||||
}
|
||||
|
||||
// go back to current db, just in case
|
||||
$this->dbi->selectDb($db);
|
||||
|
||||
// Duplicate the bookmarks for this db (done once for each db)
|
||||
$this->operations->duplicateBookmarks($_error, $db);
|
||||
|
||||
if (! $_error && $move) {
|
||||
if (isset($_POST['adjust_privileges'])
|
||||
&& ! empty($_POST['adjust_privileges'])
|
||||
) {
|
||||
$this->operations->adjustPrivilegesMoveDb($db, $_POST['newname']);
|
||||
}
|
||||
|
||||
/**
|
||||
* cleanup pmadb stuff for this db
|
||||
*/
|
||||
$this->relationCleanup->database($db);
|
||||
|
||||
// if someday the RENAME DATABASE reappears, do not DROP
|
||||
$local_query = 'DROP DATABASE '
|
||||
. Util::backquote($db) . ';';
|
||||
$sql_query .= "\n" . $local_query;
|
||||
$this->dbi->query($local_query);
|
||||
|
||||
$message = Message::success(
|
||||
__('Database %1$s has been renamed to %2$s.')
|
||||
);
|
||||
$message->addParam($db);
|
||||
$message->addParam($_POST['newname']);
|
||||
} elseif (! $_error) {
|
||||
if (isset($_POST['adjust_privileges'])
|
||||
&& ! empty($_POST['adjust_privileges'])
|
||||
) {
|
||||
$this->operations->adjustPrivilegesCopyDb($db, $_POST['newname']);
|
||||
}
|
||||
|
||||
$message = Message::success(
|
||||
__('Database %1$s has been copied to %2$s.')
|
||||
);
|
||||
$message->addParam($db);
|
||||
$message->addParam($_POST['newname']);
|
||||
} else {
|
||||
$message = Message::error();
|
||||
}
|
||||
$reload = true;
|
||||
|
||||
/* Change database to be used */
|
||||
if (! $_error && $move) {
|
||||
$db = $_POST['newname'];
|
||||
} elseif (! $_error) {
|
||||
if (isset($_POST['switch_to_new'])
|
||||
&& $_POST['switch_to_new'] === 'true'
|
||||
) {
|
||||
$_SESSION['pma_switch_to_new'] = true;
|
||||
$db = $_POST['newname'];
|
||||
} else {
|
||||
$_SESSION['pma_switch_to_new'] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Database has been successfully renamed/moved. If in an Ajax request,
|
||||
* generate the output with {@link Response} and exit
|
||||
*/
|
||||
if ($this->response->isAjax()) {
|
||||
$this->response->setRequestStatus($message->isSuccess());
|
||||
$this->response->addJSON('message', $message);
|
||||
$this->response->addJSON('newname', $_POST['newname']);
|
||||
$this->response->addJSON(
|
||||
'sql_query',
|
||||
Generator::getMessage('', $sql_query)
|
||||
);
|
||||
$this->response->addJSON('db', $db);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Settings for relations stuff
|
||||
*/
|
||||
$cfgRelation = $this->relation->getRelationsParam();
|
||||
|
||||
/**
|
||||
* Check if comments were updated
|
||||
* (must be done before displaying the menu tabs)
|
||||
*/
|
||||
if (isset($_POST['comment'])) {
|
||||
$this->relation->setDbComment($db, $_POST['comment']);
|
||||
}
|
||||
|
||||
Util::checkParameters(['db']);
|
||||
|
||||
$err_url = Util::getScriptNameForOption($cfg['DefaultTabDatabase'], 'database');
|
||||
$err_url .= Url::getCommon(['db' => $db], '&');
|
||||
|
||||
if (! $this->hasDatabase()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$url_params['goto'] = Url::getFromRoute('/database/operations');
|
||||
|
||||
// Gets the database structure
|
||||
$sub_part = '_structure';
|
||||
|
||||
[
|
||||
$tables,
|
||||
$num_tables,
|
||||
$total_num_tables,
|
||||
$sub_part,,
|
||||
$isSystemSchema,
|
||||
$tooltip_truename,
|
||||
$tooltip_aliasname,
|
||||
$pos,
|
||||
] = Util::getDbInfo($db, $sub_part ?? '');
|
||||
|
||||
$oldMessage = '';
|
||||
if (isset($message)) {
|
||||
$oldMessage = Generator::getMessage($message, $sql_query);
|
||||
unset($message);
|
||||
}
|
||||
|
||||
$db_collation = $this->dbi->getDbCollation($db);
|
||||
$is_information_schema = Utilities::isSystemSchema($db);
|
||||
|
||||
if ($is_information_schema) {
|
||||
return;
|
||||
}
|
||||
|
||||
$databaseComment = '';
|
||||
if ($cfgRelation['commwork']) {
|
||||
$databaseComment = $this->relation->getDbComment($db);
|
||||
}
|
||||
|
||||
$hasAdjustPrivileges = $GLOBALS['db_priv'] && $GLOBALS['table_priv']
|
||||
&& $GLOBALS['col_priv'] && $GLOBALS['proc_priv'] && $GLOBALS['is_reload_priv'];
|
||||
|
||||
$isDropDatabaseAllowed = ($this->dbi->isSuperUser() || $cfg['AllowUserDropDatabase'])
|
||||
&& ! $isSystemSchema && $db !== 'mysql';
|
||||
|
||||
$switchToNew = isset($_SESSION['pma_switch_to_new']) && $_SESSION['pma_switch_to_new'];
|
||||
|
||||
$charsets = Charsets::getCharsets($this->dbi, $GLOBALS['cfg']['Server']['DisableIS']);
|
||||
$collations = Charsets::getCollations($this->dbi, $GLOBALS['cfg']['Server']['DisableIS']);
|
||||
|
||||
if (! $cfgRelation['allworks']
|
||||
&& $cfg['PmaNoRelation_DisableWarning'] == false
|
||||
) {
|
||||
$message = Message::notice(
|
||||
__(
|
||||
'The phpMyAdmin configuration storage has been deactivated. ' .
|
||||
'%sFind out why%s.'
|
||||
)
|
||||
);
|
||||
$message->addParamHtml(
|
||||
'<a href="' . Url::getFromRoute('/check-relations')
|
||||
. '" data-post="' . Url::getCommon(['db' => $db]) . '">'
|
||||
);
|
||||
$message->addParamHtml('</a>');
|
||||
/* Show error if user has configured something, notice elsewhere */
|
||||
if (! empty($cfg['Servers'][$server]['pmadb'])) {
|
||||
$message->isError(true);
|
||||
}
|
||||
}
|
||||
|
||||
$this->render('database/operations/index', [
|
||||
'message' => $oldMessage,
|
||||
'db' => $db,
|
||||
'has_comment' => $cfgRelation['commwork'],
|
||||
'db_comment' => $databaseComment,
|
||||
'db_collation' => $db_collation,
|
||||
'has_adjust_privileges' => $hasAdjustPrivileges,
|
||||
'is_drop_database_allowed' => $isDropDatabaseAllowed,
|
||||
'switch_to_new' => $switchToNew,
|
||||
'charsets' => $charsets,
|
||||
'collations' => $collations,
|
||||
]);
|
||||
}
|
||||
|
||||
public function collation(): void
|
||||
{
|
||||
global $db, $cfg, $err_url;
|
||||
|
||||
if (! $this->response->isAjax()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (empty($_POST['db_collation'])) {
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON('message', Message::error(__('No collation provided.')));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Util::checkParameters(['db']);
|
||||
|
||||
$err_url = Util::getScriptNameForOption($cfg['DefaultTabDatabase'], 'database');
|
||||
$err_url .= Url::getCommon(['db' => $db], '&');
|
||||
|
||||
if (! $this->hasDatabase()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sql_query = 'ALTER DATABASE ' . Util::backquote($db)
|
||||
. ' DEFAULT' . Util::getCharsetQueryPart($_POST['db_collation'] ?? '');
|
||||
$this->dbi->query($sql_query);
|
||||
$message = Message::success();
|
||||
|
||||
/**
|
||||
* Changes tables charset if requested by the user
|
||||
*/
|
||||
if (isset($_POST['change_all_tables_collations']) &&
|
||||
$_POST['change_all_tables_collations'] === 'on'
|
||||
) {
|
||||
[$tables] = Util::getDbInfo($db, null);
|
||||
foreach ($tables as $tableName => $data) {
|
||||
if ($this->dbi->getTable($db, $tableName)->isView()) {
|
||||
// Skip views, we can not change the collation of a view.
|
||||
// issue #15283
|
||||
continue;
|
||||
}
|
||||
$sql_query = 'ALTER TABLE '
|
||||
. Util::backquote($db)
|
||||
. '.'
|
||||
. Util::backquote($tableName)
|
||||
. ' DEFAULT '
|
||||
. Util::getCharsetQueryPart($_POST['db_collation'] ?? '');
|
||||
$this->dbi->query($sql_query);
|
||||
|
||||
/**
|
||||
* Changes columns charset if requested by the user
|
||||
*/
|
||||
if (! isset($_POST['change_all_tables_columns_collations']) ||
|
||||
$_POST['change_all_tables_columns_collations'] !== 'on'
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->operations->changeAllColumnsCollation($db, $tableName, $_POST['db_collation']);
|
||||
}
|
||||
}
|
||||
|
||||
$this->response->setRequestStatus($message->isSuccess());
|
||||
$this->response->addJSON('message', $message);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
/**
|
||||
* Controller for database privileges
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Database;
|
||||
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Server\Privileges;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Util;
|
||||
|
||||
/**
|
||||
* Controller for database privileges
|
||||
*/
|
||||
class PrivilegesController extends AbstractController
|
||||
{
|
||||
/** @var Privileges */
|
||||
private $privileges;
|
||||
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param string $db Database name
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, $db, Privileges $privileges, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template, $db);
|
||||
$this->privileges = $privileges;
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $params Request parameters
|
||||
*/
|
||||
public function index(array $params): string
|
||||
{
|
||||
global $cfg, $text_dir, $PMA_Theme;
|
||||
|
||||
$scriptName = Util::getScriptNameForOption(
|
||||
$cfg['DefaultTabDatabase'],
|
||||
'database'
|
||||
);
|
||||
|
||||
$privileges = [];
|
||||
if ($this->dbi->isSuperUser()) {
|
||||
$privileges = $this->privileges->getAllPrivileges($params['checkprivsdb']);
|
||||
}
|
||||
|
||||
return $this->template->render('database/privileges/index', [
|
||||
'is_superuser' => $this->dbi->isSuperUser(),
|
||||
'db' => $params['checkprivsdb'],
|
||||
'database_url' => $scriptName,
|
||||
'theme_image_path' => $PMA_Theme->getImgPath(),
|
||||
'text_dir' => $text_dir,
|
||||
'is_createuser' => $this->dbi->isCreateUser(),
|
||||
'is_grantuser' => $this->dbi->isGrantUser(),
|
||||
'privileges' => $privileges,
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,169 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Database;
|
||||
|
||||
use PhpMyAdmin\Database\Qbe;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Operations;
|
||||
use PhpMyAdmin\Relation;
|
||||
use PhpMyAdmin\RelationCleanup;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\SavedSearches;
|
||||
use PhpMyAdmin\Sql;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Transformations;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
use function stripos;
|
||||
|
||||
class QueryByExampleController extends AbstractController
|
||||
{
|
||||
/** @var Relation */
|
||||
private $relation;
|
||||
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param string $db Database name
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, $db, Relation $relation, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template, $db);
|
||||
$this->relation = $relation;
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $db, $savedSearchList, $savedSearch, $currentSearchId, $PMA_Theme;
|
||||
global $sql_query, $goto, $sub_part, $tables, $num_tables, $total_num_tables;
|
||||
global $tooltip_truename, $tooltip_aliasname, $pos, $url_params, $cfg, $err_url;
|
||||
|
||||
// Gets the relation settings
|
||||
$cfgRelation = $this->relation->getRelationsParam();
|
||||
|
||||
$savedSearchList = [];
|
||||
$savedSearch = null;
|
||||
$currentSearchId = null;
|
||||
$this->addScriptFiles(['database/qbe.js']);
|
||||
if ($cfgRelation['savedsearcheswork']) {
|
||||
//Get saved search list.
|
||||
$savedSearch = new SavedSearches($GLOBALS, $this->relation);
|
||||
$savedSearch->setUsername($GLOBALS['cfg']['Server']['user'])
|
||||
->setDbname($db);
|
||||
|
||||
if (! empty($_POST['searchId'])) {
|
||||
$savedSearch->setId($_POST['searchId']);
|
||||
}
|
||||
|
||||
//Action field is sent.
|
||||
if (isset($_POST['action'])) {
|
||||
$savedSearch->setSearchName($_POST['searchName']);
|
||||
if ($_POST['action'] === 'create') {
|
||||
$saveResult = $savedSearch->setId(null)
|
||||
->setCriterias($_POST)
|
||||
->save();
|
||||
} elseif ($_POST['action'] === 'update') {
|
||||
$saveResult = $savedSearch->setCriterias($_POST)
|
||||
->save();
|
||||
} elseif ($_POST['action'] === 'delete') {
|
||||
$deleteResult = $savedSearch->delete();
|
||||
//After deletion, reset search.
|
||||
$savedSearch = new SavedSearches($GLOBALS, $this->relation);
|
||||
$savedSearch->setUsername($GLOBALS['cfg']['Server']['user'])
|
||||
->setDbname($db);
|
||||
$_POST = [];
|
||||
} elseif ($_POST['action'] === 'load') {
|
||||
if (empty($_POST['searchId'])) {
|
||||
//when not loading a search, reset the object.
|
||||
$savedSearch = new SavedSearches($GLOBALS, $this->relation);
|
||||
$savedSearch->setUsername($GLOBALS['cfg']['Server']['user'])
|
||||
->setDbname($db);
|
||||
$_POST = [];
|
||||
} else {
|
||||
$loadResult = $savedSearch->load();
|
||||
}
|
||||
}
|
||||
//Else, it's an "update query"
|
||||
}
|
||||
|
||||
$savedSearchList = $savedSearch->getList();
|
||||
$currentSearchId = $savedSearch->getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* A query has been submitted -> (maybe) execute it
|
||||
*/
|
||||
$hasMessageToDisplay = false;
|
||||
if (isset($_POST['submit_sql']) && ! empty($sql_query)) {
|
||||
if (stripos($sql_query, 'SELECT') !== 0) {
|
||||
$hasMessageToDisplay = true;
|
||||
} else {
|
||||
$goto = Url::getFromRoute('/database/sql');
|
||||
|
||||
$sql = new Sql(
|
||||
$this->dbi,
|
||||
$this->relation,
|
||||
new RelationCleanup($this->dbi, $this->relation),
|
||||
new Operations($this->dbi, $this->relation),
|
||||
new Transformations(),
|
||||
$this->template
|
||||
);
|
||||
|
||||
$this->response->addHTML($sql->executeQueryAndSendQueryResponse(
|
||||
null, // analyzed_sql_results
|
||||
false, // is_gotofile
|
||||
$_POST['db'], // db
|
||||
null, // table
|
||||
false, // find_real_end
|
||||
null, // sql_query_for_bookmark
|
||||
null, // extra_data
|
||||
null, // message_to_show
|
||||
null, // sql_data
|
||||
$goto, // goto
|
||||
$PMA_Theme->getImgPath(),
|
||||
null, // disp_query
|
||||
null, // disp_message
|
||||
$sql_query, // sql_query
|
||||
null // complete_query
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
$sub_part = '_qbe';
|
||||
|
||||
Util::checkParameters(['db']);
|
||||
|
||||
$err_url = Util::getScriptNameForOption($cfg['DefaultTabDatabase'], 'database');
|
||||
$err_url .= Url::getCommon(['db' => $db], '&');
|
||||
|
||||
if (! $this->hasDatabase()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$url_params['goto'] = Url::getFromRoute('/database/qbe');
|
||||
|
||||
[
|
||||
$tables,
|
||||
$num_tables,
|
||||
$total_num_tables,
|
||||
$sub_part,,,
|
||||
$tooltip_truename,
|
||||
$tooltip_aliasname,
|
||||
$pos,
|
||||
] = Util::getDbInfo($db, $sub_part ?? '');
|
||||
|
||||
$databaseQbe = new Qbe($this->relation, $this->template, $this->dbi, $db, $savedSearchList, $savedSearch);
|
||||
|
||||
$this->render('database/qbe/index', [
|
||||
'url_params' => $url_params,
|
||||
'has_message_to_display' => $hasMessageToDisplay,
|
||||
'selection_form_html' => $databaseQbe->getSelectionForm(),
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Database;
|
||||
|
||||
use PhpMyAdmin\CheckUserPrivileges;
|
||||
use PhpMyAdmin\Core;
|
||||
use PhpMyAdmin\Database\Routines;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\DbTableExists;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
use function in_array;
|
||||
use function strlen;
|
||||
|
||||
/**
|
||||
* Routines management.
|
||||
*/
|
||||
class RoutinesController extends AbstractController
|
||||
{
|
||||
/** @var CheckUserPrivileges */
|
||||
private $checkUserPrivileges;
|
||||
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param string $db Database name
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, $db, CheckUserPrivileges $checkUserPrivileges, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template, $db);
|
||||
$this->checkUserPrivileges = $checkUserPrivileges;
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $db, $table, $tables, $num_tables, $total_num_tables, $sub_part;
|
||||
global $tooltip_truename, $tooltip_aliasname, $pos;
|
||||
global $errors, $PMA_Theme, $text_dir, $err_url, $url_params, $cfg;
|
||||
|
||||
$type = $_REQUEST['type'] ?? null;
|
||||
|
||||
$this->checkUserPrivileges->getPrivileges();
|
||||
|
||||
if (! $this->response->isAjax()) {
|
||||
/**
|
||||
* Displays the header and tabs
|
||||
*/
|
||||
if (! empty($table) && in_array($table, $this->dbi->getTables($db))) {
|
||||
Util::checkParameters(['db', 'table']);
|
||||
|
||||
$url_params = ['db' => $db, 'table' => $table];
|
||||
$err_url = Util::getScriptNameForOption($cfg['DefaultTabTable'], 'table');
|
||||
$err_url .= Url::getCommon($url_params, '&');
|
||||
|
||||
DbTableExists::check();
|
||||
} else {
|
||||
$table = '';
|
||||
|
||||
Util::checkParameters(['db']);
|
||||
|
||||
$err_url = Util::getScriptNameForOption($cfg['DefaultTabDatabase'], 'database');
|
||||
$err_url .= Url::getCommon(['db' => $db], '&');
|
||||
|
||||
if (! $this->hasDatabase()) {
|
||||
return;
|
||||
}
|
||||
|
||||
[
|
||||
$tables,
|
||||
$num_tables,
|
||||
$total_num_tables,
|
||||
$sub_part,,,
|
||||
$tooltip_truename,
|
||||
$tooltip_aliasname,
|
||||
$pos,
|
||||
] = Util::getDbInfo($db, $sub_part ?? '');
|
||||
}
|
||||
} elseif (strlen($db) > 0) {
|
||||
$this->dbi->selectDb($db);
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep a list of errors that occurred while
|
||||
* processing an 'Add' or 'Edit' operation.
|
||||
*/
|
||||
$errors = [];
|
||||
|
||||
$routines = new Routines($this->dbi, $this->template, $this->response);
|
||||
|
||||
$routines->handleEditor();
|
||||
$routines->handleExecute();
|
||||
$routines->export();
|
||||
|
||||
if (! Core::isValid($type, ['FUNCTION', 'PROCEDURE'])) {
|
||||
$type = null;
|
||||
}
|
||||
|
||||
$items = $this->dbi->getRoutines($db, $type);
|
||||
$isAjax = $this->response->isAjax() && empty($_REQUEST['ajax_page_request']);
|
||||
|
||||
$rows = '';
|
||||
foreach ($items as $item) {
|
||||
$rows .= $routines->getRow($item, $isAjax ? 'ajaxInsert hide' : '');
|
||||
}
|
||||
|
||||
$this->render('database/routines/index', [
|
||||
'db' => $db,
|
||||
'table' => $table,
|
||||
'items' => $items,
|
||||
'rows' => $rows,
|
||||
'select_all_arrow_src' => $PMA_Theme->getImgPath() . 'arrow_' . $text_dir . '.png',
|
||||
'has_privilege' => Util::currentUserHasPrivilege('CREATE ROUTINE', $db, $table),
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Database;
|
||||
|
||||
use PhpMyAdmin\Database\Search;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Html\Generator;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
|
||||
class SearchController extends AbstractController
|
||||
{
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param string $db Database name.
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, $db, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template, $db);
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $cfg, $db, $err_url, $url_params, $tables, $num_tables, $total_num_tables, $sub_part;
|
||||
global $tooltip_truename, $tooltip_aliasname, $pos;
|
||||
|
||||
$this->addScriptFiles([
|
||||
'database/search.js',
|
||||
'vendor/stickyfill.min.js',
|
||||
'sql.js',
|
||||
'makegrid.js',
|
||||
]);
|
||||
|
||||
Util::checkParameters(['db']);
|
||||
|
||||
$err_url = Util::getScriptNameForOption($cfg['DefaultTabDatabase'], 'database');
|
||||
$err_url .= Url::getCommon(['db' => $db], '&');
|
||||
|
||||
if (! $this->hasDatabase()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If config variable $cfg['UseDbSearch'] is on false : exit.
|
||||
if (! $cfg['UseDbSearch']) {
|
||||
Generator::mysqlDie(
|
||||
__('Access denied!'),
|
||||
'',
|
||||
false,
|
||||
$err_url
|
||||
);
|
||||
}
|
||||
$url_params['goto'] = Url::getFromRoute('/database/search');
|
||||
|
||||
// Create a database search instance
|
||||
$databaseSearch = new Search($this->dbi, $db, $this->template);
|
||||
|
||||
// Display top links if we are not in an Ajax request
|
||||
if (! $this->response->isAjax()) {
|
||||
[
|
||||
$tables,
|
||||
$num_tables,
|
||||
$total_num_tables,
|
||||
$sub_part,,,
|
||||
$tooltip_truename,
|
||||
$tooltip_aliasname,
|
||||
$pos,
|
||||
] = Util::getDbInfo($db, $sub_part ?? '');
|
||||
}
|
||||
|
||||
// Main search form has been submitted, get results
|
||||
if (isset($_POST['submit_search'])) {
|
||||
$this->response->addHTML($databaseSearch->getSearchResults());
|
||||
}
|
||||
|
||||
// If we are in an Ajax request, we need to exit after displaying all the HTML
|
||||
if ($this->response->isAjax() && empty($_REQUEST['ajax_page_request'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Display the search form
|
||||
$this->response->addHTML($databaseSearch->getMainHtml());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Database;
|
||||
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use function json_encode;
|
||||
|
||||
/**
|
||||
* Table/Column autocomplete in SQL editors.
|
||||
*/
|
||||
class SqlAutoCompleteController extends AbstractController
|
||||
{
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param string $db Database name.
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, $db, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template, $db);
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $cfg, $db, $sql_autocomplete;
|
||||
|
||||
$sql_autocomplete = true;
|
||||
if ($cfg['EnableAutocompleteForTablesAndColumns']) {
|
||||
$db = $_POST['db'] ?? $db;
|
||||
$sql_autocomplete = [];
|
||||
if ($db) {
|
||||
$tableNames = $this->dbi->getTables($db);
|
||||
foreach ($tableNames as $tableName) {
|
||||
$sql_autocomplete[$tableName] = $this->dbi->getColumns(
|
||||
$db,
|
||||
$tableName
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->response->addJSON(['tables' => json_encode($sql_autocomplete)]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Database;
|
||||
|
||||
use PhpMyAdmin\Config\PageSettings;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\SqlQueryForm;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
use function htmlspecialchars;
|
||||
|
||||
/**
|
||||
* Database SQL executor
|
||||
*/
|
||||
class SqlController extends AbstractController
|
||||
{
|
||||
/** @var SqlQueryForm */
|
||||
private $sqlQueryForm;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param string $db Database name
|
||||
*/
|
||||
public function __construct($response, Template $template, $db, SqlQueryForm $sqlQueryForm)
|
||||
{
|
||||
parent::__construct($response, $template, $db);
|
||||
$this->sqlQueryForm = $sqlQueryForm;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $goto, $back, $db, $cfg, $err_url;
|
||||
|
||||
$this->addScriptFiles([
|
||||
'makegrid.js',
|
||||
'vendor/jquery/jquery.uitablefilter.js',
|
||||
'vendor/stickyfill.min.js',
|
||||
'sql.js',
|
||||
]);
|
||||
|
||||
$pageSettings = new PageSettings('Sql');
|
||||
$this->response->addHTML($pageSettings->getErrorHTML());
|
||||
$this->response->addHTML($pageSettings->getHTML());
|
||||
|
||||
Util::checkParameters(['db']);
|
||||
|
||||
$err_url = Util::getScriptNameForOption($cfg['DefaultTabDatabase'], 'database');
|
||||
$err_url .= Url::getCommon(['db' => $db], '&');
|
||||
|
||||
if (! $this->hasDatabase()) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* After a syntax error, we return to this script
|
||||
* with the typed query in the textarea.
|
||||
*/
|
||||
$goto = Url::getFromRoute('/database/sql');
|
||||
$back = $goto;
|
||||
|
||||
$this->response->addHTML($this->sqlQueryForm->getHtml(
|
||||
true,
|
||||
false,
|
||||
isset($_POST['delimiter'])
|
||||
? htmlspecialchars($_POST['delimiter'])
|
||||
: ';'
|
||||
));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Database;
|
||||
|
||||
use PhpMyAdmin\SqlParser\Utils\Formatter;
|
||||
use function strlen;
|
||||
|
||||
/**
|
||||
* Format SQL for SQL editors.
|
||||
*/
|
||||
class SqlFormatController extends AbstractController
|
||||
{
|
||||
public function index(): void
|
||||
{
|
||||
$params = ['sql' => $_POST['sql'] ?? null];
|
||||
$query = strlen((string) $params['sql']) > 0 ? $params['sql'] : '';
|
||||
$this->response->addJSON(['sql' => Formatter::format($query)]);
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,159 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Database;
|
||||
|
||||
use PhpMyAdmin\CheckUserPrivileges;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Html\Generator;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Tracker;
|
||||
use PhpMyAdmin\Tracking;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
use function count;
|
||||
use function htmlspecialchars;
|
||||
use function sprintf;
|
||||
|
||||
/**
|
||||
* Tracking configuration for database.
|
||||
*/
|
||||
class TrackingController extends AbstractController
|
||||
{
|
||||
/** @var Tracking */
|
||||
private $tracking;
|
||||
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param string $db Database name.
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, $db, Tracking $tracking, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template, $db);
|
||||
$this->tracking = $tracking;
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $db, $text_dir, $url_params, $tables, $num_tables, $PMA_Theme;
|
||||
global $total_num_tables, $sub_part, $pos, $data, $cfg;
|
||||
global $tooltip_truename, $tooltip_aliasname, $err_url;
|
||||
|
||||
$this->addScriptFiles(['vendor/jquery/jquery.tablesorter.js', 'database/tracking.js']);
|
||||
|
||||
Util::checkParameters(['db']);
|
||||
|
||||
$err_url = Util::getScriptNameForOption($cfg['DefaultTabDatabase'], 'database');
|
||||
$err_url .= Url::getCommon(['db' => $db], '&');
|
||||
|
||||
if (! $this->hasDatabase()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$url_params['goto'] = Url::getFromRoute('/table/tracking');
|
||||
$url_params['back'] = Url::getFromRoute('/database/tracking');
|
||||
|
||||
// Get the database structure
|
||||
$sub_part = '_structure';
|
||||
|
||||
[
|
||||
$tables,
|
||||
$num_tables,
|
||||
$total_num_tables,
|
||||
$sub_part,,
|
||||
$isSystemSchema,
|
||||
$tooltip_truename,
|
||||
$tooltip_aliasname,
|
||||
$pos,
|
||||
] = Util::getDbInfo($db, $sub_part ?? '');
|
||||
|
||||
if (isset($_POST['delete_tracking'], $_POST['table'])) {
|
||||
Tracker::deleteTracking($db, $_POST['table']);
|
||||
echo Message::success(
|
||||
__('Tracking data deleted successfully.')
|
||||
)->getDisplay();
|
||||
} elseif (isset($_POST['submit_create_version'])) {
|
||||
$this->tracking->createTrackingForMultipleTables($_POST['selected']);
|
||||
echo Message::success(
|
||||
sprintf(
|
||||
__(
|
||||
'Version %1$s was created for selected tables,'
|
||||
. ' tracking is active for them.'
|
||||
),
|
||||
htmlspecialchars($_POST['version'])
|
||||
)
|
||||
)->getDisplay();
|
||||
} elseif (isset($_POST['submit_mult'])) {
|
||||
if (! empty($_POST['selected_tbl'])) {
|
||||
if ($_POST['submit_mult'] === 'delete_tracking') {
|
||||
foreach ($_POST['selected_tbl'] as $table) {
|
||||
Tracker::deleteTracking($db, $table);
|
||||
}
|
||||
echo Message::success(
|
||||
__('Tracking data deleted successfully.')
|
||||
)->getDisplay();
|
||||
} elseif ($_POST['submit_mult'] === 'track') {
|
||||
echo $this->template->render('create_tracking_version', [
|
||||
'route' => '/database/tracking',
|
||||
'url_params' => $url_params,
|
||||
'last_version' => 0,
|
||||
'db' => $db,
|
||||
'selected' => $_POST['selected_tbl'],
|
||||
'type' => 'both',
|
||||
'default_statements' => $cfg['Server']['tracking_default_statements'],
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
echo Message::notice(
|
||||
__('No tables selected.')
|
||||
)->getDisplay();
|
||||
}
|
||||
}
|
||||
|
||||
// Get tracked data about the database
|
||||
$data = Tracker::getTrackedData($db, '', '1');
|
||||
|
||||
// No tables present and no log exist
|
||||
if ($num_tables == 0 && count($data['ddlog']) === 0) {
|
||||
echo '<p>' , __('No tables found in database.') , '</p>' , "\n";
|
||||
|
||||
if (empty($isSystemSchema)) {
|
||||
$checkUserPrivileges = new CheckUserPrivileges($this->dbi);
|
||||
$checkUserPrivileges->getPrivileges();
|
||||
|
||||
echo $this->template->render('database/create_table', ['db' => $db]);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
echo $this->tracking->getHtmlForDbTrackingTables(
|
||||
$db,
|
||||
$url_params,
|
||||
$PMA_Theme->getImgPath(),
|
||||
$text_dir
|
||||
);
|
||||
|
||||
// If available print out database log
|
||||
if (count($data['ddlog']) <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$log = '';
|
||||
foreach ($data['ddlog'] as $entry) {
|
||||
$log .= '# ' . $entry['date'] . ' ' . $entry['username'] . "\n"
|
||||
. $entry['statement'] . "\n";
|
||||
}
|
||||
echo Generator::getMessage(__('Database Log'), $log);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Database;
|
||||
|
||||
use PhpMyAdmin\Database\Triggers;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\DbTableExists;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
use function in_array;
|
||||
use function strlen;
|
||||
|
||||
/**
|
||||
* Triggers management.
|
||||
*/
|
||||
class TriggersController extends AbstractController
|
||||
{
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param string $db Database name.
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, $db, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template, $db);
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $db, $table, $tables, $num_tables, $total_num_tables, $sub_part;
|
||||
global $tooltip_truename, $tooltip_aliasname, $pos;
|
||||
global $errors, $url_params, $err_url, $cfg;
|
||||
|
||||
if (! $this->response->isAjax()) {
|
||||
/**
|
||||
* Displays the header and tabs
|
||||
*/
|
||||
if (! empty($table) && in_array($table, $this->dbi->getTables($db))) {
|
||||
Util::checkParameters(['db', 'table']);
|
||||
|
||||
$url_params = ['db' => $db, 'table' => $table];
|
||||
$err_url = Util::getScriptNameForOption($cfg['DefaultTabTable'], 'table');
|
||||
$err_url .= Url::getCommon($url_params, '&');
|
||||
|
||||
DbTableExists::check();
|
||||
} else {
|
||||
$table = '';
|
||||
|
||||
Util::checkParameters(['db']);
|
||||
|
||||
$err_url = Util::getScriptNameForOption($cfg['DefaultTabDatabase'], 'database');
|
||||
$err_url .= Url::getCommon(['db' => $db], '&');
|
||||
|
||||
if (! $this->hasDatabase()) {
|
||||
return;
|
||||
}
|
||||
|
||||
[
|
||||
$tables,
|
||||
$num_tables,
|
||||
$total_num_tables,
|
||||
$sub_part,,,
|
||||
$tooltip_truename,
|
||||
$tooltip_aliasname,
|
||||
$pos,
|
||||
] = Util::getDbInfo($db, $sub_part ?? '');
|
||||
}
|
||||
} elseif (strlen($db) > 0) {
|
||||
$this->dbi->selectDb($db);
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep a list of errors that occurred while
|
||||
* processing an 'Add' or 'Edit' operation.
|
||||
*/
|
||||
$errors = [];
|
||||
|
||||
$triggers = new Triggers($this->dbi, $this->template, $this->response);
|
||||
$triggers->main();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers;
|
||||
|
||||
final class DatabaseController extends AbstractController
|
||||
{
|
||||
public function all(): void
|
||||
{
|
||||
global $dblist;
|
||||
|
||||
$this->response->addJSON(['databases' => $dblist->databases]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,163 @@
|
|||
<?php
|
||||
/**
|
||||
* Handle error report submission
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers;
|
||||
|
||||
use PhpMyAdmin\ErrorHandler;
|
||||
use PhpMyAdmin\ErrorReport;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\UserPreferences;
|
||||
use function count;
|
||||
use function in_array;
|
||||
use function is_string;
|
||||
use function json_decode;
|
||||
use function time;
|
||||
|
||||
/**
|
||||
* Handle error report submission
|
||||
*/
|
||||
class ErrorReportController extends AbstractController
|
||||
{
|
||||
/** @var ErrorReport */
|
||||
private $errorReport;
|
||||
|
||||
/** @var ErrorHandler */
|
||||
private $errorHandler;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
*/
|
||||
public function __construct(
|
||||
$response,
|
||||
Template $template,
|
||||
ErrorReport $errorReport,
|
||||
ErrorHandler $errorHandler
|
||||
) {
|
||||
parent::__construct($response, $template);
|
||||
$this->errorReport = $errorReport;
|
||||
$this->errorHandler = $errorHandler;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $cfg;
|
||||
|
||||
if (! isset($_POST['exception_type'])
|
||||
|| ! in_array($_POST['exception_type'], ['js', 'php'])
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($_POST['send_error_report'])
|
||||
&& ($_POST['send_error_report'] == true
|
||||
|| $_POST['send_error_report'] == '1')
|
||||
) {
|
||||
if ($_POST['exception_type'] === 'php') {
|
||||
/**
|
||||
* Prevent infinite error submission.
|
||||
* Happens in case error submissions fails.
|
||||
* If reporting is done in some time interval,
|
||||
* just clear them & clear json data too.
|
||||
*/
|
||||
if (isset($_SESSION['prev_error_subm_time'], $_SESSION['error_subm_count'])
|
||||
&& $_SESSION['error_subm_count'] >= 3
|
||||
&& ($_SESSION['prev_error_subm_time'] - time()) <= 3000
|
||||
) {
|
||||
$_SESSION['error_subm_count'] = 0;
|
||||
$_SESSION['prev_errors'] = '';
|
||||
$this->response->addJSON('stopErrorReportLoop', '1');
|
||||
} else {
|
||||
$_SESSION['prev_error_subm_time'] = time();
|
||||
$_SESSION['error_subm_count'] = isset($_SESSION['error_subm_count'])
|
||||
? $_SESSION['error_subm_count'] + 1
|
||||
: 0;
|
||||
}
|
||||
}
|
||||
$reportData = $this->errorReport->getData($_POST['exception_type']);
|
||||
// report if and only if there were 'actual' errors.
|
||||
if (count($reportData) > 0) {
|
||||
$server_response = $this->errorReport->send($reportData);
|
||||
if (! is_string($server_response)) {
|
||||
$success = false;
|
||||
} else {
|
||||
$decoded_response = json_decode($server_response, true);
|
||||
$success = ! empty($decoded_response) ?
|
||||
$decoded_response['success'] : false;
|
||||
}
|
||||
|
||||
/* Message to show to the user */
|
||||
if ($success) {
|
||||
if ((isset($_POST['automatic'])
|
||||
&& $_POST['automatic'] === 'true')
|
||||
|| $cfg['SendErrorReports'] === 'always'
|
||||
) {
|
||||
$msg = __(
|
||||
'An error has been detected and an error report has been '
|
||||
. 'automatically submitted based on your settings.'
|
||||
);
|
||||
} else {
|
||||
$msg = __('Thank you for submitting this report.');
|
||||
}
|
||||
} else {
|
||||
$msg = __(
|
||||
'An error has been detected and an error report has been '
|
||||
. 'generated but failed to be sent.'
|
||||
);
|
||||
$msg .= ' ';
|
||||
$msg .= __(
|
||||
'If you experience any '
|
||||
. 'problems please submit a bug report manually.'
|
||||
);
|
||||
}
|
||||
$msg .= ' ' . __('You may want to refresh the page.');
|
||||
|
||||
/* Create message object */
|
||||
if ($success) {
|
||||
$msg = Message::notice($msg);
|
||||
} else {
|
||||
$msg = Message::error($msg);
|
||||
}
|
||||
|
||||
/* Add message to response */
|
||||
if ($this->response->isAjax()) {
|
||||
if ($_POST['exception_type'] === 'js') {
|
||||
$this->response->addJSON('message', $msg);
|
||||
} else {
|
||||
$this->response->addJSON('errSubmitMsg', $msg);
|
||||
}
|
||||
} elseif ($_POST['exception_type'] === 'php') {
|
||||
$jsCode = 'Functions.ajaxShowMessage(\'<div class="alert alert-danger" role="alert">'
|
||||
. $msg
|
||||
. '</div>\', false);';
|
||||
$this->response->getFooter()->getScripts()->addCode($jsCode);
|
||||
}
|
||||
|
||||
if ($_POST['exception_type'] === 'php') {
|
||||
// clear previous errors & save new ones.
|
||||
$this->errorHandler->savePreviousErrors();
|
||||
}
|
||||
|
||||
/* Persist always send settings */
|
||||
if (isset($_POST['always_send'])
|
||||
&& $_POST['always_send'] === 'true'
|
||||
) {
|
||||
$userPreferences = new UserPreferences();
|
||||
$userPreferences->persistOption('SendErrorReports', 'always', 'ask');
|
||||
}
|
||||
}
|
||||
} elseif (! empty($_POST['get_settings'])) {
|
||||
$this->response->addJSON('report_setting', $cfg['SendErrorReports']);
|
||||
} elseif ($_POST['exception_type'] === 'js') {
|
||||
$this->response->addHTML($this->errorReport->getForm());
|
||||
} else {
|
||||
// clear previous errors & save new ones.
|
||||
$this->errorHandler->savePreviousErrors();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,704 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers;
|
||||
|
||||
use PhpMyAdmin\Controllers\Database\ExportController as DatabaseExportController;
|
||||
use PhpMyAdmin\Core;
|
||||
use PhpMyAdmin\Encoding;
|
||||
use PhpMyAdmin\Exceptions\ExportException;
|
||||
use PhpMyAdmin\Export;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\Plugins;
|
||||
use PhpMyAdmin\Plugins\ExportPlugin;
|
||||
use PhpMyAdmin\Relation;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Sanitize;
|
||||
use PhpMyAdmin\SqlParser\Parser;
|
||||
use PhpMyAdmin\SqlParser\Statements\SelectStatement;
|
||||
use PhpMyAdmin\SqlParser\Utils\Misc;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
use const PHP_EOL;
|
||||
use function count;
|
||||
use function function_exists;
|
||||
use function in_array;
|
||||
use function ini_set;
|
||||
use function is_array;
|
||||
use function ob_end_clean;
|
||||
use function ob_get_length;
|
||||
use function ob_get_level;
|
||||
use function register_shutdown_function;
|
||||
use function strlen;
|
||||
use function time;
|
||||
|
||||
final class ExportController extends AbstractController
|
||||
{
|
||||
/** @var Export */
|
||||
private $export;
|
||||
|
||||
/** @var Relation */
|
||||
private $relation;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
*/
|
||||
public function __construct($response, Template $template, Export $export, Relation $relation)
|
||||
{
|
||||
parent::__construct($response, $template);
|
||||
$this->export = $export;
|
||||
$this->relation = $relation;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $containerBuilder, $db, $export_type, $filename_template, $sql_query, $err_url, $message;
|
||||
global $compression, $crlf, $asfile, $buffer_needed, $save_on_server, $file_handle, $separate_files;
|
||||
global $output_charset_conversion, $output_kanji_conversion, $table, $what, $export_plugin, $single_table;
|
||||
global $compression_methods, $onserver, $back_button, $refreshButton, $save_filename, $filename;
|
||||
global $quick_export, $cfg, $tables, $table_select, $aliases, $dump_buffer, $dump_buffer_len;
|
||||
global $time_start, $charset, $remember_template, $mime_type, $num_tables, $dump_buffer_objects;
|
||||
global $active_page, $do_relation, $do_comments, $do_mime, $do_dates, $whatStrucOrData, $db_select;
|
||||
global $table_structure, $table_data, $lock_tables, $allrows, $limit_to, $limit_from;
|
||||
|
||||
$this->addScriptFiles(['export_output.js']);
|
||||
|
||||
/**
|
||||
* Sets globals from $_POST
|
||||
*
|
||||
* - Please keep the parameters in order of their appearance in the form
|
||||
* - Some of these parameters are not used, as the code below directly
|
||||
* verifies from the superglobal $_POST or $_REQUEST
|
||||
* TODO: this should be removed to avoid passing user input to GLOBALS
|
||||
* without checking
|
||||
*/
|
||||
$post_params = [
|
||||
'db',
|
||||
'table',
|
||||
'what',
|
||||
'single_table',
|
||||
'export_type',
|
||||
'export_method',
|
||||
'quick_or_custom',
|
||||
'db_select',
|
||||
'table_select',
|
||||
'table_structure',
|
||||
'table_data',
|
||||
'limit_to',
|
||||
'limit_from',
|
||||
'allrows',
|
||||
'lock_tables',
|
||||
'output_format',
|
||||
'filename_template',
|
||||
'maxsize',
|
||||
'remember_template',
|
||||
'charset',
|
||||
'compression',
|
||||
'as_separate_files',
|
||||
'knjenc',
|
||||
'xkana',
|
||||
'htmlword_structure_or_data',
|
||||
'htmlword_null',
|
||||
'htmlword_columns',
|
||||
'mediawiki_headers',
|
||||
'mediawiki_structure_or_data',
|
||||
'mediawiki_caption',
|
||||
'pdf_structure_or_data',
|
||||
'odt_structure_or_data',
|
||||
'odt_relation',
|
||||
'odt_comments',
|
||||
'odt_mime',
|
||||
'odt_columns',
|
||||
'odt_null',
|
||||
'codegen_structure_or_data',
|
||||
'codegen_format',
|
||||
'excel_null',
|
||||
'excel_removeCRLF',
|
||||
'excel_columns',
|
||||
'excel_edition',
|
||||
'excel_structure_or_data',
|
||||
'yaml_structure_or_data',
|
||||
'ods_null',
|
||||
'ods_structure_or_data',
|
||||
'ods_columns',
|
||||
'json_structure_or_data',
|
||||
'json_pretty_print',
|
||||
'json_unicode',
|
||||
'xml_structure_or_data',
|
||||
'xml_export_events',
|
||||
'xml_export_functions',
|
||||
'xml_export_procedures',
|
||||
'xml_export_tables',
|
||||
'xml_export_triggers',
|
||||
'xml_export_views',
|
||||
'xml_export_contents',
|
||||
'texytext_structure_or_data',
|
||||
'texytext_columns',
|
||||
'texytext_null',
|
||||
'phparray_structure_or_data',
|
||||
'sql_include_comments',
|
||||
'sql_header_comment',
|
||||
'sql_dates',
|
||||
'sql_relation',
|
||||
'sql_mime',
|
||||
'sql_use_transaction',
|
||||
'sql_disable_fk',
|
||||
'sql_compatibility',
|
||||
'sql_structure_or_data',
|
||||
'sql_create_database',
|
||||
'sql_drop_table',
|
||||
'sql_procedure_function',
|
||||
'sql_create_table',
|
||||
'sql_create_view',
|
||||
'sql_create_trigger',
|
||||
'sql_view_current_user',
|
||||
'sql_simple_view_export',
|
||||
'sql_if_not_exists',
|
||||
'sql_or_replace_view',
|
||||
'sql_auto_increment',
|
||||
'sql_backquotes',
|
||||
'sql_truncate',
|
||||
'sql_delayed',
|
||||
'sql_ignore',
|
||||
'sql_type',
|
||||
'sql_insert_syntax',
|
||||
'sql_max_query_size',
|
||||
'sql_hex_for_binary',
|
||||
'sql_utc_time',
|
||||
'sql_drop_database',
|
||||
'sql_views_as_tables',
|
||||
'sql_metadata',
|
||||
'csv_separator',
|
||||
'csv_enclosed',
|
||||
'csv_escaped',
|
||||
'csv_terminated',
|
||||
'csv_null',
|
||||
'csv_removeCRLF',
|
||||
'csv_columns',
|
||||
'csv_structure_or_data',
|
||||
// csv_replace should have been here but we use it directly from $_POST
|
||||
'latex_caption',
|
||||
'latex_structure_or_data',
|
||||
'latex_structure_caption',
|
||||
'latex_structure_continued_caption',
|
||||
'latex_structure_label',
|
||||
'latex_relation',
|
||||
'latex_comments',
|
||||
'latex_mime',
|
||||
'latex_columns',
|
||||
'latex_data_caption',
|
||||
'latex_data_continued_caption',
|
||||
'latex_data_label',
|
||||
'latex_null',
|
||||
'aliases',
|
||||
];
|
||||
|
||||
foreach ($post_params as $one_post_param) {
|
||||
if (! isset($_POST[$one_post_param])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$GLOBALS[$one_post_param] = $_POST[$one_post_param];
|
||||
}
|
||||
|
||||
Util::checkParameters(['what', 'export_type']);
|
||||
|
||||
// sanitize this parameter which will be used below in a file inclusion
|
||||
$what = Core::securePath($_POST['what']);
|
||||
|
||||
// export class instance, not array of properties, as before
|
||||
/** @var ExportPlugin $export_plugin */
|
||||
$export_plugin = Plugins::getPlugin(
|
||||
'export',
|
||||
$what,
|
||||
'libraries/classes/Plugins/Export/',
|
||||
[
|
||||
'export_type' => $export_type,
|
||||
'single_table' => isset($single_table),
|
||||
]
|
||||
);
|
||||
|
||||
// Check export type
|
||||
if (empty($export_plugin)) {
|
||||
Core::fatalError(__('Bad type!'));
|
||||
}
|
||||
|
||||
/**
|
||||
* valid compression methods
|
||||
*/
|
||||
$compression_methods = [];
|
||||
if ($GLOBALS['cfg']['ZipDump'] && function_exists('gzcompress')) {
|
||||
$compression_methods[] = 'zip';
|
||||
}
|
||||
if ($GLOBALS['cfg']['GZipDump'] && function_exists('gzencode')) {
|
||||
$compression_methods[] = 'gzip';
|
||||
}
|
||||
|
||||
/**
|
||||
* init and variable checking
|
||||
*/
|
||||
$compression = '';
|
||||
$onserver = false;
|
||||
$save_on_server = false;
|
||||
$buffer_needed = false;
|
||||
$back_button = '';
|
||||
$refreshButton = '';
|
||||
$save_filename = '';
|
||||
$file_handle = '';
|
||||
$err_url = '';
|
||||
$filename = '';
|
||||
$separate_files = '';
|
||||
|
||||
// Is it a quick or custom export?
|
||||
if (isset($_POST['quick_or_custom'])
|
||||
&& $_POST['quick_or_custom'] === 'quick'
|
||||
) {
|
||||
$quick_export = true;
|
||||
} else {
|
||||
$quick_export = false;
|
||||
}
|
||||
|
||||
if ($_POST['output_format'] === 'astext') {
|
||||
$asfile = false;
|
||||
} else {
|
||||
$asfile = true;
|
||||
$selectedCompression = $_POST['compression'] ?? '';
|
||||
if (isset($_POST['as_separate_files'])
|
||||
&& ! empty($_POST['as_separate_files'])
|
||||
) {
|
||||
if (! empty($selectedCompression)
|
||||
&& $selectedCompression === 'zip'
|
||||
) {
|
||||
$separate_files = $_POST['as_separate_files'];
|
||||
}
|
||||
}
|
||||
if (in_array($selectedCompression, $compression_methods)) {
|
||||
$compression = $selectedCompression;
|
||||
$buffer_needed = true;
|
||||
}
|
||||
if (($quick_export && ! empty($_POST['quick_export_onserver']))
|
||||
|| (! $quick_export && ! empty($_POST['onserver']))
|
||||
) {
|
||||
if ($quick_export) {
|
||||
$onserver = $_POST['quick_export_onserver'];
|
||||
} else {
|
||||
$onserver = $_POST['onserver'];
|
||||
}
|
||||
// Will we save dump on server?
|
||||
$save_on_server = ! empty($cfg['SaveDir']) && $onserver;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If we are sending the export file (as opposed to just displaying it
|
||||
* as text), we have to bypass the usual PhpMyAdmin\Response mechanism
|
||||
*/
|
||||
if (isset($_POST['output_format']) && $_POST['output_format'] === 'sendit' && ! $save_on_server) {
|
||||
$this->response->disable();
|
||||
//Disable all active buffers (see: ob_get_status(true) at this point)
|
||||
do {
|
||||
if (ob_get_length() > 0 || ob_get_level() > 0) {
|
||||
$hasBuffer = ob_end_clean();
|
||||
} else {
|
||||
$hasBuffer = false;
|
||||
}
|
||||
} while ($hasBuffer);
|
||||
}
|
||||
|
||||
$tables = [];
|
||||
// Generate error url and check for needed variables
|
||||
if ($export_type === 'server') {
|
||||
$err_url = Url::getFromRoute('/server/export');
|
||||
} elseif ($export_type === 'database' && strlen($db) > 0) {
|
||||
$err_url = Url::getFromRoute('/database/export', ['db' => $db]);
|
||||
// Check if we have something to export
|
||||
if (isset($table_select)) {
|
||||
$tables = $table_select;
|
||||
} else {
|
||||
$tables = [];
|
||||
}
|
||||
} elseif ($export_type === 'table' && strlen($db) > 0 && strlen($table) > 0) {
|
||||
$err_url = Url::getFromRoute('/table/export', [
|
||||
'db' => $db,
|
||||
'table' => $table,
|
||||
]);
|
||||
} elseif ($export_type === 'raw') {
|
||||
$err_url = Url::getFromRoute('/server/export', ['sql_query' => $sql_query]);
|
||||
} else {
|
||||
Core::fatalError(__('Bad parameters!'));
|
||||
}
|
||||
|
||||
// Merge SQL Query aliases with Export aliases from
|
||||
// export page, Export page aliases are given more
|
||||
// preference over SQL Query aliases.
|
||||
$parser = new Parser($sql_query);
|
||||
$aliases = [];
|
||||
if (! empty($parser->statements[0])
|
||||
&& ($parser->statements[0] instanceof SelectStatement)
|
||||
) {
|
||||
$aliases = Misc::getAliases($parser->statements[0], $db);
|
||||
}
|
||||
if (! empty($_POST['aliases'])) {
|
||||
$aliases = $this->export->mergeAliases($aliases, $_POST['aliases']);
|
||||
$_SESSION['tmpval']['aliases'] = $_POST['aliases'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Increase time limit for script execution and initializes some variables
|
||||
*/
|
||||
Util::setTimeLimit();
|
||||
if (! empty($cfg['MemoryLimit'])) {
|
||||
ini_set('memory_limit', $cfg['MemoryLimit']);
|
||||
}
|
||||
register_shutdown_function([$this->export, 'shutdown']);
|
||||
// Start with empty buffer
|
||||
$dump_buffer = '';
|
||||
$dump_buffer_len = 0;
|
||||
|
||||
// Array of dump_buffers - used in separate file exports
|
||||
$dump_buffer_objects = [];
|
||||
|
||||
// We send fake headers to avoid browser timeout when buffering
|
||||
$time_start = time();
|
||||
|
||||
// Defines the default <CR><LF> format.
|
||||
// For SQL always use \n as MySQL wants this on all platforms.
|
||||
if ($what === 'sql') {
|
||||
$crlf = "\n";
|
||||
} else {
|
||||
$crlf = PHP_EOL;
|
||||
}
|
||||
|
||||
$output_kanji_conversion = Encoding::canConvertKanji();
|
||||
|
||||
// Do we need to convert charset?
|
||||
$output_charset_conversion = $asfile
|
||||
&& Encoding::isSupported()
|
||||
&& isset($charset) && $charset !== 'utf-8';
|
||||
|
||||
// Use on the fly compression?
|
||||
$GLOBALS['onfly_compression'] = $GLOBALS['cfg']['CompressOnFly']
|
||||
&& $compression === 'gzip';
|
||||
if ($GLOBALS['onfly_compression']) {
|
||||
$GLOBALS['memory_limit'] = $this->export->getMemoryLimit();
|
||||
}
|
||||
|
||||
// Generate filename and mime type if needed
|
||||
if ($asfile) {
|
||||
if (empty($remember_template)) {
|
||||
$remember_template = '';
|
||||
}
|
||||
[$filename, $mime_type] = $this->export->getFilenameAndMimetype(
|
||||
$export_type,
|
||||
$remember_template,
|
||||
$export_plugin,
|
||||
$compression,
|
||||
$filename_template
|
||||
);
|
||||
} else {
|
||||
$mime_type = '';
|
||||
}
|
||||
|
||||
// For raw query export, filename will be export.extension
|
||||
if ($export_type === 'raw') {
|
||||
[$filename] = $this->export->getFinalFilenameAndMimetypeForFilename(
|
||||
$export_plugin,
|
||||
$compression,
|
||||
'export'
|
||||
);
|
||||
}
|
||||
|
||||
// Open file on server if needed
|
||||
if ($save_on_server) {
|
||||
[$save_filename, $message, $file_handle] = $this->export->openFile(
|
||||
$filename,
|
||||
$quick_export
|
||||
);
|
||||
|
||||
// problem opening export file on server?
|
||||
if (! empty($message)) {
|
||||
$this->export->showPage($export_type);
|
||||
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
/**
|
||||
* Send headers depending on whether the user chose to download a dump file
|
||||
* or not
|
||||
*/
|
||||
if ($asfile) {
|
||||
// Download
|
||||
// (avoid rewriting data containing HTML with anchors and forms;
|
||||
// this was reported to happen under Plesk)
|
||||
ini_set('url_rewriter.tags', '');
|
||||
$filename = Sanitize::sanitizeFilename($filename);
|
||||
|
||||
Core::downloadHeader($filename, $mime_type);
|
||||
} else {
|
||||
// HTML
|
||||
if ($export_type === 'database') {
|
||||
$num_tables = count($tables);
|
||||
if ($num_tables === 0) {
|
||||
$message = Message::error(
|
||||
__('No tables found in database.')
|
||||
);
|
||||
$active_page = Url::getFromRoute('/database/export');
|
||||
/** @var DatabaseExportController $controller */
|
||||
$controller = $containerBuilder->get(DatabaseExportController::class);
|
||||
$controller->index();
|
||||
exit;
|
||||
}
|
||||
}
|
||||
[$html, $back_button, $refreshButton] = $this->export->getHtmlForDisplayedExportHeader(
|
||||
$export_type,
|
||||
$db,
|
||||
$table
|
||||
);
|
||||
echo $html;
|
||||
unset($html);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// Re - initialize
|
||||
$dump_buffer = '';
|
||||
$dump_buffer_len = 0;
|
||||
|
||||
// Add possibly some comments to export
|
||||
if (! $export_plugin->exportHeader()) {
|
||||
throw new ExportException('Failure during header export.');
|
||||
}
|
||||
|
||||
// Will we need relation & co. setup?
|
||||
$do_relation = isset($GLOBALS[$what . '_relation']);
|
||||
$do_comments = isset($GLOBALS[$what . '_include_comments'])
|
||||
|| isset($GLOBALS[$what . '_comments']);
|
||||
$do_mime = isset($GLOBALS[$what . '_mime']);
|
||||
if ($do_relation || $do_comments || $do_mime) {
|
||||
$this->relation->getRelationsParam();
|
||||
}
|
||||
|
||||
// Include dates in export?
|
||||
$do_dates = isset($GLOBALS[$what . '_dates']);
|
||||
|
||||
$whatStrucOrData = $GLOBALS[$what . '_structure_or_data'];
|
||||
|
||||
if ($export_type === 'raw') {
|
||||
$whatStrucOrData = 'raw';
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the dump
|
||||
*/
|
||||
if ($export_type === 'server') {
|
||||
if (! isset($db_select)) {
|
||||
$db_select = '';
|
||||
}
|
||||
$this->export->exportServer(
|
||||
$db_select,
|
||||
$whatStrucOrData,
|
||||
$export_plugin,
|
||||
$crlf,
|
||||
$err_url,
|
||||
$export_type,
|
||||
$do_relation,
|
||||
$do_comments,
|
||||
$do_mime,
|
||||
$do_dates,
|
||||
$aliases,
|
||||
$separate_files
|
||||
);
|
||||
} elseif ($export_type === 'database') {
|
||||
if (! isset($table_structure) || ! is_array($table_structure)) {
|
||||
$table_structure = [];
|
||||
}
|
||||
if (! isset($table_data) || ! is_array($table_data)) {
|
||||
$table_data = [];
|
||||
}
|
||||
if (! empty($_POST['structure_or_data_forced'])) {
|
||||
$table_structure = $tables;
|
||||
$table_data = $tables;
|
||||
}
|
||||
if (isset($lock_tables)) {
|
||||
$this->export->lockTables($db, $tables, 'READ');
|
||||
try {
|
||||
$this->export->exportDatabase(
|
||||
$db,
|
||||
$tables,
|
||||
$whatStrucOrData,
|
||||
$table_structure,
|
||||
$table_data,
|
||||
$export_plugin,
|
||||
$crlf,
|
||||
$err_url,
|
||||
$export_type,
|
||||
$do_relation,
|
||||
$do_comments,
|
||||
$do_mime,
|
||||
$do_dates,
|
||||
$aliases,
|
||||
$separate_files
|
||||
);
|
||||
} finally {
|
||||
$this->export->unlockTables();
|
||||
}
|
||||
} else {
|
||||
$this->export->exportDatabase(
|
||||
$db,
|
||||
$tables,
|
||||
$whatStrucOrData,
|
||||
$table_structure,
|
||||
$table_data,
|
||||
$export_plugin,
|
||||
$crlf,
|
||||
$err_url,
|
||||
$export_type,
|
||||
$do_relation,
|
||||
$do_comments,
|
||||
$do_mime,
|
||||
$do_dates,
|
||||
$aliases,
|
||||
$separate_files
|
||||
);
|
||||
}
|
||||
} elseif ($export_type === 'raw') {
|
||||
Export::exportRaw(
|
||||
$whatStrucOrData,
|
||||
$export_plugin,
|
||||
$crlf,
|
||||
$err_url,
|
||||
$sql_query,
|
||||
$export_type
|
||||
);
|
||||
} else {
|
||||
// We export just one table
|
||||
// $allrows comes from the form when "Dump all rows" has been selected
|
||||
if (! isset($allrows)) {
|
||||
$allrows = '';
|
||||
}
|
||||
if (! isset($limit_to)) {
|
||||
$limit_to = '0';
|
||||
}
|
||||
if (! isset($limit_from)) {
|
||||
$limit_from = '0';
|
||||
}
|
||||
if (isset($lock_tables)) {
|
||||
try {
|
||||
$this->export->lockTables($db, [$table], 'READ');
|
||||
$this->export->exportTable(
|
||||
$db,
|
||||
$table,
|
||||
$whatStrucOrData,
|
||||
$export_plugin,
|
||||
$crlf,
|
||||
$err_url,
|
||||
$export_type,
|
||||
$do_relation,
|
||||
$do_comments,
|
||||
$do_mime,
|
||||
$do_dates,
|
||||
$allrows,
|
||||
$limit_to,
|
||||
$limit_from,
|
||||
$sql_query,
|
||||
$aliases
|
||||
);
|
||||
} finally {
|
||||
$this->export->unlockTables();
|
||||
}
|
||||
} else {
|
||||
$this->export->exportTable(
|
||||
$db,
|
||||
$table,
|
||||
$whatStrucOrData,
|
||||
$export_plugin,
|
||||
$crlf,
|
||||
$err_url,
|
||||
$export_type,
|
||||
$do_relation,
|
||||
$do_comments,
|
||||
$do_mime,
|
||||
$do_dates,
|
||||
$allrows,
|
||||
$limit_to,
|
||||
$limit_from,
|
||||
$sql_query,
|
||||
$aliases
|
||||
);
|
||||
}
|
||||
}
|
||||
if (! $export_plugin->exportFooter()) {
|
||||
throw new ExportException('Failure during footer export.');
|
||||
}
|
||||
} catch (ExportException $e) {
|
||||
null; // Avoid phpcs error...
|
||||
}
|
||||
|
||||
if ($save_on_server && ! empty($message)) {
|
||||
$this->export->showPage($export_type);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the dump as a file...
|
||||
*/
|
||||
if (empty($asfile)) {
|
||||
echo $this->export->getHtmlForDisplayedExportFooter($back_button, $refreshButton);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert the charset if required.
|
||||
if ($output_charset_conversion) {
|
||||
$dump_buffer = Encoding::convertString(
|
||||
'utf-8',
|
||||
$GLOBALS['charset'],
|
||||
$dump_buffer
|
||||
);
|
||||
}
|
||||
|
||||
// Compression needed?
|
||||
if ($compression) {
|
||||
if (! empty($separate_files)) {
|
||||
$dump_buffer = $this->export->compress(
|
||||
$dump_buffer_objects,
|
||||
$compression,
|
||||
$filename
|
||||
);
|
||||
} else {
|
||||
$dump_buffer = $this->export->compress($dump_buffer, $compression, $filename);
|
||||
}
|
||||
}
|
||||
|
||||
/* If we saved on server, we have to close file now */
|
||||
if ($save_on_server) {
|
||||
$message = $this->export->closeFile(
|
||||
$file_handle,
|
||||
$dump_buffer,
|
||||
$save_filename
|
||||
);
|
||||
$this->export->showPage($export_type);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
echo $dump_buffer;
|
||||
}
|
||||
|
||||
public function checkTimeOut(): void
|
||||
{
|
||||
$this->response->setAjax(true);
|
||||
|
||||
if (isset($_SESSION['pma_export_error'])) {
|
||||
unset($_SESSION['pma_export_error']);
|
||||
$this->response->addJSON('message', 'timeout');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->response->addJSON('message', 'success');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,160 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers;
|
||||
|
||||
use PhpMyAdmin\Export\Template as ExportTemplate;
|
||||
use PhpMyAdmin\Export\TemplateModel;
|
||||
use PhpMyAdmin\Relation;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use function is_array;
|
||||
use function is_string;
|
||||
|
||||
final class ExportTemplateController extends AbstractController
|
||||
{
|
||||
/** @var TemplateModel */
|
||||
private $model;
|
||||
|
||||
/** @var Relation */
|
||||
private $relation;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
*/
|
||||
public function __construct(
|
||||
$response,
|
||||
Template $template,
|
||||
TemplateModel $model,
|
||||
Relation $relation
|
||||
) {
|
||||
parent::__construct($response, $template);
|
||||
$this->model = $model;
|
||||
$this->relation = $relation;
|
||||
}
|
||||
|
||||
public function create(): void
|
||||
{
|
||||
global $cfg;
|
||||
|
||||
$cfgRelation = $this->relation->getRelationsParam();
|
||||
|
||||
if (! $cfgRelation['exporttemplateswork']) {
|
||||
return;
|
||||
}
|
||||
|
||||
$template = ExportTemplate::fromArray([
|
||||
'username' => $cfg['Server']['user'],
|
||||
'exportType' => $_POST['exportType'] ?? '',
|
||||
'name' => $_POST['templateName'] ?? '',
|
||||
'data' => $_POST['templateData'] ?? '',
|
||||
]);
|
||||
$result = $this->model->create($cfgRelation['db'], $cfgRelation['export_templates'], $template);
|
||||
|
||||
if (is_string($result)) {
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON('message', $result);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$templates = $this->model->getAll(
|
||||
$cfgRelation['db'],
|
||||
$cfgRelation['export_templates'],
|
||||
$template->getUsername(),
|
||||
$template->getExportType()
|
||||
);
|
||||
|
||||
$this->response->setRequestStatus(true);
|
||||
$this->response->addJSON(
|
||||
'data',
|
||||
$this->template->render('export/template_options', [
|
||||
'templates' => is_array($templates) ? $templates : [],
|
||||
'selected_template' => $_POST['template_id'] ?? null,
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
public function delete(): void
|
||||
{
|
||||
global $cfg;
|
||||
|
||||
$cfgRelation = $this->relation->getRelationsParam();
|
||||
|
||||
if (! $cfgRelation['exporttemplateswork']) {
|
||||
return;
|
||||
}
|
||||
|
||||
$result = $this->model->delete(
|
||||
$cfgRelation['db'],
|
||||
$cfgRelation['export_templates'],
|
||||
$cfg['Server']['user'],
|
||||
(int) $_POST['templateId']
|
||||
);
|
||||
|
||||
if (is_string($result)) {
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON('message', $result);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->response->setRequestStatus(true);
|
||||
}
|
||||
|
||||
public function load(): void
|
||||
{
|
||||
global $cfg;
|
||||
|
||||
$cfgRelation = $this->relation->getRelationsParam();
|
||||
|
||||
if (! $cfgRelation['exporttemplateswork']) {
|
||||
return;
|
||||
}
|
||||
|
||||
$template = $this->model->load(
|
||||
$cfgRelation['db'],
|
||||
$cfgRelation['export_templates'],
|
||||
$cfg['Server']['user'],
|
||||
(int) $_POST['templateId']
|
||||
);
|
||||
|
||||
if (! $template instanceof ExportTemplate) {
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON('message', $template);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->response->setRequestStatus(true);
|
||||
$this->response->addJSON('data', $template->getData());
|
||||
}
|
||||
|
||||
public function update(): void
|
||||
{
|
||||
global $cfg;
|
||||
|
||||
$cfgRelation = $this->relation->getRelationsParam();
|
||||
|
||||
if (! $cfgRelation['exporttemplateswork']) {
|
||||
return;
|
||||
}
|
||||
|
||||
$template = ExportTemplate::fromArray([
|
||||
'id' => (int) $_POST['templateId'],
|
||||
'username' => $cfg['Server']['user'],
|
||||
'data' => $_POST['templateData'],
|
||||
]);
|
||||
$result = $this->model->update($cfgRelation['db'], $cfgRelation['export_templates'], $template);
|
||||
|
||||
if (is_string($result)) {
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON('message', $result);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->response->setRequestStatus(true);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,151 @@
|
|||
<?php
|
||||
/**
|
||||
* Editor for Geometry data types.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers;
|
||||
|
||||
use PhpMyAdmin\Core;
|
||||
use PhpMyAdmin\Gis\GisFactory;
|
||||
use PhpMyAdmin\Gis\GisVisualization;
|
||||
use function array_merge;
|
||||
use function in_array;
|
||||
use function intval;
|
||||
use function mb_strpos;
|
||||
use function mb_strtoupper;
|
||||
use function mb_substr;
|
||||
use function substr;
|
||||
use function trim;
|
||||
|
||||
/**
|
||||
* Editor for Geometry data types.
|
||||
*/
|
||||
class GisDataEditorController extends AbstractController
|
||||
{
|
||||
public function index(): void
|
||||
{
|
||||
global $gis_data, $gis_types, $start, $geom_type, $gis_obj, $srid, $wkt, $wkt_with_zero, $PMA_Theme;
|
||||
global $result, $visualizationSettings, $data, $visualization, $open_layers, $geom_count, $dbi;
|
||||
|
||||
if (! isset($_POST['field'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get data if any posted
|
||||
$gis_data = [];
|
||||
if (Core::isValid($_POST['gis_data'], 'array')) {
|
||||
$gis_data = $_POST['gis_data'];
|
||||
}
|
||||
|
||||
$gis_types = [
|
||||
'POINT',
|
||||
'MULTIPOINT',
|
||||
'LINESTRING',
|
||||
'MULTILINESTRING',
|
||||
'POLYGON',
|
||||
'MULTIPOLYGON',
|
||||
'GEOMETRYCOLLECTION',
|
||||
];
|
||||
|
||||
// Extract type from the initial call and make sure that it's a valid one.
|
||||
// Extract from field's values if available, if not use the column type passed.
|
||||
if (! isset($gis_data['gis_type'])) {
|
||||
if (isset($_POST['type']) && $_POST['type'] != '') {
|
||||
$gis_data['gis_type'] = mb_strtoupper($_POST['type']);
|
||||
}
|
||||
if (isset($_POST['value']) && trim($_POST['value']) != '') {
|
||||
$start = substr($_POST['value'], 0, 1) == "'" ? 1 : 0;
|
||||
$gis_data['gis_type'] = mb_substr(
|
||||
$_POST['value'],
|
||||
$start,
|
||||
mb_strpos($_POST['value'], '(') - $start
|
||||
);
|
||||
}
|
||||
if (! isset($gis_data['gis_type'])
|
||||
|| (! in_array($gis_data['gis_type'], $gis_types))
|
||||
) {
|
||||
$gis_data['gis_type'] = $gis_types[0];
|
||||
}
|
||||
}
|
||||
$geom_type = $gis_data['gis_type'];
|
||||
|
||||
// Generate parameters from value passed.
|
||||
$gis_obj = GisFactory::factory($geom_type);
|
||||
if ($gis_obj === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($_POST['value'])) {
|
||||
$gis_data = array_merge(
|
||||
$gis_data,
|
||||
$gis_obj->generateParams($_POST['value'])
|
||||
);
|
||||
}
|
||||
|
||||
// Generate Well Known Text
|
||||
$srid = isset($gis_data['srid']) && $gis_data['srid'] != '' ? $gis_data['srid'] : 0;
|
||||
$wkt = $gis_obj->generateWkt($gis_data, 0);
|
||||
$wkt_with_zero = $gis_obj->generateWkt($gis_data, 0, '0');
|
||||
$result = "'" . $wkt . "'," . $srid;
|
||||
|
||||
// Generate SVG based visualization
|
||||
$visualizationSettings = [
|
||||
'width' => 450,
|
||||
'height' => 300,
|
||||
'spatialColumn' => 'wkt',
|
||||
'mysqlVersion' => $dbi->getVersion(),
|
||||
'isMariaDB' => $dbi->isMariaDB(),
|
||||
];
|
||||
$data = [
|
||||
[
|
||||
'wkt' => $wkt_with_zero,
|
||||
'srid' => $srid,
|
||||
],
|
||||
];
|
||||
$visualization = GisVisualization::getByData($data, $visualizationSettings)
|
||||
->toImage('svg');
|
||||
|
||||
$open_layers = GisVisualization::getByData($data, $visualizationSettings)
|
||||
->asOl();
|
||||
|
||||
// If the call is to update the WKT and visualization make an AJAX response
|
||||
if (isset($_POST['generate']) && $_POST['generate'] == true) {
|
||||
$this->response->addJSON([
|
||||
'result' => $result,
|
||||
'visualization' => $visualization,
|
||||
'openLayers' => $open_layers,
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$geom_count = 1;
|
||||
if ($geom_type === 'GEOMETRYCOLLECTION') {
|
||||
$geom_count = isset($gis_data[$geom_type]['geom_count'])
|
||||
? intval($gis_data[$geom_type]['geom_count']) : 1;
|
||||
if (isset($gis_data[$geom_type]['add_geom'])) {
|
||||
$geom_count++;
|
||||
}
|
||||
}
|
||||
|
||||
$templateOutput = $this->template->render('gis_data_editor_form', [
|
||||
'width' => $visualizationSettings['width'],
|
||||
'height' => $visualizationSettings['height'],
|
||||
'theme_image_path' => $PMA_Theme->getImgPath(),
|
||||
'field' => $_POST['field'],
|
||||
'input_name' => $_POST['input_name'],
|
||||
'srid' => $srid,
|
||||
'visualization' => $visualization,
|
||||
'open_layers' => $open_layers,
|
||||
'gis_types' => $gis_types,
|
||||
'geom_type' => $geom_type,
|
||||
'geom_count' => $geom_count,
|
||||
'gis_data' => $gis_data,
|
||||
'result' => $result,
|
||||
]);
|
||||
|
||||
$this->response->addJSON(['gis_editor' => $templateOutput]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,500 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers;
|
||||
|
||||
use PhpMyAdmin\Charsets;
|
||||
use PhpMyAdmin\Charsets\Charset;
|
||||
use PhpMyAdmin\Charsets\Collation;
|
||||
use PhpMyAdmin\CheckUserPrivileges;
|
||||
use PhpMyAdmin\Config;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Git;
|
||||
use PhpMyAdmin\Html\Generator;
|
||||
use PhpMyAdmin\LanguageManager;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\RecentFavoriteTable;
|
||||
use PhpMyAdmin\Relation;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Server\Select;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\ThemeManager;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\UserPreferences;
|
||||
use PhpMyAdmin\Util;
|
||||
use const E_USER_NOTICE;
|
||||
use const E_USER_WARNING;
|
||||
use const PHP_VERSION;
|
||||
use function count;
|
||||
use function extension_loaded;
|
||||
use function file_exists;
|
||||
use function ini_get;
|
||||
use function preg_match;
|
||||
use function sprintf;
|
||||
use function strlen;
|
||||
use function strtotime;
|
||||
use function trigger_error;
|
||||
|
||||
class HomeController extends AbstractController
|
||||
{
|
||||
/** @var Config */
|
||||
private $config;
|
||||
|
||||
/** @var ThemeManager */
|
||||
private $themeManager;
|
||||
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param Config $config
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, $config, ThemeManager $themeManager, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template);
|
||||
$this->config = $config;
|
||||
$this->themeManager = $themeManager;
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $cfg, $server, $collation_connection, $message, $show_query, $db, $table, $err_url;
|
||||
|
||||
if ($this->response->isAjax() && ! empty($_REQUEST['access_time'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
// This is for $cfg['ShowDatabasesNavigationAsTree'] = false;
|
||||
// See: https://github.com/phpmyadmin/phpmyadmin/issues/16520
|
||||
// The DB is defined here and sent to the JS front-end to refresh the DB tree
|
||||
$db = $_POST['db'] ?? '';
|
||||
$table = '';
|
||||
$show_query = '1';
|
||||
$err_url = Url::getFromRoute('/');
|
||||
|
||||
if ($server > 0 && $this->dbi->isSuperUser()) {
|
||||
$this->dbi->selectDb('mysql');
|
||||
}
|
||||
|
||||
$languageManager = LanguageManager::getInstance();
|
||||
|
||||
if (! empty($message)) {
|
||||
$displayMessage = Generator::getMessage($message);
|
||||
unset($message);
|
||||
}
|
||||
if (isset($_SESSION['partial_logout'])) {
|
||||
$partialLogout = Message::success(__(
|
||||
'You were logged out from one server, to logout completely '
|
||||
. 'from phpMyAdmin, you need to logout from all servers.'
|
||||
))->getDisplay();
|
||||
unset($_SESSION['partial_logout']);
|
||||
}
|
||||
|
||||
$syncFavoriteTables = RecentFavoriteTable::getInstance('favorite')
|
||||
->getHtmlSyncFavoriteTables();
|
||||
|
||||
$hasServer = $server > 0 || count($cfg['Servers']) > 1;
|
||||
if ($hasServer) {
|
||||
$hasServerSelection = $cfg['ServerDefault'] == 0
|
||||
|| (! $cfg['NavigationDisplayServers']
|
||||
&& (count($cfg['Servers']) > 1
|
||||
|| ($server == 0 && count($cfg['Servers']) === 1)));
|
||||
if ($hasServerSelection) {
|
||||
$serverSelection = Select::render(true, true);
|
||||
}
|
||||
|
||||
if ($server > 0) {
|
||||
$checkUserPrivileges = new CheckUserPrivileges($this->dbi);
|
||||
$checkUserPrivileges->getPrivileges();
|
||||
|
||||
$charsets = Charsets::getCharsets($this->dbi, $cfg['Server']['DisableIS']);
|
||||
$collations = Charsets::getCollations($this->dbi, $cfg['Server']['DisableIS']);
|
||||
$charsetsList = [];
|
||||
/** @var Charset $charset */
|
||||
foreach ($charsets as $charset) {
|
||||
$collationsList = [];
|
||||
/** @var Collation $collation */
|
||||
foreach ($collations[$charset->getName()] as $collation) {
|
||||
$collationsList[] = [
|
||||
'name' => $collation->getName(),
|
||||
'description' => $collation->getDescription(),
|
||||
'is_selected' => $collation_connection === $collation->getName(),
|
||||
];
|
||||
}
|
||||
$charsetsList[] = [
|
||||
'name' => $charset->getName(),
|
||||
'description' => $charset->getDescription(),
|
||||
'collations' => $collationsList,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$languageSelector = '';
|
||||
if (empty($cfg['Lang']) && $languageManager->hasChoice()) {
|
||||
$languageSelector = $languageManager->getSelectorDisplay($this->template);
|
||||
}
|
||||
|
||||
$themeSelection = '';
|
||||
if ($cfg['ThemeManager']) {
|
||||
$themeSelection = $this->themeManager->getHtmlSelectBox();
|
||||
}
|
||||
|
||||
$databaseServer = [];
|
||||
if ($server > 0 && $cfg['ShowServerInfo']) {
|
||||
$hostInfo = '';
|
||||
if (! empty($cfg['Server']['verbose'])) {
|
||||
$hostInfo .= $cfg['Server']['verbose'];
|
||||
if ($cfg['ShowServerInfo']) {
|
||||
$hostInfo .= ' (';
|
||||
}
|
||||
}
|
||||
if ($cfg['ShowServerInfo'] || empty($cfg['Server']['verbose'])) {
|
||||
$hostInfo .= $this->dbi->getHostInfo();
|
||||
}
|
||||
if (! empty($cfg['Server']['verbose']) && $cfg['ShowServerInfo']) {
|
||||
$hostInfo .= ')';
|
||||
}
|
||||
|
||||
$serverCharset = Charsets::getServerCharset($this->dbi, $cfg['Server']['DisableIS']);
|
||||
$databaseServer = [
|
||||
'host' => $hostInfo,
|
||||
'type' => Util::getServerType(),
|
||||
'connection' => Generator::getServerSSL(),
|
||||
'version' => $this->dbi->getVersionString() . ' - ' . $this->dbi->getVersionComment(),
|
||||
'protocol' => $this->dbi->getProtoInfo(),
|
||||
'user' => $this->dbi->fetchValue('SELECT USER();'),
|
||||
'charset' => $serverCharset->getDescription() . ' (' . $serverCharset->getName() . ')',
|
||||
];
|
||||
}
|
||||
|
||||
$webServer = [];
|
||||
if ($cfg['ShowServerInfo']) {
|
||||
$webServer['software'] = $_SERVER['SERVER_SOFTWARE'] ?? null;
|
||||
|
||||
if ($server > 0) {
|
||||
$clientVersion = $this->dbi->getClientInfo();
|
||||
if (preg_match('#\d+\.\d+\.\d+#', $clientVersion)) {
|
||||
$clientVersion = 'libmysql - ' . $clientVersion;
|
||||
}
|
||||
|
||||
$webServer['database'] = $clientVersion;
|
||||
$webServer['php_extensions'] = Util::listPHPExtensions();
|
||||
$webServer['php_version'] = PHP_VERSION;
|
||||
}
|
||||
}
|
||||
|
||||
$relation = new Relation($this->dbi);
|
||||
if ($server > 0) {
|
||||
$cfgRelation = $relation->getRelationsParam();
|
||||
if (! $cfgRelation['allworks']
|
||||
&& $cfg['PmaNoRelation_DisableWarning'] == false
|
||||
) {
|
||||
$messageText = __(
|
||||
'The phpMyAdmin configuration storage is not completely '
|
||||
. 'configured, some extended features have been deactivated. '
|
||||
. '%sFind out why%s. '
|
||||
);
|
||||
if ($cfg['ZeroConf'] == true) {
|
||||
$messageText .= '<br>' .
|
||||
__(
|
||||
'Or alternately go to \'Operations\' tab of any database '
|
||||
. 'to set it up there.'
|
||||
);
|
||||
}
|
||||
$messageInstance = Message::notice($messageText);
|
||||
$messageInstance->addParamHtml(
|
||||
'<a href="' . Url::getFromRoute('/check-relations')
|
||||
. '" data-post="' . Url::getCommon() . '">'
|
||||
);
|
||||
$messageInstance->addParamHtml('</a>');
|
||||
/* Show error if user has configured something, notice elsewhere */
|
||||
if (! empty($cfg['Servers'][$server]['pmadb'])) {
|
||||
$messageInstance->isError(true);
|
||||
}
|
||||
$configStorageMessage = $messageInstance->getDisplay();
|
||||
}
|
||||
}
|
||||
|
||||
$this->checkRequirements();
|
||||
|
||||
$git = new Git($this->config);
|
||||
|
||||
$this->render('home/index', [
|
||||
'message' => $displayMessage ?? '',
|
||||
'partial_logout' => $partialLogout ?? '',
|
||||
'is_git_revision' => $git->isGitRevision(),
|
||||
'server' => $server,
|
||||
'sync_favorite_tables' => $syncFavoriteTables,
|
||||
'has_server' => $hasServer,
|
||||
'is_demo' => $cfg['DBG']['demo'],
|
||||
'has_server_selection' => $hasServerSelection ?? false,
|
||||
'server_selection' => $serverSelection ?? '',
|
||||
'has_change_password_link' => $cfg['Server']['auth_type'] !== 'config' && $cfg['ShowChgPassword'],
|
||||
'charsets' => $charsetsList ?? [],
|
||||
'language_selector' => $languageSelector,
|
||||
'theme_selection' => $themeSelection,
|
||||
'database_server' => $databaseServer,
|
||||
'web_server' => $webServer,
|
||||
'show_php_info' => $cfg['ShowPhpInfo'],
|
||||
'is_version_checked' => $cfg['VersionCheck'],
|
||||
'phpmyadmin_version' => PMA_VERSION,
|
||||
'config_storage_message' => $configStorageMessage ?? '',
|
||||
]);
|
||||
}
|
||||
|
||||
public function setTheme(): void
|
||||
{
|
||||
$this->themeManager->setActiveTheme($_POST['set_theme']);
|
||||
$this->themeManager->setThemeCookie();
|
||||
|
||||
$userPreferences = new UserPreferences();
|
||||
$preferences = $userPreferences->load();
|
||||
$preferences['config_data']['ThemeDefault'] = $_POST['set_theme'];
|
||||
$userPreferences->save($preferences['config_data']);
|
||||
|
||||
$this->response->header('Location: index.php?route=/' . Url::getCommonRaw([], '&'));
|
||||
}
|
||||
|
||||
public function setCollationConnection(): void
|
||||
{
|
||||
$this->config->setUserValue(
|
||||
null,
|
||||
'DefaultConnectionCollation',
|
||||
$_POST['collation_connection'],
|
||||
'utf8mb4_unicode_ci'
|
||||
);
|
||||
|
||||
$this->response->header('Location: index.php?route=/' . Url::getCommonRaw([], '&'));
|
||||
}
|
||||
|
||||
public function reloadRecentTablesList(): void
|
||||
{
|
||||
if (! $this->response->isAjax()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->response->addJSON([
|
||||
'list' => RecentFavoriteTable::getInstance('recent')->getHtmlList(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function gitRevision(): void
|
||||
{
|
||||
if (! $this->response->isAjax()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$git = new Git($this->config);
|
||||
|
||||
if (! $git->isGitRevision()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$commit = $git->checkGitRevision();
|
||||
|
||||
if (! $this->config->get('PMA_VERSION_GIT') || $commit === null) {
|
||||
$this->response->setRequestStatus(false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$commit['author']['date'] = Util::localisedDate(strtotime($commit['author']['date']));
|
||||
$commit['committer']['date'] = Util::localisedDate(strtotime($commit['committer']['date']));
|
||||
|
||||
$this->render('home/git_info', $commit);
|
||||
}
|
||||
|
||||
private function checkRequirements(): void
|
||||
{
|
||||
global $cfg, $server, $lang;
|
||||
|
||||
/**
|
||||
* mbstring is used for handling multibytes inside parser, so it is good
|
||||
* to tell user something might be broken without it, see bug #1063149.
|
||||
*/
|
||||
if (! extension_loaded('mbstring')) {
|
||||
trigger_error(
|
||||
__(
|
||||
'The mbstring PHP extension was not found and you seem to be using'
|
||||
. ' a multibyte charset. Without the mbstring extension phpMyAdmin'
|
||||
. ' is unable to split strings correctly and it may result in'
|
||||
. ' unexpected results.'
|
||||
),
|
||||
E_USER_WARNING
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Missing functionality
|
||||
*/
|
||||
if (! extension_loaded('curl') && ! ini_get('allow_url_fopen')) {
|
||||
trigger_error(
|
||||
__(
|
||||
'The curl extension was not found and allow_url_fopen is '
|
||||
. 'disabled. Due to this some features such as error reporting '
|
||||
. 'or version check are disabled.'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ($cfg['LoginCookieValidityDisableWarning'] == false) {
|
||||
/**
|
||||
* Check whether session.gc_maxlifetime limits session validity.
|
||||
*/
|
||||
$gc_time = (int) ini_get('session.gc_maxlifetime');
|
||||
if ($gc_time < $cfg['LoginCookieValidity']) {
|
||||
trigger_error(
|
||||
__(
|
||||
'Your PHP parameter [a@https://www.php.net/manual/en/session.' .
|
||||
'configuration.php#ini.session.gc-maxlifetime@_blank]session.' .
|
||||
'gc_maxlifetime[/a] is lower than cookie validity configured ' .
|
||||
'in phpMyAdmin, because of this, your login might expire sooner ' .
|
||||
'than configured in phpMyAdmin.'
|
||||
),
|
||||
E_USER_WARNING
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether LoginCookieValidity is limited by LoginCookieStore.
|
||||
*/
|
||||
if ($cfg['LoginCookieStore'] != 0
|
||||
&& $cfg['LoginCookieStore'] < $cfg['LoginCookieValidity']
|
||||
) {
|
||||
trigger_error(
|
||||
__(
|
||||
'Login cookie store is lower than cookie validity configured in ' .
|
||||
'phpMyAdmin, because of this, your login will expire sooner than ' .
|
||||
'configured in phpMyAdmin.'
|
||||
),
|
||||
E_USER_WARNING
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Warning if using the default MySQL controluser account
|
||||
*/
|
||||
if (isset($cfg['Server']['controluser'], $cfg['Server']['controlpass'])
|
||||
&& $server != 0
|
||||
&& $cfg['Server']['controluser'] === 'pma'
|
||||
&& $cfg['Server']['controlpass'] === 'pmapass'
|
||||
) {
|
||||
trigger_error(
|
||||
__(
|
||||
'Your server is running with default values for the ' .
|
||||
'controluser and password (controlpass) and is open to ' .
|
||||
'intrusion; you really should fix this security weakness' .
|
||||
' by changing the password for controluser \'pma\'.'
|
||||
),
|
||||
E_USER_WARNING
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user does not have defined blowfish secret and it is being used.
|
||||
*/
|
||||
if (! empty($_SESSION['encryption_key'])) {
|
||||
if (empty($cfg['blowfish_secret'])) {
|
||||
trigger_error(
|
||||
__(
|
||||
'The configuration file now needs a secret passphrase (blowfish_secret).'
|
||||
),
|
||||
E_USER_WARNING
|
||||
);
|
||||
} elseif (strlen($cfg['blowfish_secret']) < 32) {
|
||||
trigger_error(
|
||||
__(
|
||||
'The secret passphrase in configuration (blowfish_secret) is too short.'
|
||||
),
|
||||
E_USER_WARNING
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for existence of config directory which should not exist in
|
||||
* production environment.
|
||||
*/
|
||||
if (@file_exists(ROOT_PATH . 'config')) {
|
||||
trigger_error(
|
||||
__(
|
||||
'Directory [code]config[/code], which is used by the setup script, ' .
|
||||
'still exists in your phpMyAdmin directory. It is strongly ' .
|
||||
'recommended to remove it once phpMyAdmin has been configured. ' .
|
||||
'Otherwise the security of your server may be compromised by ' .
|
||||
'unauthorized people downloading your configuration.'
|
||||
),
|
||||
E_USER_WARNING
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Warning about Suhosin only if its simulation mode is not enabled
|
||||
*/
|
||||
if ($cfg['SuhosinDisableWarning'] == false
|
||||
&& ini_get('suhosin.request.max_value_length')
|
||||
&& ini_get('suhosin.simulation') == '0'
|
||||
) {
|
||||
trigger_error(
|
||||
sprintf(
|
||||
__(
|
||||
'Server running with Suhosin. Please refer ' .
|
||||
'to %sdocumentation%s for possible issues.'
|
||||
),
|
||||
'[doc@faq1-38]',
|
||||
'[/doc]'
|
||||
),
|
||||
E_USER_WARNING
|
||||
);
|
||||
}
|
||||
|
||||
/* Missing template cache */
|
||||
if ($this->config->getTempDir('twig') === null) {
|
||||
trigger_error(
|
||||
sprintf(
|
||||
__(
|
||||
'The $cfg[\'TempDir\'] (%s) is not accessible. ' .
|
||||
'phpMyAdmin is not able to cache templates and will ' .
|
||||
'be slow because of this.'
|
||||
),
|
||||
$this->config->get('TempDir')
|
||||
),
|
||||
E_USER_WARNING
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Warning about incomplete translations.
|
||||
*
|
||||
* The data file is created while creating release by ./scripts/remove-incomplete-mo
|
||||
*/
|
||||
if (! @file_exists(ROOT_PATH . 'libraries/language_stats.inc.php')) {
|
||||
return;
|
||||
}
|
||||
|
||||
include ROOT_PATH . 'libraries/language_stats.inc.php';
|
||||
/*
|
||||
* This message is intentionally not translated, because we're
|
||||
* handling incomplete translations here and focus on english
|
||||
* speaking users.
|
||||
*/
|
||||
if (! isset($GLOBALS['language_stats'][$lang])
|
||||
|| $GLOBALS['language_stats'][$lang] >= $cfg['TranslationWarningThreshold']
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
trigger_error(
|
||||
'You are using an incomplete translation, please help to make it '
|
||||
. 'better by [a@https://www.phpmyadmin.net/translate/'
|
||||
. '@_blank]contributing[/a].',
|
||||
E_USER_NOTICE
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,884 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers;
|
||||
|
||||
use PhpMyAdmin\Bookmark;
|
||||
use PhpMyAdmin\Console;
|
||||
use PhpMyAdmin\Core;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Encoding;
|
||||
use PhpMyAdmin\File;
|
||||
use PhpMyAdmin\Html\Generator;
|
||||
use PhpMyAdmin\Import;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\ParseAnalyze;
|
||||
use PhpMyAdmin\Plugins;
|
||||
use PhpMyAdmin\Plugins\ImportPlugin;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Sql;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
use Throwable;
|
||||
use function define;
|
||||
use function htmlspecialchars;
|
||||
use function in_array;
|
||||
use function ini_get;
|
||||
use function ini_set;
|
||||
use function intval;
|
||||
use function is_array;
|
||||
use function is_link;
|
||||
use function is_uploaded_file;
|
||||
use function mb_strlen;
|
||||
use function mb_strtolower;
|
||||
use function preg_match;
|
||||
use function preg_quote;
|
||||
use function preg_replace;
|
||||
use function sprintf;
|
||||
use function strlen;
|
||||
use function substr;
|
||||
use function time;
|
||||
use function trim;
|
||||
|
||||
final class ImportController extends AbstractController
|
||||
{
|
||||
/** @var Import */
|
||||
private $import;
|
||||
|
||||
/** @var Sql */
|
||||
private $sql;
|
||||
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, Import $import, Sql $sql, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template);
|
||||
$this->import = $import;
|
||||
$this->sql = $sql;
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $cfg, $collation_connection, $db, $import_type, $table, $goto, $display_query, $PMA_Theme;
|
||||
global $format, $local_import_file, $ajax_reload, $import_text, $sql_query, $message, $err_url, $url_params;
|
||||
global $memory_limit, $read_limit, $finished, $offset, $charset_conversion, $charset_of_file;
|
||||
global $timestamp, $maximum_time, $timeout_passed, $import_file, $go_sql, $sql_file, $error, $max_sql_len, $msg;
|
||||
global $sql_query_disabled, $executed_queries, $run_query, $reset_charset, $bookmark_created;
|
||||
global $result, $import_file_name, $sql_data, $import_notice, $read_multiply, $my_die, $active_page;
|
||||
global $show_as_php, $reload, $charset_connection, $is_js_confirmed, $MAX_FILE_SIZE, $message_to_show;
|
||||
global $noplugin, $skip_queries;
|
||||
|
||||
$charset_of_file = $_POST['charset_of_file'] ?? null;
|
||||
$format = $_POST['format'] ?? '';
|
||||
$import_type = $_POST['import_type'] ?? null;
|
||||
$is_js_confirmed = $_POST['is_js_confirmed'] ?? null;
|
||||
$MAX_FILE_SIZE = $_POST['MAX_FILE_SIZE'] ?? null;
|
||||
$message_to_show = $_POST['message_to_show'] ?? null;
|
||||
$noplugin = $_POST['noplugin'] ?? null;
|
||||
$skip_queries = $_POST['skip_queries'] ?? null;
|
||||
$local_import_file = $_POST['local_import_file'] ?? null;
|
||||
$show_as_php = $_POST['show_as_php'] ?? null;
|
||||
|
||||
/* Enable LOAD DATA LOCAL INFILE for LDI plugin */
|
||||
if ($format === 'ldi') {
|
||||
define('PMA_ENABLE_LDI', 1);
|
||||
}
|
||||
|
||||
// If there is a request to 'Simulate DML'.
|
||||
if (isset($_POST['simulate_dml'])) {
|
||||
$this->import->handleSimulateDmlRequest();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// If it's a refresh console bookmarks request
|
||||
if (isset($_GET['console_bookmark_refresh'])) {
|
||||
$this->response->addJSON(
|
||||
'console_message_bookmark',
|
||||
Console::getBookmarkContent()
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
// If it's a console bookmark add request
|
||||
if (isset($_POST['console_bookmark_add'])) {
|
||||
if (! isset($_POST['label'], $_POST['db'], $_POST['bookmark_query'], $_POST['shared'])) {
|
||||
$this->response->addJSON('message', __('Incomplete params'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$cfgBookmark = Bookmark::getParams($cfg['Server']['user']);
|
||||
|
||||
if (! is_array($cfgBookmark)) {
|
||||
$cfgBookmark = [];
|
||||
}
|
||||
|
||||
$bookmarkFields = [
|
||||
'bkm_database' => $_POST['db'],
|
||||
'bkm_user' => $cfgBookmark['user'],
|
||||
'bkm_sql_query' => $_POST['bookmark_query'],
|
||||
'bkm_label' => $_POST['label'],
|
||||
];
|
||||
$isShared = ($_POST['shared'] === 'true');
|
||||
$bookmark = Bookmark::createBookmark(
|
||||
$this->dbi,
|
||||
$cfg['Server']['user'],
|
||||
$bookmarkFields,
|
||||
$isShared
|
||||
);
|
||||
if ($bookmark !== false && $bookmark->save()) {
|
||||
$this->response->addJSON('message', __('Succeeded'));
|
||||
$this->response->addJSON('data', $bookmarkFields);
|
||||
$this->response->addJSON('isShared', $isShared);
|
||||
} else {
|
||||
$this->response->addJSON('message', __('Failed'));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// reset import messages for ajax request
|
||||
$_SESSION['Import_message']['message'] = null;
|
||||
$_SESSION['Import_message']['go_back_url'] = null;
|
||||
// default values
|
||||
$reload = false;
|
||||
|
||||
// Use to identify current cycle is executing
|
||||
// a multiquery statement or stored routine
|
||||
if (! isset($_SESSION['is_multi_query'])) {
|
||||
$_SESSION['is_multi_query'] = false;
|
||||
}
|
||||
|
||||
$ajax_reload = [];
|
||||
$import_text = '';
|
||||
// Are we just executing plain query or sql file?
|
||||
// (eg. non import, but query box/window run)
|
||||
if (! empty($sql_query)) {
|
||||
// apply values for parameters
|
||||
if (! empty($_POST['parameterized'])
|
||||
&& ! empty($_POST['parameters'])
|
||||
&& is_array($_POST['parameters'])
|
||||
) {
|
||||
$parameters = $_POST['parameters'];
|
||||
foreach ($parameters as $parameter => $replacement) {
|
||||
$quoted = preg_quote($parameter, '/');
|
||||
// making sure that :param does not apply values to :param1
|
||||
$sql_query = preg_replace(
|
||||
'/' . $quoted . '([^a-zA-Z0-9_])/',
|
||||
$this->dbi->escapeString($replacement) . '${1}',
|
||||
$sql_query
|
||||
);
|
||||
// for parameters the appear at the end of the string
|
||||
$sql_query = preg_replace(
|
||||
'/' . $quoted . '$/',
|
||||
$this->dbi->escapeString($replacement),
|
||||
$sql_query
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// run SQL query
|
||||
$import_text = $sql_query;
|
||||
$import_type = 'query';
|
||||
$format = 'sql';
|
||||
$_SESSION['sql_from_query_box'] = true;
|
||||
|
||||
// If there is a request to ROLLBACK when finished.
|
||||
if (isset($_POST['rollback_query'])) {
|
||||
$this->import->handleRollbackRequest($import_text);
|
||||
}
|
||||
|
||||
// refresh navigation and main panels
|
||||
if (preg_match('/^(DROP)\s+(VIEW|TABLE|DATABASE|SCHEMA)\s+/i', $sql_query)) {
|
||||
$reload = true;
|
||||
$ajax_reload['reload'] = true;
|
||||
}
|
||||
|
||||
// refresh navigation panel only
|
||||
if (preg_match(
|
||||
'/^(CREATE|ALTER)\s+(VIEW|TABLE|DATABASE|SCHEMA)\s+/i',
|
||||
$sql_query
|
||||
)) {
|
||||
$ajax_reload['reload'] = true;
|
||||
}
|
||||
|
||||
// do a dynamic reload if table is RENAMED
|
||||
// (by sending the instruction to the AJAX response handler)
|
||||
if (preg_match(
|
||||
'/^RENAME\s+TABLE\s+(.*?)\s+TO\s+(.*?)($|;|\s)/i',
|
||||
$sql_query,
|
||||
$rename_table_names
|
||||
)) {
|
||||
$ajax_reload['reload'] = true;
|
||||
$ajax_reload['table_name'] = Util::unQuote(
|
||||
$rename_table_names[2]
|
||||
);
|
||||
}
|
||||
|
||||
$sql_query = '';
|
||||
} elseif (! empty($sql_file)) {
|
||||
// run uploaded SQL file
|
||||
$import_file = $sql_file;
|
||||
$import_type = 'queryfile';
|
||||
$format = 'sql';
|
||||
unset($sql_file);
|
||||
} elseif (! empty($_POST['id_bookmark'])) {
|
||||
// run bookmark
|
||||
$import_type = 'query';
|
||||
$format = 'sql';
|
||||
}
|
||||
|
||||
// If we didn't get any parameters, either user called this directly, or
|
||||
// upload limit has been reached, let's assume the second possibility.
|
||||
if ($_POST == [] && $_GET == []) {
|
||||
$message = Message::error(
|
||||
__(
|
||||
'You probably tried to upload a file that is too large. Please refer ' .
|
||||
'to %sdocumentation%s for a workaround for this limit.'
|
||||
)
|
||||
);
|
||||
$message->addParam('[doc@faq1-16]');
|
||||
$message->addParam('[/doc]');
|
||||
|
||||
// so we can obtain the message
|
||||
$_SESSION['Import_message']['message'] = $message->getDisplay();
|
||||
$_SESSION['Import_message']['go_back_url'] = $goto;
|
||||
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON('message', $message);
|
||||
|
||||
return; // the footer is displayed automatically
|
||||
}
|
||||
|
||||
// Add console message id to response output
|
||||
if (isset($_POST['console_message_id'])) {
|
||||
$this->response->addJSON('console_message_id', $_POST['console_message_id']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets globals from $_POST patterns, for import plugins
|
||||
* We only need to load the selected plugin
|
||||
*/
|
||||
|
||||
if (! in_array(
|
||||
$format,
|
||||
[
|
||||
'csv',
|
||||
'ldi',
|
||||
'mediawiki',
|
||||
'ods',
|
||||
'shp',
|
||||
'sql',
|
||||
'xml',
|
||||
]
|
||||
)
|
||||
) {
|
||||
// this should not happen for a normal user
|
||||
// but only during an attack
|
||||
Core::fatalError('Incorrect format parameter');
|
||||
}
|
||||
|
||||
$post_patterns = [
|
||||
'/^force_file_/',
|
||||
'/^' . $format . '_/',
|
||||
];
|
||||
|
||||
Core::setPostAsGlobal($post_patterns);
|
||||
|
||||
// Check needed parameters
|
||||
Util::checkParameters(['import_type', 'format']);
|
||||
|
||||
// We don't want anything special in format
|
||||
$format = Core::securePath($format);
|
||||
|
||||
if (strlen($table) > 0 && strlen($db) > 0) {
|
||||
$url_params = [
|
||||
'db' => $db,
|
||||
'table' => $table,
|
||||
];
|
||||
} elseif (strlen($db) > 0) {
|
||||
$url_params = ['db' => $db];
|
||||
} else {
|
||||
$url_params = [];
|
||||
}
|
||||
|
||||
// Create error and goto url
|
||||
if ($import_type === 'table') {
|
||||
$goto = Url::getFromRoute('/table/import');
|
||||
} elseif ($import_type === 'database') {
|
||||
$goto = Url::getFromRoute('/database/import');
|
||||
} elseif ($import_type === 'server') {
|
||||
$goto = Url::getFromRoute('/server/import');
|
||||
} elseif (empty($goto) || ! preg_match('@^index\.php$@i', $goto)) {
|
||||
if (strlen($table) > 0 && strlen($db) > 0) {
|
||||
$goto = Url::getFromRoute('/table/structure');
|
||||
} elseif (strlen($db) > 0) {
|
||||
$goto = Url::getFromRoute('/database/structure');
|
||||
} else {
|
||||
$goto = Url::getFromRoute('/server/sql');
|
||||
}
|
||||
}
|
||||
$err_url = $goto . Url::getCommon($url_params, '&');
|
||||
$_SESSION['Import_message']['go_back_url'] = $err_url;
|
||||
|
||||
if (strlen($db) > 0) {
|
||||
$this->dbi->selectDb($db);
|
||||
}
|
||||
|
||||
Util::setTimeLimit();
|
||||
if (! empty($cfg['MemoryLimit'])) {
|
||||
ini_set('memory_limit', $cfg['MemoryLimit']);
|
||||
}
|
||||
|
||||
$timestamp = time();
|
||||
if (isset($_POST['allow_interrupt'])) {
|
||||
$maximum_time = ini_get('max_execution_time');
|
||||
} else {
|
||||
$maximum_time = 0;
|
||||
}
|
||||
|
||||
// set default values
|
||||
$timeout_passed = false;
|
||||
$error = false;
|
||||
$read_multiply = 1;
|
||||
$finished = false;
|
||||
$offset = 0;
|
||||
$max_sql_len = 0;
|
||||
$sql_query = '';
|
||||
$sql_query_disabled = false;
|
||||
$go_sql = false;
|
||||
$executed_queries = 0;
|
||||
$run_query = true;
|
||||
$charset_conversion = false;
|
||||
$reset_charset = false;
|
||||
$bookmark_created = false;
|
||||
$msg = 'Sorry an unexpected error happened!';
|
||||
|
||||
/** @var mixed|bool $result */
|
||||
$result = false;
|
||||
|
||||
// Bookmark Support: get a query back from bookmark if required
|
||||
if (! empty($_POST['id_bookmark'])) {
|
||||
$id_bookmark = (int) $_POST['id_bookmark'];
|
||||
switch ($_POST['action_bookmark']) {
|
||||
case 0: // bookmarked query that have to be run
|
||||
$bookmark = Bookmark::get(
|
||||
$this->dbi,
|
||||
$cfg['Server']['user'],
|
||||
$db,
|
||||
$id_bookmark,
|
||||
'id',
|
||||
isset($_POST['action_bookmark_all'])
|
||||
);
|
||||
if (! $bookmark instanceof Bookmark) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (! empty($_POST['bookmark_variable'])) {
|
||||
$import_text = $bookmark->applyVariables(
|
||||
$_POST['bookmark_variable']
|
||||
);
|
||||
} else {
|
||||
$import_text = $bookmark->getQuery();
|
||||
}
|
||||
|
||||
// refresh navigation and main panels
|
||||
if (preg_match(
|
||||
'/^(DROP)\s+(VIEW|TABLE|DATABASE|SCHEMA)\s+/i',
|
||||
$import_text
|
||||
)) {
|
||||
$reload = true;
|
||||
$ajax_reload['reload'] = true;
|
||||
}
|
||||
|
||||
// refresh navigation panel only
|
||||
if (preg_match(
|
||||
'/^(CREATE|ALTER)\s+(VIEW|TABLE|DATABASE|SCHEMA)\s+/i',
|
||||
$import_text
|
||||
)
|
||||
) {
|
||||
$ajax_reload['reload'] = true;
|
||||
}
|
||||
break;
|
||||
case 1: // bookmarked query that have to be displayed
|
||||
$bookmark = Bookmark::get(
|
||||
$this->dbi,
|
||||
$cfg['Server']['user'],
|
||||
$db,
|
||||
$id_bookmark
|
||||
);
|
||||
if (! $bookmark instanceof Bookmark) {
|
||||
break;
|
||||
}
|
||||
$import_text = $bookmark->getQuery();
|
||||
if ($this->response->isAjax()) {
|
||||
$message = Message::success(__('Showing bookmark'));
|
||||
$this->response->setRequestStatus($message->isSuccess());
|
||||
$this->response->addJSON('message', $message);
|
||||
$this->response->addJSON('sql_query', $import_text);
|
||||
$this->response->addJSON('action_bookmark', $_POST['action_bookmark']);
|
||||
|
||||
return;
|
||||
} else {
|
||||
$run_query = false;
|
||||
}
|
||||
break;
|
||||
case 2: // bookmarked query that have to be deleted
|
||||
$bookmark = Bookmark::get(
|
||||
$this->dbi,
|
||||
$cfg['Server']['user'],
|
||||
$db,
|
||||
$id_bookmark
|
||||
);
|
||||
if (! $bookmark instanceof Bookmark) {
|
||||
break;
|
||||
}
|
||||
$bookmark->delete();
|
||||
if ($this->response->isAjax()) {
|
||||
$message = Message::success(
|
||||
__('The bookmark has been deleted.')
|
||||
);
|
||||
$this->response->setRequestStatus($message->isSuccess());
|
||||
$this->response->addJSON('message', $message);
|
||||
$this->response->addJSON('action_bookmark', $_POST['action_bookmark']);
|
||||
$this->response->addJSON('id_bookmark', $id_bookmark);
|
||||
|
||||
return;
|
||||
} else {
|
||||
$run_query = false;
|
||||
$error = true; // this is kind of hack to skip processing the query
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Do no run query if we show PHP code
|
||||
if (isset($show_as_php)) {
|
||||
$run_query = false;
|
||||
$go_sql = true;
|
||||
}
|
||||
|
||||
// We can not read all at once, otherwise we can run out of memory
|
||||
$memory_limit = trim((string) ini_get('memory_limit'));
|
||||
// 2 MB as default
|
||||
if (empty($memory_limit)) {
|
||||
$memory_limit = 2 * 1024 * 1024;
|
||||
}
|
||||
// In case no memory limit we work on 10MB chunks
|
||||
if ($memory_limit == -1) {
|
||||
$memory_limit = 10 * 1024 * 1024;
|
||||
}
|
||||
|
||||
// Calculate value of the limit
|
||||
$memoryUnit = mb_strtolower(substr((string) $memory_limit, -1));
|
||||
if ($memoryUnit === 'm') {
|
||||
$memory_limit = (int) substr((string) $memory_limit, 0, -1) * 1024 * 1024;
|
||||
} elseif ($memoryUnit === 'k') {
|
||||
$memory_limit = (int) substr((string) $memory_limit, 0, -1) * 1024;
|
||||
} elseif ($memoryUnit === 'g') {
|
||||
$memory_limit = (int) substr((string) $memory_limit, 0, -1) * 1024 * 1024 * 1024;
|
||||
} else {
|
||||
$memory_limit = (int) $memory_limit;
|
||||
}
|
||||
|
||||
// Just to be sure, there might be lot of memory needed for uncompression
|
||||
$read_limit = $memory_limit / 8;
|
||||
|
||||
// handle filenames
|
||||
if (isset($_FILES['import_file'])) {
|
||||
$import_file = $_FILES['import_file']['tmp_name'];
|
||||
$import_file_name = $_FILES['import_file']['name'];
|
||||
}
|
||||
if (! empty($local_import_file) && ! empty($cfg['UploadDir'])) {
|
||||
// sanitize $local_import_file as it comes from a POST
|
||||
$local_import_file = Core::securePath($local_import_file);
|
||||
|
||||
$import_file = Util::userDir($cfg['UploadDir'])
|
||||
. $local_import_file;
|
||||
|
||||
/*
|
||||
* Do not allow symlinks to avoid security issues
|
||||
* (user can create symlink to file they can not access,
|
||||
* but phpMyAdmin can).
|
||||
*/
|
||||
if (@is_link($import_file)) {
|
||||
$import_file = 'none';
|
||||
}
|
||||
} elseif (empty($import_file) || ! is_uploaded_file($import_file)) {
|
||||
$import_file = 'none';
|
||||
}
|
||||
|
||||
// Do we have file to import?
|
||||
|
||||
if ($import_file !== 'none' && ! $error) {
|
||||
/**
|
||||
* Handle file compression
|
||||
*/
|
||||
$importHandle = new File($import_file);
|
||||
$importHandle->checkUploadedFile();
|
||||
if ($importHandle->isError()) {
|
||||
/** @var Message $errorMessage */
|
||||
$errorMessage = $importHandle->getError();
|
||||
|
||||
$importHandle->close();
|
||||
|
||||
$_SESSION['Import_message']['message'] = $errorMessage->getDisplay();
|
||||
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON('message', $errorMessage->getDisplay());
|
||||
$this->response->addHTML($errorMessage->getDisplay());
|
||||
|
||||
return;
|
||||
}
|
||||
$importHandle->setDecompressContent(true);
|
||||
$importHandle->open();
|
||||
if ($importHandle->isError()) {
|
||||
/** @var Message $errorMessage */
|
||||
$errorMessage = $importHandle->getError();
|
||||
|
||||
$importHandle->close();
|
||||
|
||||
$_SESSION['Import_message']['message'] = $errorMessage->getDisplay();
|
||||
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON('message', $errorMessage->getDisplay());
|
||||
$this->response->addHTML($errorMessage->getDisplay());
|
||||
|
||||
return;
|
||||
}
|
||||
} elseif (! $error && (! isset($import_text) || empty($import_text))) {
|
||||
$message = Message::error(
|
||||
__(
|
||||
'No data was received to import. Either no file name was ' .
|
||||
'submitted, or the file size exceeded the maximum size permitted ' .
|
||||
'by your PHP configuration. See [doc@faq1-16]FAQ 1.16[/doc].'
|
||||
)
|
||||
);
|
||||
|
||||
$_SESSION['Import_message']['message'] = $message->getDisplay();
|
||||
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON('message', $message->getDisplay());
|
||||
$this->response->addHTML($message->getDisplay());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert the file's charset if necessary
|
||||
if (Encoding::isSupported() && isset($charset_of_file)) {
|
||||
if ($charset_of_file !== 'utf-8') {
|
||||
$charset_conversion = true;
|
||||
}
|
||||
} elseif (isset($charset_of_file) && $charset_of_file !== 'utf-8') {
|
||||
$this->dbi->query('SET NAMES \'' . $charset_of_file . '\'');
|
||||
// We can not show query in this case, it is in different charset
|
||||
$sql_query_disabled = true;
|
||||
$reset_charset = true;
|
||||
}
|
||||
|
||||
// Something to skip? (because timeout has passed)
|
||||
if (! $error && isset($_POST['skip'])) {
|
||||
$original_skip = $skip = intval($_POST['skip']);
|
||||
while ($skip > 0 && ! $finished) {
|
||||
$this->import->getNextChunk($importHandle ?? null, $skip < $read_limit ? $skip : $read_limit);
|
||||
// Disable read progressivity, otherwise we eat all memory!
|
||||
$read_multiply = 1;
|
||||
$skip -= $read_limit;
|
||||
}
|
||||
unset($skip);
|
||||
}
|
||||
|
||||
// This array contain the data like number of valid sql queries in the statement
|
||||
// and complete valid sql statement (which affected for rows)
|
||||
$sql_data = [
|
||||
'valid_sql' => [],
|
||||
'valid_queries' => 0,
|
||||
];
|
||||
|
||||
if (! $error) {
|
||||
/**
|
||||
* @var ImportPlugin $import_plugin
|
||||
*/
|
||||
$import_plugin = Plugins::getPlugin(
|
||||
'import',
|
||||
$format,
|
||||
'libraries/classes/Plugins/Import/',
|
||||
$import_type
|
||||
);
|
||||
if ($import_plugin == null) {
|
||||
$message = Message::error(
|
||||
__('Could not load import plugins, please check your installation!')
|
||||
);
|
||||
|
||||
$_SESSION['Import_message']['message'] = $message->getDisplay();
|
||||
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON('message', $message->getDisplay());
|
||||
$this->response->addHTML($message->getDisplay());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Do the real import
|
||||
$default_fk_check = Util::handleDisableFKCheckInit();
|
||||
try {
|
||||
$import_plugin->doImport($importHandle ?? null, $sql_data);
|
||||
Util::handleDisableFKCheckCleanup($default_fk_check);
|
||||
} catch (Throwable $e) {
|
||||
Util::handleDisableFKCheckCleanup($default_fk_check);
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($importHandle)) {
|
||||
$importHandle->close();
|
||||
}
|
||||
|
||||
// Reset charset back, if we did some changes
|
||||
if ($reset_charset) {
|
||||
$this->dbi->query('SET CHARACTER SET ' . $charset_connection);
|
||||
$this->dbi->setCollation($collation_connection);
|
||||
}
|
||||
|
||||
// Show correct message
|
||||
if (! empty($id_bookmark) && $_POST['action_bookmark'] == 2) {
|
||||
$message = Message::success(__('The bookmark has been deleted.'));
|
||||
$display_query = $import_text;
|
||||
$error = false; // unset error marker, it was used just to skip processing
|
||||
} elseif (! empty($id_bookmark) && $_POST['action_bookmark'] == 1) {
|
||||
$message = Message::notice(__('Showing bookmark'));
|
||||
} elseif ($bookmark_created) {
|
||||
$special_message = '[br]' . sprintf(
|
||||
__('Bookmark %s has been created.'),
|
||||
htmlspecialchars($_POST['bkm_label'])
|
||||
);
|
||||
} elseif ($finished && ! $error) {
|
||||
// Do not display the query with message, we do it separately
|
||||
$display_query = ';';
|
||||
if ($import_type !== 'query') {
|
||||
$message = Message::success(
|
||||
'<em>'
|
||||
. _ngettext(
|
||||
'Import has been successfully finished, %d query executed.',
|
||||
'Import has been successfully finished, %d queries executed.',
|
||||
$executed_queries
|
||||
)
|
||||
. '</em>'
|
||||
);
|
||||
$message->addParam($executed_queries);
|
||||
|
||||
if (! empty($import_notice)) {
|
||||
$message->addHtml($import_notice);
|
||||
}
|
||||
if (! empty($local_import_file)) {
|
||||
$message->addText('(' . $local_import_file . ')');
|
||||
} else {
|
||||
$message->addText('(' . $_FILES['import_file']['name'] . ')');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Did we hit timeout? Tell it user.
|
||||
if ($timeout_passed) {
|
||||
$url_params['timeout_passed'] = '1';
|
||||
$url_params['offset'] = $offset;
|
||||
if (isset($local_import_file)) {
|
||||
$url_params['local_import_file'] = $local_import_file;
|
||||
}
|
||||
|
||||
$importUrl = $err_url = $goto . Url::getCommon($url_params, '&');
|
||||
|
||||
$message = Message::error(
|
||||
__(
|
||||
'Script timeout passed, if you want to finish import,'
|
||||
. ' please %sresubmit the same file%s and import will resume.'
|
||||
)
|
||||
);
|
||||
$message->addParamHtml('<a href="' . $importUrl . '">');
|
||||
$message->addParamHtml('</a>');
|
||||
|
||||
if ($offset == 0 || (isset($original_skip) && $original_skip == $offset)) {
|
||||
$message->addText(
|
||||
__(
|
||||
'However on last run no data has been parsed,'
|
||||
. ' this usually means phpMyAdmin won\'t be able to'
|
||||
. ' finish this import unless you increase php time limits.'
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// if there is any message, copy it into $_SESSION as well,
|
||||
// so we can obtain it by AJAX call
|
||||
if (isset($message)) {
|
||||
$_SESSION['Import_message']['message'] = $message->getDisplay();
|
||||
}
|
||||
// Parse and analyze the query, for correct db and table name
|
||||
// in case of a query typed in the query window
|
||||
// (but if the query is too large, in case of an imported file, the parser
|
||||
// can choke on it so avoid parsing)
|
||||
$sqlLength = mb_strlen($sql_query);
|
||||
if ($sqlLength <= $cfg['MaxCharactersInDisplayedSQL']) {
|
||||
[
|
||||
$analyzed_sql_results,
|
||||
$db,
|
||||
$table_from_sql,
|
||||
] = ParseAnalyze::sqlQuery($sql_query, $db);
|
||||
|
||||
$reload = $analyzed_sql_results['reload'];
|
||||
$offset = $analyzed_sql_results['offset'];
|
||||
|
||||
if ($table != $table_from_sql && ! empty($table_from_sql)) {
|
||||
$table = $table_from_sql;
|
||||
}
|
||||
}
|
||||
|
||||
// There was an error?
|
||||
if (isset($my_die)) {
|
||||
foreach ($my_die as $key => $die) {
|
||||
Generator::mysqlDie(
|
||||
$die['error'],
|
||||
$die['sql'],
|
||||
false,
|
||||
$err_url,
|
||||
$error
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ($go_sql) {
|
||||
if (! empty($sql_data) && ($sql_data['valid_queries'] > 1)) {
|
||||
$_SESSION['is_multi_query'] = true;
|
||||
$sql_queries = $sql_data['valid_sql'];
|
||||
} else {
|
||||
$sql_queries = [$sql_query];
|
||||
}
|
||||
|
||||
$html_output = '';
|
||||
|
||||
foreach ($sql_queries as $sql_query) {
|
||||
// parse sql query
|
||||
[
|
||||
$analyzed_sql_results,
|
||||
$db,
|
||||
$table_from_sql,
|
||||
] = ParseAnalyze::sqlQuery($sql_query, $db);
|
||||
|
||||
$offset = $analyzed_sql_results['offset'];
|
||||
$reload = $analyzed_sql_results['reload'];
|
||||
|
||||
// Check if User is allowed to issue a 'DROP DATABASE' Statement
|
||||
if ($this->sql->hasNoRightsToDropDatabase(
|
||||
$analyzed_sql_results,
|
||||
$cfg['AllowUserDropDatabase'],
|
||||
$this->dbi->isSuperUser()
|
||||
)) {
|
||||
Generator::mysqlDie(
|
||||
__('"DROP DATABASE" statements are disabled.'),
|
||||
'',
|
||||
false,
|
||||
$_SESSION['Import_message']['go_back_url']
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($table != $table_from_sql && ! empty($table_from_sql)) {
|
||||
$table = $table_from_sql;
|
||||
}
|
||||
|
||||
$html_output .= $this->sql->executeQueryAndGetQueryResponse(
|
||||
$analyzed_sql_results, // analyzed_sql_results
|
||||
false, // is_gotofile
|
||||
$db, // db
|
||||
$table, // table
|
||||
null, // find_real_end
|
||||
null, // sql_query_for_bookmark - see below
|
||||
null, // extra_data
|
||||
null, // message_to_show
|
||||
null, // sql_data
|
||||
$goto, // goto
|
||||
$PMA_Theme->getImgPath(),
|
||||
null, // disp_query
|
||||
null, // disp_message
|
||||
$sql_query, // sql_query
|
||||
null // complete_query
|
||||
);
|
||||
}
|
||||
|
||||
// sql_query_for_bookmark is not included in Sql::executeQueryAndGetQueryResponse
|
||||
// since only one bookmark has to be added for all the queries submitted through
|
||||
// the SQL tab
|
||||
if (! empty($_POST['bkm_label']) && ! empty($import_text)) {
|
||||
$cfgBookmark = Bookmark::getParams($cfg['Server']['user']);
|
||||
|
||||
if (! is_array($cfgBookmark)) {
|
||||
$cfgBookmark = [];
|
||||
}
|
||||
|
||||
$this->sql->storeTheQueryAsBookmark(
|
||||
$db,
|
||||
$cfgBookmark['user'],
|
||||
$_POST['sql_query'],
|
||||
$_POST['bkm_label'],
|
||||
isset($_POST['bkm_replace'])
|
||||
);
|
||||
}
|
||||
|
||||
$this->response->addJSON('ajax_reload', $ajax_reload);
|
||||
$this->response->addHTML($html_output);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($result) {
|
||||
// Save a Bookmark with more than one queries (if Bookmark label given).
|
||||
if (! empty($_POST['bkm_label']) && ! empty($import_text)) {
|
||||
$cfgBookmark = Bookmark::getParams($cfg['Server']['user']);
|
||||
|
||||
if (! is_array($cfgBookmark)) {
|
||||
$cfgBookmark = [];
|
||||
}
|
||||
|
||||
$this->sql->storeTheQueryAsBookmark(
|
||||
$db,
|
||||
$cfgBookmark['user'],
|
||||
$_POST['sql_query'],
|
||||
$_POST['bkm_label'],
|
||||
isset($_POST['bkm_replace'])
|
||||
);
|
||||
}
|
||||
|
||||
$this->response->setRequestStatus(true);
|
||||
$this->response->addJSON('message', Message::success($msg));
|
||||
$this->response->addJSON(
|
||||
'sql_query',
|
||||
Generator::getMessage($msg, $sql_query, 'success')
|
||||
);
|
||||
} elseif ($result === false) {
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON('message', Message::error($msg));
|
||||
} else {
|
||||
$active_page = $goto;
|
||||
include ROOT_PATH . $goto;
|
||||
}
|
||||
|
||||
// If there is request for ROLLBACK in the end.
|
||||
if (! isset($_POST['rollback_query'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->dbi->query('ROLLBACK');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers;
|
||||
|
||||
use PhpMyAdmin\Core;
|
||||
use PhpMyAdmin\Import\Ajax;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\Template;
|
||||
use function header;
|
||||
use function ini_get;
|
||||
use function session_start;
|
||||
use function session_write_close;
|
||||
use function time;
|
||||
use function usleep;
|
||||
|
||||
/**
|
||||
* Import progress bar backend
|
||||
*/
|
||||
class ImportStatusController
|
||||
{
|
||||
/** @var Template */
|
||||
private $template;
|
||||
|
||||
/**
|
||||
* @param Template $template Template object
|
||||
*/
|
||||
public function __construct(Template $template)
|
||||
{
|
||||
$this->template = $template;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $SESSION_KEY, $upload_id, $plugins, $timestamp;
|
||||
|
||||
[
|
||||
$SESSION_KEY,
|
||||
$upload_id,
|
||||
$plugins,
|
||||
] = Ajax::uploadProgressSetup();
|
||||
|
||||
// $_GET["message"] is used for asking for an import message
|
||||
if (isset($_GET['message']) && $_GET['message']) {
|
||||
// AJAX requests can't be cached!
|
||||
Core::noCacheHeader();
|
||||
|
||||
header('Content-type: text/html');
|
||||
|
||||
// wait 0.3 sec before we check for $_SESSION variable
|
||||
usleep(300000);
|
||||
|
||||
$maximumTime = ini_get('max_execution_time');
|
||||
$timestamp = time();
|
||||
// wait until message is available
|
||||
while (($_SESSION['Import_message']['message'] ?? null) == null) {
|
||||
// close session before sleeping
|
||||
session_write_close();
|
||||
// sleep
|
||||
usleep(250000); // 0.25 sec
|
||||
// reopen session
|
||||
session_start();
|
||||
|
||||
if (time() - $timestamp > $maximumTime) {
|
||||
$_SESSION['Import_message']['message'] = Message::error(
|
||||
__('Could not load the progress of the import.')
|
||||
)->getDisplay();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
echo $_SESSION['Import_message']['message'] ?? '';
|
||||
|
||||
if (isset($_SESSION['Import_message']['go_back_url'])) {
|
||||
echo $this->template->render('import_status', [
|
||||
'go_back_url' => $_SESSION['Import_message']['go_back_url'],
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
Ajax::status($_GET['id']);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,738 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers;
|
||||
|
||||
use function json_encode;
|
||||
|
||||
/**
|
||||
* Exporting of translated messages from PHP to JavaScript.
|
||||
*/
|
||||
final class JavaScriptMessagesController
|
||||
{
|
||||
/** @var array<string, string> */
|
||||
private $messages = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->setMessages();
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
echo 'var Messages = ' . json_encode($this->messages) . ';';
|
||||
}
|
||||
|
||||
private function setMessages(): void
|
||||
{
|
||||
global $cfg, $PMA_Theme;
|
||||
|
||||
$ajaxClockSmallGifPath = $PMA_Theme !== null ? $PMA_Theme->getImgPath('ajax_clock_small.gif') : '';
|
||||
|
||||
$this->messages = [
|
||||
/* For confirmations */
|
||||
'strConfirm' => __('Confirm'),
|
||||
'strDoYouReally' => __('Do you really want to execute "%s"?'),
|
||||
'strDropDatabaseStrongWarning' => __('You are about to DESTROY a complete database!'),
|
||||
'strDatabaseRenameToSameName' => __(
|
||||
'Cannot rename database to the same name. Change the name and try again'
|
||||
),
|
||||
'strDropTableStrongWarning' => __('You are about to DESTROY a complete table!'),
|
||||
'strTruncateTableStrongWarning' => __('You are about to TRUNCATE a complete table!'),
|
||||
'strDeleteTrackingData' => __('Delete tracking data for this table?'),
|
||||
'strDeleteTrackingDataMultiple' => __('Delete tracking data for these tables?'),
|
||||
'strDeleteTrackingVersion' => __('Delete tracking data for this version?'),
|
||||
'strDeleteTrackingVersionMultiple' => __('Delete tracking data for these versions?'),
|
||||
'strDeletingTrackingEntry' => __('Delete entry from tracking report?'),
|
||||
'strDeletingTrackingData' => __('Deleting tracking data'),
|
||||
'strDroppingPrimaryKeyIndex' => __('Dropping Primary Key/Index'),
|
||||
'strDroppingForeignKey' => __('Dropping Foreign key.'),
|
||||
'strOperationTakesLongTime' => __('This operation could take a long time. Proceed anyway?'),
|
||||
'strDropUserGroupWarning' => __('Do you really want to delete user group "%s"?'),
|
||||
'strConfirmDeleteQBESearch' => __('Do you really want to delete the search "%s"?'),
|
||||
'strConfirmNavigation' => __('You have unsaved changes; are you sure you want to leave this page?'),
|
||||
'strConfirmRowChange' => __(
|
||||
'You are trying to reduce the number of rows, but have already entered'
|
||||
. ' data in those rows which will be lost. Do you wish to continue?'
|
||||
),
|
||||
'strDropUserWarning' => __('Do you really want to revoke the selected user(s) ?'),
|
||||
'strDeleteCentralColumnWarning' => __('Do you really want to delete this central column?'),
|
||||
'strDropRTEitems' => __('Do you really want to delete the selected items?'),
|
||||
'strDropPartitionWarning' => __(
|
||||
'Do you really want to DROP the selected partition(s)? This will also DELETE ' .
|
||||
'the data related to the selected partition(s)!'
|
||||
),
|
||||
'strTruncatePartitionWarning' => __('Do you really want to TRUNCATE the selected partition(s)?'),
|
||||
'strRemovePartitioningWarning' => __('Do you really want to remove partitioning?'),
|
||||
'strResetSlaveWarning' => __('Do you really want to RESET SLAVE?'),
|
||||
'strChangeColumnCollation' => __(
|
||||
'This operation will attempt to convert your data to the new collation. In '
|
||||
. 'rare cases, especially where a character doesn\'t exist in the new '
|
||||
. 'collation, this process could cause the data to appear incorrectly under '
|
||||
. 'the new collation; in this case we suggest you revert to the original '
|
||||
. 'collation and refer to the tips at '
|
||||
)
|
||||
. '<a href="%s" target="garbled_data_wiki">' . __('Garbled Data') . '</a>.'
|
||||
. '<br><br>'
|
||||
. __('Are you sure you wish to change the collation and convert the data?'),
|
||||
|
||||
'strChangeAllColumnCollationsWarning' => __(
|
||||
'Through this operation, MySQL attempts to map the data values between '
|
||||
. 'collations. If the character sets are incompatible, there may be data loss '
|
||||
. 'and this lost data may <b>NOT</b> be recoverable simply by changing back the '
|
||||
. 'column collation(s). <b>To convert existing data, it is suggested to use the '
|
||||
. 'column(s) editing feature (the "Change" Link) on the table structure page. '
|
||||
. '</b>'
|
||||
)
|
||||
. '<br><br>'
|
||||
. __(
|
||||
'Are you sure you wish to change all the column collations and convert the data?'
|
||||
),
|
||||
|
||||
/* For modal dialog buttons */
|
||||
'strSaveAndClose' => __('Save & close'),
|
||||
'strReset' => __('Reset'),
|
||||
'strResetAll' => __('Reset all'),
|
||||
|
||||
/* For indexes */
|
||||
'strFormEmpty' => __('Missing value in the form!'),
|
||||
'strRadioUnchecked' => __('Select at least one of the options!'),
|
||||
'strEnterValidNumber' => __('Please enter a valid number!'),
|
||||
'strEnterValidLength' => __('Please enter a valid length!'),
|
||||
'strAddIndex' => __('Add index'),
|
||||
'strEditIndex' => __('Edit index'),
|
||||
/* l10n: Rename a table Index */
|
||||
'strRenameIndex' => __('Rename index'),
|
||||
'strAddToIndex' => __('Add %s column(s) to index'),
|
||||
'strCreateSingleColumnIndex' => __('Create single-column index'),
|
||||
'strCreateCompositeIndex' => __('Create composite index'),
|
||||
'strCompositeWith' => __('Composite with:'),
|
||||
'strMissingColumn' => __('Please select column(s) for the index.'),
|
||||
|
||||
/* For Preview SQL*/
|
||||
'strPreviewSQL' => __('Preview SQL'),
|
||||
|
||||
/* For Simulate DML*/
|
||||
'strSimulateDML' => __('Simulate query'),
|
||||
'strMatchedRows' => __('Matched rows:'),
|
||||
'strSQLQuery' => __('SQL query:'),
|
||||
|
||||
/* Charts */
|
||||
/* l10n: Default label for the y-Axis of Charts */
|
||||
'strYValues' => __('Y values'),
|
||||
|
||||
/* Database multi-table query */
|
||||
'strEmptyQuery' => __('Please enter the SQL query first.'),
|
||||
|
||||
/* For server/privileges.js */
|
||||
'strHostEmpty' => __('The host name is empty!'),
|
||||
'strUserEmpty' => __('The user name is empty!'),
|
||||
'strPasswordEmpty' => __('The password is empty!'),
|
||||
'strPasswordNotSame' => __('The passwords aren\'t the same!'),
|
||||
'strRemovingSelectedUsers' => __('Removing Selected Users'),
|
||||
'strClose' => __('Close'),
|
||||
|
||||
/* For export.js */
|
||||
'strTemplateCreated' => __('Template was created.'),
|
||||
'strTemplateLoaded' => __('Template was loaded.'),
|
||||
'strTemplateUpdated' => __('Template was updated.'),
|
||||
'strTemplateDeleted' => __('Template was deleted.'),
|
||||
|
||||
/* l10n: Other, small valued, queries */
|
||||
'strOther' => __('Other'),
|
||||
/* l10n: Thousands separator */
|
||||
'strThousandsSeparator' => __(','),
|
||||
/* l10n: Decimal separator */
|
||||
'strDecimalSeparator' => __('.'),
|
||||
|
||||
'strChartConnectionsTitle' => __('Connections / Processes'),
|
||||
|
||||
/* server status monitor */
|
||||
'strIncompatibleMonitorConfig' => __('Local monitor configuration incompatible!'),
|
||||
'strIncompatibleMonitorConfigDescription' => __(
|
||||
'The chart arrangement configuration in your browsers local storage is not '
|
||||
. 'compatible anymore to the newer version of the monitor dialog. It is very '
|
||||
. 'likely that your current configuration will not work anymore. Please reset '
|
||||
. 'your configuration to default in the <i>Settings</i> menu.'
|
||||
),
|
||||
|
||||
'strQueryCacheEfficiency' => __('Query cache efficiency'),
|
||||
'strQueryCacheUsage' => __('Query cache usage'),
|
||||
'strQueryCacheUsed' => __('Query cache used'),
|
||||
|
||||
'strSystemCPUUsage' => __('System CPU usage'),
|
||||
'strSystemMemory' => __('System memory'),
|
||||
'strSystemSwap' => __('System swap'),
|
||||
|
||||
'strAverageLoad' => __('Average load'),
|
||||
'strTotalMemory' => __('Total memory'),
|
||||
'strCachedMemory' => __('Cached memory'),
|
||||
'strBufferedMemory' => __('Buffered memory'),
|
||||
'strFreeMemory' => __('Free memory'),
|
||||
'strUsedMemory' => __('Used memory'),
|
||||
|
||||
'strTotalSwap' => __('Total swap'),
|
||||
'strCachedSwap' => __('Cached swap'),
|
||||
'strUsedSwap' => __('Used swap'),
|
||||
'strFreeSwap' => __('Free swap'),
|
||||
|
||||
'strBytesSent' => __('Bytes sent'),
|
||||
'strBytesReceived' => __('Bytes received'),
|
||||
'strConnections' => __('Connections'),
|
||||
'strProcesses' => __('Processes'),
|
||||
|
||||
/* summary row */
|
||||
'strB' => __('B'),
|
||||
'strKiB' => __('KiB'),
|
||||
'strMiB' => __('MiB'),
|
||||
'strGiB' => __('GiB'),
|
||||
'strTiB' => __('TiB'),
|
||||
'strPiB' => __('PiB'),
|
||||
'strEiB' => __('EiB'),
|
||||
'strNTables' => __('%d table(s)'),
|
||||
|
||||
/* l10n: Questions is the name of a MySQL Status variable */
|
||||
'strQuestions' => __('Questions'),
|
||||
'strTraffic' => __('Traffic'),
|
||||
'strSettings' => __('Settings'),
|
||||
'strAddChart' => __('Add chart to grid'),
|
||||
'strAddOneSeriesWarning' => __('Please add at least one variable to the series!'),
|
||||
'strNone' => __('None'),
|
||||
/* l10n: SQL Query on modal to show exported query */
|
||||
'strQuery' => __('SQL Query'),
|
||||
'strResumeMonitor' => __('Resume monitor'),
|
||||
'strPauseMonitor' => __('Pause monitor'),
|
||||
'strStartRefresh' => __('Start auto refresh'),
|
||||
'strStopRefresh' => __('Stop auto refresh'),
|
||||
/* Monitor: Instructions Dialog */
|
||||
'strBothLogOn' => __('general_log and slow_query_log are enabled.'),
|
||||
'strGenLogOn' => __('general_log is enabled.'),
|
||||
'strSlowLogOn' => __('slow_query_log is enabled.'),
|
||||
'strBothLogOff' => __('slow_query_log and general_log are disabled.'),
|
||||
'strLogOutNotTable' => __('log_output is not set to TABLE.'),
|
||||
'strLogOutIsTable' => __('log_output is set to TABLE.'),
|
||||
'strSmallerLongQueryTimeAdvice' => __(
|
||||
'slow_query_log is enabled, but the server logs only queries that take longer '
|
||||
. 'than %d seconds. It is advisable to set this long_query_time 0-2 seconds, '
|
||||
. 'depending on your system.'
|
||||
),
|
||||
'strLongQueryTimeSet' => __('long_query_time is set to %d second(s).'),
|
||||
'strSettingsAppliedGlobal' => __(
|
||||
'Following settings will be applied globally and reset to default on server '
|
||||
. 'restart:'
|
||||
),
|
||||
/* l10n: %s is FILE or TABLE */
|
||||
'strSetLogOutput' => __('Set log_output to %s'),
|
||||
/* l10n: Enable in this context means setting a status variable to ON */
|
||||
'strEnableVar' => __('Enable %s'),
|
||||
/* l10n: Disable in this context means setting a status variable to OFF */
|
||||
'strDisableVar' => __('Disable %s'),
|
||||
/* l10n: %d seconds */
|
||||
'setSetLongQueryTime' => __('Set long_query_time to %d seconds.'),
|
||||
'strNoSuperUser' => __(
|
||||
'You can\'t change these variables. Please log in as root or contact'
|
||||
. ' your database administrator.'
|
||||
),
|
||||
'strChangeSettings' => __('Change settings'),
|
||||
'strCurrentSettings' => __('Current settings'),
|
||||
|
||||
'strChartTitle' => __('Chart title'),
|
||||
/* l10n: As in differential values */
|
||||
'strDifferential' => __('Differential'),
|
||||
'strDividedBy' => __('Divided by %s'),
|
||||
'strUnit' => __('Unit'),
|
||||
|
||||
'strFromSlowLog' => __('From slow log'),
|
||||
'strFromGeneralLog' => __('From general log'),
|
||||
'strServerLogError' => __(
|
||||
'The database name is not known for this query in the server\'s logs.'
|
||||
),
|
||||
'strAnalysingLogsTitle' => __('Analysing logs'),
|
||||
'strAnalysingLogs' => __('Analysing & loading logs. This may take a while.'),
|
||||
'strCancelRequest' => __('Cancel request'),
|
||||
'strCountColumnExplanation' => __(
|
||||
'This column shows the amount of identical queries that are grouped together. '
|
||||
. 'However only the SQL query itself has been used as a grouping criteria, so '
|
||||
. 'the other attributes of queries, such as start time, may differ.'
|
||||
),
|
||||
'strMoreCountColumnExplanation' => __(
|
||||
'Since grouping of INSERTs queries has been selected, INSERT queries into the '
|
||||
. 'same table are also being grouped together, disregarding of the inserted '
|
||||
. 'data.'
|
||||
),
|
||||
'strLogDataLoaded' => __('Log data loaded. Queries executed in this time span:'),
|
||||
|
||||
'strJumpToTable' => __('Jump to Log table'),
|
||||
'strNoDataFoundTitle' => __('No data found'),
|
||||
'strNoDataFound' => __('Log analysed, but no data found in this time span.'),
|
||||
|
||||
'strAnalyzing' => __('Analyzing…'),
|
||||
'strExplainOutput' => __('Explain output'),
|
||||
'strStatus' => __('Status'),
|
||||
'strTime' => __('Time'),
|
||||
'strTotalTime' => __('Total time:'),
|
||||
'strProfilingResults' => __('Profiling results'),
|
||||
'strTable' => _pgettext('Display format', 'Table'),
|
||||
'strChart' => __('Chart'),
|
||||
|
||||
'strAliasDatabase' => _pgettext('Alias', 'Database'),
|
||||
'strAliasTable' => _pgettext('Alias', 'Table'),
|
||||
'strAliasColumn' => _pgettext('Alias', 'Column'),
|
||||
|
||||
/* l10n: A collection of available filters */
|
||||
'strFiltersForLogTable' => __('Log table filter options'),
|
||||
/* l10n: Filter as in "Start Filtering" */
|
||||
'strFilter' => __('Filter'),
|
||||
'strFilterByWordRegexp' => __('Filter queries by word/regexp:'),
|
||||
'strIgnoreWhereAndGroup' => __('Group queries, ignoring variable data in WHERE clauses'),
|
||||
'strSumRows' => __('Sum of grouped rows:'),
|
||||
'strTotal' => __('Total:'),
|
||||
|
||||
'strLoadingLogs' => __('Loading logs'),
|
||||
'strRefreshFailed' => __('Monitor refresh failed'),
|
||||
'strInvalidResponseExplanation' => __(
|
||||
'While requesting new chart data the server returned an invalid response. This '
|
||||
. 'is most likely because your session expired. Reloading the page and '
|
||||
. 'reentering your credentials should help.'
|
||||
),
|
||||
'strReloadPage' => __('Reload page'),
|
||||
|
||||
'strAffectedRows' => __('Affected rows:'),
|
||||
|
||||
'strFailedParsingConfig' => __(
|
||||
'Failed parsing config file. It doesn\'t seem to be valid JSON code.'
|
||||
),
|
||||
'strFailedBuildingGrid' => __(
|
||||
'Failed building chart grid with imported config. Resetting to default config…'
|
||||
),
|
||||
'strImport' => __('Import'),
|
||||
'strImportDialogTitle' => __('Import monitor configuration'),
|
||||
'strImportDialogMessage' => __('Please select the file you want to import.'),
|
||||
'strTableNameDialogMessage' => __('Please enter a valid table name.'),
|
||||
'strDBNameDialogMessage' => __('Please enter a valid database name.'),
|
||||
'strNoImportFile' => __('No files available on server for import!'),
|
||||
|
||||
'strAnalyzeQuery' => __('Analyse query'),
|
||||
|
||||
/* For query editor */
|
||||
'strFormatting' => __('Formatting SQL…'),
|
||||
'strNoParam' => __('No parameters found!'),
|
||||
|
||||
/* For inline query editing */
|
||||
'strGo' => __('Go'),
|
||||
'strCancel' => __('Cancel'),
|
||||
|
||||
/* For page-related settings */
|
||||
'strPageSettings' => __('Page-related settings'),
|
||||
'strApply' => __('Apply'),
|
||||
|
||||
/* For Ajax Notifications */
|
||||
'strLoading' => __('Loading…'),
|
||||
'strAbortedRequest' => __('Request aborted!!'),
|
||||
'strProcessingRequest' => __('Processing request'),
|
||||
'strRequestFailed' => __('Request failed!!'),
|
||||
'strErrorProcessingRequest' => __('Error in processing request'),
|
||||
'strErrorCode' => __('Error code: %s'),
|
||||
'strErrorText' => __('Error text: %s'),
|
||||
'strErrorConnection' => __(
|
||||
'It seems that the connection to server has been lost. Please check your ' .
|
||||
'network connectivity and server status.'
|
||||
),
|
||||
'strNoDatabasesSelected' => __('No databases selected.'),
|
||||
'strNoAccountSelected' => __('No accounts selected.'),
|
||||
'strDroppingColumn' => __('Dropping column'),
|
||||
'strAddingPrimaryKey' => __('Adding primary key'),
|
||||
'strOK' => __('OK'),
|
||||
'strDismiss' => __('Click to dismiss this notification'),
|
||||
|
||||
/* For database/operations.js */
|
||||
'strRenamingDatabases' => __('Renaming databases'),
|
||||
'strCopyingDatabase' => __('Copying database'),
|
||||
'strChangingCharset' => __('Changing charset'),
|
||||
'strNo' => __('No'),
|
||||
|
||||
/* For Foreign key checks */
|
||||
'strForeignKeyCheck' => __('Enable foreign key checks'),
|
||||
|
||||
/* For database/structure.js */
|
||||
'strErrorRealRowCount' => __('Failed to get real row count.'),
|
||||
|
||||
/* For database/search.js */
|
||||
'strSearching' => __('Searching'),
|
||||
'strHideSearchResults' => __('Hide search results'),
|
||||
'strShowSearchResults' => __('Show search results'),
|
||||
'strBrowsing' => __('Browsing'),
|
||||
'strDeleting' => __('Deleting'),
|
||||
'strConfirmDeleteResults' => __('Delete the matches for the %s table?'),
|
||||
|
||||
/* For rte.js */
|
||||
'MissingReturn' => __('The definition of a stored function must contain a RETURN statement!'),
|
||||
'strExport' => __('Export'),
|
||||
'NoExportable' => __('No routine is exportable. Required privileges may be lacking.'),
|
||||
|
||||
/* For ENUM/SET editor*/
|
||||
'enum_editor' => __('ENUM/SET editor'),
|
||||
'enum_columnVals' => __('Values for column %s'),
|
||||
'enum_newColumnVals' => __('Values for a new column'),
|
||||
'enum_hint' => __('Enter each value in a separate field.'),
|
||||
'enum_addValue' => __('Add %d value(s)'),
|
||||
|
||||
/* For import.js */
|
||||
'strImportCSV' => __(
|
||||
'Note: If the file contains multiple tables, they will be combined into one.'
|
||||
),
|
||||
|
||||
/* For sql.js */
|
||||
'strHideQueryBox' => __('Hide query box'),
|
||||
'strShowQueryBox' => __('Show query box'),
|
||||
'strEdit' => __('Edit'),
|
||||
'strDelete' => __('Delete'),
|
||||
'strNotValidRowNumber' => __('%d is not valid row number.'),
|
||||
'strBrowseForeignValues' => __('Browse foreign values'),
|
||||
'strNoAutoSavedQuery' => __('No previously auto-saved query is available. Loading default query.'),
|
||||
'strPreviousSaveQuery' => __(
|
||||
'You have a previously saved query. Click Get auto-saved query to load the query.'
|
||||
),
|
||||
'strBookmarkVariable' => __('Variable %d:'),
|
||||
|
||||
/* For Central list of columns */
|
||||
'pickColumn' => __('Pick'),
|
||||
'pickColumnTitle' => __('Column selector'),
|
||||
'searchList' => __('Search this list'),
|
||||
'strEmptyCentralList' => __(
|
||||
'No columns in the central list. Make sure the Central columns list for '
|
||||
. 'database %s has columns that are not present in the current table.'
|
||||
),
|
||||
'seeMore' => __('See more'),
|
||||
'confirmTitle' => __('Are you sure?'),
|
||||
'makeConsistentMessage' => __(
|
||||
'This action may change some of the columns definition.<br>Are you sure you '
|
||||
. 'want to continue?'
|
||||
),
|
||||
'strContinue' => __('Continue'),
|
||||
|
||||
/** For normalization */
|
||||
'strAddPrimaryKey' => __('Add primary key'),
|
||||
'strPrimaryKeyAdded' => __('Primary key added.'),
|
||||
'strToNextStep' => __('Taking you to next step…'),
|
||||
'strFinishMsg' => __("The first step of normalization is complete for table '%s'."),
|
||||
'strEndStep' => __('End of step'),
|
||||
'str2NFNormalization' => __('Second step of normalization (2NF)'),
|
||||
'strDone' => __('Done'),
|
||||
'strConfirmPd' => __('Confirm partial dependencies'),
|
||||
'strSelectedPd' => __('Selected partial dependencies are as follows:'),
|
||||
'strPdHintNote' => __(
|
||||
'Note: a, b -> d,f implies values of columns a and b combined together can '
|
||||
. 'determine values of column d and column f.'
|
||||
),
|
||||
'strNoPdSelected' => __('No partial dependencies selected!'),
|
||||
'strBack' => __('Back'),
|
||||
'strShowPossiblePd' => __('Show me the possible partial dependencies based on data in the table'),
|
||||
'strHidePd' => __('Hide partial dependencies list'),
|
||||
'strWaitForPd' => __(
|
||||
'Sit tight! It may take few seconds depending on data size and column count of '
|
||||
. 'the table.'
|
||||
),
|
||||
'strStep' => __('Step'),
|
||||
'strMoveRepeatingGroup' => '<ol><b>' . __('The following actions will be performed:') . '</b>'
|
||||
. '<li>' . __('DROP columns %s from the table %s') . '</li>'
|
||||
. '<li>' . __('Create the following table') . '</li>',
|
||||
'strNewTablePlaceholder' => 'Enter new table name',
|
||||
'strNewColumnPlaceholder' => 'Enter column name',
|
||||
'str3NFNormalization' => __('Third step of normalization (3NF)'),
|
||||
'strConfirmTd' => __('Confirm transitive dependencies'),
|
||||
'strSelectedTd' => __('Selected dependencies are as follows:'),
|
||||
'strNoTdSelected' => __('No dependencies selected!'),
|
||||
|
||||
/* For server/variables.js */
|
||||
'strSave' => __('Save'),
|
||||
|
||||
/* For table/select.js */
|
||||
'strHideSearchCriteria' => __('Hide search criteria'),
|
||||
'strShowSearchCriteria' => __('Show search criteria'),
|
||||
'strRangeSearch' => __('Range search'),
|
||||
'strColumnMax' => __('Column maximum:'),
|
||||
'strColumnMin' => __('Column minimum:'),
|
||||
'strMinValue' => __('Minimum value:'),
|
||||
'strMaxValue' => __('Maximum value:'),
|
||||
|
||||
/* For table/find_replace.js */
|
||||
'strHideFindNReplaceCriteria' => __('Hide find and replace criteria'),
|
||||
'strShowFindNReplaceCriteria' => __('Show find and replace criteria'),
|
||||
|
||||
/* For table/zoom_plot_jqplot.js */
|
||||
'strDisplayHelp' => '<ul><li>'
|
||||
. __('Each point represents a data row.')
|
||||
. '</li><li>'
|
||||
. __('Hovering over a point will show its label.')
|
||||
. '</li><li>'
|
||||
. __('To zoom in, select a section of the plot with the mouse.')
|
||||
. '</li><li>'
|
||||
. __('Click reset zoom button to come back to original state.')
|
||||
. '</li><li>'
|
||||
. __('Click a data point to view and possibly edit the data row.')
|
||||
. '</li><li>'
|
||||
. __('The plot can be resized by dragging it along the bottom right corner.')
|
||||
. '</li></ul>',
|
||||
'strHelpTitle' => 'Zoom search instructions',
|
||||
'strInputNull' => '<strong>' . __('Select two columns') . '</strong>',
|
||||
'strSameInputs' => '<strong>'
|
||||
. __('Select two different columns')
|
||||
. '</strong>',
|
||||
'strDataPointContent' => __('Data point content'),
|
||||
|
||||
/* For table/change.js */
|
||||
'strIgnore' => __('Ignore'),
|
||||
'strCopy' => __('Copy'),
|
||||
'strX' => __('X'),
|
||||
'strY' => __('Y'),
|
||||
'strPoint' => __('Point'),
|
||||
'strPointN' => __('Point %d'),
|
||||
'strLineString' => __('Linestring'),
|
||||
'strPolygon' => __('Polygon'),
|
||||
'strGeometry' => __('Geometry'),
|
||||
'strInnerRing' => __('Inner ring'),
|
||||
'strOuterRing' => __('Outer ring'),
|
||||
'strAddPoint' => __('Add a point'),
|
||||
'strAddInnerRing' => __('Add an inner ring'),
|
||||
'strYes' => __('Yes'),
|
||||
'strCopyEncryptionKey' => __('Do you want to copy encryption key?'),
|
||||
'strEncryptionKey' => __('Encryption key'),
|
||||
/* l10n: Tip for HEX conversion of Integers */
|
||||
'HexConversionInfo' => __(
|
||||
'The HEX function will treat the integer as a string while calculating the hexadecimal value'
|
||||
),
|
||||
|
||||
/* For Tip to be shown on Time field */
|
||||
'strMysqlAllowedValuesTipTime' => __(
|
||||
'MySQL accepts additional values not selectable by the slider;'
|
||||
. ' key in those values directly if desired'
|
||||
),
|
||||
|
||||
/* For Tip to be shown on Date field */
|
||||
'strMysqlAllowedValuesTipDate' => __(
|
||||
'MySQL accepts additional values not selectable by the datepicker;'
|
||||
. ' key in those values directly if desired'
|
||||
),
|
||||
|
||||
/* For Lock symbol Tooltip */
|
||||
'strLockToolTip' => __(
|
||||
'Indicates that you have made changes to this page;'
|
||||
. ' you will be prompted for confirmation before abandoning changes'
|
||||
),
|
||||
|
||||
/* Designer (js/designer/move.js) */
|
||||
'strSelectReferencedKey' => __('Select referenced key'),
|
||||
'strSelectForeignKey' => __('Select Foreign Key'),
|
||||
'strPleaseSelectPrimaryOrUniqueKey' => __('Please select the primary key or a unique key!'),
|
||||
'strChangeDisplay' => __('Choose column to display'),
|
||||
'strLeavingDesigner' => __(
|
||||
'You haven\'t saved the changes in the layout. They will be lost if you'
|
||||
. ' don\'t save them. Do you want to continue?'
|
||||
),
|
||||
'strQueryEmpty' => __('value/subQuery is empty'),
|
||||
'strAddTables' => __('Add tables from other databases'),
|
||||
'strPageName' => __('Page name'),
|
||||
'strSavePage' => __('Save page'),
|
||||
'strSavePageAs' => __('Save page as'),
|
||||
'strOpenPage' => __('Open page'),
|
||||
'strDeletePage' => __('Delete page'),
|
||||
/* l10n: When the user opens a page saved in the Designer */
|
||||
'strSavedPageTableMissing' => __('Some tables saved in this page might have been renamed or deleted.'),
|
||||
'strUntitled' => __('Untitled'),
|
||||
'strSelectPage' => __('Please select a page to continue'),
|
||||
'strEnterValidPageName' => __('Please enter a valid page name'),
|
||||
'strLeavingPage' => __('Do you want to save the changes to the current page?'),
|
||||
'strSuccessfulPageDelete' => __('Successfully deleted the page'),
|
||||
'strExportRelationalSchema' => __('Export relational schema'),
|
||||
'strModificationSaved' => __('Modifications have been saved'),
|
||||
|
||||
/* Visual query builder (js/designer/move.js) */
|
||||
'strObjectsCreated' => __('%d object(s) created.'),
|
||||
'strColumnName' => __('Column name'),
|
||||
'strSubmit' => __('Submit'),
|
||||
|
||||
/* For makegrid.js (column reordering, show/hide column, grid editing) */
|
||||
'strCellEditHint' => __('Press escape to cancel editing.'),
|
||||
'strSaveCellWarning' => __(
|
||||
'You have edited some data and they have not been saved. Are you sure you want '
|
||||
. 'to leave this page before saving the data?'
|
||||
),
|
||||
'strColOrderHint' => __('Drag to reorder.'),
|
||||
'strSortHint' => __('Click to sort results by this column.'),
|
||||
'strMultiSortHint' => __(
|
||||
'Shift+Click to add this column to ORDER BY clause or to toggle ASC/DESC.'
|
||||
. '<br>- Ctrl+Click or Alt+Click (Mac: Shift+Option+Click) to remove column '
|
||||
. 'from ORDER BY clause'
|
||||
),
|
||||
'strColMarkHint' => __('Click to mark/unmark.'),
|
||||
'strColNameCopyHint' => __('Double-click to copy column name.'),
|
||||
'strColVisibHint' => __(
|
||||
'Click the drop-down arrow<br>to toggle column\'s visibility.'
|
||||
),
|
||||
'strShowAllCol' => __('Show all'),
|
||||
'strAlertNonUnique' => __(
|
||||
'This table does not contain a unique column. Features related to the grid '
|
||||
. 'edit, checkbox, Edit, Copy and Delete links may not work after saving.'
|
||||
),
|
||||
'strEnterValidHex' => __('Please enter a valid hexadecimal string. Valid characters are 0-9, A-F.'),
|
||||
'strShowAllRowsWarning' => __(
|
||||
'Do you really want to see all of the rows? For a big table this could crash '
|
||||
. 'the browser.'
|
||||
),
|
||||
'strOriginalLength' => __('Original length'),
|
||||
|
||||
/** Drag & Drop sql import messages */
|
||||
'dropImportMessageCancel' => __('cancel'),
|
||||
'dropImportMessageAborted' => __('Aborted'),
|
||||
'dropImportMessageFailed' => __('Failed'),
|
||||
'dropImportMessageSuccess' => __('Success'),
|
||||
'dropImportImportResultHeader' => __('Import status'),
|
||||
'dropImportDropFiles' => __('Drop files here'),
|
||||
'dropImportSelectDB' => __('Select database first'),
|
||||
|
||||
/* For Print view */
|
||||
'print' => __('Print'),
|
||||
'back' => __('Back'),
|
||||
|
||||
// this approach does not work when the parameter is changed via user prefs
|
||||
'strGridEditFeatureHint' => $cfg['GridEditing'] === 'double-click'
|
||||
? __('You can also edit most values<br>by double-clicking directly on them.')
|
||||
: ($cfg['GridEditing'] === 'click'
|
||||
? __('You can also edit most values<br>by clicking directly on them.')
|
||||
: ''),
|
||||
|
||||
'strGoToLink' => __('Go to link:'),
|
||||
'strColNameCopyTitle' => __('Copy column name.'),
|
||||
'strColNameCopyText' => __('Right-click the column name to copy it to your clipboard.'),
|
||||
|
||||
/* password generation */
|
||||
'strGeneratePassword' => __('Generate password'),
|
||||
'strGenerate' => __('Generate'),
|
||||
'strChangePassword' => __('Change password'),
|
||||
|
||||
/* navigation tabs */
|
||||
'strMore' => __('More'),
|
||||
|
||||
/* navigation panel */
|
||||
'strShowPanel' => __('Show panel'),
|
||||
'strHidePanel' => __('Hide panel'),
|
||||
'strUnhideNavItem' => __('Show hidden navigation tree items.'),
|
||||
'linkWithMain' => __('Link with main panel'),
|
||||
'unlinkWithMain' => __('Unlink from main panel'),
|
||||
|
||||
/* microhistory */
|
||||
'strInvalidPage' => __('The requested page was not found in the history, it may have expired.'),
|
||||
|
||||
/* update */
|
||||
'strNewerVersion' => __(
|
||||
'A newer version of phpMyAdmin is available and you should consider upgrading. '
|
||||
. 'The newest version is %s, released on %s.'
|
||||
),
|
||||
/* l10n: Latest available phpMyAdmin version */
|
||||
'strLatestAvailable' => __(', latest stable version:'),
|
||||
'strUpToDate' => __('up to date'),
|
||||
|
||||
'strCreateView' => __('Create view'),
|
||||
|
||||
/* Error Reporting */
|
||||
'strSendErrorReport' => __('Send error report'),
|
||||
'strSubmitErrorReport' => __('Submit error report'),
|
||||
'strErrorOccurred' => __(
|
||||
'A fatal JavaScript error has occurred. Would you like to send an error report?'
|
||||
),
|
||||
'strChangeReportSettings' => __('Change report settings'),
|
||||
'strShowReportDetails' => __('Show report details'),
|
||||
'strTimeOutError' => __(
|
||||
'Your export is incomplete, due to a low execution time limit at the PHP level!'
|
||||
),
|
||||
|
||||
'strTooManyInputs' => __(
|
||||
'Warning: a form on this page has more than %d fields. On submission, '
|
||||
. "some of the fields might be ignored, due to PHP's "
|
||||
. 'max_input_vars configuration.'
|
||||
),
|
||||
|
||||
'phpErrorsFound' => '<div class="alert alert-danger" role="alert">'
|
||||
. __('Some errors have been detected on the server!')
|
||||
. '<br>'
|
||||
. __('Please look at the bottom of this window.')
|
||||
. '<div>'
|
||||
. '<input id="pma_ignore_errors_popup" type="submit" value="'
|
||||
. __('Ignore')
|
||||
. '" class="btn btn-secondary floatright message_errors_found">'
|
||||
. '<input id="pma_ignore_all_errors_popup" type="submit" value="'
|
||||
. __('Ignore All')
|
||||
. '" class="btn btn-secondary floatright message_errors_found">'
|
||||
. '</div></div>',
|
||||
|
||||
'phpErrorsBeingSubmitted' => '<div class="alert alert-danger" role="alert">'
|
||||
. __('Some errors have been detected on the server!')
|
||||
. '<br>'
|
||||
. __(
|
||||
'As per your settings, they are being submitted currently, please be '
|
||||
. 'patient.'
|
||||
)
|
||||
. '<br>'
|
||||
. '<img src="'
|
||||
. $ajaxClockSmallGifPath
|
||||
. '" width="16" height="16" alt="ajax clock">'
|
||||
. '</div>',
|
||||
'strCopyQueryButtonSuccess' => __('Successfully copied!'),
|
||||
'strCopyQueryButtonFailure' => __('Copying failed!'),
|
||||
|
||||
// For console
|
||||
'strConsoleRequeryConfirm' => __('Execute this query again?'),
|
||||
'strConsoleDeleteBookmarkConfirm' => __('Do you really want to delete this bookmark?'),
|
||||
'strConsoleDebugError' => __('Some error occurred while getting SQL debug info.'),
|
||||
'strConsoleDebugSummary' => __('%s queries executed %s times in %s seconds.'),
|
||||
'strConsoleDebugArgsSummary' => __('%s argument(s) passed'),
|
||||
'strConsoleDebugShowArgs' => __('Show arguments'),
|
||||
'strConsoleDebugHideArgs' => __('Hide arguments'),
|
||||
'strConsoleDebugTimeTaken' => __('Time taken:'),
|
||||
'strNoLocalStorage' => __(
|
||||
'There was a problem accessing your browser storage, some features may not'
|
||||
. ' work properly for you. It is likely that the browser doesn\'t support storage'
|
||||
. ' or the quota limit has been reached. In Firefox, corrupted storage can also'
|
||||
. ' cause such a problem, clearing your "Offline Website Data" might help. In Safari,'
|
||||
. ' such problem is commonly caused by "Private Mode Browsing".'
|
||||
),
|
||||
// For modals in /database/structure
|
||||
'strCopyTablesTo' => __('Copy tables to'),
|
||||
'strAddPrefix' => __('Add table prefix'),
|
||||
'strReplacePrefix' => __('Replace table with prefix'),
|
||||
'strCopyPrefix' => __('Copy table with prefix'),
|
||||
|
||||
/* For password strength simulation */
|
||||
'strExtrWeak' => __('Extremely weak'),
|
||||
'strVeryWeak' => __('Very weak'),
|
||||
'strWeak' => __('Weak'),
|
||||
'strGood' => __('Good'),
|
||||
'strStrong' => __('Strong'),
|
||||
|
||||
/* U2F errors */
|
||||
// l10n: error code 5 (from U2F API)
|
||||
'strU2FTimeout' => _pgettext('U2F error', 'Timed out waiting for security key activation.'),
|
||||
// l10n: error code 2 (from U2F API)
|
||||
'strU2FBadRequest' => _pgettext('U2F error', 'Invalid request sent to security key.'),
|
||||
// l10n: unknown error code (from U2F API)
|
||||
'strU2FUnknown' => _pgettext('U2F error', 'Unknown security key error.'),
|
||||
// l10n: error code 3 (from U2F API)
|
||||
'strU2FInvalidClient' => _pgettext('U2F error', 'Client does not support security key.'),
|
||||
// l10n: error code 4 (from U2F API) on register
|
||||
'strU2FErrorRegister' => _pgettext('U2F error', 'Failed security key activation.'),
|
||||
// l10n: error code 4 (from U2F API) on authanticate
|
||||
'strU2FErrorAuthenticate' => _pgettext('U2F error', 'Invalid security key.'),
|
||||
|
||||
/* Designer */
|
||||
'strTableAlreadyExists' => _pgettext(
|
||||
'The table already exists in the designer and can not be added once more.',
|
||||
'Table %s already exists!'
|
||||
),
|
||||
'strHide' => __('Hide'),
|
||||
'strShow' => __('Show'),
|
||||
'strStructure' => __('Structure'),
|
||||
];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
/**
|
||||
* Simple script to set correct charset for the license
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers;
|
||||
|
||||
use function is_readable;
|
||||
use function printf;
|
||||
use function readfile;
|
||||
|
||||
/**
|
||||
* Simple script to set correct charset for the license
|
||||
*/
|
||||
class LicenseController extends AbstractController
|
||||
{
|
||||
public function index(): void
|
||||
{
|
||||
$this->response->disable();
|
||||
$this->response->header('Content-type: text/plain; charset=utf-8');
|
||||
|
||||
$filename = LICENSE_FILE;
|
||||
|
||||
// Check if the file is available, some distributions remove these.
|
||||
if (@is_readable($filename)) {
|
||||
readfile($filename);
|
||||
} else {
|
||||
printf(
|
||||
__(
|
||||
'The %s file is not available on this system, please visit ' .
|
||||
'%s for more information.'
|
||||
),
|
||||
$filename,
|
||||
'https://www.phpmyadmin.net/'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
/**
|
||||
* Represents the interface between the linter and the query editor.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers;
|
||||
|
||||
use PhpMyAdmin\Core;
|
||||
use PhpMyAdmin\Linter;
|
||||
use function json_encode;
|
||||
|
||||
/**
|
||||
* Represents the interface between the linter and the query editor.
|
||||
*/
|
||||
class LintController extends AbstractController
|
||||
{
|
||||
public function index(): void
|
||||
{
|
||||
$params = [
|
||||
'sql_query' => $_POST['sql_query'] ?? null,
|
||||
'options' => $_POST['options'] ?? null,
|
||||
];
|
||||
|
||||
/**
|
||||
* The SQL query to be analyzed.
|
||||
*
|
||||
* This does not need to be checked again XSS or MySQL injections because it is
|
||||
* never executed, just parsed.
|
||||
*
|
||||
* The client, which will receive the JSON response will decode the message and
|
||||
* and any HTML fragments that are displayed to the user will be encoded anyway.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
$sqlQuery = ! empty($params['sql_query']) ? $params['sql_query'] : '';
|
||||
|
||||
$this->response->setAjax(true);
|
||||
|
||||
// Disabling standard response.
|
||||
$this->response->disable();
|
||||
|
||||
Core::headerJSON();
|
||||
|
||||
if (! empty($params['options'])) {
|
||||
$options = $params['options'];
|
||||
|
||||
if (! empty($options['routine_editor'])) {
|
||||
$sqlQuery = 'CREATE PROCEDURE `a`() ' . $sqlQuery;
|
||||
} elseif (! empty($options['trigger_editor'])) {
|
||||
$sqlQuery = 'CREATE TRIGGER `a` AFTER INSERT ON `b` FOR EACH ROW ' . $sqlQuery;
|
||||
} elseif (! empty($options['event_editor'])) {
|
||||
$sqlQuery = 'CREATE EVENT `a` ON SCHEDULE EVERY MINUTE DO ' . $sqlQuery;
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode(Linter::lint($sqlQuery));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers;
|
||||
|
||||
use PhpMyAdmin\Core;
|
||||
|
||||
class LogoutController
|
||||
{
|
||||
public function index(): void
|
||||
{
|
||||
global $auth_plugin, $token_mismatch;
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST' || $token_mismatch) {
|
||||
Core::sendHeaderLocation('./index.php?route=/');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$auth_plugin->logOut();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers;
|
||||
|
||||
use PhpMyAdmin\Config\PageSettings;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\Navigation\Navigation;
|
||||
use PhpMyAdmin\Relation;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Utils\SessionCache;
|
||||
|
||||
/**
|
||||
* The navigation panel
|
||||
*
|
||||
* Displays server, database and table selection tree.
|
||||
*/
|
||||
class NavigationController extends AbstractController
|
||||
{
|
||||
/** @var Navigation */
|
||||
private $navigation;
|
||||
|
||||
/** @var Relation */
|
||||
private $relation;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
*/
|
||||
public function __construct(
|
||||
$response,
|
||||
Template $template,
|
||||
Navigation $navigation,
|
||||
Relation $relation
|
||||
) {
|
||||
parent::__construct($response, $template);
|
||||
$this->navigation = $navigation;
|
||||
$this->relation = $relation;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
if (! $this->response->isAjax()) {
|
||||
$this->response->addHTML(
|
||||
Message::error(
|
||||
__('Fatal error: The navigation can only be accessed via AJAX')
|
||||
)->getDisplay()
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($_POST['getNaviSettings']) && $_POST['getNaviSettings']) {
|
||||
$pageSettings = new PageSettings('Navi', 'pma_navigation_settings');
|
||||
$this->response->addHTML($pageSettings->getErrorHTML());
|
||||
$this->response->addJSON('message', $pageSettings->getHTML());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($_POST['reload'])) {
|
||||
SessionCache::set('dbs_to_test', false);// Empty database list cache, see #14252
|
||||
}
|
||||
|
||||
$cfgRelation = $this->relation->getRelationsParam();
|
||||
if ($cfgRelation['navwork']) {
|
||||
if (isset($_POST['hideNavItem'])) {
|
||||
if (! empty($_POST['itemName'])
|
||||
&& ! empty($_POST['itemType'])
|
||||
&& ! empty($_POST['dbName'])
|
||||
) {
|
||||
$this->navigation->hideNavigationItem(
|
||||
$_POST['itemName'],
|
||||
$_POST['itemType'],
|
||||
$_POST['dbName'],
|
||||
(! empty($_POST['tableName']) ? $_POST['tableName'] : null)
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($_POST['unhideNavItem'])) {
|
||||
if (! empty($_POST['itemName'])
|
||||
&& ! empty($_POST['itemType'])
|
||||
&& ! empty($_POST['dbName'])
|
||||
) {
|
||||
$this->navigation->unhideNavigationItem(
|
||||
$_POST['itemName'],
|
||||
$_POST['itemType'],
|
||||
$_POST['dbName'],
|
||||
(! empty($_POST['tableName']) ? $_POST['tableName'] : null)
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($_POST['showUnhideDialog'])) {
|
||||
if (! empty($_POST['dbName'])) {
|
||||
$this->response->addJSON(
|
||||
'message',
|
||||
$this->navigation->getItemUnhideDialog($_POST['dbName'])
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$this->response->addJSON('message', $this->navigation->getDisplay());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,163 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers;
|
||||
|
||||
use PhpMyAdmin\Core;
|
||||
use PhpMyAdmin\Normalization;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use function intval;
|
||||
use function json_decode;
|
||||
use function json_encode;
|
||||
use function min;
|
||||
|
||||
/**
|
||||
* Normalization process (temporarily specific to 1NF).
|
||||
*/
|
||||
class NormalizationController extends AbstractController
|
||||
{
|
||||
/** @var Normalization */
|
||||
private $normalization;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
*/
|
||||
public function __construct($response, Template $template, Normalization $normalization)
|
||||
{
|
||||
parent::__construct($response, $template);
|
||||
$this->normalization = $normalization;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $db, $table;
|
||||
|
||||
if (isset($_POST['getColumns'])) {
|
||||
$html = '<option selected disabled>' . __('Select one…') . '</option>'
|
||||
. '<option value="no_such_col">' . __('No such column') . '</option>';
|
||||
//get column whose datatype falls under string category
|
||||
$html .= $this->normalization->getHtmlForColumnsList(
|
||||
$db,
|
||||
$table,
|
||||
_pgettext('string types', 'String')
|
||||
);
|
||||
echo $html;
|
||||
|
||||
return;
|
||||
}
|
||||
if (isset($_POST['splitColumn'])) {
|
||||
$num_fields = min(4096, intval($_POST['numFields']));
|
||||
$html = $this->normalization->getHtmlForCreateNewColumn($num_fields, $db, $table);
|
||||
$html .= Url::getHiddenInputs($db, $table);
|
||||
echo $html;
|
||||
|
||||
return;
|
||||
}
|
||||
if (isset($_POST['addNewPrimary'])) {
|
||||
$num_fields = 1;
|
||||
$columnMeta = [
|
||||
'Field' => $table . '_id',
|
||||
'Extra' => 'auto_increment',
|
||||
];
|
||||
$html = $this->normalization->getHtmlForCreateNewColumn(
|
||||
$num_fields,
|
||||
$db,
|
||||
$table,
|
||||
$columnMeta
|
||||
);
|
||||
$html .= Url::getHiddenInputs($db, $table);
|
||||
echo $html;
|
||||
|
||||
return;
|
||||
}
|
||||
if (isset($_POST['findPdl'])) {
|
||||
$html = $this->normalization->findPartialDependencies($table, $db);
|
||||
echo $html;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($_POST['getNewTables2NF'])) {
|
||||
$partialDependencies = json_decode($_POST['pd'], true);
|
||||
$html = $this->normalization->getHtmlForNewTables2NF($partialDependencies, $table);
|
||||
echo $html;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($_POST['getNewTables3NF'])) {
|
||||
$dependencies = json_decode($_POST['pd']);
|
||||
$tables = json_decode($_POST['tables'], true);
|
||||
$newTables = $this->normalization->getHtmlForNewTables3NF($dependencies, $tables, $db);
|
||||
$this->response->disable();
|
||||
Core::headerJSON();
|
||||
echo json_encode($newTables);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->addScriptFiles(['normalization.js', 'vendor/jquery/jquery.uitablefilter.js']);
|
||||
|
||||
$normalForm = '1nf';
|
||||
if (Core::isValid($_POST['normalizeTo'], ['1nf', '2nf', '3nf'])) {
|
||||
$normalForm = $_POST['normalizeTo'];
|
||||
}
|
||||
if (isset($_POST['createNewTables2NF'])) {
|
||||
$partialDependencies = json_decode($_POST['pd'], true);
|
||||
$tablesName = json_decode($_POST['newTablesName']);
|
||||
$res = $this->normalization->createNewTablesFor2NF($partialDependencies, $tablesName, $table, $db);
|
||||
$this->response->addJSON($res);
|
||||
|
||||
return;
|
||||
}
|
||||
if (isset($_POST['createNewTables3NF'])) {
|
||||
$newtables = json_decode($_POST['newTables'], true);
|
||||
$res = $this->normalization->createNewTablesFor3NF($newtables, $db);
|
||||
$this->response->addJSON($res);
|
||||
|
||||
return;
|
||||
}
|
||||
if (isset($_POST['repeatingColumns'])) {
|
||||
$repeatingColumns = $_POST['repeatingColumns'];
|
||||
$newTable = $_POST['newTable'];
|
||||
$newColumn = $_POST['newColumn'];
|
||||
$primary_columns = $_POST['primary_columns'];
|
||||
$res = $this->normalization->moveRepeatingGroup(
|
||||
$repeatingColumns,
|
||||
$primary_columns,
|
||||
$newTable,
|
||||
$newColumn,
|
||||
$table,
|
||||
$db
|
||||
);
|
||||
$this->response->addJSON($res);
|
||||
|
||||
return;
|
||||
}
|
||||
if (isset($_POST['step1'])) {
|
||||
$html = $this->normalization->getHtmlFor1NFStep1($db, $table, $normalForm);
|
||||
$this->response->addHTML($html);
|
||||
} elseif (isset($_POST['step2'])) {
|
||||
$res = $this->normalization->getHtmlContentsFor1NFStep2($db, $table);
|
||||
$this->response->addJSON($res);
|
||||
} elseif (isset($_POST['step3'])) {
|
||||
$res = $this->normalization->getHtmlContentsFor1NFStep3($db, $table);
|
||||
$this->response->addJSON($res);
|
||||
} elseif (isset($_POST['step4'])) {
|
||||
$res = $this->normalization->getHtmlContentsFor1NFStep4($db, $table);
|
||||
$this->response->addJSON($res);
|
||||
} elseif (isset($_POST['step']) && $_POST['step'] == '2.1') {
|
||||
$res = $this->normalization->getHtmlFor2NFstep1($db, $table);
|
||||
$this->response->addJSON($res);
|
||||
} elseif (isset($_POST['step']) && $_POST['step'] == '3.1') {
|
||||
$tables = $_POST['tables'];
|
||||
$res = $this->normalization->getHtmlFor3NFstep1($db, $tables);
|
||||
$this->response->addJSON($res);
|
||||
} else {
|
||||
$this->response->addHTML($this->normalization->getHtmlForNormalizeTable());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
/**
|
||||
* phpinfo() wrapper to allow displaying only when configured to do so.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers;
|
||||
|
||||
use const INFO_CONFIGURATION;
|
||||
use const INFO_GENERAL;
|
||||
use const INFO_MODULES;
|
||||
use function phpinfo;
|
||||
|
||||
/**
|
||||
* phpinfo() wrapper to allow displaying only when configured to do so.
|
||||
*/
|
||||
class PhpInfoController extends AbstractController
|
||||
{
|
||||
public function index(): void
|
||||
{
|
||||
global $cfg;
|
||||
|
||||
$this->response->disable();
|
||||
$this->response->getHeader()->sendHttpHeaders();
|
||||
|
||||
if (! $cfg['ShowPhpInfo']) {
|
||||
return;
|
||||
}
|
||||
|
||||
phpinfo(INFO_GENERAL | INFO_CONFIGURATION | INFO_MODULES);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Preferences;
|
||||
|
||||
use PhpMyAdmin\Config\ConfigFile;
|
||||
use PhpMyAdmin\Config\Forms\User\ExportForm;
|
||||
use PhpMyAdmin\Controllers\AbstractController;
|
||||
use PhpMyAdmin\Core;
|
||||
use PhpMyAdmin\Relation;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\TwoFactor;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\UserPreferences;
|
||||
use function define;
|
||||
use function ltrim;
|
||||
|
||||
class ExportController extends AbstractController
|
||||
{
|
||||
/** @var UserPreferences */
|
||||
private $userPreferences;
|
||||
|
||||
/** @var Relation */
|
||||
private $relation;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
*/
|
||||
public function __construct(
|
||||
$response,
|
||||
Template $template,
|
||||
UserPreferences $userPreferences,
|
||||
Relation $relation
|
||||
) {
|
||||
parent::__construct($response, $template);
|
||||
$this->userPreferences = $userPreferences;
|
||||
$this->relation = $relation;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $cfg, $cf, $error, $tabHash, $hash;
|
||||
global $server, $PMA_Config, $route;
|
||||
|
||||
$cf = new ConfigFile($PMA_Config->baseSettings);
|
||||
$this->userPreferences->pageInit($cf);
|
||||
|
||||
$formDisplay = new ExportForm($cf, 1);
|
||||
|
||||
if (isset($_POST['revert'])) {
|
||||
// revert erroneous fields to their default values
|
||||
$formDisplay->fixErrors();
|
||||
Core::sendHeaderLocation('./index.php?route=/preferences/export');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$error = null;
|
||||
if ($formDisplay->process(false) && ! $formDisplay->hasErrors()) {
|
||||
// Load 2FA settings
|
||||
$twoFactor = new TwoFactor($cfg['Server']['user']);
|
||||
// save settings
|
||||
$result = $this->userPreferences->save($cf->getConfigArray());
|
||||
// save back the 2FA setting only
|
||||
$twoFactor->save();
|
||||
if ($result === true) {
|
||||
// reload config
|
||||
$PMA_Config->loadUserPreferences();
|
||||
$tabHash = $_POST['tab_hash'] ?? null;
|
||||
$hash = ltrim($tabHash, '#');
|
||||
$this->userPreferences->redirect(
|
||||
'index.php?route=/preferences/export',
|
||||
null,
|
||||
$hash
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$error = $result;
|
||||
}
|
||||
|
||||
$this->addScriptFiles(['config.js']);
|
||||
|
||||
$cfgRelation = $this->relation->getRelationsParam();
|
||||
|
||||
$this->render('preferences/header', [
|
||||
'route' => $route,
|
||||
'is_saved' => ! empty($_GET['saved']),
|
||||
'has_config_storage' => $cfgRelation['userconfigwork'],
|
||||
]);
|
||||
|
||||
if ($formDisplay->hasErrors()) {
|
||||
$formErrors = $formDisplay->displayErrors();
|
||||
}
|
||||
|
||||
$this->render('preferences/forms/main', [
|
||||
'error' => $error ? $error->getDisplay() : '',
|
||||
'has_errors' => $formDisplay->hasErrors(),
|
||||
'errors' => $formErrors ?? null,
|
||||
'form' => $formDisplay->getDisplay(
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
Url::getFromRoute('/preferences/export'),
|
||||
['server' => $server]
|
||||
),
|
||||
]);
|
||||
|
||||
if ($this->response->isAjax()) {
|
||||
$this->response->addJSON('disableNaviSettings', true);
|
||||
} else {
|
||||
define('PMA_DISABLE_NAVI_SETTINGS', true);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Preferences;
|
||||
|
||||
use PhpMyAdmin\Config\ConfigFile;
|
||||
use PhpMyAdmin\Config\Forms\User\FeaturesForm;
|
||||
use PhpMyAdmin\Controllers\AbstractController;
|
||||
use PhpMyAdmin\Core;
|
||||
use PhpMyAdmin\Relation;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\TwoFactor;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\UserPreferences;
|
||||
use function define;
|
||||
use function ltrim;
|
||||
|
||||
class FeaturesController extends AbstractController
|
||||
{
|
||||
/** @var UserPreferences */
|
||||
private $userPreferences;
|
||||
|
||||
/** @var Relation */
|
||||
private $relation;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
*/
|
||||
public function __construct(
|
||||
$response,
|
||||
Template $template,
|
||||
UserPreferences $userPreferences,
|
||||
Relation $relation
|
||||
) {
|
||||
parent::__construct($response, $template);
|
||||
$this->userPreferences = $userPreferences;
|
||||
$this->relation = $relation;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $cfg, $cf, $error, $tabHash, $hash;
|
||||
global $server, $PMA_Config, $route;
|
||||
|
||||
$cf = new ConfigFile($PMA_Config->baseSettings);
|
||||
$this->userPreferences->pageInit($cf);
|
||||
|
||||
$formDisplay = new FeaturesForm($cf, 1);
|
||||
|
||||
if (isset($_POST['revert'])) {
|
||||
// revert erroneous fields to their default values
|
||||
$formDisplay->fixErrors();
|
||||
Core::sendHeaderLocation('./index.php?route=/preferences/features');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$error = null;
|
||||
if ($formDisplay->process(false) && ! $formDisplay->hasErrors()) {
|
||||
// Load 2FA settings
|
||||
$twoFactor = new TwoFactor($cfg['Server']['user']);
|
||||
// save settings
|
||||
$result = $this->userPreferences->save($cf->getConfigArray());
|
||||
// save back the 2FA setting only
|
||||
$twoFactor->save();
|
||||
if ($result === true) {
|
||||
// reload config
|
||||
$PMA_Config->loadUserPreferences();
|
||||
$tabHash = $_POST['tab_hash'] ?? null;
|
||||
$hash = ltrim($tabHash, '#');
|
||||
$this->userPreferences->redirect(
|
||||
'index.php?route=/preferences/features',
|
||||
null,
|
||||
$hash
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$error = $result;
|
||||
}
|
||||
|
||||
$this->addScriptFiles(['config.js']);
|
||||
|
||||
$cfgRelation = $this->relation->getRelationsParam();
|
||||
|
||||
$this->render('preferences/header', [
|
||||
'route' => $route,
|
||||
'is_saved' => ! empty($_GET['saved']),
|
||||
'has_config_storage' => $cfgRelation['userconfigwork'],
|
||||
]);
|
||||
|
||||
if ($formDisplay->hasErrors()) {
|
||||
$formErrors = $formDisplay->displayErrors();
|
||||
}
|
||||
|
||||
$this->render('preferences/forms/main', [
|
||||
'error' => $error ? $error->getDisplay() : '',
|
||||
'has_errors' => $formDisplay->hasErrors(),
|
||||
'errors' => $formErrors ?? null,
|
||||
'form' => $formDisplay->getDisplay(
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
Url::getFromRoute('/preferences/features'),
|
||||
['server' => $server]
|
||||
),
|
||||
]);
|
||||
|
||||
if ($this->response->isAjax()) {
|
||||
$this->response->addJSON('disableNaviSettings', true);
|
||||
} else {
|
||||
define('PMA_DISABLE_NAVI_SETTINGS', true);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Preferences;
|
||||
|
||||
use PhpMyAdmin\Config\ConfigFile;
|
||||
use PhpMyAdmin\Config\Forms\User\ImportForm;
|
||||
use PhpMyAdmin\Controllers\AbstractController;
|
||||
use PhpMyAdmin\Core;
|
||||
use PhpMyAdmin\Relation;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\TwoFactor;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\UserPreferences;
|
||||
use function define;
|
||||
use function ltrim;
|
||||
|
||||
class ImportController extends AbstractController
|
||||
{
|
||||
/** @var UserPreferences */
|
||||
private $userPreferences;
|
||||
|
||||
/** @var Relation */
|
||||
private $relation;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
*/
|
||||
public function __construct(
|
||||
$response,
|
||||
Template $template,
|
||||
UserPreferences $userPreferences,
|
||||
Relation $relation
|
||||
) {
|
||||
parent::__construct($response, $template);
|
||||
$this->userPreferences = $userPreferences;
|
||||
$this->relation = $relation;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $cfg, $cf, $error, $tabHash, $hash;
|
||||
global $server, $PMA_Config, $route;
|
||||
|
||||
$cf = new ConfigFile($PMA_Config->baseSettings);
|
||||
$this->userPreferences->pageInit($cf);
|
||||
|
||||
$formDisplay = new ImportForm($cf, 1);
|
||||
|
||||
if (isset($_POST['revert'])) {
|
||||
// revert erroneous fields to their default values
|
||||
$formDisplay->fixErrors();
|
||||
Core::sendHeaderLocation('./index.php?route=/preferences/import');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$error = null;
|
||||
if ($formDisplay->process(false) && ! $formDisplay->hasErrors()) {
|
||||
// Load 2FA settings
|
||||
$twoFactor = new TwoFactor($cfg['Server']['user']);
|
||||
// save settings
|
||||
$result = $this->userPreferences->save($cf->getConfigArray());
|
||||
// save back the 2FA setting only
|
||||
$twoFactor->save();
|
||||
if ($result === true) {
|
||||
// reload config
|
||||
$PMA_Config->loadUserPreferences();
|
||||
$tabHash = $_POST['tab_hash'] ?? null;
|
||||
$hash = ltrim($tabHash, '#');
|
||||
$this->userPreferences->redirect(
|
||||
'index.php?route=/preferences/import',
|
||||
null,
|
||||
$hash
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$error = $result;
|
||||
}
|
||||
|
||||
$this->addScriptFiles(['config.js']);
|
||||
|
||||
$cfgRelation = $this->relation->getRelationsParam();
|
||||
|
||||
$this->render('preferences/header', [
|
||||
'route' => $route,
|
||||
'is_saved' => ! empty($_GET['saved']),
|
||||
'has_config_storage' => $cfgRelation['userconfigwork'],
|
||||
]);
|
||||
|
||||
if ($formDisplay->hasErrors()) {
|
||||
$formErrors = $formDisplay->displayErrors();
|
||||
}
|
||||
|
||||
$this->render('preferences/forms/main', [
|
||||
'error' => $error ? $error->getDisplay() : '',
|
||||
'has_errors' => $formDisplay->hasErrors(),
|
||||
'errors' => $formErrors ?? null,
|
||||
'form' => $formDisplay->getDisplay(
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
Url::getFromRoute('/preferences/import'),
|
||||
['server' => $server]
|
||||
),
|
||||
]);
|
||||
|
||||
if ($this->response->isAjax()) {
|
||||
$this->response->addJSON('disableNaviSettings', true);
|
||||
} else {
|
||||
define('PMA_DISABLE_NAVI_SETTINGS', true);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Preferences;
|
||||
|
||||
use PhpMyAdmin\Config\ConfigFile;
|
||||
use PhpMyAdmin\Config\Forms\User\MainForm;
|
||||
use PhpMyAdmin\Controllers\AbstractController;
|
||||
use PhpMyAdmin\Core;
|
||||
use PhpMyAdmin\Relation;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\TwoFactor;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\UserPreferences;
|
||||
use function define;
|
||||
use function ltrim;
|
||||
|
||||
class MainPanelController extends AbstractController
|
||||
{
|
||||
/** @var UserPreferences */
|
||||
private $userPreferences;
|
||||
|
||||
/** @var Relation */
|
||||
private $relation;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
*/
|
||||
public function __construct(
|
||||
$response,
|
||||
Template $template,
|
||||
UserPreferences $userPreferences,
|
||||
Relation $relation
|
||||
) {
|
||||
parent::__construct($response, $template);
|
||||
$this->userPreferences = $userPreferences;
|
||||
$this->relation = $relation;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $cfg, $cf, $error, $tabHash, $hash;
|
||||
global $server, $PMA_Config, $route;
|
||||
|
||||
$cf = new ConfigFile($PMA_Config->baseSettings);
|
||||
$this->userPreferences->pageInit($cf);
|
||||
|
||||
$formDisplay = new MainForm($cf, 1);
|
||||
|
||||
if (isset($_POST['revert'])) {
|
||||
// revert erroneous fields to their default values
|
||||
$formDisplay->fixErrors();
|
||||
Core::sendHeaderLocation('./index.php?route=/preferences/main-panel');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$error = null;
|
||||
if ($formDisplay->process(false) && ! $formDisplay->hasErrors()) {
|
||||
// Load 2FA settings
|
||||
$twoFactor = new TwoFactor($cfg['Server']['user']);
|
||||
// save settings
|
||||
$result = $this->userPreferences->save($cf->getConfigArray());
|
||||
// save back the 2FA setting only
|
||||
$twoFactor->save();
|
||||
if ($result === true) {
|
||||
// reload config
|
||||
$PMA_Config->loadUserPreferences();
|
||||
$tabHash = $_POST['tab_hash'] ?? null;
|
||||
$hash = ltrim($tabHash, '#');
|
||||
$this->userPreferences->redirect(
|
||||
'index.php?route=/preferences/main-panel',
|
||||
null,
|
||||
$hash
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$error = $result;
|
||||
}
|
||||
|
||||
$this->addScriptFiles(['config.js']);
|
||||
|
||||
$cfgRelation = $this->relation->getRelationsParam();
|
||||
|
||||
$this->render('preferences/header', [
|
||||
'route' => $route,
|
||||
'is_saved' => ! empty($_GET['saved']),
|
||||
'has_config_storage' => $cfgRelation['userconfigwork'],
|
||||
]);
|
||||
|
||||
if ($formDisplay->hasErrors()) {
|
||||
$formErrors = $formDisplay->displayErrors();
|
||||
}
|
||||
|
||||
$this->render('preferences/forms/main', [
|
||||
'error' => $error ? $error->getDisplay() : '',
|
||||
'has_errors' => $formDisplay->hasErrors(),
|
||||
'errors' => $formErrors ?? null,
|
||||
'form' => $formDisplay->getDisplay(
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
Url::getFromRoute('/preferences/main-panel'),
|
||||
['server' => $server]
|
||||
),
|
||||
]);
|
||||
|
||||
if ($this->response->isAjax()) {
|
||||
$this->response->addJSON('disableNaviSettings', true);
|
||||
} else {
|
||||
define('PMA_DISABLE_NAVI_SETTINGS', true);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,266 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Preferences;
|
||||
|
||||
use PhpMyAdmin\Config\ConfigFile;
|
||||
use PhpMyAdmin\Config\Forms\User\UserFormList;
|
||||
use PhpMyAdmin\Controllers\AbstractController;
|
||||
use PhpMyAdmin\Core;
|
||||
use PhpMyAdmin\File;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\Relation;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\ThemeManager;
|
||||
use PhpMyAdmin\UserPreferences;
|
||||
use PhpMyAdmin\Util;
|
||||
use const JSON_PRETTY_PRINT;
|
||||
use const PHP_URL_PATH;
|
||||
use const UPLOAD_ERR_OK;
|
||||
use function array_merge;
|
||||
use function define;
|
||||
use function file_exists;
|
||||
use function is_array;
|
||||
use function is_uploaded_file;
|
||||
use function json_decode;
|
||||
use function json_encode;
|
||||
use function mb_strpos;
|
||||
use function mb_substr;
|
||||
use function parse_url;
|
||||
use function str_replace;
|
||||
use function urlencode;
|
||||
use function var_export;
|
||||
|
||||
/**
|
||||
* User preferences management page.
|
||||
*/
|
||||
class ManageController extends AbstractController
|
||||
{
|
||||
/** @var UserPreferences */
|
||||
private $userPreferences;
|
||||
|
||||
/** @var Relation */
|
||||
private $relation;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
*/
|
||||
public function __construct(
|
||||
$response,
|
||||
Template $template,
|
||||
UserPreferences $userPreferences,
|
||||
Relation $relation
|
||||
) {
|
||||
parent::__construct($response, $template);
|
||||
$this->userPreferences = $userPreferences;
|
||||
$this->relation = $relation;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $cf, $error, $filename, $json, $PMA_Config, $lang, $max_upload_size;
|
||||
global $new_config, $config, $return_url, $form_display, $all_ok, $params, $query, $route;
|
||||
|
||||
$cf = new ConfigFile($PMA_Config->baseSettings);
|
||||
$this->userPreferences->pageInit($cf);
|
||||
|
||||
$error = '';
|
||||
if (isset($_POST['submit_export'], $_POST['export_type']) && $_POST['export_type'] === 'text_file') {
|
||||
// export to JSON file
|
||||
$this->response->disable();
|
||||
$filename = 'phpMyAdmin-config-' . urlencode(Core::getenv('HTTP_HOST')) . '.json';
|
||||
Core::downloadHeader($filename, 'application/json');
|
||||
$settings = $this->userPreferences->load();
|
||||
echo json_encode($settings['config_data'], JSON_PRETTY_PRINT);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($_POST['submit_export'], $_POST['export_type']) && $_POST['export_type'] === 'php_file') {
|
||||
// export to JSON file
|
||||
$this->response->disable();
|
||||
$filename = 'phpMyAdmin-config-' . urlencode(Core::getenv('HTTP_HOST')) . '.php';
|
||||
Core::downloadHeader($filename, 'application/php');
|
||||
$settings = $this->userPreferences->load();
|
||||
echo '/* ' . __('phpMyAdmin configuration snippet') . " */\n\n";
|
||||
echo '/* ' . __('Paste it to your config.inc.php') . " */\n\n";
|
||||
foreach ($settings['config_data'] as $key => $val) {
|
||||
echo '$cfg[\'' . str_replace('/', '\'][\'', $key) . '\'] = ';
|
||||
echo var_export($val, true) . ";\n";
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($_POST['submit_get_json'])) {
|
||||
$settings = $this->userPreferences->load();
|
||||
$this->response->addJSON('prefs', json_encode($settings['config_data']));
|
||||
$this->response->addJSON('mtime', $settings['mtime']);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($_POST['submit_import'])) {
|
||||
// load from JSON file
|
||||
$json = '';
|
||||
if (isset($_POST['import_type'], $_FILES['import_file'])
|
||||
&& $_POST['import_type'] === 'text_file'
|
||||
&& $_FILES['import_file']['error'] == UPLOAD_ERR_OK
|
||||
&& is_uploaded_file($_FILES['import_file']['tmp_name'])
|
||||
) {
|
||||
$importHandle = new File($_FILES['import_file']['tmp_name']);
|
||||
$importHandle->checkUploadedFile();
|
||||
if ($importHandle->isError()) {
|
||||
$error = $importHandle->getError();
|
||||
} else {
|
||||
// read JSON from uploaded file
|
||||
$json = $importHandle->getRawContent();
|
||||
}
|
||||
} else {
|
||||
// read from POST value (json)
|
||||
$json = $_POST['json'] ?? null;
|
||||
}
|
||||
|
||||
// hide header message
|
||||
$_SESSION['userprefs_autoload'] = true;
|
||||
|
||||
$config = json_decode($json, true);
|
||||
$return_url = $_POST['return_url'] ?? null;
|
||||
if (! is_array($config)) {
|
||||
if (! isset($error)) {
|
||||
$error = __('Could not import configuration');
|
||||
}
|
||||
} else {
|
||||
// sanitize input values: treat them as though
|
||||
// they came from HTTP POST request
|
||||
$form_display = new UserFormList($cf);
|
||||
$new_config = $cf->getFlatDefaultConfig();
|
||||
if (! empty($_POST['import_merge'])) {
|
||||
$new_config = array_merge($new_config, $cf->getConfigArray());
|
||||
}
|
||||
$new_config = array_merge($new_config, $config);
|
||||
$_POST_bak = $_POST;
|
||||
foreach ($new_config as $k => $v) {
|
||||
$_POST[str_replace('/', '-', (string) $k)] = $v;
|
||||
}
|
||||
$cf->resetConfigData();
|
||||
$all_ok = $form_display->process(true, false);
|
||||
$all_ok = $all_ok && ! $form_display->hasErrors();
|
||||
$_POST = $_POST_bak;
|
||||
|
||||
if (! $all_ok && isset($_POST['fix_errors'])) {
|
||||
$form_display->fixErrors();
|
||||
$all_ok = true;
|
||||
}
|
||||
if (! $all_ok) {
|
||||
// mimic original form and post json in a hidden field
|
||||
$cfgRelation = $this->relation->getRelationsParam();
|
||||
|
||||
echo $this->template->render('preferences/header', [
|
||||
'route' => $route,
|
||||
'is_saved' => ! empty($_GET['saved']),
|
||||
'has_config_storage' => $cfgRelation['userconfigwork'],
|
||||
]);
|
||||
|
||||
echo $this->template->render('preferences/manage/error', [
|
||||
'form_errors' => $form_display->displayErrors(),
|
||||
'json' => $json,
|
||||
'import_merge' => $_POST['import_merge'] ?? null,
|
||||
'return_url' => $return_url,
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// check for ThemeDefault
|
||||
$params = [];
|
||||
$tmanager = ThemeManager::getInstance();
|
||||
if (isset($config['ThemeDefault'])
|
||||
&& $tmanager->theme->getId() != $config['ThemeDefault']
|
||||
&& $tmanager->checkTheme($config['ThemeDefault'])
|
||||
) {
|
||||
$tmanager->setActiveTheme($config['ThemeDefault']);
|
||||
$tmanager->setThemeCookie();
|
||||
}
|
||||
if (isset($config['lang'])
|
||||
&& $config['lang'] != $lang
|
||||
) {
|
||||
$params['lang'] = $config['lang'];
|
||||
}
|
||||
|
||||
// save settings
|
||||
$result = $this->userPreferences->save($cf->getConfigArray());
|
||||
if ($result === true) {
|
||||
if ($return_url) {
|
||||
$query = Util::splitURLQuery($return_url);
|
||||
$return_url = parse_url($return_url, PHP_URL_PATH);
|
||||
|
||||
foreach ($query as $q) {
|
||||
$pos = mb_strpos($q, '=');
|
||||
$k = mb_substr($q, 0, (int) $pos);
|
||||
if ($k === 'token') {
|
||||
continue;
|
||||
}
|
||||
$params[$k] = mb_substr($q, $pos + 1);
|
||||
}
|
||||
} else {
|
||||
$return_url = 'index.php?route=/preferences/manage';
|
||||
}
|
||||
// reload config
|
||||
$PMA_Config->loadUserPreferences();
|
||||
$this->userPreferences->redirect($return_url ?? '', $params);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$error = $result;
|
||||
}
|
||||
} elseif (isset($_POST['submit_clear'])) {
|
||||
$result = $this->userPreferences->save([]);
|
||||
if ($result === true) {
|
||||
$params = [];
|
||||
$PMA_Config->removeCookie('pma_collaction_connection');
|
||||
$PMA_Config->removeCookie('pma_lang');
|
||||
$this->userPreferences->redirect('index.php?route=/preferences/manage', $params);
|
||||
|
||||
return;
|
||||
} else {
|
||||
$error = $result;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->addScriptFiles(['config.js']);
|
||||
|
||||
$cfgRelation = $this->relation->getRelationsParam();
|
||||
|
||||
echo $this->template->render('preferences/header', [
|
||||
'route' => $route,
|
||||
'is_saved' => ! empty($_GET['saved']),
|
||||
'has_config_storage' => $cfgRelation['userconfigwork'],
|
||||
]);
|
||||
|
||||
if ($error) {
|
||||
if (! $error instanceof Message) {
|
||||
$error = Message::error($error);
|
||||
}
|
||||
$error->getDisplay();
|
||||
}
|
||||
|
||||
echo $this->template->render('preferences/manage/main', [
|
||||
'error' => $error,
|
||||
'max_upload_size' => $max_upload_size,
|
||||
'exists_setup_and_not_exists_config' => @file_exists(ROOT_PATH . 'setup/index.php')
|
||||
&& ! @file_exists(CONFIG_FILE),
|
||||
]);
|
||||
|
||||
if ($this->response->isAjax()) {
|
||||
$this->response->addJSON('disableNaviSettings', true);
|
||||
} else {
|
||||
define('PMA_DISABLE_NAVI_SETTINGS', true);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Preferences;
|
||||
|
||||
use PhpMyAdmin\Config\ConfigFile;
|
||||
use PhpMyAdmin\Config\Forms\User\NaviForm;
|
||||
use PhpMyAdmin\Controllers\AbstractController;
|
||||
use PhpMyAdmin\Core;
|
||||
use PhpMyAdmin\Relation;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\TwoFactor;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\UserPreferences;
|
||||
use function define;
|
||||
use function ltrim;
|
||||
|
||||
class NavigationController extends AbstractController
|
||||
{
|
||||
/** @var UserPreferences */
|
||||
private $userPreferences;
|
||||
|
||||
/** @var Relation */
|
||||
private $relation;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
*/
|
||||
public function __construct(
|
||||
$response,
|
||||
Template $template,
|
||||
UserPreferences $userPreferences,
|
||||
Relation $relation
|
||||
) {
|
||||
parent::__construct($response, $template);
|
||||
$this->userPreferences = $userPreferences;
|
||||
$this->relation = $relation;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $cfg, $cf, $error, $tabHash, $hash;
|
||||
global $server, $PMA_Config, $route;
|
||||
|
||||
$cf = new ConfigFile($PMA_Config->baseSettings);
|
||||
$this->userPreferences->pageInit($cf);
|
||||
|
||||
$formDisplay = new NaviForm($cf, 1);
|
||||
|
||||
if (isset($_POST['revert'])) {
|
||||
// revert erroneous fields to their default values
|
||||
$formDisplay->fixErrors();
|
||||
Core::sendHeaderLocation('./index.php?route=/preferences/navigation');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$error = null;
|
||||
if ($formDisplay->process(false) && ! $formDisplay->hasErrors()) {
|
||||
// Load 2FA settings
|
||||
$twoFactor = new TwoFactor($cfg['Server']['user']);
|
||||
// save settings
|
||||
$result = $this->userPreferences->save($cf->getConfigArray());
|
||||
// save back the 2FA setting only
|
||||
$twoFactor->save();
|
||||
if ($result === true) {
|
||||
// reload config
|
||||
$PMA_Config->loadUserPreferences();
|
||||
$tabHash = $_POST['tab_hash'] ?? null;
|
||||
$hash = ltrim($tabHash, '#');
|
||||
$this->userPreferences->redirect(
|
||||
'index.php?route=/preferences/navigation',
|
||||
null,
|
||||
$hash
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$error = $result;
|
||||
}
|
||||
|
||||
$this->addScriptFiles(['config.js']);
|
||||
|
||||
$cfgRelation = $this->relation->getRelationsParam();
|
||||
|
||||
$this->render('preferences/header', [
|
||||
'route' => $route,
|
||||
'is_saved' => ! empty($_GET['saved']),
|
||||
'has_config_storage' => $cfgRelation['userconfigwork'],
|
||||
]);
|
||||
|
||||
if ($formDisplay->hasErrors()) {
|
||||
$formErrors = $formDisplay->displayErrors();
|
||||
}
|
||||
|
||||
$this->render('preferences/forms/main', [
|
||||
'error' => $error ? $error->getDisplay() : '',
|
||||
'has_errors' => $formDisplay->hasErrors(),
|
||||
'errors' => $formErrors ?? null,
|
||||
'form' => $formDisplay->getDisplay(
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
Url::getFromRoute('/preferences/navigation'),
|
||||
['server' => $server]
|
||||
),
|
||||
]);
|
||||
|
||||
if ($this->response->isAjax()) {
|
||||
$this->response->addJSON('disableNaviSettings', true);
|
||||
} else {
|
||||
define('PMA_DISABLE_NAVI_SETTINGS', true);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Preferences;
|
||||
|
||||
use PhpMyAdmin\Config\ConfigFile;
|
||||
use PhpMyAdmin\Config\Forms\User\SqlForm;
|
||||
use PhpMyAdmin\Controllers\AbstractController;
|
||||
use PhpMyAdmin\Core;
|
||||
use PhpMyAdmin\Relation;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\TwoFactor;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\UserPreferences;
|
||||
use function define;
|
||||
use function ltrim;
|
||||
|
||||
class SqlController extends AbstractController
|
||||
{
|
||||
/** @var UserPreferences */
|
||||
private $userPreferences;
|
||||
|
||||
/** @var Relation */
|
||||
private $relation;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
*/
|
||||
public function __construct(
|
||||
$response,
|
||||
Template $template,
|
||||
UserPreferences $userPreferences,
|
||||
Relation $relation
|
||||
) {
|
||||
parent::__construct($response, $template);
|
||||
$this->userPreferences = $userPreferences;
|
||||
$this->relation = $relation;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $cfg, $cf, $error, $tabHash, $hash;
|
||||
global $server, $PMA_Config, $route;
|
||||
|
||||
$cf = new ConfigFile($PMA_Config->baseSettings);
|
||||
$this->userPreferences->pageInit($cf);
|
||||
|
||||
$formDisplay = new SqlForm($cf, 1);
|
||||
|
||||
if (isset($_POST['revert'])) {
|
||||
// revert erroneous fields to their default values
|
||||
$formDisplay->fixErrors();
|
||||
Core::sendHeaderLocation('./index.php?route=/preferences/sql');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$error = null;
|
||||
if ($formDisplay->process(false) && ! $formDisplay->hasErrors()) {
|
||||
// Load 2FA settings
|
||||
$twoFactor = new TwoFactor($cfg['Server']['user']);
|
||||
// save settings
|
||||
$result = $this->userPreferences->save($cf->getConfigArray());
|
||||
// save back the 2FA setting only
|
||||
$twoFactor->save();
|
||||
if ($result === true) {
|
||||
// reload config
|
||||
$PMA_Config->loadUserPreferences();
|
||||
$tabHash = $_POST['tab_hash'] ?? null;
|
||||
$hash = ltrim($tabHash, '#');
|
||||
$this->userPreferences->redirect(
|
||||
'index.php?route=/preferences/sql',
|
||||
null,
|
||||
$hash
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$error = $result;
|
||||
}
|
||||
|
||||
$this->addScriptFiles(['config.js']);
|
||||
|
||||
$cfgRelation = $this->relation->getRelationsParam();
|
||||
|
||||
$this->render('preferences/header', [
|
||||
'route' => $route,
|
||||
'is_saved' => ! empty($_GET['saved']),
|
||||
'has_config_storage' => $cfgRelation['userconfigwork'],
|
||||
]);
|
||||
|
||||
if ($formDisplay->hasErrors()) {
|
||||
$formErrors = $formDisplay->displayErrors();
|
||||
}
|
||||
|
||||
$this->render('preferences/forms/main', [
|
||||
'error' => $error ? $error->getDisplay() : '',
|
||||
'has_errors' => $formDisplay->hasErrors(),
|
||||
'errors' => $formErrors ?? null,
|
||||
'form' => $formDisplay->getDisplay(
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
Url::getFromRoute('/preferences/sql'),
|
||||
['server' => $server]
|
||||
),
|
||||
]);
|
||||
|
||||
if ($this->response->isAjax()) {
|
||||
$this->response->addJSON('disableNaviSettings', true);
|
||||
} else {
|
||||
define('PMA_DISABLE_NAVI_SETTINGS', true);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Preferences;
|
||||
|
||||
use PhpMyAdmin\Controllers\AbstractController;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\Relation;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\TwoFactor;
|
||||
use function count;
|
||||
|
||||
class TwoFactorController extends AbstractController
|
||||
{
|
||||
/** @var Relation */
|
||||
private $relation;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
*/
|
||||
public function __construct($response, Template $template, Relation $relation)
|
||||
{
|
||||
parent::__construct($response, $template);
|
||||
$this->relation = $relation;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $cfg, $route;
|
||||
|
||||
$cfgRelation = $this->relation->getRelationsParam();
|
||||
|
||||
echo $this->template->render('preferences/header', [
|
||||
'route' => $route,
|
||||
'is_saved' => ! empty($_GET['saved']),
|
||||
'has_config_storage' => $cfgRelation['userconfigwork'],
|
||||
]);
|
||||
|
||||
$twoFactor = new TwoFactor($cfg['Server']['user']);
|
||||
|
||||
if (isset($_POST['2fa_remove'])) {
|
||||
if (! $twoFactor->check(true)) {
|
||||
echo $this->template->render('preferences/two_factor/confirm', [
|
||||
'form' => $twoFactor->render(),
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$twoFactor->configure('');
|
||||
echo Message::rawNotice(__('Two-factor authentication has been removed.'))->getDisplay();
|
||||
} elseif (isset($_POST['2fa_configure'])) {
|
||||
if (! $twoFactor->configure($_POST['2fa_configure'])) {
|
||||
echo $this->template->render('preferences/two_factor/configure', [
|
||||
'form' => $twoFactor->setup(),
|
||||
'configure' => $_POST['2fa_configure'],
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
echo Message::rawNotice(__('Two-factor authentication has been configured.'))->getDisplay();
|
||||
}
|
||||
|
||||
$backend = $twoFactor->getBackend();
|
||||
echo $this->template->render('preferences/two_factor/main', [
|
||||
'enabled' => $twoFactor->isWritable(),
|
||||
'num_backends' => count($twoFactor->getAvailable()),
|
||||
'backend_id' => $backend::$id,
|
||||
'backend_name' => $backend::getName(),
|
||||
'backend_description' => $backend::getDescription(),
|
||||
'backends' => $twoFactor->getAllBackends(),
|
||||
'missing' => $twoFactor->getMissingDeps(),
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers;
|
||||
|
||||
use PhpMyAdmin\Export;
|
||||
use PhpMyAdmin\Relation;
|
||||
use PhpMyAdmin\Util;
|
||||
|
||||
/**
|
||||
* Schema export handler
|
||||
*/
|
||||
class SchemaExportController
|
||||
{
|
||||
/** @var Export */
|
||||
private $export;
|
||||
|
||||
/** @var Relation */
|
||||
private $relation;
|
||||
|
||||
/**
|
||||
* @param Export $export A Export instance.
|
||||
* @param Relation $relation A Relation instance.
|
||||
*/
|
||||
public function __construct(Export $export, Relation $relation)
|
||||
{
|
||||
$this->export = $export;
|
||||
$this->relation = $relation;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $cfgRelation;
|
||||
|
||||
/**
|
||||
* get all variables needed for exporting relational schema
|
||||
* in $cfgRelation
|
||||
*/
|
||||
$cfgRelation = $this->relation->getRelationsParam();
|
||||
|
||||
if (! isset($_POST['export_type'])) {
|
||||
Util::checkParameters(['export_type']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Include the appropriate Schema Class depending on $export_type
|
||||
* default is PDF
|
||||
*/
|
||||
$this->export->processExportSchema($_POST['export_type']);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,149 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Server;
|
||||
|
||||
use PhpMyAdmin\Controllers\AbstractController;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Html\Generator;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
use function array_key_exists;
|
||||
|
||||
/**
|
||||
* Handles viewing binary logs
|
||||
*/
|
||||
class BinlogController extends AbstractController
|
||||
{
|
||||
/**
|
||||
* binary log files
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $binaryLogs;
|
||||
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template);
|
||||
$this->dbi = $dbi;
|
||||
|
||||
$this->binaryLogs = $this->dbi->fetchResult(
|
||||
'SHOW MASTER LOGS',
|
||||
'Log_name',
|
||||
null,
|
||||
DatabaseInterface::CONNECT_USER,
|
||||
DatabaseInterface::QUERY_STORE
|
||||
);
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $cfg, $PMA_Theme, $err_url;
|
||||
|
||||
$params = [
|
||||
'log' => $_POST['log'] ?? null,
|
||||
'pos' => $_POST['pos'] ?? null,
|
||||
'is_full_query' => $_POST['is_full_query'] ?? null,
|
||||
];
|
||||
$err_url = Url::getFromRoute('/');
|
||||
|
||||
if ($this->dbi->isSuperUser()) {
|
||||
$this->dbi->selectDb('mysql');
|
||||
}
|
||||
|
||||
$position = ! empty($params['pos']) ? (int) $params['pos'] : 0;
|
||||
|
||||
$urlParams = [];
|
||||
if (isset($params['log'])
|
||||
&& array_key_exists($params['log'], $this->binaryLogs)
|
||||
) {
|
||||
$urlParams['log'] = $params['log'];
|
||||
}
|
||||
|
||||
$isFullQuery = false;
|
||||
if (! empty($params['is_full_query'])) {
|
||||
$isFullQuery = true;
|
||||
$urlParams['is_full_query'] = 1;
|
||||
}
|
||||
|
||||
$sqlQuery = $this->getSqlQuery(
|
||||
$params['log'] ?? '',
|
||||
$position,
|
||||
(int) $cfg['MaxRows']
|
||||
);
|
||||
$result = $this->dbi->query($sqlQuery);
|
||||
|
||||
$numRows = 0;
|
||||
if (isset($result) && $result) {
|
||||
$numRows = $this->dbi->numRows($result);
|
||||
}
|
||||
|
||||
$previousParams = $urlParams;
|
||||
$fullQueriesParams = $urlParams;
|
||||
$nextParams = $urlParams;
|
||||
if ($position > 0) {
|
||||
$fullQueriesParams['pos'] = $position;
|
||||
if ($position > $cfg['MaxRows']) {
|
||||
$previousParams['pos'] = $position - $cfg['MaxRows'];
|
||||
}
|
||||
}
|
||||
$fullQueriesParams['is_full_query'] = 1;
|
||||
if ($isFullQuery) {
|
||||
unset($fullQueriesParams['is_full_query']);
|
||||
}
|
||||
if ($numRows >= $cfg['MaxRows']) {
|
||||
$nextParams['pos'] = $position + $cfg['MaxRows'];
|
||||
}
|
||||
|
||||
$values = [];
|
||||
while ($value = $this->dbi->fetchAssoc($result)) {
|
||||
$values[] = $value;
|
||||
}
|
||||
|
||||
$this->render('server/binlog/index', [
|
||||
'url_params' => $urlParams,
|
||||
'binary_logs' => $this->binaryLogs,
|
||||
'log' => $params['log'],
|
||||
'sql_message' => Generator::getMessage(Message::success(), $sqlQuery),
|
||||
'values' => $values,
|
||||
'has_previous' => $position > 0,
|
||||
'has_next' => $numRows >= $cfg['MaxRows'],
|
||||
'previous_params' => $previousParams,
|
||||
'full_queries_params' => $fullQueriesParams,
|
||||
'next_params' => $nextParams,
|
||||
'has_icons' => Util::showIcons('TableNavigationLinksMode'),
|
||||
'is_full_query' => $isFullQuery,
|
||||
'image_path' => $PMA_Theme->getImgPath(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $log Binary log file name
|
||||
* @param int $position Position to display
|
||||
* @param int $maxRows Maximum number of rows
|
||||
*/
|
||||
private function getSqlQuery(
|
||||
string $log,
|
||||
int $position,
|
||||
int $maxRows
|
||||
): string {
|
||||
$sqlQuery = 'SHOW BINLOG EVENTS';
|
||||
if (! empty($log)) {
|
||||
$sqlQuery .= ' IN \'' . $log . '\'';
|
||||
}
|
||||
$sqlQuery .= ' LIMIT ' . $position . ', ' . $maxRows;
|
||||
|
||||
return $sqlQuery;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Server;
|
||||
|
||||
use PhpMyAdmin\Charsets;
|
||||
use PhpMyAdmin\Charsets\Charset;
|
||||
use PhpMyAdmin\Charsets\Collation;
|
||||
use PhpMyAdmin\Controllers\AbstractController;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
|
||||
/**
|
||||
* Handles viewing character sets and collations
|
||||
*/
|
||||
class CollationsController extends AbstractController
|
||||
{
|
||||
/** @var array|null */
|
||||
private $charsets;
|
||||
|
||||
/** @var array|null */
|
||||
private $collations;
|
||||
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param DatabaseInterface $dbi
|
||||
* @param array|null $charsets Array of charsets
|
||||
* @param array|null $collations Array of collations
|
||||
*/
|
||||
public function __construct(
|
||||
$response,
|
||||
Template $template,
|
||||
$dbi,
|
||||
?array $charsets = null,
|
||||
?array $collations = null
|
||||
) {
|
||||
global $cfg;
|
||||
|
||||
parent::__construct($response, $template);
|
||||
$this->dbi = $dbi;
|
||||
|
||||
$this->charsets = $charsets ?? Charsets::getCharsets($this->dbi, $cfg['Server']['DisableIS']);
|
||||
$this->collations = $collations ?? Charsets::getCollations($this->dbi, $cfg['Server']['DisableIS']);
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $err_url;
|
||||
|
||||
$err_url = Url::getFromRoute('/');
|
||||
|
||||
if ($this->dbi->isSuperUser()) {
|
||||
$this->dbi->selectDb('mysql');
|
||||
}
|
||||
|
||||
$charsets = [];
|
||||
/** @var Charset $charset */
|
||||
foreach ($this->charsets as $charset) {
|
||||
$charsetCollations = [];
|
||||
/** @var Collation $collation */
|
||||
foreach ($this->collations[$charset->getName()] as $collation) {
|
||||
$charsetCollations[] = [
|
||||
'name' => $collation->getName(),
|
||||
'description' => $collation->getDescription(),
|
||||
'is_default' => $collation->isDefault(),
|
||||
];
|
||||
}
|
||||
|
||||
$charsets[] = [
|
||||
'name' => $charset->getName(),
|
||||
'description' => $charset->getDescription(),
|
||||
'collations' => $charsetCollations,
|
||||
];
|
||||
}
|
||||
|
||||
$this->render('server/collations/index', ['charsets' => $charsets]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,509 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Server;
|
||||
|
||||
use PhpMyAdmin\Charsets;
|
||||
use PhpMyAdmin\Charsets\Charset;
|
||||
use PhpMyAdmin\Charsets\Collation;
|
||||
use PhpMyAdmin\CheckUserPrivileges;
|
||||
use PhpMyAdmin\Controllers\AbstractController;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Html\Generator;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\Query\Utilities;
|
||||
use PhpMyAdmin\RelationCleanup;
|
||||
use PhpMyAdmin\ReplicationInfo;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Transformations;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
use function array_key_exists;
|
||||
use function array_keys;
|
||||
use function array_search;
|
||||
use function count;
|
||||
use function explode;
|
||||
use function in_array;
|
||||
use function mb_strlen;
|
||||
use function mb_strtolower;
|
||||
use function strlen;
|
||||
use function strpos;
|
||||
|
||||
/**
|
||||
* Handles viewing and creating and deleting databases
|
||||
*/
|
||||
class DatabasesController extends AbstractController
|
||||
{
|
||||
/** @var array array of database details */
|
||||
private $databases = [];
|
||||
|
||||
/** @var int number of databases */
|
||||
private $databaseCount = 0;
|
||||
|
||||
/** @var string sort by column */
|
||||
private $sortBy;
|
||||
|
||||
/** @var string sort order of databases */
|
||||
private $sortOrder;
|
||||
|
||||
/** @var bool whether to show database statistics */
|
||||
private $hasStatistics;
|
||||
|
||||
/** @var int position in list navigation */
|
||||
private $position;
|
||||
|
||||
/** @var Transformations */
|
||||
private $transformations;
|
||||
|
||||
/** @var RelationCleanup */
|
||||
private $relationCleanup;
|
||||
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct(
|
||||
$response,
|
||||
Template $template,
|
||||
Transformations $transformations,
|
||||
RelationCleanup $relationCleanup,
|
||||
$dbi
|
||||
) {
|
||||
parent::__construct($response, $template);
|
||||
$this->transformations = $transformations;
|
||||
$this->relationCleanup = $relationCleanup;
|
||||
$this->dbi = $dbi;
|
||||
|
||||
$checkUserPrivileges = new CheckUserPrivileges($dbi);
|
||||
$checkUserPrivileges->getPrivileges();
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $cfg, $server, $dblist, $is_create_db_priv;
|
||||
global $db_to_create, $text_dir, $PMA_Theme, $err_url;
|
||||
|
||||
$params = [
|
||||
'statistics' => $_REQUEST['statistics'] ?? null,
|
||||
'pos' => $_REQUEST['pos'] ?? null,
|
||||
'sort_by' => $_REQUEST['sort_by'] ?? null,
|
||||
'sort_order' => $_REQUEST['sort_order'] ?? null,
|
||||
];
|
||||
|
||||
$this->addScriptFiles(['server/databases.js']);
|
||||
$err_url = Url::getFromRoute('/');
|
||||
|
||||
if ($this->dbi->isSuperUser()) {
|
||||
$this->dbi->selectDb('mysql');
|
||||
}
|
||||
|
||||
$replicationInfo = new ReplicationInfo($this->dbi);
|
||||
$replicationInfo->load($_POST['master_connection'] ?? null);
|
||||
|
||||
$primaryInfo = $replicationInfo->getPrimaryInfo();
|
||||
$replicaInfo = $replicationInfo->getReplicaInfo();
|
||||
|
||||
$this->setSortDetails($params['sort_by'], $params['sort_order']);
|
||||
$this->hasStatistics = ! empty($params['statistics']);
|
||||
$this->position = ! empty($params['pos']) ? (int) $params['pos'] : 0;
|
||||
|
||||
/**
|
||||
* Gets the databases list
|
||||
*/
|
||||
if ($server > 0) {
|
||||
$this->databases = $this->dbi->getDatabasesFull(
|
||||
null,
|
||||
$this->hasStatistics,
|
||||
DatabaseInterface::CONNECT_USER,
|
||||
$this->sortBy,
|
||||
$this->sortOrder,
|
||||
$this->position,
|
||||
true
|
||||
);
|
||||
$this->databaseCount = count($dblist->databases);
|
||||
}
|
||||
|
||||
$urlParams = [
|
||||
'statistics' => $this->hasStatistics,
|
||||
'pos' => $this->position,
|
||||
'sort_by' => $this->sortBy,
|
||||
'sort_order' => $this->sortOrder,
|
||||
];
|
||||
|
||||
$databases = $this->getDatabases($primaryInfo, $replicaInfo);
|
||||
|
||||
$charsetsList = [];
|
||||
if ($cfg['ShowCreateDb'] && $is_create_db_priv) {
|
||||
$charsets = Charsets::getCharsets($this->dbi, $cfg['Server']['DisableIS']);
|
||||
$collations = Charsets::getCollations($this->dbi, $cfg['Server']['DisableIS']);
|
||||
$serverCollation = $this->dbi->getServerCollation();
|
||||
/** @var Charset $charset */
|
||||
foreach ($charsets as $charset) {
|
||||
$collationsList = [];
|
||||
/** @var Collation $collation */
|
||||
foreach ($collations[$charset->getName()] as $collation) {
|
||||
$collationsList[] = [
|
||||
'name' => $collation->getName(),
|
||||
'description' => $collation->getDescription(),
|
||||
'is_selected' => $serverCollation === $collation->getName(),
|
||||
];
|
||||
}
|
||||
$charsetsList[] = [
|
||||
'name' => $charset->getName(),
|
||||
'description' => $charset->getDescription(),
|
||||
'collations' => $collationsList,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$headerStatistics = $this->getStatisticsColumns();
|
||||
|
||||
$this->render('server/databases/index', [
|
||||
'is_create_database_shown' => $cfg['ShowCreateDb'],
|
||||
'has_create_database_privileges' => $is_create_db_priv,
|
||||
'has_statistics' => $this->hasStatistics,
|
||||
'database_to_create' => $db_to_create,
|
||||
'databases' => $databases['databases'],
|
||||
'total_statistics' => $databases['total_statistics'],
|
||||
'header_statistics' => $headerStatistics,
|
||||
'charsets' => $charsetsList,
|
||||
'database_count' => $this->databaseCount,
|
||||
'pos' => $this->position,
|
||||
'url_params' => $urlParams,
|
||||
'max_db_list' => $cfg['MaxDbList'],
|
||||
'has_master_replication' => $primaryInfo['status'],
|
||||
'has_slave_replication' => $replicaInfo['status'],
|
||||
'is_drop_allowed' => $this->dbi->isSuperUser() || $cfg['AllowUserDropDatabase'],
|
||||
'theme_image_path' => $PMA_Theme->getImgPath(),
|
||||
'text_dir' => $text_dir,
|
||||
]);
|
||||
}
|
||||
|
||||
public function create(): void
|
||||
{
|
||||
global $cfg, $db;
|
||||
|
||||
$params = [
|
||||
'new_db' => $_POST['new_db'] ?? null,
|
||||
'db_collation' => $_POST['db_collation'] ?? null,
|
||||
];
|
||||
|
||||
if (! isset($params['new_db']) || mb_strlen($params['new_db']) === 0 || ! $this->response->isAjax()) {
|
||||
$this->response->addJSON(['message' => Message::error()]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// lower_case_table_names=1 `DB` becomes `db`
|
||||
if ($this->dbi->getLowerCaseNames() === '1') {
|
||||
$params['new_db'] = mb_strtolower(
|
||||
$params['new_db']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and executes the db creation sql query
|
||||
*/
|
||||
$sqlQuery = 'CREATE DATABASE ' . Util::backquote($params['new_db']);
|
||||
if (! empty($params['db_collation'])) {
|
||||
[$databaseCharset] = explode('_', $params['db_collation']);
|
||||
$charsets = Charsets::getCharsets(
|
||||
$this->dbi,
|
||||
$cfg['Server']['DisableIS']
|
||||
);
|
||||
$collations = Charsets::getCollations(
|
||||
$this->dbi,
|
||||
$cfg['Server']['DisableIS']
|
||||
);
|
||||
if (array_key_exists($databaseCharset, $charsets)
|
||||
&& array_key_exists($params['db_collation'], $collations[$databaseCharset])
|
||||
) {
|
||||
$sqlQuery .= ' DEFAULT'
|
||||
. Util::getCharsetQueryPart($params['db_collation']);
|
||||
}
|
||||
}
|
||||
$sqlQuery .= ';';
|
||||
|
||||
$result = $this->dbi->tryQuery($sqlQuery);
|
||||
|
||||
if (! $result) {
|
||||
// avoid displaying the not-created db name in header or navi panel
|
||||
$db = '';
|
||||
|
||||
$message = Message::rawError((string) $this->dbi->getError());
|
||||
$json = ['message' => $message];
|
||||
|
||||
$this->response->setRequestStatus(false);
|
||||
} else {
|
||||
$db = $params['new_db'];
|
||||
|
||||
$message = Message::success(__('Database %1$s has been created.'));
|
||||
$message->addParam($params['new_db']);
|
||||
|
||||
$scriptName = Util::getScriptNameForOption(
|
||||
$cfg['DefaultTabDatabase'],
|
||||
'database'
|
||||
);
|
||||
|
||||
$json = [
|
||||
'message' => $message,
|
||||
'sql_query' => Generator::getMessage('', $sqlQuery, 'success'),
|
||||
'url' => $scriptName . Url::getCommon(
|
||||
['db' => $params['new_db']],
|
||||
strpos($scriptName, '?') === false ? '?' : '&'
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
$this->response->addJSON($json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles dropping multiple databases
|
||||
*/
|
||||
public function destroy(): void
|
||||
{
|
||||
global $selected, $err_url, $cfg, $dblist, $reload;
|
||||
|
||||
$params = [
|
||||
'drop_selected_dbs' => $_POST['drop_selected_dbs'] ?? null,
|
||||
'selected_dbs' => $_POST['selected_dbs'] ?? null,
|
||||
];
|
||||
/** @var Message|int $message */
|
||||
$message = -1;
|
||||
|
||||
if (! isset($params['drop_selected_dbs'])
|
||||
|| ! $this->response->isAjax()
|
||||
|| (! $this->dbi->isSuperUser() && ! $cfg['AllowUserDropDatabase'])
|
||||
) {
|
||||
$message = Message::error();
|
||||
$json = ['message' => $message];
|
||||
$this->response->setRequestStatus($message->isSuccess());
|
||||
$this->response->addJSON($json);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (! isset($params['selected_dbs'])) {
|
||||
$message = Message::error(__('No databases selected.'));
|
||||
$json = ['message' => $message];
|
||||
$this->response->setRequestStatus($message->isSuccess());
|
||||
$this->response->addJSON($json);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$err_url = Url::getFromRoute('/server/databases');
|
||||
$selected = $_POST['selected_dbs'];
|
||||
$rebuildDatabaseList = false;
|
||||
$sqlQuery = '';
|
||||
$numberOfDatabases = count($selected);
|
||||
|
||||
for ($i = 0; $i < $numberOfDatabases; $i++) {
|
||||
$this->relationCleanup->database($selected[$i]);
|
||||
$aQuery = 'DROP DATABASE ' . Util::backquote($selected[$i]);
|
||||
$reload = true;
|
||||
$rebuildDatabaseList = true;
|
||||
|
||||
$sqlQuery .= $aQuery . ';' . "\n";
|
||||
$this->dbi->query($aQuery);
|
||||
$this->transformations->clear($selected[$i]);
|
||||
}
|
||||
|
||||
if ($rebuildDatabaseList) {
|
||||
$dblist->databases->build();
|
||||
}
|
||||
|
||||
if ($message === -1) { // no error message
|
||||
$message = Message::success(
|
||||
_ngettext(
|
||||
'%1$d database has been dropped successfully.',
|
||||
'%1$d databases have been dropped successfully.',
|
||||
$numberOfDatabases
|
||||
)
|
||||
);
|
||||
$message->addParam($numberOfDatabases);
|
||||
}
|
||||
|
||||
$json = [];
|
||||
if ($message instanceof Message) {
|
||||
$json = ['message' => $message];
|
||||
$this->response->setRequestStatus($message->isSuccess());
|
||||
}
|
||||
|
||||
$this->response->addJSON($json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts parameters sort order and sort by
|
||||
*
|
||||
* @param string|null $sortBy sort by
|
||||
* @param string|null $sortOrder sort order
|
||||
*/
|
||||
private function setSortDetails(?string $sortBy, ?string $sortOrder): void
|
||||
{
|
||||
if (empty($sortBy)) {
|
||||
$this->sortBy = 'SCHEMA_NAME';
|
||||
} else {
|
||||
$sortByAllowList = [
|
||||
'SCHEMA_NAME',
|
||||
'DEFAULT_COLLATION_NAME',
|
||||
'SCHEMA_TABLES',
|
||||
'SCHEMA_TABLE_ROWS',
|
||||
'SCHEMA_DATA_LENGTH',
|
||||
'SCHEMA_INDEX_LENGTH',
|
||||
'SCHEMA_LENGTH',
|
||||
'SCHEMA_DATA_FREE',
|
||||
];
|
||||
$this->sortBy = 'SCHEMA_NAME';
|
||||
if (in_array($sortBy, $sortByAllowList)) {
|
||||
$this->sortBy = $sortBy;
|
||||
}
|
||||
}
|
||||
|
||||
$this->sortOrder = 'asc';
|
||||
if (! isset($sortOrder)
|
||||
|| mb_strtolower($sortOrder) !== 'desc'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->sortOrder = 'desc';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $primaryInfo
|
||||
* @param array $replicaInfo
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getDatabases($primaryInfo, $replicaInfo): array
|
||||
{
|
||||
global $cfg;
|
||||
|
||||
$databases = [];
|
||||
$totalStatistics = $this->getStatisticsColumns();
|
||||
foreach ($this->databases as $database) {
|
||||
$replication = [
|
||||
'master' => ['status' => $primaryInfo['status']],
|
||||
'slave' => ['status' => $replicaInfo['status']],
|
||||
];
|
||||
|
||||
if ($primaryInfo['status']) {
|
||||
$key = array_search($database['SCHEMA_NAME'], $primaryInfo['Ignore_DB']);
|
||||
$replication['master']['is_replicated'] = false;
|
||||
|
||||
if (strlen((string) $key) === 0) {
|
||||
$key = array_search($database['SCHEMA_NAME'], $primaryInfo['Do_DB']);
|
||||
|
||||
if (strlen((string) $key) > 0 || count($primaryInfo['Do_DB']) === 0) {
|
||||
$replication['master']['is_replicated'] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($replicaInfo['status']) {
|
||||
$key = array_search($database['SCHEMA_NAME'], $replicaInfo['Ignore_DB']);
|
||||
$replication['slave']['is_replicated'] = false;
|
||||
|
||||
if (strlen((string) $key) === 0) {
|
||||
$key = array_search($database['SCHEMA_NAME'], $replicaInfo['Do_DB']);
|
||||
|
||||
if (strlen((string) $key) > 0 || count($replicaInfo['Do_DB']) === 0) {
|
||||
$replication['slave']['is_replicated'] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$statistics = $this->getStatisticsColumns();
|
||||
if ($this->hasStatistics) {
|
||||
foreach (array_keys($statistics) as $key) {
|
||||
$statistics[$key]['raw'] = $database[$key] ?? null;
|
||||
$totalStatistics[$key]['raw'] += (int) $database[$key] ?? 0;
|
||||
}
|
||||
}
|
||||
|
||||
$url = Util::getScriptNameForOption($cfg['DefaultTabDatabase'], 'database');
|
||||
$url .= Url::getCommonRaw(
|
||||
['db' => $database['SCHEMA_NAME']],
|
||||
strpos($url, '?') === false ? '?' : '&'
|
||||
);
|
||||
$databases[$database['SCHEMA_NAME']] = [
|
||||
'name' => $database['SCHEMA_NAME'],
|
||||
'collation' => [],
|
||||
'statistics' => $statistics,
|
||||
'replication' => $replication,
|
||||
'is_system_schema' => Utilities::isSystemSchema(
|
||||
$database['SCHEMA_NAME'],
|
||||
true
|
||||
),
|
||||
'is_pmadb' => $database['SCHEMA_NAME'] === ($cfg['Server']['pmadb'] ?? ''),
|
||||
'url' => $url,
|
||||
];
|
||||
$collation = Charsets::findCollationByName(
|
||||
$this->dbi,
|
||||
$cfg['Server']['DisableIS'],
|
||||
$database['DEFAULT_COLLATION_NAME']
|
||||
);
|
||||
if ($collation === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$databases[$database['SCHEMA_NAME']]['collation'] = [
|
||||
'name' => $collation->getName(),
|
||||
'description' => $collation->getDescription(),
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'databases' => $databases,
|
||||
'total_statistics' => $totalStatistics,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the statistics columns
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getStatisticsColumns(): array
|
||||
{
|
||||
return [
|
||||
'SCHEMA_TABLES' => [
|
||||
'title' => __('Tables'),
|
||||
'format' => 'number',
|
||||
'raw' => 0,
|
||||
],
|
||||
'SCHEMA_TABLE_ROWS' => [
|
||||
'title' => __('Rows'),
|
||||
'format' => 'number',
|
||||
'raw' => 0,
|
||||
],
|
||||
'SCHEMA_DATA_LENGTH' => [
|
||||
'title' => __('Data'),
|
||||
'format' => 'byte',
|
||||
'raw' => 0,
|
||||
],
|
||||
'SCHEMA_INDEX_LENGTH' => [
|
||||
'title' => __('Indexes'),
|
||||
'format' => 'byte',
|
||||
'raw' => 0,
|
||||
],
|
||||
'SCHEMA_LENGTH' => [
|
||||
'title' => __('Total'),
|
||||
'format' => 'byte',
|
||||
'raw' => 0,
|
||||
],
|
||||
'SCHEMA_DATA_FREE' => [
|
||||
'title' => __('Overhead'),
|
||||
'format' => 'byte',
|
||||
'raw' => 0,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Server;
|
||||
|
||||
use PhpMyAdmin\Controllers\AbstractController;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\StorageEngine;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
|
||||
/**
|
||||
* Handles viewing storage engine details
|
||||
*/
|
||||
class EnginesController extends AbstractController
|
||||
{
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template);
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $err_url;
|
||||
|
||||
$err_url = Url::getFromRoute('/');
|
||||
|
||||
if ($this->dbi->isSuperUser()) {
|
||||
$this->dbi->selectDb('mysql');
|
||||
}
|
||||
|
||||
$this->render('server/engines/index', [
|
||||
'engines' => StorageEngine::getStorageEngines(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays details about a given Storage Engine
|
||||
*
|
||||
* @param array $params Request params
|
||||
*/
|
||||
public function show(array $params): void
|
||||
{
|
||||
global $err_url;
|
||||
|
||||
$err_url = Url::getFromRoute('/');
|
||||
|
||||
if ($this->dbi->isSuperUser()) {
|
||||
$this->dbi->selectDb('mysql');
|
||||
}
|
||||
|
||||
$page = $params['page'] ?? '';
|
||||
|
||||
$engine = [];
|
||||
if (StorageEngine::isValid($params['engine'])) {
|
||||
$storageEngine = StorageEngine::getEngine($params['engine']);
|
||||
$engine = [
|
||||
'engine' => $params['engine'],
|
||||
'title' => $storageEngine->getTitle(),
|
||||
'help_page' => $storageEngine->getMysqlHelpPage(),
|
||||
'comment' => $storageEngine->getComment(),
|
||||
'info_pages' => $storageEngine->getInfoPages(),
|
||||
'support' => $storageEngine->getSupportInformationMessage(),
|
||||
'variables' => $storageEngine->getHtmlVariables(),
|
||||
'page' => ! empty($page) ? $storageEngine->getPage($page) : '',
|
||||
];
|
||||
}
|
||||
|
||||
$this->render('server/engines/show', [
|
||||
'engine' => $engine,
|
||||
'page' => $page,
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Server;
|
||||
|
||||
use PhpMyAdmin\Config\PageSettings;
|
||||
use PhpMyAdmin\Controllers\AbstractController;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Export\Options;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\Plugins;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use function array_merge;
|
||||
|
||||
final class ExportController extends AbstractController
|
||||
{
|
||||
/** @var Options */
|
||||
private $export;
|
||||
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, Options $export, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template);
|
||||
$this->export = $export;
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $db, $table, $sql_query, $num_tables, $unlim_num_rows;
|
||||
global $tmp_select, $select_item, $err_url;
|
||||
|
||||
$err_url = Url::getFromRoute('/');
|
||||
|
||||
if ($this->dbi->isSuperUser()) {
|
||||
$this->dbi->selectDb('mysql');
|
||||
}
|
||||
|
||||
$pageSettings = new PageSettings('Export');
|
||||
$pageSettingsErrorHtml = $pageSettings->getErrorHTML();
|
||||
$pageSettingsHtml = $pageSettings->getHTML();
|
||||
|
||||
$this->addScriptFiles(['export.js']);
|
||||
|
||||
$select_item = $tmp_select ?? '';
|
||||
$databases = $this->export->getDatabasesForSelectOptions($select_item);
|
||||
|
||||
if (! isset($sql_query)) {
|
||||
$sql_query = '';
|
||||
}
|
||||
if (! isset($num_tables)) {
|
||||
$num_tables = 0;
|
||||
}
|
||||
if (! isset($unlim_num_rows)) {
|
||||
$unlim_num_rows = 0;
|
||||
}
|
||||
|
||||
$GLOBALS['single_table'] = $_POST['single_table'] ?? $_GET['single_table'] ?? $GLOBALS['single_table'] ?? null;
|
||||
|
||||
$exportList = Plugins::getExport('server', isset($GLOBALS['single_table']));
|
||||
|
||||
if (empty($exportList)) {
|
||||
$this->response->addHTML(Message::error(
|
||||
__('Could not load export plugins, please check your installation!')
|
||||
)->getDisplay());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$options = $this->export->getOptions(
|
||||
'server',
|
||||
$db,
|
||||
$table,
|
||||
$sql_query,
|
||||
$num_tables,
|
||||
$unlim_num_rows,
|
||||
$exportList
|
||||
);
|
||||
|
||||
$this->render('server/export/index', array_merge($options, [
|
||||
'page_settings_error_html' => $pageSettingsErrorHtml,
|
||||
'page_settings_html' => $pageSettingsHtml,
|
||||
'databases' => $databases,
|
||||
]));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Server;
|
||||
|
||||
use PhpMyAdmin\Charsets;
|
||||
use PhpMyAdmin\Charsets\Charset;
|
||||
use PhpMyAdmin\Config\PageSettings;
|
||||
use PhpMyAdmin\Controllers\AbstractController;
|
||||
use PhpMyAdmin\Core;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Encoding;
|
||||
use PhpMyAdmin\Import;
|
||||
use PhpMyAdmin\Import\Ajax;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\Plugins;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
use function intval;
|
||||
|
||||
final class ImportController extends AbstractController
|
||||
{
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template);
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $db, $max_upload_size, $table, $SESSION_KEY, $cfg, $PMA_Theme, $err_url;
|
||||
|
||||
$pageSettings = new PageSettings('Import');
|
||||
$pageSettingsErrorHtml = $pageSettings->getErrorHTML();
|
||||
$pageSettingsHtml = $pageSettings->getHTML();
|
||||
|
||||
$this->addScriptFiles(['import.js']);
|
||||
$err_url = Url::getFromRoute('/');
|
||||
|
||||
if ($this->dbi->isSuperUser()) {
|
||||
$this->dbi->selectDb('mysql');
|
||||
}
|
||||
|
||||
[$SESSION_KEY, $uploadId] = Ajax::uploadProgressSetup();
|
||||
|
||||
$importList = Plugins::getImport('server');
|
||||
|
||||
if (empty($importList)) {
|
||||
$this->response->addHTML(Message::error(__(
|
||||
'Could not load import plugins, please check your installation!'
|
||||
))->getDisplay());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$offset = null;
|
||||
if (Core::isValid($_REQUEST['offset'], 'numeric')) {
|
||||
$offset = intval($_REQUEST['offset']);
|
||||
}
|
||||
|
||||
$timeoutPassed = $_REQUEST['timeout_passed'] ?? null;
|
||||
$localImportFile = $_REQUEST['local_import_file'] ?? null;
|
||||
$compressions = Import::getCompressions();
|
||||
|
||||
$allCharsets = Charsets::getCharsets($this->dbi, $cfg['Server']['DisableIS']);
|
||||
$charsets = [];
|
||||
/** @var Charset $charset */
|
||||
foreach ($allCharsets as $charset) {
|
||||
$charsets[] = [
|
||||
'name' => $charset->getName(),
|
||||
'description' => $charset->getDescription(),
|
||||
];
|
||||
}
|
||||
|
||||
$idKey = $_SESSION[$SESSION_KEY]['handler']::getIdKey();
|
||||
$hiddenInputs = [
|
||||
$idKey => $uploadId,
|
||||
'import_type' => 'server',
|
||||
];
|
||||
|
||||
$this->render('server/import/index', [
|
||||
'page_settings_error_html' => $pageSettingsErrorHtml,
|
||||
'page_settings_html' => $pageSettingsHtml,
|
||||
'upload_id' => $uploadId,
|
||||
'handler' => $_SESSION[$SESSION_KEY]['handler'],
|
||||
'theme_image_path' => $PMA_Theme->getImgPath(),
|
||||
'hidden_inputs' => $hiddenInputs,
|
||||
'db' => $db,
|
||||
'table' => $table,
|
||||
'max_upload_size' => $max_upload_size,
|
||||
'import_list' => $importList,
|
||||
'local_import_file' => $localImportFile,
|
||||
'is_upload' => $GLOBALS['is_upload'],
|
||||
'upload_dir' => $cfg['UploadDir'] ?? null,
|
||||
'timeout_passed_global' => $GLOBALS['timeout_passed'] ?? null,
|
||||
'compressions' => $compressions,
|
||||
'is_encoding_supported' => Encoding::isSupported(),
|
||||
'encodings' => Encoding::listEncodings(),
|
||||
'import_charset' => $cfg['Import']['charset'] ?? null,
|
||||
'timeout_passed' => $timeoutPassed,
|
||||
'offset' => $offset,
|
||||
'can_convert_kanji' => Encoding::canConvertKanji(),
|
||||
'charsets' => $charsets,
|
||||
'is_foreign_key_check' => Util::isForeignKeyCheck(),
|
||||
'user_upload_dir' => Util::userDir($cfg['UploadDir'] ?? ''),
|
||||
'local_files' => Import::getLocalFiles($importList),
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Server;
|
||||
|
||||
use PhpMyAdmin\Controllers\AbstractController;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Server\Plugins;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use function array_keys;
|
||||
use function ksort;
|
||||
use function mb_strtolower;
|
||||
use function preg_replace;
|
||||
|
||||
/**
|
||||
* Handles viewing server plugin details
|
||||
*/
|
||||
class PluginsController extends AbstractController
|
||||
{
|
||||
/** @var Plugins */
|
||||
private $plugins;
|
||||
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, Plugins $plugins, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template);
|
||||
$this->plugins = $plugins;
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $err_url;
|
||||
|
||||
$err_url = Url::getFromRoute('/');
|
||||
|
||||
if ($this->dbi->isSuperUser()) {
|
||||
$this->dbi->selectDb('mysql');
|
||||
}
|
||||
|
||||
$this->addScriptFiles(['vendor/jquery/jquery.tablesorter.js', 'server/plugins.js']);
|
||||
|
||||
$plugins = [];
|
||||
$serverPlugins = $this->plugins->getAll();
|
||||
foreach ($serverPlugins as $plugin) {
|
||||
$plugins[$plugin->getType()][] = $plugin->toArray();
|
||||
}
|
||||
ksort($plugins);
|
||||
|
||||
$cleanTypes = [];
|
||||
foreach (array_keys($plugins) as $type) {
|
||||
$cleanTypes[$type] = preg_replace(
|
||||
'/[^a-z]/',
|
||||
'',
|
||||
mb_strtolower($type)
|
||||
);
|
||||
}
|
||||
|
||||
$this->render('server/plugins/index', [
|
||||
'plugins' => $plugins,
|
||||
'clean_types' => $cleanTypes,
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,486 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Server;
|
||||
|
||||
use PhpMyAdmin\CheckUserPrivileges;
|
||||
use PhpMyAdmin\Controllers\AbstractController;
|
||||
use PhpMyAdmin\Controllers\Database\PrivilegesController as DatabaseController;
|
||||
use PhpMyAdmin\Controllers\Table\PrivilegesController as TableController;
|
||||
use PhpMyAdmin\Core;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Html\Generator;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\Relation;
|
||||
use PhpMyAdmin\RelationCleanup;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Server\Privileges;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
use function header;
|
||||
use function implode;
|
||||
use function is_array;
|
||||
use function ob_get_clean;
|
||||
use function ob_start;
|
||||
use function str_replace;
|
||||
use function urlencode;
|
||||
|
||||
/**
|
||||
* Server privileges and users manipulations.
|
||||
*/
|
||||
class PrivilegesController extends AbstractController
|
||||
{
|
||||
/** @var Relation */
|
||||
private $relation;
|
||||
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, Relation $relation, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template);
|
||||
$this->relation = $relation;
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $db, $table, $err_url, $message, $text_dir, $post_patterns, $PMA_Theme;
|
||||
global $username, $hostname, $dbname, $tablename, $routinename, $db_and_table, $dbname_is_wildcard;
|
||||
global $queries, $password, $ret_message, $ret_queries, $queries_for_display, $sql_query, $_add_user_error;
|
||||
global $itemType, $tables, $num_tables, $total_num_tables, $sub_part;
|
||||
global $tooltip_truename, $tooltip_aliasname, $pos, $title, $export, $grants, $one_grant, $url_dbname;
|
||||
|
||||
$checkUserPrivileges = new CheckUserPrivileges($this->dbi);
|
||||
$checkUserPrivileges->getPrivileges();
|
||||
|
||||
$cfgRelation = $this->relation->getRelationsParam();
|
||||
|
||||
$this->addScriptFiles(['server/privileges.js', 'vendor/zxcvbn.js']);
|
||||
|
||||
$relationCleanup = new RelationCleanup($this->dbi, $this->relation);
|
||||
$serverPrivileges = new Privileges($this->template, $this->dbi, $this->relation, $relationCleanup);
|
||||
|
||||
$databaseController = new DatabaseController(
|
||||
$this->response,
|
||||
$this->template,
|
||||
$db,
|
||||
$serverPrivileges,
|
||||
$this->dbi
|
||||
);
|
||||
|
||||
$tableController = new TableController(
|
||||
$this->response,
|
||||
$this->template,
|
||||
$db,
|
||||
$table,
|
||||
$serverPrivileges,
|
||||
$this->dbi
|
||||
);
|
||||
|
||||
if ((isset($_GET['viewing_mode'])
|
||||
&& $_GET['viewing_mode'] === 'server')
|
||||
&& $GLOBALS['cfgRelation']['menuswork']
|
||||
) {
|
||||
$this->response->addHTML('<div class="container-fluid">');
|
||||
$this->render('server/privileges/subnav', [
|
||||
'active' => 'privileges',
|
||||
'is_super_user' => $this->dbi->isSuperUser(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets globals from $_POST patterns, for privileges and max_* vars
|
||||
*/
|
||||
$post_patterns = [
|
||||
'/_priv$/i',
|
||||
'/^max_/i',
|
||||
];
|
||||
|
||||
Core::setPostAsGlobal($post_patterns);
|
||||
|
||||
$err_url = Url::getFromRoute('/');
|
||||
|
||||
if ($this->dbi->isSuperUser()) {
|
||||
$this->dbi->selectDb('mysql');
|
||||
}
|
||||
|
||||
$_add_user_error = false;
|
||||
/**
|
||||
* Get DB information: username, hostname, dbname,
|
||||
* tablename, db_and_table, dbname_is_wildcard
|
||||
*/
|
||||
[
|
||||
$username,
|
||||
$hostname,
|
||||
$dbname,
|
||||
$tablename,
|
||||
$routinename,
|
||||
$db_and_table,
|
||||
$dbname_is_wildcard,
|
||||
] = $serverPrivileges->getDataForDBInfo();
|
||||
|
||||
/**
|
||||
* Checks if the user is allowed to do what they try to...
|
||||
*/
|
||||
$isGrantUser = $this->dbi->isGrantUser();
|
||||
$isCreateUser = $this->dbi->isCreateUser();
|
||||
|
||||
if (! $this->dbi->isSuperUser() && ! $isGrantUser && ! $isCreateUser) {
|
||||
$this->render('server/sub_page_header', [
|
||||
'type' => 'privileges',
|
||||
'is_image' => false,
|
||||
]);
|
||||
$this->response->addHTML(
|
||||
Message::error(__('No Privileges'))
|
||||
->getDisplay()
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
if (! $isGrantUser && ! $isCreateUser) {
|
||||
$this->response->addHTML(Message::notice(
|
||||
__('You do not have the privileges to administrate the users!')
|
||||
)->getDisplay());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the user is using "Change Login Information / Copy User" dialog
|
||||
* only to update the password
|
||||
*/
|
||||
if (isset($_POST['change_copy']) && $username == $_POST['old_username']
|
||||
&& $hostname == $_POST['old_hostname']
|
||||
) {
|
||||
$this->response->addHTML(
|
||||
Message::error(
|
||||
__(
|
||||
"Username and hostname didn't change. "
|
||||
. 'If you only want to change the password, '
|
||||
. "'Change password' tab should be used."
|
||||
)
|
||||
)->getDisplay()
|
||||
);
|
||||
$this->response->setRequestStatus(false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes / copies a user, part I
|
||||
*/
|
||||
[$queries, $password] = $serverPrivileges->getDataForChangeOrCopyUser();
|
||||
|
||||
/**
|
||||
* Adds a user
|
||||
* (Changes / copies a user, part II)
|
||||
*/
|
||||
[
|
||||
$ret_message,
|
||||
$ret_queries,
|
||||
$queries_for_display,
|
||||
$sql_query,
|
||||
$_add_user_error,
|
||||
] = $serverPrivileges->addUser(
|
||||
$dbname ?? null,
|
||||
$username ?? null,
|
||||
$hostname ?? null,
|
||||
$password ?? null,
|
||||
(bool) $cfgRelation['menuswork']
|
||||
);
|
||||
//update the old variables
|
||||
if (isset($ret_queries)) {
|
||||
$queries = $ret_queries;
|
||||
unset($ret_queries);
|
||||
}
|
||||
if (isset($ret_message)) {
|
||||
$message = $ret_message;
|
||||
unset($ret_message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes / copies a user, part III
|
||||
*/
|
||||
if (isset($_POST['change_copy'])) {
|
||||
$queries = $serverPrivileges->getDbSpecificPrivsQueriesForChangeOrCopyUser(
|
||||
$queries,
|
||||
$username,
|
||||
$hostname
|
||||
);
|
||||
}
|
||||
|
||||
$itemType = '';
|
||||
if (! empty($routinename)) {
|
||||
$itemType = $serverPrivileges->getRoutineType($dbname, $routinename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates privileges
|
||||
*/
|
||||
if (! empty($_POST['update_privs'])) {
|
||||
if (is_array($dbname)) {
|
||||
foreach ($dbname as $key => $db_name) {
|
||||
[$sql_query[$key], $message] = $serverPrivileges->updatePrivileges(
|
||||
($username ?? ''),
|
||||
($hostname ?? ''),
|
||||
($tablename ?? ($routinename ?? '')),
|
||||
($db_name ?? ''),
|
||||
$itemType
|
||||
);
|
||||
}
|
||||
|
||||
$sql_query = implode("\n", $sql_query);
|
||||
} else {
|
||||
[$sql_query, $message] = $serverPrivileges->updatePrivileges(
|
||||
($username ?? ''),
|
||||
($hostname ?? ''),
|
||||
($tablename ?? ($routinename ?? '')),
|
||||
($dbname ?? ''),
|
||||
$itemType
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign users to user groups
|
||||
*/
|
||||
if (! empty($_POST['changeUserGroup']) && $cfgRelation['menuswork']
|
||||
&& $this->dbi->isSuperUser() && $this->dbi->isCreateUser()
|
||||
) {
|
||||
$serverPrivileges->setUserGroup($username, $_POST['userGroup']);
|
||||
$message = Message::success();
|
||||
}
|
||||
|
||||
/**
|
||||
* Revokes Privileges
|
||||
*/
|
||||
if (isset($_POST['revokeall'])) {
|
||||
[$message, $sql_query] = $serverPrivileges->getMessageAndSqlQueryForPrivilegesRevoke(
|
||||
($dbname ?? ''),
|
||||
($tablename ?? ($routinename ?? '')),
|
||||
$username,
|
||||
$hostname,
|
||||
$itemType
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the password
|
||||
*/
|
||||
if (isset($_POST['change_pw'])) {
|
||||
$message = $serverPrivileges->updatePassword(
|
||||
$err_url,
|
||||
$username,
|
||||
$hostname
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes users
|
||||
* (Changes / copies a user, part IV)
|
||||
*/
|
||||
if (isset($_POST['delete'])
|
||||
|| (isset($_POST['change_copy']) && $_POST['mode'] < 4)
|
||||
) {
|
||||
$queries = $serverPrivileges->getDataForDeleteUsers($queries);
|
||||
if (empty($_POST['change_copy'])) {
|
||||
[$sql_query, $message] = $serverPrivileges->deleteUser($queries);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes / copies a user, part V
|
||||
*/
|
||||
if (isset($_POST['change_copy'])) {
|
||||
$queries = $serverPrivileges->getDataForQueries($queries, $queries_for_display);
|
||||
$message = Message::success();
|
||||
$sql_query = implode("\n", $queries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads the privilege tables into memory
|
||||
*/
|
||||
$message_ret = $serverPrivileges->updateMessageForReload();
|
||||
if ($message_ret !== null) {
|
||||
$message = $message_ret;
|
||||
unset($message_ret);
|
||||
}
|
||||
|
||||
/**
|
||||
* If we are in an Ajax request for Create User/Edit User/Revoke User/
|
||||
* Flush Privileges, show $message and return.
|
||||
*/
|
||||
if ($this->response->isAjax()
|
||||
&& empty($_REQUEST['ajax_page_request'])
|
||||
&& ! isset($_GET['export'])
|
||||
&& (! isset($_POST['submit_mult']) || $_POST['submit_mult'] !== 'export')
|
||||
&& ((! isset($_GET['initial']) || $_GET['initial'] === null
|
||||
|| $_GET['initial'] === '')
|
||||
|| (isset($_POST['delete']) && $_POST['delete'] === __('Go')))
|
||||
&& ! isset($_GET['showall'])
|
||||
&& ! isset($_GET['edit_user_group_dialog'])
|
||||
) {
|
||||
$extra_data = $serverPrivileges->getExtraDataForAjaxBehavior(
|
||||
($password ?? ''),
|
||||
($sql_query ?? ''),
|
||||
($hostname ?? ''),
|
||||
($username ?? '')
|
||||
);
|
||||
|
||||
if (! empty($message) && $message instanceof Message) {
|
||||
$this->response->setRequestStatus($message->isSuccess());
|
||||
$this->response->addJSON('message', $message);
|
||||
$this->response->addJSON($extra_data);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the links
|
||||
*/
|
||||
if (isset($_GET['viewing_mode']) && $_GET['viewing_mode'] === 'db') {
|
||||
$db = $_REQUEST['db'] = $_GET['checkprivsdb'];
|
||||
|
||||
// Gets the database structure
|
||||
$sub_part = '_structure';
|
||||
ob_start();
|
||||
|
||||
[
|
||||
$tables,
|
||||
$num_tables,
|
||||
$total_num_tables,
|
||||
$sub_part,,,
|
||||
$tooltip_truename,
|
||||
$tooltip_aliasname,
|
||||
$pos,
|
||||
] = Util::getDbInfo($db, $sub_part ?? '');
|
||||
|
||||
$content = ob_get_clean();
|
||||
$this->response->addHTML($content . "\n");
|
||||
} elseif (! empty($GLOBALS['message'])) {
|
||||
$this->response->addHTML(Generator::getMessage($GLOBALS['message']));
|
||||
unset($GLOBALS['message']);
|
||||
}
|
||||
|
||||
if (! empty($_GET['edit_user_group_dialog']) && $cfgRelation['menuswork']) {
|
||||
$dialog = $serverPrivileges->getHtmlToChooseUserGroup($username ?? null);
|
||||
|
||||
if ($this->response->isAjax()) {
|
||||
$this->response->addJSON('message', $dialog);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->response->addHTML($dialog);
|
||||
}
|
||||
|
||||
// export user definition
|
||||
if (isset($_GET['export'])
|
||||
|| (isset($_POST['submit_mult']) && $_POST['submit_mult'] === 'export')
|
||||
) {
|
||||
[$title, $export] = $serverPrivileges->getListForExportUserDefinition(
|
||||
$username ?? '',
|
||||
$hostname ?? ''
|
||||
);
|
||||
|
||||
unset($username, $hostname, $grants, $one_grant);
|
||||
|
||||
if ($this->response->isAjax()) {
|
||||
$this->response->addJSON('message', $export);
|
||||
$this->response->addJSON('title', $title);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->response->addHTML('<h2>' . $title . '</h2>' . $export);
|
||||
}
|
||||
|
||||
// Show back the form if an error occurred
|
||||
if (isset($_GET['adduser']) || $_add_user_error === true) {
|
||||
// Add user
|
||||
$this->response->addHTML(
|
||||
$serverPrivileges->getHtmlForAddUser(($dbname ?? ''))
|
||||
);
|
||||
} elseif (isset($_GET['checkprivsdb'])) {
|
||||
if (isset($_GET['checkprivstable'])) {
|
||||
$this->response->addHTML($tableController->index([
|
||||
'checkprivsdb' => $_GET['checkprivsdb'],
|
||||
'checkprivstable' => $_GET['checkprivstable'],
|
||||
]));
|
||||
} elseif ($this->response->isAjax() === true && empty($_REQUEST['ajax_page_request'])) {
|
||||
$message = Message::success(__('User has been added.'));
|
||||
$this->response->addJSON('message', $message);
|
||||
|
||||
return;
|
||||
} else {
|
||||
$this->response->addHTML($databaseController->index([
|
||||
'checkprivsdb' => $_GET['checkprivsdb'],
|
||||
]));
|
||||
}
|
||||
} else {
|
||||
if (isset($dbname) && ! is_array($dbname)) {
|
||||
$url_dbname = urlencode(
|
||||
str_replace(
|
||||
[
|
||||
'\_',
|
||||
'\%',
|
||||
],
|
||||
[
|
||||
'_',
|
||||
'%',
|
||||
],
|
||||
$dbname
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (! isset($username)) {
|
||||
// No username is given --> display the overview
|
||||
$this->response->addHTML(
|
||||
$serverPrivileges->getHtmlForUserOverview($PMA_Theme->getImgPath(), $text_dir)
|
||||
);
|
||||
} elseif (! empty($routinename)) {
|
||||
$this->response->addHTML(
|
||||
$serverPrivileges->getHtmlForRoutineSpecificPrivileges(
|
||||
$username,
|
||||
$hostname ?? '',
|
||||
$dbname,
|
||||
$routinename,
|
||||
$url_dbname ?? ''
|
||||
)
|
||||
);
|
||||
} else {
|
||||
// A user was selected -> display the user's properties
|
||||
// In an Ajax request, prevent cached values from showing
|
||||
if ($this->response->isAjax()) {
|
||||
header('Cache-Control: no-cache');
|
||||
}
|
||||
|
||||
$this->response->addHTML(
|
||||
$serverPrivileges->getHtmlForUserProperties(
|
||||
$dbname_is_wildcard,
|
||||
$url_dbname ?? '',
|
||||
$username,
|
||||
$hostname ?? '',
|
||||
$dbname ?? '',
|
||||
$tablename ?? ''
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ((! isset($_GET['viewing_mode']) || $_GET['viewing_mode'] !== 'server')
|
||||
|| ! $cfgRelation['menuswork']
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->response->addHTML('</div>');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
<?php
|
||||
/**
|
||||
* Server replications
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Server;
|
||||
|
||||
use PhpMyAdmin\Controllers\AbstractController;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\ReplicationGui;
|
||||
use PhpMyAdmin\ReplicationInfo;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use function is_array;
|
||||
|
||||
/**
|
||||
* Server replications
|
||||
*/
|
||||
class ReplicationController extends AbstractController
|
||||
{
|
||||
/** @var ReplicationGui */
|
||||
private $replicationGui;
|
||||
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, ReplicationGui $replicationGui, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template);
|
||||
$this->replicationGui = $replicationGui;
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $url_params, $err_url;
|
||||
|
||||
$params = [
|
||||
'url_params' => $_POST['url_params'] ?? null,
|
||||
'mr_configure' => $_POST['mr_configure'] ?? null,
|
||||
'sl_configure' => $_POST['sl_configure'] ?? null,
|
||||
'repl_clear_scr' => $_POST['repl_clear_scr'] ?? null,
|
||||
];
|
||||
$err_url = Url::getFromRoute('/');
|
||||
|
||||
if ($this->dbi->isSuperUser()) {
|
||||
$this->dbi->selectDb('mysql');
|
||||
}
|
||||
|
||||
$replicationInfo = new ReplicationInfo($this->dbi);
|
||||
$replicationInfo->load($_POST['master_connection'] ?? null);
|
||||
|
||||
$primaryInfo = $replicationInfo->getPrimaryInfo();
|
||||
$replicaInfo = $replicationInfo->getReplicaInfo();
|
||||
|
||||
$this->addScriptFiles(['server/privileges.js', 'replication.js', 'vendor/zxcvbn.js']);
|
||||
|
||||
if (isset($params['url_params']) && is_array($params['url_params'])) {
|
||||
$url_params = $params['url_params'];
|
||||
}
|
||||
|
||||
if ($this->dbi->isSuperUser()) {
|
||||
$this->replicationGui->handleControlRequest();
|
||||
}
|
||||
|
||||
$errorMessages = $this->replicationGui->getHtmlForErrorMessage();
|
||||
|
||||
if ($primaryInfo['status']) {
|
||||
$masterReplicationHtml = $this->replicationGui->getHtmlForMasterReplication();
|
||||
}
|
||||
|
||||
if (isset($params['mr_configure'])) {
|
||||
$masterConfigurationHtml = $this->replicationGui->getHtmlForMasterConfiguration();
|
||||
} else {
|
||||
if (! isset($params['repl_clear_scr'])) {
|
||||
$slaveConfigurationHtml = $this->replicationGui->getHtmlForSlaveConfiguration(
|
||||
$replicaInfo['status'],
|
||||
$replicationInfo->getReplicaStatus()
|
||||
);
|
||||
}
|
||||
if (isset($params['sl_configure'])) {
|
||||
$changeMasterHtml = $this->replicationGui->getHtmlForReplicationChangeMaster('slave_changemaster');
|
||||
}
|
||||
}
|
||||
|
||||
$this->render('server/replication/index', [
|
||||
'url_params' => $url_params,
|
||||
'is_super_user' => $this->dbi->isSuperUser(),
|
||||
'error_messages' => $errorMessages,
|
||||
'is_master' => $primaryInfo['status'],
|
||||
'master_configure' => $params['mr_configure'],
|
||||
'slave_configure' => $params['sl_configure'],
|
||||
'clear_screen' => $params['repl_clear_scr'],
|
||||
'master_replication_html' => $masterReplicationHtml ?? '',
|
||||
'master_configuration_html' => $masterConfigurationHtml ?? '',
|
||||
'slave_configuration_html' => $slaveConfigurationHtml ?? '',
|
||||
'change_master_html' => $changeMasterHtml ?? '',
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Server;
|
||||
|
||||
use PhpMyAdmin\Config\PageSettings;
|
||||
use PhpMyAdmin\Controllers\AbstractController;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\SqlQueryForm;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
|
||||
/**
|
||||
* Server SQL executor
|
||||
*/
|
||||
class SqlController extends AbstractController
|
||||
{
|
||||
/** @var SqlQueryForm */
|
||||
private $sqlQueryForm;
|
||||
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, SqlQueryForm $sqlQueryForm, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template);
|
||||
$this->sqlQueryForm = $sqlQueryForm;
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $err_url;
|
||||
|
||||
$this->addScriptFiles([
|
||||
'makegrid.js',
|
||||
'vendor/jquery/jquery.uitablefilter.js',
|
||||
'vendor/stickyfill.min.js',
|
||||
'sql.js',
|
||||
]);
|
||||
|
||||
$pageSettings = new PageSettings('Sql');
|
||||
$this->response->addHTML($pageSettings->getErrorHTML());
|
||||
$this->response->addHTML($pageSettings->getHTML());
|
||||
$err_url = Url::getFromRoute('/');
|
||||
|
||||
if ($this->dbi->isSuperUser()) {
|
||||
$this->dbi->selectDb('mysql');
|
||||
}
|
||||
|
||||
$this->response->addHTML($this->sqlQueryForm->getHtml());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Server\Status;
|
||||
|
||||
use PhpMyAdmin\Controllers\AbstractController as Controller;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Server\Status\Data;
|
||||
use PhpMyAdmin\Template;
|
||||
|
||||
abstract class AbstractController extends Controller
|
||||
{
|
||||
/** @var Data */
|
||||
protected $data;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param Data $data
|
||||
*/
|
||||
public function __construct($response, Template $template, $data)
|
||||
{
|
||||
parent::__construct($response, $template);
|
||||
$this->data = $data;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Server\Status;
|
||||
|
||||
use PhpMyAdmin\Advisor;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Server\Status\Data;
|
||||
use PhpMyAdmin\Template;
|
||||
|
||||
/**
|
||||
* Displays the advisor feature
|
||||
*/
|
||||
class AdvisorController extends AbstractController
|
||||
{
|
||||
/** @var Advisor */
|
||||
private $advisor;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param Data $data
|
||||
*/
|
||||
public function __construct($response, Template $template, $data, Advisor $advisor)
|
||||
{
|
||||
parent::__construct($response, $template, $data);
|
||||
$this->advisor = $advisor;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
$data = [];
|
||||
if ($this->data->dataLoaded) {
|
||||
$data = $this->advisor->run();
|
||||
}
|
||||
|
||||
$this->render('server/status/advisor/index', ['data' => $data]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,216 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Server\Status;
|
||||
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Server\Status\Data;
|
||||
use PhpMyAdmin\Server\Status\Monitor;
|
||||
use PhpMyAdmin\Server\SysInfo\SysInfo;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use function is_numeric;
|
||||
use function microtime;
|
||||
|
||||
class MonitorController extends AbstractController
|
||||
{
|
||||
/** @var Monitor */
|
||||
private $monitor;
|
||||
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param Data $data
|
||||
* @param Monitor $monitor
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, $data, $monitor, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template, $data);
|
||||
$this->monitor = $monitor;
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $PMA_Theme, $err_url;
|
||||
|
||||
$err_url = Url::getFromRoute('/');
|
||||
|
||||
if ($this->dbi->isSuperUser()) {
|
||||
$this->dbi->selectDb('mysql');
|
||||
}
|
||||
|
||||
$this->addScriptFiles([
|
||||
'vendor/jquery/jquery.tablesorter.js',
|
||||
'jquery.sortable-table.js',
|
||||
'vendor/jqplot/jquery.jqplot.js',
|
||||
'vendor/jqplot/plugins/jqplot.pieRenderer.js',
|
||||
'vendor/jqplot/plugins/jqplot.enhancedPieLegendRenderer.js',
|
||||
'vendor/jqplot/plugins/jqplot.canvasTextRenderer.js',
|
||||
'vendor/jqplot/plugins/jqplot.canvasAxisLabelRenderer.js',
|
||||
'vendor/jqplot/plugins/jqplot.dateAxisRenderer.js',
|
||||
'vendor/jqplot/plugins/jqplot.highlighter.js',
|
||||
'vendor/jqplot/plugins/jqplot.cursor.js',
|
||||
'jqplot/plugins/jqplot.byteFormatter.js',
|
||||
'server/status/monitor.js',
|
||||
'server/status/sorter.js',
|
||||
]);
|
||||
|
||||
$form = [
|
||||
'server_time' => (int) (microtime(true) * 1000),
|
||||
'server_os' => SysInfo::getOs(),
|
||||
'is_superuser' => $this->dbi->isSuperUser(),
|
||||
'server_db_isLocal' => $this->data->dbIsLocal,
|
||||
];
|
||||
|
||||
$javascriptVariableNames = [];
|
||||
foreach ($this->data->status as $name => $value) {
|
||||
if (! is_numeric($value)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$javascriptVariableNames[] = $name;
|
||||
}
|
||||
|
||||
$this->render('server/status/monitor/index', [
|
||||
'image_path' => $PMA_Theme->getImgPath(),
|
||||
'javascript_variable_names' => $javascriptVariableNames,
|
||||
'form' => $form,
|
||||
]);
|
||||
}
|
||||
|
||||
public function chartingData(): void
|
||||
{
|
||||
global $err_url;
|
||||
|
||||
$params = ['requiredData' => $_POST['requiredData'] ?? null];
|
||||
$err_url = Url::getFromRoute('/');
|
||||
|
||||
if ($this->dbi->isSuperUser()) {
|
||||
$this->dbi->selectDb('mysql');
|
||||
}
|
||||
|
||||
if (! $this->response->isAjax()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->response->addJSON([
|
||||
'message' => $this->monitor->getJsonForChartingData(
|
||||
$params['requiredData'] ?? ''
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
public function logDataTypeSlow(): void
|
||||
{
|
||||
global $err_url;
|
||||
|
||||
$params = [
|
||||
'time_start' => $_POST['time_start'] ?? null,
|
||||
'time_end' => $_POST['time_end'] ?? null,
|
||||
];
|
||||
$err_url = Url::getFromRoute('/');
|
||||
|
||||
if ($this->dbi->isSuperUser()) {
|
||||
$this->dbi->selectDb('mysql');
|
||||
}
|
||||
|
||||
if (! $this->response->isAjax()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->response->addJSON([
|
||||
'message' => $this->monitor->getJsonForLogDataTypeSlow(
|
||||
(int) $params['time_start'],
|
||||
(int) $params['time_end']
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
public function logDataTypeGeneral(): void
|
||||
{
|
||||
global $err_url;
|
||||
|
||||
$params = [
|
||||
'time_start' => $_POST['time_start'] ?? null,
|
||||
'time_end' => $_POST['time_end'] ?? null,
|
||||
'limitTypes' => $_POST['limitTypes'] ?? null,
|
||||
'removeVariables' => $_POST['removeVariables'] ?? null,
|
||||
];
|
||||
$err_url = Url::getFromRoute('/');
|
||||
|
||||
if ($this->dbi->isSuperUser()) {
|
||||
$this->dbi->selectDb('mysql');
|
||||
}
|
||||
|
||||
if (! $this->response->isAjax()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->response->addJSON([
|
||||
'message' => $this->monitor->getJsonForLogDataTypeGeneral(
|
||||
(int) $params['time_start'],
|
||||
(int) $params['time_end'],
|
||||
(bool) $params['limitTypes'],
|
||||
(bool) $params['removeVariables']
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
public function loggingVars(): void
|
||||
{
|
||||
global $err_url;
|
||||
|
||||
$params = [
|
||||
'varName' => $_POST['varName'] ?? null,
|
||||
'varValue' => $_POST['varValue'] ?? null,
|
||||
];
|
||||
$err_url = Url::getFromRoute('/');
|
||||
|
||||
if ($this->dbi->isSuperUser()) {
|
||||
$this->dbi->selectDb('mysql');
|
||||
}
|
||||
|
||||
if (! $this->response->isAjax()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->response->addJSON([
|
||||
'message' => $this->monitor->getJsonForLoggingVars(
|
||||
$params['varName'],
|
||||
$params['varValue']
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
public function queryAnalyzer(): void
|
||||
{
|
||||
global $err_url;
|
||||
|
||||
$params = [
|
||||
'database' => $_POST['database'] ?? null,
|
||||
'query' => $_POST['query'] ?? null,
|
||||
];
|
||||
$err_url = Url::getFromRoute('/');
|
||||
|
||||
if ($this->dbi->isSuperUser()) {
|
||||
$this->dbi->selectDb('mysql');
|
||||
}
|
||||
|
||||
if (! $this->response->isAjax()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->response->addJSON([
|
||||
'message' => $this->monitor->getJsonForQueryAnalyzer(
|
||||
$params['database'] ?? '',
|
||||
$params['query'] ?? ''
|
||||
),
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,283 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Server\Status;
|
||||
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Html\Generator;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Server\Status\Data;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
use function array_keys;
|
||||
use function count;
|
||||
use function mb_strtolower;
|
||||
use function strlen;
|
||||
use function ucfirst;
|
||||
|
||||
class ProcessesController extends AbstractController
|
||||
{
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param Data $data
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, $data, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template, $data);
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $err_url;
|
||||
|
||||
$params = [
|
||||
'showExecuting' => $_POST['showExecuting'] ?? null,
|
||||
'full' => $_POST['full'] ?? null,
|
||||
'column_name' => $_POST['column_name'] ?? null,
|
||||
'order_by_field' => $_POST['order_by_field'] ?? null,
|
||||
'sort_order' => $_POST['sort_order'] ?? null,
|
||||
];
|
||||
$err_url = Url::getFromRoute('/');
|
||||
|
||||
if ($this->dbi->isSuperUser()) {
|
||||
$this->dbi->selectDb('mysql');
|
||||
}
|
||||
|
||||
$this->addScriptFiles(['server/status/processes.js']);
|
||||
|
||||
$isChecked = false;
|
||||
if (! empty($params['showExecuting'])) {
|
||||
$isChecked = true;
|
||||
}
|
||||
|
||||
$urlParams = [
|
||||
'ajax_request' => true,
|
||||
'full' => $params['full'] ?? '',
|
||||
'column_name' => $params['column_name'] ?? '',
|
||||
'order_by_field' => $params['order_by_field'] ?? '',
|
||||
'sort_order' => $params['sort_order'] ?? '',
|
||||
];
|
||||
|
||||
$serverProcessList = $this->getList($params);
|
||||
|
||||
$this->render('server/status/processes/index', [
|
||||
'url_params' => $urlParams,
|
||||
'is_checked' => $isChecked,
|
||||
'server_process_list' => $serverProcessList,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Only sends the process list table
|
||||
*/
|
||||
public function refresh(): void
|
||||
{
|
||||
$params = [
|
||||
'showExecuting' => $_POST['showExecuting'] ?? null,
|
||||
'full' => $_POST['full'] ?? null,
|
||||
'column_name' => $_POST['column_name'] ?? null,
|
||||
'order_by_field' => $_POST['order_by_field'] ?? null,
|
||||
'sort_order' => $_POST['sort_order'] ?? null,
|
||||
];
|
||||
|
||||
if (! $this->response->isAjax()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->response->addHTML($this->getList($params));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $params Request parameters
|
||||
*/
|
||||
public function kill(array $params): void
|
||||
{
|
||||
if (! $this->response->isAjax()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$kill = (int) $params['id'];
|
||||
$query = $this->dbi->getKillQuery($kill);
|
||||
|
||||
if ($this->dbi->tryQuery($query)) {
|
||||
$message = Message::success(
|
||||
__('Thread %s was successfully killed.')
|
||||
);
|
||||
$this->response->setRequestStatus(true);
|
||||
} else {
|
||||
$message = Message::error(
|
||||
__(
|
||||
'phpMyAdmin was unable to kill thread %s.'
|
||||
. ' It probably has already been closed.'
|
||||
)
|
||||
);
|
||||
$this->response->setRequestStatus(false);
|
||||
}
|
||||
$message->addParam($kill);
|
||||
|
||||
$this->response->addJSON(['message' => $message]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $params Request parameters
|
||||
*/
|
||||
private function getList(array $params): string
|
||||
{
|
||||
$urlParams = [];
|
||||
|
||||
$showFullSql = ! empty($params['full']);
|
||||
if ($showFullSql) {
|
||||
$urlParams['full'] = '';
|
||||
} else {
|
||||
$urlParams['full'] = 1;
|
||||
}
|
||||
|
||||
// This array contains display name and real column name of each
|
||||
// sortable column in the table
|
||||
$sortableColumns = [
|
||||
[
|
||||
'column_name' => __('ID'),
|
||||
'order_by_field' => 'Id',
|
||||
],
|
||||
[
|
||||
'column_name' => __('User'),
|
||||
'order_by_field' => 'User',
|
||||
],
|
||||
[
|
||||
'column_name' => __('Host'),
|
||||
'order_by_field' => 'Host',
|
||||
],
|
||||
[
|
||||
'column_name' => __('Database'),
|
||||
'order_by_field' => 'db',
|
||||
],
|
||||
[
|
||||
'column_name' => __('Command'),
|
||||
'order_by_field' => 'Command',
|
||||
],
|
||||
[
|
||||
'column_name' => __('Time'),
|
||||
'order_by_field' => 'Time',
|
||||
],
|
||||
[
|
||||
'column_name' => __('Status'),
|
||||
'order_by_field' => 'State',
|
||||
],
|
||||
[
|
||||
'column_name' => __('Progress'),
|
||||
'order_by_field' => 'Progress',
|
||||
],
|
||||
[
|
||||
'column_name' => __('SQL query'),
|
||||
'order_by_field' => 'Info',
|
||||
],
|
||||
];
|
||||
$sortableColCount = count($sortableColumns);
|
||||
|
||||
$sqlQuery = $showFullSql
|
||||
? 'SHOW FULL PROCESSLIST'
|
||||
: 'SHOW PROCESSLIST';
|
||||
if ((! empty($params['order_by_field'])
|
||||
&& ! empty($params['sort_order']))
|
||||
|| ! empty($params['showExecuting'])
|
||||
) {
|
||||
$urlParams['order_by_field'] = $params['order_by_field'];
|
||||
$urlParams['sort_order'] = $params['sort_order'];
|
||||
$urlParams['showExecuting'] = $params['showExecuting'];
|
||||
$sqlQuery = 'SELECT * FROM `INFORMATION_SCHEMA`.`PROCESSLIST` ';
|
||||
}
|
||||
if (! empty($params['showExecuting'])) {
|
||||
$sqlQuery .= ' WHERE state != "" ';
|
||||
}
|
||||
if (! empty($params['order_by_field']) && ! empty($params['sort_order'])) {
|
||||
$sqlQuery .= ' ORDER BY '
|
||||
. Util::backquote($params['order_by_field'])
|
||||
. ' ' . $params['sort_order'];
|
||||
}
|
||||
|
||||
$result = $this->dbi->query($sqlQuery);
|
||||
|
||||
$columns = [];
|
||||
foreach ($sortableColumns as $columnKey => $column) {
|
||||
$is_sorted = ! empty($params['order_by_field'])
|
||||
&& ! empty($params['sort_order'])
|
||||
&& ($params['order_by_field'] == $column['order_by_field']);
|
||||
|
||||
$column['sort_order'] = 'ASC';
|
||||
if ($is_sorted && $params['sort_order'] === 'ASC') {
|
||||
$column['sort_order'] = 'DESC';
|
||||
}
|
||||
if (isset($params['showExecuting'])) {
|
||||
$column['showExecuting'] = 'on';
|
||||
}
|
||||
|
||||
$columns[$columnKey] = [
|
||||
'name' => $column['column_name'],
|
||||
'params' => $column,
|
||||
'is_sorted' => $is_sorted,
|
||||
'sort_order' => $column['sort_order'],
|
||||
'has_full_query' => false,
|
||||
'is_full' => false,
|
||||
];
|
||||
|
||||
if (0 !== --$sortableColCount) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$columns[$columnKey]['has_full_query'] = true;
|
||||
if (! $showFullSql) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$columns[$columnKey]['is_full'] = true;
|
||||
}
|
||||
|
||||
$rows = [];
|
||||
while ($process = $this->dbi->fetchAssoc($result)) {
|
||||
// Array keys need to modify due to the way it has used
|
||||
// to display column values
|
||||
if ((! empty($params['order_by_field']) && ! empty($params['sort_order']))
|
||||
|| ! empty($params['showExecuting'])
|
||||
) {
|
||||
foreach (array_keys($process) as $key) {
|
||||
$newKey = ucfirst(mb_strtolower($key));
|
||||
if ($newKey === $key) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$process[$newKey] = $process[$key];
|
||||
unset($process[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
$rows[] = [
|
||||
'id' => $process['Id'],
|
||||
'user' => $process['User'],
|
||||
'host' => $process['Host'],
|
||||
'db' => ! isset($process['db']) || strlen($process['db']) === 0 ? '' : $process['db'],
|
||||
'command' => $process['Command'],
|
||||
'time' => $process['Time'],
|
||||
'state' => ! empty($process['State']) ? $process['State'] : '---',
|
||||
'progress' => ! empty($process['Progress']) ? $process['Progress'] : '---',
|
||||
'info' => ! empty($process['Info']) ? Generator::formatSql(
|
||||
$process['Info'],
|
||||
! $showFullSql
|
||||
) : '---',
|
||||
];
|
||||
}
|
||||
|
||||
return $this->template->render('server/status/processes/list', [
|
||||
'columns' => $columns,
|
||||
'rows' => $rows,
|
||||
'refresh_params' => $urlParams,
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
<?php
|
||||
/**
|
||||
* Displays query statistics for the server
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Server\Status;
|
||||
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Server\Status\Data;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use function array_sum;
|
||||
use function arsort;
|
||||
use function count;
|
||||
use function str_replace;
|
||||
|
||||
class QueriesController extends AbstractController
|
||||
{
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param Data $data
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, $data, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template, $data);
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $err_url;
|
||||
|
||||
$err_url = Url::getFromRoute('/');
|
||||
|
||||
if ($this->dbi->isSuperUser()) {
|
||||
$this->dbi->selectDb('mysql');
|
||||
}
|
||||
|
||||
$this->addScriptFiles([
|
||||
'chart.js',
|
||||
'vendor/jqplot/jquery.jqplot.js',
|
||||
'vendor/jqplot/plugins/jqplot.pieRenderer.js',
|
||||
'vendor/jqplot/plugins/jqplot.highlighter.js',
|
||||
'vendor/jqplot/plugins/jqplot.enhancedPieLegendRenderer.js',
|
||||
'vendor/jquery/jquery.tablesorter.js',
|
||||
'server/status/sorter.js',
|
||||
'server/status/queries.js',
|
||||
]);
|
||||
|
||||
if ($this->data->dataLoaded) {
|
||||
$hourFactor = 3600 / $this->data->status['Uptime'];
|
||||
$usedQueries = $this->data->usedQueries;
|
||||
$totalQueries = array_sum($usedQueries);
|
||||
|
||||
$stats = [
|
||||
'total' => $totalQueries,
|
||||
'per_hour' => $totalQueries * $hourFactor,
|
||||
'per_minute' => $totalQueries * 60 / $this->data->status['Uptime'],
|
||||
'per_second' => $totalQueries / $this->data->status['Uptime'],
|
||||
];
|
||||
|
||||
// reverse sort by value to show most used statements first
|
||||
arsort($usedQueries);
|
||||
|
||||
$chart = [];
|
||||
$querySum = array_sum($usedQueries);
|
||||
$otherSum = 0;
|
||||
$queries = [];
|
||||
foreach ($usedQueries as $key => $value) {
|
||||
// For the percentage column, use Questions - Connections, because
|
||||
// the number of connections is not an item of the Query types
|
||||
// but is included in Questions. Then the total of the percentages is 100.
|
||||
$name = str_replace(['Com_', '_'], ['', ' '], $key);
|
||||
// Group together values that make out less than 2% into "Other", but only
|
||||
// if we have more than 6 fractions already
|
||||
if ($value < $querySum * 0.02 && count($chart) > 6) {
|
||||
$otherSum += $value;
|
||||
} else {
|
||||
$chart[$name] = $value;
|
||||
}
|
||||
|
||||
$queries[$key] = [
|
||||
'name' => $name,
|
||||
'value' => $value,
|
||||
'per_hour' => $value * $hourFactor,
|
||||
'percentage' => $value * 100 / $totalQueries,
|
||||
];
|
||||
}
|
||||
|
||||
if ($otherSum > 0) {
|
||||
$chart[__('Other')] = $otherSum;
|
||||
}
|
||||
}
|
||||
|
||||
$this->render('server/status/queries/index', [
|
||||
'is_data_loaded' => $this->data->dataLoaded,
|
||||
'stats' => $stats ?? null,
|
||||
'queries' => $queries ?? [],
|
||||
'chart' => $chart ?? [],
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,256 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Server\Status;
|
||||
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\ReplicationGui;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Server\Status\Data;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
use function implode;
|
||||
|
||||
/**
|
||||
* Object the server status page: processes, connections and traffic.
|
||||
*/
|
||||
class StatusController extends AbstractController
|
||||
{
|
||||
/** @var ReplicationGui */
|
||||
private $replicationGui;
|
||||
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param Data $data
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, $data, ReplicationGui $replicationGui, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template, $data);
|
||||
$this->replicationGui = $replicationGui;
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $err_url;
|
||||
|
||||
$err_url = Url::getFromRoute('/');
|
||||
|
||||
if ($this->dbi->isSuperUser()) {
|
||||
$this->dbi->selectDb('mysql');
|
||||
}
|
||||
|
||||
$replicationInfo = $this->data->getReplicationInfo();
|
||||
$primaryInfo = $replicationInfo->getPrimaryInfo();
|
||||
$replicaInfo = $replicationInfo->getReplicaInfo();
|
||||
|
||||
$traffic = [];
|
||||
$connections = [];
|
||||
$replication = '';
|
||||
if ($this->data->dataLoaded) {
|
||||
// In some case the data was reported not to exist, check it for all keys
|
||||
if (isset($this->data->status['Bytes_received'], $this->data->status['Bytes_sent'])) {
|
||||
$networkTraffic = implode(
|
||||
' ',
|
||||
Util::formatByteDown(
|
||||
$this->data->status['Bytes_received'] + $this->data->status['Bytes_sent'],
|
||||
3,
|
||||
1
|
||||
)
|
||||
);
|
||||
}
|
||||
if (isset($this->data->status['Uptime'])) {
|
||||
$uptime = Util::timespanFormat($this->data->status['Uptime']);
|
||||
}
|
||||
$startTime = Util::localisedDate($this->getStartTime());
|
||||
|
||||
$traffic = $this->getTrafficInfo();
|
||||
|
||||
$connections = $this->getConnectionsInfo();
|
||||
|
||||
if ($primaryInfo['status']) {
|
||||
$replication .= $this->replicationGui->getHtmlForReplicationStatusTable('master');
|
||||
}
|
||||
if ($replicaInfo['status']) {
|
||||
$replication .= $this->replicationGui->getHtmlForReplicationStatusTable('slave');
|
||||
}
|
||||
}
|
||||
|
||||
$this->render('server/status/status/index', [
|
||||
'is_data_loaded' => $this->data->dataLoaded,
|
||||
'network_traffic' => $networkTraffic ?? null,
|
||||
'uptime' => $uptime ?? null,
|
||||
'start_time' => $startTime ?? null,
|
||||
'traffic' => $traffic,
|
||||
'connections' => $connections,
|
||||
'is_master' => $primaryInfo['status'],
|
||||
'is_slave' => $replicaInfo['status'],
|
||||
'replication' => $replication,
|
||||
]);
|
||||
}
|
||||
|
||||
private function getStartTime(): int
|
||||
{
|
||||
return (int) $this->dbi->fetchValue(
|
||||
'SELECT UNIX_TIMESTAMP() - ' . $this->data->status['Uptime']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function getTrafficInfo(): array
|
||||
{
|
||||
$hourFactor = 3600 / $this->data->status['Uptime'];
|
||||
|
||||
return [
|
||||
[
|
||||
'name' => __('Received'),
|
||||
'number' => implode(
|
||||
' ',
|
||||
Util::formatByteDown(
|
||||
$this->data->status['Bytes_received'],
|
||||
3,
|
||||
1
|
||||
)
|
||||
),
|
||||
'per_hour' => implode(
|
||||
' ',
|
||||
Util::formatByteDown(
|
||||
$this->data->status['Bytes_received'] * $hourFactor,
|
||||
3,
|
||||
1
|
||||
)
|
||||
),
|
||||
],
|
||||
[
|
||||
'name' => __('Sent'),
|
||||
'number' => implode(
|
||||
' ',
|
||||
Util::formatByteDown(
|
||||
$this->data->status['Bytes_sent'],
|
||||
3,
|
||||
1
|
||||
)
|
||||
),
|
||||
'per_hour' => implode(
|
||||
' ',
|
||||
Util::formatByteDown(
|
||||
$this->data->status['Bytes_sent'] * $hourFactor,
|
||||
3,
|
||||
1
|
||||
)
|
||||
),
|
||||
],
|
||||
[
|
||||
'name' => __('Total'),
|
||||
'number' => implode(
|
||||
' ',
|
||||
Util::formatByteDown(
|
||||
$this->data->status['Bytes_received'] + $this->data->status['Bytes_sent'],
|
||||
3,
|
||||
1
|
||||
)
|
||||
),
|
||||
'per_hour' => implode(
|
||||
' ',
|
||||
Util::formatByteDown(
|
||||
($this->data->status['Bytes_received'] + $this->data->status['Bytes_sent']) * $hourFactor,
|
||||
3,
|
||||
1
|
||||
)
|
||||
),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function getConnectionsInfo(): array
|
||||
{
|
||||
$hourFactor = 3600 / $this->data->status['Uptime'];
|
||||
|
||||
$failedAttemptsPercentage = '---';
|
||||
$abortedPercentage = '---';
|
||||
if ($this->data->status['Connections'] > 0) {
|
||||
$failedAttemptsPercentage = Util::formatNumber(
|
||||
$this->data->status['Aborted_connects'] * 100 / $this->data->status['Connections'],
|
||||
0,
|
||||
2,
|
||||
true
|
||||
) . '%';
|
||||
|
||||
$abortedPercentage = Util::formatNumber(
|
||||
$this->data->status['Aborted_clients'] * 100 / $this->data->status['Connections'],
|
||||
0,
|
||||
2,
|
||||
true
|
||||
) . '%';
|
||||
}
|
||||
|
||||
return [
|
||||
[
|
||||
'name' => __('Max. concurrent connections'),
|
||||
'number' => Util::formatNumber(
|
||||
$this->data->status['Max_used_connections'],
|
||||
0
|
||||
),
|
||||
'per_hour' => '---',
|
||||
'percentage' => '---',
|
||||
],
|
||||
[
|
||||
'name' => __('Failed attempts'),
|
||||
'number' => Util::formatNumber(
|
||||
$this->data->status['Aborted_connects'],
|
||||
4,
|
||||
1,
|
||||
true
|
||||
),
|
||||
'per_hour' => Util::formatNumber(
|
||||
$this->data->status['Aborted_connects'] * $hourFactor,
|
||||
4,
|
||||
2,
|
||||
true
|
||||
),
|
||||
'percentage' => $failedAttemptsPercentage,
|
||||
],
|
||||
[
|
||||
'name' => __('Aborted'),
|
||||
'number' => Util::formatNumber(
|
||||
$this->data->status['Aborted_clients'],
|
||||
4,
|
||||
1,
|
||||
true
|
||||
),
|
||||
'per_hour' => Util::formatNumber(
|
||||
$this->data->status['Aborted_clients'] * $hourFactor,
|
||||
4,
|
||||
2,
|
||||
true
|
||||
),
|
||||
'percentage' => $abortedPercentage,
|
||||
],
|
||||
[
|
||||
'name' => __('Total'),
|
||||
'number' => Util::formatNumber(
|
||||
$this->data->status['Connections'],
|
||||
4,
|
||||
0
|
||||
),
|
||||
'per_hour' => Util::formatNumber(
|
||||
$this->data->status['Connections'] * $hourFactor,
|
||||
4,
|
||||
2
|
||||
),
|
||||
'percentage' => Util::formatNumber(100, 0, 2) . '%',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,679 @@
|
|||
<?php
|
||||
/**
|
||||
* Displays a list of server status variables
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Server\Status;
|
||||
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Html\Generator;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Server\Status\Data;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use function in_array;
|
||||
use function is_numeric;
|
||||
use function mb_strpos;
|
||||
|
||||
class VariablesController extends AbstractController
|
||||
{
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param Data $data
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, $data, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template, $data);
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $err_url;
|
||||
|
||||
$params = [
|
||||
'flush' => $_POST['flush'] ?? null,
|
||||
'filterAlert' => $_POST['filterAlert'] ?? null,
|
||||
'filterText' => $_POST['filterText'] ?? null,
|
||||
'filterCategory' => $_POST['filterCategory'] ?? null,
|
||||
'dontFormat' => $_POST['dontFormat'] ?? null,
|
||||
];
|
||||
$err_url = Url::getFromRoute('/');
|
||||
|
||||
if ($this->dbi->isSuperUser()) {
|
||||
$this->dbi->selectDb('mysql');
|
||||
}
|
||||
|
||||
$this->addScriptFiles([
|
||||
'server/status/variables.js',
|
||||
'vendor/jquery/jquery.tablesorter.js',
|
||||
'server/status/sorter.js',
|
||||
]);
|
||||
|
||||
if (isset($params['flush'])) {
|
||||
$this->flush($params['flush']);
|
||||
}
|
||||
|
||||
if ($this->data->dataLoaded) {
|
||||
$categories = [];
|
||||
foreach ($this->data->sections as $sectionId => $sectionName) {
|
||||
if (! isset($this->data->sectionUsed[$sectionId])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$categories[$sectionId] = [
|
||||
'id' => $sectionId,
|
||||
'name' => $sectionName,
|
||||
'is_selected' => false,
|
||||
];
|
||||
if (empty($params['filterCategory'])
|
||||
|| $params['filterCategory'] !== $sectionId
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$categories[$sectionId]['is_selected'] = true;
|
||||
}
|
||||
|
||||
$links = [];
|
||||
foreach ($this->data->links as $sectionName => $sectionLinks) {
|
||||
$links[$sectionName] = [
|
||||
'name' => 'status_' . $sectionName,
|
||||
'links' => $sectionLinks,
|
||||
];
|
||||
}
|
||||
|
||||
$descriptions = $this->getDescriptions();
|
||||
$alerts = $this->getAlerts();
|
||||
|
||||
$variables = [];
|
||||
foreach ($this->data->status as $name => $value) {
|
||||
$variables[$name] = [
|
||||
'name' => $name,
|
||||
'value' => $value,
|
||||
'is_numeric' => is_numeric($value),
|
||||
'class' => $this->data->allocationMap[$name] ?? null,
|
||||
'doc' => '',
|
||||
'has_alert' => false,
|
||||
'is_alert' => false,
|
||||
'description' => $descriptions[$name] ?? '',
|
||||
'description_doc' => [],
|
||||
];
|
||||
|
||||
// Fields containing % are calculated,
|
||||
// they can not be described in MySQL documentation
|
||||
if (mb_strpos($name, '%') === false) {
|
||||
$variables[$name]['doc'] = Generator::linkToVarDocumentation(
|
||||
$name,
|
||||
$this->dbi->isMariaDB()
|
||||
);
|
||||
}
|
||||
|
||||
if (isset($alerts[$name])) {
|
||||
$variables[$name]['has_alert'] = true;
|
||||
if ($value > $alerts[$name]) {
|
||||
$variables[$name]['is_alert'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (! isset($this->data->links[$name])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($this->data->links[$name] as $linkName => $linkUrl) {
|
||||
$variables[$name]['description_doc'][] = [
|
||||
'name' => $linkName,
|
||||
'url' => $linkUrl,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->render('server/status/variables/index', [
|
||||
'is_data_loaded' => $this->data->dataLoaded,
|
||||
'filter_text' => ! empty($params['filterText']) ? $params['filterText'] : '',
|
||||
'is_only_alerts' => ! empty($params['filterAlert']),
|
||||
'is_not_formatted' => ! empty($params['dontFormat']),
|
||||
'categories' => $categories ?? [],
|
||||
'links' => $links ?? [],
|
||||
'variables' => $variables ?? [],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush status variables if requested
|
||||
*
|
||||
* @param string $flush Variable name
|
||||
*/
|
||||
private function flush(string $flush): void
|
||||
{
|
||||
$flushCommands = [
|
||||
'STATUS',
|
||||
'TABLES',
|
||||
'QUERY CACHE',
|
||||
];
|
||||
|
||||
if (! in_array($flush, $flushCommands)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->dbi->query('FLUSH ' . $flush . ';');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function getAlerts(): array
|
||||
{
|
||||
// name => max value before alert
|
||||
return [
|
||||
// lower is better
|
||||
// variable => max value
|
||||
'Aborted_clients' => 0,
|
||||
'Aborted_connects' => 0,
|
||||
|
||||
'Binlog_cache_disk_use' => 0,
|
||||
|
||||
'Created_tmp_disk_tables' => 0,
|
||||
|
||||
'Handler_read_rnd' => 0,
|
||||
'Handler_read_rnd_next' => 0,
|
||||
|
||||
'Innodb_buffer_pool_pages_dirty' => 0,
|
||||
'Innodb_buffer_pool_reads' => 0,
|
||||
'Innodb_buffer_pool_wait_free' => 0,
|
||||
'Innodb_log_waits' => 0,
|
||||
'Innodb_row_lock_time_avg' => 10, // ms
|
||||
'Innodb_row_lock_time_max' => 50, // ms
|
||||
'Innodb_row_lock_waits' => 0,
|
||||
|
||||
'Slow_queries' => 0,
|
||||
'Delayed_errors' => 0,
|
||||
'Select_full_join' => 0,
|
||||
'Select_range_check' => 0,
|
||||
'Sort_merge_passes' => 0,
|
||||
'Opened_tables' => 0,
|
||||
'Table_locks_waited' => 0,
|
||||
'Qcache_lowmem_prunes' => 0,
|
||||
|
||||
'Qcache_free_blocks' =>
|
||||
isset($this->data->status['Qcache_total_blocks'])
|
||||
? $this->data->status['Qcache_total_blocks'] / 5
|
||||
: 0,
|
||||
'Slow_launch_threads' => 0,
|
||||
|
||||
// depends on Key_read_requests
|
||||
// normally lower then 1:0.01
|
||||
'Key_reads' => isset($this->data->status['Key_read_requests'])
|
||||
? 0.01 * $this->data->status['Key_read_requests'] : 0,
|
||||
// depends on Key_write_requests
|
||||
// normally nearly 1:1
|
||||
'Key_writes' => isset($this->data->status['Key_write_requests'])
|
||||
? 0.9 * $this->data->status['Key_write_requests'] : 0,
|
||||
|
||||
'Key_buffer_fraction' => 0.5,
|
||||
|
||||
// alert if more than 95% of thread cache is in use
|
||||
'Threads_cached' => isset($this->data->variables['thread_cache_size'])
|
||||
? 0.95 * $this->data->variables['thread_cache_size'] : 0,
|
||||
|
||||
// higher is better
|
||||
// variable => min value
|
||||
//'Handler read key' => '> ',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of variable descriptions
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getDescriptions(): array
|
||||
{
|
||||
/**
|
||||
* Messages are built using the message name
|
||||
*/
|
||||
return [
|
||||
'Aborted_clients' => __(
|
||||
'The number of connections that were aborted because the client died'
|
||||
. ' without closing the connection properly.'
|
||||
),
|
||||
'Aborted_connects' => __(
|
||||
'The number of failed attempts to connect to the MySQL server.'
|
||||
),
|
||||
'Binlog_cache_disk_use' => __(
|
||||
'The number of transactions that used the temporary binary log cache'
|
||||
. ' but that exceeded the value of binlog_cache_size and used a'
|
||||
. ' temporary file to store statements from the transaction.'
|
||||
),
|
||||
'Binlog_cache_use' => __(
|
||||
'The number of transactions that used the temporary binary log cache.'
|
||||
),
|
||||
'Connections' => __(
|
||||
'The number of connection attempts (successful or not)'
|
||||
. ' to the MySQL server.'
|
||||
),
|
||||
'Created_tmp_disk_tables' => __(
|
||||
'The number of temporary tables on disk created automatically by'
|
||||
. ' the server while executing statements. If'
|
||||
. ' Created_tmp_disk_tables is big, you may want to increase the'
|
||||
. ' tmp_table_size value to cause temporary tables to be'
|
||||
. ' memory-based instead of disk-based.'
|
||||
),
|
||||
'Created_tmp_files' => __(
|
||||
'How many temporary files mysqld has created.'
|
||||
),
|
||||
'Created_tmp_tables' => __(
|
||||
'The number of in-memory temporary tables created automatically'
|
||||
. ' by the server while executing statements.'
|
||||
),
|
||||
'Delayed_errors' => __(
|
||||
'The number of rows written with INSERT DELAYED for which some'
|
||||
. ' error occurred (probably duplicate key).'
|
||||
),
|
||||
'Delayed_insert_threads' => __(
|
||||
'The number of INSERT DELAYED handler threads in use. Every'
|
||||
. ' different table on which one uses INSERT DELAYED gets'
|
||||
. ' its own thread.'
|
||||
),
|
||||
'Delayed_writes' => __(
|
||||
'The number of INSERT DELAYED rows written.'
|
||||
),
|
||||
'Flush_commands' => __(
|
||||
'The number of executed FLUSH statements.'
|
||||
),
|
||||
'Handler_commit' => __(
|
||||
'The number of internal COMMIT statements.'
|
||||
),
|
||||
'Handler_delete' => __(
|
||||
'The number of times a row was deleted from a table.'
|
||||
),
|
||||
'Handler_discover' => __(
|
||||
'The MySQL server can ask the NDB Cluster storage engine if it'
|
||||
. ' knows about a table with a given name. This is called discovery.'
|
||||
. ' Handler_discover indicates the number of time tables have been'
|
||||
. ' discovered.'
|
||||
),
|
||||
'Handler_read_first' => __(
|
||||
'The number of times the first entry was read from an index. If this'
|
||||
. ' is high, it suggests that the server is doing a lot of full'
|
||||
. ' index scans; for example, SELECT col1 FROM foo, assuming that'
|
||||
. ' col1 is indexed.'
|
||||
),
|
||||
'Handler_read_key' => __(
|
||||
'The number of requests to read a row based on a key. If this is'
|
||||
. ' high, it is a good indication that your queries and tables'
|
||||
. ' are properly indexed.'
|
||||
),
|
||||
'Handler_read_next' => __(
|
||||
'The number of requests to read the next row in key order. This is'
|
||||
. ' incremented if you are querying an index column with a range'
|
||||
. ' constraint or if you are doing an index scan.'
|
||||
),
|
||||
'Handler_read_prev' => __(
|
||||
'The number of requests to read the previous row in key order.'
|
||||
. ' This read method is mainly used to optimize ORDER BY … DESC.'
|
||||
),
|
||||
'Handler_read_rnd' => __(
|
||||
'The number of requests to read a row based on a fixed position.'
|
||||
. ' This is high if you are doing a lot of queries that require'
|
||||
. ' sorting of the result. You probably have a lot of queries that'
|
||||
. ' require MySQL to scan whole tables or you have joins that'
|
||||
. ' don\'t use keys properly.'
|
||||
),
|
||||
'Handler_read_rnd_next' => __(
|
||||
'The number of requests to read the next row in the data file.'
|
||||
. ' This is high if you are doing a lot of table scans. Generally'
|
||||
. ' this suggests that your tables are not properly indexed or that'
|
||||
. ' your queries are not written to take advantage of the indexes'
|
||||
. ' you have.'
|
||||
),
|
||||
'Handler_rollback' => __(
|
||||
'The number of internal ROLLBACK statements.'
|
||||
),
|
||||
'Handler_update' => __(
|
||||
'The number of requests to update a row in a table.'
|
||||
),
|
||||
'Handler_write' => __(
|
||||
'The number of requests to insert a row in a table.'
|
||||
),
|
||||
'Innodb_buffer_pool_pages_data' => __(
|
||||
'The number of pages containing data (dirty or clean).'
|
||||
),
|
||||
'Innodb_buffer_pool_pages_dirty' => __(
|
||||
'The number of pages currently dirty.'
|
||||
),
|
||||
'Innodb_buffer_pool_pages_flushed' => __(
|
||||
'The number of buffer pool pages that have been requested'
|
||||
. ' to be flushed.'
|
||||
),
|
||||
'Innodb_buffer_pool_pages_free' => __(
|
||||
'The number of free pages.'
|
||||
),
|
||||
'Innodb_buffer_pool_pages_latched' => __(
|
||||
'The number of latched pages in InnoDB buffer pool. These are pages'
|
||||
. ' currently being read or written or that can\'t be flushed or'
|
||||
. ' removed for some other reason.'
|
||||
),
|
||||
'Innodb_buffer_pool_pages_misc' => __(
|
||||
'The number of pages busy because they have been allocated for'
|
||||
. ' administrative overhead such as row locks or the adaptive'
|
||||
. ' hash index. This value can also be calculated as'
|
||||
. ' Innodb_buffer_pool_pages_total - Innodb_buffer_pool_pages_free'
|
||||
. ' - Innodb_buffer_pool_pages_data.'
|
||||
),
|
||||
'Innodb_buffer_pool_pages_total' => __(
|
||||
'Total size of buffer pool, in pages.'
|
||||
),
|
||||
'Innodb_buffer_pool_read_ahead_rnd' => __(
|
||||
'The number of "random" read-aheads InnoDB initiated. This happens'
|
||||
. ' when a query is to scan a large portion of a table but in'
|
||||
. ' random order.'
|
||||
),
|
||||
'Innodb_buffer_pool_read_ahead_seq' => __(
|
||||
'The number of sequential read-aheads InnoDB initiated. This'
|
||||
. ' happens when InnoDB does a sequential full table scan.'
|
||||
),
|
||||
'Innodb_buffer_pool_read_requests' => __(
|
||||
'The number of logical read requests InnoDB has done.'
|
||||
),
|
||||
'Innodb_buffer_pool_reads' => __(
|
||||
'The number of logical reads that InnoDB could not satisfy'
|
||||
. ' from buffer pool and had to do a single-page read.'
|
||||
),
|
||||
'Innodb_buffer_pool_wait_free' => __(
|
||||
'Normally, writes to the InnoDB buffer pool happen in the'
|
||||
. ' background. However, if it\'s necessary to read or create a page'
|
||||
. ' and no clean pages are available, it\'s necessary to wait for'
|
||||
. ' pages to be flushed first. This counter counts instances of'
|
||||
. ' these waits. If the buffer pool size was set properly, this'
|
||||
. ' value should be small.'
|
||||
),
|
||||
'Innodb_buffer_pool_write_requests' => __(
|
||||
'The number writes done to the InnoDB buffer pool.'
|
||||
),
|
||||
'Innodb_data_fsyncs' => __(
|
||||
'The number of fsync() operations so far.'
|
||||
),
|
||||
'Innodb_data_pending_fsyncs' => __(
|
||||
'The current number of pending fsync() operations.'
|
||||
),
|
||||
'Innodb_data_pending_reads' => __(
|
||||
'The current number of pending reads.'
|
||||
),
|
||||
'Innodb_data_pending_writes' => __(
|
||||
'The current number of pending writes.'
|
||||
),
|
||||
'Innodb_data_read' => __(
|
||||
'The amount of data read so far, in bytes.'
|
||||
),
|
||||
'Innodb_data_reads' => __(
|
||||
'The total number of data reads.'
|
||||
),
|
||||
'Innodb_data_writes' => __(
|
||||
'The total number of data writes.'
|
||||
),
|
||||
'Innodb_data_written' => __(
|
||||
'The amount of data written so far, in bytes.'
|
||||
),
|
||||
'Innodb_dblwr_pages_written' => __(
|
||||
'The number of pages that have been written for'
|
||||
. ' doublewrite operations.'
|
||||
),
|
||||
'Innodb_dblwr_writes' => __(
|
||||
'The number of doublewrite operations that have been performed.'
|
||||
),
|
||||
'Innodb_log_waits' => __(
|
||||
'The number of waits we had because log buffer was too small and'
|
||||
. ' we had to wait for it to be flushed before continuing.'
|
||||
),
|
||||
'Innodb_log_write_requests' => __(
|
||||
'The number of log write requests.'
|
||||
),
|
||||
'Innodb_log_writes' => __(
|
||||
'The number of physical writes to the log file.'
|
||||
),
|
||||
'Innodb_os_log_fsyncs' => __(
|
||||
'The number of fsync() writes done to the log file.'
|
||||
),
|
||||
'Innodb_os_log_pending_fsyncs' => __(
|
||||
'The number of pending log file fsyncs.'
|
||||
),
|
||||
'Innodb_os_log_pending_writes' => __(
|
||||
'Pending log file writes.'
|
||||
),
|
||||
'Innodb_os_log_written' => __(
|
||||
'The number of bytes written to the log file.'
|
||||
),
|
||||
'Innodb_pages_created' => __(
|
||||
'The number of pages created.'
|
||||
),
|
||||
'Innodb_page_size' => __(
|
||||
'The compiled-in InnoDB page size (default 16KB). Many values are'
|
||||
. ' counted in pages; the page size allows them to be easily'
|
||||
. ' converted to bytes.'
|
||||
),
|
||||
'Innodb_pages_read' => __(
|
||||
'The number of pages read.'
|
||||
),
|
||||
'Innodb_pages_written' => __(
|
||||
'The number of pages written.'
|
||||
),
|
||||
'Innodb_row_lock_current_waits' => __(
|
||||
'The number of row locks currently being waited for.'
|
||||
),
|
||||
'Innodb_row_lock_time_avg' => __(
|
||||
'The average time to acquire a row lock, in milliseconds.'
|
||||
),
|
||||
'Innodb_row_lock_time' => __(
|
||||
'The total time spent in acquiring row locks, in milliseconds.'
|
||||
),
|
||||
'Innodb_row_lock_time_max' => __(
|
||||
'The maximum time to acquire a row lock, in milliseconds.'
|
||||
),
|
||||
'Innodb_row_lock_waits' => __(
|
||||
'The number of times a row lock had to be waited for.'
|
||||
),
|
||||
'Innodb_rows_deleted' => __(
|
||||
'The number of rows deleted from InnoDB tables.'
|
||||
),
|
||||
'Innodb_rows_inserted' => __(
|
||||
'The number of rows inserted in InnoDB tables.'
|
||||
),
|
||||
'Innodb_rows_read' => __(
|
||||
'The number of rows read from InnoDB tables.'
|
||||
),
|
||||
'Innodb_rows_updated' => __(
|
||||
'The number of rows updated in InnoDB tables.'
|
||||
),
|
||||
'Key_blocks_not_flushed' => __(
|
||||
'The number of key blocks in the key cache that have changed but'
|
||||
. ' haven\'t yet been flushed to disk. It used to be known as'
|
||||
. ' Not_flushed_key_blocks.'
|
||||
),
|
||||
'Key_blocks_unused' => __(
|
||||
'The number of unused blocks in the key cache. You can use this'
|
||||
. ' value to determine how much of the key cache is in use.'
|
||||
),
|
||||
'Key_blocks_used' => __(
|
||||
'The number of used blocks in the key cache. This value is a'
|
||||
. ' high-water mark that indicates the maximum number of blocks'
|
||||
. ' that have ever been in use at one time.'
|
||||
),
|
||||
'Key_buffer_fraction_%' => __(
|
||||
'Percentage of used key cache (calculated value)'
|
||||
),
|
||||
'Key_read_requests' => __(
|
||||
'The number of requests to read a key block from the cache.'
|
||||
),
|
||||
'Key_reads' => __(
|
||||
'The number of physical reads of a key block from disk. If Key_reads'
|
||||
. ' is big, then your key_buffer_size value is probably too small.'
|
||||
. ' The cache miss rate can be calculated as'
|
||||
. ' Key_reads/Key_read_requests.'
|
||||
),
|
||||
'Key_read_ratio_%' => __(
|
||||
'Key cache miss calculated as rate of physical reads compared'
|
||||
. ' to read requests (calculated value)'
|
||||
),
|
||||
'Key_write_requests' => __(
|
||||
'The number of requests to write a key block to the cache.'
|
||||
),
|
||||
'Key_writes' => __(
|
||||
'The number of physical writes of a key block to disk.'
|
||||
),
|
||||
'Key_write_ratio_%' => __(
|
||||
'Percentage of physical writes compared'
|
||||
. ' to write requests (calculated value)'
|
||||
),
|
||||
'Last_query_cost' => __(
|
||||
'The total cost of the last compiled query as computed by the query'
|
||||
. ' optimizer. Useful for comparing the cost of different query'
|
||||
. ' plans for the same query. The default value of 0 means that'
|
||||
. ' no query has been compiled yet.'
|
||||
),
|
||||
'Max_used_connections' => __(
|
||||
'The maximum number of connections that have been in use'
|
||||
. ' simultaneously since the server started.'
|
||||
),
|
||||
'Not_flushed_delayed_rows' => __(
|
||||
'The number of rows waiting to be written in INSERT DELAYED queues.'
|
||||
),
|
||||
'Opened_tables' => __(
|
||||
'The number of tables that have been opened. If opened tables is'
|
||||
. ' big, your table_open_cache value is probably too small.'
|
||||
),
|
||||
'Open_files' => __(
|
||||
'The number of files that are open.'
|
||||
),
|
||||
'Open_streams' => __(
|
||||
'The number of streams that are open (used mainly for logging).'
|
||||
),
|
||||
'Open_tables' => __(
|
||||
'The number of tables that are open.'
|
||||
),
|
||||
'Qcache_free_blocks' => __(
|
||||
'The number of free memory blocks in query cache. High numbers can'
|
||||
. ' indicate fragmentation issues, which may be solved by issuing'
|
||||
. ' a FLUSH QUERY CACHE statement.'
|
||||
),
|
||||
'Qcache_free_memory' => __(
|
||||
'The amount of free memory for query cache.'
|
||||
),
|
||||
'Qcache_hits' => __(
|
||||
'The number of cache hits.'
|
||||
),
|
||||
'Qcache_inserts' => __(
|
||||
'The number of queries added to the cache.'
|
||||
),
|
||||
'Qcache_lowmem_prunes' => __(
|
||||
'The number of queries that have been removed from the cache to'
|
||||
. ' free up memory for caching new queries. This information can'
|
||||
. ' help you tune the query cache size. The query cache uses a'
|
||||
. ' least recently used (LRU) strategy to decide which queries'
|
||||
. ' to remove from the cache.'
|
||||
),
|
||||
'Qcache_not_cached' => __(
|
||||
'The number of non-cached queries (not cachable, or not cached'
|
||||
. ' due to the query_cache_type setting).'
|
||||
),
|
||||
'Qcache_queries_in_cache' => __(
|
||||
'The number of queries registered in the cache.'
|
||||
),
|
||||
'Qcache_total_blocks' => __(
|
||||
'The total number of blocks in the query cache.'
|
||||
),
|
||||
'Rpl_status' => __(
|
||||
'The status of failsafe replication (not yet implemented).'
|
||||
),
|
||||
'Select_full_join' => __(
|
||||
'The number of joins that do not use indexes. If this value is'
|
||||
. ' not 0, you should carefully check the indexes of your tables.'
|
||||
),
|
||||
'Select_full_range_join' => __(
|
||||
'The number of joins that used a range search on a reference table.'
|
||||
),
|
||||
'Select_range_check' => __(
|
||||
'The number of joins without keys that check for key usage after'
|
||||
. ' each row. (If this is not 0, you should carefully check the'
|
||||
. ' indexes of your tables.)'
|
||||
),
|
||||
'Select_range' => __(
|
||||
'The number of joins that used ranges on the first table. (It\'s'
|
||||
. ' normally not critical even if this is big.)'
|
||||
),
|
||||
'Select_scan' => __(
|
||||
'The number of joins that did a full scan of the first table.'
|
||||
),
|
||||
'Slave_open_temp_tables' => __(
|
||||
'The number of temporary tables currently'
|
||||
. ' open by the slave SQL thread.'
|
||||
),
|
||||
'Slave_retried_transactions' => __(
|
||||
'Total (since startup) number of times the replication slave SQL'
|
||||
. ' thread has retried transactions.'
|
||||
),
|
||||
'Slave_running' => __(
|
||||
'This is ON if this server is a slave that is connected to a master.'
|
||||
),
|
||||
'Slow_launch_threads' => __(
|
||||
'The number of threads that have taken more than slow_launch_time'
|
||||
. ' seconds to create.'
|
||||
),
|
||||
'Slow_queries' => __(
|
||||
'The number of queries that have taken more than long_query_time'
|
||||
. ' seconds.'
|
||||
),
|
||||
'Sort_merge_passes' => __(
|
||||
'The number of merge passes the sort algorithm has had to do.'
|
||||
. ' If this value is large, you should consider increasing the'
|
||||
. ' value of the sort_buffer_size system variable.'
|
||||
),
|
||||
'Sort_range' => __(
|
||||
'The number of sorts that were done with ranges.'
|
||||
),
|
||||
'Sort_rows' => __(
|
||||
'The number of sorted rows.'
|
||||
),
|
||||
'Sort_scan' => __(
|
||||
'The number of sorts that were done by scanning the table.'
|
||||
),
|
||||
'Table_locks_immediate' => __(
|
||||
'The number of times that a table lock was acquired immediately.'
|
||||
),
|
||||
'Table_locks_waited' => __(
|
||||
'The number of times that a table lock could not be acquired'
|
||||
. ' immediately and a wait was needed. If this is high, and you have'
|
||||
. ' performance problems, you should first optimize your queries,'
|
||||
. ' and then either split your table or tables or use replication.'
|
||||
),
|
||||
'Threads_cached' => __(
|
||||
'The number of threads in the thread cache. The cache hit rate can'
|
||||
. ' be calculated as Threads_created/Connections. If this value is'
|
||||
. ' red you should raise your thread_cache_size.'
|
||||
),
|
||||
'Threads_connected' => __(
|
||||
'The number of currently open connections.'
|
||||
),
|
||||
'Threads_created' => __(
|
||||
'The number of threads created to handle connections. If'
|
||||
. ' Threads_created is big, you may want to increase the'
|
||||
. ' thread_cache_size value. (Normally this doesn\'t give a notable'
|
||||
. ' performance improvement if you have a good thread'
|
||||
. ' implementation.)'
|
||||
),
|
||||
'Threads_cache_hitrate_%' => __(
|
||||
'Thread cache hit rate (calculated value)'
|
||||
),
|
||||
'Threads_running' => __(
|
||||
'The number of threads that are not sleeping.'
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Server;
|
||||
|
||||
use PhpMyAdmin\Controllers\AbstractController;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\Relation;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Server\UserGroups;
|
||||
use PhpMyAdmin\Template;
|
||||
|
||||
/**
|
||||
* Displays the 'User groups' sub page under 'Users' page.
|
||||
*/
|
||||
class UserGroupsController extends AbstractController
|
||||
{
|
||||
/** @var Relation */
|
||||
private $relation;
|
||||
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, Relation $relation, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template);
|
||||
$this->relation = $relation;
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
$cfgRelation = $this->relation->getRelationsParam();
|
||||
if (! $cfgRelation['menuswork']) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->addScriptFiles(['server/user_groups.js']);
|
||||
|
||||
/**
|
||||
* Only allowed to superuser
|
||||
*/
|
||||
if (! $this->dbi->isSuperUser()) {
|
||||
$this->response->addHTML(
|
||||
Message::error(__('No Privileges'))->getDisplay()
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->response->addHTML('<div class="container-fluid">');
|
||||
$this->render('server/privileges/subnav', [
|
||||
'active' => 'user-groups',
|
||||
'is_super_user' => $this->dbi->isSuperUser(),
|
||||
]);
|
||||
|
||||
/**
|
||||
* Delete user group
|
||||
*/
|
||||
if (! empty($_POST['deleteUserGroup'])) {
|
||||
UserGroups::delete($_POST['userGroup']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new user group
|
||||
*/
|
||||
if (! empty($_POST['addUserGroupSubmit'])) {
|
||||
UserGroups::edit($_POST['userGroup'], true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a user group
|
||||
*/
|
||||
if (! empty($_POST['editUserGroupSubmit'])) {
|
||||
UserGroups::edit($_POST['userGroup']);
|
||||
}
|
||||
|
||||
if (isset($_POST['viewUsers'])) {
|
||||
// Display users belonging to a user group
|
||||
$this->response->addHTML(UserGroups::getHtmlForListingUsersofAGroup($_POST['userGroup']));
|
||||
}
|
||||
|
||||
if (isset($_GET['addUserGroup'])) {
|
||||
// Display add user group dialog
|
||||
$this->response->addHTML(UserGroups::getHtmlToEditUserGroup());
|
||||
} elseif (isset($_POST['editUserGroup'])) {
|
||||
// Display edit user group dialog
|
||||
$this->response->addHTML(UserGroups::getHtmlToEditUserGroup($_POST['userGroup']));
|
||||
} else {
|
||||
// Display user groups table
|
||||
$this->response->addHTML(UserGroups::getHtmlForUserGroupsTable());
|
||||
}
|
||||
|
||||
$this->response->addHTML('</div>');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,262 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Server;
|
||||
|
||||
use PhpMyAdmin\Controllers\AbstractController;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Html\Generator;
|
||||
use PhpMyAdmin\Providers\ServerVariables\ServerVariablesProvider;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
use function header;
|
||||
use function htmlspecialchars;
|
||||
use function implode;
|
||||
use function in_array;
|
||||
use function is_numeric;
|
||||
use function mb_strtolower;
|
||||
use function pow;
|
||||
use function preg_match;
|
||||
use function str_replace;
|
||||
use function strtolower;
|
||||
use function trim;
|
||||
|
||||
/**
|
||||
* Handles viewing and editing server variables
|
||||
*/
|
||||
class VariablesController extends AbstractController
|
||||
{
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template);
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $err_url;
|
||||
|
||||
$params = ['filter' => $_GET['filter'] ?? null];
|
||||
$err_url = Url::getFromRoute('/');
|
||||
|
||||
if ($this->dbi->isSuperUser()) {
|
||||
$this->dbi->selectDb('mysql');
|
||||
}
|
||||
|
||||
$filterValue = ! empty($params['filter']) ? $params['filter'] : '';
|
||||
|
||||
$this->addScriptFiles(['server/variables.js']);
|
||||
|
||||
$variables = [];
|
||||
$serverVarsResult = $this->dbi->tryQuery('SHOW SESSION VARIABLES;');
|
||||
if ($serverVarsResult !== false) {
|
||||
$serverVarsSession = [];
|
||||
while ($arr = $this->dbi->fetchRow($serverVarsResult)) {
|
||||
$serverVarsSession[$arr[0]] = $arr[1];
|
||||
}
|
||||
$this->dbi->freeResult($serverVarsResult);
|
||||
|
||||
$serverVars = $this->dbi->fetchResult('SHOW GLOBAL VARIABLES;', 0, 1);
|
||||
|
||||
// list of static (i.e. non-editable) system variables
|
||||
$staticVariables = ServerVariablesProvider::getImplementation()->getStaticVariables();
|
||||
|
||||
foreach ($serverVars as $name => $value) {
|
||||
$hasSessionValue = isset($serverVarsSession[$name])
|
||||
&& $serverVarsSession[$name] !== $value;
|
||||
$docLink = Generator::linkToVarDocumentation(
|
||||
$name,
|
||||
$this->dbi->isMariaDB(),
|
||||
str_replace('_', ' ', $name)
|
||||
);
|
||||
|
||||
[$formattedValue, $isEscaped] = $this->formatVariable($name, $value);
|
||||
if ($hasSessionValue) {
|
||||
[$sessionFormattedValue] = $this->formatVariable(
|
||||
$name,
|
||||
$serverVarsSession[$name]
|
||||
);
|
||||
}
|
||||
|
||||
$variables[] = [
|
||||
'name' => $name,
|
||||
'is_editable' => ! in_array(strtolower($name), $staticVariables),
|
||||
'doc_link' => $docLink,
|
||||
'value' => $formattedValue,
|
||||
'is_escaped' => $isEscaped,
|
||||
'has_session_value' => $hasSessionValue,
|
||||
'session_value' => $sessionFormattedValue ?? null,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$this->render('server/variables/index', [
|
||||
'variables' => $variables,
|
||||
'filter_value' => $filterValue,
|
||||
'is_superuser' => $this->dbi->isSuperUser(),
|
||||
'is_mariadb' => $this->dbi->isMariaDB(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the AJAX request for a single variable value
|
||||
*
|
||||
* @param array $params Request parameters
|
||||
*/
|
||||
public function getValue(array $params): void
|
||||
{
|
||||
if (! $this->response->isAjax()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Send with correct charset
|
||||
header('Content-Type: text/html; charset=UTF-8');
|
||||
// Do not use double quotes inside the query to avoid a problem
|
||||
// when server is running in ANSI_QUOTES sql_mode
|
||||
$varValue = $this->dbi->fetchSingleRow(
|
||||
'SHOW GLOBAL VARIABLES WHERE Variable_name=\''
|
||||
. $this->dbi->escapeString($params['name']) . '\';',
|
||||
'NUM'
|
||||
);
|
||||
|
||||
$json = [
|
||||
'message' => $varValue[1],
|
||||
];
|
||||
|
||||
$variableType = ServerVariablesProvider::getImplementation()->getVariableType($params['name']);
|
||||
|
||||
if ($variableType === 'byte') {
|
||||
$json['message'] = implode(
|
||||
' ',
|
||||
Util::formatByteDown($varValue[1], 3, 3)
|
||||
);
|
||||
}
|
||||
|
||||
$this->response->addJSON($json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the AJAX request for setting value for a single variable
|
||||
*
|
||||
* @param array $vars Request parameters
|
||||
*/
|
||||
public function setValue(array $vars): void
|
||||
{
|
||||
$params = [
|
||||
'varName' => $vars['name'],
|
||||
'varValue' => $_POST['varValue'] ?? null,
|
||||
];
|
||||
|
||||
if (! $this->response->isAjax()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$value = (string) $params['varValue'];
|
||||
$variableName = (string) $params['varName'];
|
||||
$matches = [];
|
||||
$variableType = ServerVariablesProvider::getImplementation()->getVariableType($variableName);
|
||||
|
||||
if ($variableType === 'byte' && preg_match(
|
||||
'/^\s*(\d+(\.\d+)?)\s*(mb|kb|mib|kib|gb|gib)\s*$/i',
|
||||
$value,
|
||||
$matches
|
||||
)) {
|
||||
$exp = [
|
||||
'kb' => 1,
|
||||
'kib' => 1,
|
||||
'mb' => 2,
|
||||
'mib' => 2,
|
||||
'gb' => 3,
|
||||
'gib' => 3,
|
||||
];
|
||||
$value = (float) $matches[1] * pow(
|
||||
1024,
|
||||
$exp[mb_strtolower($matches[3])]
|
||||
);
|
||||
} else {
|
||||
$value = $this->dbi->escapeString($value);
|
||||
}
|
||||
|
||||
if (! is_numeric($value)) {
|
||||
$value = "'" . $value . "'";
|
||||
}
|
||||
|
||||
$json = [];
|
||||
if (! preg_match('/[^a-zA-Z0-9_]+/', $params['varName'])
|
||||
&& $this->dbi->query(
|
||||
'SET GLOBAL ' . $params['varName'] . ' = ' . $value
|
||||
)
|
||||
) {
|
||||
// Some values are rounded down etc.
|
||||
$varValue = $this->dbi->fetchSingleRow(
|
||||
'SHOW GLOBAL VARIABLES WHERE Variable_name="'
|
||||
. $this->dbi->escapeString($params['varName'])
|
||||
. '";',
|
||||
'NUM'
|
||||
);
|
||||
[$formattedValue, $isHtmlFormatted] = $this->formatVariable(
|
||||
$params['varName'],
|
||||
$varValue[1]
|
||||
);
|
||||
|
||||
if ($isHtmlFormatted === false) {
|
||||
$json['variable'] = htmlspecialchars($formattedValue);
|
||||
} else {
|
||||
$json['variable'] = $formattedValue;
|
||||
}
|
||||
} else {
|
||||
$this->response->setRequestStatus(false);
|
||||
$json['error'] = __('Setting variable failed');
|
||||
}
|
||||
|
||||
$this->response->addJSON($json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format Variable
|
||||
*
|
||||
* @param string $name variable name
|
||||
* @param int|string $value variable value
|
||||
*
|
||||
* @return array formatted string and bool if string is HTML formatted
|
||||
*/
|
||||
private function formatVariable($name, $value): array
|
||||
{
|
||||
$isHtmlFormatted = false;
|
||||
$formattedValue = $value;
|
||||
|
||||
if (is_numeric($value)) {
|
||||
$variableType = ServerVariablesProvider::getImplementation()->getVariableType($name);
|
||||
|
||||
if ($variableType === 'byte') {
|
||||
$isHtmlFormatted = true;
|
||||
$formattedValue = trim(
|
||||
$this->template->render(
|
||||
'server/variables/format_variable',
|
||||
[
|
||||
'valueTitle' => Util::formatNumber($value, 0),
|
||||
'value' => implode(' ', Util::formatByteDown($value, 3, 3)),
|
||||
]
|
||||
)
|
||||
);
|
||||
} else {
|
||||
$formattedValue = Util::formatNumber($value, 0);
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
$formattedValue,
|
||||
$isHtmlFormatted,
|
||||
];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Setup;
|
||||
|
||||
use PhpMyAdmin\Config\ConfigFile;
|
||||
use PhpMyAdmin\Config\Forms\BaseForm;
|
||||
use PhpMyAdmin\Config\Forms\Setup\SetupFormList;
|
||||
use PhpMyAdmin\Template;
|
||||
use function in_array;
|
||||
|
||||
abstract class AbstractController
|
||||
{
|
||||
/** @var ConfigFile */
|
||||
protected $config;
|
||||
|
||||
/** @var Template */
|
||||
protected $template;
|
||||
|
||||
/**
|
||||
* @param ConfigFile $config ConfigFile instance
|
||||
* @param Template $template Template instance
|
||||
*/
|
||||
public function __construct($config, $template)
|
||||
{
|
||||
$this->config = $config;
|
||||
$this->template = $template;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function getPages(): array
|
||||
{
|
||||
$ignored = [
|
||||
'Config',
|
||||
'Servers',
|
||||
];
|
||||
$pages = [];
|
||||
foreach (SetupFormList::getAll() as $formset) {
|
||||
if (in_array($formset, $ignored)) {
|
||||
continue;
|
||||
}
|
||||
/** @var BaseForm $formClass */
|
||||
$formClass = SetupFormList::get($formset);
|
||||
|
||||
$pages[$formset] = [
|
||||
'name' => $formClass::getName(),
|
||||
'formset' => $formset,
|
||||
];
|
||||
}
|
||||
|
||||
return $pages;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Setup;
|
||||
|
||||
use PhpMyAdmin\Config\FormDisplayTemplate;
|
||||
use PhpMyAdmin\Core;
|
||||
use PhpMyAdmin\Setup\ConfigGenerator;
|
||||
|
||||
class ConfigController extends AbstractController
|
||||
{
|
||||
/**
|
||||
* @param array $params Request parameters
|
||||
*
|
||||
* @return string HTML
|
||||
*/
|
||||
public function index(array $params): string
|
||||
{
|
||||
$pages = $this->getPages();
|
||||
|
||||
$formDisplayTemplate = new FormDisplayTemplate($GLOBALS['PMA_Config']);
|
||||
|
||||
$formTop = $formDisplayTemplate->displayFormTop('config.php');
|
||||
$fieldsetTop = $formDisplayTemplate->displayFieldsetTop(
|
||||
'config.inc.php',
|
||||
'',
|
||||
null,
|
||||
['class' => 'simple']
|
||||
);
|
||||
$formBottom = $formDisplayTemplate->displayFieldsetBottom(false);
|
||||
$fieldsetBottom = $formDisplayTemplate->displayFormBottom();
|
||||
|
||||
$config = ConfigGenerator::getConfigFile($this->config);
|
||||
|
||||
return $this->template->render('setup/config/index', [
|
||||
'formset' => $params['formset'] ?? '',
|
||||
'pages' => $pages,
|
||||
'form_top_html' => $formTop,
|
||||
'fieldset_top_html' => $fieldsetTop,
|
||||
'form_bottom_html' => $formBottom,
|
||||
'fieldset_bottom_html' => $fieldsetBottom,
|
||||
'eol' => Core::ifSetOr($params['eol'], 'unix'),
|
||||
'config' => $config,
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Setup;
|
||||
|
||||
use PhpMyAdmin\Config\Forms\BaseForm;
|
||||
use PhpMyAdmin\Config\Forms\Setup\SetupFormList;
|
||||
use PhpMyAdmin\Core;
|
||||
use PhpMyAdmin\Setup\FormProcessing;
|
||||
use function ob_get_clean;
|
||||
use function ob_start;
|
||||
|
||||
class FormController extends AbstractController
|
||||
{
|
||||
/**
|
||||
* @param array $params Request parameters
|
||||
*
|
||||
* @return string HTML
|
||||
*/
|
||||
public function index(array $params): string
|
||||
{
|
||||
$pages = $this->getPages();
|
||||
|
||||
$formset = Core::isValid($params['formset'], 'scalar') ? $params['formset'] : null;
|
||||
|
||||
$formClass = SetupFormList::get($formset);
|
||||
if ($formClass === null) {
|
||||
Core::fatalError(__('Incorrect form specified!'));
|
||||
}
|
||||
|
||||
ob_start();
|
||||
/** @var BaseForm $form */
|
||||
$form = new $formClass($this->config);
|
||||
FormProcessing::process($form);
|
||||
$page = ob_get_clean();
|
||||
|
||||
return $this->template->render('setup/form/index', [
|
||||
'formset' => $params['formset'] ?? '',
|
||||
'pages' => $pages,
|
||||
'name' => $form::getName(),
|
||||
'page' => $page,
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,222 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Setup;
|
||||
|
||||
use PhpMyAdmin\Config\FormDisplay;
|
||||
use PhpMyAdmin\Config\FormDisplayTemplate;
|
||||
use PhpMyAdmin\Config\ServerConfigChecks;
|
||||
use PhpMyAdmin\Core;
|
||||
use PhpMyAdmin\LanguageManager;
|
||||
use PhpMyAdmin\Sanitize;
|
||||
use PhpMyAdmin\Setup\Index;
|
||||
use function preg_replace;
|
||||
use function uniqid;
|
||||
|
||||
class HomeController extends AbstractController
|
||||
{
|
||||
/**
|
||||
* @param array $params Request parameters
|
||||
*
|
||||
* @return string HTML
|
||||
*/
|
||||
public function index(array $params): string
|
||||
{
|
||||
$pages = $this->getPages();
|
||||
|
||||
// Handle done action info
|
||||
$actionDone = Core::isValid($params['action_done'], 'scalar') ? $params['action_done'] : '';
|
||||
$actionDone = preg_replace('/[^a-z_]/', '', $actionDone);
|
||||
|
||||
// message handling
|
||||
Index::messagesBegin();
|
||||
|
||||
// Check phpMyAdmin version
|
||||
if (isset($params['version_check'])) {
|
||||
Index::versionCheck();
|
||||
}
|
||||
|
||||
// Perform various security, compatibility and consistency checks
|
||||
$configChecker = new ServerConfigChecks($this->config);
|
||||
$configChecker->performConfigChecks();
|
||||
|
||||
$text = __(
|
||||
'You are not using a secure connection; all data (including potentially '
|
||||
. 'sensitive information, like passwords) is transferred unencrypted!'
|
||||
);
|
||||
$text .= ' <a href="#">';
|
||||
$text .= __(
|
||||
'If your server is also configured to accept HTTPS requests '
|
||||
. 'follow this link to use a secure connection.'
|
||||
);
|
||||
$text .= '</a>';
|
||||
Index::messagesSet('notice', 'no_https', __('Insecure connection'), $text);
|
||||
|
||||
// Check for done action info and set notice message if present
|
||||
switch ($actionDone) {
|
||||
case 'config_saved':
|
||||
/* Use uniqid to display this message every time configuration is saved */
|
||||
Index::messagesSet(
|
||||
'notice',
|
||||
uniqid('config_saved'),
|
||||
__('Configuration saved.'),
|
||||
Sanitize::sanitizeMessage(
|
||||
__(
|
||||
'Configuration saved to file config/config.inc.php in phpMyAdmin '
|
||||
. 'top level directory, copy it to top level one and delete '
|
||||
. 'directory config to use it.'
|
||||
)
|
||||
)
|
||||
);
|
||||
break;
|
||||
case 'config_not_saved':
|
||||
/* Use uniqid to display this message every time configuration is saved */
|
||||
Index::messagesSet(
|
||||
'notice',
|
||||
uniqid('config_not_saved'),
|
||||
__('Configuration not saved!'),
|
||||
Sanitize::sanitizeMessage(
|
||||
__(
|
||||
'Please create web server writable folder [em]config[/em] in '
|
||||
. 'phpMyAdmin top level directory as described in '
|
||||
. '[doc@setup_script]documentation[/doc]. Otherwise you will be '
|
||||
. 'only able to download or display it.'
|
||||
)
|
||||
)
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
Index::messagesEnd();
|
||||
$messages = Index::messagesShowHtml();
|
||||
|
||||
$formDisplay = new FormDisplay($this->config);
|
||||
|
||||
$defaultLanguageOptions = [
|
||||
'doc' => $formDisplay->getDocLink('DefaultLang'),
|
||||
'values' => [],
|
||||
'values_escaped' => true,
|
||||
];
|
||||
|
||||
// prepare unfiltered language list
|
||||
$sortedLanguages = LanguageManager::getInstance()->sortedLanguages();
|
||||
$languages = [];
|
||||
foreach ($sortedLanguages as $language) {
|
||||
$languages[] = [
|
||||
'code' => $language->getCode(),
|
||||
'name' => $language->getName(),
|
||||
'is_active' => $language->isActive(),
|
||||
];
|
||||
$defaultLanguageOptions['values'][$language->getCode()] = $language->getName();
|
||||
}
|
||||
|
||||
$serverDefaultOptions = [
|
||||
'doc' => $formDisplay->getDocLink('ServerDefault'),
|
||||
'values' => [],
|
||||
'values_disabled' => [],
|
||||
];
|
||||
|
||||
$servers = [];
|
||||
if ($this->config->getServerCount() > 0) {
|
||||
$serverDefaultOptions['values']['0'] = __('let the user choose');
|
||||
$serverDefaultOptions['values']['-'] = '------------------------------';
|
||||
if ($this->config->getServerCount() === 1) {
|
||||
$serverDefaultOptions['values_disabled'][] = '0';
|
||||
}
|
||||
$serverDefaultOptions['values_disabled'][] = '-';
|
||||
|
||||
foreach ($this->config->getServers() as $id => $server) {
|
||||
$servers[$id] = [
|
||||
'id' => $id,
|
||||
'name' => $this->config->getServerName($id),
|
||||
'auth_type' => $this->config->getValue('Servers/' . $id . '/auth_type'),
|
||||
'dsn' => $this->config->getServerDSN($id),
|
||||
'params' => [
|
||||
'token' => $_SESSION[' PMA_token '],
|
||||
'edit' => [
|
||||
'page' => 'servers',
|
||||
'mode' => 'edit',
|
||||
'id' => $id,
|
||||
],
|
||||
'remove' => [
|
||||
'page' => 'servers',
|
||||
'mode' => 'remove',
|
||||
'id' => $id,
|
||||
],
|
||||
],
|
||||
];
|
||||
$serverDefaultOptions['values'][(string) $id] = $this->config->getServerName($id) . ' [' . $id . ']';
|
||||
}
|
||||
} else {
|
||||
$serverDefaultOptions['values']['1'] = __('- none -');
|
||||
$serverDefaultOptions['values_escaped'] = true;
|
||||
}
|
||||
|
||||
$formDisplayTemplate = new FormDisplayTemplate($GLOBALS['PMA_Config']);
|
||||
$serversFormTopHtml = $formDisplayTemplate->displayFormTop(
|
||||
'index.php',
|
||||
'get',
|
||||
[
|
||||
'page' => 'servers',
|
||||
'mode' => 'add',
|
||||
]
|
||||
);
|
||||
$configFormTopHtml = $formDisplayTemplate->displayFormTop('config.php');
|
||||
$formBottomHtml = $formDisplayTemplate->displayFormBottom();
|
||||
|
||||
$defaultLanguageInput = $formDisplayTemplate->displayInput(
|
||||
'DefaultLang',
|
||||
__('Default language'),
|
||||
'select',
|
||||
$this->config->getValue('DefaultLang'),
|
||||
'',
|
||||
true,
|
||||
$defaultLanguageOptions
|
||||
);
|
||||
$serverDefaultInput = $formDisplayTemplate->displayInput(
|
||||
'ServerDefault',
|
||||
__('Default server'),
|
||||
'select',
|
||||
$this->config->getValue('ServerDefault'),
|
||||
'',
|
||||
true,
|
||||
$serverDefaultOptions
|
||||
);
|
||||
|
||||
$eolOptions = [
|
||||
'values' => [
|
||||
'unix' => 'UNIX / Linux (\n)',
|
||||
'win' => 'Windows (\r\n)',
|
||||
],
|
||||
'values_escaped' => true,
|
||||
];
|
||||
$eol = Core::ifSetOr($_SESSION['eol'], (PMA_IS_WINDOWS ? 'win' : 'unix'));
|
||||
$eolInput = $formDisplayTemplate->displayInput(
|
||||
'eol',
|
||||
__('End of line'),
|
||||
'select',
|
||||
$eol,
|
||||
'',
|
||||
true,
|
||||
$eolOptions
|
||||
);
|
||||
|
||||
return $this->template->render('setup/home/index', [
|
||||
'formset' => $params['formset'] ?? '',
|
||||
'languages' => $languages,
|
||||
'messages' => $messages,
|
||||
'servers_form_top_html' => $serversFormTopHtml,
|
||||
'config_form_top_html' => $configFormTopHtml,
|
||||
'form_bottom_html' => $formBottomHtml,
|
||||
'server_count' => $this->config->getServerCount(),
|
||||
'servers' => $servers,
|
||||
'default_language_input' => $defaultLanguageInput,
|
||||
'server_default_input' => $serverDefaultInput,
|
||||
'eol_input' => $eolInput,
|
||||
'pages' => $pages,
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Setup;
|
||||
|
||||
use PhpMyAdmin\Config\Forms\Setup\ServersForm;
|
||||
use PhpMyAdmin\Core;
|
||||
use PhpMyAdmin\Setup\FormProcessing;
|
||||
use function ob_get_clean;
|
||||
use function ob_start;
|
||||
|
||||
class ServersController extends AbstractController
|
||||
{
|
||||
/**
|
||||
* @param array $params Request parameters
|
||||
*
|
||||
* @return string HTML
|
||||
*/
|
||||
public function index(array $params): string
|
||||
{
|
||||
$pages = $this->getPages();
|
||||
|
||||
$id = Core::isValid($params['id'], 'numeric') ? (int) $params['id'] : null;
|
||||
$hasServer = ! empty($id) && $this->config->get('Servers/' . $id) !== null;
|
||||
|
||||
if (! $hasServer && ($params['mode'] !== 'revert' && $params['mode'] !== 'edit')) {
|
||||
$id = 0;
|
||||
}
|
||||
|
||||
ob_start();
|
||||
FormProcessing::process(new ServersForm($this->config, $id));
|
||||
$page = ob_get_clean();
|
||||
|
||||
return $this->template->render('setup/servers/index', [
|
||||
'formset' => $params['formset'] ?? '',
|
||||
'pages' => $pages,
|
||||
'has_server' => $hasServer,
|
||||
'mode' => $params['mode'],
|
||||
'server_id' => $id,
|
||||
'server_dsn' => $this->config->getServerDSN($id),
|
||||
'page' => $page,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $params Request parameters
|
||||
*/
|
||||
public function destroy(array $params): void
|
||||
{
|
||||
$id = Core::isValid($params['id'], 'numeric') ? (int) $params['id'] : null;
|
||||
|
||||
$hasServer = ! empty($id) && $this->config->get('Servers/' . $id) !== null;
|
||||
|
||||
if (! $hasServer) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->config->removeServer($id);
|
||||
}
|
||||
}
|
382
admin/phpMyAdmin/libraries/classes/Controllers/SqlController.php
Normal file
382
admin/phpMyAdmin/libraries/classes/Controllers/SqlController.php
Normal file
|
@ -0,0 +1,382 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers;
|
||||
|
||||
use PhpMyAdmin\Bookmark;
|
||||
use PhpMyAdmin\CheckUserPrivileges;
|
||||
use PhpMyAdmin\Config\PageSettings;
|
||||
use PhpMyAdmin\Core;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Html\Generator;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\ParseAnalyze;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Sql;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
use const ENT_COMPAT;
|
||||
use function htmlentities;
|
||||
use function mb_strpos;
|
||||
use function strlen;
|
||||
use function strpos;
|
||||
use function urlencode;
|
||||
|
||||
class SqlController extends AbstractController
|
||||
{
|
||||
/** @var Sql */
|
||||
private $sql;
|
||||
|
||||
/** @var CheckUserPrivileges */
|
||||
private $checkUserPrivileges;
|
||||
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct(
|
||||
$response,
|
||||
Template $template,
|
||||
Sql $sql,
|
||||
CheckUserPrivileges $checkUserPrivileges,
|
||||
$dbi
|
||||
) {
|
||||
parent::__construct($response, $template);
|
||||
$this->sql = $sql;
|
||||
$this->checkUserPrivileges = $checkUserPrivileges;
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $cfg, $db, $display_query, $sql_query, $table, $PMA_Theme;
|
||||
global $ajax_reload, $goto, $err_url, $find_real_end, $unlim_num_rows, $import_text, $disp_query;
|
||||
global $extra_data, $message_to_show, $sql_data, $disp_message, $complete_query;
|
||||
global $is_gotofile, $back, $table_from_sql;
|
||||
|
||||
$this->checkUserPrivileges->getPrivileges();
|
||||
|
||||
$pageSettings = new PageSettings('Browse');
|
||||
$this->response->addHTML($pageSettings->getErrorHTML());
|
||||
$this->response->addHTML($pageSettings->getHTML());
|
||||
|
||||
$this->addScriptFiles([
|
||||
'vendor/jquery/jquery.uitablefilter.js',
|
||||
'table/change.js',
|
||||
'indexes.js',
|
||||
'vendor/stickyfill.min.js',
|
||||
'gis_data_editor.js',
|
||||
'multi_column_sort.js',
|
||||
]);
|
||||
|
||||
/**
|
||||
* Set ajax_reload in the response if it was already set
|
||||
*/
|
||||
if (isset($ajax_reload) && $ajax_reload['reload'] === true) {
|
||||
$this->response->addJSON('ajax_reload', $ajax_reload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the url to return to in case of error in a sql statement
|
||||
*/
|
||||
$is_gotofile = true;
|
||||
if (empty($goto)) {
|
||||
if (empty($table)) {
|
||||
$goto = Util::getScriptNameForOption(
|
||||
$cfg['DefaultTabDatabase'],
|
||||
'database'
|
||||
);
|
||||
} else {
|
||||
$goto = Util::getScriptNameForOption(
|
||||
$cfg['DefaultTabTable'],
|
||||
'table'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (! isset($err_url)) {
|
||||
$err_url = ! empty($back) ? $back : $goto;
|
||||
$err_url .= Url::getCommon(
|
||||
['db' => $GLOBALS['db']],
|
||||
strpos($err_url, '?') === false ? '?' : '&'
|
||||
);
|
||||
if ((mb_strpos(' ' . $err_url, 'db_') !== 1 || mb_strpos($err_url, '?route=/database/') === false)
|
||||
&& strlen($table) > 0
|
||||
) {
|
||||
$err_url .= '&table=' . urlencode($table);
|
||||
}
|
||||
}
|
||||
|
||||
// Coming from a bookmark dialog
|
||||
if (isset($_POST['bkm_fields']['bkm_sql_query'])) {
|
||||
$sql_query = $_POST['bkm_fields']['bkm_sql_query'];
|
||||
} elseif (isset($_POST['sql_query'])) {
|
||||
$sql_query = $_POST['sql_query'];
|
||||
} elseif (isset($_GET['sql_query'], $_GET['sql_signature'])) {
|
||||
if (Core::checkSqlQuerySignature($_GET['sql_query'], $_GET['sql_signature'])) {
|
||||
$sql_query = $_GET['sql_query'];
|
||||
}
|
||||
}
|
||||
|
||||
// This one is just to fill $db
|
||||
if (isset($_POST['bkm_fields']['bkm_database'])) {
|
||||
$db = $_POST['bkm_fields']['bkm_database'];
|
||||
}
|
||||
|
||||
// Default to browse if no query set and we have table
|
||||
// (needed for browsing from DefaultTabTable)
|
||||
if (empty($sql_query) && strlen($table) > 0 && strlen($db) > 0) {
|
||||
$sql_query = $this->sql->getDefaultSqlQueryForBrowse($db, $table);
|
||||
|
||||
// set $goto to what will be displayed if query returns 0 rows
|
||||
$goto = '';
|
||||
} else {
|
||||
// Now we can check the parameters
|
||||
Util::checkParameters(['sql_query']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and analyze the query
|
||||
*/
|
||||
[
|
||||
$analyzed_sql_results,
|
||||
$db,
|
||||
$table_from_sql,
|
||||
] = ParseAnalyze::sqlQuery($sql_query, $db);
|
||||
|
||||
if ($table != $table_from_sql && ! empty($table_from_sql)) {
|
||||
$table = $table_from_sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check rights in case of DROP DATABASE
|
||||
*
|
||||
* This test may be bypassed if $is_js_confirmed = 1 (already checked with js)
|
||||
* but since a malicious user may pass this variable by url/form, we don't take
|
||||
* into account this case.
|
||||
*/
|
||||
if ($this->sql->hasNoRightsToDropDatabase(
|
||||
$analyzed_sql_results,
|
||||
$cfg['AllowUserDropDatabase'],
|
||||
$this->dbi->isSuperUser()
|
||||
)) {
|
||||
Generator::mysqlDie(
|
||||
__('"DROP DATABASE" statements are disabled.'),
|
||||
'',
|
||||
false,
|
||||
$err_url
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Need to find the real end of rows?
|
||||
*/
|
||||
if (isset($find_real_end) && $find_real_end) {
|
||||
$unlim_num_rows = $this->sql->findRealEndOfRows($db, $table);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bookmark add
|
||||
*/
|
||||
if (isset($_POST['store_bkm'])) {
|
||||
$this->addBookmark($goto);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets or modifies the $goto variable if required
|
||||
*/
|
||||
if ($goto === Url::getFromRoute('/sql')) {
|
||||
$is_gotofile = false;
|
||||
$goto = Url::getFromRoute('/sql', [
|
||||
'db' => $db,
|
||||
'table' => $table,
|
||||
'sql_query' => $sql_query,
|
||||
]);
|
||||
}
|
||||
|
||||
$this->response->addHTML($this->sql->executeQueryAndSendQueryResponse(
|
||||
$analyzed_sql_results,
|
||||
$is_gotofile,
|
||||
$db,
|
||||
$table,
|
||||
$find_real_end ?? null,
|
||||
$import_text ?? null,
|
||||
$extra_data ?? null,
|
||||
$message_to_show ?? null,
|
||||
$sql_data ?? null,
|
||||
$goto,
|
||||
$PMA_Theme->getImgPath(),
|
||||
isset($disp_query) ? $display_query : null,
|
||||
$disp_message ?? null,
|
||||
$sql_query,
|
||||
$complete_query ?? null
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get values for the relational columns
|
||||
*
|
||||
* During grid edit, if we have a relational field, show the dropdown for it.
|
||||
*/
|
||||
public function getRelationalValues(): void
|
||||
{
|
||||
global $db, $table;
|
||||
|
||||
$this->checkUserPrivileges->getPrivileges();
|
||||
|
||||
$column = $_POST['column'];
|
||||
if ($_SESSION['tmpval']['relational_display'] === 'D'
|
||||
&& isset($_POST['relation_key_or_display_column'])
|
||||
&& $_POST['relation_key_or_display_column']
|
||||
) {
|
||||
$curr_value = $_POST['relation_key_or_display_column'];
|
||||
} else {
|
||||
$curr_value = $_POST['curr_value'];
|
||||
}
|
||||
$dropdown = $this->sql->getHtmlForRelationalColumnDropdown(
|
||||
$db,
|
||||
$table,
|
||||
$column,
|
||||
$curr_value
|
||||
);
|
||||
$this->response->addJSON('dropdown', $dropdown);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get possible values for enum fields during grid edit.
|
||||
*/
|
||||
public function getEnumValues(): void
|
||||
{
|
||||
global $db, $table;
|
||||
|
||||
$this->checkUserPrivileges->getPrivileges();
|
||||
|
||||
$column = $_POST['column'];
|
||||
$curr_value = $_POST['curr_value'];
|
||||
$values = $this->sql->getValuesForColumn($db, $table, $column);
|
||||
$dropdown = $this->template->render('sql/enum_column_dropdown', [
|
||||
'values' => $values,
|
||||
'selected_values' => [$curr_value],
|
||||
]);
|
||||
|
||||
$this->response->addJSON('dropdown', $dropdown);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get possible values for SET fields during grid edit.
|
||||
*/
|
||||
public function getSetValues(): void
|
||||
{
|
||||
global $db, $table;
|
||||
|
||||
$this->checkUserPrivileges->getPrivileges();
|
||||
|
||||
$column = $_POST['column'];
|
||||
$currentValue = $_POST['curr_value'];
|
||||
$fullValues = $_POST['get_full_values'] ?? false;
|
||||
$whereClause = $_POST['where_clause'] ?? null;
|
||||
|
||||
$values = $this->sql->getValuesForColumn($db, $table, $column);
|
||||
|
||||
// If the $currentValue was truncated, we should fetch the correct full values from the table.
|
||||
if ($fullValues && ! empty($whereClause)) {
|
||||
$currentValue = $this->sql->getFullValuesForSetColumn(
|
||||
$db,
|
||||
$table,
|
||||
$column,
|
||||
$whereClause
|
||||
);
|
||||
}
|
||||
|
||||
// Converts characters of $currentValue to HTML entities.
|
||||
$convertedCurrentValue = htmlentities($currentValue, ENT_COMPAT, 'UTF-8');
|
||||
|
||||
$select = $this->template->render('sql/set_column', [
|
||||
'values' => $values,
|
||||
'current_values' => $convertedCurrentValue,
|
||||
]);
|
||||
|
||||
$this->response->addJSON('select', $select);
|
||||
}
|
||||
|
||||
public function getDefaultForeignKeyCheckValue(): void
|
||||
{
|
||||
$this->checkUserPrivileges->getPrivileges();
|
||||
|
||||
$this->response->addJSON(
|
||||
'default_fk_check_value',
|
||||
Util::isForeignKeyCheck()
|
||||
);
|
||||
}
|
||||
|
||||
public function setColumnOrderOrVisibility(): void
|
||||
{
|
||||
global $db, $table;
|
||||
|
||||
$this->checkUserPrivileges->getPrivileges();
|
||||
|
||||
$tableObject = $this->dbi->getTable($db, $table);
|
||||
$status = false;
|
||||
|
||||
// set column order
|
||||
if (isset($_POST['col_order'])) {
|
||||
$status = $this->sql->setColumnProperty($tableObject, 'col_order');
|
||||
}
|
||||
|
||||
// set column visibility
|
||||
if ($status === true && isset($_POST['col_visib'])) {
|
||||
$status = $this->sql->setColumnProperty($tableObject, 'col_visib');
|
||||
}
|
||||
|
||||
if ($status instanceof Message) {
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON('message', $status->getString());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->response->setRequestStatus($status === true);
|
||||
}
|
||||
|
||||
private function addBookmark(string $goto): void
|
||||
{
|
||||
global $cfg;
|
||||
|
||||
$bookmark = Bookmark::createBookmark(
|
||||
$this->dbi,
|
||||
$cfg['Server']['user'],
|
||||
$_POST['bkm_fields'],
|
||||
isset($_POST['bkm_all_users']) && $_POST['bkm_all_users'] === 'true'
|
||||
);
|
||||
|
||||
$result = null;
|
||||
if ($bookmark instanceof Bookmark) {
|
||||
$result = $bookmark->save();
|
||||
}
|
||||
|
||||
if (! $this->response->isAjax()) {
|
||||
Core::sendHeaderLocation('./' . $goto . '&label=' . $_POST['bkm_fields']['bkm_label']);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($result) {
|
||||
$msg = Message::success(__('Bookmark %s has been created.'));
|
||||
$msg->addParam($_POST['bkm_fields']['bkm_label']);
|
||||
$this->response->addJSON('message', $msg);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$msg = Message::error(__('Bookmark not created!'));
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON('message', $msg);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Table;
|
||||
|
||||
use PhpMyAdmin\Controllers\AbstractController as Controller;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
|
||||
abstract class AbstractController extends Controller
|
||||
{
|
||||
/** @var string */
|
||||
protected $db;
|
||||
|
||||
/** @var string */
|
||||
protected $table;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param string $db Database name
|
||||
* @param string $table Table name
|
||||
*/
|
||||
public function __construct($response, Template $template, $db, $table)
|
||||
{
|
||||
parent::__construct($response, $template);
|
||||
$this->db = $db;
|
||||
$this->table = $table;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,190 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Table;
|
||||
|
||||
use PhpMyAdmin\Config;
|
||||
use PhpMyAdmin\CreateAddField;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\DbTableExists;
|
||||
use PhpMyAdmin\Html\Generator;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\Relation;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Table\ColumnsDefinition;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Transformations;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
use function intval;
|
||||
use function is_array;
|
||||
use function min;
|
||||
use function strlen;
|
||||
|
||||
/**
|
||||
* Displays add field form and handles it.
|
||||
*/
|
||||
class AddFieldController extends AbstractController
|
||||
{
|
||||
/** @var Transformations */
|
||||
private $transformations;
|
||||
|
||||
/** @var Config */
|
||||
private $config;
|
||||
|
||||
/** @var Relation */
|
||||
private $relation;
|
||||
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param string $db Database name.
|
||||
* @param string $table Table name.
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct(
|
||||
$response,
|
||||
Template $template,
|
||||
$db,
|
||||
$table,
|
||||
Transformations $transformations,
|
||||
Config $config,
|
||||
Relation $relation,
|
||||
$dbi
|
||||
) {
|
||||
parent::__construct($response, $template, $db, $table);
|
||||
$this->transformations = $transformations;
|
||||
$this->config = $config;
|
||||
$this->relation = $relation;
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $err_url, $message, $action, $active_page, $sql_query;
|
||||
global $num_fields, $regenerate, $result, $db, $table;
|
||||
|
||||
$this->addScriptFiles(['table/structure.js']);
|
||||
|
||||
// Check parameters
|
||||
Util::checkParameters(['db', 'table']);
|
||||
|
||||
$cfg = $this->config->settings;
|
||||
|
||||
/**
|
||||
* Defines the url to return to in case of error in a sql statement
|
||||
*/
|
||||
$err_url = Url::getFromRoute('/table/sql', [
|
||||
'db' => $db,
|
||||
'table' => $table,
|
||||
]);
|
||||
|
||||
// check number of fields to be created
|
||||
if (isset($_POST['submit_num_fields'])) {
|
||||
if (isset($_POST['orig_after_field'])) {
|
||||
$_POST['after_field'] = $_POST['orig_after_field'];
|
||||
}
|
||||
if (isset($_POST['orig_field_where'])) {
|
||||
$_POST['field_where'] = $_POST['orig_field_where'];
|
||||
}
|
||||
$num_fields = min(
|
||||
intval($_POST['orig_num_fields']) + intval($_POST['added_fields']),
|
||||
4096
|
||||
);
|
||||
$regenerate = true;
|
||||
} elseif (isset($_POST['num_fields']) && intval($_POST['num_fields']) > 0) {
|
||||
$num_fields = min(4096, intval($_POST['num_fields']));
|
||||
} else {
|
||||
$num_fields = 1;
|
||||
}
|
||||
|
||||
if (isset($_POST['do_save_data'])) {
|
||||
// avoid an incorrect calling of PMA_updateColumns() via
|
||||
// /table/structure below
|
||||
unset($_POST['do_save_data']);
|
||||
|
||||
$createAddField = new CreateAddField($this->dbi);
|
||||
|
||||
[$result, $sql_query] = $createAddField->tryColumnCreationQuery($db, $table, $err_url);
|
||||
|
||||
if ($result !== true) {
|
||||
$error_message_html = Generator::mysqlDie(
|
||||
'',
|
||||
'',
|
||||
false,
|
||||
$err_url,
|
||||
false
|
||||
);
|
||||
$this->response->addHTML($error_message_html ?? '');
|
||||
$this->response->setRequestStatus(false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Update comment table for mime types [MIME]
|
||||
if (isset($_POST['field_mimetype'])
|
||||
&& is_array($_POST['field_mimetype'])
|
||||
&& $cfg['BrowseMIME']
|
||||
) {
|
||||
foreach ($_POST['field_mimetype'] as $fieldindex => $mimetype) {
|
||||
if (! isset($_POST['field_name'][$fieldindex])
|
||||
|| strlen($_POST['field_name'][$fieldindex]) <= 0
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->transformations->setMime(
|
||||
$db,
|
||||
$table,
|
||||
$_POST['field_name'][$fieldindex],
|
||||
$mimetype,
|
||||
$_POST['field_transformation'][$fieldindex],
|
||||
$_POST['field_transformation_options'][$fieldindex],
|
||||
$_POST['field_input_transformation'][$fieldindex],
|
||||
$_POST['field_input_transformation_options'][$fieldindex]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Go back to the structure sub-page
|
||||
$message = Message::success(
|
||||
__('Table %1$s has been altered successfully.')
|
||||
);
|
||||
$message->addParam($table);
|
||||
$this->response->addJSON(
|
||||
'message',
|
||||
Generator::getMessage($message, $sql_query, 'success')
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$url_params = ['db' => $db, 'table' => $table];
|
||||
$err_url = Util::getScriptNameForOption($cfg['DefaultTabTable'], 'table');
|
||||
$err_url .= Url::getCommon($url_params, '&');
|
||||
|
||||
DbTableExists::check();
|
||||
|
||||
$active_page = Url::getFromRoute('/table/structure');
|
||||
/**
|
||||
* Display the form
|
||||
*/
|
||||
$action = Url::getFromRoute('/table/add-field');
|
||||
|
||||
$this->addScriptFiles(['vendor/jquery/jquery.uitablefilter.js', 'indexes.js']);
|
||||
|
||||
$templateData = ColumnsDefinition::displayForm(
|
||||
$this->transformations,
|
||||
$this->relation,
|
||||
$this->dbi,
|
||||
$action,
|
||||
$num_fields,
|
||||
$regenerate
|
||||
);
|
||||
|
||||
$this->render('columns_definitions/column_definitions_form', $templateData);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,324 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Table;
|
||||
|
||||
use PhpMyAdmin\Config\PageSettings;
|
||||
use PhpMyAdmin\DbTableExists;
|
||||
use PhpMyAdmin\Html\Generator;
|
||||
use PhpMyAdmin\InsertEdit;
|
||||
use PhpMyAdmin\Relation;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use function array_fill;
|
||||
use function count;
|
||||
use function is_array;
|
||||
use function mb_strpos;
|
||||
use function strlen;
|
||||
|
||||
/**
|
||||
* Displays form for editing and inserting new table rows.
|
||||
*/
|
||||
class ChangeController extends AbstractController
|
||||
{
|
||||
/** @var InsertEdit */
|
||||
private $insertEdit;
|
||||
|
||||
/** @var Relation */
|
||||
private $relation;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param string $db Database name.
|
||||
* @param string $table Table name.
|
||||
*/
|
||||
public function __construct(
|
||||
$response,
|
||||
Template $template,
|
||||
$db,
|
||||
$table,
|
||||
InsertEdit $insertEdit,
|
||||
Relation $relation
|
||||
) {
|
||||
parent::__construct($response, $template, $db, $table);
|
||||
$this->insertEdit = $insertEdit;
|
||||
$this->relation = $relation;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $cfg, $is_upload, $db, $table, $text_dir, $disp_message, $url_params;
|
||||
global $err_url, $where_clause, $unsaved_values, $insert_mode, $where_clause_array, $where_clauses;
|
||||
global $result, $rows, $found_unique_key, $after_insert, $comments_map, $table_columns;
|
||||
global $chg_evt_handler, $timestamp_seen, $columns_cnt, $tabindex, $tabindex_for_function;
|
||||
global $tabindex_for_null, $tabindex_for_value, $o_rows, $biggest_max_file_size, $has_blob_field;
|
||||
global $jsvkey, $vkey, $current_result, $repopulate, $checked;
|
||||
|
||||
$pageSettings = new PageSettings('Edit');
|
||||
$this->response->addHTML($pageSettings->getErrorHTML());
|
||||
$this->response->addHTML($pageSettings->getHTML());
|
||||
|
||||
DbTableExists::check();
|
||||
|
||||
/**
|
||||
* Determine whether Insert or Edit and set global variables
|
||||
*/
|
||||
[
|
||||
$insert_mode,
|
||||
$where_clause,
|
||||
$where_clause_array,
|
||||
$where_clauses,
|
||||
$result,
|
||||
$rows,
|
||||
$found_unique_key,
|
||||
$after_insert,
|
||||
] = $this->insertEdit->determineInsertOrEdit(
|
||||
$where_clause ?? null,
|
||||
$db,
|
||||
$table
|
||||
);
|
||||
// Increase number of rows if unsaved rows are more
|
||||
if (! empty($unsaved_values) && count($rows) < count($unsaved_values)) {
|
||||
$rows = array_fill(0, count($unsaved_values), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the url to return to in case of error in a sql statement
|
||||
* (at this point, $GLOBALS['goto'] will be set but could be empty)
|
||||
*/
|
||||
if (empty($GLOBALS['goto'])) {
|
||||
if (strlen($table) > 0) {
|
||||
// avoid a problem (see bug #2202709)
|
||||
$GLOBALS['goto'] = Url::getFromRoute('/table/sql');
|
||||
} else {
|
||||
$GLOBALS['goto'] = Url::getFromRoute('/database/sql');
|
||||
}
|
||||
}
|
||||
|
||||
$_url_params = $this->insertEdit->getUrlParameters($db, $table);
|
||||
$err_url = $GLOBALS['goto'] . Url::getCommon(
|
||||
$_url_params,
|
||||
mb_strpos($GLOBALS['goto'], '?') === false ? '?' : '&'
|
||||
);
|
||||
unset($_url_params);
|
||||
|
||||
$comments_map = $this->insertEdit->getCommentsMap($db, $table);
|
||||
|
||||
/**
|
||||
* START REGULAR OUTPUT
|
||||
*/
|
||||
|
||||
$this->addScriptFiles([
|
||||
'makegrid.js',
|
||||
'vendor/stickyfill.min.js',
|
||||
'sql.js',
|
||||
'table/change.js',
|
||||
'vendor/jquery/additional-methods.js',
|
||||
'gis_data_editor.js',
|
||||
]);
|
||||
|
||||
/**
|
||||
* Displays the query submitted and its result
|
||||
*
|
||||
* $disp_message come from /table/replace
|
||||
*/
|
||||
if (! empty($disp_message)) {
|
||||
$this->response->addHTML(Generator::getMessage($disp_message, null));
|
||||
}
|
||||
|
||||
$table_columns = $this->insertEdit->getTableColumns($db, $table);
|
||||
|
||||
// retrieve keys into foreign fields, if any
|
||||
$foreigners = $this->relation->getForeigners($db, $table);
|
||||
|
||||
// Retrieve form parameters for insert/edit form
|
||||
$_form_params = $this->insertEdit->getFormParametersForInsertForm(
|
||||
$db,
|
||||
$table,
|
||||
$where_clauses,
|
||||
$where_clause_array,
|
||||
$err_url
|
||||
);
|
||||
|
||||
/**
|
||||
* Displays the form
|
||||
*/
|
||||
// autocomplete feature of IE kills the "onchange" event handler and it
|
||||
// must be replaced by the "onpropertychange" one in this case
|
||||
$chg_evt_handler = 'onchange';
|
||||
// Had to put the URI because when hosted on an https server,
|
||||
// some browsers send wrongly this form to the http server.
|
||||
|
||||
$html_output = '';
|
||||
// Set if we passed the first timestamp field
|
||||
$timestamp_seen = false;
|
||||
$columns_cnt = count($table_columns);
|
||||
|
||||
$tabindex = 0;
|
||||
$tabindex_for_function = +3000;
|
||||
$tabindex_for_null = +6000;
|
||||
$tabindex_for_value = 0;
|
||||
$o_rows = 0;
|
||||
$biggest_max_file_size = 0;
|
||||
|
||||
$url_params['db'] = $db;
|
||||
$url_params['table'] = $table;
|
||||
$url_params = $this->insertEdit->urlParamsInEditMode(
|
||||
$url_params,
|
||||
$where_clause_array
|
||||
);
|
||||
|
||||
$has_blob_field = false;
|
||||
foreach ($table_columns as $column) {
|
||||
if ($this->insertEdit->isColumn(
|
||||
$column,
|
||||
[
|
||||
'blob',
|
||||
'tinyblob',
|
||||
'mediumblob',
|
||||
'longblob',
|
||||
]
|
||||
)) {
|
||||
$has_blob_field = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//Insert/Edit form
|
||||
//If table has blob fields we have to disable ajax.
|
||||
$html_output .= $this->insertEdit->getHtmlForInsertEditFormHeader($has_blob_field, $is_upload);
|
||||
|
||||
$html_output .= Url::getHiddenInputs($_form_params);
|
||||
|
||||
// user can toggle the display of Function column and column types
|
||||
// (currently does not work for multi-edits)
|
||||
if (! $cfg['ShowFunctionFields'] || ! $cfg['ShowFieldTypesInDataEditView']) {
|
||||
$html_output .= __('Show');
|
||||
}
|
||||
|
||||
if (! $cfg['ShowFunctionFields']) {
|
||||
$html_output .= $this->insertEdit->showTypeOrFunction('function', $url_params, false);
|
||||
}
|
||||
|
||||
if (! $cfg['ShowFieldTypesInDataEditView']) {
|
||||
$html_output .= $this->insertEdit->showTypeOrFunction('type', $url_params, false);
|
||||
}
|
||||
|
||||
$GLOBALS['plugin_scripts'] = [];
|
||||
foreach ($rows as $row_id => $current_row) {
|
||||
if (empty($current_row)) {
|
||||
$current_row = [];
|
||||
}
|
||||
|
||||
$jsvkey = $row_id;
|
||||
$vkey = '[multi_edit][' . $jsvkey . ']';
|
||||
|
||||
$current_result = (isset($result) && is_array($result) && isset($result[$row_id])
|
||||
? $result[$row_id]
|
||||
: $result);
|
||||
$repopulate = [];
|
||||
$checked = true;
|
||||
if (isset($unsaved_values[$row_id])) {
|
||||
$repopulate = $unsaved_values[$row_id];
|
||||
$checked = false;
|
||||
}
|
||||
if ($insert_mode && $row_id > 0) {
|
||||
$html_output .= $this->insertEdit->getHtmlForIgnoreOption($row_id, $checked);
|
||||
}
|
||||
|
||||
$html_output .= $this->insertEdit->getHtmlForInsertEditRow(
|
||||
$url_params,
|
||||
$table_columns,
|
||||
$comments_map,
|
||||
$timestamp_seen,
|
||||
$current_result,
|
||||
$chg_evt_handler,
|
||||
$jsvkey,
|
||||
$vkey,
|
||||
$insert_mode,
|
||||
$current_row,
|
||||
$o_rows,
|
||||
$tabindex,
|
||||
$columns_cnt,
|
||||
$is_upload,
|
||||
$tabindex_for_function,
|
||||
$foreigners,
|
||||
$tabindex_for_null,
|
||||
$tabindex_for_value,
|
||||
$table,
|
||||
$db,
|
||||
$row_id,
|
||||
$biggest_max_file_size,
|
||||
$text_dir,
|
||||
$repopulate,
|
||||
$where_clause_array
|
||||
);
|
||||
}
|
||||
|
||||
$this->addScriptFiles($GLOBALS['plugin_scripts']);
|
||||
|
||||
unset($unsaved_values, $checked, $repopulate, $GLOBALS['plugin_scripts']);
|
||||
|
||||
if (! isset($after_insert)) {
|
||||
$after_insert = 'back';
|
||||
}
|
||||
|
||||
//action panel
|
||||
$html_output .= $this->insertEdit->getActionsPanel(
|
||||
$where_clause,
|
||||
$after_insert,
|
||||
$tabindex,
|
||||
$tabindex_for_value,
|
||||
$found_unique_key
|
||||
);
|
||||
|
||||
if ($biggest_max_file_size > 0) {
|
||||
$html_output .= '<input type="hidden" name="MAX_FILE_SIZE" value="' . $biggest_max_file_size . '">' . "\n";
|
||||
}
|
||||
$html_output .= '</form>';
|
||||
|
||||
$html_output .= $this->insertEdit->getHtmlForGisEditor();
|
||||
// end Insert/Edit form
|
||||
|
||||
if ($insert_mode) {
|
||||
//Continue insertion form
|
||||
$html_output .= $this->insertEdit->getContinueInsertionForm(
|
||||
$table,
|
||||
$db,
|
||||
$where_clause_array,
|
||||
$err_url
|
||||
);
|
||||
}
|
||||
|
||||
$this->response->addHTML($html_output);
|
||||
}
|
||||
|
||||
public function rows(): void
|
||||
{
|
||||
global $active_page, $where_clause;
|
||||
|
||||
if (isset($_POST['goto']) && (! isset($_POST['rows_to_delete']) || ! is_array($_POST['rows_to_delete']))) {
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON('message', __('No row selected.'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// As we got the rows to be edited from the
|
||||
// 'rows_to_delete' checkbox, we use the index of it as the
|
||||
// indicating WHERE clause. Then we build the array which is used
|
||||
// for the /table/change script.
|
||||
$where_clause = [];
|
||||
if (isset($_POST['rows_to_delete']) && is_array($_POST['rows_to_delete'])) {
|
||||
foreach ($_POST['rows_to_delete'] as $i => $i_where_clause) {
|
||||
$where_clause[] = $i_where_clause;
|
||||
}
|
||||
}
|
||||
|
||||
$active_page = Url::getFromRoute('/table/change');
|
||||
|
||||
$this->index();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,237 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Table;
|
||||
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\DbTableExists;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\SqlParser\Components\Limit;
|
||||
use PhpMyAdmin\SqlParser\Parser;
|
||||
use PhpMyAdmin\SqlParser\Statements\SelectStatement;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
use function array_keys;
|
||||
use function htmlspecialchars;
|
||||
use function in_array;
|
||||
use function json_encode;
|
||||
use function min;
|
||||
use function strlen;
|
||||
|
||||
/**
|
||||
* Handles creation of the chart.
|
||||
*/
|
||||
class ChartController extends AbstractController
|
||||
{
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param string $db Database name.
|
||||
* @param string $table Table name.
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, $db, $table, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template, $db, $table);
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $db, $table, $cfg, $sql_query, $err_url;
|
||||
|
||||
if (isset($_REQUEST['pos'], $_REQUEST['session_max_rows']) && $this->response->isAjax()
|
||||
) {
|
||||
$this->ajax();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Throw error if no sql query is set
|
||||
if (! isset($sql_query) || $sql_query == '') {
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addHTML(
|
||||
Message::error(__('No SQL query was set to fetch data.'))
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->addScriptFiles([
|
||||
'chart.js',
|
||||
'table/chart.js',
|
||||
'vendor/jqplot/jquery.jqplot.js',
|
||||
'vendor/jqplot/plugins/jqplot.barRenderer.js',
|
||||
'vendor/jqplot/plugins/jqplot.canvasAxisLabelRenderer.js',
|
||||
'vendor/jqplot/plugins/jqplot.canvasTextRenderer.js',
|
||||
'vendor/jqplot/plugins/jqplot.categoryAxisRenderer.js',
|
||||
'vendor/jqplot/plugins/jqplot.dateAxisRenderer.js',
|
||||
'vendor/jqplot/plugins/jqplot.pointLabels.js',
|
||||
'vendor/jqplot/plugins/jqplot.pieRenderer.js',
|
||||
'vendor/jqplot/plugins/jqplot.enhancedPieLegendRenderer.js',
|
||||
'vendor/jqplot/plugins/jqplot.highlighter.js',
|
||||
]);
|
||||
|
||||
$url_params = [];
|
||||
|
||||
/**
|
||||
* Runs common work
|
||||
*/
|
||||
if (strlen($table) > 0) {
|
||||
Util::checkParameters(['db', 'table']);
|
||||
|
||||
$url_params = ['db' => $db, 'table' => $table];
|
||||
$err_url = Util::getScriptNameForOption($cfg['DefaultTabTable'], 'table');
|
||||
$err_url .= Url::getCommon($url_params, '&');
|
||||
|
||||
DbTableExists::check();
|
||||
|
||||
$url_params['goto'] = Util::getScriptNameForOption($cfg['DefaultTabTable'], 'table');
|
||||
$url_params['back'] = Url::getFromRoute('/table/sql');
|
||||
$this->dbi->selectDb($db);
|
||||
} elseif (strlen($db) > 0) {
|
||||
$url_params['goto'] = Util::getScriptNameForOption(
|
||||
$cfg['DefaultTabDatabase'],
|
||||
'database'
|
||||
);
|
||||
$url_params['back'] = Url::getFromRoute('/sql');
|
||||
|
||||
Util::checkParameters(['db']);
|
||||
|
||||
$err_url = Util::getScriptNameForOption($cfg['DefaultTabDatabase'], 'database');
|
||||
$err_url .= Url::getCommon(['db' => $db], '&');
|
||||
|
||||
if (! $this->hasDatabase()) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
$url_params['goto'] = Util::getScriptNameForOption(
|
||||
$cfg['DefaultTabServer'],
|
||||
'server'
|
||||
);
|
||||
$url_params['back'] = Url::getFromRoute('/sql');
|
||||
$err_url = Url::getFromRoute('/');
|
||||
|
||||
if ($this->dbi->isSuperUser()) {
|
||||
$this->dbi->selectDb('mysql');
|
||||
}
|
||||
}
|
||||
|
||||
$data = [];
|
||||
|
||||
$result = $this->dbi->tryQuery($sql_query);
|
||||
$fields_meta = $this->dbi->getFieldsMeta($result);
|
||||
while ($row = $this->dbi->fetchAssoc($result)) {
|
||||
$data[] = $row;
|
||||
}
|
||||
|
||||
$keys = array_keys($data[0]);
|
||||
|
||||
$numeric_types = [
|
||||
'int',
|
||||
'real',
|
||||
];
|
||||
$numeric_column_count = 0;
|
||||
foreach ($keys as $idx => $key) {
|
||||
if (! in_array($fields_meta[$idx]->type, $numeric_types)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$numeric_column_count++;
|
||||
}
|
||||
|
||||
if ($numeric_column_count == 0) {
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON(
|
||||
'message',
|
||||
__('No numeric columns present in the table to plot.')
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$url_params['db'] = $db;
|
||||
$url_params['reload'] = 1;
|
||||
|
||||
/**
|
||||
* Displays the page
|
||||
*/
|
||||
$this->render('table/chart/tbl_chart', [
|
||||
'url_params' => $url_params,
|
||||
'keys' => $keys,
|
||||
'fields_meta' => $fields_meta,
|
||||
'numeric_types' => $numeric_types,
|
||||
'numeric_column_count' => $numeric_column_count,
|
||||
'sql_query' => $sql_query,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle ajax request
|
||||
*/
|
||||
public function ajax(): void
|
||||
{
|
||||
global $db, $table, $sql_query, $url_params, $err_url, $cfg;
|
||||
|
||||
if (strlen($table) > 0 && strlen($db) > 0) {
|
||||
Util::checkParameters(['db', 'table']);
|
||||
|
||||
$url_params = ['db' => $db, 'table' => $table];
|
||||
$err_url = Util::getScriptNameForOption($cfg['DefaultTabTable'], 'table');
|
||||
$err_url .= Url::getCommon($url_params, '&');
|
||||
|
||||
DbTableExists::check();
|
||||
}
|
||||
|
||||
$parser = new Parser($sql_query);
|
||||
/**
|
||||
* @var SelectStatement $statement
|
||||
*/
|
||||
$statement = $parser->statements[0];
|
||||
if (empty($statement->limit)) {
|
||||
$statement->limit = new Limit(
|
||||
$_REQUEST['session_max_rows'],
|
||||
$_REQUEST['pos']
|
||||
);
|
||||
} else {
|
||||
$start = $statement->limit->offset + $_REQUEST['pos'];
|
||||
$rows = min(
|
||||
$_REQUEST['session_max_rows'],
|
||||
$statement->limit->rowCount - $_REQUEST['pos']
|
||||
);
|
||||
$statement->limit = new Limit($rows, $start);
|
||||
}
|
||||
$sql_with_limit = $statement->build();
|
||||
|
||||
$data = [];
|
||||
$result = $this->dbi->tryQuery($sql_with_limit);
|
||||
while ($row = $this->dbi->fetchAssoc($result)) {
|
||||
$data[] = $row;
|
||||
}
|
||||
|
||||
if (empty($data)) {
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON('message', __('No data to display'));
|
||||
|
||||
return;
|
||||
}
|
||||
$sanitized_data = [];
|
||||
|
||||
foreach ($data as $data_row_number => $data_row) {
|
||||
$tmp_row = [];
|
||||
foreach ($data_row as $data_column => $data_value) {
|
||||
$escaped_value = $data_value === null ? null : htmlspecialchars($data_value);
|
||||
$tmp_row[htmlspecialchars($data_column)] = $escaped_value;
|
||||
}
|
||||
$sanitized_data[] = $tmp_row;
|
||||
}
|
||||
$this->response->setRequestStatus(true);
|
||||
$this->response->addJSON('message', null);
|
||||
$this->response->addJSON('chartData', json_encode($sanitized_data));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,183 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Table;
|
||||
|
||||
use PhpMyAdmin\Config;
|
||||
use PhpMyAdmin\Core;
|
||||
use PhpMyAdmin\CreateAddField;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Html\Generator;
|
||||
use PhpMyAdmin\Relation;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Table\ColumnsDefinition;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Transformations;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
use function htmlspecialchars;
|
||||
use function is_array;
|
||||
use function mb_strtolower;
|
||||
use function sprintf;
|
||||
use function strlen;
|
||||
|
||||
/**
|
||||
* Displays table create form and handles it.
|
||||
*/
|
||||
class CreateController extends AbstractController
|
||||
{
|
||||
/** @var Transformations */
|
||||
private $transformations;
|
||||
|
||||
/** @var Config */
|
||||
private $config;
|
||||
|
||||
/** @var Relation */
|
||||
private $relation;
|
||||
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param string $db Database name.
|
||||
* @param string $table Table name.
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct(
|
||||
$response,
|
||||
Template $template,
|
||||
$db,
|
||||
$table,
|
||||
Transformations $transformations,
|
||||
Config $config,
|
||||
Relation $relation,
|
||||
$dbi
|
||||
) {
|
||||
parent::__construct($response, $template, $db, $table);
|
||||
$this->transformations = $transformations;
|
||||
$this->config = $config;
|
||||
$this->relation = $relation;
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $num_fields, $action, $sql_query, $result, $db, $table;
|
||||
|
||||
Util::checkParameters(['db']);
|
||||
|
||||
$cfg = $this->config->settings;
|
||||
|
||||
/* Check if database name is empty */
|
||||
if (strlen($db) === 0) {
|
||||
Generator::mysqlDie(
|
||||
__('The database name is empty!'),
|
||||
'',
|
||||
false,
|
||||
'index.php'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects the database to work with
|
||||
*/
|
||||
if (! $this->dbi->selectDb($db)) {
|
||||
Generator::mysqlDie(
|
||||
sprintf(__('\'%s\' database does not exist.'), htmlspecialchars($db)),
|
||||
'',
|
||||
false,
|
||||
'index.php'
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->dbi->getColumns($db, $table)) {
|
||||
// table exists already
|
||||
Generator::mysqlDie(
|
||||
sprintf(__('Table %s already exists!'), htmlspecialchars($table)),
|
||||
'',
|
||||
false,
|
||||
Url::getFromRoute('/database/structure', ['db' => $db])
|
||||
);
|
||||
}
|
||||
|
||||
$createAddField = new CreateAddField($this->dbi);
|
||||
|
||||
$num_fields = $createAddField->getNumberOfFieldsFromRequest();
|
||||
|
||||
$action = Url::getFromRoute('/table/create');
|
||||
|
||||
/**
|
||||
* The form used to define the structure of the table has been submitted
|
||||
*/
|
||||
if (isset($_POST['do_save_data'])) {
|
||||
// lower_case_table_names=1 `DB` becomes `db`
|
||||
if ($this->dbi->getLowerCaseNames() === '1') {
|
||||
$db = mb_strtolower(
|
||||
$db
|
||||
);
|
||||
$table = mb_strtolower(
|
||||
$table
|
||||
);
|
||||
}
|
||||
$sql_query = $createAddField->getTableCreationQuery($db, $table);
|
||||
|
||||
// If there is a request for SQL previewing.
|
||||
if (isset($_POST['preview_sql'])) {
|
||||
Core::previewSQL($sql_query);
|
||||
|
||||
return;
|
||||
}
|
||||
// Executes the query
|
||||
$result = $this->dbi->tryQuery($sql_query);
|
||||
|
||||
if ($result) {
|
||||
// Update comment table for mime types [MIME]
|
||||
if (isset($_POST['field_mimetype'])
|
||||
&& is_array($_POST['field_mimetype'])
|
||||
&& $cfg['BrowseMIME']
|
||||
) {
|
||||
foreach ($_POST['field_mimetype'] as $fieldindex => $mimetype) {
|
||||
if (! isset($_POST['field_name'][$fieldindex])
|
||||
|| strlen($_POST['field_name'][$fieldindex]) <= 0
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->transformations->setMime(
|
||||
$db,
|
||||
$table,
|
||||
$_POST['field_name'][$fieldindex],
|
||||
$mimetype,
|
||||
$_POST['field_transformation'][$fieldindex],
|
||||
$_POST['field_transformation_options'][$fieldindex],
|
||||
$_POST['field_input_transformation'][$fieldindex],
|
||||
$_POST['field_input_transformation_options'][$fieldindex]
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON('message', $this->dbi->getError());
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// This global variable needs to be reset for the header class to function properly
|
||||
$table = '';
|
||||
|
||||
$this->addScriptFiles(['vendor/jquery/jquery.uitablefilter.js', 'indexes.js']);
|
||||
|
||||
$templateData = ColumnsDefinition::displayForm(
|
||||
$this->transformations,
|
||||
$this->relation,
|
||||
$this->dbi,
|
||||
$action,
|
||||
$num_fields
|
||||
);
|
||||
|
||||
$this->render('columns_definitions/column_definitions_form', $templateData);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,142 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Table;
|
||||
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\DbTableExists;
|
||||
use PhpMyAdmin\Operations;
|
||||
use PhpMyAdmin\Relation;
|
||||
use PhpMyAdmin\RelationCleanup;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Sql;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Transformations;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
use function is_array;
|
||||
use function sprintf;
|
||||
|
||||
class DeleteController extends AbstractController
|
||||
{
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param string $db Database name.
|
||||
* @param string $table Table name.
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, $db, $table, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template, $db, $table);
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function rows(): void
|
||||
{
|
||||
global $db, $goto, $sql_query, $table, $disp_message, $disp_query, $PMA_Theme, $active_page;
|
||||
|
||||
$mult_btn = $_POST['mult_btn'] ?? '';
|
||||
$original_sql_query = $_POST['original_sql_query'] ?? '';
|
||||
$selected = $_POST['selected'] ?? [];
|
||||
|
||||
$relation = new Relation($this->dbi);
|
||||
$sql = new Sql(
|
||||
$this->dbi,
|
||||
$relation,
|
||||
new RelationCleanup($this->dbi, $relation),
|
||||
new Operations($this->dbi, $relation),
|
||||
new Transformations(),
|
||||
$this->template
|
||||
);
|
||||
|
||||
if ($mult_btn === __('Yes')) {
|
||||
$default_fk_check_value = Util::handleDisableFKCheckInit();
|
||||
$sql_query = '';
|
||||
|
||||
foreach ($selected as $row) {
|
||||
$query = sprintf(
|
||||
'DELETE FROM %s WHERE %s LIMIT 1;',
|
||||
Util::backquote($table),
|
||||
$row
|
||||
);
|
||||
$sql_query .= $query . "\n";
|
||||
$this->dbi->selectDb($db);
|
||||
$this->dbi->query($query);
|
||||
}
|
||||
|
||||
if (! empty($_REQUEST['pos'])) {
|
||||
$_REQUEST['pos'] = $sql->calculatePosForLastPage(
|
||||
$db,
|
||||
$table,
|
||||
$_REQUEST['pos']
|
||||
);
|
||||
}
|
||||
|
||||
Util::handleDisableFKCheckCleanup($default_fk_check_value);
|
||||
|
||||
$disp_message = __('Your SQL query has been executed successfully.');
|
||||
$disp_query = $sql_query;
|
||||
}
|
||||
|
||||
$_url_params = $GLOBALS['url_params'];
|
||||
$_url_params['goto'] = Url::getFromRoute('/table/sql');
|
||||
|
||||
if (isset($original_sql_query)) {
|
||||
$sql_query = $original_sql_query;
|
||||
}
|
||||
|
||||
$active_page = Url::getFromRoute('/sql');
|
||||
|
||||
$this->response->addHTML($sql->executeQueryAndSendQueryResponse(
|
||||
null,
|
||||
false,
|
||||
$db,
|
||||
$table,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
$goto,
|
||||
$PMA_Theme->getImgPath(),
|
||||
null,
|
||||
null,
|
||||
$sql_query,
|
||||
null
|
||||
));
|
||||
}
|
||||
|
||||
public function confirm(): void
|
||||
{
|
||||
global $db, $table, $sql_query, $url_params, $err_url, $cfg;
|
||||
|
||||
$selected = $_POST['rows_to_delete'] ?? null;
|
||||
|
||||
if (! isset($selected) || ! is_array($selected)) {
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON('message', __('No row selected.'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Util::checkParameters(['db', 'table']);
|
||||
|
||||
$url_params = ['db' => $db, 'table' => $table];
|
||||
$err_url = Util::getScriptNameForOption($cfg['DefaultTabTable'], 'table');
|
||||
$err_url .= Url::getCommon($url_params, '&');
|
||||
|
||||
DbTableExists::check();
|
||||
|
||||
$this->render('table/delete/confirm', [
|
||||
'db' => $db,
|
||||
'table' => $table,
|
||||
'selected' => $selected,
|
||||
'sql_query' => $sql_query,
|
||||
'is_foreign_key_check' => Util::isForeignKeyCheck(),
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,175 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Table;
|
||||
|
||||
use PhpMyAdmin\Config\PageSettings;
|
||||
use PhpMyAdmin\Export\Options;
|
||||
use PhpMyAdmin\Html\Generator;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\Plugins;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\SqlParser\Parser;
|
||||
use PhpMyAdmin\SqlParser\Statements\SelectStatement;
|
||||
use PhpMyAdmin\SqlParser\Utils\Query;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
use function array_merge;
|
||||
use function implode;
|
||||
use function is_array;
|
||||
|
||||
class ExportController extends AbstractController
|
||||
{
|
||||
/** @var Options */
|
||||
private $export;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param string $db Database name.
|
||||
* @param string $table Table name.
|
||||
*/
|
||||
public function __construct(
|
||||
$response,
|
||||
Template $template,
|
||||
$db,
|
||||
$table,
|
||||
Options $export
|
||||
) {
|
||||
parent::__construct($response, $template, $db, $table);
|
||||
$this->export = $export;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $db, $url_params, $table, $replaces, $cfg, $err_url;
|
||||
global $sql_query, $where_clause, $num_tables, $unlim_num_rows;
|
||||
|
||||
$pageSettings = new PageSettings('Export');
|
||||
$pageSettingsErrorHtml = $pageSettings->getErrorHTML();
|
||||
$pageSettingsHtml = $pageSettings->getHTML();
|
||||
|
||||
$this->addScriptFiles(['export.js']);
|
||||
|
||||
Util::checkParameters(['db', 'table']);
|
||||
|
||||
$url_params = ['db' => $db, 'table' => $table];
|
||||
$err_url = Util::getScriptNameForOption($cfg['DefaultTabTable'], 'table');
|
||||
$err_url .= Url::getCommon($url_params, '&');
|
||||
|
||||
$url_params['goto'] = Url::getFromRoute('/table/export');
|
||||
$url_params['back'] = Url::getFromRoute('/table/export');
|
||||
|
||||
$message = '';
|
||||
|
||||
// When we have some query, we need to remove LIMIT from that and possibly
|
||||
// generate WHERE clause (if we are asked to export specific rows)
|
||||
|
||||
if (! empty($sql_query)) {
|
||||
$parser = new Parser($sql_query);
|
||||
|
||||
if (! empty($parser->statements[0])
|
||||
&& ($parser->statements[0] instanceof SelectStatement)
|
||||
) {
|
||||
// Checking if the WHERE clause has to be replaced.
|
||||
if (! empty($where_clause) && is_array($where_clause)) {
|
||||
$replaces[] = [
|
||||
'WHERE',
|
||||
'WHERE (' . implode(') OR (', $where_clause) . ')',
|
||||
];
|
||||
}
|
||||
|
||||
// Preparing to remove the LIMIT clause.
|
||||
$replaces[] = [
|
||||
'LIMIT',
|
||||
'',
|
||||
];
|
||||
|
||||
// Replacing the clauses.
|
||||
$sql_query = Query::replaceClauses(
|
||||
$parser->statements[0],
|
||||
$parser->list,
|
||||
$replaces
|
||||
);
|
||||
}
|
||||
|
||||
$message = Generator::getMessage(Message::success());
|
||||
}
|
||||
|
||||
if (! isset($sql_query)) {
|
||||
$sql_query = '';
|
||||
}
|
||||
if (! isset($num_tables)) {
|
||||
$num_tables = 0;
|
||||
}
|
||||
if (! isset($unlim_num_rows)) {
|
||||
$unlim_num_rows = 0;
|
||||
}
|
||||
|
||||
$GLOBALS['single_table'] = $_POST['single_table'] ?? $_GET['single_table'] ?? $GLOBALS['single_table'] ?? null;
|
||||
|
||||
$exportList = Plugins::getExport('table', isset($GLOBALS['single_table']));
|
||||
|
||||
if (empty($exportList)) {
|
||||
$this->response->addHTML(Message::error(
|
||||
__('Could not load export plugins, please check your installation!')
|
||||
)->getDisplay());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$exportType = 'table';
|
||||
$isReturnBackFromRawExport = isset($_POST['export_type']) && $_POST['export_type'] === 'raw';
|
||||
if (isset($_POST['raw_query']) || $isReturnBackFromRawExport) {
|
||||
$exportType = 'raw';
|
||||
}
|
||||
|
||||
$options = $this->export->getOptions(
|
||||
$exportType,
|
||||
$db,
|
||||
$table,
|
||||
$sql_query,
|
||||
$num_tables,
|
||||
$unlim_num_rows,
|
||||
$exportList
|
||||
);
|
||||
|
||||
$this->render('table/export/index', array_merge($options, [
|
||||
'export_type' => $exportType,
|
||||
'page_settings_error_html' => $pageSettingsErrorHtml,
|
||||
'page_settings_html' => $pageSettingsHtml,
|
||||
'message' => $message,
|
||||
]));
|
||||
}
|
||||
|
||||
public function rows(): void
|
||||
{
|
||||
global $active_page, $single_table, $where_clause;
|
||||
|
||||
if (isset($_POST['goto']) && (! isset($_POST['rows_to_delete']) || ! is_array($_POST['rows_to_delete']))) {
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON('message', __('No row selected.'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Needed to allow SQL export
|
||||
$single_table = true;
|
||||
|
||||
// As we got the rows to be exported from the
|
||||
// 'rows_to_delete' checkbox, we use the index of it as the
|
||||
// indicating WHERE clause. Then we build the array which is used
|
||||
// for the /table/change script.
|
||||
$where_clause = [];
|
||||
if (isset($_POST['rows_to_delete']) && is_array($_POST['rows_to_delete'])) {
|
||||
foreach ($_POST['rows_to_delete'] as $i => $i_where_clause) {
|
||||
$where_clause[] = $i_where_clause;
|
||||
}
|
||||
}
|
||||
|
||||
$active_page = Url::getFromRoute('/table/export');
|
||||
|
||||
$this->index();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,390 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Table;
|
||||
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\DbTableExists;
|
||||
use PhpMyAdmin\Html\Generator;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
use function array_key_exists;
|
||||
use function count;
|
||||
use function is_array;
|
||||
use function mb_strtolower;
|
||||
use function preg_match;
|
||||
use function preg_replace;
|
||||
use function str_ireplace;
|
||||
use function str_replace;
|
||||
use function strncasecmp;
|
||||
use function strpos;
|
||||
|
||||
/**
|
||||
* Handles find and replace tab.
|
||||
*
|
||||
* Displays find and replace form, allows previewing and do the replacing.
|
||||
*/
|
||||
class FindReplaceController extends AbstractController
|
||||
{
|
||||
/** @var array */
|
||||
private $columnNames;
|
||||
|
||||
/** @var array */
|
||||
private $columnTypes;
|
||||
|
||||
/** @var string */
|
||||
private $connectionCharSet;
|
||||
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param string $db Database name
|
||||
* @param string $table Table name
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, $db, $table, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template, $db, $table);
|
||||
$this->dbi = $dbi;
|
||||
|
||||
$this->columnNames = [];
|
||||
$this->columnTypes = [];
|
||||
$this->loadTableInfo();
|
||||
$this->connectionCharSet = $this->dbi->fetchValue(
|
||||
'SELECT @@character_set_connection'
|
||||
);
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $db, $table, $url_params, $cfg, $err_url;
|
||||
|
||||
Util::checkParameters(['db', 'table']);
|
||||
|
||||
$url_params = ['db' => $db, 'table' => $table];
|
||||
$err_url = Util::getScriptNameForOption($cfg['DefaultTabTable'], 'table');
|
||||
$err_url .= Url::getCommon($url_params, '&');
|
||||
|
||||
DbTableExists::check();
|
||||
|
||||
if (isset($_POST['find'])) {
|
||||
$this->findAction();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->addScriptFiles(['table/find_replace.js']);
|
||||
|
||||
if (isset($_POST['replace'])) {
|
||||
$this->replaceAction();
|
||||
}
|
||||
|
||||
// Displays the find and replace form
|
||||
$this->displaySelectionFormAction();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the columns of a table along with their types.
|
||||
*/
|
||||
private function loadTableInfo(): void
|
||||
{
|
||||
// Gets the list and number of columns
|
||||
$columns = $this->dbi->getColumns(
|
||||
$this->db,
|
||||
$this->table,
|
||||
null,
|
||||
true
|
||||
);
|
||||
|
||||
foreach ($columns as $row) {
|
||||
// set column name
|
||||
$this->columnNames[] = $row['Field'];
|
||||
|
||||
$type = (string) $row['Type'];
|
||||
// reformat mysql query output
|
||||
if (strncasecmp($type, 'set', 3) == 0
|
||||
|| strncasecmp($type, 'enum', 4) == 0
|
||||
) {
|
||||
$type = str_replace(',', ', ', $type);
|
||||
} else {
|
||||
// strip the "BINARY" attribute, except if we find "BINARY(" because
|
||||
// this would be a BINARY or VARBINARY column type
|
||||
if (! preg_match('@BINARY[\(]@i', $type)) {
|
||||
$type = str_ireplace('BINARY', '', $type);
|
||||
}
|
||||
$type = str_ireplace('ZEROFILL', '', $type);
|
||||
$type = str_ireplace('UNSIGNED', '', $type);
|
||||
$type = mb_strtolower($type);
|
||||
}
|
||||
if (empty($type)) {
|
||||
$type = ' ';
|
||||
}
|
||||
$this->columnTypes[] = $type;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display selection form action
|
||||
*/
|
||||
public function displaySelectionFormAction(): void
|
||||
{
|
||||
global $goto;
|
||||
|
||||
if (! isset($goto)) {
|
||||
$goto = Util::getScriptNameForOption(
|
||||
$GLOBALS['cfg']['DefaultTabTable'],
|
||||
'table'
|
||||
);
|
||||
}
|
||||
|
||||
$column_names = $this->columnNames;
|
||||
$column_types = $this->columnTypes;
|
||||
$types = [];
|
||||
$num_cols = count($column_names);
|
||||
for ($i = 0; $i < $num_cols; $i++) {
|
||||
$types[$column_names[$i]] = preg_replace(
|
||||
'@\\(.*@s',
|
||||
'',
|
||||
$column_types[$i]
|
||||
);
|
||||
}
|
||||
|
||||
$this->render('table/find_replace/index', [
|
||||
'db' => $this->db,
|
||||
'table' => $this->table,
|
||||
'goto' => $goto,
|
||||
'column_names' => $column_names,
|
||||
'types' => $types,
|
||||
'sql_types' => $this->dbi->types,
|
||||
]);
|
||||
}
|
||||
|
||||
public function findAction(): void
|
||||
{
|
||||
$useRegex = array_key_exists('useRegex', $_POST)
|
||||
&& $_POST['useRegex'] === 'on';
|
||||
|
||||
$preview = $this->getReplacePreview(
|
||||
$_POST['columnIndex'],
|
||||
$_POST['find'],
|
||||
$_POST['replaceWith'],
|
||||
$useRegex,
|
||||
$this->connectionCharSet
|
||||
);
|
||||
$this->response->addJSON('preview', $preview);
|
||||
}
|
||||
|
||||
public function replaceAction(): void
|
||||
{
|
||||
$this->replace(
|
||||
$_POST['columnIndex'],
|
||||
$_POST['findString'],
|
||||
$_POST['replaceWith'],
|
||||
$_POST['useRegex'],
|
||||
$this->connectionCharSet
|
||||
);
|
||||
$this->response->addHTML(
|
||||
Generator::getMessage(
|
||||
__('Your SQL query has been executed successfully.'),
|
||||
null,
|
||||
'success'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns HTML for previewing strings found and their replacements
|
||||
*
|
||||
* @param int $columnIndex index of the column
|
||||
* @param string $find string to find in the column
|
||||
* @param string $replaceWith string to replace with
|
||||
* @param bool $useRegex to use Regex replace or not
|
||||
* @param string $charSet character set of the connection
|
||||
*
|
||||
* @return string HTML for previewing strings found and their replacements
|
||||
*/
|
||||
public function getReplacePreview(
|
||||
$columnIndex,
|
||||
$find,
|
||||
$replaceWith,
|
||||
$useRegex,
|
||||
$charSet
|
||||
) {
|
||||
$column = $this->columnNames[$columnIndex];
|
||||
if ($useRegex) {
|
||||
$result = $this->getRegexReplaceRows(
|
||||
$columnIndex,
|
||||
$find,
|
||||
$replaceWith,
|
||||
$charSet
|
||||
);
|
||||
} else {
|
||||
$sql_query = 'SELECT '
|
||||
. Util::backquote($column) . ','
|
||||
. ' REPLACE('
|
||||
. Util::backquote($column) . ", '" . $find . "', '"
|
||||
. $replaceWith
|
||||
. "'),"
|
||||
. ' COUNT(*)'
|
||||
. ' FROM ' . Util::backquote($this->db)
|
||||
. '.' . Util::backquote($this->table)
|
||||
. ' WHERE ' . Util::backquote($column)
|
||||
. " LIKE '%" . $find . "%' COLLATE " . $charSet . '_bin'; // here we
|
||||
// change the collation of the 2nd operand to a case sensitive
|
||||
// binary collation to make sure that the comparison
|
||||
// is case sensitive
|
||||
$sql_query .= ' GROUP BY ' . Util::backquote($column)
|
||||
. ' ORDER BY ' . Util::backquote($column) . ' ASC';
|
||||
|
||||
$result = $this->dbi->fetchResult($sql_query, 0);
|
||||
}
|
||||
|
||||
return $this->template->render('table/find_replace/replace_preview', [
|
||||
'db' => $this->db,
|
||||
'table' => $this->table,
|
||||
'column_index' => $columnIndex,
|
||||
'find' => $find,
|
||||
'replace_with' => $replaceWith,
|
||||
'use_regex' => $useRegex,
|
||||
'result' => $result,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds and returns Regex pattern and their replacements
|
||||
*
|
||||
* @param int $columnIndex index of the column
|
||||
* @param string $find string to find in the column
|
||||
* @param string $replaceWith string to replace with
|
||||
* @param string $charSet character set of the connection
|
||||
*
|
||||
* @return array|bool Array containing original values, replaced values and count
|
||||
*/
|
||||
private function getRegexReplaceRows(
|
||||
$columnIndex,
|
||||
$find,
|
||||
$replaceWith,
|
||||
$charSet
|
||||
) {
|
||||
$column = $this->columnNames[$columnIndex];
|
||||
$sql_query = 'SELECT '
|
||||
. Util::backquote($column) . ','
|
||||
. ' 1,' // to add an extra column that will have replaced value
|
||||
. ' COUNT(*)'
|
||||
. ' FROM ' . Util::backquote($this->db)
|
||||
. '.' . Util::backquote($this->table)
|
||||
. ' WHERE ' . Util::backquote($column)
|
||||
. " RLIKE '" . $this->dbi->escapeString($find) . "' COLLATE "
|
||||
. $charSet . '_bin'; // here we
|
||||
// change the collation of the 2nd operand to a case sensitive
|
||||
// binary collation to make sure that the comparison is case sensitive
|
||||
$sql_query .= ' GROUP BY ' . Util::backquote($column)
|
||||
. ' ORDER BY ' . Util::backquote($column) . ' ASC';
|
||||
|
||||
$result = $this->dbi->fetchResult($sql_query, 0);
|
||||
|
||||
if (is_array($result)) {
|
||||
/* Iterate over possible delimiters to get one */
|
||||
$delimiters = [
|
||||
'/',
|
||||
'@',
|
||||
'#',
|
||||
'~',
|
||||
'!',
|
||||
'$',
|
||||
'%',
|
||||
'^',
|
||||
'&',
|
||||
'_',
|
||||
];
|
||||
$found = false;
|
||||
for ($i = 0, $l = count($delimiters); $i < $l; $i++) {
|
||||
if (strpos($find, $delimiters[$i]) === false) {
|
||||
$found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (! $found) {
|
||||
return false;
|
||||
}
|
||||
$find = $delimiters[$i] . $find . $delimiters[$i];
|
||||
foreach ($result as $index => $row) {
|
||||
$result[$index][1] = preg_replace(
|
||||
$find,
|
||||
$replaceWith,
|
||||
$row[0]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces a given string in a column with a give replacement
|
||||
*
|
||||
* @param int $columnIndex index of the column
|
||||
* @param string $find string to find in the column
|
||||
* @param string $replaceWith string to replace with
|
||||
* @param bool $useRegex to use Regex replace or not
|
||||
* @param string $charSet character set of the connection
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function replace(
|
||||
$columnIndex,
|
||||
$find,
|
||||
$replaceWith,
|
||||
$useRegex,
|
||||
$charSet
|
||||
) {
|
||||
$column = $this->columnNames[$columnIndex];
|
||||
if ($useRegex) {
|
||||
$toReplace = $this->getRegexReplaceRows(
|
||||
$columnIndex,
|
||||
$find,
|
||||
$replaceWith,
|
||||
$charSet
|
||||
);
|
||||
$sql_query = 'UPDATE ' . Util::backquote($this->table)
|
||||
. ' SET ' . Util::backquote($column) . ' = CASE';
|
||||
if (is_array($toReplace)) {
|
||||
foreach ($toReplace as $row) {
|
||||
$sql_query .= "\n WHEN " . Util::backquote($column)
|
||||
. " = '" . $this->dbi->escapeString($row[0])
|
||||
. "' THEN '" . $this->dbi->escapeString($row[1]) . "'";
|
||||
}
|
||||
}
|
||||
$sql_query .= ' END'
|
||||
. ' WHERE ' . Util::backquote($column)
|
||||
. " RLIKE '" . $this->dbi->escapeString($find) . "' COLLATE "
|
||||
. $charSet . '_bin'; // here we
|
||||
// change the collation of the 2nd operand to a case sensitive
|
||||
// binary collation to make sure that the comparison
|
||||
// is case sensitive
|
||||
} else {
|
||||
$sql_query = 'UPDATE ' . Util::backquote($this->table)
|
||||
. ' SET ' . Util::backquote($column) . ' ='
|
||||
. ' REPLACE('
|
||||
. Util::backquote($column) . ", '" . $find . "', '"
|
||||
. $replaceWith
|
||||
. "')"
|
||||
. ' WHERE ' . Util::backquote($column)
|
||||
. " LIKE '%" . $find . "%' COLLATE " . $charSet . '_bin'; // here we
|
||||
// change the collation of the 2nd operand to a case sensitive
|
||||
// binary collation to make sure that the comparison
|
||||
// is case sensitive
|
||||
}
|
||||
$this->dbi->query(
|
||||
$sql_query,
|
||||
DatabaseInterface::CONNECT_USER,
|
||||
DatabaseInterface::QUERY_STORE
|
||||
);
|
||||
$GLOBALS['sql_query'] = $sql_query;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Table;
|
||||
|
||||
use PhpMyAdmin\Core;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Html\Generator;
|
||||
use PhpMyAdmin\Mime;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Util;
|
||||
use function htmlspecialchars;
|
||||
use function ini_set;
|
||||
use function sprintf;
|
||||
use function strlen;
|
||||
|
||||
/**
|
||||
* Provides download to a given field defined in parameters.
|
||||
*/
|
||||
class GetFieldController extends AbstractController
|
||||
{
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param string $db Database name.
|
||||
* @param string $table Table name.
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, $db, $table, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template, $db, $table);
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $db, $table;
|
||||
|
||||
$this->response->disable();
|
||||
|
||||
/* Check parameters */
|
||||
Util::checkParameters([
|
||||
'db',
|
||||
'table',
|
||||
]);
|
||||
|
||||
/* Select database */
|
||||
if (! $this->dbi->selectDb($db)) {
|
||||
Generator::mysqlDie(
|
||||
sprintf(__('\'%s\' database does not exist.'), htmlspecialchars($db)),
|
||||
'',
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
/* Check if table exists */
|
||||
if (! $this->dbi->getColumns($db, $table)) {
|
||||
Generator::mysqlDie(__('Invalid table name'));
|
||||
}
|
||||
|
||||
if (! isset($_GET['where_clause'])
|
||||
|| ! isset($_GET['where_clause_sign'])
|
||||
|| ! Core::checkSqlQuerySignature($_GET['where_clause'], $_GET['where_clause_sign'])
|
||||
) {
|
||||
/* l10n: In case a SQL query did not pass a security check */
|
||||
Core::fatalError(__('There is an issue with your request.'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* Grab data */
|
||||
$sql = 'SELECT ' . Util::backquote($_GET['transform_key'])
|
||||
. ' FROM ' . Util::backquote($table)
|
||||
. ' WHERE ' . $_GET['where_clause'] . ';';
|
||||
$result = $this->dbi->fetchValue($sql);
|
||||
|
||||
/* Check return code */
|
||||
if ($result === false) {
|
||||
Generator::mysqlDie(
|
||||
__('MySQL returned an empty result set (i.e. zero rows).'),
|
||||
$sql
|
||||
);
|
||||
}
|
||||
|
||||
/* Avoid corrupting data */
|
||||
ini_set('url_rewriter.tags', '');
|
||||
|
||||
Core::downloadHeader(
|
||||
$table . '-' . $_GET['transform_key'] . '.bin',
|
||||
Mime::detect($result),
|
||||
strlen($result)
|
||||
);
|
||||
echo $result;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,202 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Table;
|
||||
|
||||
use PhpMyAdmin\Core;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Gis\GisVisualization;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
use function array_merge;
|
||||
|
||||
/**
|
||||
* Handles creation of the GIS visualizations.
|
||||
*/
|
||||
final class GisVisualizationController extends AbstractController
|
||||
{
|
||||
/** @var GisVisualization */
|
||||
private $visualization;
|
||||
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param string $db Database name.
|
||||
* @param string $table Table name.
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, $db, $table, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template, $db, $table);
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $cfg, $url_params, $PMA_Theme, $db, $err_url;
|
||||
|
||||
Util::checkParameters(['db']);
|
||||
|
||||
$err_url = Util::getScriptNameForOption($cfg['DefaultTabDatabase'], 'database');
|
||||
$err_url .= Url::getCommon(['db' => $db], '&');
|
||||
|
||||
if (! $this->hasDatabase()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// SQL query for retrieving GIS data
|
||||
$sqlQuery = '';
|
||||
if (isset($_GET['sql_query'], $_GET['sql_signature'])) {
|
||||
if (Core::checkSqlQuerySignature($_GET['sql_query'], $_GET['sql_signature'])) {
|
||||
$sqlQuery = $_GET['sql_query'];
|
||||
}
|
||||
} elseif (isset($_POST['sql_query'])) {
|
||||
$sqlQuery = $_POST['sql_query'];
|
||||
}
|
||||
|
||||
// Throw error if no sql query is set
|
||||
if ($sqlQuery == '') {
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addHTML(
|
||||
Message::error(__('No SQL query was set to fetch data.'))
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Execute the query and return the result
|
||||
$result = $this->dbi->tryQuery($sqlQuery);
|
||||
// Get the meta data of results
|
||||
$meta = $this->dbi->getFieldsMeta($result);
|
||||
|
||||
// Find the candidate fields for label column and spatial column
|
||||
$labelCandidates = [];
|
||||
$spatialCandidates = [];
|
||||
foreach ($meta as $column_meta) {
|
||||
if ($column_meta->type === 'geometry') {
|
||||
$spatialCandidates[] = $column_meta->name;
|
||||
} else {
|
||||
$labelCandidates[] = $column_meta->name;
|
||||
}
|
||||
}
|
||||
|
||||
// Get settings if any posted
|
||||
$visualizationSettings = [];
|
||||
if (Core::isValid($_POST['visualizationSettings'], 'array')) {
|
||||
$visualizationSettings = $_POST['visualizationSettings'];
|
||||
}
|
||||
|
||||
// Check mysql version
|
||||
$visualizationSettings['mysqlVersion'] = $this->dbi->getVersion();
|
||||
$visualizationSettings['isMariaDB'] = $this->dbi->isMariaDB();
|
||||
|
||||
if (! isset($visualizationSettings['labelColumn'])
|
||||
&& isset($labelCandidates[0])
|
||||
) {
|
||||
$visualizationSettings['labelColumn'] = '';
|
||||
}
|
||||
|
||||
// If spatial column is not set, use first geometric column as spatial column
|
||||
if (! isset($visualizationSettings['spatialColumn'])) {
|
||||
$visualizationSettings['spatialColumn'] = $spatialCandidates[0];
|
||||
}
|
||||
|
||||
// Convert geometric columns from bytes to text.
|
||||
$pos = $_GET['pos'] ?? $_SESSION['tmpval']['pos'];
|
||||
if (isset($_GET['session_max_rows'])) {
|
||||
$rows = $_GET['session_max_rows'];
|
||||
} else {
|
||||
if ($_SESSION['tmpval']['max_rows'] !== 'all') {
|
||||
$rows = $_SESSION['tmpval']['max_rows'];
|
||||
} else {
|
||||
$rows = $GLOBALS['cfg']['MaxRows'];
|
||||
}
|
||||
}
|
||||
$this->visualization = GisVisualization::get(
|
||||
$sqlQuery,
|
||||
$visualizationSettings,
|
||||
$rows,
|
||||
$pos
|
||||
);
|
||||
|
||||
if (isset($_GET['saveToFile'])) {
|
||||
$this->saveToFile($visualizationSettings['spatialColumn'], $_GET['fileFormat']);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->addScriptFiles([
|
||||
'vendor/openlayers/OpenLayers.js',
|
||||
'vendor/jquery/jquery.svg.js',
|
||||
'table/gis_visualization.js',
|
||||
]);
|
||||
|
||||
// If all the rows contain SRID, use OpenStreetMaps on the initial loading.
|
||||
if (! isset($_POST['displayVisualization'])) {
|
||||
if ($this->visualization->hasSrid()) {
|
||||
$visualizationSettings['choice'] = 'useBaseLayer';
|
||||
} else {
|
||||
unset($visualizationSettings['choice']);
|
||||
}
|
||||
}
|
||||
|
||||
$this->visualization->setUserSpecifiedSettings($visualizationSettings);
|
||||
if ($visualizationSettings != null) {
|
||||
foreach ($this->visualization->getSettings() as $setting => $val) {
|
||||
if (isset($visualizationSettings[$setting])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$visualizationSettings[$setting] = $val;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the page
|
||||
*/
|
||||
$url_params['goto'] = Util::getScriptNameForOption(
|
||||
$cfg['DefaultTabDatabase'],
|
||||
'database'
|
||||
);
|
||||
$url_params['back'] = Url::getFromRoute('/sql');
|
||||
$url_params['sql_query'] = $sqlQuery;
|
||||
$url_params['sql_signature'] = Core::signSqlQuery($sqlQuery);
|
||||
$downloadUrl = Url::getFromRoute('/table/gis-visualization', array_merge(
|
||||
$url_params,
|
||||
[
|
||||
'saveToFile' => true,
|
||||
'session_max_rows' => $rows,
|
||||
'pos' => $pos,
|
||||
]
|
||||
));
|
||||
$html = $this->template->render('table/gis_visualization/gis_visualization', [
|
||||
'url_params' => $url_params,
|
||||
'download_url' => $downloadUrl,
|
||||
'label_candidates' => $labelCandidates,
|
||||
'spatial_candidates' => $spatialCandidates,
|
||||
'visualization_settings' => $visualizationSettings,
|
||||
'sql_query' => $sqlQuery,
|
||||
'visualization' => $this->visualization->toImage('svg'),
|
||||
'draw_ol' => $this->visualization->asOl(),
|
||||
'theme_image_path' => $PMA_Theme->getImgPath(),
|
||||
]);
|
||||
|
||||
$this->response->addHTML($html);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filename File name
|
||||
* @param string $format Save format
|
||||
*/
|
||||
private function saveToFile(string $filename, string $format): void
|
||||
{
|
||||
$this->response->disable();
|
||||
$this->visualization->toFile($filename, $format);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Table;
|
||||
|
||||
use PhpMyAdmin\Charsets;
|
||||
use PhpMyAdmin\Charsets\Charset;
|
||||
use PhpMyAdmin\Config\PageSettings;
|
||||
use PhpMyAdmin\Core;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\DbTableExists;
|
||||
use PhpMyAdmin\Encoding;
|
||||
use PhpMyAdmin\Import;
|
||||
use PhpMyAdmin\Import\Ajax;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\Plugins;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
use function intval;
|
||||
|
||||
final class ImportController extends AbstractController
|
||||
{
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param string $db Database name.
|
||||
* @param string $table Table name.
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, $db, $table, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template, $db, $table);
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $db, $max_upload_size, $table, $url_params, $SESSION_KEY, $cfg, $PMA_Theme, $err_url;
|
||||
|
||||
$pageSettings = new PageSettings('Import');
|
||||
$pageSettingsErrorHtml = $pageSettings->getErrorHTML();
|
||||
$pageSettingsHtml = $pageSettings->getHTML();
|
||||
|
||||
$this->addScriptFiles(['import.js']);
|
||||
|
||||
Util::checkParameters(['db', 'table']);
|
||||
|
||||
$url_params = ['db' => $db, 'table' => $table];
|
||||
$err_url = Util::getScriptNameForOption($cfg['DefaultTabTable'], 'table');
|
||||
$err_url .= Url::getCommon($url_params, '&');
|
||||
|
||||
DbTableExists::check();
|
||||
|
||||
$url_params['goto'] = Url::getFromRoute('/table/import');
|
||||
$url_params['back'] = Url::getFromRoute('/table/import');
|
||||
|
||||
[$SESSION_KEY, $uploadId] = Ajax::uploadProgressSetup();
|
||||
|
||||
$importList = Plugins::getImport('table');
|
||||
|
||||
if (empty($importList)) {
|
||||
$this->response->addHTML(Message::error(__(
|
||||
'Could not load import plugins, please check your installation!'
|
||||
))->getDisplay());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$offset = null;
|
||||
if (Core::isValid($_REQUEST['offset'], 'numeric')) {
|
||||
$offset = intval($_REQUEST['offset']);
|
||||
}
|
||||
|
||||
$timeoutPassed = $_REQUEST['timeout_passed'] ?? null;
|
||||
$localImportFile = $_REQUEST['local_import_file'] ?? null;
|
||||
$compressions = Import::getCompressions();
|
||||
|
||||
$allCharsets = Charsets::getCharsets($this->dbi, $cfg['Server']['DisableIS']);
|
||||
$charsets = [];
|
||||
/** @var Charset $charset */
|
||||
foreach ($allCharsets as $charset) {
|
||||
$charsets[] = [
|
||||
'name' => $charset->getName(),
|
||||
'description' => $charset->getDescription(),
|
||||
];
|
||||
}
|
||||
|
||||
$idKey = $_SESSION[$SESSION_KEY]['handler']::getIdKey();
|
||||
$hiddenInputs = [
|
||||
$idKey => $uploadId,
|
||||
'import_type' => 'table',
|
||||
'db' => $db,
|
||||
'table' => $table,
|
||||
];
|
||||
|
||||
$this->render('table/import/index', [
|
||||
'page_settings_error_html' => $pageSettingsErrorHtml,
|
||||
'page_settings_html' => $pageSettingsHtml,
|
||||
'upload_id' => $uploadId,
|
||||
'handler' => $_SESSION[$SESSION_KEY]['handler'],
|
||||
'theme_image_path' => $PMA_Theme->getImgPath(),
|
||||
'hidden_inputs' => $hiddenInputs,
|
||||
'db' => $db,
|
||||
'table' => $table,
|
||||
'max_upload_size' => $max_upload_size,
|
||||
'import_list' => $importList,
|
||||
'local_import_file' => $localImportFile,
|
||||
'is_upload' => $GLOBALS['is_upload'],
|
||||
'upload_dir' => $cfg['UploadDir'] ?? null,
|
||||
'timeout_passed_global' => $GLOBALS['timeout_passed'] ?? null,
|
||||
'compressions' => $compressions,
|
||||
'is_encoding_supported' => Encoding::isSupported(),
|
||||
'encodings' => Encoding::listEncodings(),
|
||||
'import_charset' => $cfg['Import']['charset'] ?? null,
|
||||
'timeout_passed' => $timeoutPassed,
|
||||
'offset' => $offset,
|
||||
'can_convert_kanji' => Encoding::canConvertKanji(),
|
||||
'charsets' => $charsets,
|
||||
'is_foreign_key_check' => Util::isForeignKeyCheck(),
|
||||
'user_upload_dir' => Util::userDir($cfg['UploadDir'] ?? ''),
|
||||
'local_files' => Import::getLocalFiles($importList),
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,278 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Table;
|
||||
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\DbTableExists;
|
||||
use PhpMyAdmin\Html\Generator;
|
||||
use PhpMyAdmin\Index;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\Query\Compatibility;
|
||||
use PhpMyAdmin\Query\Generator as QueryGenerator;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
use function count;
|
||||
use function is_array;
|
||||
use function json_decode;
|
||||
|
||||
/**
|
||||
* Displays index edit/creation form and handles it.
|
||||
*/
|
||||
class IndexesController extends AbstractController
|
||||
{
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param string $db Database name.
|
||||
* @param string $table Table name.
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, $db, $table, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template, $db, $table);
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $db, $table, $url_params, $cfg, $err_url;
|
||||
|
||||
if (! isset($_POST['create_edit_table'])) {
|
||||
Util::checkParameters(['db', 'table']);
|
||||
|
||||
$url_params = ['db' => $db, 'table' => $table];
|
||||
$err_url = Util::getScriptNameForOption($cfg['DefaultTabTable'], 'table');
|
||||
$err_url .= Url::getCommon($url_params, '&');
|
||||
|
||||
DbTableExists::check();
|
||||
}
|
||||
if (isset($_POST['index'])) {
|
||||
if (is_array($_POST['index'])) {
|
||||
// coming already from form
|
||||
$index = new Index($_POST['index']);
|
||||
} else {
|
||||
$index = $this->dbi->getTable($this->db, $this->table)->getIndex($_POST['index']);
|
||||
}
|
||||
} else {
|
||||
$index = new Index();
|
||||
}
|
||||
|
||||
if (isset($_POST['do_save_data'])) {
|
||||
$this->doSaveData($index, false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->displayForm($index);
|
||||
}
|
||||
|
||||
public function indexRename(): void
|
||||
{
|
||||
global $db, $table, $url_params, $cfg, $err_url;
|
||||
|
||||
if (! isset($_POST['create_edit_table'])) {
|
||||
Util::checkParameters(['db', 'table']);
|
||||
|
||||
$url_params = ['db' => $db, 'table' => $table];
|
||||
$err_url = Util::getScriptNameForOption($cfg['DefaultTabTable'], 'table');
|
||||
$err_url .= Url::getCommon($url_params, '&');
|
||||
|
||||
DbTableExists::check();
|
||||
}
|
||||
if (isset($_POST['index'])) {
|
||||
if (is_array($_POST['index'])) {
|
||||
// coming already from form
|
||||
$index = new Index($_POST['index']);
|
||||
} else {
|
||||
$index = $this->dbi->getTable($this->db, $this->table)->getIndex($_POST['index']);
|
||||
}
|
||||
} else {
|
||||
$index = new Index();
|
||||
}
|
||||
|
||||
if (isset($_POST['do_save_data'])) {
|
||||
$this->doSaveData($index, true);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->displayRenameForm($index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the rename form to rename an index
|
||||
*
|
||||
* @param Index $index An Index instance.
|
||||
*/
|
||||
public function displayRenameForm(Index $index): void
|
||||
{
|
||||
$this->dbi->selectDb($GLOBALS['db']);
|
||||
|
||||
$formParams = [
|
||||
'db' => $this->db,
|
||||
'table' => $this->table,
|
||||
];
|
||||
|
||||
if (isset($_POST['old_index'])) {
|
||||
$formParams['old_index'] = $_POST['old_index'];
|
||||
} elseif (isset($_POST['index'])) {
|
||||
$formParams['old_index'] = $_POST['index'];
|
||||
}
|
||||
|
||||
$this->addScriptFiles(['indexes.js']);
|
||||
|
||||
$this->render('table/index_rename_form', [
|
||||
'index' => $index,
|
||||
'form_params' => $formParams,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the form to edit/create an index
|
||||
*
|
||||
* @param Index $index An Index instance.
|
||||
*/
|
||||
public function displayForm(Index $index): void
|
||||
{
|
||||
$this->dbi->selectDb($GLOBALS['db']);
|
||||
$add_fields = 0;
|
||||
if (isset($_POST['index']) && is_array($_POST['index'])) {
|
||||
// coming already from form
|
||||
if (isset($_POST['index']['columns']['names'])) {
|
||||
$add_fields = count($_POST['index']['columns']['names'])
|
||||
- $index->getColumnCount();
|
||||
}
|
||||
if (isset($_POST['add_fields'])) {
|
||||
$add_fields += $_POST['added_fields'];
|
||||
}
|
||||
} elseif (isset($_POST['create_index'])) {
|
||||
$add_fields = $_POST['added_fields'];
|
||||
}
|
||||
|
||||
// Get fields and stores their name/type
|
||||
if (isset($_POST['create_edit_table'])) {
|
||||
$fields = json_decode($_POST['columns'], true);
|
||||
$index_params = [
|
||||
'Non_unique' => $_POST['index']['Index_choice'] === 'UNIQUE'
|
||||
? '0' : '1',
|
||||
];
|
||||
$index->set($index_params);
|
||||
$add_fields = count($fields);
|
||||
} else {
|
||||
$fields = $this->dbi->getTable($this->db, $this->table)
|
||||
->getNameAndTypeOfTheColumns();
|
||||
}
|
||||
|
||||
$form_params = [
|
||||
'db' => $this->db,
|
||||
'table' => $this->table,
|
||||
];
|
||||
|
||||
if (isset($_POST['create_index'])) {
|
||||
$form_params['create_index'] = 1;
|
||||
} elseif (isset($_POST['old_index'])) {
|
||||
$form_params['old_index'] = $_POST['old_index'];
|
||||
} elseif (isset($_POST['index'])) {
|
||||
$form_params['old_index'] = $_POST['index'];
|
||||
}
|
||||
|
||||
$this->addScriptFiles(['indexes.js']);
|
||||
|
||||
$this->render('table/index_form', [
|
||||
'fields' => $fields,
|
||||
'index' => $index,
|
||||
'form_params' => $form_params,
|
||||
'add_fields' => $add_fields,
|
||||
'create_edit_table' => isset($_POST['create_edit_table']),
|
||||
'default_sliders_state' => $GLOBALS['cfg']['InitialSlidersState'],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the data from the edit/create index form,
|
||||
* run the query to build the new index
|
||||
* and moves back to /table/sql
|
||||
*
|
||||
* @param Index $index An Index instance.
|
||||
* @param bool $renameMode Rename the Index mode
|
||||
*/
|
||||
public function doSaveData(Index $index, bool $renameMode): void
|
||||
{
|
||||
global $containerBuilder;
|
||||
|
||||
$error = false;
|
||||
$sql_query = '';
|
||||
if ($renameMode && Compatibility::isCompatibleRenameIndex($this->dbi->getVersion())) {
|
||||
$oldIndexName = $_POST['old_index'];
|
||||
|
||||
if ($oldIndexName === 'PRIMARY') {
|
||||
if ($index->getName() === '') {
|
||||
$index->setName('PRIMARY');
|
||||
} elseif ($index->getName() !== 'PRIMARY') {
|
||||
$error = Message::error(
|
||||
__('The name of the primary key must be "PRIMARY"!')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$sql_query = QueryGenerator::getSqlQueryForIndexRename(
|
||||
$this->db,
|
||||
$this->table,
|
||||
$oldIndexName,
|
||||
$index->getName()
|
||||
);
|
||||
} else {
|
||||
$sql_query = $this->dbi->getTable($this->db, $this->table)
|
||||
->getSqlQueryForIndexCreateOrEdit($index, $error);
|
||||
}
|
||||
|
||||
// If there is a request for SQL previewing.
|
||||
if (isset($_POST['preview_sql'])) {
|
||||
$this->response->addJSON(
|
||||
'sql_data',
|
||||
$this->template->render('preview_sql', ['query_data' => $sql_query])
|
||||
);
|
||||
} elseif (! $error) {
|
||||
$this->dbi->query($sql_query);
|
||||
$response = Response::getInstance();
|
||||
if ($response->isAjax()) {
|
||||
$message = Message::success(
|
||||
__('Table %1$s has been altered successfully.')
|
||||
);
|
||||
$message->addParam($this->table);
|
||||
$this->response->addJSON(
|
||||
'message',
|
||||
Generator::getMessage($message, $sql_query, 'success')
|
||||
);
|
||||
|
||||
$indexes = Index::getFromTable($this->table, $this->db);
|
||||
$indexesDuplicates = Index::findDuplicates($this->table, $this->db);
|
||||
|
||||
$this->response->addJSON(
|
||||
'index_table',
|
||||
$this->template->render('indexes', [
|
||||
'url_params' => [
|
||||
'db' => $this->db,
|
||||
'table' => $this->table,
|
||||
],
|
||||
'indexes' => $indexes,
|
||||
'indexes_duplicates' => $indexesDuplicates,
|
||||
])
|
||||
);
|
||||
} else {
|
||||
/** @var StructureController $controller */
|
||||
$controller = $containerBuilder->get(StructureController::class);
|
||||
$controller->index();
|
||||
}
|
||||
} else {
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON('message', $error);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,213 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Table;
|
||||
|
||||
use PhpMyAdmin\Html\Generator;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Table\Maintenance;
|
||||
use PhpMyAdmin\Template;
|
||||
use function count;
|
||||
use function is_array;
|
||||
|
||||
final class MaintenanceController extends AbstractController
|
||||
{
|
||||
/** @var Maintenance */
|
||||
private $model;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param string $db
|
||||
* @param string $table
|
||||
*/
|
||||
public function __construct(
|
||||
$response,
|
||||
Template $template,
|
||||
$db,
|
||||
$table,
|
||||
Maintenance $model
|
||||
) {
|
||||
parent::__construct($response, $template, $db, $table);
|
||||
$this->model = $model;
|
||||
}
|
||||
|
||||
public function analyze(): void
|
||||
{
|
||||
global $cfg;
|
||||
|
||||
/** @var string[] $selected */
|
||||
$selected = $_POST['selected_tbl'] ?? [];
|
||||
|
||||
if (empty($selected) || ! is_array($selected)) {
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON('message', __('No table selected.'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($cfg['DisableMultiTableMaintenance'] && count($selected) > 1) {
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON('message', __('Maintenance operations on multiple tables are disabled.'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
[$rows, $query] = $this->model->getAnalyzeTableRows($this->db, $selected);
|
||||
|
||||
$message = Generator::getMessage(
|
||||
__('Your SQL query has been executed successfully.'),
|
||||
$query,
|
||||
'success'
|
||||
);
|
||||
|
||||
$this->render('table/maintenance/analyze', [
|
||||
'message' => $message,
|
||||
'rows' => $rows,
|
||||
]);
|
||||
}
|
||||
|
||||
public function check(): void
|
||||
{
|
||||
global $cfg;
|
||||
|
||||
/** @var string[] $selected */
|
||||
$selected = $_POST['selected_tbl'] ?? [];
|
||||
|
||||
if (empty($selected) || ! is_array($selected)) {
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON('message', __('No table selected.'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($cfg['DisableMultiTableMaintenance'] && count($selected) > 1) {
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON('message', __('Maintenance operations on multiple tables are disabled.'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
[$rows, $query] = $this->model->getCheckTableRows($this->db, $selected);
|
||||
|
||||
$message = Generator::getMessage(
|
||||
__('Your SQL query has been executed successfully.'),
|
||||
$query,
|
||||
'success'
|
||||
);
|
||||
|
||||
$indexesProblems = $this->model->getIndexesProblems($this->db, $selected);
|
||||
|
||||
$this->render('table/maintenance/check', [
|
||||
'message' => $message,
|
||||
'rows' => $rows,
|
||||
'indexes_problems' => $indexesProblems,
|
||||
]);
|
||||
}
|
||||
|
||||
public function checksum(): void
|
||||
{
|
||||
global $cfg;
|
||||
|
||||
/** @var string[] $selected */
|
||||
$selected = $_POST['selected_tbl'] ?? [];
|
||||
|
||||
if (empty($selected) || ! is_array($selected)) {
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON('message', __('No table selected.'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($cfg['DisableMultiTableMaintenance'] && count($selected) > 1) {
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON('message', __('Maintenance operations on multiple tables are disabled.'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
[$rows, $query, $warnings] = $this->model->getChecksumTableRows($this->db, $selected);
|
||||
|
||||
$message = Generator::getMessage(
|
||||
__('Your SQL query has been executed successfully.'),
|
||||
$query,
|
||||
'success'
|
||||
);
|
||||
|
||||
$this->render('table/maintenance/checksum', [
|
||||
'message' => $message,
|
||||
'rows' => $rows,
|
||||
'warnings' => $warnings,
|
||||
]);
|
||||
}
|
||||
|
||||
public function optimize(): void
|
||||
{
|
||||
global $cfg;
|
||||
|
||||
/** @var string[] $selected */
|
||||
$selected = $_POST['selected_tbl'] ?? [];
|
||||
|
||||
if (empty($selected) || ! is_array($selected)) {
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON('message', __('No table selected.'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($cfg['DisableMultiTableMaintenance'] && count($selected) > 1) {
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON('message', __('Maintenance operations on multiple tables are disabled.'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
[$rows, $query] = $this->model->getOptimizeTableRows($this->db, $selected);
|
||||
|
||||
$message = Generator::getMessage(
|
||||
__('Your SQL query has been executed successfully.'),
|
||||
$query,
|
||||
'success'
|
||||
);
|
||||
|
||||
$this->render('table/maintenance/optimize', [
|
||||
'message' => $message,
|
||||
'rows' => $rows,
|
||||
]);
|
||||
}
|
||||
|
||||
public function repair(): void
|
||||
{
|
||||
global $cfg;
|
||||
|
||||
/** @var string[] $selected */
|
||||
$selected = $_POST['selected_tbl'] ?? [];
|
||||
|
||||
if (empty($selected) || ! is_array($selected)) {
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON('message', __('No table selected.'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($cfg['DisableMultiTableMaintenance'] && count($selected) > 1) {
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON('message', __('Maintenance operations on multiple tables are disabled.'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
[$rows, $query] = $this->model->getRepairTableRows($this->db, $selected);
|
||||
|
||||
$message = Generator::getMessage(
|
||||
__('Your SQL query has been executed successfully.'),
|
||||
$query,
|
||||
'success'
|
||||
);
|
||||
|
||||
$this->render('table/maintenance/repair', [
|
||||
'message' => $message,
|
||||
'rows' => $rows,
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,496 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Table;
|
||||
|
||||
use PhpMyAdmin\Charsets;
|
||||
use PhpMyAdmin\CheckUserPrivileges;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\DbTableExists;
|
||||
use PhpMyAdmin\Html\Generator;
|
||||
use PhpMyAdmin\Index;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\Operations;
|
||||
use PhpMyAdmin\Partition;
|
||||
use PhpMyAdmin\Query\Utilities;
|
||||
use PhpMyAdmin\Relation;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\StorageEngine;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
use function count;
|
||||
use function implode;
|
||||
use function mb_strstr;
|
||||
use function mb_strtolower;
|
||||
use function mb_strtoupper;
|
||||
use function preg_replace;
|
||||
use function strlen;
|
||||
|
||||
class OperationsController extends AbstractController
|
||||
{
|
||||
/** @var Operations */
|
||||
private $operations;
|
||||
|
||||
/** @var CheckUserPrivileges */
|
||||
private $checkUserPrivileges;
|
||||
|
||||
/** @var Relation */
|
||||
private $relation;
|
||||
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param string $db Database name.
|
||||
* @param string $table Table name.
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct(
|
||||
$response,
|
||||
Template $template,
|
||||
$db,
|
||||
$table,
|
||||
Operations $operations,
|
||||
CheckUserPrivileges $checkUserPrivileges,
|
||||
Relation $relation,
|
||||
$dbi
|
||||
) {
|
||||
parent::__construct($response, $template, $db, $table);
|
||||
$this->operations = $operations;
|
||||
$this->checkUserPrivileges = $checkUserPrivileges;
|
||||
$this->relation = $relation;
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $url_params, $reread_info, $tbl_is_view, $tbl_storage_engine;
|
||||
global $show_comment, $tbl_collation, $table_info_num_rows, $row_format, $auto_increment, $create_options;
|
||||
global $table_alters, $warning_messages, $lowerCaseNames, $db, $table, $reload, $result;
|
||||
global $new_tbl_storage_engine, $sql_query, $message_to_show, $columns, $hideOrderTable, $indexes;
|
||||
global $notNull, $comment, $err_url, $cfg;
|
||||
|
||||
$this->checkUserPrivileges->getPrivileges();
|
||||
|
||||
// lower_case_table_names=1 `DB` becomes `db`
|
||||
$lowerCaseNames = $this->dbi->getLowerCaseNames() === '1';
|
||||
|
||||
if ($lowerCaseNames) {
|
||||
$table = mb_strtolower($table);
|
||||
}
|
||||
|
||||
$pma_table = $this->dbi->getTable($db, $table);
|
||||
|
||||
$this->addScriptFiles(['table/operations.js']);
|
||||
|
||||
Util::checkParameters(['db', 'table']);
|
||||
|
||||
$isSystemSchema = Utilities::isSystemSchema($db);
|
||||
$url_params = ['db' => $db, 'table' => $table];
|
||||
$err_url = Util::getScriptNameForOption($cfg['DefaultTabTable'], 'table');
|
||||
$err_url .= Url::getCommon($url_params, '&');
|
||||
|
||||
DbTableExists::check();
|
||||
|
||||
$url_params['goto'] = $url_params['back'] = Url::getFromRoute('/table/operations');
|
||||
|
||||
/**
|
||||
* Gets relation settings
|
||||
*/
|
||||
$cfgRelation = $this->relation->getRelationsParam();
|
||||
|
||||
// reselect current db (needed in some cases probably due to
|
||||
// the calling of PhpMyAdmin\Relation)
|
||||
$this->dbi->selectDb($db);
|
||||
|
||||
$reread_info = $pma_table->getStatusInfo(null, false);
|
||||
$GLOBALS['showtable'] = $pma_table->getStatusInfo(null, (isset($reread_info) && $reread_info));
|
||||
if ($pma_table->isView()) {
|
||||
$tbl_is_view = true;
|
||||
$tbl_storage_engine = __('View');
|
||||
$show_comment = null;
|
||||
} else {
|
||||
$tbl_is_view = false;
|
||||
$tbl_storage_engine = $pma_table->getStorageEngine();
|
||||
$show_comment = $pma_table->getComment();
|
||||
}
|
||||
$tbl_collation = $pma_table->getCollation();
|
||||
$table_info_num_rows = $pma_table->getNumRows();
|
||||
$row_format = $pma_table->getRowFormat();
|
||||
$auto_increment = $pma_table->getAutoIncrement();
|
||||
$create_options = $pma_table->getCreateOptions();
|
||||
|
||||
// set initial value of these variables, based on the current table engine
|
||||
if ($pma_table->isEngine('ARIA')) {
|
||||
// the value for transactional can be implicit
|
||||
// (no create option found, in this case it means 1)
|
||||
// or explicit (option found with a value of 0 or 1)
|
||||
// ($create_options['transactional'] may have been set by Table class,
|
||||
// from the $create_options)
|
||||
$create_options['transactional'] = ($create_options['transactional'] ?? '') == '0'
|
||||
? '0'
|
||||
: '1';
|
||||
$create_options['page_checksum'] = $create_options['page_checksum'] ?? '';
|
||||
}
|
||||
|
||||
$pma_table = $this->dbi->getTable(
|
||||
$db,
|
||||
$table
|
||||
);
|
||||
$reread_info = false;
|
||||
$table_alters = [];
|
||||
|
||||
/**
|
||||
* If the table has to be moved to some other database
|
||||
*/
|
||||
if (isset($_POST['submit_move']) || isset($_POST['submit_copy'])) {
|
||||
$message = $this->operations->moveOrCopyTable($db, $table);
|
||||
|
||||
if (! $this->response->isAjax()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->response->addJSON('message', $message);
|
||||
|
||||
if ($message->isSuccess()) {
|
||||
$this->response->addJSON('db', $db);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->response->setRequestStatus(false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates table comment, type and options if required
|
||||
*/
|
||||
if (isset($_POST['submitoptions'])) {
|
||||
$_message = '';
|
||||
$warning_messages = [];
|
||||
|
||||
if (isset($_POST['new_name'])) {
|
||||
// lower_case_table_names=1 `DB` becomes `db`
|
||||
if ($lowerCaseNames) {
|
||||
$_POST['new_name'] = mb_strtolower(
|
||||
$_POST['new_name']
|
||||
);
|
||||
}
|
||||
// Get original names before rename operation
|
||||
$oldTable = $pma_table->getName();
|
||||
$oldDb = $pma_table->getDbName();
|
||||
|
||||
if ($pma_table->rename($_POST['new_name'])) {
|
||||
if (isset($_POST['adjust_privileges'])
|
||||
&& ! empty($_POST['adjust_privileges'])
|
||||
) {
|
||||
$this->operations->adjustPrivilegesRenameOrMoveTable(
|
||||
$oldDb,
|
||||
$oldTable,
|
||||
$_POST['db'],
|
||||
$_POST['new_name']
|
||||
);
|
||||
}
|
||||
|
||||
// Reselect the original DB
|
||||
$db = $oldDb;
|
||||
$this->dbi->selectDb($oldDb);
|
||||
$_message .= $pma_table->getLastMessage();
|
||||
$result = true;
|
||||
$table = $pma_table->getName();
|
||||
$reread_info = true;
|
||||
$reload = true;
|
||||
} else {
|
||||
$_message .= $pma_table->getLastError();
|
||||
$result = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (! empty($_POST['new_tbl_storage_engine'])
|
||||
&& mb_strtoupper($_POST['new_tbl_storage_engine']) !== $tbl_storage_engine
|
||||
) {
|
||||
$new_tbl_storage_engine = mb_strtoupper($_POST['new_tbl_storage_engine']);
|
||||
|
||||
if ($pma_table->isEngine('ARIA')) {
|
||||
$create_options['transactional'] = ($create_options['transactional'] ?? '') == '0'
|
||||
? '0'
|
||||
: '1';
|
||||
$create_options['page_checksum'] = $create_options['page_checksum'] ?? '';
|
||||
}
|
||||
} else {
|
||||
$new_tbl_storage_engine = '';
|
||||
}
|
||||
|
||||
$row_format = $create_options['row_format'] ?? $pma_table->getRowFormat();
|
||||
|
||||
$table_alters = $this->operations->getTableAltersArray(
|
||||
$pma_table,
|
||||
$create_options['pack_keys'],
|
||||
(empty($create_options['checksum']) ? '0' : '1'),
|
||||
($create_options['page_checksum'] ?? ''),
|
||||
(empty($create_options['delay_key_write']) ? '0' : '1'),
|
||||
$row_format,
|
||||
$new_tbl_storage_engine,
|
||||
(isset($create_options['transactional']) && $create_options['transactional'] == '0' ? '0' : '1'),
|
||||
$tbl_collation
|
||||
);
|
||||
|
||||
if (count($table_alters) > 0) {
|
||||
$sql_query = 'ALTER TABLE '
|
||||
. Util::backquote($table);
|
||||
$sql_query .= "\r\n" . implode("\r\n", $table_alters);
|
||||
$sql_query .= ';';
|
||||
$result = (bool) $this->dbi->query($sql_query);
|
||||
$reread_info = true;
|
||||
unset($table_alters);
|
||||
$warning_messages = $this->operations->getWarningMessagesArray();
|
||||
}
|
||||
|
||||
if (isset($_POST['tbl_collation'], $_POST['change_all_collations'])
|
||||
&& ! empty($_POST['tbl_collation'])
|
||||
&& ! empty($_POST['change_all_collations'])
|
||||
) {
|
||||
$this->operations->changeAllColumnsCollation(
|
||||
$db,
|
||||
$table,
|
||||
$_POST['tbl_collation']
|
||||
);
|
||||
}
|
||||
|
||||
if (isset($_POST['tbl_collation']) && empty($_POST['tbl_collation'])) {
|
||||
if ($this->response->isAjax()) {
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON(
|
||||
'message',
|
||||
Message::error(__('No collation provided.'))
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Reordering the table has been requested by the user
|
||||
*/
|
||||
if (isset($_POST['submitorderby']) && ! empty($_POST['order_field'])) {
|
||||
[$sql_query, $result] = $this->operations->getQueryAndResultForReorderingTable();
|
||||
}
|
||||
|
||||
/**
|
||||
* A partition operation has been requested by the user
|
||||
*/
|
||||
if (isset($_POST['submit_partition'])
|
||||
&& ! empty($_POST['partition_operation'])
|
||||
) {
|
||||
[$sql_query, $result] = $this->operations->getQueryAndResultForPartition();
|
||||
}
|
||||
|
||||
if ($reread_info) {
|
||||
// to avoid showing the old value (for example the AUTO_INCREMENT) after
|
||||
// a change, clear the cache
|
||||
$this->dbi->getCache()->clearTableCache();
|
||||
$this->dbi->selectDb($db);
|
||||
$GLOBALS['showtable'] = $pma_table->getStatusInfo(null, true);
|
||||
if ($pma_table->isView()) {
|
||||
$tbl_is_view = true;
|
||||
$tbl_storage_engine = __('View');
|
||||
$show_comment = null;
|
||||
} else {
|
||||
$tbl_is_view = false;
|
||||
$tbl_storage_engine = $pma_table->getStorageEngine();
|
||||
$show_comment = $pma_table->getComment();
|
||||
}
|
||||
$tbl_collation = $pma_table->getCollation();
|
||||
$table_info_num_rows = $pma_table->getNumRows();
|
||||
$row_format = $pma_table->getRowFormat();
|
||||
$auto_increment = $pma_table->getAutoIncrement();
|
||||
$create_options = $pma_table->getCreateOptions();
|
||||
}
|
||||
unset($reread_info);
|
||||
|
||||
if (isset($result) && empty($message_to_show)) {
|
||||
if (empty($_message)) {
|
||||
if (empty($sql_query)) {
|
||||
$_message = Message::success(__('No change'));
|
||||
} else {
|
||||
$_message = $result
|
||||
? Message::success()
|
||||
: Message::error();
|
||||
}
|
||||
|
||||
if ($this->response->isAjax()) {
|
||||
$this->response->setRequestStatus($_message->isSuccess());
|
||||
$this->response->addJSON('message', $_message);
|
||||
if (! empty($sql_query)) {
|
||||
$this->response->addJSON(
|
||||
'sql_query',
|
||||
Generator::getMessage('', $sql_query)
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
$_message = $result
|
||||
? Message::success($_message)
|
||||
: Message::error($_message);
|
||||
}
|
||||
|
||||
if (! empty($warning_messages)) {
|
||||
$_message = new Message();
|
||||
$_message->addMessagesString($warning_messages);
|
||||
$_message->isError(true);
|
||||
if ($this->response->isAjax()) {
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON('message', $_message);
|
||||
if (! empty($sql_query)) {
|
||||
$this->response->addJSON(
|
||||
'sql_query',
|
||||
Generator::getMessage('', $sql_query)
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
unset($warning_messages);
|
||||
}
|
||||
|
||||
if (empty($sql_query)) {
|
||||
$this->response->addHTML(
|
||||
$_message->getDisplay()
|
||||
);
|
||||
} else {
|
||||
$this->response->addHTML(
|
||||
Generator::getMessage($_message, $sql_query)
|
||||
);
|
||||
}
|
||||
unset($_message);
|
||||
}
|
||||
|
||||
$url_params['goto'] = $url_params['back'] = Url::getFromRoute('/table/operations');
|
||||
|
||||
$columns = $this->dbi->getColumns($db, $table);
|
||||
|
||||
$hideOrderTable = false;
|
||||
// `ALTER TABLE ORDER BY` does not make sense for InnoDB tables that contain
|
||||
// a user-defined clustered index (PRIMARY KEY or NOT NULL UNIQUE index).
|
||||
// InnoDB always orders table rows according to such an index if one is present.
|
||||
if ($tbl_storage_engine === 'INNODB') {
|
||||
$indexes = Index::getFromTable($table, $db);
|
||||
foreach ($indexes as $name => $idx) {
|
||||
if ($name === 'PRIMARY') {
|
||||
$hideOrderTable = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if ($idx->getNonUnique()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$notNull = true;
|
||||
foreach ($idx->getColumns() as $column) {
|
||||
if ($column->getNull()) {
|
||||
$notNull = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($notNull) {
|
||||
$hideOrderTable = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$comment = '';
|
||||
if (mb_strstr((string) $show_comment, '; InnoDB free') === false) {
|
||||
if (mb_strstr((string) $show_comment, 'InnoDB free') === false) {
|
||||
// only user entered comment
|
||||
$comment = (string) $show_comment;
|
||||
} else {
|
||||
// here we have just InnoDB generated part
|
||||
$comment = '';
|
||||
}
|
||||
} else {
|
||||
// remove InnoDB comment from end, just the minimal part (*? is non greedy)
|
||||
$comment = preg_replace('@; InnoDB free:.*?$@', '', (string) $show_comment);
|
||||
}
|
||||
|
||||
$storageEngines = StorageEngine::getArray();
|
||||
|
||||
$charsets = Charsets::getCharsets($this->dbi, $GLOBALS['cfg']['Server']['DisableIS']);
|
||||
$collations = Charsets::getCollations($this->dbi, $GLOBALS['cfg']['Server']['DisableIS']);
|
||||
|
||||
$hasPackKeys = isset($create_options['pack_keys'])
|
||||
&& $pma_table->isEngine(['MYISAM', 'ARIA', 'ISAM']);
|
||||
$hasChecksumAndDelayKeyWrite = $pma_table->isEngine(['MYISAM', 'ARIA']);
|
||||
$hasTransactionalAndPageChecksum = $pma_table->isEngine('ARIA');
|
||||
$hasAutoIncrement = strlen((string) $auto_increment) > 0
|
||||
&& $pma_table->isEngine(['MYISAM', 'ARIA', 'INNODB', 'PBXT', 'ROCKSDB']);
|
||||
|
||||
$possibleRowFormats = $this->operations->getPossibleRowFormat();
|
||||
|
||||
$databaseList = [];
|
||||
if (count($GLOBALS['dblist']->databases) <= $GLOBALS['cfg']['MaxDbList']) {
|
||||
$databaseList = $GLOBALS['dblist']->databases->getList();
|
||||
}
|
||||
|
||||
$hasForeignKeys = ! empty($this->relation->getForeigners($db, $table, '', 'foreign'));
|
||||
$hasPrivileges = $GLOBALS['table_priv'] && $GLOBALS['col_priv'] && $GLOBALS['is_reload_priv'];
|
||||
$switchToNew = isset($_SESSION['pma_switch_to_new']) && $_SESSION['pma_switch_to_new'];
|
||||
|
||||
$partitions = [];
|
||||
$partitionsChoices = [];
|
||||
|
||||
if (Partition::havePartitioning()) {
|
||||
$partitionNames = Partition::getPartitionNames($db, $table);
|
||||
if ($partitionNames[0] !== null) {
|
||||
$partitions = $partitionNames;
|
||||
$partitionsChoices = $this->operations->getPartitionMaintenanceChoices();
|
||||
}
|
||||
}
|
||||
|
||||
$foreigners = $this->operations->getForeignersForReferentialIntegrityCheck(
|
||||
$url_params,
|
||||
(bool) $cfgRelation['relwork']
|
||||
);
|
||||
|
||||
$this->render('table/operations/index', [
|
||||
'db' => $db,
|
||||
'table' => $table,
|
||||
'url_params' => $url_params,
|
||||
'columns' => $columns,
|
||||
'hide_order_table' => $hideOrderTable,
|
||||
'table_comment' => $comment,
|
||||
'storage_engine' => $tbl_storage_engine,
|
||||
'storage_engines' => $storageEngines,
|
||||
'charsets' => $charsets,
|
||||
'collations' => $collations,
|
||||
'tbl_collation' => $tbl_collation,
|
||||
'row_formats' => $possibleRowFormats[$tbl_storage_engine] ?? [],
|
||||
'row_format_current' => $GLOBALS['showtable']['Row_format'],
|
||||
'has_auto_increment' => $hasAutoIncrement,
|
||||
'auto_increment' => $auto_increment,
|
||||
'has_pack_keys' => $hasPackKeys,
|
||||
'pack_keys' => $create_options['pack_keys'] ?? '',
|
||||
'has_transactional_and_page_checksum' => $hasTransactionalAndPageChecksum,
|
||||
'has_checksum_and_delay_key_write' => $hasChecksumAndDelayKeyWrite,
|
||||
'delay_key_write' => empty($create_options['delay_key_write']) ? '0' : '1',
|
||||
'transactional' => ($create_options['transactional'] ?? '') == '0' ? '0' : '1',
|
||||
'page_checksum' => $create_options['page_checksum'] ?? '',
|
||||
'checksum' => empty($create_options['checksum']) ? '0' : '1',
|
||||
'database_list' => $databaseList,
|
||||
'has_foreign_keys' => $hasForeignKeys,
|
||||
'has_privileges' => $hasPrivileges,
|
||||
'switch_to_new' => $switchToNew,
|
||||
'is_system_schema' => $isSystemSchema,
|
||||
'is_view' => $tbl_is_view,
|
||||
'partitions' => $partitions,
|
||||
'partitions_choices' => $partitionsChoices,
|
||||
'foreigners' => $foreigners,
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,211 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Table;
|
||||
|
||||
use PhpMyAdmin\Html\Generator;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Table\Partition;
|
||||
use PhpMyAdmin\Template;
|
||||
use function strlen;
|
||||
|
||||
final class PartitionController extends AbstractController
|
||||
{
|
||||
/** @var Partition */
|
||||
private $model;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param string $db
|
||||
* @param string $table
|
||||
* @param Partition $partition
|
||||
*/
|
||||
public function __construct($response, Template $template, $db, $table, $partition)
|
||||
{
|
||||
parent::__construct($response, $template, $db, $table);
|
||||
$this->model = $partition;
|
||||
}
|
||||
|
||||
public function analyze(): void
|
||||
{
|
||||
$partitionName = $_POST['partition_name'] ?? '';
|
||||
|
||||
if (strlen($partitionName) === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
[$rows, $query] = $this->model->analyze($this->db, $this->table, $partitionName);
|
||||
|
||||
$message = Generator::getMessage(
|
||||
__('Your SQL query has been executed successfully.'),
|
||||
$query,
|
||||
'success'
|
||||
);
|
||||
|
||||
$this->render('table/partition/analyze', [
|
||||
'partition_name' => $partitionName,
|
||||
'message' => $message,
|
||||
'rows' => $rows,
|
||||
]);
|
||||
}
|
||||
|
||||
public function check(): void
|
||||
{
|
||||
$partitionName = $_POST['partition_name'] ?? '';
|
||||
|
||||
if (strlen($partitionName) === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
[$rows, $query] = $this->model->check($this->db, $this->table, $partitionName);
|
||||
|
||||
$message = Generator::getMessage(
|
||||
__('Your SQL query has been executed successfully.'),
|
||||
$query,
|
||||
'success'
|
||||
);
|
||||
|
||||
$this->render('table/partition/check', [
|
||||
'partition_name' => $partitionName,
|
||||
'message' => $message,
|
||||
'rows' => $rows,
|
||||
]);
|
||||
}
|
||||
|
||||
public function drop(): void
|
||||
{
|
||||
$partitionName = $_POST['partition_name'] ?? '';
|
||||
|
||||
if (strlen($partitionName) === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
[$result, $query] = $this->model->drop($this->db, $this->table, $partitionName);
|
||||
|
||||
if ($result) {
|
||||
$message = Generator::getMessage(
|
||||
__('Your SQL query has been executed successfully.'),
|
||||
$query,
|
||||
'success'
|
||||
);
|
||||
} else {
|
||||
$message = Generator::getMessage(
|
||||
__('Error'),
|
||||
$query,
|
||||
'error'
|
||||
);
|
||||
}
|
||||
|
||||
$this->render('table/partition/drop', [
|
||||
'partition_name' => $partitionName,
|
||||
'message' => $message,
|
||||
]);
|
||||
}
|
||||
|
||||
public function optimize(): void
|
||||
{
|
||||
$partitionName = $_POST['partition_name'] ?? '';
|
||||
|
||||
if (strlen($partitionName) === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
[$rows, $query] = $this->model->optimize($this->db, $this->table, $partitionName);
|
||||
|
||||
$message = Generator::getMessage(
|
||||
__('Your SQL query has been executed successfully.'),
|
||||
$query,
|
||||
'success'
|
||||
);
|
||||
|
||||
$this->render('table/partition/optimize', [
|
||||
'partition_name' => $partitionName,
|
||||
'message' => $message,
|
||||
'rows' => $rows,
|
||||
]);
|
||||
}
|
||||
|
||||
public function rebuild(): void
|
||||
{
|
||||
$partitionName = $_POST['partition_name'] ?? '';
|
||||
|
||||
if (strlen($partitionName) === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
[$result, $query] = $this->model->rebuild($this->db, $this->table, $partitionName);
|
||||
|
||||
if ($result) {
|
||||
$message = Generator::getMessage(
|
||||
__('Your SQL query has been executed successfully.'),
|
||||
$query,
|
||||
'success'
|
||||
);
|
||||
} else {
|
||||
$message = Generator::getMessage(
|
||||
__('Error'),
|
||||
$query,
|
||||
'error'
|
||||
);
|
||||
}
|
||||
|
||||
$this->render('table/partition/rebuild', [
|
||||
'partition_name' => $partitionName,
|
||||
'message' => $message,
|
||||
]);
|
||||
}
|
||||
|
||||
public function repair(): void
|
||||
{
|
||||
$partitionName = $_POST['partition_name'] ?? '';
|
||||
|
||||
if (strlen($partitionName) === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
[$rows, $query] = $this->model->repair($this->db, $this->table, $partitionName);
|
||||
|
||||
$message = Generator::getMessage(
|
||||
__('Your SQL query has been executed successfully.'),
|
||||
$query,
|
||||
'success'
|
||||
);
|
||||
|
||||
$this->render('table/partition/repair', [
|
||||
'partition_name' => $partitionName,
|
||||
'message' => $message,
|
||||
'rows' => $rows,
|
||||
]);
|
||||
}
|
||||
|
||||
public function truncate(): void
|
||||
{
|
||||
$partitionName = $_POST['partition_name'] ?? '';
|
||||
|
||||
if (strlen($partitionName) === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
[$result, $query] = $this->model->truncate($this->db, $this->table, $partitionName);
|
||||
|
||||
if ($result) {
|
||||
$message = Generator::getMessage(
|
||||
__('Your SQL query has been executed successfully.'),
|
||||
$query,
|
||||
'success'
|
||||
);
|
||||
} else {
|
||||
$message = Generator::getMessage(
|
||||
__('Error'),
|
||||
$query,
|
||||
'error'
|
||||
);
|
||||
}
|
||||
|
||||
$this->render('table/partition/truncate', [
|
||||
'partition_name' => $partitionName,
|
||||
'message' => $message,
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
/**
|
||||
* Controller for table privileges
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Table;
|
||||
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Server\Privileges;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Util;
|
||||
|
||||
/**
|
||||
* Controller for table privileges
|
||||
*/
|
||||
class PrivilegesController extends AbstractController
|
||||
{
|
||||
/** @var Privileges */
|
||||
private $privileges;
|
||||
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param string $db Database name
|
||||
* @param string $table Table name
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, $db, $table, Privileges $privileges, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template, $db, $table);
|
||||
$this->privileges = $privileges;
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $params Request parameters
|
||||
*/
|
||||
public function index(array $params): string
|
||||
{
|
||||
global $cfg, $text_dir, $PMA_Theme;
|
||||
|
||||
$scriptName = Util::getScriptNameForOption(
|
||||
$cfg['DefaultTabTable'],
|
||||
'table'
|
||||
);
|
||||
|
||||
$privileges = [];
|
||||
if ($this->dbi->isSuperUser()) {
|
||||
$privileges = $this->privileges->getAllPrivileges(
|
||||
$params['checkprivsdb'],
|
||||
$params['checkprivstable']
|
||||
);
|
||||
}
|
||||
|
||||
return $this->template->render('table/privileges/index', [
|
||||
'db' => $params['checkprivsdb'],
|
||||
'table' => $params['checkprivstable'],
|
||||
'is_superuser' => $this->dbi->isSuperUser(),
|
||||
'table_url' => $scriptName,
|
||||
'theme_image_path' => $PMA_Theme->getImgPath(),
|
||||
'text_dir' => $text_dir,
|
||||
'is_createuser' => $this->dbi->isCreateUser(),
|
||||
'is_grantuser' => $this->dbi->isGrantUser(),
|
||||
'privileges' => $privileges,
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Table;
|
||||
|
||||
use PhpMyAdmin\Controllers\SqlController;
|
||||
use PhpMyAdmin\RecentFavoriteTable;
|
||||
|
||||
/**
|
||||
* Browse recent and favorite tables chosen from navigation.
|
||||
*/
|
||||
class RecentFavoriteController extends AbstractController
|
||||
{
|
||||
public function index(): void
|
||||
{
|
||||
global $containerBuilder;
|
||||
|
||||
RecentFavoriteTable::getInstance('recent')->removeIfInvalid(
|
||||
$_REQUEST['db'],
|
||||
$_REQUEST['table']
|
||||
);
|
||||
|
||||
RecentFavoriteTable::getInstance('favorite')->removeIfInvalid(
|
||||
$_REQUEST['db'],
|
||||
$_REQUEST['table']
|
||||
);
|
||||
|
||||
/** @var SqlController $controller */
|
||||
$controller = $containerBuilder->get(SqlController::class);
|
||||
$controller->index();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,388 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Table;
|
||||
|
||||
use PhpMyAdmin\Core;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Html\Generator;
|
||||
use PhpMyAdmin\Index;
|
||||
use PhpMyAdmin\Relation;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Table;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Util;
|
||||
use function array_key_exists;
|
||||
use function array_keys;
|
||||
use function array_values;
|
||||
use function htmlspecialchars;
|
||||
use function mb_strtoupper;
|
||||
use function md5;
|
||||
use function strtoupper;
|
||||
use function uksort;
|
||||
use function usort;
|
||||
|
||||
/**
|
||||
* Display table relations for viewing and editing.
|
||||
*
|
||||
* Includes phpMyAdmin relations and InnoDB relations.
|
||||
*/
|
||||
final class RelationController extends AbstractController
|
||||
{
|
||||
/** @var Relation */
|
||||
private $relation;
|
||||
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param string $db Database name
|
||||
* @param string $table Table name
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct(
|
||||
$response,
|
||||
Template $template,
|
||||
$db,
|
||||
$table,
|
||||
Relation $relation,
|
||||
$dbi
|
||||
) {
|
||||
parent::__construct($response, $template, $db, $table);
|
||||
$this->relation = $relation;
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
/**
|
||||
* Index
|
||||
*/
|
||||
public function index(): void
|
||||
{
|
||||
global $route;
|
||||
|
||||
$options = [
|
||||
'CASCADE' => 'CASCADE',
|
||||
'SET_NULL' => 'SET NULL',
|
||||
'NO_ACTION' => 'NO ACTION',
|
||||
'RESTRICT' => 'RESTRICT',
|
||||
];
|
||||
|
||||
$table = $this->dbi->getTable($this->db, $this->table);
|
||||
$storageEngine = mb_strtoupper((string) $table->getStatusInfo('Engine'));
|
||||
|
||||
$cfgRelation = $this->relation->getRelationsParam();
|
||||
|
||||
$relations = [];
|
||||
if ($cfgRelation['relwork']) {
|
||||
$relations = $this->relation->getForeigners(
|
||||
$this->db,
|
||||
$this->table,
|
||||
'',
|
||||
'internal'
|
||||
);
|
||||
}
|
||||
|
||||
$relationsForeign = [];
|
||||
if (Util::isForeignKeySupported($storageEngine)) {
|
||||
$relationsForeign = $this->relation->getForeigners(
|
||||
$this->db,
|
||||
$this->table,
|
||||
'',
|
||||
'foreign'
|
||||
);
|
||||
}
|
||||
|
||||
// Send table of column names to populate corresponding dropdowns depending
|
||||
// on the current selection
|
||||
if (isset($_POST['getDropdownValues'])
|
||||
&& $_POST['getDropdownValues'] === 'true'
|
||||
) {
|
||||
// if both db and table are selected
|
||||
if (isset($_POST['foreignTable'])) {
|
||||
$this->getDropdownValueForTable();
|
||||
} else { // if only the db is selected
|
||||
$this->getDropdownValueForDatabase($storageEngine);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->addScriptFiles(['table/relation.js', 'indexes.js']);
|
||||
|
||||
// Set the database
|
||||
$this->dbi->selectDb($this->db);
|
||||
|
||||
// updates for Internal relations
|
||||
if (isset($_POST['destination_db']) && $cfgRelation['relwork']) {
|
||||
$this->updateForInternalRelation($table, $cfgRelation, $relations);
|
||||
}
|
||||
|
||||
// updates for foreign keys
|
||||
$this->updateForForeignKeys($table, $options, $relationsForeign);
|
||||
|
||||
// Updates for display field
|
||||
if ($cfgRelation['displaywork'] && isset($_POST['display_field'])) {
|
||||
$this->updateForDisplayField($table, $cfgRelation);
|
||||
}
|
||||
|
||||
// If we did an update, refresh our data
|
||||
if (isset($_POST['destination_db']) && $cfgRelation['relwork']) {
|
||||
$relations = $this->relation->getForeigners(
|
||||
$this->db,
|
||||
$this->table,
|
||||
'',
|
||||
'internal'
|
||||
);
|
||||
}
|
||||
if (isset($_POST['destination_foreign_db'])
|
||||
&& Util::isForeignKeySupported($storageEngine)
|
||||
) {
|
||||
$relationsForeign = $this->relation->getForeigners(
|
||||
$this->db,
|
||||
$this->table,
|
||||
'',
|
||||
'foreign'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dialog
|
||||
*/
|
||||
// Now find out the columns of our $table
|
||||
// need to use DatabaseInterface::QUERY_STORE with $this->dbi->numRows()
|
||||
// in mysqli
|
||||
$columns = $this->dbi->getColumns($this->db, $this->table);
|
||||
|
||||
$column_array = [];
|
||||
$column_hash_array = [];
|
||||
$column_array[''] = '';
|
||||
foreach ($columns as $column) {
|
||||
if (strtoupper($storageEngine) !== 'INNODB'
|
||||
&& empty($column['Key'])
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$column_array[$column['Field']] = $column['Field'];
|
||||
$column_hash_array[$column['Field']] = md5($column['Field']);
|
||||
}
|
||||
if ($GLOBALS['cfg']['NaturalOrder']) {
|
||||
uksort($column_array, 'strnatcasecmp');
|
||||
}
|
||||
|
||||
// common form
|
||||
$engine = $this->dbi->getTable($this->db, $this->table)->getStorageEngine();
|
||||
$this->render('table/relation/common_form', [
|
||||
'is_foreign_key_supported' => Util::isForeignKeySupported($engine),
|
||||
'db' => $this->db,
|
||||
'table' => $this->table,
|
||||
'cfg_relation' => $cfgRelation,
|
||||
'tbl_storage_engine' => $storageEngine,
|
||||
'existrel' => $relations,
|
||||
'existrel_foreign' => array_key_exists('foreign_keys_data', $relationsForeign)
|
||||
? $relationsForeign['foreign_keys_data']
|
||||
: [],
|
||||
'options_array' => $options,
|
||||
'column_array' => $column_array,
|
||||
'column_hash_array' => $column_hash_array,
|
||||
'save_row' => array_values($columns),
|
||||
'url_params' => $GLOBALS['url_params'],
|
||||
'databases' => $GLOBALS['dblist']->databases,
|
||||
'dbi' => $this->dbi,
|
||||
'default_sliders_state' => $GLOBALS['cfg']['InitialSlidersState'],
|
||||
'route' => $route,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update for display field
|
||||
*
|
||||
* @param Table $table table
|
||||
* @param array $cfgRelation relation parameters
|
||||
*/
|
||||
private function updateForDisplayField(Table $table, array $cfgRelation): void
|
||||
{
|
||||
if (! $table->updateDisplayField(
|
||||
$_POST['display_field'],
|
||||
$cfgRelation
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->response->addHTML(
|
||||
Generator::getMessage(
|
||||
__('Display column was successfully updated.'),
|
||||
'',
|
||||
'success'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update for FK
|
||||
*
|
||||
* @param Table $table Table
|
||||
* @param array $options Options
|
||||
* @param array $relationsForeign External relations
|
||||
*/
|
||||
private function updateForForeignKeys(Table $table, array $options, array $relationsForeign): void
|
||||
{
|
||||
$multi_edit_columns_name = $_POST['foreign_key_fields_name'] ?? null;
|
||||
$preview_sql_data = '';
|
||||
$seen_error = false;
|
||||
|
||||
// (for now, one index name only; we keep the definitions if the
|
||||
// foreign db is not the same)
|
||||
if (isset($_POST['destination_foreign_db'], $_POST['destination_foreign_table'])
|
||||
&& isset($_POST['destination_foreign_column'])) {
|
||||
[
|
||||
$html,
|
||||
$preview_sql_data,
|
||||
$display_query,
|
||||
$seen_error,
|
||||
] = $table->updateForeignKeys(
|
||||
$_POST['destination_foreign_db'],
|
||||
$multi_edit_columns_name,
|
||||
$_POST['destination_foreign_table'],
|
||||
$_POST['destination_foreign_column'],
|
||||
$options,
|
||||
$this->table,
|
||||
array_key_exists('foreign_keys_data', $relationsForeign)
|
||||
? $relationsForeign['foreign_keys_data']
|
||||
: []
|
||||
);
|
||||
$this->response->addHTML($html);
|
||||
}
|
||||
|
||||
// If there is a request for SQL previewing.
|
||||
if (isset($_POST['preview_sql'])) {
|
||||
Core::previewSQL($preview_sql_data);
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
if (empty($display_query) || $seen_error) {
|
||||
return;
|
||||
}
|
||||
|
||||
$GLOBALS['display_query'] = $display_query;
|
||||
$this->response->addHTML(
|
||||
Generator::getMessage(
|
||||
__('Your SQL query has been executed successfully.'),
|
||||
null,
|
||||
'success'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update for internal relation
|
||||
*
|
||||
* @param Table $table Table
|
||||
* @param array $cfgRelation Relation parameters
|
||||
* @param array $relations Relations
|
||||
*/
|
||||
private function updateForInternalRelation(Table $table, array $cfgRelation, array $relations): void
|
||||
{
|
||||
$multi_edit_columns_name = $_POST['fields_name'] ?? null;
|
||||
|
||||
if (! $table->updateInternalRelations(
|
||||
$multi_edit_columns_name,
|
||||
$_POST['destination_db'],
|
||||
$_POST['destination_table'],
|
||||
$_POST['destination_column'],
|
||||
$cfgRelation,
|
||||
$relations
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->response->addHTML(
|
||||
Generator::getMessage(
|
||||
__('Internal relationships were successfully updated.'),
|
||||
'',
|
||||
'success'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send table columns for foreign table dropdown
|
||||
*/
|
||||
public function getDropdownValueForTable(): void
|
||||
{
|
||||
$foreignTable = $_POST['foreignTable'];
|
||||
$table_obj = $this->dbi->getTable($_POST['foreignDb'], $foreignTable);
|
||||
// Since views do not have keys defined on them provide the full list of
|
||||
// columns
|
||||
if ($table_obj->isView()) {
|
||||
$columnList = $table_obj->getColumns(false, false);
|
||||
} else {
|
||||
$columnList = $table_obj->getIndexedColumns(false, false);
|
||||
}
|
||||
$columns = [];
|
||||
foreach ($columnList as $column) {
|
||||
$columns[] = htmlspecialchars($column);
|
||||
}
|
||||
if ($GLOBALS['cfg']['NaturalOrder']) {
|
||||
usort($columns, 'strnatcasecmp');
|
||||
}
|
||||
$this->response->addJSON('columns', $columns);
|
||||
|
||||
// @todo should be: $server->db($db)->table($table)->primary()
|
||||
$primary = Index::getPrimary($foreignTable, $_POST['foreignDb']);
|
||||
if ($primary === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->response->addJSON('primary', array_keys($primary->getColumns()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Send database selection values for dropdown
|
||||
*
|
||||
* @param string $storageEngine Storage engine.
|
||||
*/
|
||||
public function getDropdownValueForDatabase(string $storageEngine): void
|
||||
{
|
||||
$tables = [];
|
||||
$foreign = isset($_POST['foreign']) && $_POST['foreign'] === 'true';
|
||||
|
||||
if ($foreign) {
|
||||
$query = 'SHOW TABLE STATUS FROM '
|
||||
. Util::backquote($_POST['foreignDb']);
|
||||
$tables_rs = $this->dbi->query(
|
||||
$query,
|
||||
DatabaseInterface::CONNECT_USER,
|
||||
DatabaseInterface::QUERY_STORE
|
||||
);
|
||||
|
||||
while ($row = $this->dbi->fetchArray($tables_rs)) {
|
||||
if (! isset($row['Engine'])
|
||||
|| mb_strtoupper($row['Engine']) != $storageEngine
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tables[] = htmlspecialchars($row['Name']);
|
||||
}
|
||||
} else {
|
||||
$query = 'SHOW TABLES FROM '
|
||||
. Util::backquote($_POST['foreignDb']);
|
||||
$tables_rs = $this->dbi->query(
|
||||
$query,
|
||||
DatabaseInterface::CONNECT_USER,
|
||||
DatabaseInterface::QUERY_STORE
|
||||
);
|
||||
while ($row = $this->dbi->fetchArray($tables_rs)) {
|
||||
$tables[] = htmlspecialchars($row[0]);
|
||||
}
|
||||
}
|
||||
if ($GLOBALS['cfg']['NaturalOrder']) {
|
||||
usort($tables, 'strnatcasecmp');
|
||||
}
|
||||
$this->response->addJSON('tables', $tables);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,651 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Table;
|
||||
|
||||
use PhpMyAdmin\Controllers\Database\SqlController as DatabaseSqlController;
|
||||
use PhpMyAdmin\Controllers\SqlController;
|
||||
use PhpMyAdmin\Controllers\Table\SqlController as TableSqlController;
|
||||
use PhpMyAdmin\Core;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\File;
|
||||
use PhpMyAdmin\Html\Generator;
|
||||
use PhpMyAdmin\InsertEdit;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\Plugins\IOTransformationsPlugin;
|
||||
use PhpMyAdmin\Relation;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Table;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Transformations;
|
||||
use PhpMyAdmin\Util;
|
||||
use function array_values;
|
||||
use function class_exists;
|
||||
use function count;
|
||||
use function implode;
|
||||
use function in_array;
|
||||
use function is_file;
|
||||
use function method_exists;
|
||||
use function parse_str;
|
||||
use function sprintf;
|
||||
|
||||
/**
|
||||
* Manipulation of table data like inserting, replacing and updating.
|
||||
*/
|
||||
final class ReplaceController extends AbstractController
|
||||
{
|
||||
/** @var InsertEdit */
|
||||
private $insertEdit;
|
||||
|
||||
/** @var Transformations */
|
||||
private $transformations;
|
||||
|
||||
/** @var Relation */
|
||||
private $relation;
|
||||
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param string $db Database name.
|
||||
* @param string $table Table name.
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct(
|
||||
$response,
|
||||
Template $template,
|
||||
$db,
|
||||
$table,
|
||||
InsertEdit $insertEdit,
|
||||
Transformations $transformations,
|
||||
Relation $relation,
|
||||
$dbi
|
||||
) {
|
||||
parent::__construct($response, $template, $db, $table);
|
||||
$this->insertEdit = $insertEdit;
|
||||
$this->transformations = $transformations;
|
||||
$this->relation = $relation;
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $containerBuilder, $db, $table, $url_params, $message;
|
||||
global $err_url, $mime_map, $unsaved_values, $active_page, $disp_query, $disp_message;
|
||||
global $goto_include, $loop_array, $using_key, $is_insert, $is_insertignore, $query;
|
||||
global $value_sets, $func_no_param, $func_optional_param, $gis_from_text_functions, $gis_from_wkb_functions;
|
||||
global $query_fields, $insert_errors, $row_skipped, $query_values;
|
||||
global $total_affected_rows, $last_messages, $warning_messages, $error_messages, $return_to_sql_query;
|
||||
|
||||
Util::checkParameters(['db', 'table', 'goto']);
|
||||
|
||||
$this->dbi->selectDb($db);
|
||||
|
||||
/**
|
||||
* Initializes some variables
|
||||
*/
|
||||
$goto_include = false;
|
||||
|
||||
$this->addScriptFiles([
|
||||
'makegrid.js',
|
||||
'vendor/stickyfill.min.js',
|
||||
'sql.js',
|
||||
'indexes.js',
|
||||
'gis_data_editor.js',
|
||||
]);
|
||||
|
||||
// check whether insert row mode, if so include /table/change
|
||||
$this->insertEdit->isInsertRow();
|
||||
|
||||
$after_insert_actions = [
|
||||
'new_insert',
|
||||
'same_insert',
|
||||
'edit_next',
|
||||
];
|
||||
if (isset($_POST['after_insert'])
|
||||
&& in_array($_POST['after_insert'], $after_insert_actions)
|
||||
) {
|
||||
$url_params['after_insert'] = $_POST['after_insert'];
|
||||
if (isset($_POST['where_clause'])) {
|
||||
foreach ($_POST['where_clause'] as $one_where_clause) {
|
||||
if ($_POST['after_insert'] === 'same_insert') {
|
||||
$url_params['where_clause'][] = $one_where_clause;
|
||||
} elseif ($_POST['after_insert'] === 'edit_next') {
|
||||
$this->insertEdit->setSessionForEditNext($one_where_clause);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//get $goto_include for different cases
|
||||
$goto_include = $this->insertEdit->getGotoInclude($goto_include);
|
||||
|
||||
// Defines the url to return in case of failure of the query
|
||||
$err_url = $this->insertEdit->getErrorUrl($url_params);
|
||||
|
||||
/**
|
||||
* Prepares the update/insert of a row
|
||||
*/
|
||||
[
|
||||
$loop_array,
|
||||
$using_key,
|
||||
$is_insert,
|
||||
$is_insertignore,
|
||||
] = $this->insertEdit->getParamsForUpdateOrInsert();
|
||||
|
||||
$query = [];
|
||||
$value_sets = [];
|
||||
$func_no_param = [
|
||||
'CONNECTION_ID',
|
||||
'CURRENT_USER',
|
||||
'CURDATE',
|
||||
'CURTIME',
|
||||
'CURRENT_DATE',
|
||||
'CURRENT_TIME',
|
||||
'DATABASE',
|
||||
'LAST_INSERT_ID',
|
||||
'NOW',
|
||||
'PI',
|
||||
'RAND',
|
||||
'SYSDATE',
|
||||
'UNIX_TIMESTAMP',
|
||||
'USER',
|
||||
'UTC_DATE',
|
||||
'UTC_TIME',
|
||||
'UTC_TIMESTAMP',
|
||||
'UUID',
|
||||
'UUID_SHORT',
|
||||
'VERSION',
|
||||
];
|
||||
$func_optional_param = [
|
||||
'RAND',
|
||||
'UNIX_TIMESTAMP',
|
||||
];
|
||||
|
||||
$gis_from_text_functions = [
|
||||
'GeomFromText',
|
||||
'GeomCollFromText',
|
||||
'LineFromText',
|
||||
'MLineFromText',
|
||||
'PointFromText',
|
||||
'MPointFromText',
|
||||
'PolyFromText',
|
||||
'MPolyFromText',
|
||||
];
|
||||
|
||||
$gis_from_wkb_functions = [];
|
||||
if ($this->dbi->getVersion() >= 50600) {
|
||||
$gis_from_wkb_functions = [
|
||||
'ST_GeomFromText',
|
||||
'ST_GeomCollFromText',
|
||||
'ST_LineFromText',
|
||||
'ST_MLineFromText',
|
||||
'ST_PointFromText',
|
||||
'ST_MPointFromText',
|
||||
'ST_PolyFromText',
|
||||
'ST_MPolyFromText',
|
||||
];
|
||||
}
|
||||
|
||||
$mime_map = $this->transformations->getMime($db, $table);
|
||||
if ($mime_map === null) {
|
||||
$mime_map = [];
|
||||
}
|
||||
|
||||
$query_fields = [];
|
||||
$insert_errors = [];
|
||||
$row_skipped = false;
|
||||
$unsaved_values = [];
|
||||
foreach ($loop_array as $rownumber => $where_clause) {
|
||||
// skip fields to be ignored
|
||||
if (! $using_key && isset($_POST['insert_ignore_' . $where_clause])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Defines the SET part of the sql query
|
||||
$query_values = [];
|
||||
|
||||
// Map multi-edit keys to single-level arrays, dependent on how we got the fields
|
||||
$multi_edit_columns
|
||||
= $_POST['fields']['multi_edit'][$rownumber] ?? [];
|
||||
$multi_edit_columns_name
|
||||
= $_POST['fields_name']['multi_edit'][$rownumber] ?? [];
|
||||
$multi_edit_columns_prev
|
||||
= $_POST['fields_prev']['multi_edit'][$rownumber] ?? null;
|
||||
$multi_edit_funcs
|
||||
= $_POST['funcs']['multi_edit'][$rownumber] ?? null;
|
||||
$multi_edit_salt
|
||||
= $_POST['salt']['multi_edit'][$rownumber] ?? null;
|
||||
$multi_edit_columns_type
|
||||
= $_POST['fields_type']['multi_edit'][$rownumber] ?? null;
|
||||
$multi_edit_columns_null
|
||||
= $_POST['fields_null']['multi_edit'][$rownumber] ?? null;
|
||||
$multi_edit_columns_null_prev
|
||||
= $_POST['fields_null_prev']['multi_edit'][$rownumber] ?? null;
|
||||
$multi_edit_auto_increment
|
||||
= $_POST['auto_increment']['multi_edit'][$rownumber] ?? null;
|
||||
$multi_edit_virtual
|
||||
= $_POST['virtual']['multi_edit'][$rownumber] ?? null;
|
||||
|
||||
// When a select field is nullified, it's not present in $_POST
|
||||
// so initialize it; this way, the foreach($multi_edit_columns) will process it
|
||||
foreach ($multi_edit_columns_name as $key => $val) {
|
||||
if (isset($multi_edit_columns[$key])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$multi_edit_columns[$key] = '';
|
||||
}
|
||||
|
||||
// Iterate in the order of $multi_edit_columns_name,
|
||||
// not $multi_edit_columns, to avoid problems
|
||||
// when inserting multiple entries
|
||||
$insert_fail = false;
|
||||
foreach ($multi_edit_columns_name as $key => $column_name) {
|
||||
$current_value = $multi_edit_columns[$key];
|
||||
// Note: $key is an md5 of the fieldname. The actual fieldname is
|
||||
// available in $multi_edit_columns_name[$key]
|
||||
|
||||
$file_to_insert = new File();
|
||||
$file_to_insert->checkTblChangeForm((string) $key, (string) $rownumber);
|
||||
|
||||
$possibly_uploaded_val = $file_to_insert->getContent();
|
||||
if ($possibly_uploaded_val !== false) {
|
||||
$current_value = $possibly_uploaded_val;
|
||||
}
|
||||
// Apply Input Transformation if defined
|
||||
if (! empty($mime_map[$column_name])
|
||||
&& ! empty($mime_map[$column_name]['input_transformation'])
|
||||
) {
|
||||
$filename = 'libraries/classes/Plugins/Transformations/'
|
||||
. $mime_map[$column_name]['input_transformation'];
|
||||
if (is_file($filename)) {
|
||||
$classname = $this->transformations->getClassName($filename);
|
||||
if (class_exists($classname)) {
|
||||
/** @var IOTransformationsPlugin $transformation_plugin */
|
||||
$transformation_plugin = new $classname();
|
||||
$transformation_options = $this->transformations->getOptions(
|
||||
$mime_map[$column_name]['input_transformation_options']
|
||||
);
|
||||
$current_value = $transformation_plugin->applyTransformation(
|
||||
$current_value,
|
||||
$transformation_options
|
||||
);
|
||||
// check if transformation was successful or not
|
||||
// and accordingly set error messages & insert_fail
|
||||
if (method_exists($transformation_plugin, 'isSuccess')
|
||||
&& ! $transformation_plugin->isSuccess()
|
||||
) {
|
||||
$insert_fail = true;
|
||||
$row_skipped = true;
|
||||
$insert_errors[] = sprintf(
|
||||
__('Row: %1$s, Column: %2$s, Error: %3$s'),
|
||||
$rownumber,
|
||||
$column_name,
|
||||
$transformation_plugin->getError()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($file_to_insert->isError()) {
|
||||
$insert_errors[] = $file_to_insert->getError();
|
||||
}
|
||||
// delete $file_to_insert temporary variable
|
||||
$file_to_insert->cleanUp();
|
||||
|
||||
$current_value = $this->insertEdit->getCurrentValueForDifferentTypes(
|
||||
$possibly_uploaded_val,
|
||||
$key,
|
||||
$multi_edit_columns_type,
|
||||
$current_value,
|
||||
$multi_edit_auto_increment,
|
||||
$rownumber,
|
||||
$multi_edit_columns_name,
|
||||
$multi_edit_columns_null,
|
||||
$multi_edit_columns_null_prev,
|
||||
$is_insert,
|
||||
$using_key,
|
||||
$where_clause,
|
||||
$table,
|
||||
$multi_edit_funcs
|
||||
);
|
||||
|
||||
$current_value_as_an_array = $this->insertEdit->getCurrentValueAsAnArrayForMultipleEdit(
|
||||
$multi_edit_funcs,
|
||||
$multi_edit_salt,
|
||||
$gis_from_text_functions,
|
||||
$current_value,
|
||||
$gis_from_wkb_functions,
|
||||
$func_optional_param,
|
||||
$func_no_param,
|
||||
$key
|
||||
);
|
||||
|
||||
if (! isset($multi_edit_virtual, $multi_edit_virtual[$key])) {
|
||||
[
|
||||
$query_values,
|
||||
$query_fields,
|
||||
] = $this->insertEdit->getQueryValuesForInsertAndUpdateInMultipleEdit(
|
||||
$multi_edit_columns_name,
|
||||
$multi_edit_columns_null,
|
||||
$current_value,
|
||||
$multi_edit_columns_prev,
|
||||
$multi_edit_funcs,
|
||||
$is_insert,
|
||||
$query_values,
|
||||
$query_fields,
|
||||
$current_value_as_an_array,
|
||||
$value_sets,
|
||||
$key,
|
||||
$multi_edit_columns_null_prev
|
||||
);
|
||||
}
|
||||
if (! isset($multi_edit_columns_null[$key])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$multi_edit_columns[$key] = null;
|
||||
}
|
||||
|
||||
// temporarily store rows not inserted
|
||||
// so that they can be populated again.
|
||||
if ($insert_fail) {
|
||||
$unsaved_values[$rownumber] = $multi_edit_columns;
|
||||
}
|
||||
if ($insert_fail || count($query_values) <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($is_insert) {
|
||||
$value_sets[] = implode(', ', $query_values);
|
||||
} else {
|
||||
// build update query
|
||||
$query[] = 'UPDATE ' . Util::backquote($table)
|
||||
. ' SET ' . implode(', ', $query_values)
|
||||
. ' WHERE ' . $where_clause
|
||||
. ($_POST['clause_is_unique'] ? '' : ' LIMIT 1');
|
||||
}
|
||||
}
|
||||
unset(
|
||||
$multi_edit_columns_name,
|
||||
$multi_edit_columns_prev,
|
||||
$multi_edit_funcs,
|
||||
$multi_edit_columns_type,
|
||||
$multi_edit_columns_null,
|
||||
$func_no_param,
|
||||
$multi_edit_auto_increment,
|
||||
$current_value_as_an_array,
|
||||
$key,
|
||||
$current_value,
|
||||
$loop_array,
|
||||
$where_clause,
|
||||
$using_key,
|
||||
$multi_edit_columns_null_prev,
|
||||
$insert_fail
|
||||
);
|
||||
|
||||
// Builds the sql query
|
||||
if ($is_insert && count($value_sets) > 0) {
|
||||
$query = $this->insertEdit->buildSqlQuery($is_insertignore, $query_fields, $value_sets);
|
||||
} elseif (empty($query) && ! isset($_POST['preview_sql']) && ! $row_skipped) {
|
||||
// No change -> move back to the calling script
|
||||
//
|
||||
// Note: logic passes here for inline edit
|
||||
$message = Message::success(__('No change'));
|
||||
// Avoid infinite recursion
|
||||
if ($goto_include === '/table/replace') {
|
||||
$goto_include = '/table/change';
|
||||
}
|
||||
$active_page = $goto_include;
|
||||
|
||||
if ($goto_include === '/sql') {
|
||||
/** @var SqlController $controller */
|
||||
$controller = $containerBuilder->get(SqlController::class);
|
||||
$controller->index();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($goto_include === '/database/sql') {
|
||||
/** @var DatabaseSqlController $controller */
|
||||
$controller = $containerBuilder->get(DatabaseSqlController::class);
|
||||
$controller->index();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($goto_include === '/table/change') {
|
||||
/** @var ChangeController $controller */
|
||||
$controller = $containerBuilder->get(ChangeController::class);
|
||||
$controller->index();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($goto_include === '/table/sql') {
|
||||
/** @var TableSqlController $controller */
|
||||
$controller = $containerBuilder->get(TableSqlController::class);
|
||||
$controller->index();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
include ROOT_PATH . Core::securePath((string) $goto_include);
|
||||
|
||||
return;
|
||||
}
|
||||
unset($multi_edit_columns, $is_insertignore);
|
||||
|
||||
// If there is a request for SQL previewing.
|
||||
if (isset($_POST['preview_sql'])) {
|
||||
Core::previewSQL($query);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the sql query and get the result, then move back to the calling
|
||||
* page
|
||||
*/
|
||||
[
|
||||
$url_params,
|
||||
$total_affected_rows,
|
||||
$last_messages,
|
||||
$warning_messages,
|
||||
$error_messages,
|
||||
$return_to_sql_query,
|
||||
] = $this->insertEdit->executeSqlQuery($url_params, $query);
|
||||
|
||||
if ($is_insert && (count($value_sets) > 0 || $row_skipped)) {
|
||||
$message = Message::getMessageForInsertedRows(
|
||||
$total_affected_rows
|
||||
);
|
||||
$unsaved_values = array_values($unsaved_values);
|
||||
} else {
|
||||
$message = Message::getMessageForAffectedRows(
|
||||
$total_affected_rows
|
||||
);
|
||||
}
|
||||
if ($row_skipped) {
|
||||
$goto_include = '/table/change';
|
||||
$message->addMessagesString($insert_errors, '<br>');
|
||||
$message->isError(true);
|
||||
}
|
||||
|
||||
$message->addMessages($last_messages, '<br>');
|
||||
|
||||
if (! empty($warning_messages)) {
|
||||
$message->addMessagesString($warning_messages, '<br>');
|
||||
$message->isError(true);
|
||||
}
|
||||
if (! empty($error_messages)) {
|
||||
$message->addMessagesString($error_messages);
|
||||
$message->isError(true);
|
||||
}
|
||||
unset(
|
||||
$error_messages,
|
||||
$warning_messages,
|
||||
$total_affected_rows,
|
||||
$last_messages,
|
||||
$row_skipped,
|
||||
$insert_errors
|
||||
);
|
||||
|
||||
/**
|
||||
* The following section only applies to grid editing.
|
||||
* However, verifying isAjax() is not enough to ensure we are coming from
|
||||
* grid editing. If we are coming from the Edit or Copy link in Browse mode,
|
||||
* ajax_page_request is present in the POST parameters.
|
||||
*/
|
||||
if ($this->response->isAjax() && ! isset($_POST['ajax_page_request'])) {
|
||||
/**
|
||||
* If we are in grid editing, we need to process the relational and
|
||||
* transformed fields, if they were edited. After that, output the correct
|
||||
* link/transformed value and exit
|
||||
*/
|
||||
if (isset($_POST['rel_fields_list']) && $_POST['rel_fields_list'] != '') {
|
||||
$map = $this->relation->getForeigners($db, $table, '', 'both');
|
||||
|
||||
/** @var array<int,array> $relation_fields */
|
||||
$relation_fields = [];
|
||||
parse_str($_POST['rel_fields_list'], $relation_fields);
|
||||
|
||||
// loop for each relation cell
|
||||
foreach ($relation_fields as $cell_index => $curr_rel_field) {
|
||||
foreach ($curr_rel_field as $relation_field => $relation_field_value) {
|
||||
$where_comparison = "='" . $relation_field_value . "'";
|
||||
$dispval = $this->insertEdit->getDisplayValueForForeignTableColumn(
|
||||
$where_comparison,
|
||||
$map,
|
||||
$relation_field
|
||||
);
|
||||
|
||||
$extra_data['relations'][$cell_index] = $this->insertEdit->getLinkForRelationalDisplayField(
|
||||
$map,
|
||||
$relation_field,
|
||||
$where_comparison,
|
||||
$dispval,
|
||||
$relation_field_value
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isset($_POST['do_transformations'])
|
||||
&& $_POST['do_transformations'] == true
|
||||
) {
|
||||
$edited_values = [];
|
||||
parse_str($_POST['transform_fields_list'], $edited_values);
|
||||
|
||||
if (! isset($extra_data)) {
|
||||
$extra_data = [];
|
||||
}
|
||||
$transformation_types = [
|
||||
'input_transformation',
|
||||
'transformation',
|
||||
];
|
||||
foreach ($mime_map as $transformation) {
|
||||
$column_name = $transformation['column_name'];
|
||||
foreach ($transformation_types as $type) {
|
||||
$file = Core::securePath($transformation[$type]);
|
||||
$extra_data = $this->insertEdit->transformEditedValues(
|
||||
$db,
|
||||
$table,
|
||||
$transformation,
|
||||
$edited_values,
|
||||
$file,
|
||||
$column_name,
|
||||
$extra_data,
|
||||
$type
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Need to check the inline edited value can be truncated by MySQL
|
||||
// without informing while saving
|
||||
$column_name = $_POST['fields_name']['multi_edit'][0][0];
|
||||
|
||||
$this->insertEdit->verifyWhetherValueCanBeTruncatedAndAppendExtraData(
|
||||
$db,
|
||||
$table,
|
||||
$column_name,
|
||||
$extra_data
|
||||
);
|
||||
|
||||
/**Get the total row count of the table*/
|
||||
$_table = new Table($_POST['table'], $_POST['db']);
|
||||
$extra_data['row_count'] = $_table->countRecords();
|
||||
|
||||
$extra_data['sql_query'] = Generator::getMessage(
|
||||
$message,
|
||||
$GLOBALS['display_query']
|
||||
);
|
||||
|
||||
$this->response->setRequestStatus($message->isSuccess());
|
||||
$this->response->addJSON('message', $message);
|
||||
$this->response->addJSON($extra_data);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (! empty($return_to_sql_query)) {
|
||||
$disp_query = $GLOBALS['sql_query'];
|
||||
$disp_message = $message;
|
||||
unset($message);
|
||||
$GLOBALS['sql_query'] = $return_to_sql_query;
|
||||
}
|
||||
|
||||
$this->addScriptFiles(['vendor/jquery/additional-methods.js', 'table/change.js']);
|
||||
|
||||
$active_page = $goto_include;
|
||||
|
||||
/**
|
||||
* If user asked for "and then Insert another new row" we have to remove
|
||||
* WHERE clause information so that /table/change does not go back
|
||||
* to the current record
|
||||
*/
|
||||
if (isset($_POST['after_insert']) && $_POST['after_insert'] === 'new_insert') {
|
||||
unset($_POST['where_clause']);
|
||||
}
|
||||
|
||||
if ($goto_include === '/sql') {
|
||||
/** @var SqlController $controller */
|
||||
$controller = $containerBuilder->get(SqlController::class);
|
||||
$controller->index();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($goto_include === '/database/sql') {
|
||||
/** @var DatabaseSqlController $controller */
|
||||
$controller = $containerBuilder->get(DatabaseSqlController::class);
|
||||
$controller->index();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($goto_include === '/table/change') {
|
||||
/** @var ChangeController $controller */
|
||||
$controller = $containerBuilder->get(ChangeController::class);
|
||||
$controller->index();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($goto_include === '/table/sql') {
|
||||
/** @var TableSqlController $controller */
|
||||
$controller = $containerBuilder->get(TableSqlController::class);
|
||||
$controller->index();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load target page.
|
||||
*/
|
||||
require ROOT_PATH . Core::securePath((string) $goto_include);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,444 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Table;
|
||||
|
||||
use PhpMyAdmin\Core;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\DbTableExists;
|
||||
use PhpMyAdmin\Operations;
|
||||
use PhpMyAdmin\Relation;
|
||||
use PhpMyAdmin\RelationCleanup;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Sql;
|
||||
use PhpMyAdmin\Table\Search;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Transformations;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
use function in_array;
|
||||
use function intval;
|
||||
use function mb_strtolower;
|
||||
use function md5;
|
||||
use function preg_match;
|
||||
use function preg_replace;
|
||||
use function str_ireplace;
|
||||
use function str_replace;
|
||||
use function strncasecmp;
|
||||
use function strtoupper;
|
||||
|
||||
/**
|
||||
* Handles table search tab.
|
||||
*
|
||||
* Display table search form, create SQL query from form data
|
||||
* and call Sql::executeQueryAndSendQueryResponse() to execute it.
|
||||
*/
|
||||
class SearchController extends AbstractController
|
||||
{
|
||||
/**
|
||||
* Names of columns
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private $columnNames;
|
||||
/**
|
||||
* Types of columns
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private $columnTypes;
|
||||
/**
|
||||
* Types of columns without any replacement
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private $originalColumnTypes;
|
||||
/**
|
||||
* Collations of columns
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private $columnCollations;
|
||||
/**
|
||||
* Null Flags of columns
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private $columnNullFlags;
|
||||
/**
|
||||
* Whether a geometry column is present
|
||||
*
|
||||
* @access private
|
||||
* @var bool
|
||||
*/
|
||||
private $geomColumnFlag;
|
||||
/**
|
||||
* Foreign Keys
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private $foreigners;
|
||||
|
||||
/** @var Search */
|
||||
private $search;
|
||||
|
||||
/** @var Relation */
|
||||
private $relation;
|
||||
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param string $db Database name
|
||||
* @param string $table Table name
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct(
|
||||
$response,
|
||||
Template $template,
|
||||
$db,
|
||||
$table,
|
||||
Search $search,
|
||||
Relation $relation,
|
||||
$dbi
|
||||
) {
|
||||
parent::__construct($response, $template, $db, $table);
|
||||
$this->search = $search;
|
||||
$this->relation = $relation;
|
||||
$this->dbi = $dbi;
|
||||
|
||||
$this->columnNames = [];
|
||||
$this->columnTypes = [];
|
||||
$this->originalColumnTypes = [];
|
||||
$this->columnCollations = [];
|
||||
$this->columnNullFlags = [];
|
||||
$this->geomColumnFlag = false;
|
||||
$this->foreigners = [];
|
||||
$this->loadTableInfo();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the columns of a table along with their types, collations
|
||||
* and whether null or not.
|
||||
*/
|
||||
private function loadTableInfo(): void
|
||||
{
|
||||
// Gets the list and number of columns
|
||||
$columns = $this->dbi->getColumns(
|
||||
$this->db,
|
||||
$this->table,
|
||||
null,
|
||||
true
|
||||
);
|
||||
// Get details about the geometry functions
|
||||
$geom_types = Util::getGISDatatypes();
|
||||
|
||||
foreach ($columns as $row) {
|
||||
// set column name
|
||||
$this->columnNames[] = $row['Field'];
|
||||
|
||||
$type = (string) $row['Type'];
|
||||
// before any replacement
|
||||
$this->originalColumnTypes[] = mb_strtolower($type);
|
||||
// check whether table contains geometric columns
|
||||
if (in_array($type, $geom_types)) {
|
||||
$this->geomColumnFlag = true;
|
||||
}
|
||||
// reformat mysql query output
|
||||
if (strncasecmp($type, 'set', 3) == 0
|
||||
|| strncasecmp($type, 'enum', 4) == 0
|
||||
) {
|
||||
$type = str_replace(',', ', ', $type);
|
||||
} else {
|
||||
// strip the "BINARY" attribute, except if we find "BINARY(" because
|
||||
// this would be a BINARY or VARBINARY column type
|
||||
if (! preg_match('@BINARY[\(]@i', $type)) {
|
||||
$type = str_ireplace('BINARY', '', $type);
|
||||
}
|
||||
$type = str_ireplace('ZEROFILL', '', $type);
|
||||
$type = str_ireplace('UNSIGNED', '', $type);
|
||||
$type = mb_strtolower($type);
|
||||
}
|
||||
if (empty($type)) {
|
||||
$type = ' ';
|
||||
}
|
||||
$this->columnTypes[] = $type;
|
||||
$this->columnNullFlags[] = $row['Null'];
|
||||
$this->columnCollations[]
|
||||
= ! empty($row['Collation']) && $row['Collation'] !== 'NULL'
|
||||
? $row['Collation']
|
||||
: '';
|
||||
}
|
||||
|
||||
// Retrieve foreign keys
|
||||
$this->foreigners = $this->relation->getForeigners($this->db, $this->table);
|
||||
}
|
||||
|
||||
/**
|
||||
* Index action
|
||||
*/
|
||||
public function index(): void
|
||||
{
|
||||
global $db, $table, $url_params, $cfg, $err_url;
|
||||
|
||||
Util::checkParameters(['db', 'table']);
|
||||
|
||||
$url_params = ['db' => $db, 'table' => $table];
|
||||
$err_url = Util::getScriptNameForOption($cfg['DefaultTabTable'], 'table');
|
||||
$err_url .= Url::getCommon($url_params, '&');
|
||||
|
||||
DbTableExists::check();
|
||||
|
||||
$this->addScriptFiles([
|
||||
'makegrid.js',
|
||||
'vendor/stickyfill.min.js',
|
||||
'sql.js',
|
||||
'table/select.js',
|
||||
'table/change.js',
|
||||
'vendor/jquery/jquery.uitablefilter.js',
|
||||
'gis_data_editor.js',
|
||||
]);
|
||||
|
||||
if (isset($_POST['range_search'])) {
|
||||
$this->rangeSearchAction();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* No selection criteria received -> display the selection form
|
||||
*/
|
||||
if (! isset($_POST['columnsToDisplay'])
|
||||
&& ! isset($_POST['displayAllColumns'])
|
||||
) {
|
||||
$this->displaySelectionFormAction();
|
||||
} else {
|
||||
$this->doSelectionAction();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get data row action
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function getDataRowAction()
|
||||
{
|
||||
if (! Core::checkSqlQuerySignature($_POST['where_clause'], $_POST['where_clause_sign'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$extra_data = [];
|
||||
$row_info_query = 'SELECT * FROM ' . Util::backquote($_POST['db']) . '.'
|
||||
. Util::backquote($_POST['table']) . ' WHERE ' . $_POST['where_clause'];
|
||||
$result = $this->dbi->query(
|
||||
$row_info_query . ';',
|
||||
DatabaseInterface::CONNECT_USER,
|
||||
DatabaseInterface::QUERY_STORE
|
||||
);
|
||||
$fields_meta = $this->dbi->getFieldsMeta($result);
|
||||
while ($row = $this->dbi->fetchAssoc($result)) {
|
||||
// for bit fields we need to convert them to printable form
|
||||
$i = 0;
|
||||
foreach ($row as $col => $val) {
|
||||
if ($fields_meta[$i]->type === 'bit') {
|
||||
$row[$col] = Util::printableBitValue(
|
||||
(int) $val,
|
||||
(int) $fields_meta[$i]->length
|
||||
);
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
$extra_data['row_info'] = $row;
|
||||
}
|
||||
$this->response->addJSON($extra_data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Do selection action
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function doSelectionAction()
|
||||
{
|
||||
global $PMA_Theme;
|
||||
|
||||
/**
|
||||
* Selection criteria have been submitted -> do the work
|
||||
*/
|
||||
$sql_query = $this->search->buildSqlQuery();
|
||||
|
||||
/**
|
||||
* Add this to ensure following procedures included running correctly.
|
||||
*/
|
||||
$sql = new Sql(
|
||||
$this->dbi,
|
||||
$this->relation,
|
||||
new RelationCleanup($this->dbi, $this->relation),
|
||||
new Operations($this->dbi, $this->relation),
|
||||
new Transformations(),
|
||||
$this->template
|
||||
);
|
||||
|
||||
$this->response->addHTML($sql->executeQueryAndSendQueryResponse(
|
||||
null, // analyzed_sql_results
|
||||
false, // is_gotofile
|
||||
$this->db, // db
|
||||
$this->table, // table
|
||||
null, // find_real_end
|
||||
null, // sql_query_for_bookmark
|
||||
null, // extra_data
|
||||
null, // message_to_show
|
||||
null, // sql_data
|
||||
$GLOBALS['goto'], // goto
|
||||
$PMA_Theme->getImgPath(),
|
||||
null, // disp_query
|
||||
null, // disp_message
|
||||
$sql_query, // sql_query
|
||||
null // complete_query
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display selection form action
|
||||
*/
|
||||
public function displaySelectionFormAction(): void
|
||||
{
|
||||
global $goto, $cfg;
|
||||
|
||||
if (! isset($goto)) {
|
||||
$goto = Util::getScriptNameForOption(
|
||||
$cfg['DefaultTabTable'],
|
||||
'table'
|
||||
);
|
||||
}
|
||||
|
||||
$this->render('table/search/index', [
|
||||
'db' => $this->db,
|
||||
'table' => $this->table,
|
||||
'goto' => $goto,
|
||||
'self' => $this,
|
||||
'geom_column_flag' => $this->geomColumnFlag,
|
||||
'column_names' => $this->columnNames,
|
||||
'column_types' => $this->columnTypes,
|
||||
'column_collations' => $this->columnCollations,
|
||||
'default_sliders_state' => $cfg['InitialSlidersState'],
|
||||
'max_rows' => intval($cfg['MaxRows']),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Range search action
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function rangeSearchAction()
|
||||
{
|
||||
$min_max = $this->getColumnMinMax($_POST['column']);
|
||||
$this->response->addJSON('column_data', $min_max);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds minimum and maximum value of a given column.
|
||||
*
|
||||
* @param string $column Column name
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getColumnMinMax($column)
|
||||
{
|
||||
$sql_query = 'SELECT MIN(' . Util::backquote($column) . ') AS `min`, '
|
||||
. 'MAX(' . Util::backquote($column) . ') AS `max` '
|
||||
. 'FROM ' . Util::backquote($this->db) . '.'
|
||||
. Util::backquote($this->table);
|
||||
|
||||
return $this->dbi->fetchSingleRow($sql_query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a column's type, collation, operators list, and criteria value
|
||||
* to display in table search form
|
||||
*
|
||||
* @param int $search_index Row number in table search form
|
||||
* @param int $column_index Column index in ColumnNames array
|
||||
*
|
||||
* @return array Array containing column's properties
|
||||
*/
|
||||
public function getColumnProperties($search_index, $column_index)
|
||||
{
|
||||
$selected_operator = ($_POST['criteriaColumnOperators'][$search_index] ?? '');
|
||||
$entered_value = ($_POST['criteriaValues'] ?? '');
|
||||
//Gets column's type and collation
|
||||
$type = $this->columnTypes[$column_index];
|
||||
$collation = $this->columnCollations[$column_index];
|
||||
$cleanType = preg_replace('@\(.*@s', '', $type);
|
||||
//Gets column's comparison operators depending on column type
|
||||
$typeOperators = $this->dbi->types->getTypeOperatorsHtml(
|
||||
$cleanType,
|
||||
$this->columnNullFlags[$column_index],
|
||||
$selected_operator
|
||||
);
|
||||
$func = $this->template->render('table/search/column_comparison_operators', [
|
||||
'search_index' => $search_index,
|
||||
'type_operators' => $typeOperators,
|
||||
]);
|
||||
//Gets link to browse foreign data(if any) and criteria inputbox
|
||||
$foreignData = $this->relation->getForeignData(
|
||||
$this->foreigners,
|
||||
$this->columnNames[$column_index],
|
||||
false,
|
||||
'',
|
||||
''
|
||||
);
|
||||
$htmlAttributes = '';
|
||||
if (in_array($cleanType, $this->dbi->types->getIntegerTypes())) {
|
||||
$extractedColumnspec = Util::extractColumnSpec(
|
||||
$this->originalColumnTypes[$column_index]
|
||||
);
|
||||
$is_unsigned = $extractedColumnspec['unsigned'];
|
||||
$minMaxValues = $this->dbi->types->getIntegerRange(
|
||||
$cleanType,
|
||||
! $is_unsigned
|
||||
);
|
||||
$htmlAttributes = 'data-min="' . $minMaxValues[0] . '" '
|
||||
. 'data-max="' . $minMaxValues[1] . '"';
|
||||
}
|
||||
|
||||
$htmlAttributes .= ' onfocus="return '
|
||||
. 'verifyAfterSearchFieldChange(' . $search_index . ', \'#tbl_search_form\')"';
|
||||
|
||||
$value = $this->template->render('table/search/input_box', [
|
||||
'str' => '',
|
||||
'column_type' => (string) $type,
|
||||
'column_data_type' => strtoupper($cleanType),
|
||||
'html_attributes' => $htmlAttributes,
|
||||
'column_id' => 'fieldID_',
|
||||
'in_zoom_search_edit' => false,
|
||||
'foreigners' => $this->foreigners,
|
||||
'column_name' => $this->columnNames[$column_index],
|
||||
'column_name_hash' => md5($this->columnNames[$column_index]),
|
||||
'foreign_data' => $foreignData,
|
||||
'table' => $this->table,
|
||||
'column_index' => $search_index,
|
||||
'foreign_max_limit' => $GLOBALS['cfg']['ForeignKeyMaxLimit'],
|
||||
'criteria_values' => $entered_value,
|
||||
'db' => $this->db,
|
||||
'in_fbs' => true,
|
||||
]);
|
||||
|
||||
return [
|
||||
'type' => $type,
|
||||
'collation' => $collation,
|
||||
'func' => $func,
|
||||
'value' => $value,
|
||||
];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Table;
|
||||
|
||||
use PhpMyAdmin\Config\PageSettings;
|
||||
use PhpMyAdmin\DbTableExists;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\SqlQueryForm;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
use function htmlspecialchars;
|
||||
|
||||
/**
|
||||
* Table SQL executor
|
||||
*/
|
||||
final class SqlController extends AbstractController
|
||||
{
|
||||
/** @var SqlQueryForm */
|
||||
private $sqlQueryForm;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param string $db Database name.
|
||||
* @param string $table Table name.
|
||||
*/
|
||||
public function __construct($response, Template $template, $db, $table, SqlQueryForm $sqlQueryForm)
|
||||
{
|
||||
parent::__construct($response, $template, $db, $table);
|
||||
$this->sqlQueryForm = $sqlQueryForm;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $err_url, $goto, $back, $db, $table, $cfg;
|
||||
|
||||
$this->addScriptFiles([
|
||||
'makegrid.js',
|
||||
'vendor/jquery/jquery.uitablefilter.js',
|
||||
'vendor/stickyfill.min.js',
|
||||
'sql.js',
|
||||
]);
|
||||
|
||||
$pageSettings = new PageSettings('Sql');
|
||||
$this->response->addHTML($pageSettings->getErrorHTML());
|
||||
$this->response->addHTML($pageSettings->getHTML());
|
||||
|
||||
Util::checkParameters(['db', 'table']);
|
||||
|
||||
$url_params = ['db' => $db, 'table' => $table];
|
||||
$err_url = Util::getScriptNameForOption($cfg['DefaultTabTable'], 'table');
|
||||
$err_url .= Url::getCommon($url_params, '&');
|
||||
|
||||
DbTableExists::check();
|
||||
|
||||
/**
|
||||
* After a syntax error, we return to this script
|
||||
* with the typed query in the textarea.
|
||||
*/
|
||||
$goto = Url::getFromRoute('/table/sql');
|
||||
$back = Url::getFromRoute('/table/sql');
|
||||
|
||||
$this->response->addHTML($this->sqlQueryForm->getHtml(
|
||||
$_GET['sql_query'] ?? true,
|
||||
false,
|
||||
isset($_POST['delimiter'])
|
||||
? htmlspecialchars($_POST['delimiter'])
|
||||
: ';'
|
||||
));
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,241 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Table;
|
||||
|
||||
use PhpMyAdmin\DbTableExists;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Tracker;
|
||||
use PhpMyAdmin\Tracking;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
use function array_map;
|
||||
use function define;
|
||||
use function explode;
|
||||
use function htmlspecialchars;
|
||||
use function sprintf;
|
||||
use function strtotime;
|
||||
|
||||
final class TrackingController extends AbstractController
|
||||
{
|
||||
/** @var Tracking */
|
||||
private $tracking;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param string $db Database name.
|
||||
* @param string $table Table name.
|
||||
*/
|
||||
public function __construct(
|
||||
$response,
|
||||
Template $template,
|
||||
$db,
|
||||
$table,
|
||||
Tracking $tracking
|
||||
) {
|
||||
parent::__construct($response, $template, $db, $table);
|
||||
$this->tracking = $tracking;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $text_dir, $url_params, $msg, $PMA_Theme, $err_url;
|
||||
global $data, $entries, $filter_ts_from, $filter_ts_to, $filter_users, $selection_schema;
|
||||
global $selection_data, $selection_both, $sql_result, $db, $table, $cfg;
|
||||
|
||||
$this->addScriptFiles(['vendor/jquery/jquery.tablesorter.js', 'table/tracking.js']);
|
||||
|
||||
define('TABLE_MAY_BE_ABSENT', true);
|
||||
|
||||
Util::checkParameters(['db', 'table']);
|
||||
|
||||
$url_params = ['db' => $db, 'table' => $table];
|
||||
$err_url = Util::getScriptNameForOption($cfg['DefaultTabTable'], 'table');
|
||||
$err_url .= Url::getCommon($url_params, '&');
|
||||
|
||||
DbTableExists::check();
|
||||
|
||||
$activeMessage = '';
|
||||
if (Tracker::isActive()
|
||||
&& Tracker::isTracked($GLOBALS['db'], $GLOBALS['table'])
|
||||
&& ! (isset($_POST['toggle_activation'])
|
||||
&& $_POST['toggle_activation'] === 'deactivate_now')
|
||||
&& ! (isset($_POST['report_export'])
|
||||
&& $_POST['export_type'] === 'sqldumpfile')
|
||||
) {
|
||||
$msg = Message::notice(
|
||||
sprintf(
|
||||
__('Tracking of %s is activated.'),
|
||||
htmlspecialchars($GLOBALS['db'] . '.' . $GLOBALS['table'])
|
||||
)
|
||||
);
|
||||
$activeMessage = $msg->getDisplay();
|
||||
}
|
||||
|
||||
$url_params['goto'] = Url::getFromRoute('/table/tracking');
|
||||
$url_params['back'] = Url::getFromRoute('/table/tracking');
|
||||
|
||||
$data = [];
|
||||
$entries = [];
|
||||
$filter_ts_from = null;
|
||||
$filter_ts_to = null;
|
||||
$filter_users = [];
|
||||
$selection_schema = false;
|
||||
$selection_data = false;
|
||||
$selection_both = false;
|
||||
|
||||
// Init vars for tracking report
|
||||
if (isset($_POST['report']) || isset($_POST['report_export'])) {
|
||||
$data = Tracker::getTrackedData(
|
||||
$GLOBALS['db'],
|
||||
$GLOBALS['table'],
|
||||
$_POST['version']
|
||||
);
|
||||
|
||||
if (! isset($_POST['logtype'])) {
|
||||
$_POST['logtype'] = 'schema_and_data';
|
||||
}
|
||||
if ($_POST['logtype'] === 'schema') {
|
||||
$selection_schema = true;
|
||||
} elseif ($_POST['logtype'] === 'data') {
|
||||
$selection_data = true;
|
||||
} else {
|
||||
$selection_both = true;
|
||||
}
|
||||
if (! isset($_POST['date_from'])) {
|
||||
$_POST['date_from'] = $data['date_from'];
|
||||
}
|
||||
if (! isset($_POST['date_to'])) {
|
||||
$_POST['date_to'] = $data['date_to'];
|
||||
}
|
||||
if (! isset($_POST['users'])) {
|
||||
$_POST['users'] = '*';
|
||||
}
|
||||
$filter_ts_from = strtotime($_POST['date_from']);
|
||||
$filter_ts_to = strtotime($_POST['date_to']);
|
||||
$filter_users = array_map('trim', explode(',', $_POST['users']));
|
||||
}
|
||||
|
||||
// Prepare export
|
||||
if (isset($_POST['report_export'])) {
|
||||
$entries = $this->tracking->getEntries(
|
||||
$data,
|
||||
(int) $filter_ts_from,
|
||||
(int) $filter_ts_to,
|
||||
$filter_users
|
||||
);
|
||||
}
|
||||
|
||||
// Export as file download
|
||||
if (isset($_POST['report_export'])
|
||||
&& $_POST['export_type'] === 'sqldumpfile'
|
||||
) {
|
||||
$this->tracking->exportAsFileDownload($entries);
|
||||
}
|
||||
|
||||
$actionMessage = '';
|
||||
if (isset($_POST['submit_mult'])) {
|
||||
if (! empty($_POST['selected_versions'])) {
|
||||
if ($_POST['submit_mult'] === 'delete_version') {
|
||||
foreach ($_POST['selected_versions'] as $version) {
|
||||
$this->tracking->deleteTrackingVersion($version);
|
||||
}
|
||||
$actionMessage = Message::success(
|
||||
__('Tracking versions deleted successfully.')
|
||||
)->getDisplay();
|
||||
}
|
||||
} else {
|
||||
$actionMessage = Message::notice(
|
||||
__('No versions selected.')
|
||||
)->getDisplay();
|
||||
}
|
||||
}
|
||||
|
||||
$deleteVersion = '';
|
||||
if (isset($_POST['submit_delete_version'])) {
|
||||
$deleteVersion = $this->tracking->deleteTrackingVersion($_POST['version']);
|
||||
}
|
||||
|
||||
$createVersion = '';
|
||||
if (isset($_POST['submit_create_version'])) {
|
||||
$createVersion = $this->tracking->createTrackingVersion();
|
||||
}
|
||||
|
||||
$deactivateTracking = '';
|
||||
if (isset($_POST['toggle_activation'])
|
||||
&& $_POST['toggle_activation'] === 'deactivate_now'
|
||||
) {
|
||||
$deactivateTracking = $this->tracking->changeTracking('deactivate');
|
||||
}
|
||||
|
||||
$activateTracking = '';
|
||||
if (isset($_POST['toggle_activation'])
|
||||
&& $_POST['toggle_activation'] === 'activate_now'
|
||||
) {
|
||||
$activateTracking = $this->tracking->changeTracking('activate');
|
||||
}
|
||||
|
||||
// Export as SQL execution
|
||||
$message = '';
|
||||
if (isset($_POST['report_export']) && $_POST['export_type'] === 'execution') {
|
||||
$sql_result = $this->tracking->exportAsSqlExecution($entries);
|
||||
$msg = Message::success(__('SQL statements executed.'));
|
||||
$message = $msg->getDisplay();
|
||||
}
|
||||
|
||||
$sqlDump = '';
|
||||
if (isset($_POST['report_export']) && $_POST['export_type'] === 'sqldump') {
|
||||
$sqlDump = $this->tracking->exportAsSqlDump($entries);
|
||||
}
|
||||
|
||||
$schemaSnapshot = '';
|
||||
if (isset($_POST['snapshot'])) {
|
||||
$schemaSnapshot = $this->tracking->getHtmlForSchemaSnapshot($url_params);
|
||||
}
|
||||
|
||||
$trackingReportRows = '';
|
||||
if (isset($_POST['report'])
|
||||
&& (isset($_POST['delete_ddlog']) || isset($_POST['delete_dmlog']))
|
||||
) {
|
||||
$trackingReportRows = $this->tracking->deleteTrackingReportRows($data);
|
||||
}
|
||||
|
||||
$trackingReport = '';
|
||||
if (isset($_POST['report']) || isset($_POST['report_export'])) {
|
||||
$trackingReport = $this->tracking->getHtmlForTrackingReport(
|
||||
$data,
|
||||
$url_params,
|
||||
$selection_schema,
|
||||
$selection_data,
|
||||
$selection_both,
|
||||
(int) $filter_ts_to,
|
||||
(int) $filter_ts_from,
|
||||
$filter_users
|
||||
);
|
||||
}
|
||||
|
||||
$main = $this->tracking->getHtmlForMainPage(
|
||||
$url_params,
|
||||
$PMA_Theme->getImgPath(),
|
||||
$text_dir
|
||||
);
|
||||
|
||||
$this->render('table/tracking/index', [
|
||||
'active_message' => $activeMessage,
|
||||
'action_message' => $actionMessage,
|
||||
'delete_version' => $deleteVersion,
|
||||
'create_version' => $createVersion,
|
||||
'deactivate_tracking' => $deactivateTracking,
|
||||
'activate_tracking' => $activateTracking,
|
||||
'message' => $message,
|
||||
'sql_dump' => $sqlDump,
|
||||
'schema_snapshot' => $schemaSnapshot,
|
||||
'tracking_report_rows' => $trackingReportRows,
|
||||
'tracking_report' => $trackingReport,
|
||||
'main' => $main,
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Table;
|
||||
|
||||
use PhpMyAdmin\Database\Triggers;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\DbTableExists;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
use function in_array;
|
||||
use function strlen;
|
||||
|
||||
/**
|
||||
* Triggers management.
|
||||
*/
|
||||
class TriggersController extends AbstractController
|
||||
{
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param string $db Database name.
|
||||
* @param string $table Table name.
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, $db, $table, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template, $db, $table);
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $db, $table, $tables, $num_tables, $total_num_tables, $sub_part;
|
||||
global $tooltip_truename, $tooltip_aliasname, $pos;
|
||||
global $errors, $url_params, $err_url, $cfg;
|
||||
|
||||
if (! $this->response->isAjax()) {
|
||||
/**
|
||||
* Displays the header and tabs
|
||||
*/
|
||||
if (! empty($table) && in_array($table, $this->dbi->getTables($db))) {
|
||||
Util::checkParameters(['db', 'table']);
|
||||
|
||||
$url_params = ['db' => $db, 'table' => $table];
|
||||
$err_url = Util::getScriptNameForOption($cfg['DefaultTabTable'], 'table');
|
||||
$err_url .= Url::getCommon($url_params, '&');
|
||||
|
||||
DbTableExists::check();
|
||||
} else {
|
||||
$table = '';
|
||||
|
||||
Util::checkParameters(['db']);
|
||||
|
||||
$err_url = Util::getScriptNameForOption($cfg['DefaultTabDatabase'], 'database');
|
||||
$err_url .= Url::getCommon(['db' => $db], '&');
|
||||
|
||||
if (! $this->hasDatabase()) {
|
||||
return;
|
||||
}
|
||||
|
||||
[
|
||||
$tables,
|
||||
$num_tables,
|
||||
$total_num_tables,
|
||||
$sub_part,,,
|
||||
$tooltip_truename,
|
||||
$tooltip_aliasname,
|
||||
$pos,
|
||||
] = Util::getDbInfo($db, $sub_part ?? '');
|
||||
}
|
||||
} elseif (strlen($db) > 0) {
|
||||
$this->dbi->selectDb($db);
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep a list of errors that occurred while
|
||||
* processing an 'Add' or 'Edit' operation.
|
||||
*/
|
||||
$errors = [];
|
||||
|
||||
$triggers = new Triggers($this->dbi, $this->template, $this->response);
|
||||
$triggers->main();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,501 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers\Table;
|
||||
|
||||
use PhpMyAdmin\Core;
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\DbTableExists;
|
||||
use PhpMyAdmin\Relation;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Table\Search;
|
||||
use PhpMyAdmin\Template;
|
||||
use PhpMyAdmin\Url;
|
||||
use PhpMyAdmin\Util;
|
||||
use function array_search;
|
||||
use function count;
|
||||
use function htmlspecialchars;
|
||||
use function in_array;
|
||||
use function intval;
|
||||
use function is_numeric;
|
||||
use function json_encode;
|
||||
use function mb_strtolower;
|
||||
use function md5;
|
||||
use function preg_match;
|
||||
use function preg_replace;
|
||||
use function str_ireplace;
|
||||
use function str_replace;
|
||||
use function strncasecmp;
|
||||
use function strtoupper;
|
||||
|
||||
/**
|
||||
* Handles table zoom search tab.
|
||||
*
|
||||
* Display table zoom search form, create SQL queries from form data.
|
||||
*/
|
||||
class ZoomSearchController extends AbstractController
|
||||
{
|
||||
/** @var Search */
|
||||
private $search;
|
||||
|
||||
/** @var Relation */
|
||||
private $relation;
|
||||
|
||||
/** @var array */
|
||||
private $columnNames;
|
||||
|
||||
/** @var array */
|
||||
private $columnTypes;
|
||||
|
||||
/** @var array */
|
||||
private $originalColumnTypes;
|
||||
|
||||
/** @var array */
|
||||
private $columnCollations;
|
||||
|
||||
/** @var array */
|
||||
private $columnNullFlags;
|
||||
|
||||
/** @var bool Whether a geometry column is present */
|
||||
private $geomColumnFlag;
|
||||
|
||||
/** @var array Foreign keys */
|
||||
private $foreigners;
|
||||
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param string $db Database name.
|
||||
* @param string $table Table name.
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, $db, $table, Search $search, Relation $relation, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template, $db, $table);
|
||||
$this->search = $search;
|
||||
$this->relation = $relation;
|
||||
$this->dbi = $dbi;
|
||||
|
||||
$this->columnNames = [];
|
||||
$this->columnTypes = [];
|
||||
$this->originalColumnTypes = [];
|
||||
$this->columnCollations = [];
|
||||
$this->columnNullFlags = [];
|
||||
$this->geomColumnFlag = false;
|
||||
$this->foreigners = [];
|
||||
$this->loadTableInfo();
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
global $goto, $db, $table, $url_params, $cfg, $err_url;
|
||||
|
||||
Util::checkParameters(['db', 'table']);
|
||||
|
||||
$url_params = ['db' => $db, 'table' => $table];
|
||||
$err_url = Util::getScriptNameForOption($cfg['DefaultTabTable'], 'table');
|
||||
$err_url .= Url::getCommon($url_params, '&');
|
||||
|
||||
DbTableExists::check();
|
||||
|
||||
$this->addScriptFiles([
|
||||
'vendor/stickyfill.min.js',
|
||||
'makegrid.js',
|
||||
'sql.js',
|
||||
'vendor/jqplot/jquery.jqplot.js',
|
||||
'vendor/jqplot/plugins/jqplot.canvasTextRenderer.js',
|
||||
'vendor/jqplot/plugins/jqplot.canvasAxisLabelRenderer.js',
|
||||
'vendor/jqplot/plugins/jqplot.dateAxisRenderer.js',
|
||||
'vendor/jqplot/plugins/jqplot.highlighter.js',
|
||||
'vendor/jqplot/plugins/jqplot.cursor.js',
|
||||
'table/zoom_plot_jqplot.js',
|
||||
'table/change.js',
|
||||
]);
|
||||
|
||||
/**
|
||||
* Handle AJAX request for data row on point select
|
||||
*/
|
||||
if (isset($_POST['get_data_row'])
|
||||
&& $_POST['get_data_row'] == true
|
||||
) {
|
||||
$this->getDataRowAction();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle AJAX request for changing field information
|
||||
* (value,collation,operators,field values) in input form
|
||||
*/
|
||||
if (isset($_POST['change_tbl_info'])
|
||||
&& $_POST['change_tbl_info'] == true
|
||||
) {
|
||||
$this->changeTableInfoAction();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
//Set default datalabel if not selected
|
||||
if (! isset($_POST['zoom_submit']) || $_POST['dataLabel'] == '') {
|
||||
$dataLabel = $this->relation->getDisplayField($this->db, $this->table);
|
||||
} else {
|
||||
$dataLabel = $_POST['dataLabel'];
|
||||
}
|
||||
|
||||
// Displays the zoom search form
|
||||
$this->displaySelectionFormAction($dataLabel);
|
||||
|
||||
/**
|
||||
* Handle the input criteria and generate the query result
|
||||
* Form for displaying query results
|
||||
*/
|
||||
if (! isset($_POST['zoom_submit'])
|
||||
|| $_POST['criteriaColumnNames'][0] === 'pma_null'
|
||||
|| $_POST['criteriaColumnNames'][1] === 'pma_null'
|
||||
|| $_POST['criteriaColumnNames'][0] == $_POST['criteriaColumnNames'][1]
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! isset($goto)) {
|
||||
$goto = Util::getScriptNameForOption(
|
||||
$GLOBALS['cfg']['DefaultTabTable'],
|
||||
'table'
|
||||
);
|
||||
}
|
||||
$this->zoomSubmitAction($dataLabel, $goto);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the columns of a table along with their types, collations
|
||||
* and whether null or not.
|
||||
*/
|
||||
private function loadTableInfo(): void
|
||||
{
|
||||
// Gets the list and number of columns
|
||||
$columns = $this->dbi->getColumns(
|
||||
$this->db,
|
||||
$this->table,
|
||||
null,
|
||||
true
|
||||
);
|
||||
// Get details about the geometry functions
|
||||
$geom_types = Util::getGISDatatypes();
|
||||
|
||||
foreach ($columns as $row) {
|
||||
// set column name
|
||||
$this->columnNames[] = $row['Field'];
|
||||
|
||||
$type = (string) $row['Type'];
|
||||
// before any replacement
|
||||
$this->originalColumnTypes[] = mb_strtolower($type);
|
||||
// check whether table contains geometric columns
|
||||
if (in_array($type, $geom_types)) {
|
||||
$this->geomColumnFlag = true;
|
||||
}
|
||||
// reformat mysql query output
|
||||
if (strncasecmp($type, 'set', 3) == 0
|
||||
|| strncasecmp($type, 'enum', 4) == 0
|
||||
) {
|
||||
$type = str_replace(',', ', ', $type);
|
||||
} else {
|
||||
// strip the "BINARY" attribute, except if we find "BINARY(" because
|
||||
// this would be a BINARY or VARBINARY column type
|
||||
if (! preg_match('@BINARY[\(]@i', $type)) {
|
||||
$type = str_ireplace('BINARY', '', $type);
|
||||
}
|
||||
$type = str_ireplace('ZEROFILL', '', $type);
|
||||
$type = str_ireplace('UNSIGNED', '', $type);
|
||||
$type = mb_strtolower($type);
|
||||
}
|
||||
if (empty($type)) {
|
||||
$type = ' ';
|
||||
}
|
||||
$this->columnTypes[] = $type;
|
||||
$this->columnNullFlags[] = $row['Null'];
|
||||
$this->columnCollations[]
|
||||
= ! empty($row['Collation']) && $row['Collation'] !== 'NULL'
|
||||
? $row['Collation']
|
||||
: '';
|
||||
}
|
||||
|
||||
// Retrieve foreign keys
|
||||
$this->foreigners = $this->relation->getForeigners($this->db, $this->table);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display selection form action
|
||||
*
|
||||
* @param string $dataLabel Data label
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function displaySelectionFormAction($dataLabel = null)
|
||||
{
|
||||
global $goto;
|
||||
|
||||
if (! isset($goto)) {
|
||||
$goto = Util::getScriptNameForOption(
|
||||
$GLOBALS['cfg']['DefaultTabTable'],
|
||||
'table'
|
||||
);
|
||||
}
|
||||
|
||||
$column_names = $this->columnNames;
|
||||
$criteria_column_names = $_POST['criteriaColumnNames'] ?? null;
|
||||
$keys = [];
|
||||
for ($i = 0; $i < 4; $i++) {
|
||||
if (! isset($criteria_column_names[$i])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($criteria_column_names[$i] === 'pma_null') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$keys[$criteria_column_names[$i]] = array_search($criteria_column_names[$i], $column_names);
|
||||
}
|
||||
|
||||
$this->render('table/zoom_search/index', [
|
||||
'db' => $this->db,
|
||||
'table' => $this->table,
|
||||
'goto' => $goto,
|
||||
'self' => $this,
|
||||
'geom_column_flag' => $this->geomColumnFlag,
|
||||
'column_names' => $column_names,
|
||||
'data_label' => $dataLabel,
|
||||
'keys' => $keys,
|
||||
'criteria_column_names' => $criteria_column_names,
|
||||
'criteria_column_types' => $_POST['criteriaColumnTypes'] ?? null,
|
||||
'max_plot_limit' => ! empty($_POST['maxPlotLimit'])
|
||||
? intval($_POST['maxPlotLimit'])
|
||||
: intval($GLOBALS['cfg']['maxRowPlotLimit']),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get data row action
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function getDataRowAction()
|
||||
{
|
||||
if (! Core::checkSqlQuerySignature($_POST['where_clause'], $_POST['where_clause_sign'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$extra_data = [];
|
||||
$row_info_query = 'SELECT * FROM ' . Util::backquote($_POST['db']) . '.'
|
||||
. Util::backquote($_POST['table']) . ' WHERE ' . $_POST['where_clause'];
|
||||
$result = $this->dbi->query(
|
||||
$row_info_query . ';',
|
||||
DatabaseInterface::CONNECT_USER,
|
||||
DatabaseInterface::QUERY_STORE
|
||||
);
|
||||
$fields_meta = $this->dbi->getFieldsMeta($result);
|
||||
while ($row = $this->dbi->fetchAssoc($result)) {
|
||||
// for bit fields we need to convert them to printable form
|
||||
$i = 0;
|
||||
foreach ($row as $col => $val) {
|
||||
if ($fields_meta[$i]->type === 'bit') {
|
||||
$row[$col] = Util::printableBitValue(
|
||||
(int) $val,
|
||||
(int) $fields_meta[$i]->length
|
||||
);
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
$extra_data['row_info'] = $row;
|
||||
}
|
||||
$this->response->addJSON($extra_data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change table info action
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function changeTableInfoAction()
|
||||
{
|
||||
$field = $_POST['field'];
|
||||
if ($field === 'pma_null') {
|
||||
$this->response->addJSON('field_type', '');
|
||||
$this->response->addJSON('field_collation', '');
|
||||
$this->response->addJSON('field_operators', '');
|
||||
$this->response->addJSON('field_value', '');
|
||||
|
||||
return;
|
||||
}
|
||||
$key = array_search($field, $this->columnNames);
|
||||
$search_index
|
||||
= (isset($_POST['it']) && is_numeric($_POST['it'])
|
||||
? intval($_POST['it']) : 0);
|
||||
|
||||
$properties = $this->getColumnProperties($search_index, $key);
|
||||
$this->response->addJSON(
|
||||
'field_type',
|
||||
htmlspecialchars($properties['type'])
|
||||
);
|
||||
$this->response->addJSON('field_collation', $properties['collation']);
|
||||
$this->response->addJSON('field_operators', $properties['func']);
|
||||
$this->response->addJSON('field_value', $properties['value']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Zoom submit action
|
||||
*
|
||||
* @param string $dataLabel Data label
|
||||
* @param string $goto Goto
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function zoomSubmitAction($dataLabel, $goto)
|
||||
{
|
||||
//Query generation part
|
||||
$sql_query = $this->search->buildSqlQuery();
|
||||
$sql_query .= ' LIMIT ' . $_POST['maxPlotLimit'];
|
||||
|
||||
//Query execution part
|
||||
$result = $this->dbi->query(
|
||||
$sql_query . ';',
|
||||
DatabaseInterface::CONNECT_USER,
|
||||
DatabaseInterface::QUERY_STORE
|
||||
);
|
||||
$fields_meta = $this->dbi->getFieldsMeta($result);
|
||||
$data = [];
|
||||
while ($row = $this->dbi->fetchAssoc($result)) {
|
||||
//Need a row with indexes as 0,1,2 for the getUniqueCondition
|
||||
// hence using a temporary array
|
||||
$tmpRow = [];
|
||||
foreach ($row as $val) {
|
||||
$tmpRow[] = $val;
|
||||
}
|
||||
//Get unique condition on each row (will be needed for row update)
|
||||
$uniqueCondition = Util::getUniqueCondition(
|
||||
$result,
|
||||
count($this->columnNames),
|
||||
$fields_meta,
|
||||
$tmpRow,
|
||||
true
|
||||
);
|
||||
//Append it to row array as where_clause
|
||||
$row['where_clause'] = $uniqueCondition[0];
|
||||
$row['where_clause_sign'] = Core::signSqlQuery($uniqueCondition[0]);
|
||||
|
||||
$tmpData = [
|
||||
$_POST['criteriaColumnNames'][0] =>
|
||||
$row[$_POST['criteriaColumnNames'][0]],
|
||||
$_POST['criteriaColumnNames'][1] =>
|
||||
$row[$_POST['criteriaColumnNames'][1]],
|
||||
'where_clause' => $uniqueCondition[0],
|
||||
'where_clause_sign' => Core::signSqlQuery($uniqueCondition[0]),
|
||||
];
|
||||
$tmpData[$dataLabel] = $dataLabel ? $row[$dataLabel] : '';
|
||||
$data[] = $tmpData;
|
||||
}
|
||||
unset($tmpData);
|
||||
|
||||
$column_names_hashes = [];
|
||||
|
||||
foreach ($this->columnNames as $columnName) {
|
||||
$column_names_hashes[$columnName] = md5($columnName);
|
||||
}
|
||||
|
||||
$this->render('table/zoom_search/result_form', [
|
||||
'db' => $this->db,
|
||||
'table' => $this->table,
|
||||
'column_names' => $this->columnNames,
|
||||
'column_names_hashes' => $column_names_hashes,
|
||||
'foreigners' => $this->foreigners,
|
||||
'column_null_flags' => $this->columnNullFlags,
|
||||
'column_types' => $this->columnTypes,
|
||||
'goto' => $goto,
|
||||
'data' => $data,
|
||||
'data_json' => json_encode($data),
|
||||
'zoom_submit' => isset($_POST['zoom_submit']),
|
||||
'foreign_max_limit' => $GLOBALS['cfg']['ForeignKeyMaxLimit'],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a column's type, collation, operators list, and criteria value
|
||||
* to display in table search form
|
||||
*
|
||||
* @param int $search_index Row number in table search form
|
||||
* @param int $column_index Column index in ColumnNames array
|
||||
*
|
||||
* @return array Array containing column's properties
|
||||
*/
|
||||
public function getColumnProperties($search_index, $column_index)
|
||||
{
|
||||
$selected_operator = ($_POST['criteriaColumnOperators'][$search_index] ?? '');
|
||||
$entered_value = ($_POST['criteriaValues'] ?? '');
|
||||
//Gets column's type and collation
|
||||
$type = $this->columnTypes[$column_index];
|
||||
$collation = $this->columnCollations[$column_index];
|
||||
$cleanType = preg_replace('@\(.*@s', '', $type);
|
||||
//Gets column's comparison operators depending on column type
|
||||
$typeOperators = $this->dbi->types->getTypeOperatorsHtml(
|
||||
$cleanType,
|
||||
$this->columnNullFlags[$column_index],
|
||||
$selected_operator
|
||||
);
|
||||
$func = $this->template->render('table/search/column_comparison_operators', [
|
||||
'search_index' => $search_index,
|
||||
'type_operators' => $typeOperators,
|
||||
]);
|
||||
//Gets link to browse foreign data(if any) and criteria inputbox
|
||||
$foreignData = $this->relation->getForeignData(
|
||||
$this->foreigners,
|
||||
$this->columnNames[$column_index],
|
||||
false,
|
||||
'',
|
||||
''
|
||||
);
|
||||
$htmlAttributes = '';
|
||||
if (in_array($cleanType, $this->dbi->types->getIntegerTypes())) {
|
||||
$extractedColumnspec = Util::extractColumnSpec(
|
||||
$this->originalColumnTypes[$column_index]
|
||||
);
|
||||
$is_unsigned = $extractedColumnspec['unsigned'];
|
||||
$minMaxValues = $this->dbi->types->getIntegerRange(
|
||||
$cleanType,
|
||||
! $is_unsigned
|
||||
);
|
||||
$htmlAttributes = 'data-min="' . $minMaxValues[0] . '" '
|
||||
. 'data-max="' . $minMaxValues[1] . '"';
|
||||
}
|
||||
|
||||
$htmlAttributes .= ' onfocus="return '
|
||||
. 'verifyAfterSearchFieldChange(' . $search_index . ', \'#zoom_search_form\')"';
|
||||
|
||||
$value = $this->template->render('table/search/input_box', [
|
||||
'str' => '',
|
||||
'column_type' => (string) $type,
|
||||
'column_data_type' => strtoupper($cleanType),
|
||||
'html_attributes' => $htmlAttributes,
|
||||
'column_id' => 'fieldID_',
|
||||
'in_zoom_search_edit' => false,
|
||||
'foreigners' => $this->foreigners,
|
||||
'column_name' => $this->columnNames[$column_index],
|
||||
'column_name_hash' => md5($this->columnNames[$column_index]),
|
||||
'foreign_data' => $foreignData,
|
||||
'table' => $this->table,
|
||||
'column_index' => $search_index,
|
||||
'foreign_max_limit' => $GLOBALS['cfg']['ForeignKeyMaxLimit'],
|
||||
'criteria_values' => $entered_value,
|
||||
'db' => $this->db,
|
||||
'in_fbs' => true,
|
||||
]);
|
||||
|
||||
return [
|
||||
'type' => $type,
|
||||
'collation' => $collation,
|
||||
'func' => $func,
|
||||
'value' => $value,
|
||||
];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpMyAdmin\Controllers;
|
||||
|
||||
use PhpMyAdmin\DatabaseInterface;
|
||||
use PhpMyAdmin\Message;
|
||||
use PhpMyAdmin\Response;
|
||||
use PhpMyAdmin\Template;
|
||||
|
||||
final class TableController extends AbstractController
|
||||
{
|
||||
/** @var DatabaseInterface */
|
||||
private $dbi;
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param DatabaseInterface $dbi
|
||||
*/
|
||||
public function __construct($response, Template $template, $dbi)
|
||||
{
|
||||
parent::__construct($response, $template);
|
||||
$this->dbi = $dbi;
|
||||
}
|
||||
|
||||
public function all(): void
|
||||
{
|
||||
if (! isset($_POST['db'])) {
|
||||
$this->response->setRequestStatus(false);
|
||||
$this->response->addJSON(['message' => Message::error()]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->response->addJSON(['tables' => $this->dbi->getTables($_POST['db'])]);
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue