| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796 | 
							- <?php
 
- /**
 
-  * Handles DB QBE search
 
-  */
 
- declare(strict_types=1);
 
- namespace PhpMyAdmin\Database;
 
- use PhpMyAdmin\ConfigStorage\Relation;
 
- use PhpMyAdmin\DatabaseInterface;
 
- use PhpMyAdmin\Html\Generator;
 
- use PhpMyAdmin\Message;
 
- use PhpMyAdmin\SavedSearches;
 
- use PhpMyAdmin\Table;
 
- use PhpMyAdmin\Template;
 
- use PhpMyAdmin\Util;
 
- use function __;
 
- use function array_diff;
 
- use function array_fill;
 
- use function array_keys;
 
- use function array_map;
 
- use function array_multisort;
 
- use function count;
 
- use function explode;
 
- use function htmlspecialchars;
 
- use function implode;
 
- use function in_array;
 
- use function is_array;
 
- use function is_numeric;
 
- use function key;
 
- use function max;
 
- use function mb_strlen;
 
- use function mb_strtoupper;
 
- use function mb_substr;
 
- use function min;
 
- use function reset;
 
- use function str_replace;
 
- use function stripos;
 
- use function strlen;
 
- /**
 
-  * Class to handle database QBE search
 
-  */
 
- class Qbe
 
