diff --git a/.github/workflows/test-selenium.yml b/.github/workflows/test-selenium.yml
index 7ec7199f04..e54c265efb 100644
--- a/.github/workflows/test-selenium.yml
+++ b/.github/workflows/test-selenium.yml
@@ -65,7 +65,6 @@ jobs:
- "Database/Events"
- "Database/Operations"
- "Database/Procedures"
- - "Database/QueryByExample"
- "Database/Structure"
- "Database/Triggers"
- "Export"
diff --git a/doc/intro.rst b/doc/intro.rst
index 105e233207..3351ff1589 100644
--- a/doc/intro.rst
+++ b/doc/intro.rst
@@ -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
diff --git a/js/src/database/qbe.ts b/js/src/database/qbe.ts
deleted file mode 100644
index cd8ac6f31c..0000000000
--- a/js/src/database/qbe.ts
+++ /dev/null
@@ -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');
-});
diff --git a/libraries/classes/Config/Descriptions.php b/libraries/classes/Config/Descriptions.php
index d34e64162b..593947da48 100644
--- a/libraries/classes/Config/Descriptions.php
+++ b/libraries/classes/Config/Descriptions.php
@@ -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'),
diff --git a/libraries/classes/Controllers/Database/QueryByExampleController.php b/libraries/classes/Controllers/Database/QueryByExampleController.php
deleted file mode 100644
index fa57c4548e..0000000000
--- a/libraries/classes/Controllers/Database/QueryByExampleController.php
+++ /dev/null
@@ -1,191 +0,0 @@
-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(),
- ]);
- }
-}
diff --git a/libraries/classes/Controllers/JavaScriptMessagesController.php b/libraries/classes/Controllers/JavaScriptMessagesController.php
index a3980bb8e7..ca1ad1db49 100644
--- a/libraries/classes/Controllers/JavaScriptMessagesController.php
+++ b/libraries/classes/Controllers/JavaScriptMessagesController.php
@@ -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'
diff --git a/libraries/classes/Database/Qbe.php b/libraries/classes/Database/Qbe.php
deleted file mode 100644
index 46a8ee512e..0000000000
--- a/libraries/classes/Database/Qbe.php
+++ /dev/null
@@ -1,1729 +0,0 @@
-db = $dbname;
- $this->savedSearchList = $savedSearchList;
- $this->currentSearch = $currentSearch;
-
- $this->loadCriterias();
- // Sets criteria parameters
- $this->setSearchParams();
- $this->setCriteriaTablesAndColumns();
- }
-
- /**
- * Initialize criterias
- *
- * @return static
- */
- private function loadCriterias(): static
- {
- if ($this->currentSearch === null) {
- return $this;
- }
-
- $criterias = $this->currentSearch->getCriterias() ?? [];
- $_POST = $criterias + $_POST;
-
- return $this;
- }
-
- /**
- * Getter for current search
- */
- private function getCurrentSearch(): SavedSearches|null
- {
- return $this->currentSearch;
- }
-
- /**
- * Sets search parameters
- */
- private function setSearchParams(): void
- {
- $criteriaColumnCount = $this->initializeCriteriasCount();
-
- $this->criteriaColumnInsert = isset($_POST['criteriaColumnInsert']) && is_array($_POST['criteriaColumnInsert'])
- ? $_POST['criteriaColumnInsert']
- : null;
- $this->criteriaColumnDelete = isset($_POST['criteriaColumnDelete']) && is_array($_POST['criteriaColumnDelete'])
- ? $_POST['criteriaColumnDelete']
- : null;
-
- $this->prevCriteria = $_POST['prev_criteria'] ?? [];
- $this->criteria = $_POST['criteria'] ?? array_fill(0, $criteriaColumnCount, '');
-
- $this->criteriaRowInsert = $_POST['criteriaRowInsert'] ?? array_fill(0, $criteriaColumnCount, '');
- $this->criteriaRowDelete = $_POST['criteriaRowDelete'] ?? array_fill(0, $criteriaColumnCount, '');
- $this->criteriaAndOrRow = $_POST['criteriaAndOrRow'] ?? array_fill(0, $criteriaColumnCount, '');
- $this->criteriaAndOrColumn = $_POST['criteriaAndOrColumn'] ?? array_fill(0, $criteriaColumnCount, '');
- // sets minimum width
- $this->formColumnWidth = 12;
- $this->formColumns = [];
- $this->formSorts = [];
- $this->formShows = [];
- $this->formCriterions = [];
- $this->formAndOrRows = [];
- $this->formAndOrCols = [];
- }
-
- /**
- * Sets criteria tables and columns
- */
- private function setCriteriaTablesAndColumns(): void
- {
- // The tables list sent by a previously submitted form
- if (isset($_POST['TableList']) && is_array($_POST['TableList'])) {
- foreach ($_POST['TableList'] as $eachTable) {
- $this->criteriaTables[$eachTable] = ' selected="selected"';
- }
- }
-
- $allTables = $this->dbi->query('SHOW TABLES FROM ' . Util::backquote($this->db) . ';');
- $allTablesCount = $allTables->numRows();
- if ($allTablesCount == 0) {
- echo Message::error(__('No tables found in database.'))->getDisplay();
- exit;
- }
-
- // The tables list gets from MySQL
- foreach ($allTables->fetchAllColumn() as $table) {
- $columns = $this->dbi->getColumns($this->db, $table);
-
- if (empty($this->criteriaTables[$table]) && ! empty($_POST['TableList'])) {
- $this->criteriaTables[$table] = '';
- } else {
- $this->criteriaTables[$table] = ' selected="selected"';
- }
-
- // The fields list per selected tables
- if ($this->criteriaTables[$table] !== ' selected="selected"') {
- continue;
- }
-
- $eachTable = Util::backquote($table);
- $this->columnNames[] = $eachTable . '.*';
- foreach ($columns as $eachColumn) {
- $eachColumn = $eachTable . '.'
- . Util::backquote($eachColumn['Field']);
- $this->columnNames[] = $eachColumn;
- // increase the width if necessary
- $this->formColumnWidth = max(
- mb_strlen($eachColumn),
- $this->formColumnWidth,
- );
- }
- }
-
- // sets the largest width found
- $this->realwidth = $this->formColumnWidth . 'ex';
- }
-
- /**
- * Provides select options list containing column names
- *
- * @param int $columnNumber Column Number (0,1,2) or more
- * @param string $selected Selected criteria column name
- *
- * @return string HTML for select options
- */
- private function showColumnSelectCell(int $columnNumber, string $selected = ''): string
- {
- return $this->template->render('database/qbe/column_select_cell', [
- 'column_number' => $columnNumber,
- 'column_names' => $this->columnNames,
- 'selected' => $selected,
- ]);
- }
-
- /**
- * Provides select options list containing sort options (ASC/DESC)
- *
- * @param int $columnNumber Column Number (0,1,2) or more
- * @param string $selected Selected criteria 'ASC' or 'DESC'
- *
- * @return string HTML for select options
- */
- private function getSortSelectCell(
- int $columnNumber,
- string $selected = '',
- ): string {
- return $this->template->render('database/qbe/sort_select_cell', [
- 'real_width' => $this->realwidth,
- 'column_number' => $columnNumber,
- 'selected' => $selected,
- ]);
- }
-
- /**
- * Provides select options list containing sort order
- *
- * @param int $columnNumber Column Number (0,1,2) or more
- * @param int|null $sortOrder Sort order
- *
- * @return string HTML for select options
- */
- private function getSortOrderSelectCell(int $columnNumber, int|null $sortOrder): string
- {
- $totalColumnCount = $this->getNewColumnCount();
-
- return $this->template->render('database/qbe/sort_order_select_cell', [
- 'total_column_count' => $totalColumnCount,
- 'column_number' => $columnNumber,
- 'sort_order' => $sortOrder,
- ]);
- }
-
- /**
- * Returns the new column count after adding and removing columns as instructed
- *
- * @return int new column count
- */
- private function getNewColumnCount(): int
- {
- $totalColumnCount = $this->criteriaColumnCount;
- if (! empty($this->criteriaColumnInsert)) {
- $totalColumnCount += count($this->criteriaColumnInsert);
- }
-
- if (! empty($this->criteriaColumnDelete)) {
- $totalColumnCount -= count($this->criteriaColumnDelete);
- }
-
- return $totalColumnCount;
- }
-
- /**
- * Provides search form's row containing column select options
- *
- * @return string HTML for search table's row
- */
- private function getColumnNamesRow(): string
- {
- $htmlOutput = '';
-
- $newColumnCount = 0;
- for ($columnIndex = 0; $columnIndex < $this->criteriaColumnCount; $columnIndex++) {
- if (
- isset($this->criteriaColumnInsert[$columnIndex])
- && $this->criteriaColumnInsert[$columnIndex] === 'on'
- ) {
- $htmlOutput .= $this->showColumnSelectCell($newColumnCount);
- $newColumnCount++;
- }
-
- if (
- ! empty($this->criteriaColumnDelete)
- && isset($this->criteriaColumnDelete[$columnIndex])
- && $this->criteriaColumnDelete[$columnIndex] === 'on'
- ) {
- continue;
- }
-
- $selected = '';
- if (isset($_POST['criteriaColumn'][$columnIndex])) {
- $selected = $_POST['criteriaColumn'][$columnIndex];
- $this->formColumns[$newColumnCount] = $_POST['criteriaColumn'][$columnIndex];
- }
-
- $htmlOutput .= $this->showColumnSelectCell($newColumnCount, $selected);
- $newColumnCount++;
- }
-
- $this->newColumnCount = $newColumnCount;
-
- return $htmlOutput;
- }
-
- /**
- * Provides search form's row containing column aliases
- *
- * @return string HTML for search table's row
- */
- private function getColumnAliasRow(): string
- {
- $htmlOutput = '';
-
- $newColumnCount = 0;
-
- for ($colInd = 0; $colInd < $this->criteriaColumnCount; $colInd++) {
- if (
- ! empty($this->criteriaColumnInsert)
- && isset($this->criteriaColumnInsert[$colInd])
- && $this->criteriaColumnInsert[$colInd] === 'on'
- ) {
- $htmlOutput .= '
';
- $htmlOutput .= '';
- $htmlOutput .= ' | ';
- $newColumnCount++;
- }
-
- if (
- ! empty($this->criteriaColumnDelete)
- && isset($this->criteriaColumnDelete[$colInd])
- && $this->criteriaColumnDelete[$colInd] === 'on'
- ) {
- continue;
- }
-
- $tmpAlias = '';
- if (! empty($_POST['criteriaAlias'][$colInd])) {
- $tmpAlias = $this->formAliases[$newColumnCount] = $_POST['criteriaAlias'][$colInd];
- }
-
- $htmlOutput .= '';
- $htmlOutput .= '';
- $htmlOutput .= ' | ';
- $newColumnCount++;
- }
-
- return $htmlOutput;
- }
-
- /**
- * Provides search form's row containing sort(ASC/DESC) select options
- *
- * @return string HTML for search table's row
- */
- private function getSortRow(): string
- {
- $htmlOutput = '';
-
- $newColumnCount = 0;
-
- for ($colInd = 0; $colInd < $this->criteriaColumnCount; $colInd++) {
- if (
- ! empty($this->criteriaColumnInsert)
- && isset($this->criteriaColumnInsert[$colInd])
- && $this->criteriaColumnInsert[$colInd] === 'on'
- ) {
- $htmlOutput .= $this->getSortSelectCell($newColumnCount);
- $newColumnCount++;
- }
-
- if (
- ! empty($this->criteriaColumnDelete)
- && isset($this->criteriaColumnDelete[$colInd])
- && $this->criteriaColumnDelete[$colInd] === 'on'
- ) {
- continue;
- }
-
- // If they have chosen all fields using the * selector,
- // then sorting is not available, Fix for Bug #570698
- if (
- isset($_POST['criteriaSort'][$colInd], $_POST['criteriaColumn'][$colInd])
- && mb_substr($_POST['criteriaColumn'][$colInd], -2) === '.*'
- ) {
- $_POST['criteriaSort'][$colInd] = '';
- }
-
- $selected = '';
- if (isset($_POST['criteriaSort'][$colInd])) {
- $this->formSorts[$newColumnCount] = $_POST['criteriaSort'][$colInd];
-
- if ($_POST['criteriaSort'][$colInd] === 'ASC') {
- $selected = 'ASC';
- } elseif ($_POST['criteriaSort'][$colInd] === 'DESC') {
- $selected = 'DESC';
- }
- } else {
- $this->formSorts[$newColumnCount] = '';
- }
-
- $htmlOutput .= $this->getSortSelectCell($newColumnCount, $selected);
- $newColumnCount++;
- }
-
- return $htmlOutput;
- }
-
- /**
- * Provides search form's row containing sort order
- *
- * @return string HTML for search table's row
- */
- private function getSortOrder(): string
- {
- $htmlOutput = '';
-
- $newColumnCount = 0;
-
- for ($colInd = 0; $colInd < $this->criteriaColumnCount; $colInd++) {
- if (
- ! empty($this->criteriaColumnInsert)
- && isset($this->criteriaColumnInsert[$colInd])
- && $this->criteriaColumnInsert[$colInd] === 'on'
- ) {
- $htmlOutput .= $this->getSortOrderSelectCell($newColumnCount, null);
- $newColumnCount++;
- }
-
- if (
- ! empty($this->criteriaColumnDelete)
- && isset($this->criteriaColumnDelete[$colInd])
- && $this->criteriaColumnDelete[$colInd] === 'on'
- ) {
- continue;
- }
-
- $sortOrder = null;
- if (! empty($_POST['criteriaSortOrder'][$colInd])) {
- $sortOrder = (int) $this->formSortOrders[$newColumnCount] = $_POST['criteriaSortOrder'][$colInd];
- }
-
- $htmlOutput .= $this->getSortOrderSelectCell($newColumnCount, $sortOrder);
- $newColumnCount++;
- }
-
- return $htmlOutput;
- }
-
- /**
- * Provides search form's row containing SHOW checkboxes
- *
- * @return string HTML for search table's row
- */
- private function getShowRow(): string
- {
- $htmlOutput = '';
-
- $newColumnCount = 0;
- for ($columnIndex = 0; $columnIndex < $this->criteriaColumnCount; $columnIndex++) {
- if (
- ! empty($this->criteriaColumnInsert)
- && isset($this->criteriaColumnInsert[$columnIndex])
- && $this->criteriaColumnInsert[$columnIndex] === 'on'
- ) {
- $htmlOutput .= '';
- $htmlOutput .= '';
- $htmlOutput .= ' | ';
- $newColumnCount++;
- }
-
- if (
- ! empty($this->criteriaColumnDelete)
- && isset($this->criteriaColumnDelete[$columnIndex])
- && $this->criteriaColumnDelete[$columnIndex] === 'on'
- ) {
- continue;
- }
-
- if (isset($_POST['criteriaShow'][$columnIndex])) {
- $checkedOptions = ' checked="checked"';
- $this->formShows[$newColumnCount] = $_POST['criteriaShow'][$columnIndex];
- } else {
- $checkedOptions = '';
- }
-
- $htmlOutput .= '';
- $htmlOutput .= '';
- $htmlOutput .= ' | ';
- $newColumnCount++;
- }
-
- return $htmlOutput;
- }
-
- /**
- * Provides search form's row containing criteria Inputboxes
- *
- * @return string HTML for search table's row
- */
- private function getCriteriaInputboxRow(): string
- {
- $htmlOutput = '';
-
- $newColumnCount = 0;
- for ($columnIndex = 0; $columnIndex < $this->criteriaColumnCount; $columnIndex++) {
- if (
- ! empty($this->criteriaColumnInsert)
- && isset($this->criteriaColumnInsert[$columnIndex])
- && $this->criteriaColumnInsert[$columnIndex] === 'on'
- ) {
- $htmlOutput .= '';
- $htmlOutput .= '';
- $htmlOutput .= ' | ';
- $newColumnCount++;
- }
-
- if (
- ! empty($this->criteriaColumnDelete)
- && isset($this->criteriaColumnDelete[$columnIndex])
- && $this->criteriaColumnDelete[$columnIndex] === 'on'
- ) {
- continue;
- }
-
- $tmpCriteria = '';
- if (isset($this->criteria[$columnIndex])) {
- $tmpCriteria = $this->criteria[$columnIndex];
- }
-
- if (
- ($this->prevCriteria === []
- || ! isset($this->prevCriteria[$columnIndex]))
- || $this->prevCriteria[$columnIndex] != $tmpCriteria
- ) {
- $this->formCriterions[$newColumnCount] = $tmpCriteria;
- } else {
- $this->formCriterions[$newColumnCount] = $this->prevCriteria[$columnIndex];
- }
-
- $htmlOutput .= '';
- $htmlOutput .= '';
- $htmlOutput .= '';
- $htmlOutput .= ' | ';
- $newColumnCount++;
- }
-
- return $htmlOutput;
- }
-
- /**
- * Provides And/Or modification cell along with Insert/Delete options
- * (For modifying search form's table columns)
- *
- * @param int $columnNumber Column Number (0,1,2) or more
- * @param mixed[]|null $selected Selected criteria column name
- * @param bool $lastColumn Whether this is the last column
- *
- * @return string HTML for modification cell
- */
- private function getAndOrColCell(
- int $columnNumber,
- array|null $selected = null,
- bool $lastColumn = false,
- ): string {
- $htmlOutput = '';
- if (! $lastColumn) {
- $htmlOutput .= '' . __('Or:') . '';
- $htmlOutput .= '';
- $htmlOutput .= ' ' . __('And:') . '';
- $htmlOutput .= '';
- }
-
- $htmlOutput .= ' ' . __('Ins');
- $htmlOutput .= '';
- $htmlOutput .= ' ' . __('Del');
- $htmlOutput .= '';
- $htmlOutput .= ' | ';
-
- return $htmlOutput;
- }
-
- /**
- * Provides search form's row containing column modifications options
- * (For modifying search form's table columns)
- *
- * @return string HTML for search table's row
- */
- private function getModifyColumnsRow(): string
- {
- $htmlOutput = '';
-
- $newColumnCount = 0;
- for ($columnIndex = 0; $columnIndex < $this->criteriaColumnCount; $columnIndex++) {
- if (
- ! empty($this->criteriaColumnInsert)
- && isset($this->criteriaColumnInsert[$columnIndex])
- && $this->criteriaColumnInsert[$columnIndex] === 'on'
- ) {
- $htmlOutput .= $this->getAndOrColCell($newColumnCount);
- $newColumnCount++;
- }
-
- if (
- ! empty($this->criteriaColumnDelete)
- && isset($this->criteriaColumnDelete[$columnIndex])
- && $this->criteriaColumnDelete[$columnIndex] === 'on'
- ) {
- continue;
- }
-
- if (isset($this->criteriaAndOrColumn[$columnIndex])) {
- $this->formAndOrCols[$newColumnCount] = $this->criteriaAndOrColumn[$columnIndex];
- }
-
- $checkedOptions = [];
- if (isset($this->criteriaAndOrColumn[$columnIndex]) && $this->criteriaAndOrColumn[$columnIndex] === 'or') {
- $checkedOptions['or'] = ' checked="checked"';
- $checkedOptions['and'] = '';
- } else {
- $checkedOptions['and'] = ' checked="checked"';
- $checkedOptions['or'] = '';
- }
-
- $htmlOutput .= $this->getAndOrColCell(
- $newColumnCount,
- $checkedOptions,
- $columnIndex + 1 === $this->criteriaColumnCount,
- );
- $newColumnCount++;
- }
-
- return $htmlOutput;
- }
-
- /**
- * Provides rows for criteria inputbox Insert/Delete options
- * with AND/OR relationship modification options
- *
- * @param int $newRowIndex New row index if rows are added/deleted
- *
- * @return string HTML table rows
- */
- private function getInputboxRow(int $newRowIndex): string
- {
- $htmlOutput = '';
- $newColumnCount = 0;
- for ($columnIndex = 0; $columnIndex < $this->criteriaColumnCount; $columnIndex++) {
- if (
- ! empty($this->criteriaColumnInsert)
- && isset($this->criteriaColumnInsert[$columnIndex])
- && $this->criteriaColumnInsert[$columnIndex] === 'on'
- ) {
- $orFieldName = 'Or' . $newRowIndex . '[' . $newColumnCount . ']';
- $htmlOutput .= '';
- $htmlOutput .= '';
- $htmlOutput .= ' | ';
- $newColumnCount++;
- }
-
- if (
- ! empty($this->criteriaColumnDelete)
- && isset($this->criteriaColumnDelete[$columnIndex])
- && $this->criteriaColumnDelete[$columnIndex] === 'on'
- ) {
- continue;
- }
-
- $or = 'Or' . $newRowIndex;
- if (! empty($_POST[$or]) && isset($_POST[$or][$columnIndex])) {
- $tmpOr = $_POST[$or][$columnIndex];
- } else {
- $tmpOr = '';
- }
-
- $htmlOutput .= '';
- $htmlOutput .= '';
- $htmlOutput .= ' | ';
- if (! empty(${$or}) && isset(${$or}[$columnIndex])) {
- $GLOBALS[${'cur' . $or}][$newColumnCount] = ${$or}[$columnIndex];
- }
-
- $newColumnCount++;
- }
-
- return $htmlOutput;
- }
-
- /**
- * Provides rows for criteria inputbox Insert/Delete options
- * with AND/OR relationship modification options
- *
- * @return string HTML table rows
- */
- private function getInsDelAndOrCriteriaRows(): string
- {
- $htmlOutput = '';
- $newRowCount = 0;
- $checkedOptions = [];
- for ($rowIndex = 0; $rowIndex <= $this->criteriaRowCount; $rowIndex++) {
- if (isset($this->criteriaRowInsert[$rowIndex]) && $this->criteriaRowInsert[$rowIndex] === 'on') {
- $checkedOptions['or'] = true;
- $checkedOptions['and'] = false;
- $htmlOutput .= '';
- $htmlOutput .= $this->template->render('database/qbe/ins_del_and_or_cell', [
- 'row_index' => $newRowCount,
- 'checked_options' => $checkedOptions,
- ]);
- $htmlOutput .= $this->getInputboxRow($newRowCount);
- $newRowCount++;
- $htmlOutput .= '
';
- }
-
- if (isset($this->criteriaRowDelete[$rowIndex]) && $this->criteriaRowDelete[$rowIndex] === 'on') {
- continue;
- }
-
- if (isset($this->criteriaAndOrRow[$rowIndex])) {
- $this->formAndOrRows[$newRowCount] = $this->criteriaAndOrRow[$rowIndex];
- }
-
- if (isset($this->criteriaAndOrRow[$rowIndex]) && $this->criteriaAndOrRow[$rowIndex] === 'and') {
- $checkedOptions['and'] = true;
- $checkedOptions['or'] = false;
- } else {
- $checkedOptions['or'] = true;
- $checkedOptions['and'] = false;
- }
-
- $htmlOutput .= '';
- $htmlOutput .= $this->template->render('database/qbe/ins_del_and_or_cell', [
- 'row_index' => $newRowCount,
- 'checked_options' => $checkedOptions,
- ]);
- $htmlOutput .= $this->getInputboxRow($newRowCount);
- $newRowCount++;
- $htmlOutput .= '
';
- }
-
- $this->newRowCount = $newRowCount;
-
- return $htmlOutput;
- }
-
- /**
- * Provides SELECT clause for building SQL query
- *
- * @return string Select clause
- */
- private function getSelectClause(): string
- {
- $selectClauses = [];
- for ($columnIndex = 0; $columnIndex < $this->criteriaColumnCount; $columnIndex++) {
- if (
- empty($this->formColumns[$columnIndex])
- || ! isset($this->formShows[$columnIndex])
- || $this->formShows[$columnIndex] !== 'on'
- ) {
- continue;
- }
-
- $select = $this->formColumns[$columnIndex];
- if (! empty($this->formAliases[$columnIndex])) {
- $select .= ' AS ' . Util::backquote($this->formAliases[$columnIndex]);
- }
-
- $selectClauses[] = $select;
- }
-
- if ($selectClauses !== []) {
- return 'SELECT ' . implode(', ', $selectClauses) . "\n";
- }
-
- return '';
- }
-
- /**
- * Provides WHERE clause for building SQL query
- *
- * @return string Where clause
- */
- private function getWhereClause(): string
- {
- $whereClause = '';
- $criteriaCount = 0;
- for ($columnIndex = 0; $columnIndex < $this->criteriaColumnCount; $columnIndex++) {
- if (
- isset($lastWhere)
- && ! empty($this->formColumns[$columnIndex])
- && ! empty($this->formCriterions[$columnIndex])
- && $columnIndex
- ) {
- $whereClause .= ' '
- . mb_strtoupper($this->formAndOrCols[$lastWhere])
- . ' ';
- }
-
- if (empty($this->formColumns[$columnIndex]) || empty($this->formCriterions[$columnIndex])) {
- continue;
- }
-
- $whereClause .= '(' . $this->formColumns[$columnIndex] . ' '
- . $this->formCriterions[$columnIndex] . ')';
- $lastWhere = $columnIndex;
- $criteriaCount++;
- }
-
- if ($criteriaCount > 1) {
- $whereClause = '(' . $whereClause . ')';
- }
-
- // OR rows ${'cur' . $or}[$column_index]
- if (! isset($this->formAndOrRows)) {
- $this->formAndOrRows = [];
- }
-
- for ($rowIndex = 0; $rowIndex <= $this->criteriaRowCount; $rowIndex++) {
- $criteriaCount = 0;
- $queryOrWhere = '';
- $lastOrWhere = '';
- for ($columnIndex = 0; $columnIndex < $this->criteriaColumnCount; $columnIndex++) {
- if (
- ! empty($this->formColumns[$columnIndex])
- && ! empty($_POST['Or' . $rowIndex][$columnIndex])
- && $columnIndex
- ) {
- $queryOrWhere .= ' '
- . mb_strtoupper($this->formAndOrCols[$lastOrWhere])
- . ' ';
- }
-
- if (empty($this->formColumns[$columnIndex]) || empty($_POST['Or' . $rowIndex][$columnIndex])) {
- continue;
- }
-
- $queryOrWhere .= '(' . $this->formColumns[$columnIndex]
- . ' '
- . $_POST['Or' . $rowIndex][$columnIndex]
- . ')';
- $lastOrWhere = $columnIndex;
- $criteriaCount++;
- }
-
- if ($criteriaCount > 1) {
- $queryOrWhere = '(' . $queryOrWhere . ')';
- }
-
- if (empty($queryOrWhere)) {
- continue;
- }
-
- $whereClause .= "\n"
- . mb_strtoupper(isset($this->formAndOrRows[$rowIndex]) ? $this->formAndOrRows[$rowIndex] . ' ' : '')
- . $queryOrWhere;
- }
-
- if ($whereClause !== '' && $whereClause !== '()') {
- return 'WHERE ' . $whereClause . "\n";
- }
-
- return $whereClause;
- }
-
- /**
- * Provides ORDER BY clause for building SQL query
- *
- * @return string Order By clause
- */
- private function getOrderByClause(): string
- {
- $orderByClauses = [];
-
- // Create copy of instance variables
- $columns = $this->formColumns;
- $sort = $this->formSorts;
- $sortOrder = $this->formSortOrders;
- if ($sortOrder !== [] && count($sortOrder) === count($sort) && count($sortOrder) === count($columns)) {
- // Sort all three arrays based on sort order
- array_multisort($sortOrder, $sort, $columns);
- }
-
- for ($columnIndex = 0; $columnIndex < $this->criteriaColumnCount; $columnIndex++) {
- // if all columns are chosen with * selector,
- // then sorting isn't available
- // Fix for Bug #570698
- if (empty($columns[$columnIndex]) && empty($sort[$columnIndex])) {
- continue;
- }
-
- if (mb_substr($columns[$columnIndex], -2) === '.*') {
- continue;
- }
-
- if (empty($sort[$columnIndex])) {
- continue;
- }
-
- $orderByClauses[] = $columns[$columnIndex] . ' '
- . $sort[$columnIndex];
- }
-
- if ($orderByClauses !== []) {
- return 'ORDER BY ' . implode(', ', $orderByClauses) . "\n";
- }
-
- return '';
- }
-
- /**
- * Provides UNIQUE columns and INDEX columns present in criteria tables
- *
- * @param mixed[] $searchTables Tables involved in the search
- * @param mixed[] $searchColumns Columns involved in the search
- * @param mixed[] $whereClauseColumns Columns having criteria where clause
- *
- * @return mixed[] having UNIQUE and INDEX columns
- */
- private function getIndexes(
- array $searchTables,
- array $searchColumns,
- array $whereClauseColumns,
- ): array {
- $uniqueColumns = [];
- $indexColumns = [];
-
- foreach ($searchTables as $table) {
- $indexes = $this->dbi->getTableIndexes($this->db, $table);
- foreach ($indexes as $index) {
- $column = $table . '.' . $index['Column_name'];
- if (! isset($searchColumns[$column])) {
- continue;
- }
-
- if ($index['Non_unique'] == 0) {
- if (isset($whereClauseColumns[$column])) {
- $uniqueColumns[$column] = 'Y';
- } else {
- $uniqueColumns[$column] = 'N';
- }
- } elseif (isset($whereClauseColumns[$column])) {
- $indexColumns[$column] = 'Y';
- } else {
- $indexColumns[$column] = 'N';
- }
- }
- }
-
- return ['unique' => $uniqueColumns, 'index' => $indexColumns];
- }
-
- /**
- * Provides UNIQUE columns and INDEX columns present in criteria tables
- *
- * @param mixed[] $searchTables Tables involved in the search
- * @param mixed[] $searchColumns Columns involved in the search
- * @param mixed[] $whereClauseColumns Columns having criteria where clause
- *
- * @return mixed[] having UNIQUE and INDEX columns
- */
- private function getLeftJoinColumnCandidates(
- array $searchTables,
- array $searchColumns,
- array $whereClauseColumns,
- ): array {
- $this->dbi->selectDb($this->db);
-
- // Get unique columns and index columns
- $indexes = $this->getIndexes($searchTables, $searchColumns, $whereClauseColumns);
- $uniqueColumns = $indexes['unique'];
- $indexColumns = $indexes['index'];
-
- [$candidateColumns, $needSort] = $this->getLeftJoinColumnCandidatesBest(
- $searchTables,
- $whereClauseColumns,
- $uniqueColumns,
- $indexColumns,
- );
-
- // If we came up with $unique_columns (very good) or $index_columns (still
- // good) as $candidate_columns we want to check if we have any 'Y' there
- // (that would mean that they were also found in the whereclauses
- // which would be great). if yes, we take only those
- if ($needSort != 1) {
- return $candidateColumns;
- }
-
- $veryGood = [];
- $stillGood = [];
- foreach ($candidateColumns as $column => $isWhere) {
- $table = explode('.', $column);
- $table = $table[0];
- if ($isWhere === 'Y') {
- $veryGood[$column] = $table;
- } else {
- $stillGood[$column] = $table;
- }
- }
-
- if ($veryGood !== []) {
- $candidateColumns = $veryGood;
- // Candidates restricted in index+where
- } else {
- $candidateColumns = $stillGood;
- // None of the candidates were in a where-clause
- }
-
- return $candidateColumns;
- }
-
- /**
- * Provides the main table to form the LEFT JOIN clause
- *
- * @param mixed[] $searchTables Tables involved in the search
- * @param mixed[] $searchColumns Columns involved in the search
- * @param mixed[] $whereClauseColumns Columns having criteria where clause
- * @param mixed[] $whereClauseTables Tables having criteria where clause
- *
- * @return string table name
- */
- private function getMasterTable(
- array $searchTables,
- array $searchColumns,
- array $whereClauseColumns,
- array $whereClauseTables,
- ): string {
- if (count($whereClauseTables) === 1) {
- // If there is exactly one column that has a decent where-clause
- // we will just use this
- return (string) key($whereClauseTables);
- }
-
- // Now let's find out which of the tables has an index
- // (When the control user is the same as the normal user
- // because they are using one of their databases as pmadb,
- // the last db selected is not always the one where we need to work)
- $candidateColumns = $this->getLeftJoinColumnCandidates($searchTables, $searchColumns, $whereClauseColumns);
-
- // Generally, we need to display all the rows of foreign (referenced)
- // table, whether they have any matching row in child table or not.
- // So we select candidate tables which are foreign tables.
- $foreignTables = [];
- foreach ($candidateColumns as $oneTable) {
- $foreigners = $this->relation->getForeigners($this->db, $oneTable);
- foreach ($foreigners as $key => $foreigner) {
- if ($key !== 'foreign_keys_data') {
- if (in_array($foreigner['foreign_table'], $candidateColumns)) {
- $foreignTables[$foreigner['foreign_table']] = $foreigner['foreign_table'];
- }
-
- continue;
- }
-
- foreach ($foreigner as $oneKey) {
- if (! in_array($oneKey['ref_table_name'], $candidateColumns)) {
- continue;
- }
-
- $foreignTables[$oneKey['ref_table_name']] = $oneKey['ref_table_name'];
- }
- }
- }
-
- if ($foreignTables !== []) {
- $candidateColumns = $foreignTables;
- }
-
- // If our array of candidates has more than one member we'll just
- // find the smallest table.
- // Of course the actual query would be faster if we check for
- // the Criteria which gives the smallest result set in its table,
- // but it would take too much time to check this
- if (count($candidateColumns) <= 1) {
- // Only one single candidate
- return reset($candidateColumns);
- }
-
- // Of course we only want to check each table once
- $checkedTables = $candidateColumns;
- $tsize = [];
- $maxsize = -1;
- $result = '';
- foreach ($candidateColumns as $table) {
- if ($checkedTables[$table] != 1) {
- $tableObj = new Table($table, $this->db, $this->dbi);
- $tsize[$table] = $tableObj->countRecords();
- $checkedTables[$table] = 1;
- }
-
- if ($tsize[$table] <= $maxsize) {
- continue;
- }
-
- $maxsize = $tsize[$table];
- $result = $table;
- }
-
- // Return largest table
- return $result;
- }
-
- /**
- * Provides columns and tables that have valid where clause criteria
- *
- * @return mixed[]
- */
- private function getWhereClauseTablesAndColumns(): array
- {
- $whereClauseColumns = [];
- $whereClauseTables = [];
-
- // Now we need all tables that we have in the where clause
- for ($columnIndex = 0, $nb = count($this->criteria); $columnIndex < $nb; $columnIndex++) {
- $currentTable = explode('.', $_POST['criteriaColumn'][$columnIndex]);
- if (empty($currentTable[0]) || empty($currentTable[1])) {
- continue;
- }
-
- $table = str_replace('`', '', $currentTable[0]);
- $column = str_replace('`', '', $currentTable[1]);
- $column = $table . '.' . $column;
- // Now we know that our array has the same numbers as $criteria
- // we can check which of our columns has a where clause
- if (empty($this->criteria[$columnIndex])) {
- continue;
- }
-
- if (
- mb_substr($this->criteria[$columnIndex], 0, 1) !== '='
- && stripos($this->criteria[$columnIndex], 'is') === false
- ) {
- continue;
- }
-
- $whereClauseColumns[$column] = $column;
- $whereClauseTables[$table] = $table;
- }
-
- return ['where_clause_tables' => $whereClauseTables, 'where_clause_columns' => $whereClauseColumns];
- }
-
- /**
- * Provides FROM clause for building SQL query
- *
- * @param mixed[] $formColumns List of selected columns in the form
- *
- * @return string FROM clause
- */
- private function getFromClause(array $formColumns): string
- {
- if ($formColumns === []) {
- return '';
- }
-
- // Initialize some variables
- $searchTables = $searchColumns = [];
-
- // We only start this if we have fields, otherwise it would be dumb
- foreach ($formColumns as $value) {
- $parts = explode('.', $value);
- if (empty($parts[0]) || empty($parts[1])) {
- continue;
- }
-
- $table = str_replace('`', '', $parts[0]);
- $searchTables[$table] = $table;
- $searchColumns[] = $table . '.' . str_replace('`', '', $parts[1]);
- }
-
- // Create LEFT JOINS out of Relations
- $fromClause = $this->getJoinForFromClause($searchTables, $searchColumns);
-
- // In case relations are not defined, just generate the FROM clause
- // from the list of tables, however we don't generate any JOIN
- if ($fromClause === '') {
- // Create cartesian product
- return implode(
- ', ',
- array_map(Util::backquote(...), $searchTables),
- );
- }
-
- return $fromClause;
- }
-
- /**
- * Formulates the WHERE clause by JOINing tables
- *
- * @param mixed[] $searchTables Tables involved in the search
- * @param mixed[] $searchColumns Columns involved in the search
- *
- * @return string table name
- */
- private function getJoinForFromClause(array $searchTables, array $searchColumns): string
- {
- // $relations[master_table][foreign_table] => clause
- $relations = [];
-
- // Fill $relations with inter table relationship data
- foreach ($searchTables as $oneTable) {
- $this->loadRelationsForTable($relations, $oneTable);
- }
-
- // Get tables and columns with valid where clauses
- $validWhereClauses = $this->getWhereClauseTablesAndColumns();
- $whereClauseTables = $validWhereClauses['where_clause_tables'];
- $whereClauseColumns = $validWhereClauses['where_clause_columns'];
-
- // Get master table
- $master = $this->getMasterTable($searchTables, $searchColumns, $whereClauseColumns, $whereClauseTables);
-
- // Will include master tables and all tables that can be combined into
- // a cluster by their relation
- $finalized = [];
- if (strlen($master) > 0) {
- // Add master tables
- $finalized[$master] = '';
- }
-
- // Fill the $finalized array with JOIN clauses for each table
- $this->fillJoinClauses($finalized, $relations, $searchTables);
-
- // JOIN clause
- $join = '';
-
- // Tables that can not be combined with the table cluster
- // which includes master table
- $unfinalized = array_diff($searchTables, array_keys($finalized));
- if ($unfinalized !== []) {
- // We need to look for intermediary tables to JOIN unfinalized tables
- // Heuristic to chose intermediary tables is to look for tables
- // having relationships with unfinalized tables
- foreach ($unfinalized as $oneTable) {
- $references = $this->relation->getChildReferences($this->db, $oneTable);
- foreach ($references as $columnReferences) {
- foreach ($columnReferences as $reference) {
- // Only from this schema
- if ($reference['table_schema'] != $this->db) {
- continue;
- }
-
- $table = $reference['table_name'];
-
- $this->loadRelationsForTable($relations, $table);
-
- // Make copies
- $tempFinalized = $finalized;
- $tempSearchTables = $searchTables;
- $tempSearchTables[] = $table;
-
- // Try joining with the added table
- $this->fillJoinClauses($tempFinalized, $relations, $tempSearchTables);
-
- $tempUnfinalized = array_diff(
- $tempSearchTables,
- array_keys($tempFinalized),
- );
- // Take greedy approach.
- // If the unfinalized count drops we keep the new table
- // and switch temporary varibles with the original ones
- if (count($tempUnfinalized) < count($unfinalized)) {
- $finalized = $tempFinalized;
- $searchTables = $tempSearchTables;
- }
-
- // We are done if no unfinalized tables anymore
- if ($tempUnfinalized === []) {
- break 3;
- }
- }
- }
- }
-
- $unfinalized = array_diff($searchTables, array_keys($finalized));
- // If there are still unfinalized tables
- if ($unfinalized !== []) {
- // Add these tables as cartesian product before joined tables
- $join .= implode(
- ', ',
- array_map(Util::backquote(...), $unfinalized),
- );
- }
- }
-
- $first = true;
- // Add joined tables
- foreach ($finalized as $table => $clause) {
- if ($first) {
- if (! empty($join)) {
- $join .= ', ';
- }
-
- $join .= Util::backquote($table);
- $first = false;
- } else {
- $join .= "\n LEFT JOIN " . Util::backquote($table) . ' ON ' . $clause;
- }
- }
-
- return $join;
- }
-
- /**
- * Loads relations for a given table into the $relations array
- *
- * @param mixed[] $relations
- * @param string $oneTable the table
- */
- private function loadRelationsForTable(array &$relations, string $oneTable): void
- {
- $relations[$oneTable] = [];
-
- $foreigners = $this->relation->getForeigners($GLOBALS['db'], $oneTable);
- foreach ($foreigners as $field => $foreigner) {
- // Foreign keys data
- if ($field === 'foreign_keys_data') {
- foreach ($foreigner as $oneKey) {
- $clauses = [];
- // There may be multiple column relations
- foreach ($oneKey['index_list'] as $index => $oneField) {
- $clauses[] = Util::backquote($oneTable) . '.'
- . Util::backquote($oneField) . ' = '
- . Util::backquote($oneKey['ref_table_name']) . '.'
- . Util::backquote($oneKey['ref_index_list'][$index]);
- }
-
- // Combine multiple column relations with AND
- $relations[$oneTable][$oneKey['ref_table_name']] = implode(' AND ', $clauses);
- }
- } else { // Internal relations
- $relations[$oneTable][$foreigner['foreign_table']] = Util::backquote($oneTable) . '.'
- . Util::backquote((string) $field) . ' = '
- . Util::backquote($foreigner['foreign_table']) . '.'
- . Util::backquote($foreigner['foreign_field']);
- }
- }
- }
-
- /**
- * Fills the $finalized arrays with JOIN clauses for each of the tables
- *
- * @param mixed[] $finalized JOIN clauses for each table
- * @param mixed[] $relations Relations among tables
- * @param mixed[] $searchTables Tables involved in the search
- */
- private function fillJoinClauses(array &$finalized, array $relations, array $searchTables): void
- {
- while (true) {
- $added = false;
- foreach ($searchTables as $masterTable) {
- $foreignData = $relations[$masterTable];
- foreach ($foreignData as $foreignTable => $clause) {
- if (! isset($finalized[$masterTable]) && isset($finalized[$foreignTable])) {
- $finalized[$masterTable] = $clause;
- $added = true;
- } elseif (
- ! isset($finalized[$foreignTable])
- && isset($finalized[$masterTable])
- && in_array($foreignTable, $searchTables)
- ) {
- $finalized[$foreignTable] = $clause;
- $added = true;
- }
-
- if (! $added) {
- continue;
- }
-
- // We are done if all tables are in $finalized
- if (count($finalized) === count($searchTables)) {
- return;
- }
- }
- }
-
- // If no new tables were added during this iteration, break;
- if (! $added) {
- return;
- }
- }
- }
-
- /**
- * Provides the generated SQL query
- *
- * @param mixed[] $formColumns List of selected columns in the form
- *
- * @return string SQL query
- */
- private function getSQLQuery(array $formColumns): string
- {
- $sqlQuery = '';
- // get SELECT clause
- $sqlQuery .= $this->getSelectClause();
- // get FROM clause
- $fromClause = $this->getFromClause($formColumns);
- if ($fromClause !== '') {
- $sqlQuery .= 'FROM ' . $fromClause . "\n";
- }
-
- // get WHERE clause
- $sqlQuery .= $this->getWhereClause();
- // get ORDER BY clause
- $sqlQuery .= $this->getOrderByClause();
-
- return $sqlQuery;
- }
-
- public function getSelectionForm(): string
- {
- $relationParameters = $this->relation->getRelationParameters();
- $savedSearchesField = $relationParameters->savedQueryByExampleSearchesFeature !== null
- ? $this->getSavedSearchesField()
- : '';
-
- $columnNamesRow = $this->getColumnNamesRow();
- $columnAliasRow = $this->getColumnAliasRow();
- $showRow = $this->getShowRow();
- $sortRow = $this->getSortRow();
- $sortOrder = $this->getSortOrder();
- $criteriaInputBoxRow = $this->getCriteriaInputboxRow();
- $insDelAndOrCriteriaRows = $this->getInsDelAndOrCriteriaRows();
- $modifyColumnsRow = $this->getModifyColumnsRow();
-
- $this->newRowCount--;
- $urlParams = [];
- $urlParams['db'] = $this->db;
- $urlParams['criteriaColumnCount'] = $this->newColumnCount;
- $urlParams['rows'] = $this->newRowCount;
-
- $sqlQuery = $this->getSQLQuery($this->formColumns);
-
- return $this->template->render('database/qbe/selection_form', [
- 'db' => $this->db,
- 'url_params' => $urlParams,
- 'db_link' => Generator::getDbLink($this->db),
- 'criteria_tables' => $this->criteriaTables,
- 'saved_searches_field' => $savedSearchesField,
- 'column_names_row' => $columnNamesRow,
- 'column_alias_row' => $columnAliasRow,
- 'show_row' => $showRow,
- 'sort_row' => $sortRow,
- 'sort_order' => $sortOrder,
- 'criteria_input_box_row' => $criteriaInputBoxRow,
- 'ins_del_and_or_criteria_rows' => $insDelAndOrCriteriaRows,
- 'modify_columns_row' => $modifyColumnsRow,
- 'sql_query' => $sqlQuery,
- ]);
- }
-
- /**
- * Get fields to display
- */
- private function getSavedSearchesField(): string
- {
- $htmlOutput = __('Saved bookmarked search:');
- $htmlOutput .= ' ';
- $htmlOutput .= '';
- $htmlOutput .= '';
- $htmlOutput .= '';
- if ($currentSearchId !== null) {
- $htmlOutput .= '';
- $htmlOutput .= '';
- }
-
- return $htmlOutput;
- }
-
- /**
- * Initialize _criteria_column_count
- *
- * @return int Previous number of columns
- */
- private function initializeCriteriasCount(): int
- {
- // sets column count
- $criteriaColumnCount = isset($_POST['criteriaColumnCount']) && is_numeric($_POST['criteriaColumnCount'])
- ? (int) $_POST['criteriaColumnCount']
- : 3;
- $criteriaColumnAdd = isset($_POST['criteriaColumnAdd']) && is_numeric($_POST['criteriaColumnAdd'])
- ? (int) $_POST['criteriaColumnAdd']
- : 0;
- $this->criteriaColumnCount = max($criteriaColumnCount + $criteriaColumnAdd, 0);
-
- // sets row count
- $rows = isset($_POST['rows']) && is_numeric($_POST['rows']) ? (int) $_POST['rows'] : 0;
- $criteriaRowAdd = isset($_POST['criteriaRowAdd']) && is_numeric($_POST['criteriaRowAdd'])
- ? (int) $_POST['criteriaRowAdd']
- : 0;
- $this->criteriaRowCount = min(
- 100,
- max($rows + $criteriaRowAdd, 0),
- );
-
- return $criteriaColumnCount;
- }
-
- /**
- * Get best
- *
- * @param mixed[] $searchTables Tables involved in the search
- * @param mixed[]|null $whereClauseColumns Columns with where clause
- * @param mixed[]|null $uniqueColumns Unique columns
- * @param mixed[]|null $indexColumns Indexed columns
- *
- * @return mixed[]
- */
- private function getLeftJoinColumnCandidatesBest(
- array $searchTables,
- array|null $whereClauseColumns,
- array|null $uniqueColumns,
- array|null $indexColumns,
- ): array {
- // now we want to find the best.
- if (isset($uniqueColumns) && $uniqueColumns !== []) {
- $candidateColumns = $uniqueColumns;
- $needSort = 1;
-
- return [$candidateColumns, $needSort];
- }
-
- if (isset($indexColumns) && $indexColumns !== []) {
- $candidateColumns = $indexColumns;
- $needSort = 1;
-
- return [$candidateColumns, $needSort];
- }
-
- if (isset($whereClauseColumns) && $whereClauseColumns !== []) {
- $candidateColumns = $whereClauseColumns;
- $needSort = 0;
-
- return [$candidateColumns, $needSort];
- }
-
- $candidateColumns = $searchTables;
- $needSort = 0;
-
- return [$candidateColumns, $needSort];
- }
-}
diff --git a/libraries/classes/Exceptions/SavedSearchesException.php b/libraries/classes/Exceptions/SavedSearchesException.php
deleted file mode 100644
index 6331fad670..0000000000
--- a/libraries/classes/Exceptions/SavedSearchesException.php
+++ /dev/null
@@ -1,11 +0,0 @@
-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();
- }
-}
diff --git a/libraries/routes.php b/libraries/routes.php
index 9eadbe66bd..f384ec45ae 100644
--- a/libraries/routes.php
+++ b/libraries/routes.php
@@ -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 {
diff --git a/libraries/services_controllers.php b/libraries/services_controllers.php
index de89dc03fb..394869c3f8 100644
--- a/libraries/services_controllers.php
+++ b/libraries/services_controllers.php
@@ -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' => [
diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon
index 0e000be74b..d7ce321683 100644
--- a/phpstan-baseline.neon
+++ b/phpstan-baseline.neon
@@ -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
diff --git a/psalm-baseline.xml b/psalm-baseline.xml
index 1753df3915..7d21c3adf6 100644
--- a/psalm-baseline.xml
+++ b/psalm-baseline.xml
@@ -1202,28 +1202,6 @@
__construct
-
-
-
-
-
- getParsedBodyParam('db')]]>
- getParsedBodyParam('searchName')]]>
- $searchId
-
-
-
- $action
- $searchId
-
-
- getParsedBody()]]>
- getParsedBody()]]>
-
-
- __construct
-
-
@@ -5245,180 +5223,6 @@
isSuccess
-
-
- criteriaTables]]>
- criteriaTables]]>
- criteriaTables]]>
-
-
- $column
- $columns[$columnIndex]
-
-
-
- $indexColumns
- $name
- $oneField
-
-
- $oneTable
- $oneTable
- $oneTable
- $table
- $table
- $table
- criteria[$columnIndex]]]>
- criteria[$columnIndex]]]>
- formAliases[$columnIndex]]]>
- formAndOrCols[$lastOrWhere]]]>
- formAndOrCols[$lastWhere]]]>
- formCriterions[$newColumnCount]]]>
- $tmpCriteria
- $uniqueColumns
- $value
- $whereClauseColumns
- $whereClauseTables
-
-
- $selectClauses
- $table
- $table
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- $checkedTables[$table]
- $finalized[$foreignTable]
- $finalized[$foreignTable]
- $finalized[$foreignTable]
- $finalized[$masterTable]
- $finalized[$masterTable]
- $finalized[$masterTable]
-
-
-
- $relations[$masterTable]
-
-
- formAndOrCols[$lastWhere]]]>
- $tsize[$table]
-
-
- $tsize[$table]
-
-
-
- $clause
- $clause
- $column
- $columnReferences
- $finalized[$foreignTable]
- $finalized[$masterTable]
- $foreignData
- $foreignTable
-
-
- $foreigner
- $foreigner
- $index
- $indexColumns
- $isWhere
- $masterTable
- $name
- $oneField
- $oneKey
- $oneKey
- $oneTable
- $oneTable
- $oneTable
- $reference
- $result
- $select
- $selectClauses[]
- $table
- $table
- $table
- $tmpCriteria
- $uniqueColumns
- $value
- $whereClauseColumns
- $whereClauseTables
-
-
- mixed[]
- string
-
-
- $clause
- $columns[$columnIndex]
- $select
-
-
- $sort[$columnIndex]
- formAndOrRows[$rowIndex]]]>
- formColumns[$columnIndex]]]>
- formColumns[$columnIndex]]]>
- formCriterions[$columnIndex]]]>
-
-
- $candidateColumns
- $result
- $result
- reset($candidateColumns)
- reset($candidateColumns)
-
-
-
-
- $selected
- $tmpAlias
- $tmpOr
-
-
-
- $selected
-
-
-
-
-
-
-
-
-
-
-
-
-
- $table
-
-
-
-
-
- formAndOrRows)]]>
-
-
- formSortOrders[$newColumnCount] = $_POST['criteriaSortOrder'][$colInd]]]>
-
-
escapeString
@@ -11918,33 +11722,6 @@
-
-
-
-
-
- $data[$field]
-
- criterias]]>
-
-
-
-
-
-
-
-
-
-
- getId()]]>
- getId()]]>
-
-
- bool
- bool
- bool
-
-
@@ -14342,20 +14119,6 @@
$result
-
-
- $actual
- callFunction($object, Qbe::class, 'getSortRow', [])]]>
- callFunction($object, Qbe::class, 'getSortRow', [])]]>
- callFunction($object, Qbe::class, 'getSortRow', [])]]>
- callFunction($object, Qbe::class, 'getSortSelectCell', [1, 'ASC'])]]>
- callFunction($object, Qbe::class, 'getSortSelectCell', [1])]]>
- callFunction($object, Qbe::class, 'getSortSelectCell', [1])]]>
-
-
- $actual
-
-
$_POST[$key]
diff --git a/public/themes/bootstrap/scss/_common.scss b/public/themes/bootstrap/scss/_common.scss
index 3c8b568279..cb07104456 100644
--- a/public/themes/bootstrap/scss/_common.scss
+++ b/public/themes/bootstrap/scss/_common.scss
@@ -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;
diff --git a/public/themes/metro/scss/_common.scss b/public/themes/metro/scss/_common.scss
index 05fdfc6c78..bc808314cb 100644
--- a/public/themes/metro/scss/_common.scss
+++ b/public/themes/metro/scss/_common.scss
@@ -1047,11 +1047,6 @@ li {
clear: both;
}
-#qbe_div_table_list,
-#qbe_div_sql_query {
- float: left;
-}
-
kbd {
color: $body-color;
background-color: transparent;
diff --git a/public/themes/original/scss/_common.scss b/public/themes/original/scss/_common.scss
index 485ed2bd8c..8ca9552175 100644
--- a/public/themes/original/scss/_common.scss
+++ b/public/themes/original/scss/_common.scss
@@ -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;
diff --git a/public/themes/pmahomme/scss/_common.scss b/public/themes/pmahomme/scss/_common.scss
index 2805dba570..9b26fa7ed9 100644
--- a/public/themes/pmahomme/scss/_common.scss
+++ b/public/themes/pmahomme/scss/_common.scss
@@ -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;
diff --git a/templates/database/multi_table_query/form.twig b/templates/database/multi_table_query/form.twig
index 9197cac466..0919cab8f8 100644
--- a/templates/database/multi_table_query/form.twig
+++ b/templates/database/multi_table_query/form.twig
@@ -1,17 +1,3 @@
-
-