Remove QBE

Signed-off-by: Kamil Tekiela <tekiela246@gmail.com>
This commit is contained in:
Kamil Tekiela 2023-02-07 18:24:43 +00:00
parent d6122696b5
commit 231ec1ba8b
28 changed files with 1 additions and 3534 deletions

View File

@ -65,7 +65,6 @@ jobs:
- "Database/Events"
- "Database/Operations"
- "Database/Procedures"
- "Database/QueryByExample"
- "Database/Structure"
- "Database/Triggers"
- "Export"

View File

@ -30,8 +30,6 @@ Currently phpMyAdmin can:
* administer multiple servers
* add, edit, and remove MySQL user accounts and privileges
* check referential integrity in MyISAM tables
* using Query-by-example (QBE), create complex queries automatically
connecting required tables
* create :term:`PDF` graphics of your
database layout
* search globally in a database or a subset of it

View File

@ -1,87 +0,0 @@
import $ from 'jquery';
import { AJAX } from '../modules/ajax.ts';
import { Functions } from '../modules/functions.ts';
/**
* @fileoverview function used in QBE for DB
* @name Database Operations
*
* @requires jQueryUI
*/
/**
* Ajax event handlers here for /database/qbe
*
* Actions Ajaxified here:
* Select saved search
*/
/**
* Unbind all event handlers before tearing down a page
*/
AJAX.registerTeardown('database/qbe.js', function () {
$(document).off('change', 'select[name^=criteriaColumn]');
$(document).off('change', '#searchId');
$(document).off('click', '#saveSearch');
$(document).off('click', '#updateSearch');
$(document).off('click', '#deleteSearch');
});
AJAX.registerOnload('database/qbe.js', function () {
Functions.getSqlEditor($('#textSqlquery'), {}, 'none');
$('#tblQbe').width($('#tblQbe').parent().width());
$('#tblQbeFooters').width($('#tblQbeFooters').parent().width());
$('#tblQbe').on('resize', function () {
var newWidthTblQbe = $('#textSqlquery').next().width();
$('#tblQbe').width(newWidthTblQbe);
$('#tblQbeFooters').width(newWidthTblQbe);
});
/**
* Ajax handler to check the corresponding 'show' checkbox when column is selected
*/
$(document).on('change', 'select[name^=criteriaColumn]', function () {
if ($(this).val()) {
var index = (/\d+/).exec($(this).attr('name'));
$('input[name=criteriaShow\\[' + index + '\\]]').prop('checked', true);
}
});
/**
* Ajax event handlers for 'Select saved search'
*/
$(document).on('change', '#searchId', function () {
$('#action').val('load');
$('#formQBE').trigger('submit');
});
/**
* Ajax event handlers for 'Create bookmark'
*/
$(document).on('click', '#saveSearch', function () {
$('#action').val('create');
});
/**
* Ajax event handlers for 'Update bookmark'
*/
$(document).on('click', '#updateSearch', function () {
$('#action').val('update');
});
/**
* Ajax event handlers for 'Delete bookmark'
*/
$(document).on('click', '#deleteSearch', function () {
var question = window.sprintf(window.Messages.strConfirmDeleteQBESearch, $('#searchId').find('option:selected').text());
if (! window.confirm(question)) {
return false;
}
$('#action').val('delete');
});
var windowwidth = $(window).width();
$('.jsresponsive').css('max-width', (windowwidth - 35) + 'px');
});

View File

@ -362,9 +362,6 @@ class Descriptions
'Limits number of table preferences which are stored in database, the oldest '
. 'records are automatically removed.',
),
'Servers_savedsearches_desc' => __(
'Leave blank for no QBE saved searches support, suggested: [kbd]pma__savedsearches[/kbd].',
),
'Servers_export_templates_desc' => __(
'Leave blank for no export template support, suggested: [kbd]pma__export_templates[/kbd].',
),
@ -884,7 +881,6 @@ class Descriptions
'Servers_host_name' => __('Server hostname'),
'Servers_LogoutURL_name' => __('Logout URL'),
'Servers_MaxTableUiprefs_name' => __('Maximal number of table preferences to store'),
'Servers_savedsearches_name' => __('QBE saved searches table'),
'Servers_export_templates_name' => __('Export templates table'),
'Servers_central_columns_name' => __('Central columns table'),
'Servers_only_db_name' => __('Show only listed databases'),

View File

@ -1,191 +0,0 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\Controllers\Database;
use PhpMyAdmin\ConfigStorage\Relation;
use PhpMyAdmin\ConfigStorage\RelationCleanup;
use PhpMyAdmin\Controllers\AbstractController;
use PhpMyAdmin\Database\Qbe;
use PhpMyAdmin\DatabaseInterface;
use PhpMyAdmin\Exceptions\SavedSearchesException;
use PhpMyAdmin\Http\ServerRequest;
use PhpMyAdmin\Message;
use PhpMyAdmin\Operations;
use PhpMyAdmin\ResponseRenderer;
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
{
public function __construct(
ResponseRenderer $response,
Template $template,
private Relation $relation,
private DatabaseInterface $dbi,
) {
parent::__construct($response, $template);
}
public function __invoke(ServerRequest $request): void
{
$GLOBALS['goto'] ??= null;
$GLOBALS['urlParams'] ??= null;
$GLOBALS['errorUrl'] ??= null;
$savedQbeSearchesFeature = $this->relation->getRelationParameters()->savedQueryByExampleSearchesFeature;
$savedSearchList = [];
$savedSearch = null;
$this->addScriptFiles(['database/qbe.js']);
if ($savedQbeSearchesFeature !== null) {
//Get saved search list.
$savedSearch = new SavedSearches();
$savedSearch->setUsername($GLOBALS['cfg']['Server']['user'])
->setDbname($GLOBALS['db']);
$searchId = $request->getParsedBodyParam('searchId');
if (! empty($searchId)) {
$savedSearch->setId($searchId);
}
//Action field is sent.
if ($request->hasBodyParam('action')) {
$savedSearch->setSearchName($request->getParsedBodyParam('searchName'));
$action = $request->getParsedBodyParam('action');
if ($action === 'create') {
try {
$savedSearch->setId(null)
->setCriterias($request->getParsedBody())
->save($savedQbeSearchesFeature);
} catch (SavedSearchesException $exception) {
$this->response->setRequestStatus(false);
$this->response->addJSON('fieldWithError', 'searchName');
$this->response->addJSON('message', Message::error($exception->getMessage())->getDisplay());
return;
}
} elseif ($action === 'update') {
try {
$savedSearch->setCriterias($request->getParsedBody())
->save($savedQbeSearchesFeature);
} catch (SavedSearchesException $exception) {
$this->response->setRequestStatus(false);
$this->response->addJSON('fieldWithError', 'searchName');
$this->response->addJSON('message', Message::error($exception->getMessage())->getDisplay());
return;
}
} elseif ($action === 'delete') {
try {
$savedSearch->delete($savedQbeSearchesFeature);
} catch (SavedSearchesException $exception) {
$this->response->setRequestStatus(false);
$this->response->addJSON('fieldWithError', 'searchId');
$this->response->addJSON('message', Message::error($exception->getMessage())->getDisplay());
return;
}
//After deletion, reset search.
$savedSearch = new SavedSearches();
$savedSearch->setUsername($GLOBALS['cfg']['Server']['user'])
->setDbname($GLOBALS['db']);
$_POST = [];
} elseif ($action === 'load') {
if (empty($searchId)) {
//when not loading a search, reset the object.
$savedSearch = new SavedSearches();
$savedSearch->setUsername($GLOBALS['cfg']['Server']['user'])
->setDbname($GLOBALS['db']);
$_POST = [];
} else {
try {
$savedSearch->load($savedQbeSearchesFeature);
} catch (SavedSearchesException $exception) {
$this->response->setRequestStatus(false);
$this->response->addJSON('fieldWithError', 'searchId');
$this->response->addJSON('message', Message::error($exception->getMessage())->getDisplay());
return;
}
}
}
//Else, it's an "update query"
}
$savedSearchList = $savedSearch->getList($savedQbeSearchesFeature);
}
/**
* A query has been submitted -> (maybe) execute it
*/
$hasMessageToDisplay = false;
if ($request->hasBodyParam('submit_sql') && ! empty($GLOBALS['sql_query'])) {
if (stripos($GLOBALS['sql_query'], 'SELECT') !== 0) {
$hasMessageToDisplay = true;
} else {
$GLOBALS['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,
false, // is_gotofile
$request->getParsedBodyParam('db'), // db
null, // table
false, // find_real_end
null, // sql_query_for_bookmark
null, // extra_data
null, // message_to_show
null, // sql_data
$GLOBALS['goto'], // goto
null, // disp_query
null, // disp_message
$GLOBALS['sql_query'], // sql_query
null, // complete_query
));
}
}
$this->checkParameters(['db']);
$GLOBALS['errorUrl'] = Util::getScriptNameForOption($GLOBALS['cfg']['DefaultTabDatabase'], 'database');
$GLOBALS['errorUrl'] .= Url::getCommon(['db' => $GLOBALS['db']], '&');
if (! $this->hasDatabase()) {
return;
}
$GLOBALS['urlParams']['goto'] = Url::getFromRoute('/database/qbe');
$databaseQbe = new Qbe(
$this->relation,
$this->template,
$this->dbi,
$GLOBALS['db'],
$savedSearchList,
$savedSearch,
);
$this->render('database/qbe/index', [
'url_params' => $GLOBALS['urlParams'],
'has_message_to_display' => $hasMessageToDisplay,
'selection_form_html' => $databaseQbe->getSelectionForm(),
]);
}
}