- {
 
-     /**
 
-      * Database name
 
-      *
 
-      * @var string
 
-      */
 
-     private $db;
 
-     /**
 
-      * Table Names (selected/non-selected)
 
-      *
 
-      * @var array
 
-      */
 
-     private $criteriaTables;
 
-     /**
 
-      * Column Names
 
-      *
 
-      * @var array
 
-      */
 
-     private $columnNames;
 
-     /**
 
-      * Number of columns
 
-      *
 
-      * @var int
 
-      */
 
-     private $criteriaColumnCount;
 
-     /**
 
-      * Number of Rows
 
-      *
 
-      * @var int
 
-      */
 
-     private $criteriaRowCount;
 
-     /**
 
-      * Whether to insert a new column
 
-      *
 
-      * @var array|null
 
-      */
 
-     private $criteriaColumnInsert;
 
-     /**
 
-      * Whether to delete a column
 
-      *
 
-      * @var array|null
 
-      */
 
-     private $criteriaColumnDelete;
 
-     /**
 
-      * Whether to insert a new row
 
-      *
 
-      * @var array
 
-      */
 
-     private $criteriaRowInsert;
 
-     /**
 
-      * Whether to delete a row
 
-      *
 
-      * @var array
 
-      */
 
-     private $criteriaRowDelete;
 
-     /**
 
-      * Already set criteria values
 
-      *
 
-      * @var array
 
-      */
 
-     private $criteria;
 
-     /**
 
-      * Previously set criteria values
 
-      *
 
-      * @var array
 
-      */
 
-     private $prevCriteria;
 
-     /**
 
-      * AND/OR relation b/w criteria columns
 
-      *
 
-      * @var array
 
-      */
 
-     private $criteriaAndOrColumn;
 
-     /**
 
-      * AND/OR relation b/w criteria rows
 
-      *
 
-      * @var array
 
-      */
 
-     private $criteriaAndOrRow;
 
-     /**
 
-      * Large width of a column
 
-      *
 
-      * @var string
 
-      */
 
-     private $realwidth;
 
-     /**
 
-      * Minimum width of a column
 
-      *
 
-      * @var int
 
-      */
 
-     private $formColumnWidth;
 
-     /**
 
-      * Selected columns in the form
 
-      *
 
-      * @var array
 
-      */
 
-     private $formColumns;
 
-     /**
 
-      * Entered aliases in the form
 
-      *
 
-      * @var array
 
-      */
 
-     private $formAliases;
 
-     /**
 
-      * Chosen sort options in the form
 
-      *
 
-      * @var array
 
-      */
 
-     private $formSorts;
 
-     /**
 
-      * Chosen sort orders in the form
 
-      *
 
-      * @var array
 
-      */
 
-     private $formSortOrders;
 
-     /**
 
-      * Show checkboxes in the form
 
-      *
 
-      * @var array
 
-      */
 
-     private $formShows;
 
-     /**
 
-      * Entered criteria values in the form
 
-      *
 
-      * @var array
 
-      */
 
-     private $formCriterions;
 
-     /**
 
-      * AND/OR column radio buttons in the form
 
-      *
 
-      * @var array
 
-      */
 
-     private $formAndOrCols;
 
-     /**
 
-      * AND/OR row radio buttons in the form
 
-      *
 
-      * @var array
 
-      */
 
-     private $formAndOrRows;
 
-     /**
 
-      * New column count in case of add/delete
 
-      *
 
-      * @var int
 
-      */
 
-     private $newColumnCount;
 
-     /**
 
-      * New row count in case of add/delete
 
-      *
 
-      * @var int
 
-      */
 
-     private $newRowCount;
 
-     /**
 
-      * List of saved searches
 
-      *
 
-      * @var array
 
-      */
 
-     private $savedSearchList = null;
 
-     /**
 
-      * Current search
 
-      *
 
-      * @var SavedSearches|null
 
-      */
 
-     private $currentSearch = null;
 
-     /** @var Relation */
 
-     private $relation;
 
-     /** @var DatabaseInterface */
 
-     public $dbi;
 
-     /** @var Template */
 
-     public $template;
 
-     /**
 
-      * @param Relation           $relation        Relation object
 
-      * @param Template           $template        Template object
 
-      * @param DatabaseInterface  $dbi             DatabaseInterface object
 
-      * @param string             $dbname          Database name
 
-      * @param array              $savedSearchList List of saved searches
 
-      * @param SavedSearches|null $currentSearch   Current search id
 
-      */
 
-     public function __construct(
 
-         Relation $relation,
 
-         Template $template,
 
-         $dbi,
 
-         $dbname,
 
-         array $savedSearchList = [],
 
-         $currentSearch = null
 
-     ) {
 
-         $this->db = $dbname;
 
-         $this->savedSearchList = $savedSearchList;
 
-         $this->currentSearch = $currentSearch;
 
-         $this->dbi = $dbi;
 
-         $this->relation = $relation;
 
-         $this->template = $template;
 
-         $this->loadCriterias();
 
-         // Sets criteria parameters
 
-         $this->setSearchParams();
 
-         $this->setCriteriaTablesAndColumns();
 
-     }
 
-     /**
 
-      * Initialize criterias
 
-      *
 
-      * @return static
 
-      */
 
-     private function loadCriterias()
 
-     {
 
-         if ($this->currentSearch === null || $this->currentSearch->getCriterias() === null) {
 
-             return $this;
 
-         }
 
-         $criterias = $this->currentSearch->getCriterias();
 
-         $_POST = $criterias + $_POST;
 
-         return $this;
 
-     }
 
-     /**
 
-      * Getter for current search
 
-      *
 
-      * @return SavedSearches|null
 
-      */
 
-     private function getCurrentSearch()
 
-     {
 
-         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($columnNumber, $selected = '')
 
-     {
 
-         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(
 
-         $columnNumber,
 
-         $selected = ''
 
-     ) {
 
-         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 $sortOrder    Sort order
 
-      *
 
-      * @return string HTML for select options
 
-      */
 
-     private function getSortOrderSelectCell($columnNumber, $sortOrder)
 
-     {
 
-         $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()
 
-     {
 
-         $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()
 
-     {
 
-         $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()
 
-     {
 
-         $htmlOutput = '';
 
-         $newColumnCount = 0;
 
-         for ($colInd = 0; $colInd < $this->criteriaColumnCount; $colInd++) {
 
-             if (
 
-                 ! empty($this->criteriaColumnInsert)
 
-                 && isset($this->criteriaColumnInsert[$colInd])
 
-                 && $this->criteriaColumnInsert[$colInd] === 'on'
 
-             ) {
 
-                 $htmlOutput .= '<td class="text-center">';
 
-                 $htmlOutput .= '<input type="text"'
 
-                     . ' name="criteriaAlias[' . $newColumnCount . ']">';
 
-                 $htmlOutput .= '</td>';
 
-                 $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 .= '<td class="text-center">';
 
-             $htmlOutput .= '<input type="text"'
 
-                 . ' name="criteriaAlias[' . $newColumnCount . ']"'
 
-                 . ' value="' . htmlspecialchars($tmpAlias) . '">';
 
-             $htmlOutput .= '</td>';
 
-             $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()
 
-     {
 
-         $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()
 
-     {
 
-         $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 = $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()
 
-     {
 
-         $htmlOutput = '';
 
-         $newColumnCount = 0;
 
-         for ($columnIndex = 0; $columnIndex < $this->criteriaColumnCount; $columnIndex++) {
 
-             if (
 
-                 ! empty($this->criteriaColumnInsert)
 
-                 && isset($this->criteriaColumnInsert[$columnIndex])
 
-                 && $this->criteriaColumnInsert[$columnIndex] === 'on'
 
-             ) {
 
-                 $htmlOutput .= '<td class="text-center">';
 
-                 $htmlOutput .= '<input type="checkbox"'
 
-                     . ' name="criteriaShow[' . $newColumnCount . ']">';
 
-                 $htmlOutput .= '</td>';
 
-                 $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 .= '<td class="text-center">';
 
-             $htmlOutput .= '<input type="checkbox"'
 
-                 . ' name="criteriaShow[' . $newColumnCount . ']"'
 
-                 . $checkedOptions . '>';
 
-             $htmlOutput .= '</td>';
 
-             $newColumnCount++;
 
-         }
 
-         return $htmlOutput;
 
-     }
 
-     /**
 
-      * Provides search form's row containing criteria Inputboxes
 
-      *
 
-      * @return string HTML for search table's row
 
-      */
 
-     private function getCriteriaInputboxRow()
 
-     {
 
-         $htmlOutput = '';
 
-         $newColumnCount = 0;
 
-         for ($columnIndex = 0; $columnIndex < $this->criteriaColumnCount; $columnIndex++) {
 
-             if (
 
-                 ! empty($this->criteriaColumnInsert)
 
-                 && isset($this->criteriaColumnInsert[$columnIndex])
 
-                 && $this->criteriaColumnInsert[$columnIndex] === 'on'
 
-             ) {
 
-                 $htmlOutput .= '<td class="text-center">';
 
-                 $htmlOutput .= '<input type="text"'
 
-                     . ' name="criteria[' . $newColumnCount . ']"'
 
-                     . ' class="textfield"'
 
-                     . ' style="width: ' . $this->realwidth . '"'
 
-                     . ' size="20">';
 
-                 $htmlOutput .= '</td>';
 
-                 $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 (
 
-                 (empty($this->prevCriteria)
 
-                 || ! isset($this->prevCriteria[$columnIndex]))
 
-                 || $this->prevCriteria[$columnIndex] != $tmpCriteria
 
-             ) {
 
-                 $this->formCriterions[$newColumnCount] = $tmpCriteria;
 
-             } else {
 
-                 $this->formCriterions[$newColumnCount] = $this->prevCriteria[$columnIndex];
 
-             }
 
-             $htmlOutput .= '<td class="text-center">';
 
-             $htmlOutput .= '<input type="hidden"'
 
-                 . ' name="prev_criteria[' . $newColumnCount . ']"'
 
-                 . ' value="'
 
-                 . htmlspecialchars($this->formCriterions[$newColumnCount])
 
-                 . '">';
 
-             $htmlOutput .= '<input type="text"'
 
-                 . ' name="criteria[' . $newColumnCount . ']"'
 
-                 . ' value="' . htmlspecialchars($tmpCriteria) . '"'
 
-                 . ' class="textfield"'
 
-                 . ' style="width: ' . $this->realwidth . '"'
 
-                 . ' size="20">';
 
-             $htmlOutput .= '</td>';
 
-             $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 array|null $selected     Selected criteria column name
 
-      * @param bool       $lastColumn   Whether this is the last column
 
-      *
 
-      * @return string HTML for modification cell
 
-      */
 
-     private function getAndOrColCell(
 
-         $columnNumber,
 
-         $selected = null,
 
-         $lastColumn = false
 
-     ) {
 
-         $htmlOutput = '<td class="text-center">';
 
-         if (! $lastColumn) {
 
-             $htmlOutput .= '<strong>' . __('Or:') . '</strong>';
 
-             $htmlOutput .= '<input type="radio"'
 
-                 . ' name="criteriaAndOrColumn[' . $columnNumber . ']"'
 
-                 . ' value="or"' . ($selected['or'] ?? '') . '>';
 
-             $htmlOutput .= '  <strong>' . __('And:') . '</strong>';
 
-             $htmlOutput .= '<input type="radio"'
 
-                 . ' name="criteriaAndOrColumn[' . $columnNumber . ']"'
 
-                 . ' value="and"' . ($selected['and'] ?? '') . '>';
 
-         }
 
-         $htmlOutput .= '<br>' . __('Ins');
 
-         $htmlOutput .= '<input type="checkbox"'
 
-             . ' name="criteriaColumnInsert[' . $columnNumber . ']">';
 
-         $htmlOutput .= '  ' . __('Del');
 
-         $htmlOutput .= '<input type="checkbox"'
 
-             . ' name="criteriaColumnDelete[' . $columnNumber . ']">';
 
-         $htmlOutput .= '</td>';
 
-         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()
 
-     {
 
-         $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($newRowIndex)
 
-     {
 
-         $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 .= '<td class="text-center">';
 
-                 $htmlOutput .= '<input type="text"'
 
-                     . ' name="Or' . $orFieldName . '" class="textfield"'
 
-                     . ' style="width: ' . $this->realwidth . '" size="20">';
 
-                 $htmlOutput .= '</td>';
 
-                 $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 .= '<td class="text-center">';
 
-             $htmlOutput .= '<input type="text"'
 
-                 . ' name="Or' . $newRowIndex . '[' . $newColumnCount . ']"'
 
-                 . ' value="' . htmlspecialchars($tmpOr) . '" class="textfield"'
 
-                 . ' style="width: ' . $this->realwidth . '" size="20">';
 
-             $htmlOutput .= '</td>';
 
-             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()
 
-     {
 
-         $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 .= '<tr class="noclick">';
 
-                 $htmlOutput .= $this->template->render('database/qbe/ins_del_and_or_cell', [
 
-                     'row_index' => $newRowCount,
 
-                     'checked_options' => $checkedOptions,
 
-                 ]);
 
-                 $htmlOutput .= $this->getInputboxRow($newRowCount);
 
-                 $newRowCount++;
 
-                 $htmlOutput .= '</tr>';
 
-             }
 
-             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 .= '<tr class="noclick">';
 
-             $htmlOutput .= $this->template->render('database/qbe/ins_del_and_or_cell', [
 
-                 'row_index' => $newRowCount,
 
-                 'checked_options' => $checkedOptions,
 
-             ]);
 
-             $htmlOutput .= $this->getInputboxRow($newRowCount);
 
-             $newRowCount++;
 
-             $htmlOutput .= '</tr>';
 
-         }
 
-         $this->newRowCount = $newRowCount;
 
-         return $htmlOutput;
 
-     }
 
-     /**
 
-      * Provides SELECT clause for building SQL query
 
-      *
 
-      * @return string Select clause
 
-      */
 
-     private function getSelectClause()
 
-     {
 
-         $selectClause = '';
 
-         $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 (! empty($selectClauses)) {
 
-             $selectClause = 'SELECT ' . implode(', ', $selectClauses) . "\n";
 
-         }
 
-         return $selectClause;
 
-     }
 
-     /**
 
-      * Provides WHERE clause for building SQL query
 
-      *
 
-      * @return string Where clause
 
-      */
 
-     private function getWhereClause()
 
-     {
 
-         $whereClause = '';
 
-         $criteriaCount = 0;
 
-         for ($columnIndex = 0; $columnIndex < $this->criteriaColumnCount; $columnIndex++) {
 
-             if (
 
-                 isset($lastWhere, $this->formAndOrCols)
 
-                 && ! 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 (! empty($whereClause) && $whereClause !== '()') {
 
-             $whereClause = 'WHERE ' . $whereClause . "\n";
 
-         }
 
-         return $whereClause;
 
-     }
 
-     /**
 
-      * Provides ORDER BY clause for building SQL query
 
-      *
 
-      * @return string Order By clause
 
-      */
 
-     private function getOrderByClause()
 
-     {
 
-         $orderByClause = '';
 
-         $orderByClauses = [];
 
-         // Create copy of instance variables
 
-         $columns = $this->formColumns;
 
-         $sort = $this->formSorts;
 
-         $sortOrder = $this->formSortOrders;
 
-         if (! empty($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 (! empty($orderByClauses)) {
 
-             $orderByClause = 'ORDER BY ' . implode(', ', $orderByClauses) . "\n";
 
-         }
 
-         return $orderByClause;
 
-     }
 
-     /**
 
-      * Provides UNIQUE columns and INDEX columns present in criteria tables
 
-      *
 
-      * @param array $searchTables       Tables involved in the search
 
-      * @param array $searchColumns      Columns involved in the search
 
-      * @param array $whereClauseColumns Columns having criteria where clause
 
-      *
 
-      * @return array having UNIQUE and INDEX columns
 
-      */
 
-     private function getIndexes(
 
-         array $searchTables,
 
-         array $searchColumns,
 
-         array $whereClauseColumns
 
-     ) {
 
-         $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';
 
-                     }
 
-                 } else {
 
-                     if (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 array $searchTables       Tables involved in the search
 
-      * @param array $searchColumns      Columns involved in the search
 
-      * @param array $whereClauseColumns Columns having criteria where clause
 
-      *
 
-      * @return array having UNIQUE and INDEX columns
 
-      */
 
-     private function getLeftJoinColumnCandidates(
 
-         array $searchTables,
 
-         array $searchColumns,
 
-         array $whereClauseColumns
 
-     ) {
 
-         $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 (count($veryGood) > 0) {
 
-             $candidateColumns = $veryGood;
 
-             // Candidates restricted in index+where
 
-         } else {
 
-             $candidateColumns = $stillGood;
 
-             // None of the candidates where in a where-clause
 
-         }
 
-         return $candidateColumns;
 
-     }
 
-     /**
 
-      * Provides the main table to form the LEFT JOIN clause
 
-      *
 
-      * @param array $searchTables       Tables involved in the search
 
-      * @param array $searchColumns      Columns involved in the search
 
-      * @param array $whereClauseColumns Columns having criteria where clause
 
-      * @param array $whereClauseTables  Tables having criteria where clause
 
-      *
 
-      * @return string table name
 
-      */
 
-     private function getMasterTable(
 
-         array $searchTables,
 
-         array $searchColumns,
 
-         array $whereClauseColumns,
 
-         array $whereClauseTables
 
-     ) {
 
-         if (count($whereClauseTables) === 1) {
 
-             // If there is exactly one column that has a decent where-clause
 
-             // we will just use this
 
-             return 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 (count($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);
 
-                 $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 array
 
-      */
 
-     private function getWhereClauseTablesAndColumns()
 
-     {
 
-         $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 array $formColumns List of selected columns in the form
 
-      *
 
-      * @return string FROM clause
 
-      */
 
-     private function getFromClause(array $formColumns)
 
-     {
 
-         $fromClause = '';
 
-         if (empty($formColumns)) {
 
-             return $fromClause;
 
-         }
 
-         // 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 (empty($fromClause)) {
 
-             // Create cartesian product
 
-             $fromClause = implode(
 
-                 ', ',
 
-                 array_map([Util::class, 'backquote'], $searchTables)
 
-             );
 
-         }
 
-         return $fromClause;
 
-     }
 
-     /**
 
-      * Formulates the WHERE clause by JOINing tables
 
-      *
 
-      * @param array $searchTables  Tables involved in the search
 
-      * @param array $searchColumns Columns involved in the search
 
-      *
 
-      * @return string table name
 
-      */
 
-     private function getJoinForFromClause(array $searchTables, array $searchColumns)
 
-     {
 
-         // $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((string) $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 (count($unfinalized) > 0) {
 
-             // 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 (count($tempUnfinalized) === 0) {
 
-                             break 3;
 
-                         }
 
-                     }
 
-                 }
 
-             }
 
-             $unfinalized = array_diff($searchTables, array_keys($finalized));
 
-             // If there are still unfinalized tables
 
-             if (count($unfinalized) > 0) {
 
-                 // Add these tables as cartesian product before joined tables
 
-                 $join .= implode(
 
-                     ', ',
 
-                     array_map([Util::class, '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 array  $relations array of relations
 
-      * @param string $oneTable  the table
 
-      */
 
-     private function loadRelationsForTable(array &$relations, $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 array $finalized    JOIN clauses for each table
 
-      * @param array $relations    Relations among tables
 
-      * @param array $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 array $formColumns List of selected columns in the form
 
-      *
 
-      * @return string SQL query
 
-      */
 
-     private function getSQLQuery(array $formColumns)
 
-     {
 
-         $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;
 
-         if (empty($this->formColumns)) {
 
-             $this->formColumns = [];
 
-         }
 
-         $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
 
-      *
 
-      * @return string
 
-      */
 
-     private function getSavedSearchesField()
 
-     {
 
-         $htmlOutput = __('Saved bookmarked search:');
 
-         $htmlOutput .= ' <select name="searchId" id="searchId">';
 
-         $htmlOutput .= '<option value="">' . __('New bookmark') . '</option>';
 
-         $currentSearch = $this->getCurrentSearch();
 
-         $currentSearchId = null;
 
-         $currentSearchName = null;
 
-         if ($currentSearch !== null) {
 
-             $currentSearchId = $currentSearch->getId();
 
-             $currentSearchName = $currentSearch->getSearchName();
 
-         }
 
-         foreach ($this->savedSearchList as $id => $name) {
 
-             $htmlOutput .= '<option value="' . htmlspecialchars((string) $id)
 
-                 . '" ' . (
 
-                 $id == $currentSearchId
 
-                     ? 'selected="selected" '
 
-                     : ''
 
-                 )
 
-                 . '>'
 
-                 . htmlspecialchars($name)
 
-                 . '</option>';
 
-         }
 
-         $htmlOutput .= '</select>';
 
-         $htmlOutput .= '<input type="text" name="searchName" id="searchName" '
 
-             . 'value="' . htmlspecialchars((string) $currentSearchName) . '">';
 
-         $htmlOutput .= '<input type="hidden" name="action" id="action" value="">';
 
-         $htmlOutput .= '<input class="btn btn-secondary" type="submit" name="saveSearch" id="saveSearch" '
 
-             . 'value="' . __('Create bookmark') . '">';
 
-         if ($currentSearchId !== null) {
 
-             $htmlOutput .= '<input class="btn btn-secondary" type="submit" name="updateSearch" '
 
-                 . 'id="updateSearch" value="' . __('Update bookmark') . '">';
 
-             $htmlOutput .= '<input class="btn btn-secondary" type="submit" name="deleteSearch" '
 
-                 . 'id="deleteSearch" value="' . __('Delete bookmark') . '">';
 
-         }
 
-         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 array      $searchTables       Tables involved in the search
 
-      * @param array|null $whereClauseColumns Columns with where clause
 
-      * @param array|null $uniqueColumns      Unique columns
 
-      * @param array|null $indexColumns       Indexed columns
 
-      *
 
-      * @return array
 
-      */
 
-     private function getLeftJoinColumnCandidatesBest(
 
-         array $searchTables,
 
-         ?array $whereClauseColumns,
 
-         ?array $uniqueColumns,
 
-         ?array $indexColumns
 
-     ) {
 
-         // now we want to find the best.
 
-         if (isset($uniqueColumns) && count($uniqueColumns) > 0) {
 
-             $candidateColumns = $uniqueColumns;
 
-             $needSort = 1;
 
-             return [
 
-                 $candidateColumns,
 
-                 $needSort,
 
-             ];
 
-         }
 
-         if (isset($indexColumns) && count($indexColumns) > 0) {
 
-             $candidateColumns = $indexColumns;
 
-             $needSort = 1;
 
-             return [
 
-                 $candidateColumns,
 
-                 $needSort,
 
-             ];
 
-         }
 
-         if (isset($whereClauseColumns) && count($whereClauseColumns) > 0) {
 
-             $candidateColumns = $whereClauseColumns;
 
-             $needSort = 0;
 
-             return [
 
-                 $candidateColumns,
 
-                 $needSort,
 
-             ];
 
-         }
 
-         $candidateColumns = $searchTables;
 
-         $needSort = 0;
 
-         return [
 
-             $candidateColumns,
 
-             $needSort,
 
-         ];
 
-     }
 
- }
 
 
  |