Qbe.php 67 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961
  1. <?php
  2. /* vim: set expandtab sw=4 ts=4 sts=4: */
  3. /**
  4. * Handles DB QBE search
  5. *
  6. * @package PhpMyAdmin
  7. */
  8. namespace PhpMyAdmin\Database;
  9. use PhpMyAdmin\Core;
  10. use PhpMyAdmin\DatabaseInterface;
  11. use PhpMyAdmin\Message;
  12. use PhpMyAdmin\Relation;
  13. use PhpMyAdmin\Table;
  14. use PhpMyAdmin\Template;
  15. use PhpMyAdmin\Url;
  16. use PhpMyAdmin\Util;
  17. /**
  18. * Class to handle database QBE search
  19. *
  20. * @package PhpMyAdmin
  21. */
  22. class Qbe
  23. {
  24. /**
  25. * Database name
  26. *
  27. * @access private
  28. * @var string
  29. */
  30. private $_db;
  31. /**
  32. * Table Names (selected/non-selected)
  33. *
  34. * @access private
  35. * @var array
  36. */
  37. private $_criteriaTables;
  38. /**
  39. * Column Names
  40. *
  41. * @access private
  42. * @var array
  43. */
  44. private $_columnNames;
  45. /**
  46. * Number of columns
  47. *
  48. * @access private
  49. * @var integer
  50. */
  51. private $_criteria_column_count;
  52. /**
  53. * Number of Rows
  54. *
  55. * @access private
  56. * @var integer
  57. */
  58. private $_criteria_row_count;
  59. /**
  60. * Whether to insert a new column
  61. *
  62. * @access private
  63. * @var array
  64. */
  65. private $_criteriaColumnInsert;
  66. /**
  67. * Whether to delete a column
  68. *
  69. * @access private
  70. * @var array
  71. */
  72. private $_criteriaColumnDelete;
  73. /**
  74. * Whether to insert a new row
  75. *
  76. * @access private
  77. * @var array
  78. */
  79. private $_criteriaRowInsert;
  80. /**
  81. * Whether to delete a row
  82. *
  83. * @access private
  84. * @var array
  85. */
  86. private $_criteriaRowDelete;
  87. /**
  88. * Already set criteria values
  89. *
  90. * @access private
  91. * @var array
  92. */
  93. private $_criteria;
  94. /**
  95. * Previously set criteria values
  96. *
  97. * @access private
  98. * @var array
  99. */
  100. private $_prev_criteria;
  101. /**
  102. * AND/OR relation b/w criteria columns
  103. *
  104. * @access private
  105. * @var array
  106. */
  107. private $_criteriaAndOrColumn;
  108. /**
  109. * AND/OR relation b/w criteria rows
  110. *
  111. * @access private
  112. * @var array
  113. */
  114. private $_criteriaAndOrRow;
  115. /**
  116. * Large width of a column
  117. *
  118. * @access private
  119. * @var string
  120. */
  121. private $_realwidth;
  122. /**
  123. * Minimum width of a column
  124. *
  125. * @access private
  126. * @var int
  127. */
  128. private $_form_column_width;
  129. /**
  130. * Selected columns in the form
  131. *
  132. * @access private
  133. * @var array
  134. */
  135. private $_formColumns;
  136. /**
  137. * Entered aliases in the form
  138. *
  139. * @access private
  140. * @var array
  141. */
  142. private $_formAliases;
  143. /**
  144. * Chosen sort options in the form
  145. *
  146. * @access private
  147. * @var array
  148. */
  149. private $_formSorts;
  150. /**
  151. * Chosen sort orders in the form
  152. *
  153. * @access private
  154. * @var array
  155. */
  156. private $_formSortOrders;
  157. /**
  158. * Show checkboxes in the form
  159. *
  160. * @access private
  161. * @var array
  162. */
  163. private $_formShows;
  164. /**
  165. * Entered criteria values in the form
  166. *
  167. * @access private
  168. * @var array
  169. */
  170. private $_formCriterions;
  171. /**
  172. * AND/OR column radio buttons in the form
  173. *
  174. * @access private
  175. * @var array
  176. */
  177. private $_formAndOrCols;
  178. /**
  179. * AND/OR row radio buttons in the form
  180. *
  181. * @access private
  182. * @var array
  183. */
  184. private $_formAndOrRows;
  185. /**
  186. * New column count in case of add/delete
  187. *
  188. * @access private
  189. * @var integer
  190. */
  191. private $_new_column_count;
  192. /**
  193. * New row count in case of add/delete
  194. *
  195. * @access private
  196. * @var integer
  197. */
  198. private $_new_row_count;
  199. /**
  200. * List of saved searches
  201. *
  202. * @access private
  203. * @var array
  204. */
  205. private $_savedSearchList = null;
  206. /**
  207. * Current search
  208. *
  209. * @access private
  210. * @var SavedSearches
  211. */
  212. private $_currentSearch = null;
  213. /**
  214. * @var Relation $relation
  215. */
  216. private $relation;
  217. /**
  218. * Public Constructor
  219. *
  220. * @param string $dbname Database name
  221. * @param array $savedSearchList List of saved searches
  222. * @param SavedSearches $currentSearch Current search id
  223. */
  224. public function __construct(
  225. $dbname,
  226. array $savedSearchList = array(),
  227. $currentSearch = null
  228. ) {
  229. $this->_db = $dbname;
  230. $this->_savedSearchList = $savedSearchList;
  231. $this->_currentSearch = $currentSearch;
  232. $this->_loadCriterias();
  233. // Sets criteria parameters
  234. $this->_setSearchParams();
  235. $this->_setCriteriaTablesAndColumns();
  236. $this->relation = new Relation();
  237. }
  238. /**
  239. * Initialize criterias
  240. *
  241. * @return static
  242. */
  243. private function _loadCriterias()
  244. {
  245. if (null === $this->_currentSearch
  246. || null === $this->_currentSearch->getCriterias()
  247. ) {
  248. return $this;
  249. }
  250. $criterias = $this->_currentSearch->getCriterias();
  251. $_POST = $criterias + $_POST;
  252. return $this;
  253. }
  254. /**
  255. * Getter for current search
  256. *
  257. * @return SavedSearches
  258. */
  259. private function _getCurrentSearch()
  260. {
  261. return $this->_currentSearch;
  262. }
  263. /**
  264. * Sets search parameters
  265. *
  266. * @return void
  267. */
  268. private function _setSearchParams()
  269. {
  270. $criteriaColumnCount = $this->_initializeCriteriasCount();
  271. $this->_criteriaColumnInsert = Core::ifSetOr(
  272. $_POST['criteriaColumnInsert'],
  273. null,
  274. 'array'
  275. );
  276. $this->_criteriaColumnDelete = Core::ifSetOr(
  277. $_POST['criteriaColumnDelete'],
  278. null,
  279. 'array'
  280. );
  281. $this->_prev_criteria = isset($_POST['prev_criteria'])
  282. ? $_POST['prev_criteria']
  283. : array();
  284. $this->_criteria = isset($_POST['criteria'])
  285. ? $_POST['criteria']
  286. : array_fill(0, $criteriaColumnCount, '');
  287. $this->_criteriaRowInsert = isset($_POST['criteriaRowInsert'])
  288. ? $_POST['criteriaRowInsert']
  289. : array_fill(0, $criteriaColumnCount, '');
  290. $this->_criteriaRowDelete = isset($_POST['criteriaRowDelete'])
  291. ? $_POST['criteriaRowDelete']
  292. : array_fill(0, $criteriaColumnCount, '');
  293. $this->_criteriaAndOrRow = isset($_POST['criteriaAndOrRow'])
  294. ? $_POST['criteriaAndOrRow']
  295. : array_fill(0, $criteriaColumnCount, '');
  296. $this->_criteriaAndOrColumn = isset($_POST['criteriaAndOrColumn'])
  297. ? $_POST['criteriaAndOrColumn']
  298. : array_fill(0, $criteriaColumnCount, '');
  299. // sets minimum width
  300. $this->_form_column_width = 12;
  301. $this->_formColumns = array();
  302. $this->_formSorts = array();
  303. $this->_formShows = array();
  304. $this->_formCriterions = array();
  305. $this->_formAndOrRows = array();
  306. $this->_formAndOrCols = array();
  307. }
  308. /**
  309. * Sets criteria tables and columns
  310. *
  311. * @return void
  312. */
  313. private function _setCriteriaTablesAndColumns()
  314. {
  315. // The tables list sent by a previously submitted form
  316. if (Core::isValid($_POST['TableList'], 'array')) {
  317. foreach ($_POST['TableList'] as $each_table) {
  318. $this->_criteriaTables[$each_table] = ' selected="selected"';
  319. }
  320. } // end if
  321. $all_tables = $GLOBALS['dbi']->query(
  322. 'SHOW TABLES FROM ' . Util::backquote($this->_db) . ';',
  323. DatabaseInterface::CONNECT_USER,
  324. DatabaseInterface::QUERY_STORE
  325. );
  326. $all_tables_count = $GLOBALS['dbi']->numRows($all_tables);
  327. if (0 == $all_tables_count) {
  328. Message::error(__('No tables found in database.'))->display();
  329. exit;
  330. }
  331. // The tables list gets from MySQL
  332. while (list($table) = $GLOBALS['dbi']->fetchRow($all_tables)) {
  333. $columns = $GLOBALS['dbi']->getColumns($this->_db, $table);
  334. if (empty($this->_criteriaTables[$table])
  335. && ! empty($_POST['TableList'])
  336. ) {
  337. $this->_criteriaTables[$table] = '';
  338. } else {
  339. $this->_criteriaTables[$table] = ' selected="selected"';
  340. } // end if
  341. // The fields list per selected tables
  342. if ($this->_criteriaTables[$table] == ' selected="selected"') {
  343. $each_table = Util::backquote($table);
  344. $this->_columnNames[] = $each_table . '.*';
  345. foreach ($columns as $each_column) {
  346. $each_column = $each_table . '.'
  347. . Util::backquote($each_column['Field']);
  348. $this->_columnNames[] = $each_column;
  349. // increase the width if necessary
  350. $this->_form_column_width = max(
  351. mb_strlen($each_column),
  352. $this->_form_column_width
  353. );
  354. } // end foreach
  355. } // end if
  356. } // end while
  357. $GLOBALS['dbi']->freeResult($all_tables);
  358. // sets the largest width found
  359. $this->_realwidth = $this->_form_column_width . 'ex';
  360. }
  361. /**
  362. * Provides select options list containing column names
  363. *
  364. * @param integer $column_number Column Number (0,1,2) or more
  365. * @param string $selected Selected criteria column name
  366. *
  367. * @return string HTML for select options
  368. */
  369. private function _showColumnSelectCell($column_number, $selected = '')
  370. {
  371. return Template::get('database/qbe/column_select_cell')->render([
  372. 'column_number' => $column_number,
  373. 'column_names' => $this->_columnNames,
  374. 'selected' => $selected,
  375. ]);
  376. }
  377. /**
  378. * Provides select options list containing sort options (ASC/DESC)
  379. *
  380. * @param integer $columnNumber Column Number (0,1,2) or more
  381. * @param string $selected Selected criteria 'ASC' or 'DESC'
  382. *
  383. * @return string HTML for select options
  384. */
  385. private function _getSortSelectCell(
  386. $columnNumber,
  387. $selected = ''
  388. ) {
  389. return Template::get('database/qbe/sort_select_cell')->render([
  390. 'real_width' => $this->_realwidth,
  391. 'column_number' => $columnNumber,
  392. 'selected' => $selected,
  393. ]);
  394. }
  395. /**
  396. * Provides select options list containing sort order
  397. *
  398. * @param integer $columnNumber Column Number (0,1,2) or more
  399. * @param integer $sortOrder Sort order
  400. *
  401. * @return string HTML for select options
  402. */
  403. private function _getSortOrderSelectCell($columnNumber, $sortOrder)
  404. {
  405. $totalColumnCount = $this->_getNewColumnCount();
  406. return Template::get('database/qbe/sort_order_select_cell')->render([
  407. 'total_column_count' => $totalColumnCount,
  408. 'column_number' => $columnNumber,
  409. 'sort_order' => $sortOrder,
  410. ]);
  411. }
  412. /**
  413. * Returns the new column count after adding and removing columns as instructed
  414. *
  415. * @return int new column count
  416. */
  417. private function _getNewColumnCount()
  418. {
  419. $totalColumnCount = $this->_criteria_column_count;
  420. if (! empty($this->_criteriaColumnInsert)) {
  421. $totalColumnCount += count($this->_criteriaColumnInsert);
  422. }
  423. if (! empty($this->_criteriaColumnDelete)) {
  424. $totalColumnCount -= count($this->_criteriaColumnDelete);
  425. }
  426. return $totalColumnCount;
  427. }
  428. /**
  429. * Provides search form's row containing column select options
  430. *
  431. * @return string HTML for search table's row
  432. */
  433. private function _getColumnNamesRow()
  434. {
  435. $html_output = '<tr class="noclick">';
  436. $html_output .= '<th>' . __('Column:') . '</th>';
  437. $new_column_count = 0;
  438. for (
  439. $column_index = 0;
  440. $column_index < $this->_criteria_column_count;
  441. $column_index++
  442. ) {
  443. if (isset($this->_criteriaColumnInsert[$column_index])
  444. && $this->_criteriaColumnInsert[$column_index] == 'on'
  445. ) {
  446. $html_output .= $this->_showColumnSelectCell(
  447. $new_column_count
  448. );
  449. $new_column_count++;
  450. }
  451. if (! empty($this->_criteriaColumnDelete)
  452. && isset($this->_criteriaColumnDelete[$column_index])
  453. && $this->_criteriaColumnDelete[$column_index] == 'on'
  454. ) {
  455. continue;
  456. }
  457. $selected = '';
  458. if (isset($_POST['criteriaColumn'][$column_index])) {
  459. $selected = $_POST['criteriaColumn'][$column_index];
  460. $this->_formColumns[$new_column_count]
  461. = $_POST['criteriaColumn'][$column_index];
  462. }
  463. $html_output .= $this->_showColumnSelectCell(
  464. $new_column_count,
  465. $selected
  466. );
  467. $new_column_count++;
  468. } // end for
  469. $this->_new_column_count = $new_column_count;
  470. $html_output .= '</tr>';
  471. return $html_output;
  472. }
  473. /**
  474. * Provides search form's row containing column aliases
  475. *
  476. * @return string HTML for search table's row
  477. */
  478. private function _getColumnAliasRow()
  479. {
  480. $html_output = '<tr class="noclick">';
  481. $html_output .= '<th>' . __('Alias:') . '</th>';
  482. $new_column_count = 0;
  483. for (
  484. $colInd = 0;
  485. $colInd < $this->_criteria_column_count;
  486. $colInd++
  487. ) {
  488. if (! empty($this->_criteriaColumnInsert)
  489. && isset($this->_criteriaColumnInsert[$colInd])
  490. && $this->_criteriaColumnInsert[$colInd] == 'on'
  491. ) {
  492. $html_output .= '<td class="center">';
  493. $html_output .= '<input type="text"'
  494. . ' name="criteriaAlias[' . $new_column_count . ']" />';
  495. $html_output .= '</td>';
  496. $new_column_count++;
  497. } // end if
  498. if (! empty($this->_criteriaColumnDelete)
  499. && isset($this->_criteriaColumnDelete[$colInd])
  500. && $this->_criteriaColumnDelete[$colInd] == 'on'
  501. ) {
  502. continue;
  503. }
  504. $tmp_alias = '';
  505. if (! empty($_POST['criteriaAlias'][$colInd])) {
  506. $tmp_alias
  507. = $this->_formAliases[$new_column_count]
  508. = $_POST['criteriaAlias'][$colInd];
  509. }// end if
  510. $html_output .= '<td class="center">';
  511. $html_output .= '<input type="text"'
  512. . ' name="criteriaAlias[' . $new_column_count . ']"'
  513. . ' value="' . htmlspecialchars($tmp_alias) . '" />';
  514. $html_output .= '</td>';
  515. $new_column_count++;
  516. } // end for
  517. $html_output .= '</tr>';
  518. return $html_output;
  519. }
  520. /**
  521. * Provides search form's row containing sort(ASC/DESC) select options
  522. *
  523. * @return string HTML for search table's row
  524. */
  525. private function _getSortRow()
  526. {
  527. $html_output = '<tr class="noclick">';
  528. $html_output .= '<th>' . __('Sort:') . '</th>';
  529. $new_column_count = 0;
  530. for (
  531. $colInd = 0;
  532. $colInd < $this->_criteria_column_count;
  533. $colInd++
  534. ) {
  535. if (! empty($this->_criteriaColumnInsert)
  536. && isset($this->_criteriaColumnInsert[$colInd])
  537. && $this->_criteriaColumnInsert[$colInd] == 'on'
  538. ) {
  539. $html_output .= $this->_getSortSelectCell($new_column_count);
  540. $new_column_count++;
  541. } // end if
  542. if (! empty($this->_criteriaColumnDelete)
  543. && isset($this->_criteriaColumnDelete[$colInd])
  544. && $this->_criteriaColumnDelete[$colInd] == 'on'
  545. ) {
  546. continue;
  547. }
  548. // If they have chosen all fields using the * selector,
  549. // then sorting is not available, Fix for Bug #570698
  550. if (isset($_POST['criteriaSort'][$colInd])
  551. && isset($_POST['criteriaColumn'][$colInd])
  552. && mb_substr($_POST['criteriaColumn'][$colInd], -2) == '.*'
  553. ) {
  554. $_POST['criteriaSort'][$colInd] = '';
  555. } //end if
  556. $selected = '';
  557. if (isset($_POST['criteriaSort'][$colInd])) {
  558. $this->_formSorts[$new_column_count]
  559. = $_POST['criteriaSort'][$colInd];
  560. if ($_POST['criteriaSort'][$colInd] == 'ASC') {
  561. $selected = 'ASC';
  562. } elseif ($_POST['criteriaSort'][$colInd] == 'DESC') {
  563. $selected = 'DESC';
  564. }
  565. } else {
  566. $this->_formSorts[$new_column_count] = '';
  567. }
  568. $html_output .= $this->_getSortSelectCell(
  569. $new_column_count, $selected
  570. );
  571. $new_column_count++;
  572. } // end for
  573. $html_output .= '</tr>';
  574. return $html_output;
  575. }
  576. /**
  577. * Provides search form's row containing sort order
  578. *
  579. * @return string HTML for search table's row
  580. */
  581. private function _getSortOrder()
  582. {
  583. $html_output = '<tr class="noclick">';
  584. $html_output .= '<th>' . __('Sort order:') . '</th>';
  585. $new_column_count = 0;
  586. for (
  587. $colInd = 0;
  588. $colInd < $this->_criteria_column_count;
  589. $colInd++
  590. ) {
  591. if (! empty($this->_criteriaColumnInsert)
  592. && isset($this->_criteriaColumnInsert[$colInd])
  593. && $this->_criteriaColumnInsert[$colInd] == 'on'
  594. ) {
  595. $html_output .= $this->_getSortOrderSelectCell(
  596. $new_column_count, null
  597. );
  598. $new_column_count++;
  599. } // end if
  600. if (! empty($this->_criteriaColumnDelete)
  601. && isset($this->_criteriaColumnDelete[$colInd])
  602. && $this->_criteriaColumnDelete[$colInd] == 'on'
  603. ) {
  604. continue;
  605. }
  606. $sortOrder = null;
  607. if (! empty($_POST['criteriaSortOrder'][$colInd])) {
  608. $sortOrder
  609. = $this->_formSortOrders[$new_column_count]
  610. = $_POST['criteriaSortOrder'][$colInd];
  611. }
  612. $html_output .= $this->_getSortOrderSelectCell(
  613. $new_column_count, $sortOrder
  614. );
  615. $new_column_count++;
  616. } // end for
  617. $html_output .= '</tr>';
  618. return $html_output;
  619. }
  620. /**
  621. * Provides search form's row containing SHOW checkboxes
  622. *
  623. * @return string HTML for search table's row
  624. */
  625. private function _getShowRow()
  626. {
  627. $html_output = '<tr class="noclick">';
  628. $html_output .= '<th>' . __('Show:') . '</th>';
  629. $new_column_count = 0;
  630. for (
  631. $column_index = 0;
  632. $column_index < $this->_criteria_column_count;
  633. $column_index++
  634. ) {
  635. if (! empty($this->_criteriaColumnInsert)
  636. && isset($this->_criteriaColumnInsert[$column_index])
  637. && $this->_criteriaColumnInsert[$column_index] == 'on'
  638. ) {
  639. $html_output .= '<td class="center">';
  640. $html_output .= '<input type="checkbox"'
  641. . ' name="criteriaShow[' . $new_column_count . ']" />';
  642. $html_output .= '</td>';
  643. $new_column_count++;
  644. } // end if
  645. if (! empty($this->_criteriaColumnDelete)
  646. && isset($this->_criteriaColumnDelete[$column_index])
  647. && $this->_criteriaColumnDelete[$column_index] == 'on'
  648. ) {
  649. continue;
  650. }
  651. if (isset($_POST['criteriaShow'][$column_index])) {
  652. $checked_options = ' checked="checked"';
  653. $this->_formShows[$new_column_count]
  654. = $_POST['criteriaShow'][$column_index];
  655. } else {
  656. $checked_options = '';
  657. }
  658. $html_output .= '<td class="center">';
  659. $html_output .= '<input type="checkbox"'
  660. . ' name="criteriaShow[' . $new_column_count . ']"'
  661. . $checked_options . ' />';
  662. $html_output .= '</td>';
  663. $new_column_count++;
  664. } // end for
  665. $html_output .= '</tr>';
  666. return $html_output;
  667. }
  668. /**
  669. * Provides search form's row containing criteria Inputboxes
  670. *
  671. * @return string HTML for search table's row
  672. */
  673. private function _getCriteriaInputboxRow()
  674. {
  675. $html_output = '<tr class="noclick">';
  676. $html_output .= '<th>' . __('Criteria:') . '</th>';
  677. $new_column_count = 0;
  678. for (
  679. $column_index = 0;
  680. $column_index < $this->_criteria_column_count;
  681. $column_index++
  682. ) {
  683. if (! empty($this->_criteriaColumnInsert)
  684. && isset($this->_criteriaColumnInsert[$column_index])
  685. && $this->_criteriaColumnInsert[$column_index] == 'on'
  686. ) {
  687. $html_output .= '<td class="center">';
  688. $html_output .= '<input type="text"'
  689. . ' name="criteria[' . $new_column_count . ']"'
  690. . ' class="textfield"'
  691. . ' style="width: ' . $this->_realwidth . '"'
  692. . ' size="20" />';
  693. $html_output .= '</td>';
  694. $new_column_count++;
  695. } // end if
  696. if (! empty($this->_criteriaColumnDelete)
  697. && isset($this->_criteriaColumnDelete[$column_index])
  698. && $this->_criteriaColumnDelete[$column_index] == 'on'
  699. ) {
  700. continue;
  701. }
  702. if (isset($this->_criteria[$column_index])) {
  703. $tmp_criteria = $this->_criteria[$column_index];
  704. }
  705. if ((empty($this->_prev_criteria)
  706. || ! isset($this->_prev_criteria[$column_index]))
  707. || $this->_prev_criteria[$column_index] != htmlspecialchars($tmp_criteria)
  708. ) {
  709. $this->_formCriterions[$new_column_count] = $tmp_criteria;
  710. } else {
  711. $this->_formCriterions[$new_column_count]
  712. = $this->_prev_criteria[$column_index];
  713. }
  714. $html_output .= '<td class="center">';
  715. $html_output .= '<input type="hidden"'
  716. . ' name="prev_criteria[' . $new_column_count . ']"'
  717. . ' value="'
  718. . htmlspecialchars($this->_formCriterions[$new_column_count])
  719. . '" />';
  720. $html_output .= '<input type="text"'
  721. . ' name="criteria[' . $new_column_count . ']"'
  722. . ' value="' . htmlspecialchars($tmp_criteria) . '"'
  723. . ' class="textfield"'
  724. . ' style="width: ' . $this->_realwidth . '"'
  725. . ' size="20" />';
  726. $html_output .= '</td>';
  727. $new_column_count++;
  728. } // end for
  729. $html_output .= '</tr>';
  730. return $html_output;
  731. }
  732. /**
  733. * Provides footer options for adding/deleting row/columns
  734. *
  735. * @param string $type Whether row or column
  736. *
  737. * @return string HTML for footer options
  738. */
  739. private function _getFootersOptions($type)
  740. {
  741. return Template::get('database/qbe/footer_options')->render([
  742. 'type' => $type,
  743. ]);
  744. }
  745. /**
  746. * Provides search form table's footer options
  747. *
  748. * @return string HTML for table footer
  749. */
  750. private function _getTableFooters()
  751. {
  752. $html_output = '<fieldset class="tblFooters">';
  753. $html_output .= $this->_getFootersOptions("row");
  754. $html_output .= $this->_getFootersOptions("column");
  755. $html_output .= '<div class="floatleft">';
  756. $html_output .= '<input type="submit" name="modify"'
  757. . ' value="' . __('Update Query') . '" />';
  758. $html_output .= '</div>';
  759. $html_output .= '</fieldset>';
  760. return $html_output;
  761. }
  762. /**
  763. * Provides a select list of database tables
  764. *
  765. * @return string HTML for table select list
  766. */
  767. private function _getTablesList()
  768. {
  769. $html_output = '<div class="floatleft width100">';
  770. $html_output .= '<fieldset>';
  771. $html_output .= '<legend>' . __('Use Tables') . '</legend>';
  772. // Build the options list for each table name
  773. $options = '';
  774. $numTableListOptions = 0;
  775. foreach ($this->_criteriaTables as $key => $val) {
  776. $options .= '<option value="' . htmlspecialchars($key) . '"' . $val . '>'
  777. . (str_replace(' ', '&nbsp;', htmlspecialchars($key))) . '</option>';
  778. $numTableListOptions++;
  779. }
  780. $html_output .= '<select name="TableList[]"'
  781. . ' multiple="multiple" id="listTable"'
  782. . ' size="' . (($numTableListOptions > 30) ? '15' : '7') . '">';
  783. $html_output .= $options;
  784. $html_output .= '</select>';
  785. $html_output .= '</fieldset>';
  786. $html_output .= '<fieldset class="tblFooters">';
  787. $html_output .= '<input type="submit" name="modify" value="'
  788. . __('Update Query') . '" />';
  789. $html_output .= '</fieldset>';
  790. $html_output .= '</div>';
  791. return $html_output;
  792. }
  793. /**
  794. * Provides And/Or modification cell along with Insert/Delete options
  795. * (For modifying search form's table columns)
  796. *
  797. * @param integer $column_number Column Number (0,1,2) or more
  798. * @param array|null $selected Selected criteria column name
  799. * @param bool $last_column Whether this is the last column
  800. *
  801. * @return string HTML for modification cell
  802. */
  803. private function _getAndOrColCell(
  804. $column_number, $selected = null, $last_column = false
  805. ) {
  806. $html_output = '<td class="center">';
  807. if (! $last_column) {
  808. $html_output .= '<strong>' . __('Or:') . '</strong>';
  809. $html_output .= '<input type="radio"'
  810. . ' name="criteriaAndOrColumn[' . $column_number . ']"'
  811. . ' value="or"' . (isset($selected['or']) ? $selected['or'] : '') . ' />';
  812. $html_output .= '&nbsp;&nbsp;<strong>' . __('And:') . '</strong>';
  813. $html_output .= '<input type="radio"'
  814. . ' name="criteriaAndOrColumn[' . $column_number . ']"'
  815. . ' value="and"' . (isset($selected['and']) ? $selected['and'] : '') . ' />';
  816. }
  817. $html_output .= '<br />' . __('Ins');
  818. $html_output .= '<input type="checkbox"'
  819. . ' name="criteriaColumnInsert[' . $column_number . ']" />';
  820. $html_output .= '&nbsp;&nbsp;' . __('Del');
  821. $html_output .= '<input type="checkbox"'
  822. . ' name="criteriaColumnDelete[' . $column_number . ']" />';
  823. $html_output .= '</td>';
  824. return $html_output;
  825. }
  826. /**
  827. * Provides search form's row containing column modifications options
  828. * (For modifying search form's table columns)
  829. *
  830. * @return string HTML for search table's row
  831. */
  832. private function _getModifyColumnsRow()
  833. {
  834. $html_output = '<tr class="noclick">';
  835. $html_output .= '<th>' . __('Modify:') . '</th>';
  836. $new_column_count = 0;
  837. for (
  838. $column_index = 0;
  839. $column_index < $this->_criteria_column_count;
  840. $column_index++
  841. ) {
  842. if (! empty($this->_criteriaColumnInsert)
  843. && isset($this->_criteriaColumnInsert[$column_index])
  844. && $this->_criteriaColumnInsert[$column_index] == 'on'
  845. ) {
  846. $html_output .= $this->_getAndOrColCell($new_column_count);
  847. $new_column_count++;
  848. } // end if
  849. if (! empty($this->_criteriaColumnDelete)
  850. && isset($this->_criteriaColumnDelete[$column_index])
  851. && $this->_criteriaColumnDelete[$column_index] == 'on'
  852. ) {
  853. continue;
  854. }
  855. if (isset($this->_criteriaAndOrColumn[$column_index])) {
  856. $this->_formAndOrCols[$new_column_count]
  857. = $this->_criteriaAndOrColumn[$column_index];
  858. }
  859. $checked_options = array();
  860. if (isset($this->_criteriaAndOrColumn[$column_index])
  861. && $this->_criteriaAndOrColumn[$column_index] == 'or'
  862. ) {
  863. $checked_options['or'] = ' checked="checked"';
  864. $checked_options['and'] = '';
  865. } else {
  866. $checked_options['and'] = ' checked="checked"';
  867. $checked_options['or'] = '';
  868. }
  869. $html_output .= $this->_getAndOrColCell(
  870. $new_column_count,
  871. $checked_options,
  872. ($column_index + 1 == $this->_criteria_column_count)
  873. );
  874. $new_column_count++;
  875. } // end for
  876. $html_output .= '</tr>';
  877. return $html_output;
  878. }
  879. /**
  880. * Provides Insert/Delete options for criteria inputbox
  881. * with AND/OR relationship modification options
  882. *
  883. * @param integer $row_index Number of criteria row
  884. * @param array $checked_options If checked
  885. *
  886. * @return string HTML
  887. */
  888. private function _getInsDelAndOrCell($row_index, array $checked_options)
  889. {
  890. $html_output = '<td class="value nowrap">';
  891. $html_output .= '<!-- Row controls -->';
  892. $html_output .= '<table class="nospacing nopadding">';
  893. $html_output .= '<tr>';
  894. $html_output .= '<td class="value nowrap">';
  895. $html_output .= '<small>' . __('Ins:') . '</small>';
  896. $html_output .= '<input type="checkbox"'
  897. . ' name="criteriaRowInsert[' . $row_index . ']" />';
  898. $html_output .= '</td>';
  899. $html_output .= '<td class="value">';
  900. $html_output .= '<strong>' . __('And:') . '</strong>';
  901. $html_output .= '</td>';
  902. $html_output .= '<td>';
  903. $html_output .= '<input type="radio"'
  904. . ' name="criteriaAndOrRow[' . $row_index . ']" value="and"'
  905. . $checked_options['and'] . ' />';
  906. $html_output .= '</td>';
  907. $html_output .= '</tr>';
  908. $html_output .= '<tr>';
  909. $html_output .= '<td class="value nowrap">';
  910. $html_output .= '<small>' . __('Del:') . '</small>';
  911. $html_output .= '<input type="checkbox"'
  912. . ' name="criteriaRowDelete[' . $row_index . ']" />';
  913. $html_output .= '</td>';
  914. $html_output .= '<td class="value">';
  915. $html_output .= '<strong>' . __('Or:') . '</strong>';
  916. $html_output .= '</td>';
  917. $html_output .= '<td>';
  918. $html_output .= '<input type="radio"'
  919. . ' name="criteriaAndOrRow[' . $row_index . ']"'
  920. . ' value="or"' . $checked_options['or'] . ' />';
  921. $html_output .= '</td>';
  922. $html_output .= '</tr>';
  923. $html_output .= '</table>';
  924. $html_output .= '</td>';
  925. return $html_output;
  926. }
  927. /**
  928. * Provides rows for criteria inputbox Insert/Delete options
  929. * with AND/OR relationship modification options
  930. *
  931. * @param integer $new_row_index New row index if rows are added/deleted
  932. *
  933. * @return string HTML table rows
  934. */
  935. private function _getInputboxRow($new_row_index)
  936. {
  937. $html_output = '';
  938. $new_column_count = 0;
  939. for (
  940. $column_index = 0;
  941. $column_index < $this->_criteria_column_count;
  942. $column_index++
  943. ) {
  944. if (!empty($this->_criteriaColumnInsert)
  945. && isset($this->_criteriaColumnInsert[$column_index])
  946. && $this->_criteriaColumnInsert[$column_index] == 'on'
  947. ) {
  948. $orFieldName = 'Or' . $new_row_index . '[' . $new_column_count . ']';
  949. $html_output .= '<td class="center">';
  950. $html_output .= '<input type="text"'
  951. . ' name="Or' . $orFieldName . '" class="textfield"'
  952. . ' style="width: ' . $this->_realwidth . '" size="20" />';
  953. $html_output .= '</td>';
  954. $new_column_count++;
  955. } // end if
  956. if (!empty($this->_criteriaColumnDelete)
  957. && isset($this->_criteriaColumnDelete[$column_index])
  958. && $this->_criteriaColumnDelete[$column_index] == 'on'
  959. ) {
  960. continue;
  961. }
  962. $or = 'Or' . $new_row_index;
  963. if (! empty($_POST[$or]) && isset($_POST[$or][$column_index])) {
  964. $tmp_or = $_POST[$or][$column_index];
  965. } else {
  966. $tmp_or = '';
  967. }
  968. $html_output .= '<td class="center">';
  969. $html_output .= '<input type="text"'
  970. . ' name="Or' . $new_row_index . '[' . $new_column_count . ']' . '"'
  971. . ' value="' . htmlspecialchars($tmp_or) . '" class="textfield"'
  972. . ' style="width: ' . $this->_realwidth . '" size="20" />';
  973. $html_output .= '</td>';
  974. if (!empty(${$or}) && isset(${$or}[$column_index])) {
  975. $GLOBALS[${'cur' . $or}][$new_column_count]
  976. = ${$or}[$column_index];
  977. }
  978. $new_column_count++;
  979. } // end for
  980. return $html_output;
  981. }
  982. /**
  983. * Provides rows for criteria inputbox Insert/Delete options
  984. * with AND/OR relationship modification options
  985. *
  986. * @return string HTML table rows
  987. */
  988. private function _getInsDelAndOrCriteriaRows()
  989. {
  990. $html_output = '';
  991. $new_row_count = 0;
  992. $checked_options = array();
  993. for (
  994. $row_index = 0;
  995. $row_index <= $this->_criteria_row_count;
  996. $row_index++
  997. ) {
  998. if (isset($this->_criteriaRowInsert[$row_index])
  999. && $this->_criteriaRowInsert[$row_index] == 'on'
  1000. ) {
  1001. $checked_options['or'] = ' checked="checked"';
  1002. $checked_options['and'] = '';
  1003. $html_output .= '<tr class="noclick">';
  1004. $html_output .= $this->_getInsDelAndOrCell(
  1005. $new_row_count, $checked_options
  1006. );
  1007. $html_output .= $this->_getInputboxRow(
  1008. $new_row_count
  1009. );
  1010. $new_row_count++;
  1011. $html_output .= '</tr>';
  1012. } // end if
  1013. if (isset($this->_criteriaRowDelete[$row_index])
  1014. && $this->_criteriaRowDelete[$row_index] == 'on'
  1015. ) {
  1016. continue;
  1017. }
  1018. if (isset($this->_criteriaAndOrRow[$row_index])) {
  1019. $this->_formAndOrRows[$new_row_count]
  1020. = $this->_criteriaAndOrRow[$row_index];
  1021. }
  1022. if (isset($this->_criteriaAndOrRow[$row_index])
  1023. && $this->_criteriaAndOrRow[$row_index] == 'and'
  1024. ) {
  1025. $checked_options['and'] = ' checked="checked"';
  1026. $checked_options['or'] = '';
  1027. } else {
  1028. $checked_options['or'] = ' checked="checked"';
  1029. $checked_options['and'] = '';
  1030. }
  1031. $html_output .= '<tr class="noclick">';
  1032. $html_output .= $this->_getInsDelAndOrCell(
  1033. $new_row_count, $checked_options
  1034. );
  1035. $html_output .= $this->_getInputboxRow(
  1036. $new_row_count
  1037. );
  1038. $new_row_count++;
  1039. $html_output .= '</tr>';
  1040. } // end for
  1041. $this->_new_row_count = $new_row_count;
  1042. return $html_output;
  1043. }
  1044. /**
  1045. * Provides SELECT clause for building SQL query
  1046. *
  1047. * @return string Select clause
  1048. */
  1049. private function _getSelectClause()
  1050. {
  1051. $select_clause = '';
  1052. $select_clauses = array();
  1053. for (
  1054. $column_index = 0;
  1055. $column_index < $this->_criteria_column_count;
  1056. $column_index++
  1057. ) {
  1058. if (! empty($this->_formColumns[$column_index])
  1059. && isset($this->_formShows[$column_index])
  1060. && $this->_formShows[$column_index] == 'on'
  1061. ) {
  1062. $select = $this->_formColumns[$column_index];
  1063. if (! empty($this->_formAliases[$column_index])) {
  1064. $select .= " AS "
  1065. . Util::backquote($this->_formAliases[$column_index]);
  1066. }
  1067. $select_clauses[] = $select;
  1068. }
  1069. } // end for
  1070. if (!empty($select_clauses)) {
  1071. $select_clause = 'SELECT '
  1072. . htmlspecialchars(implode(", ", $select_clauses)) . "\n";
  1073. }
  1074. return $select_clause;
  1075. }
  1076. /**
  1077. * Provides WHERE clause for building SQL query
  1078. *
  1079. * @return string Where clause
  1080. */
  1081. private function _getWhereClause()
  1082. {
  1083. $where_clause = '';
  1084. $criteria_cnt = 0;
  1085. for (
  1086. $column_index = 0;
  1087. $column_index < $this->_criteria_column_count;
  1088. $column_index++
  1089. ) {
  1090. if (! empty($this->_formColumns[$column_index])
  1091. && ! empty($this->_formCriterions[$column_index])
  1092. && $column_index
  1093. && isset($last_where)
  1094. && isset($this->_formAndOrCols)
  1095. ) {
  1096. $where_clause .= ' '
  1097. . mb_strtoupper($this->_formAndOrCols[$last_where])
  1098. . ' ';
  1099. }
  1100. if (! empty($this->_formColumns[$column_index])
  1101. && ! empty($this->_formCriterions[$column_index])
  1102. ) {
  1103. $where_clause .= '(' . $this->_formColumns[$column_index] . ' '
  1104. . $this->_formCriterions[$column_index] . ')';
  1105. $last_where = $column_index;
  1106. $criteria_cnt++;
  1107. }
  1108. } // end for
  1109. if ($criteria_cnt > 1) {
  1110. $where_clause = '(' . $where_clause . ')';
  1111. }
  1112. // OR rows ${'cur' . $or}[$column_index]
  1113. if (! isset($this->_formAndOrRows)) {
  1114. $this->_formAndOrRows = array();
  1115. }
  1116. for (
  1117. $row_index = 0;
  1118. $row_index <= $this->_criteria_row_count;
  1119. $row_index++
  1120. ) {
  1121. $criteria_cnt = 0;
  1122. $qry_orwhere = '';
  1123. $last_orwhere = '';
  1124. for (
  1125. $column_index = 0;
  1126. $column_index < $this->_criteria_column_count;
  1127. $column_index++
  1128. ) {
  1129. if (! empty($this->_formColumns[$column_index])
  1130. && ! empty($_POST['Or' . $row_index][$column_index])
  1131. && $column_index
  1132. ) {
  1133. $qry_orwhere .= ' '
  1134. . mb_strtoupper(
  1135. $this->_formAndOrCols[$last_orwhere]
  1136. )
  1137. . ' ';
  1138. }
  1139. if (! empty($this->_formColumns[$column_index])
  1140. && ! empty($_POST['Or' . $row_index][$column_index])
  1141. ) {
  1142. $qry_orwhere .= '(' . $this->_formColumns[$column_index]
  1143. . ' '
  1144. . $_POST['Or' . $row_index][$column_index]
  1145. . ')';
  1146. $last_orwhere = $column_index;
  1147. $criteria_cnt++;
  1148. }
  1149. } // end for
  1150. if ($criteria_cnt > 1) {
  1151. $qry_orwhere = '(' . $qry_orwhere . ')';
  1152. }
  1153. if (! empty($qry_orwhere)) {
  1154. $where_clause .= "\n"
  1155. . mb_strtoupper(
  1156. isset($this->_formAndOrRows[$row_index])
  1157. ? $this->_formAndOrRows[$row_index] . ' '
  1158. : ''
  1159. )
  1160. . $qry_orwhere;
  1161. } // end if
  1162. } // end for
  1163. if (! empty($where_clause) && $where_clause != '()') {
  1164. $where_clause = 'WHERE ' . htmlspecialchars($where_clause) . "\n";
  1165. } // end if
  1166. return $where_clause;
  1167. }
  1168. /**
  1169. * Provides ORDER BY clause for building SQL query
  1170. *
  1171. * @return string Order By clause
  1172. */
  1173. private function _getOrderByClause()
  1174. {
  1175. $orderby_clause = '';
  1176. $orderby_clauses = array();
  1177. // Create copy of instance variables
  1178. $columns = $this->_formColumns;
  1179. $sort = $this->_formSorts;
  1180. $sortOrder = $this->_formSortOrders;
  1181. if (!empty($sortOrder)
  1182. && count($sortOrder) == count($sort)
  1183. && count($sortOrder) == count($columns)
  1184. ) {
  1185. // Sort all three arrays based on sort order
  1186. array_multisort($sortOrder, $sort, $columns);
  1187. }
  1188. for (
  1189. $column_index = 0;
  1190. $column_index < $this->_criteria_column_count;
  1191. $column_index++
  1192. ) {
  1193. // if all columns are chosen with * selector,
  1194. // then sorting isn't available
  1195. // Fix for Bug #570698
  1196. if (empty($columns[$column_index])
  1197. && empty($sort[$column_index])
  1198. ) {
  1199. continue;
  1200. }
  1201. if (mb_substr($columns[$column_index], -2) == '.*') {
  1202. continue;
  1203. }
  1204. if (! empty($sort[$column_index])) {
  1205. $orderby_clauses[] = $columns[$column_index] . ' '
  1206. . $sort[$column_index];
  1207. }
  1208. } // end for
  1209. if (!empty($orderby_clauses)) {
  1210. $orderby_clause = 'ORDER BY '
  1211. . htmlspecialchars(implode(", ", $orderby_clauses)) . "\n";
  1212. }
  1213. return $orderby_clause;
  1214. }
  1215. /**
  1216. * Provides UNIQUE columns and INDEX columns present in criteria tables
  1217. *
  1218. * @param array $search_tables Tables involved in the search
  1219. * @param array $search_columns Columns involved in the search
  1220. * @param array $where_clause_columns Columns having criteria where clause
  1221. *
  1222. * @return array having UNIQUE and INDEX columns
  1223. */
  1224. private function _getIndexes(array $search_tables, array $search_columns,
  1225. array $where_clause_columns
  1226. ) {
  1227. $unique_columns = array();
  1228. $index_columns = array();
  1229. foreach ($search_tables as $table) {
  1230. $indexes = $GLOBALS['dbi']->getTableIndexes($this->_db, $table);
  1231. foreach ($indexes as $index) {
  1232. $column = $table . '.' . $index['Column_name'];
  1233. if (isset($search_columns[$column])) {
  1234. if ($index['Non_unique'] == 0) {
  1235. if (isset($where_clause_columns[$column])) {
  1236. $unique_columns[$column] = 'Y';
  1237. } else {
  1238. $unique_columns[$column] = 'N';
  1239. }
  1240. } else {
  1241. if (isset($where_clause_columns[$column])) {
  1242. $index_columns[$column] = 'Y';
  1243. } else {
  1244. $index_columns[$column] = 'N';
  1245. }
  1246. }
  1247. }
  1248. } // end while (each index of a table)
  1249. } // end while (each table)
  1250. return array(
  1251. 'unique' => $unique_columns,
  1252. 'index' => $index_columns
  1253. );
  1254. }
  1255. /**
  1256. * Provides UNIQUE columns and INDEX columns present in criteria tables
  1257. *
  1258. * @param array $search_tables Tables involved in the search
  1259. * @param array $search_columns Columns involved in the search
  1260. * @param array $where_clause_columns Columns having criteria where clause
  1261. *
  1262. * @return array having UNIQUE and INDEX columns
  1263. */
  1264. private function _getLeftJoinColumnCandidates(array $search_tables, array $search_columns,
  1265. array $where_clause_columns
  1266. ) {
  1267. $GLOBALS['dbi']->selectDb($this->_db);
  1268. // Get unique columns and index columns
  1269. $indexes = $this->_getIndexes(
  1270. $search_tables, $search_columns, $where_clause_columns
  1271. );
  1272. $unique_columns = $indexes['unique'];
  1273. $index_columns = $indexes['index'];
  1274. list($candidate_columns, $needsort)
  1275. = $this->_getLeftJoinColumnCandidatesBest(
  1276. $search_tables,
  1277. $where_clause_columns,
  1278. $unique_columns,
  1279. $index_columns
  1280. );
  1281. // If we came up with $unique_columns (very good) or $index_columns (still
  1282. // good) as $candidate_columns we want to check if we have any 'Y' there
  1283. // (that would mean that they were also found in the whereclauses
  1284. // which would be great). if yes, we take only those
  1285. if ($needsort != 1) {
  1286. return $candidate_columns;
  1287. }
  1288. $very_good = array();
  1289. $still_good = array();
  1290. foreach ($candidate_columns as $column => $is_where) {
  1291. $table = explode('.', $column);
  1292. $table = $table[0];
  1293. if ($is_where == 'Y') {
  1294. $very_good[$column] = $table;
  1295. } else {
  1296. $still_good[$column] = $table;
  1297. }
  1298. }
  1299. if (count($very_good) > 0) {
  1300. $candidate_columns = $very_good;
  1301. // Candidates restricted in index+where
  1302. } else {
  1303. $candidate_columns = $still_good;
  1304. // None of the candidates where in a where-clause
  1305. }
  1306. return $candidate_columns;
  1307. }
  1308. /**
  1309. * Provides the main table to form the LEFT JOIN clause
  1310. *
  1311. * @param array $search_tables Tables involved in the search
  1312. * @param array $search_columns Columns involved in the search
  1313. * @param array $where_clause_columns Columns having criteria where clause
  1314. * @param array $where_clause_tables Tables having criteria where clause
  1315. *
  1316. * @return string table name
  1317. */
  1318. private function _getMasterTable(array $search_tables, array $search_columns,
  1319. array $where_clause_columns, array $where_clause_tables
  1320. ) {
  1321. if (count($where_clause_tables) == 1) {
  1322. // If there is exactly one column that has a decent where-clause
  1323. // we will just use this
  1324. $master = key($where_clause_tables);
  1325. return $master;
  1326. }
  1327. // Now let's find out which of the tables has an index
  1328. // (When the control user is the same as the normal user
  1329. // because he is using one of his databases as pmadb,
  1330. // the last db selected is not always the one where we need to work)
  1331. $candidate_columns = $this->_getLeftJoinColumnCandidates(
  1332. $search_tables, $search_columns, $where_clause_columns
  1333. );
  1334. // Generally, we need to display all the rows of foreign (referenced)
  1335. // table, whether they have any matching row in child table or not.
  1336. // So we select candidate tables which are foreign tables.
  1337. $foreign_tables = array();
  1338. foreach ($candidate_columns as $one_table) {
  1339. $foreigners = $this->relation->getForeigners($this->_db, $one_table);
  1340. foreach ($foreigners as $key => $foreigner) {
  1341. if ($key != 'foreign_keys_data') {
  1342. if (in_array($foreigner['foreign_table'], $candidate_columns)) {
  1343. $foreign_tables[$foreigner['foreign_table']]
  1344. = $foreigner['foreign_table'];
  1345. }
  1346. continue;
  1347. }
  1348. foreach ($foreigner as $one_key) {
  1349. if (in_array($one_key['ref_table_name'], $candidate_columns)) {
  1350. $foreign_tables[$one_key['ref_table_name']]
  1351. = $one_key['ref_table_name'];
  1352. }
  1353. }
  1354. }
  1355. }
  1356. if (count($foreign_tables)) {
  1357. $candidate_columns = $foreign_tables;
  1358. }
  1359. // If our array of candidates has more than one member we'll just
  1360. // find the smallest table.
  1361. // Of course the actual query would be faster if we check for
  1362. // the Criteria which gives the smallest result set in its table,
  1363. // but it would take too much time to check this
  1364. if (!(count($candidate_columns) > 1)) {
  1365. // Only one single candidate
  1366. return reset($candidate_columns);
  1367. }
  1368. // Of course we only want to check each table once
  1369. $checked_tables = $candidate_columns;
  1370. $tsize = array();
  1371. $maxsize = -1;
  1372. $result = '';
  1373. foreach ($candidate_columns as $table) {
  1374. if ($checked_tables[$table] != 1) {
  1375. $_table = new Table($table, $this->_db);
  1376. $tsize[$table] = $_table->countRecords();
  1377. $checked_tables[$table] = 1;
  1378. }
  1379. if ($tsize[$table] > $maxsize) {
  1380. $maxsize = $tsize[$table];
  1381. $result = $table;
  1382. }
  1383. }
  1384. // Return largest table
  1385. return $result;
  1386. }
  1387. /**
  1388. * Provides columns and tables that have valid where clause criteria
  1389. *
  1390. * @return array
  1391. */
  1392. private function _getWhereClauseTablesAndColumns()
  1393. {
  1394. $where_clause_columns = array();
  1395. $where_clause_tables = array();
  1396. // Now we need all tables that we have in the where clause
  1397. for (
  1398. $column_index = 0, $nb = count($this->_criteria);
  1399. $column_index < $nb;
  1400. $column_index++
  1401. ) {
  1402. $current_table = explode('.', $_POST['criteriaColumn'][$column_index]);
  1403. if (empty($current_table[0]) || empty($current_table[1])) {
  1404. continue;
  1405. } // end if
  1406. $table = str_replace('`', '', $current_table[0]);
  1407. $column = str_replace('`', '', $current_table[1]);
  1408. $column = $table . '.' . $column;
  1409. // Now we know that our array has the same numbers as $criteria
  1410. // we can check which of our columns has a where clause
  1411. if (! empty($this->_criteria[$column_index])) {
  1412. if (mb_substr($this->_criteria[$column_index], 0, 1) == '='
  1413. || stristr($this->_criteria[$column_index], 'is')
  1414. ) {
  1415. $where_clause_columns[$column] = $column;
  1416. $where_clause_tables[$table] = $table;
  1417. }
  1418. } // end if
  1419. } // end for
  1420. return array(
  1421. 'where_clause_tables' => $where_clause_tables,
  1422. 'where_clause_columns' => $where_clause_columns
  1423. );
  1424. }
  1425. /**
  1426. * Provides FROM clause for building SQL query
  1427. *
  1428. * @param array $formColumns List of selected columns in the form
  1429. *
  1430. * @return string FROM clause
  1431. */
  1432. private function _getFromClause(array $formColumns)
  1433. {
  1434. $from_clause = '';
  1435. if (empty($formColumns)) {
  1436. return $from_clause;
  1437. }
  1438. // Initialize some variables
  1439. $search_tables = $search_columns = array();
  1440. // We only start this if we have fields, otherwise it would be dumb
  1441. foreach ($formColumns as $value) {
  1442. $parts = explode('.', $value);
  1443. if (! empty($parts[0]) && ! empty($parts[1])) {
  1444. $table = str_replace('`', '', $parts[0]);
  1445. $search_tables[$table] = $table;
  1446. $search_columns[] = $table . '.' . str_replace(
  1447. '`', '', $parts[1]
  1448. );
  1449. }
  1450. } // end while
  1451. // Create LEFT JOINS out of Relations
  1452. $from_clause = $this->_getJoinForFromClause(
  1453. $search_tables, $search_columns
  1454. );
  1455. // In case relations are not defined, just generate the FROM clause
  1456. // from the list of tables, however we don't generate any JOIN
  1457. if (empty($from_clause)) {
  1458. // Create cartesian product
  1459. $from_clause = implode(
  1460. ", ", array_map(array('PhpMyAdmin\Util', 'backquote'), $search_tables)
  1461. );
  1462. }
  1463. return $from_clause;
  1464. }
  1465. /**
  1466. * Formulates the WHERE clause by JOINing tables
  1467. *
  1468. * @param array $searchTables Tables involved in the search
  1469. * @param array $searchColumns Columns involved in the search
  1470. *
  1471. * @return string table name
  1472. */
  1473. private function _getJoinForFromClause(array $searchTables, array $searchColumns)
  1474. {
  1475. // $relations[master_table][foreign_table] => clause
  1476. $relations = array();
  1477. // Fill $relations with inter table relationship data
  1478. foreach ($searchTables as $oneTable) {
  1479. $this->_loadRelationsForTable($relations, $oneTable);
  1480. }
  1481. // Get tables and columns with valid where clauses
  1482. $validWhereClauses = $this->_getWhereClauseTablesAndColumns();
  1483. $whereClauseTables = $validWhereClauses['where_clause_tables'];
  1484. $whereClauseColumns = $validWhereClauses['where_clause_columns'];
  1485. // Get master table
  1486. $master = $this->_getMasterTable(
  1487. $searchTables, $searchColumns,
  1488. $whereClauseColumns, $whereClauseTables
  1489. );
  1490. // Will include master tables and all tables that can be combined into
  1491. // a cluster by their relation
  1492. $finalized = array();
  1493. if (strlen($master) > 0) {
  1494. // Add master tables
  1495. $finalized[$master] = '';
  1496. }
  1497. // Fill the $finalized array with JOIN clauses for each table
  1498. $this->_fillJoinClauses($finalized, $relations, $searchTables);
  1499. // JOIN clause
  1500. $join = '';
  1501. // Tables that can not be combined with the table cluster
  1502. // which includes master table
  1503. $unfinalized = array_diff($searchTables, array_keys($finalized));
  1504. if (count($unfinalized) > 0) {
  1505. // We need to look for intermediary tables to JOIN unfinalized tables
  1506. // Heuristic to chose intermediary tables is to look for tables
  1507. // having relationships with unfinalized tables
  1508. foreach ($unfinalized as $oneTable) {
  1509. $references = $this->relation->getChildReferences($this->_db, $oneTable);
  1510. foreach ($references as $column => $columnReferences) {
  1511. foreach ($columnReferences as $reference) {
  1512. // Only from this schema
  1513. if ($reference['table_schema'] != $this->_db) {
  1514. continue;
  1515. }
  1516. $table = $reference['table_name'];
  1517. $this->_loadRelationsForTable($relations, $table);
  1518. // Make copies
  1519. $tempFinalized = $finalized;
  1520. $tempSearchTables = $searchTables;
  1521. $tempSearchTables[] = $table;
  1522. // Try joining with the added table
  1523. $this->_fillJoinClauses(
  1524. $tempFinalized, $relations, $tempSearchTables
  1525. );
  1526. $tempUnfinalized = array_diff(
  1527. $tempSearchTables, array_keys($tempFinalized)
  1528. );
  1529. // Take greedy approach.
  1530. // If the unfinalized count drops we keep the new table
  1531. // and switch temporary varibles with the original ones
  1532. if (count($tempUnfinalized) < count($unfinalized)) {
  1533. $finalized = $tempFinalized;
  1534. $searchTables = $tempSearchTables;
  1535. }
  1536. // We are done if no unfinalized tables anymore
  1537. if (count($tempUnfinalized) == 0) {
  1538. break 3;
  1539. }
  1540. }
  1541. }
  1542. }
  1543. $unfinalized = array_diff($searchTables, array_keys($finalized));
  1544. // If there are still unfinalized tables
  1545. if (count($unfinalized) > 0) {
  1546. // Add these tables as cartesian product before joined tables
  1547. $join .= implode(
  1548. ', ', array_map(array('PhpMyAdmin\Util', 'backquote'), $unfinalized)
  1549. );
  1550. }
  1551. }
  1552. $first = true;
  1553. // Add joined tables
  1554. foreach ($finalized as $table => $clause) {
  1555. if ($first) {
  1556. if (! empty($join)) {
  1557. $join .= ", ";
  1558. }
  1559. $join .= Util::backquote($table);
  1560. $first = false;
  1561. } else {
  1562. $join .= "\n LEFT JOIN " . Util::backquote(
  1563. $table
  1564. ) . " ON " . $clause;
  1565. }
  1566. }
  1567. return $join;
  1568. }
  1569. /**
  1570. * Loads relations for a given table into the $relations array
  1571. *
  1572. * @param array &$relations array of relations
  1573. * @param string $oneTable the table
  1574. *
  1575. * @return void
  1576. */
  1577. private function _loadRelationsForTable(array &$relations, $oneTable)
  1578. {
  1579. $relations[$oneTable] = array();
  1580. $foreigners = $this->relation->getForeigners($GLOBALS['db'], $oneTable);
  1581. foreach ($foreigners as $field => $foreigner) {
  1582. // Foreign keys data
  1583. if ($field == 'foreign_keys_data') {
  1584. foreach ($foreigner as $oneKey) {
  1585. $clauses = array();
  1586. // There may be multiple column relations
  1587. foreach ($oneKey['index_list'] as $index => $oneField) {
  1588. $clauses[]
  1589. = Util::backquote($oneTable) . "."
  1590. . Util::backquote($oneField) . " = "
  1591. . Util::backquote($oneKey['ref_table_name']) . "."
  1592. . Util::backquote($oneKey['ref_index_list'][$index]);
  1593. }
  1594. // Combine multiple column relations with AND
  1595. $relations[$oneTable][$oneKey['ref_table_name']]
  1596. = implode(" AND ", $clauses);
  1597. }
  1598. } else { // Internal relations
  1599. $relations[$oneTable][$foreigner['foreign_table']]
  1600. = Util::backquote($oneTable) . "."
  1601. . Util::backquote($field) . " = "
  1602. . Util::backquote($foreigner['foreign_table']) . "."
  1603. . Util::backquote($foreigner['foreign_field']);
  1604. }
  1605. }
  1606. }
  1607. /**
  1608. * Fills the $finalized arrays with JOIN clauses for each of the tables
  1609. *
  1610. * @param array &$finalized JOIN clauses for each table
  1611. * @param array $relations Relations among tables
  1612. * @param array $searchTables Tables involved in the search
  1613. *
  1614. * @return void
  1615. */
  1616. private function _fillJoinClauses(array &$finalized, array $relations, array $searchTables)
  1617. {
  1618. while (true) {
  1619. $added = false;
  1620. foreach ($searchTables as $masterTable) {
  1621. $foreignData = $relations[$masterTable];
  1622. foreach ($foreignData as $foreignTable => $clause) {
  1623. if (! isset($finalized[$masterTable])
  1624. && isset($finalized[$foreignTable])
  1625. ) {
  1626. $finalized[$masterTable] = $clause;
  1627. $added = true;
  1628. } elseif (! isset($finalized[$foreignTable])
  1629. && isset($finalized[$masterTable])
  1630. && in_array($foreignTable, $searchTables)
  1631. ) {
  1632. $finalized[$foreignTable] = $clause;
  1633. $added = true;
  1634. }
  1635. if ($added) {
  1636. // We are done if all tables are in $finalized
  1637. if (count($finalized) == count($searchTables)) {
  1638. return;
  1639. }
  1640. }
  1641. }
  1642. }
  1643. // If no new tables were added during this iteration, break;
  1644. if (! $added) {
  1645. return;
  1646. }
  1647. }
  1648. }
  1649. /**
  1650. * Provides the generated SQL query
  1651. *
  1652. * @param array $formColumns List of selected columns in the form
  1653. *
  1654. * @return string SQL query
  1655. */
  1656. private function _getSQLQuery(array $formColumns)
  1657. {
  1658. $sql_query = '';
  1659. // get SELECT clause
  1660. $sql_query .= $this->_getSelectClause();
  1661. // get FROM clause
  1662. $from_clause = $this->_getFromClause($formColumns);
  1663. if (! empty($from_clause)) {
  1664. $sql_query .= 'FROM ' . htmlspecialchars($from_clause) . "\n";
  1665. }
  1666. // get WHERE clause
  1667. $sql_query .= $this->_getWhereClause();
  1668. // get ORDER BY clause
  1669. $sql_query .= $this->_getOrderByClause();
  1670. return $sql_query;
  1671. }
  1672. /**
  1673. * Provides the generated QBE form
  1674. *
  1675. * @return string QBE form
  1676. */
  1677. public function getSelectionForm()
  1678. {
  1679. $html_output = '<form action="db_qbe.php" method="post" id="formQBE" '
  1680. . 'class="lock-page">';
  1681. $html_output .= '<div class="width100">';
  1682. $html_output .= '<fieldset>';
  1683. if ($GLOBALS['cfgRelation']['savedsearcheswork']) {
  1684. $html_output .= $this->_getSavedSearchesField();
  1685. }
  1686. $html_output .= '<div class="responsivetable jsresponsive">';
  1687. $html_output .= '<table class="data" style="width: 100%;">';
  1688. // Get table's <tr> elements
  1689. $html_output .= $this->_getColumnNamesRow();
  1690. $html_output .= $this->_getColumnAliasRow();
  1691. $html_output .= $this->_getShowRow();
  1692. $html_output .= $this->_getSortRow();
  1693. $html_output .= $this->_getSortOrder();
  1694. $html_output .= $this->_getCriteriaInputboxRow();
  1695. $html_output .= $this->_getInsDelAndOrCriteriaRows();
  1696. $html_output .= $this->_getModifyColumnsRow();
  1697. $html_output .= '</table>';
  1698. $this->_new_row_count--;
  1699. $url_params = array();
  1700. $url_params['db'] = $this->_db;
  1701. $url_params['criteriaColumnCount'] = $this->_new_column_count;
  1702. $url_params['rows'] = $this->_new_row_count;
  1703. $html_output .= Url::getHiddenInputs($url_params);
  1704. $html_output .= '</div>';
  1705. $html_output .= '</fieldset>';
  1706. $html_output .= '</div>';
  1707. // get footers
  1708. $html_output .= $this->_getTableFooters();
  1709. // get tables select list
  1710. $html_output .= $this->_getTablesList();
  1711. $html_output .= '</form>';
  1712. $html_output .= '<form action="db_qbe.php" method="post" class="lock-page">';
  1713. $html_output .= Url::getHiddenInputs(array('db' => $this->_db));
  1714. // get SQL query
  1715. $html_output .= '<div class="floatleft desktop50">';
  1716. $html_output .= '<fieldset id="tblQbe">';
  1717. $html_output .= '<legend>'
  1718. . sprintf(
  1719. __('SQL query on database <b>%s</b>:'),
  1720. Util::getDbLink($this->_db)
  1721. );
  1722. $html_output .= '</legend>';
  1723. $text_dir = 'ltr';
  1724. $html_output .= '<textarea cols="80" name="sql_query" id="textSqlquery"'
  1725. . ' rows="' . ((count($this->_criteriaTables) > 30) ? '15' : '7') . '"'
  1726. . ' dir="' . $text_dir . '">';
  1727. if (empty($this->_formColumns)) {
  1728. $this->_formColumns = array();
  1729. }
  1730. $html_output .= $this->_getSQLQuery($this->_formColumns);
  1731. $html_output .= '</textarea>';
  1732. $html_output .= '</fieldset>';
  1733. // displays form's footers
  1734. $html_output .= '<fieldset class="tblFooters" id="tblQbeFooters">';
  1735. $html_output .= '<input type="hidden" name="submit_sql" value="1" />';
  1736. $html_output .= '<input type="submit" value="' . __('Submit Query') . '" />';
  1737. $html_output .= '</fieldset>';
  1738. $html_output .= '</div>';
  1739. $html_output .= '</form>';
  1740. return $html_output;
  1741. }
  1742. /**
  1743. * Get fields to display
  1744. *
  1745. * @return string
  1746. */
  1747. private function _getSavedSearchesField()
  1748. {
  1749. $html_output = __('Saved bookmarked search:');
  1750. $html_output .= ' <select name="searchId" id="searchId">';
  1751. $html_output .= '<option value="">' . __('New bookmark') . '</option>';
  1752. $currentSearch = $this->_getCurrentSearch();
  1753. $currentSearchId = null;
  1754. $currentSearchName = null;
  1755. if (null != $currentSearch) {
  1756. $currentSearchId = $currentSearch->getId();
  1757. $currentSearchName = $currentSearch->getSearchName();
  1758. }
  1759. foreach ($this->_savedSearchList as $id => $name) {
  1760. $html_output .= '<option value="' . htmlspecialchars($id)
  1761. . '" ' . (
  1762. $id == $currentSearchId
  1763. ? 'selected="selected" '
  1764. : ''
  1765. )
  1766. . '>'
  1767. . htmlspecialchars($name)
  1768. . '</option>';
  1769. }
  1770. $html_output .= '</select>';
  1771. $html_output .= '<input type="text" name="searchName" id="searchName" '
  1772. . 'value="' . htmlspecialchars($currentSearchName) . '" />';
  1773. $html_output .= '<input type="hidden" name="action" id="action" value="" />';
  1774. $html_output .= '<input type="submit" name="saveSearch" id="saveSearch" '
  1775. . 'value="' . __('Create bookmark') . '" />';
  1776. if (null !== $currentSearchId) {
  1777. $html_output .= '<input type="submit" name="updateSearch" '
  1778. . 'id="updateSearch" value="' . __('Update bookmark') . '" />';
  1779. $html_output .= '<input type="submit" name="deleteSearch" '
  1780. . 'id="deleteSearch" value="' . __('Delete bookmark') . '" />';
  1781. }
  1782. return $html_output;
  1783. }
  1784. /**
  1785. * Initialize _criteria_column_count
  1786. *
  1787. * @return int Previous number of columns
  1788. */
  1789. private function _initializeCriteriasCount()
  1790. {
  1791. // sets column count
  1792. $criteriaColumnCount = Core::ifSetOr(
  1793. $_POST['criteriaColumnCount'],
  1794. 3,
  1795. 'numeric'
  1796. );
  1797. $criteriaColumnAdd = Core::ifSetOr(
  1798. $_POST['criteriaColumnAdd'],
  1799. 0,
  1800. 'numeric'
  1801. );
  1802. $this->_criteria_column_count = max(
  1803. $criteriaColumnCount + $criteriaColumnAdd,
  1804. 0
  1805. );
  1806. // sets row count
  1807. $rows = Core::ifSetOr($_POST['rows'], 0, 'numeric');
  1808. $criteriaRowAdd = Core::ifSetOr($_POST['criteriaRowAdd'], 0, 'numeric');
  1809. $this->_criteria_row_count = min(
  1810. 100,
  1811. max($rows + $criteriaRowAdd, 0)
  1812. );
  1813. return $criteriaColumnCount;
  1814. }
  1815. /**
  1816. * Get best
  1817. *
  1818. * @param array $search_tables Tables involved in the search
  1819. * @param array $where_clause_columns Columns with where clause
  1820. * @param array $unique_columns Unique columns
  1821. * @param array $index_columns Indexed columns
  1822. *
  1823. * @return array
  1824. */
  1825. private function _getLeftJoinColumnCandidatesBest(
  1826. array $search_tables, array $where_clause_columns, array $unique_columns, array $index_columns
  1827. ) {
  1828. // now we want to find the best.
  1829. if (isset($unique_columns) && count($unique_columns) > 0) {
  1830. $candidate_columns = $unique_columns;
  1831. $needsort = 1;
  1832. return array($candidate_columns, $needsort);
  1833. } elseif (isset($index_columns) && count($index_columns) > 0) {
  1834. $candidate_columns = $index_columns;
  1835. $needsort = 1;
  1836. return array($candidate_columns, $needsort);
  1837. } elseif (isset($where_clause_columns) && count($where_clause_columns) > 0) {
  1838. $candidate_columns = $where_clause_columns;
  1839. $needsort = 0;
  1840. return array($candidate_columns, $needsort);
  1841. }
  1842. $candidate_columns = $search_tables;
  1843. $needsort = 0;
  1844. return array($candidate_columns, $needsort);
  1845. }
  1846. }