View File

@ -70,7 +70,6 @@ final class JavaScriptMessagesController
'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'

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +0,0 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\Exceptions;
use Exception;
class SavedSearchesException extends Exception
{
}

View File

@ -351,7 +351,7 @@ class Menu
$tabs['query']['text'] = __('Query');
$tabs['query']['icon'] = 's_db';
$tabs['query']['route'] = '/database/multi-table-query';
$tabs['query']['active'] = $route === '/database/multi-table-query' || $route === '/database/qbe';
$tabs['query']['active'] = $route === '/database/multi-table-query';
if ($numTables == 0) {
$tabs['query']['warning'] = __('Database seems to be empty!');
}

View File

@ -1,365 +0,0 @@
<?php
/**
* Saved searches managing
*/
declare(strict_types=1);
namespace PhpMyAdmin;
use PhpMyAdmin\ConfigStorage\Features\SavedQueryByExampleSearchesFeature;
use PhpMyAdmin\Dbal\Connection;
use PhpMyAdmin\Exceptions\SavedSearchesException;
use function __;
use function count;
use function intval;
use function is_string;
use function json_decode;
use function json_encode;
use function max;
use function min;
/**
* Saved searches managing
*/
class SavedSearches
{
/**
* Id
*/
private int|null $id = null;
/**
* Username
*/
private string $username = '';
/**
* DB name
*/
private string $dbname = '';
/**
* Saved search name
*/
private string $searchName = '';
/**
* Criterias
*/
private array|null $criterias = null;
/**
* Setter of id
*
* @param int|null $searchId Id of search
*
* @return static
*/
public function setId(int|null $searchId): static
{
$searchId = (int) $searchId;
if ($searchId === 0) {
$searchId = null;
}
$this->id = $searchId;
return $this;
}
/**
* Getter of id
*/
public function getId(): int|null
{
return $this->id;
}
/**
* Setter of searchName
*
* @param string $searchName Saved search name
*
* @return static
*/
public function setSearchName(string $searchName): static
{
$this->searchName = $searchName;
return $this;
}
/**
* Getter of searchName
*/
public function getSearchName(): string
{
return $this->searchName;
}
/**
* Setter for criterias
*
* @param mixed[]|string $criterias Criterias of saved searches
* @param bool $json Criterias are in JSON format
*
* @return static
*/
public function setCriterias(array|string $criterias, bool $json = false): static
{
if ($json && is_string($criterias)) {
$this->criterias = json_decode($criterias, true);
return $this;
}
$aListFieldsToGet = [
'criteriaColumn',
'criteriaSort',
'criteriaShow',
'criteria',
'criteriaAndOrRow',
'criteriaAndOrColumn',
'rows',
'TableList',
];
$data = [];
$data['criteriaColumnCount'] = count($criterias['criteriaColumn']);
foreach ($aListFieldsToGet as $field) {
if (! isset($criterias[$field])) {
continue;
}
$data[$field] = $criterias[$field];
}
/* Limit amount of rows */
if (! isset($data['rows'])) {
$data['rows'] = 0;
} else {
$data['rows'] = min(
max(0, intval($data['rows'])),
100,
);
}
for ($i = 0; $i <= $data['rows']; $i++) {
$data['Or' . $i] = $criterias['Or' . $i];
}
$this->criterias = $data;
return $this;
}
/**
* Getter for criterias
*/
public function getCriterias(): array|null
{
return $this->criterias;
}
/**
* Setter for username
*
* @param string $username Username
*
* @return static
*/
public function setUsername(string $username): static
{
$this->username = $username;
return $this;
}
/**
* Getter for username
*/
public function getUsername(): string
{
return $this->username;
}
/**
* Setter for DB name
*
* @param string $dbname DB name
*
* @return static
*/
public function setDbname(string $dbname): static
{
$this->dbname = $dbname;
return $this;
}
/**
* Getter for DB name
*/
public function getDbname(): string
{
return $this->dbname;
}
/**
* Save the search
*
* @throws SavedSearchesException
*/
public function save(SavedQueryByExampleSearchesFeature $savedQueryByExampleSearchesFeature): bool
{
if ($this->getSearchName() == null) {
throw new SavedSearchesException(__('Please provide a name for this bookmarked search.'));
}
if (
$this->getUsername() == null
|| $this->getDbname() == null
|| $this->getSearchName() == null
|| $this->getCriterias() == null
) {
throw new SavedSearchesException(__('Missing information to save the bookmarked search.'));
}
$savedSearchesTbl = Util::backquote($savedQueryByExampleSearchesFeature->database) . '.'
. Util::backquote($savedQueryByExampleSearchesFeature->savedSearches);
//If it's an insert.
if ($this->getId() === null) {
$wheres = [
'search_name = ' . $GLOBALS['dbi']->quoteString($this->getSearchName(), Connection::TYPE_CONTROL),
];
$existingSearches = $this->getList($savedQueryByExampleSearchesFeature, $wheres);
if ($existingSearches !== []) {
throw new SavedSearchesException(__('An entry with this name already exists.'));
}
$sqlQuery = 'INSERT INTO ' . $savedSearchesTbl
. '(`username`, `db_name`, `search_name`, `search_data`)'
. ' VALUES ('
. $GLOBALS['dbi']->quoteString($this->getUsername(), Connection::TYPE_CONTROL) . ','
. $GLOBALS['dbi']->quoteString($this->getDbname(), Connection::TYPE_CONTROL) . ','
. $GLOBALS['dbi']->quoteString($this->getSearchName(), Connection::TYPE_CONTROL) . ','
. $GLOBALS['dbi']->quoteString(json_encode($this->getCriterias()), Connection::TYPE_CONTROL)
. ')';
$GLOBALS['dbi']->queryAsControlUser($sqlQuery);
$this->setId($GLOBALS['dbi']->insertId());
return true;
}
//Else, it's an update.
$wheres = ['id != ' . $this->getId(), 'search_name = ' . $GLOBALS['dbi']->quoteString($this->getSearchName())];
$existingSearches = $this->getList($savedQueryByExampleSearchesFeature, $wheres);
if ($existingSearches !== []) {
throw new SavedSearchesException(__('An entry with this name already exists.'));
}
$sqlQuery = 'UPDATE ' . $savedSearchesTbl
. 'SET `search_name` = '
. $GLOBALS['dbi']->quoteString($this->getSearchName(), Connection::TYPE_CONTROL) . ', '
. '`search_data` = '
. $GLOBALS['dbi']->quoteString(json_encode($this->getCriterias()), Connection::TYPE_CONTROL) . ' '
. 'WHERE id = ' . $this->getId();
return (bool) $GLOBALS['dbi']->queryAsControlUser($sqlQuery);
}
/**
* Delete the search
*
* @throws SavedSearchesException
*/
public function delete(SavedQueryByExampleSearchesFeature $savedQueryByExampleSearchesFeature): bool
{
if ($this->getId() == null) {
throw new SavedSearchesException(__('Missing information to delete the search.'));
}
$savedSearchesTbl = Util::backquote($savedQueryByExampleSearchesFeature->database) . '.'
. Util::backquote($savedQueryByExampleSearchesFeature->savedSearches);
$sqlQuery = 'DELETE FROM ' . $savedSearchesTbl
. 'WHERE id = ' . $GLOBALS['dbi']->quoteString((string) $this->getId(), Connection::TYPE_CONTROL);
return (bool) $GLOBALS['dbi']->queryAsControlUser($sqlQuery);
}
/**
* Load the current search from an id.
*
* @throws SavedSearchesException
*/
public function load(SavedQueryByExampleSearchesFeature $savedQueryByExampleSearchesFeature): bool
{
if ($this->getId() == null) {
throw new SavedSearchesException(__('Missing information to load the search.'));
}
$savedSearchesTbl = Util::backquote($savedQueryByExampleSearchesFeature->database)
. '.'
. Util::backquote($savedQueryByExampleSearchesFeature->savedSearches);
$sqlQuery = 'SELECT id, search_name, search_data '
. 'FROM ' . $savedSearchesTbl . ' '
. 'WHERE id = ' . $GLOBALS['dbi']->quoteString((string) $this->getId(), Connection::TYPE_CONTROL);
$resList = $GLOBALS['dbi']->queryAsControlUser($sqlQuery);
$oneResult = $resList->fetchAssoc();
if ($oneResult === []) {
throw new SavedSearchesException(__('Error while loading the search.'));
}
$this->setSearchName($oneResult['search_name'])
->setCriterias($oneResult['search_data'], true);
return true;
}
/**
* Get the list of saved searches of a user on a DB
*
* @param string[] $wheres List of filters
*
* @return mixed[] List of saved searches or empty array on failure
*/
public function getList(
SavedQueryByExampleSearchesFeature $savedQueryByExampleSearchesFeature,
array $wheres = [],
): array {
if ($this->getUsername() == null || $this->getDbname() == null) {
return [];
}
$savedSearchesTbl = Util::backquote($savedQueryByExampleSearchesFeature->database)
. '.'
. Util::backquote($savedQueryByExampleSearchesFeature->savedSearches);
$sqlQuery = 'SELECT id, search_name '
. 'FROM ' . $savedSearchesTbl . ' '
. 'WHERE '
. 'username = ' . $GLOBALS['dbi']->quoteString($this->getUsername(), Connection::TYPE_CONTROL) . ' '
. 'AND db_name = ' . $GLOBALS['dbi']->quoteString($this->getDbname(), Connection::TYPE_CONTROL) . ' ';
foreach ($wheres as $where) {
$sqlQuery .= 'AND ' . $where . ' ';
}
$sqlQuery .= 'order by search_name ASC ';
$resList = $GLOBALS['dbi']->queryAsControlUser($sqlQuery);
return $resList->fetchAllKeyPair();
}
}

