Qbe.php 67 KB

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