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