Update website

This commit is contained in:
Guilhem Lavaux 2025-03-24 09:27:39 +01:00
parent a0b0d3dae7
commit ae7ef6ad45
3151 changed files with 566766 additions and 48 deletions

View file

@ -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;
}
}

View file

@ -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'] ?? ''
));
}
}

View file

@ -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}) (.+[^ ]) +&lt;(.*@.*)&gt;/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),
]);
}
}

View file

@ -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));
}
}

View file

@ -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'])]);
}
}

View file

@ -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]);
}
}

View file

@ -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;
}
}

View file

@ -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
);
}
}

View file

@ -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,
]);
}
}

View file

@ -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>');
}
}

View file

@ -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']),
]);
}
}

View file

@ -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();
}
}

View file

@ -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),
]);
}
}

View file

@ -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]);
}
}

View file

@ -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);
}
}

View file

@ -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,
]);
}
}

View file

@ -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(),
]);
}
}

View file

@ -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),
]);
}
}

View file

@ -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());
}
}

View file

@ -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)]);
}
}

View file

@ -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'])
: ';'
));
}
}

View file

@ -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

View file

@ -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);
}
}

View file

@ -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();
}
}

View file

@ -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]);
}
}

View file

@ -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();
}
}
}

View file

@ -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');
}
}

View file

@ -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);
}
}

View file

@ -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]);
}
}

View file

@ -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
);
}
}

View file

@ -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');
}
}

View file

@ -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']);
}
}
}

View file

@ -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'),
];
}
}

View file

@ -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/'
);
}
}
}

View file

@ -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));
}
}

View file

@ -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();
}
}

View file

@ -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());
}
}

View file

@ -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());
}
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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(),
]);
}
}

View file

@ -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']);
}
}

View file

@ -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;
}
}

View file

@ -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]);
}
}

View file

@ -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,
],
];
}
}

View file

@ -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,
]);
}
}

View file

@ -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,
]));
}
}

View file

@ -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),
]);
}
}

View file

@ -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,
]);
}
}

View file

@ -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>');
}
}

View file

@ -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 ?? '',
]);
}
}

View file

@ -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());
}
}

View file

@ -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;
}
}

View file

@ -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]);
}
}

View file

@ -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'] ?? ''
),
]);
}
}

View file

@ -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,
]);
}
}

View file

@ -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 ?? [],
]);
}
}

View file

@ -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) . '%',
],
];
}
}

View file

@ -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.'
),
];
}
}

View file

@ -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>');
}
}

View file

@ -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('_', '&nbsp;', $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,
];
}
}

View file

@ -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;
}
}

View file

@ -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,
]);
}
}

View file

@ -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,
]);
}
}

View file

@ -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,
]);
}
}

View file

@ -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);
}
}

View 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 .= '&amp;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);
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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();
}
}

View file

@ -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));
}
}

View file

@ -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);
}
}

View file

@ -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(),
]);
}
}

View file

@ -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();
}
}

View file

@ -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 = '&nbsp;';
}
$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;
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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),
]);
}
}

View file

@ -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);
}
}
}

View file

@ -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,
]);
}
}

View file

@ -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,
]);
}
}

View file

@ -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,
]);
}
}

View file

@ -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,
]);
}
}

View file

@ -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();
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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 = '&nbsp;';
}
$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,
];
}
}

View file

@ -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

View file

@ -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,
]);
}
}

View file

@ -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();
}
}

View file

@ -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 = '&nbsp;';
}
$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,
];
}
}

View file

@ -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