View File

@ -78,7 +78,6 @@ return static function (RouteCollector $routes): void {
$routes->post('/collation', Database\Operations\CollationController::class);
});
$routes->get('/privileges', Database\PrivilegesController::class);
$routes->addRoute(['GET', 'POST'], '/qbe', Database\QueryByExampleController::class);
$routes->addRoute(['GET', 'POST'], '/routines', Database\RoutinesController::class);
$routes->addRoute(['GET', 'POST'], '/search', Database\SearchController::class);
$routes->addGroup('/sql', static function (RouteCollector $routes): void {

View File

@ -182,15 +182,6 @@ return [
'$dbi' => '@dbi',
],
],
Database\QueryByExampleController::class => [
'class' => Database\QueryByExampleController::class,
'arguments' => [
'$response' => '@response',
'$template' => '@template',
'$relation' => '@relation',
'$dbi' => '@dbi',
],
],
Database\RoutinesController::class => [
'class' => Database\RoutinesController::class,
'arguments' => [

View File

@ -1010,26 +1010,6 @@ parameters:
count: 1
path: libraries/classes/Controllers/Database/OperationsController.php
-
message: "#^Parameter \\#1 \\$criterias of method PhpMyAdmin\\\\SavedSearches\\:\\:setCriterias\\(\\) expects array\\|string, array\\|object\\|null given\\.$#"
count: 2
path: libraries/classes/Controllers/Database/QueryByExampleController.php
-
message: "#^Parameter \\#1 \\$searchId of method PhpMyAdmin\\\\SavedSearches\\:\\:setId\\(\\) expects int\\|null, mixed given\\.$#"
count: 1
path: libraries/classes/Controllers/Database/QueryByExampleController.php
-
message: "#^Parameter \\#1 \\$searchName of method PhpMyAdmin\\\\SavedSearches\\:\\:setSearchName\\(\\) expects string, mixed given\\.$#"
count: 1
path: libraries/classes/Controllers/Database/QueryByExampleController.php
-
message: "#^Parameter \\#3 \\$db of method PhpMyAdmin\\\\Sql\\:\\:executeQueryAndSendQueryResponse\\(\\) expects string, mixed given\\.$#"
count: 1
path: libraries/classes/Controllers/Database/QueryByExampleController.php
-
message: "#^Parameter \\#1 \\$routine of method PhpMyAdmin\\\\Database\\\\Routines\\:\\:getRow\\(\\) expects array, mixed given\\.$#"
count: 1
@ -3010,161 +2990,6 @@ parameters:
count: 1
path: libraries/classes/Database/Events.php
-
message: "#^Argument of an invalid type mixed supplied for foreach, only iterables are supported\\.$#"
count: 6
path: libraries/classes/Database/Qbe.php
-
message: "#^Cannot access offset 'foreign_field' on mixed\\.$#"
count: 1
path: libraries/classes/Database/Qbe.php
-
message: "#^Cannot access offset 'foreign_table' on mixed\\.$#"
count: 5
path: libraries/classes/Database/Qbe.php
-
message: "#^Cannot access offset 'index_list' on mixed\\.$#"
count: 1
path: libraries/classes/Database/Qbe.php
-
message: "#^Cannot access offset 'ref_index_list' on mixed\\.$#"
count: 1
path: libraries/classes/Database/Qbe.php
-
message: "#^Cannot access offset 'ref_table_name' on mixed\\.$#"
count: 5
path: libraries/classes/Database/Qbe.php
-
message: "#^Cannot access offset 'table_name' on mixed\\.$#"
count: 1
path: libraries/classes/Database/Qbe.php
-
message: "#^Cannot access offset 'table_schema' on mixed\\.$#"
count: 1
path: libraries/classes/Database/Qbe.php
-
message: "#^Cannot access offset mixed on mixed\\.$#"
count: 1
path: libraries/classes/Database/Qbe.php
-
message: "#^For loop initial assignment overwrites variable \\$columnIndex\\.$#"
count: 1
path: libraries/classes/Database/Qbe.php
-
message: "#^Method PhpMyAdmin\\\\Database\\\\Qbe\\:\\:getLeftJoinColumnCandidates\\(\\) should return array but returns mixed\\.$#"
count: 1
path: libraries/classes/Database/Qbe.php
-
message: "#^Method PhpMyAdmin\\\\Database\\\\Qbe\\:\\:getMasterTable\\(\\) should return string but returns mixed\\.$#"
count: 2
path: libraries/classes/Database/Qbe.php
-
message: "#^Parameter \\#1 \\$haystack of function stripos expects string, mixed given\\.$#"
count: 1
path: libraries/classes/Database/Qbe.php
-
message: "#^Parameter \\#1 \\$identifier of static method PhpMyAdmin\\\\Util\\:\\:backquote\\(\\) expects string\\|Stringable\\|null, mixed given\\.$#"
count: 7
path: libraries/classes/Database/Qbe.php
-
message: "#^Parameter \\#1 \\$string of function htmlspecialchars expects string, mixed given\\.$#"
count: 3
path: libraries/classes/Database/Qbe.php
-
message: "#^Parameter \\#1 \\$string of function mb_strtoupper expects string, mixed given\\.$#"
count: 2
path: libraries/classes/Database/Qbe.php
-
message: "#^Parameter \\#1 \\$string of function mb_substr expects string, mixed given\\.$#"
count: 2
path: libraries/classes/Database/Qbe.php
-
message: "#^Parameter \\#1 \\$tableName of class PhpMyAdmin\\\\Table constructor expects string, mixed given\\.$#"
count: 1
path: libraries/classes/Database/Qbe.php
-
message: "#^Parameter \\#2 \\$oneTable of method PhpMyAdmin\\\\Database\\\\Qbe\\:\\:loadRelationsForTable\\(\\) expects string, mixed given\\.$#"
count: 2
path: libraries/classes/Database/Qbe.php
-
message: "#^Parameter \\#2 \\$string of function explode expects string, mixed given\\.$#"
count: 2
path: libraries/classes/Database/Qbe.php
-
message: "#^Parameter \\#2 \\$table of method PhpMyAdmin\\\\ConfigStorage\\\\Relation\\:\\:getChildReferences\\(\\) expects string, mixed given\\.$#"
count: 1
path: libraries/classes/Database/Qbe.php
-
message: "#^Parameter \\#2 \\$table of method PhpMyAdmin\\\\ConfigStorage\\\\Relation\\:\\:getForeigners\\(\\) expects string, mixed given\\.$#"
count: 1
path: libraries/classes/Database/Qbe.php
-
message: "#^Parameter \\#2 \\$table of method PhpMyAdmin\\\\DatabaseInterface\\:\\:getColumns\\(\\) expects string, string\\|null given\\.$#"
count: 1
path: libraries/classes/Database/Qbe.php
-
message: "#^Parameter \\#2 \\$table of method PhpMyAdmin\\\\DatabaseInterface\\:\\:getTableIndexes\\(\\) expects string, mixed given\\.$#"
count: 1
path: libraries/classes/Database/Qbe.php
-
message: "#^Parameter \\#3 \\$uniqueColumns of method PhpMyAdmin\\\\Database\\\\Qbe\\:\\:getLeftJoinColumnCandidatesBest\\(\\) expects array\\|null, mixed given\\.$#"
count: 1
path: libraries/classes/Database/Qbe.php
-
message: "#^Parameter \\#3 \\$whereClauseColumns of method PhpMyAdmin\\\\Database\\\\Qbe\\:\\:getMasterTable\\(\\) expects array, mixed given\\.$#"
count: 1
path: libraries/classes/Database/Qbe.php
-
message: "#^Parameter \\#4 \\$indexColumns of method PhpMyAdmin\\\\Database\\\\Qbe\\:\\:getLeftJoinColumnCandidatesBest\\(\\) expects array\\|null, mixed given\\.$#"
count: 1
path: libraries/classes/Database/Qbe.php
-
message: "#^Parameter \\#4 \\$whereClauseTables of method PhpMyAdmin\\\\Database\\\\Qbe\\:\\:getMasterTable\\(\\) expects array, mixed given\\.$#"
count: 1
path: libraries/classes/Database/Qbe.php
-
message: "#^Property PhpMyAdmin\\\\Database\\\\Qbe\\:\\:\\$criteriaColumnDelete type has no value type specified in iterable type array\\.$#"
count: 1
path: libraries/classes/Database/Qbe.php
-
message: "#^Property PhpMyAdmin\\\\Database\\\\Qbe\\:\\:\\$criteriaColumnInsert type has no value type specified in iterable type array\\.$#"
count: 1
path: libraries/classes/Database/Qbe.php
-
message: "#^Variable variables are not allowed\\.$#"
count: 4
path: libraries/classes/Database/Qbe.php
-
message: "#^Cannot access offset 'DTD_IDENTIFIER' on mixed\\.$#"
count: 1
@ -7410,41 +7235,6 @@ parameters:
count: 1
path: libraries/classes/Sanitize.php
-
message: "#^Method PhpMyAdmin\\\\SavedSearches\\:\\:getCriterias\\(\\) return type has no value type specified in iterable type array\\.$#"
count: 1
path: libraries/classes/SavedSearches.php
-
message: "#^Offset 'criteriaColumn' does not exist on array\\|string\\.$#"
count: 1
path: libraries/classes/SavedSearches.php
-
message: "#^Offset non\\-falsy\\-string does not exist on array\\|string\\.$#"
count: 1
path: libraries/classes/SavedSearches.php
-
message: "#^Parameter \\#1 \\$value of function count expects array\\|Countable, mixed given\\.$#"
count: 1
path: libraries/classes/SavedSearches.php
-
message: "#^Parameter \\#1 \\$value of function intval expects array\\|bool\\|float\\|int\\|resource\\|string\\|null, mixed given\\.$#"
count: 1
path: libraries/classes/SavedSearches.php
-
message: "#^Property PhpMyAdmin\\\\SavedSearches\\:\\:\\$criterias \\(array\\|null\\) does not accept mixed\\.$#"
count: 1
path: libraries/classes/SavedSearches.php
-
message: "#^Property PhpMyAdmin\\\\SavedSearches\\:\\:\\$criterias type has no value type specified in iterable type array\\.$#"
count: 1
path: libraries/classes/SavedSearches.php
-
message: "#^Parameter \\#1 \\$name of class PhpMyAdmin\\\\Server\\\\Plugin constructor expects string, mixed given\\.$#"
count: 1
@ -9320,16 +9110,6 @@ parameters:
count: 1
path: test/classes/Database/DesignerTest.php
-
message: "#^Parameter \\#2 \\$haystack of method PHPUnit\\\\Framework\\\\Assert\\:\\:assertStringContainsString\\(\\) expects string, mixed given\\.$#"
count: 7
path: test/classes/Database/QbeTest.php
-
message: "#^Parameter \\#2 \\$haystack of method PHPUnit\\\\Framework\\\\Assert\\:\\:assertStringNotContainsString\\(\\) expects string, mixed given\\.$#"
count: 1
path: test/classes/Database/QbeTest.php
-
message: "#^Call to method PHPUnit\\\\Framework\\\\Assert\\:\\:assertInstanceOf\\(\\) with 'PhpMyAdmin\\\\\\\\Dbal\\\\\\\\ResultInterface' and PhpMyAdmin\\\\Dbal\\\\ResultInterface will always evaluate to true\\.$#"
count: 1

View File

@ -1202,28 +1202,6 @@
<code>__construct</code>
</PossiblyUnusedMethod>
</file>
<file src="libraries/classes/Controllers/Database/QueryByExampleController.php">
<InvalidArrayOffset>
<code><![CDATA[$GLOBALS['errorUrl']]]></code>
</InvalidArrayOffset>
<MixedArgument>
<code><![CDATA[$request->getParsedBodyParam('db')]]></code>
<code><![CDATA[$request->getParsedBodyParam('searchName')]]></code>
<code>$searchId</code>
</MixedArgument>
<MixedAssignment>
<code><![CDATA[$GLOBALS['errorUrl']]]></code>
<code>$action</code>
<code>$searchId</code>
</MixedAssignment>
<PossiblyInvalidArgument>
<code><![CDATA[$request->getParsedBody()]]></code>
<code><![CDATA[$request->getParsedBody()]]></code>
</PossiblyInvalidArgument>
<PossiblyUnusedMethod>
<code>__construct</code>
</PossiblyUnusedMethod>
</file>
<file src="libraries/classes/Controllers/Database/RoutinesController.php">
<InvalidArrayOffset>
<code><![CDATA[$GLOBALS['errorUrl']]]></code>
@ -5245,180 +5223,6 @@
<code>isSuccess</code>
</PossiblyNullReference>
</file>
<file src="libraries/classes/Database/Qbe.php">
<InvalidPropertyAssignmentValue>
<code><![CDATA[$this->criteriaTables]]></code>
<code><![CDATA[$this->criteriaTables]]></code>
<code><![CDATA[$this->criteriaTables]]></code>
</InvalidPropertyAssignmentValue>
<MixedArgument>
<code>$column</code>
<code>$columns[$columnIndex]</code>
<code><![CDATA[$eachColumn['Field']]]></code>
<code><![CDATA[$foreigner['foreign_field']]]></code>
<code><![CDATA[$foreigner['foreign_table']]]></code>
<code>$indexColumns</code>
<code>$name</code>
<code>$oneField</code>
<code><![CDATA[$oneKey['ref_index_list'][$index]]]></code>
<code><![CDATA[$oneKey['ref_table_name']]]></code>
<code>$oneTable</code>
<code>$oneTable</code>
<code>$oneTable</code>
<code>$table</code>
<code>$table</code>
<code>$table</code>
<code><![CDATA[$this->criteria[$columnIndex]]]></code>
<code><![CDATA[$this->criteria[$columnIndex]]]></code>
<code><![CDATA[$this->formAliases[$columnIndex]]]></code>
<code><![CDATA[$this->formAndOrCols[$lastOrWhere]]]></code>
<code><![CDATA[$this->formAndOrCols[$lastWhere]]]></code>
<code><![CDATA[$this->formCriterions[$newColumnCount]]]></code>
<code>$tmpCriteria</code>
<code>$uniqueColumns</code>
<code>$value</code>
<code>$whereClauseColumns</code>
<code>$whereClauseTables</code>
</MixedArgument>
<MixedArgumentTypeCoercion>
<code>$selectClauses</code>
<code>$table</code>
<code>$table</code>
</MixedArgumentTypeCoercion>
<MixedArrayAccess>
<code><![CDATA[$foreigner['foreign_field']]]></code>
<code><![CDATA[$foreigner['foreign_table']]]></code>
<code><![CDATA[$foreigner['foreign_table']]]></code>
<code><![CDATA[$foreigner['foreign_table']]]></code>
<code><![CDATA[$foreigner['foreign_table']]]></code>
<code><![CDATA[$foreigner['foreign_table']]]></code>
<code><![CDATA[$oneKey['index_list']]]></code>
<code><![CDATA[$oneKey['ref_index_list']]]></code>
<code><![CDATA[$oneKey['ref_index_list'][$index]]]></code>
<code><![CDATA[$oneKey['ref_table_name']]]></code>
<code><![CDATA[$oneKey['ref_table_name']]]></code>
<code><![CDATA[$oneKey['ref_table_name']]]></code>
<code><![CDATA[$oneKey['ref_table_name']]]></code>
<code><![CDATA[$oneKey['ref_table_name']]]></code>
<code><![CDATA[$reference['table_name']]]></code>
<code><![CDATA[$reference['table_schema']]]></code>
</MixedArrayAccess>
<MixedArrayOffset>
<code>$checkedTables[$table]</code>
<code>$finalized[$foreignTable]</code>
<code>$finalized[$foreignTable]</code>
<code>$finalized[$foreignTable]</code>
<code>$finalized[$masterTable]</code>
<code>$finalized[$masterTable]</code>
<code>$finalized[$masterTable]</code>
<code><![CDATA[$foreignTables[$foreigner['foreign_table']]]]></code>
<code><![CDATA[$foreignTables[$oneKey['ref_table_name']]]]></code>
<code><![CDATA[$oneKey['ref_index_list'][$index]]]></code>
<code>$relations[$masterTable]</code>
<code><![CDATA[$relations[$oneTable][$foreigner['foreign_table']]]]></code>
<code><![CDATA[$relations[$oneTable][$oneKey['ref_table_name']]]]></code>
<code><![CDATA[$this->formAndOrCols[$lastWhere]]]></code>
<code>$tsize[$table]</code>
</MixedArrayOffset>
<MixedArrayTypeCoercion>
<code>$tsize[$table]</code>
</MixedArrayTypeCoercion>
<MixedAssignment>
<code><![CDATA[$GLOBALS[${'cur' . $or}][$newColumnCount]]]></code>
<code>$clause</code>
<code>$clause</code>
<code>$column</code>
<code>$columnReferences</code>
<code>$finalized[$foreignTable]</code>
<code>$finalized[$masterTable]</code>
<code>$foreignData</code>
<code>$foreignTable</code>
<code><![CDATA[$foreignTables[$foreigner['foreign_table']]]]></code>
<code><![CDATA[$foreignTables[$oneKey['ref_table_name']]]]></code>
<code>$foreigner</code>
<code>$foreigner</code>
<code>$index</code>
<code>$indexColumns</code>
<code>$isWhere</code>
<code>$masterTable</code>
<code>$name</code>
<code>$oneField</code>
<code>$oneKey</code>
<code>$oneKey</code>
<code>$oneTable</code>
<code>$oneTable</code>
<code>$oneTable</code>
<code>$reference</code>
<code>$result</code>
<code>$select</code>
<code>$selectClauses[]</code>
<code>$table</code>
<code>$table</code>
<code>$table</code>
<code>$tmpCriteria</code>
<code>$uniqueColumns</code>
<code>$value</code>
<code>$whereClauseColumns</code>
<code>$whereClauseTables</code>
</MixedAssignment>
<MixedInferredReturnType>
<code>mixed[]</code>
<code>string</code>
</MixedInferredReturnType>
<MixedOperand>
<code>$clause</code>
<code>$columns[$columnIndex]</code>
<code>$select</code>
<code><![CDATA[$selected['and'] ?? '']]></code>
<code><![CDATA[$selected['or'] ?? '']]></code>
<code>$sort[$columnIndex]</code>
<code><![CDATA[$this->formAndOrRows[$rowIndex]]]></code>
<code><![CDATA[$this->formColumns[$columnIndex]]]></code>
<code><![CDATA[$this->formColumns[$columnIndex]]]></code>
<code><![CDATA[$this->formCriterions[$columnIndex]]]></code>
</MixedOperand>
<MixedReturnStatement>
<code>$candidateColumns</code>
<code>$result</code>
<code>$result</code>
<code>reset($candidateColumns)</code>
<code>reset($candidateColumns)</code>
</MixedReturnStatement>
<PossiblyInvalidArgument>
<code><![CDATA[$_POST['criteriaColumn'][$colInd]]]></code>
<code><![CDATA[$_POST['criteriaColumn'][$columnIndex]]]></code>
<code>$selected</code>
<code>$tmpAlias</code>
<code>$tmpOr</code>
</PossiblyInvalidArgument>
<PossiblyInvalidCast>
<code><![CDATA[$_POST['criteriaColumn'][$colInd]]]></code>
<code>$selected</code>
</PossiblyInvalidCast>
<PossiblyInvalidOperand>
<code><![CDATA[$_POST['Or' . $rowIndex][$columnIndex]]]></code>
</PossiblyInvalidOperand>
<PossiblyInvalidPropertyAssignmentValue>
<code><![CDATA[$_POST['criteria'] ?? array_fill(0, $criteriaColumnCount, '')]]></code>
<code><![CDATA[$_POST['criteriaAndOrColumn'] ?? array_fill(0, $criteriaColumnCount, '')]]></code>
<code><![CDATA[$_POST['criteriaAndOrRow'] ?? array_fill(0, $criteriaColumnCount, '')]]></code>
<code><![CDATA[$_POST['criteriaRowDelete'] ?? array_fill(0, $criteriaColumnCount, '')]]></code>
<code><![CDATA[$_POST['criteriaRowInsert'] ?? array_fill(0, $criteriaColumnCount, '')]]></code>
<code><![CDATA[$_POST['prev_criteria'] ?? []]]></code>
</PossiblyInvalidPropertyAssignmentValue>
<PossiblyNullArgument>
<code>$table</code>
</PossiblyNullArgument>
<PossiblyNullOperand>
<code><![CDATA[$index['Column_name']]]></code>
</PossiblyNullOperand>
<RedundantPropertyInitializationCheck>
<code><![CDATA[isset($this->formAndOrRows)]]></code>
</RedundantPropertyInitializationCheck>
<RiskyCast>
<code><![CDATA[$this->formSortOrders[$newColumnCount] = $_POST['criteriaSortOrder'][$colInd]]]></code>
</RiskyCast>
</file>
<file src="libraries/classes/Database/Routines.php">
<DeprecatedMethod>
<code>escapeString</code>
@ -11918,33 +11722,6 @@
<code><![CDATA[isset($_COOKIE[$key]) && ! is_string($_COOKIE[$key])]]></code>
</TypeDoesNotContainType>
</file>
<file src="libraries/classes/SavedSearches.php">
<MixedArgument>
<code><![CDATA[$criterias['criteriaColumn']]]></code>
</MixedArgument>
<MixedAssignment>
<code>$data[$field]</code>
<code><![CDATA[$data['Or' . $i]]]></code>
<code><![CDATA[$this->criterias]]></code>
</MixedAssignment>
<PossiblyInvalidArrayOffset>
<code><![CDATA[$criterias['Or' . $i]]]></code>
<code><![CDATA[$criterias['criteriaColumn']]]></code>
</PossiblyInvalidArrayOffset>
<PossiblyNullArgument>
<code><![CDATA[$oneResult['search_data']]]></code>
<code><![CDATA[$oneResult['search_name']]]></code>
</PossiblyNullArgument>
<PossiblyNullOperand>
<code><![CDATA[$this->getId()]]></code>
<code><![CDATA[$this->getId()]]></code>
</PossiblyNullOperand>
<PossiblyUnusedReturnValue>
<code>bool</code>
<code>bool</code>
<code>bool</code>
</PossiblyUnusedReturnValue>
</file>
<file src="libraries/classes/Server/Plugin.php">
<MixedArgument>
<code><![CDATA[$state['authVersion'] ?? null]]></code>
@ -14342,20 +14119,6 @@
<code>$result</code>
</MixedAssignment>
</file>
<file src="test/classes/Database/QbeTest.php">
<MixedArgument>
<code>$actual</code>
<code><![CDATA[$this->callFunction($object, Qbe::class, 'getSortRow', [])]]></code>
<code><![CDATA[$this->callFunction($object, Qbe::class, 'getSortRow', [])]]></code>
<code><![CDATA[$this->callFunction($object, Qbe::class, 'getSortRow', [])]]></code>
<code><![CDATA[$this->callFunction($object, Qbe::class, 'getSortSelectCell', [1, 'ASC'])]]></code>
<code><![CDATA[$this->callFunction($object, Qbe::class, 'getSortSelectCell', [1])]]></code>
<code><![CDATA[$this->callFunction($object, Qbe::class, 'getSortSelectCell', [1])]]></code>
</MixedArgument>
<MixedAssignment>
<code>$actual</code>
</MixedAssignment>
</file>
<file src="test/classes/Database/RoutinesTest.php">
<MixedAssignment>
<code>$_POST[$key]</code>

View File

@ -797,11 +797,6 @@ textarea {
border-top: 0.1em solid silver;
}
#qbe_div_table_list,
#qbe_div_sql_query {
float: left;
}
div.sqlvalidate {
display: block;
padding: 1em;

View File

@ -1047,11 +1047,6 @@ li {
clear: both;
}
#qbe_div_table_list,
#qbe_div_sql_query {
float: left;
}
kbd {
color: $body-color;
background-color: transparent;

View File

@ -784,11 +784,6 @@ textarea {
text-align: right;
}
#qbe_div_table_list,
#qbe_div_sql_query {
float: left;
}
kbd {
color: $main-color;
background-color: transparent;

View File

@ -977,11 +977,6 @@ textarea {
border-top: 0.1em solid silver;
}
#qbe_div_table_list,
#qbe_div_sql_query {
float: left;
}
kbd {
color: $main-color;
background-color: transparent;

View File

@ -1,17 +1,3 @@
<ul class="nav nav-pills m-2">
<li class="nav-item">
<a class="nav-link active disableAjax" href="{{ url('/database/multi-table-query', {'db': db}) }}">
{% trans 'Multi-table query' %}
</a>
</li>
<li class="nav-item">
<a class="nav-link disableAjax" href="{{ url('/database/qbe', {'db': db}) }}">
{% trans 'Query by example' %}
</a>
</li>
</ul>
<div class="mb-3">
<button class="btn btn-sm btn-secondary" type="button" data-bs-toggle="collapse" data-bs-target="#queryWindow" aria-expanded="true" aria-controls="queryWindow">
{% trans 'Query window' %}

View File

@ -1,11 +0,0 @@
<td class="text-center">
<select name="criteriaColumn[{{ column_number }}]" size="1">
<option value="">&nbsp;</option>
{% for column in column_names %}
<option value="{{ column }}"
{%- if column is same as(selected) %} selected="selected"{% endif %}>
{{- column -}}
</option>
{% endfor %}
</select>
</td>

View File

@ -1,23 +0,0 @@
<ul class="nav nav-pills m-2">
<li class="nav-item">
<a class="nav-link disableAjax" href="{{ url('/database/multi-table-query', url_params) }}">
{% trans 'Multi-table query' %}
</a>
</li>
<li class="nav-item">
<a class="nav-link active disableAjax" href="{{ url('/database/qbe', url_params) }}">
{% trans 'Query by example' %}
</a>
</li>
</ul>
{% apply format('<a href="' ~ url('/database/designer', url_params|merge({query: true})) ~ '">', '</a>')|notice %}
{% trans 'Switch to %svisual builder%s' %}
{% endapply %}
{% if has_message_to_display %}
{{ 'You have to choose at least one column to display!'|trans|error }}
{% endif %}
{{ selection_form_html|raw }}

View File

@ -1,28 +0,0 @@
<td class="value text-nowrap">
<table class="table table-borderless table-sm">
<tr>
<td class="value text-nowrap p-0">
<small>{% trans 'Ins:' %}</small>
<input type="checkbox" name="criteriaRowInsert[{{ row_index }}]" aria-label="{% trans 'Insert' %}">
</td>
<td class="value p-0">
<strong>{% trans 'And:' %}</strong>
</td>
<td class="p-0">
<input type="radio" name="criteriaAndOrRow[{{ row_index }}]" value="and"{{ checked_options.and ? ' checked' }} aria-label="{% trans 'And' %}">
</td>
</tr>
<tr>
<td class="value text-nowrap p-0">
<small>{% trans 'Del:' %}</small>
<input type="checkbox" name="criteriaRowDelete[{{ row_index }}]" aria-label="{% trans 'Delete' %}">
</td>
<td class="value p-0">
<strong>{% trans 'Or:' %}</strong>
</td>
<td class="p-0">
<input type="radio" name="criteriaAndOrRow[{{ row_index }}]" value="or"{{ checked_options.or ? ' checked' }} aria-label="{% trans 'Or' %}">
</td>
</tr>
</table>
</td>

View File

@ -1,118 +0,0 @@
<form action="{{ url('/database/qbe') }}" method="post" id="formQBE" class="lock-page">
{{ get_hidden_inputs(url_params) }}
<div class="w-100">
<fieldset class="pma-fieldset">
{{ saved_searches_field|raw }}
<div class="table-responsive jsresponsive">
<table class="table table-borderless table-sm w-auto">
<tr class="noclick">
<th>{% trans 'Column:' %}</th>
{{ column_names_row|raw }}
</tr>
<tr class="noclick">
<th>{% trans 'Alias:' %}</th>
{{ column_alias_row|raw }}
</tr>
<tr class="noclick">
<th>{% trans 'Show:' %}</th>
{{ show_row|raw }}
</tr>
<tr class="noclick">
<th>{% trans 'Sort:' %}</th>
{{ sort_row|raw }}
</tr>
<tr class="noclick">
<th>{% trans 'Sort order:' %}</th>
{{ sort_order|raw }}
</tr>
<tr class="noclick">
<th>{% trans 'Criteria:' %}</th>
{{ criteria_input_box_row|raw }}
</tr>
{{ ins_del_and_or_criteria_rows|raw }}
<tr class="noclick">
<th>{% trans 'Modify:' %}</th>
{{ modify_columns_row|raw }}
</tr>
</table>
</div>
</fieldset>
</div>
<fieldset class="pma-fieldset tblFooters">
<div class="float-start">
<label for="criteriaRowAddSelect">{% trans 'Add/Delete criteria rows:' %}</label>
<select size="1" name="criteriaRowAdd" id="criteriaRowAddSelect">
<option value="-3">-3</option>
<option value="-2">-2</option>
<option value="-1">-1</option>
<option value="0" selected>0</option>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
</div>
<div class="float-start">
<label for="criteriaColumnAddSelect">{% trans 'Add/Delete columns:' %}</label>
<select size="1" name="criteriaColumnAdd" id="criteriaColumnAddSelect">
<option value="-3">-3</option>
<option value="-2">-2</option>
<option value="-1">-1</option>
<option value="0" selected>0</option>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
</div>
<div class="float-start">
<input class="btn btn-secondary" type="submit" name="modify" value="{% trans 'Update query' %}">
</div>
</fieldset>
<div class="float-start w-100">
<fieldset class="pma-fieldset">
<legend>{% trans 'Use tables' %}</legend>
<select class="resize-vertical" name="TableList[]" id="listTable" size="{{ criteria_tables|length > 30 ? '15' : '7' }}" aria-label="{% trans 'Use tables' %}" multiple>
{% for table, selected in criteria_tables %}
<option value="{{ table }}"{{ selected|raw }}>{{ table }}</option>
{% endfor %}
</select>
</fieldset>
<fieldset class="pma-fieldset tblFooters">
<input class="btn btn-secondary" type="submit" name="modify" value="{% trans 'Update query' %}">
</fieldset>
</div>
</form>
<form action="{{ url('/database/qbe') }}" method="post" class="lock-page">
{{ get_hidden_inputs(db) }}
<input type="hidden" name="submit_sql" value="1">
<div class="float-start w-50">
<fieldset class="pma-fieldset" id="tblQbe">
<legend>{{ 'SQL query on database <b>%s</b>:'|trans|format(db_link)|raw }}</legend>
<textarea cols="80" name="sql_query" id="textSqlquery" rows="{{ criteria_tables|length > 30 ? '15' : '7' }}" dir="ltr" aria-label="{% trans 'SQL query' %}">
{{- sql_query -}}
</textarea>
</fieldset>
<fieldset class="pma-fieldset tblFooters" id="tblQbeFooters">
<input class="btn btn-primary" type="submit" value="{% trans 'Submit query' %}">
</fieldset>
</div>
</form>

View File

@ -1,10 +0,0 @@
<td class="text-center">
<select name="criteriaSortOrder[{{ column_number }}]">
<option value="1000">&nbsp;</option>
{% for i in 1..total_column_count %}
<option value="{{ i }}"{{ i == sort_order ? ' selected="selected"' }}>
{{ i }}
</option>
{% endfor %}
</select>
</td>

View File

@ -1,9 +0,0 @@
<td class="text-center">
<select style="width:{{ real_width }}" name="criteriaSort[{{ column_number }}]" size="1">
<option value="">&nbsp;</option>
<option value="ASC"
{{- selected == 'ASC' ? ' selected="selected"' }}>{% trans 'Ascending' %}</option>
<option value="DESC"
{{- selected == 'DESC' ? ' selected="selected"' }}>{% trans 'Descending' %}</option>
</select>
</td>

View File

@ -1,269 +0,0 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\Tests\Database;
use PhpMyAdmin\ConfigStorage\Relation;
use PhpMyAdmin\Database\Qbe;
use PhpMyAdmin\Template;
use PhpMyAdmin\Tests\AbstractTestCase;
/** @covers \PhpMyAdmin\Database\Qbe */
class QbeTest extends AbstractTestCase
{
public function testGetSortSelectCell(): void
{
$GLOBALS['dbi'] = $this->createDatabaseInterface();
$object = new Qbe(new Relation($GLOBALS['dbi']), new Template(), $GLOBALS['dbi'], 'pma_test');
$this->assertStringContainsString(
'style="width:12ex" name="criteriaSort[1]"',
$this->callFunction($object, Qbe::class, 'getSortSelectCell', [1]),
);
$this->assertStringNotContainsString(
'selected="selected"',
$this->callFunction($object, Qbe::class, 'getSortSelectCell', [1]),
);
$this->assertStringContainsString(
'value="ASC" selected="selected">',
$this->callFunction($object, Qbe::class, 'getSortSelectCell', [1, 'ASC']),
);
}
public function testGetSortRow(): void
{
$GLOBALS['dbi'] = $this->createDatabaseInterface();
$object = new Qbe(new Relation($GLOBALS['dbi']), new Template(), $GLOBALS['dbi'], 'pma_test');
$this->assertStringContainsString(
'name="criteriaSort[0]"',
$this->callFunction($object, Qbe::class, 'getSortRow', []),
);
$this->assertStringContainsString(
'name="criteriaSort[1]"',
$this->callFunction($object, Qbe::class, 'getSortRow', []),
);
$this->assertStringContainsString(
'name="criteriaSort[2]"',
$this->callFunction($object, Qbe::class, 'getSortRow', []),
);
}
public function testGetShowRow(): void
{
$GLOBALS['dbi'] = $this->createDatabaseInterface();
$object = new Qbe(new Relation($GLOBALS['dbi']), new Template(), $GLOBALS['dbi'], 'pma_test');
$this->assertEquals(
'<td class="text-center"><input type'
. '="checkbox" name="criteriaShow[0]"></td><td class="text-center">'
. '<input type="checkbox" name="criteriaShow[1]"></td><td '
. 'class="text-center"><input type="checkbox" name="criteriaShow[2]">'
. '</td>',
$this->callFunction($object, Qbe::class, 'getShowRow', []),
);
}
public function testGetCriteriaInputboxRow(): void
{
$GLOBALS['dbi'] = $this->createDatabaseInterface();
$object = new Qbe(new Relation($GLOBALS['dbi']), new Template(), $GLOBALS['dbi'], 'pma_test');
$this->assertEquals(
'<td class="text-center">'
. '<input type="hidden" name="prev_criteria[0]" value="">'
. '<input type="text" name="criteria[0]" value="" class="textfield" '
. 'style="width: 12ex" size="20"></td><td class="text-center">'
. '<input type="hidden" name="prev_criteria[1]" value="">'
. '<input type="text" name="criteria[1]" value="" class="textfield" '
. 'style="width: 12ex" size="20"></td><td class="text-center">'
. '<input type="hidden" name="prev_criteria[2]" value="">'
. '<input type="text" name="criteria[2]" value="" class="textfield" '
. 'style="width: 12ex" size="20"></td>',
$this->callFunction($object, Qbe::class, 'getCriteriaInputboxRow', []),
);
}
public function testGetAndOrColCell(): void
{
$GLOBALS['dbi'] = $this->createDatabaseInterface();
$object = new Qbe(new Relation($GLOBALS['dbi']), new Template(), $GLOBALS['dbi'], 'pma_test');
$this->assertEquals(
'<td class="text-center"><strong>Or:</strong><input type="radio" '
. 'name="criteriaAndOrColumn[1]" value="or">&nbsp;&nbsp;<strong>And:'
. '</strong><input type="radio" name="criteriaAndOrColumn[1]" value='
. '"and"><br>Ins<input type="checkbox" name="criteriaColumnInsert'
. '[1]">&nbsp;&nbsp;Del<input type="checkbox" '
. 'name="criteriaColumnDelete[1]"></td>',
$this->callFunction($object, Qbe::class, 'getAndOrColCell', [1]),
);
}
public function testGetModifyColumnsRow(): void
{
$GLOBALS['dbi'] = $this->createDatabaseInterface();
$object = new Qbe(new Relation($GLOBALS['dbi']), new Template(), $GLOBALS['dbi'], 'pma_test');
$this->assertEquals(
'<td class="text-center"><strong>'
. 'Or:</strong><input type="radio" name="criteriaAndOrColumn[0]" value'
. '="or">&nbsp;&nbsp;<strong>And:</strong><input type="radio" name='
. '"criteriaAndOrColumn[0]" value="and" checked="checked"><br>Ins'
. '<input type="checkbox" name="criteriaColumnInsert[0]">&nbsp;&nbsp;'
. 'Del<input type="checkbox" name="criteriaColumnDelete[0]"></td><td '
. 'class="text-center"><strong>Or:</strong><input type="radio" name="'
. 'criteriaAndOrColumn[1]" value="or">&nbsp;&nbsp;<strong>And:'
. '</strong><input type="radio" name="criteriaAndOrColumn[1]" value='
. '"and" checked="checked"><br>Ins<input type="checkbox" name='
. '"criteriaColumnInsert[1]">&nbsp;&nbsp;Del<input type="checkbox" '
. 'name="criteriaColumnDelete[1]"></td><td class="text-center"><br>Ins'
. '<input type="checkbox" name="criteriaColumnInsert[2]">&nbsp;&nbsp;'
. 'Del<input type="checkbox" name="criteriaColumnDelete[2]"></td>',
$this->callFunction($object, Qbe::class, 'getModifyColumnsRow', []),
);
}
public function testGetInputboxRow(): void
{
$GLOBALS['dbi'] = $this->createDatabaseInterface();
$object = new Qbe(new Relation($GLOBALS['dbi']), new Template(), $GLOBALS['dbi'], 'pma_test');
$this->assertEquals(
'<td class="text-center"><input type="text" name="Or2[0]" value="" class='
. '"textfield" style="width: 12ex" size="20"></td><td class="text-center">'
. '<input type="text" name="Or2[1]" value="" class="textfield" '
. 'style="width: 12ex" size="20"></td><td class="text-center"><input '
. 'type="text" name="Or2[2]" value="" class="textfield" style="width: '
. '12ex" size="20"></td>',
$this->callFunction($object, Qbe::class, 'getInputboxRow', [2]),
);
}
public function testGetInsDelAndOrCriteriaRows(): void
{
$GLOBALS['dbi'] = $this->createDatabaseInterface();
$object = new Qbe(new Relation($GLOBALS['dbi']), new Template(), $GLOBALS['dbi'], 'pma_test');
$actual = $this->callFunction($object, Qbe::class, 'getInsDelAndOrCriteriaRows', [2, 3]);
$this->assertStringContainsString('<tr class="noclick">', $actual);
$this->assertStringContainsString(
'<td class="text-center"><input type="text" '
. 'name="Or0[0]" value="" class="textfield" style="width: 12ex" '
. 'size="20"></td><td class="text-center"><input type="text" name="Or0[1]" '
. 'value="" class="textfield" style="width: 12ex" size="20"></td><td '
. 'class="text-center"><input type="text" name="Or0[2]" value="" class='
. '"textfield" style="width: 12ex" size="20"></td></tr>',
$actual,
);
}
public function testGetSelectClause(): void
{
$GLOBALS['dbi'] = $this->createDatabaseInterface();
$object = new Qbe(new Relation($GLOBALS['dbi']), new Template(), $GLOBALS['dbi'], 'pma_test');
$this->assertEquals('', $this->callFunction($object, Qbe::class, 'getSelectClause', []));
}
public function testGetWhereClause(): void
{
$GLOBALS['dbi'] = $this->createDatabaseInterface();
$object = new Qbe(new Relation($GLOBALS['dbi']), new Template(), $GLOBALS['dbi'], 'pma_test');
$this->assertEquals('', $this->callFunction($object, Qbe::class, 'getWhereClause', []));
}
public function testGetOrderByClause(): void
{
$GLOBALS['dbi'] = $this->createDatabaseInterface();
$object = new Qbe(new Relation($GLOBALS['dbi']), new Template(), $GLOBALS['dbi'], 'pma_test');
$this->assertEquals('', $this->callFunction($object, Qbe::class, 'getOrderByClause', []));
}
public function testGetIndexes(): void
{
$dbiDummy = $this->createDbiDummy();
$dbiDummy->addResult('SHOW INDEXES FROM `pma_test`.```table1```', []);
$GLOBALS['dbi'] = $this->createDatabaseInterface($dbiDummy);
$object = new Qbe(new Relation($GLOBALS['dbi']), new Template(), $GLOBALS['dbi'], 'pma_test');
$this->assertEquals(
['unique' => [], 'index' => []],
$this->callFunction(
$object,
Qbe::class,
'getIndexes',
[['`table1`', 'table2'], ['column1', 'column2', 'column3'], ['column2']],
),
);
}
public function testGetLeftJoinColumnCandidates(): void
{
$dbiDummy = $this->createDbiDummy();
$dbiDummy->addSelectDb('pma_test');
$dbiDummy->addResult('SHOW INDEXES FROM `pma_test`.```table1```', []);
$GLOBALS['dbi'] = $this->createDatabaseInterface($dbiDummy);
$object = new Qbe(new Relation($GLOBALS['dbi']), new Template(), $GLOBALS['dbi'], 'pma_test');
$this->assertEquals(
[0 => 'column2'],
$this->callFunction(
$object,
Qbe::class,
'getLeftJoinColumnCandidates',
[['`table1`', 'table2'], ['column1', 'column2', 'column3'], ['column2']],
),
);
}
public function testGetMasterTable(): void
{
$GLOBALS['dbi'] = $this->createDatabaseInterface();
$object = new Qbe(new Relation($GLOBALS['dbi']), new Template(), $GLOBALS['dbi'], 'pma_test');
$this->assertEquals(
0,
$this->callFunction(
$object,
Qbe::class,
'getMasterTable',
[['table1', 'table2'], ['column1', 'column2', 'column3'], ['column2'], ['qbe_test']],
),
);
}
public function testGetWhereClauseTablesAndColumns(): void
{
$GLOBALS['dbi'] = $this->createDatabaseInterface();
$object = new Qbe(new Relation($GLOBALS['dbi']), new Template(), $GLOBALS['dbi'], 'pma_test');
$_POST['criteriaColumn'] = ['table1.id', 'table1.value', 'table1.name', 'table1.deleted'];
$this->assertEquals(
['where_clause_tables' => [], 'where_clause_columns' => []],
$this->callFunction($object, Qbe::class, 'getWhereClauseTablesAndColumns', []),
);
}
public function testGetFromClause(): void
{
$GLOBALS['db'] = 'pma_test';
$dbiDummy = $this->createDbiDummy();
$createTableStatement = 'CREATE TABLE `table1` (`id` int(11) NOT NULL,`value` int(11) NOT NULL,'
. 'PRIMARY KEY (`id`,`value`),KEY `value` (`value`)) ENGINE=InnoDB DEFAULT CHARSET=latin1';
$dbiDummy->addResult('SHOW CREATE TABLE `pma_test`.`table1`', [[$createTableStatement]]);
$dbiDummy->addSelectDb('pma_test');
$dbiDummy->addResult('SHOW CREATE TABLE `pma_test`.`table1`', [[$createTableStatement]]);
$GLOBALS['dbi'] = $this->createDatabaseInterface($dbiDummy);
$object = new Qbe(new Relation($GLOBALS['dbi']), new Template(), $GLOBALS['dbi'], 'pma_test');
$_POST['criteriaColumn'] = ['table1.id', 'table1.value', 'table1.name', 'table1.deleted'];
$this->assertEquals('`table1`', $this->callFunction($object, Qbe::class, 'getFromClause', [['`table1`.`id`']]));
}
public function testGetSQLQuery(): void
{
$GLOBALS['db'] = 'pma_test';
$dbiDummy = $this->createDbiDummy();
$createTableStatement = 'CREATE TABLE `table1` (`id` int(11) NOT NULL,`value` int(11) NOT NULL,'
. 'PRIMARY KEY (`id`,`value`),KEY `value` (`value`)) ENGINE=InnoDB DEFAULT CHARSET=latin1';
$dbiDummy->addResult('SHOW CREATE TABLE `pma_test`.`table1`', [[$createTableStatement]]);
$dbiDummy->addSelectDb('pma_test');
$dbiDummy->addResult('SHOW CREATE TABLE `pma_test`.`table1`', [[$createTableStatement]]);
$GLOBALS['dbi'] = $this->createDatabaseInterface($dbiDummy);
$object = new Qbe(new Relation($GLOBALS['dbi']), new Template(), $GLOBALS['dbi'], 'pma_test');
$_POST['criteriaColumn'] = ['table1.id', 'table1.value', 'table1.name', 'table1.deleted'];
$this->assertEquals(
'FROM `table1`' . "\n",
$this->callFunction($object, Qbe::class, 'getSQLQuery', [['`table1`.`id`']]),
);
}
}

View File

@ -1,172 +0,0 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\Tests\Selenium\Database;
use PhpMyAdmin\Tests\Selenium\TestBase;
use function trim;
/** @coversNothing */
class QueryByExampleTest extends TestBase
{
/**
* Setup the browser environment to run the selenium test case
*/
protected function setUp(): void
{
parent::setUp();
$this->dbQuery(
'USE `' . $this->databaseName . '`;'
. 'CREATE TABLE `test_table` ('
. ' `id` int(11) NOT NULL AUTO_INCREMENT,'
. ' `val` int(11) NOT NULL,'
. ' PRIMARY KEY (`id`)'
. ');'
. 'INSERT INTO `test_table` (val) VALUES (2), (6), (5), (3), (4), (4), (5);',
);
$this->login();
}
/**
* Test typing a SQL query on Server SQL page and submitting it
*/
public function testQueryByExample(): void
{
$this->navigateDatabase($this->databaseName);
$this->waitForElement('partialLinkText', 'Query')->click();
$this->waitAjax();
$this->waitForElement('partialLinkText', 'Query by example')->click();
$this->waitAjax();
/* Select Columns to be used in the query */
$this->selectByValue(
$this->waitForElement('name', 'criteriaColumn[0]'),
'`test_table`.`id`',
);
$this->selectByValue(
$this->waitForElement('name', 'criteriaColumn[1]'),
'`test_table`.`val`',
);
/* Set aliases for the columns */
$this->waitForElement('name', 'criteriaAlias[0]')->sendKeys('ID');
$this->waitForElement('name', 'criteriaAlias[1]')->sendKeys('VAL');
/* Set Sort orders */
$this->selectByLabel(
$this->waitForElement('name', 'criteriaSort[0]'),
'Descending',
);
$this->selectByLabel(
$this->waitForElement('name', 'criteriaSort[1]'),
'Ascending',
);
/* Select sort order amongst columns */
$this->selectByValue(
$this->waitForElement('name', 'criteriaSortOrder[0]'),
'2',
);
$this->selectByValue(
$this->waitForElement('name', 'criteriaSortOrder[1]'),
'1',
);
/* Set criteria conditions */
$this->waitForElement('name', 'criteria[0]')->sendKeys('> 1');
$this->waitForElement('name', 'criteria[1]')->sendKeys('< 6');
/* Change operator to AND */
/*
//TODO: Needs to be re-done
$radioElements = $this->elements(
$this->using('css selector')->sendKeys('input[name="criteriaAndOrColumn[0]"]')
);
if (count($radioElements) > 2) {
$radioElements[1]->click();
}
*/
/* Update Query in the editor */
$updateQueryButton = $this->byCssSelector('.tblFooters > input[name=modify]');
$this->scrollToElement($updateQueryButton);
$updateQueryButton->click();
$this->waitAjax();
$expected = 'SELECT `test_table`.`id` AS `ID`, `test_table`.`val` AS `VAL`'
. "\nFROM `test_table`"
. "\nWHERE ((`test_table`.`id` > 1) AND (`test_table`.`val` < 6))"
. "\nORDER BY `test_table`.`val` ASC, `test_table`.`id` DESC";
$actual = trim((string) $this->waitForElement('id', 'textSqlquery')->getAttribute('value'));
/* Compare generated query */
$this->assertEquals($expected, $actual);
/* Submit the query */
$submitButton = $this->waitForElement('cssSelector', '#tblQbeFooters > input[type=submit]');
$this->scrollToElement($submitButton);
$submitButton->click();
$this->waitAjax();
$this->waitForElement('cssSelector', 'table.table_results');
/* Assert Row 1 */
$this->assertEquals(
4,
$this->getCellByTableClass('table_results', 1, 5),
);
$this->assertEquals(
3,
$this->getCellByTableClass('table_results', 1, 6),
);
/* Assert Row 2 */
$this->assertEquals(
6,
$this->getCellByTableClass('table_results', 2, 5),
);
$this->assertEquals(
4,
$this->getCellByTableClass('table_results', 2, 6),
);
/* Assert Row 3 */
$this->assertEquals(
5,
$this->getCellByTableClass('table_results', 3, 5),
);
$this->assertEquals(
4,
$this->getCellByTableClass('table_results', 3, 6),
);
/* Assert Row 4 */
$this->assertEquals(
7,
$this->getCellByTableClass('table_results', 4, 5),
);
$this->assertEquals(
5,
$this->getCellByTableClass('table_results', 4, 6),
);
/* Assert Row 5 */
$this->assertEquals(
3,
$this->getCellByTableClass('table_results', 5, 5),
);
$this->assertEquals(
5,
$this->getCellByTableClass('table_results', 5, 6),
);
}
}

View File

@ -26,7 +26,6 @@ module.exports = [
'database/events': rootPath + '/js/src/database/events.ts',
'database/multi_table_query': rootPath + '/js/src/database/multi_table_query.ts',
'database/operations': rootPath + '/js/src/database/operations.ts',
'database/qbe': rootPath + '/js/src/database/qbe.ts',
'database/query_generator': rootPath + '/js/src/database/query_generator.ts',
'database/routines': rootPath + '/js/src/database/routines.ts',
'database/search': rootPath + '/js/src/database/search.ts',