Minor improvement to prior commit.
[openemr.git] / phpmyadmin / libraries / DBQbe.class.php
blobc1387833954d5b34004de1d0e56ce91320a58e61
1 <?php
2 /* vim: set expandtab sw=4 ts=4 sts=4: */
3 /**
4 * Handles DB QBE search
6 * @package PhpMyAdmin
7 */
8 if (! defined('PHPMYADMIN')) {
9 exit;
12 /**
13 * Class to handle database QBE search
15 * @package PhpMyAdmin
17 class PMA_DbQbe
19 /**
20 * Database name
22 * @access private
23 * @var string
25 private $_db;
26 /**
27 * Table Names (selected/non-selected)
29 * @access private
30 * @var array
32 private $_criteriaTables;
33 /**
34 * Column Names
36 * @access private
37 * @var array
39 private $_columnNames;
40 /**
41 * Number of columns
43 * @access private
44 * @var integer
46 private $_criteria_column_count;
47 /**
48 * Number of Rows
50 * @access private
51 * @var integer
53 private $_criteria_row_count;
54 /**
55 * Whether to insert a new column
57 * @access private
58 * @var array
60 private $_criteriaColumnInsert;
61 /**
62 * Whether to delete a column
64 * @access private
65 * @var array
67 private $_criteriaColumnDelete;
68 /**
69 * Whether to insert a new row
71 * @access private
72 * @var array
74 private $_criteriaRowInsert;
75 /**
76 * Whether to delete a row
78 * @access private
79 * @var array
81 private $_criteriaRowDelete;
82 /**
83 * Already set criteria values
85 * @access private
86 * @var array
88 private $_criteria;
89 /**
90 * Previously set criteria values
92 * @access private
93 * @var array
95 private $_prev_criteria;
96 /**
97 * AND/OR relation b/w criteria columns
99 * @access private
100 * @var array
102 private $_criteriaAndOrColumn;
104 * AND/OR relation b/w criteria rows
106 * @access private
107 * @var array
109 private $_criteriaAndOrRow;
111 * Large width of a column
113 * @access private
114 * @var string
116 private $_realwidth;
118 * Minimum width of a column
120 * @access private
121 * @var int
123 private $_form_column_width;
125 * Current criteria field
127 * @access private
128 * @var array
130 private $_curField;
132 * Current alias
134 * @access private
135 * @var array
137 private $_curAlias;
139 * Current criteria Sort options
141 * @access private
142 * @var array
144 private $_curSort;
146 * Current criteria sort order
148 * @access private
149 * @var array
151 private $_curSortOrder;
153 * Current criteria Show options
155 * @access private
156 * @var array
158 private $_curShow;
160 * Current criteria values
162 * @access private
163 * @var array
165 private $_curCriteria;
167 * Current criteria AND/OR column relations
169 * @access private
170 * @var array
172 private $_curAndOrCol;
174 * Current criteria AND/OR row relations
176 * @access private
177 * @var array
179 private $_curAndOrRow;
181 * New column count in case of add/delete
183 * @access private
184 * @var integer
186 private $_new_column_count;
188 * New row count in case of add/delete
190 * @access private
191 * @var integer
193 private $_new_row_count;
195 * List of saved searches
197 * @access private
198 * @var array
200 private $_savedSearchList = null;
202 * Current search
204 * @access private
205 * @var PMA_SavedSearches
207 private $_currentSearch = null;
210 * Initialize criterias
212 * @return static
214 private function _loadCriterias()
216 if (null === $this->_currentSearch
217 || null === $this->_currentSearch->getCriterias()
219 return $this;
222 $criterias = $this->_currentSearch->getCriterias();
223 $_REQUEST = $criterias + $_REQUEST;
225 return $this;
229 * Getter for current search
231 * @return PMA_SavedSearches
233 private function _getCurrentSearch()
235 return $this->_currentSearch;
239 * Public Constructor
241 * @param string $dbname Database name
242 * @param array $savedSearchList List of saved searches
243 * @param PMA_SavedSearches $currentSearch Current search id
245 public function __construct(
246 $dbname, $savedSearchList = array(), $currentSearch = null
248 $this->_db = $dbname;
249 $this->_savedSearchList = $savedSearchList;
250 $this->_currentSearch = $currentSearch;
251 $this->_loadCriterias();
252 // Sets criteria parameters
253 $this->_setSearchParams();
254 $this->_setCriteriaTablesAndColumns();
258 * Sets search parameters
260 * @return void
262 private function _setSearchParams()
264 $criteriaColumnCount = $this->_initializeCriteriasCount();
266 $this->_criteriaColumnInsert = PMA_ifSetOr(
267 $_REQUEST['criteriaColumnInsert'],
268 null,
269 'array'
271 $this->_criteriaColumnDelete = PMA_ifSetOr(
272 $_REQUEST['criteriaColumnDelete'],
273 null,
274 'array'
277 $this->_prev_criteria = isset($_REQUEST['prev_criteria'])
278 ? $_REQUEST['prev_criteria']
279 : array();
280 $this->_criteria = isset($_REQUEST['criteria'])
281 ? $_REQUEST['criteria']
282 : array_fill(0, $criteriaColumnCount, '');
284 $this->_criteriaRowInsert = isset($_REQUEST['criteriaRowInsert'])
285 ? $_REQUEST['criteriaRowInsert']
286 : array_fill(0, $criteriaColumnCount, '');
287 $this->_criteriaRowDelete = isset($_REQUEST['criteriaRowDelete'])
288 ? $_REQUEST['criteriaRowDelete']
289 : array_fill(0, $criteriaColumnCount, '');
290 $this->_criteriaAndOrRow = isset($_REQUEST['criteriaAndOrRow'])
291 ? $_REQUEST['criteriaAndOrRow']
292 : array_fill(0, $criteriaColumnCount, '');
293 $this->_criteriaAndOrColumn = isset($_REQUEST['criteriaAndOrColumn'])
294 ? $_REQUEST['criteriaAndOrColumn']
295 : array_fill(0, $criteriaColumnCount, '');
296 // sets minimum width
297 $this->_form_column_width = 12;
298 $this->_curField = array();
299 $this->_curSort = array();
300 $this->_curShow = array();
301 $this->_curCriteria = array();
302 $this->_curAndOrRow = array();
303 $this->_curAndOrCol = array();
307 * Sets criteria tables and columns
309 * @return void
311 private function _setCriteriaTablesAndColumns()
313 // The tables list sent by a previously submitted form
314 if (PMA_isValid($_REQUEST['TableList'], 'array')) {
315 foreach ($_REQUEST['TableList'] as $each_table) {
316 $this->_criteriaTables[$each_table] = ' selected="selected"';
318 } // end if
319 $all_tables = $GLOBALS['dbi']->query(
320 'SHOW TABLES FROM ' . PMA_Util::backquote($this->_db) . ';',
321 null,
322 PMA_DatabaseInterface::QUERY_STORE
324 $all_tables_count = $GLOBALS['dbi']->numRows($all_tables);
325 if (0 == $all_tables_count) {
326 PMA_Message::error(__('No tables found in database.'))->display();
327 exit;
329 // The tables list gets from MySQL
330 while (list($table) = $GLOBALS['dbi']->fetchRow($all_tables)) {
331 $columns = $GLOBALS['dbi']->getColumns($this->_db, $table);
333 if (empty($this->_criteriaTables[$table])
334 && ! empty($_REQUEST['TableList'])
336 $this->_criteriaTables[$table] = '';
337 } else {
338 $this->_criteriaTables[$table] = ' selected="selected"';
339 } // end if
341 // The fields list per selected tables
342 if ($this->_criteriaTables[$table] == ' selected="selected"') {
343 $each_table = PMA_Util::backquote($table);
344 $this->_columnNames[] = $each_table . '.*';
345 foreach ($columns as $each_column) {
346 $each_column = $each_table . '.'
347 . PMA_Util::backquote($each_column['Field']);
348 $this->_columnNames[] = $each_column;
349 // increase the width if necessary
350 $this->_form_column_width = max(
351 /*overload*/mb_strlen($each_column),
352 $this->_form_column_width
354 } // end foreach
355 } // end if
356 } // end while
357 $GLOBALS['dbi']->freeResult($all_tables);
359 // sets the largest width found
360 $this->_realwidth = $this->_form_column_width . 'ex';
363 * Provides select options list containing column names
365 * @param integer $column_number Column Number (0,1,2) or more
366 * @param string $selected Selected criteria column name
368 * @return string HTML for select options
370 private function _showColumnSelectCell($column_number, $selected = '')
372 $html_output = '';
373 $html_output .= '<td class="center">';
374 $html_output .= '<select name="criteriaColumn[' . $column_number
375 . ']" size="1">';
376 $html_output .= '<option value="">&nbsp;</option>';
377 foreach ($this->_columnNames as $column) {
378 $html_output .= '<option value="' . htmlspecialchars($column) . '"'
379 . (($column === $selected) ? ' selected="selected"' : '') . '>'
380 . str_replace(' ', '&nbsp;', htmlspecialchars($column))
381 . '</option>';
383 $html_output .= '</select>';
384 $html_output .= '</td>';
385 return $html_output;
389 * Provides select options list containing sort options (ASC/DESC)
391 * @param integer $column_number Column Number (0,1,2) or more
392 * @param string $asc_selected Selected criteria 'Ascending'
393 * @param string $desc_selected Selected criteria 'Descending'
395 * @return string HTML for select options
397 private function _getSortSelectCell($column_number, $asc_selected = '',
398 $desc_selected = ''
400 $html_output = '<td class="center">';
401 $html_output .= '<select style="width: ' . $this->_realwidth
402 . '" name="criteriaSort[' . $column_number . ']" size="1">';
403 $html_output .= '<option value="">&nbsp;</option>';
404 $html_output .= '<option value="ASC"' . $asc_selected . '>'
405 . __('Ascending')
406 . '</option>';
407 $html_output .= '<option value="DESC"' . $desc_selected . '>'
408 . __('Descending')
409 . '</option>';
410 $html_output .= '</select>';
411 $html_output .= '</td>';
412 return $html_output;
416 * Provides select options list containing sort order
418 * @param integer $columnNumber Column Number (0,1,2) or more
419 * @param integer $sortOrder Sort order
421 * @return string HTML for select options
423 private function _getSortOrderSelectCell($columnNumber, $sortOrder)
425 $totalColumnCount = $this->_getNewColumnCount();
426 $html_output = '<td class="center">';
427 $html_output .= '<select name="criteriaSortOrder[' . $columnNumber . ']">';
428 $html_output .= '<option value="1000">'
429 . '&nbsp;</option>';
430 for ($a = 1; $a <= $totalColumnCount; $a++) {
431 $html_output .= '<option value="' . $a . '"';
432 if ($a == $sortOrder) {
433 $html_output .= ' selected="selected"';
435 $html_output .= '>' . $a . '</option>';
437 $html_output .= '</select>';
438 $html_output .= '</td>';
439 return $html_output;
443 * Returns the new column count after adding and removing columns as instructed
445 * @return int new column count
447 private function _getNewColumnCount()
449 $totalColumnCount = $this->_criteria_column_count;
450 if (! empty($this->_criteriaColumnInsert)) {
451 $totalColumnCount += count($this->_criteriaColumnInsert);
453 if (! empty($this->_criteriaColumnDelete)) {
454 $totalColumnCount -= count($this->_criteriaColumnDelete);
456 return $totalColumnCount;
460 * Provides search form's row containing column select options
462 * @return string HTML for search table's row
464 private function _getColumnNamesRow()
466 $html_output = '<tr class="odd noclick">';
467 $html_output .= '<th>' . __('Column:') . '</th>';
468 $new_column_count = 0;
469 for (
470 $column_index = 0;
471 $column_index < $this->_criteria_column_count;
472 $column_index++
474 if (isset($this->_criteriaColumnInsert[$column_index])
475 && $this->_criteriaColumnInsert[$column_index] == 'on'
477 $html_output .= $this->_showColumnSelectCell(
478 $new_column_count
480 $new_column_count++;
482 if (! empty($this->_criteriaColumnDelete)
483 && isset($this->_criteriaColumnDelete[$column_index])
484 && $this->_criteriaColumnDelete[$column_index] == 'on'
486 continue;
488 $selected = '';
489 if (isset($_REQUEST['criteriaColumn'][$column_index])) {
490 $selected = $_REQUEST['criteriaColumn'][$column_index];
491 $this->_curField[$new_column_count]
492 = $_REQUEST['criteriaColumn'][$column_index];
494 $html_output .= $this->_showColumnSelectCell(
495 $new_column_count,
496 $selected
498 $new_column_count++;
499 } // end for
500 $this->_new_column_count = $new_column_count;
501 $html_output .= '</tr>';
502 return $html_output;
506 * Provides search form's row containing column aliases
508 * @return string HTML for search table's row
510 private function _getColumnAliasRow()
512 $html_output = '<tr class="even noclick">';
513 $html_output .= '<th>' . __('Alias:') . '</th>';
514 $new_column_count = 0;
516 for (
517 $colInd = 0;
518 $colInd < $this->_criteria_column_count;
519 $colInd++
521 if (! empty($this->_criteriaColumnInsert)
522 && isset($this->_criteriaColumnInsert[$colInd])
523 && $this->_criteriaColumnInsert[$colInd] == 'on'
525 $html_output .= '<td class="center">';
526 $html_output .= '<input type="text"'
527 . ' name="criteriaAlias[' . $new_column_count . ']"'
528 . ' value="" />';
529 $html_output .= '</td>';
530 $new_column_count++;
531 } // end if
533 if (! empty($this->_criteriaColumnDelete)
534 && isset($this->_criteriaColumnDelete[$colInd])
535 && $this->_criteriaColumnDelete[$colInd] == 'on'
537 continue;
540 $tmp_alias = '';
541 if (! empty($_REQUEST['criteriaAlias'][$colInd])) {
542 $tmp_alias
543 = $this->_curAlias[$new_column_count]
544 = $_REQUEST['criteriaAlias'][$colInd];
545 }// end if
547 $html_output .= '<td class="center">';
548 $html_output .= '<input type="text"'
549 . ' name="criteriaAlias[' . $new_column_count . ']"'
550 . ' value="' . htmlspecialchars($tmp_alias) . '" />';
551 $html_output .= '</td>';
552 $new_column_count++;
553 } // end for
554 $html_output .= '</tr>';
555 return $html_output;
559 * Provides search form's row containing sort(ASC/DESC) select options
561 * @return string HTML for search table's row
563 private function _getSortRow()
565 $html_output = '<tr class="even noclick">';
566 $html_output .= '<th>' . __('Sort:') . '</th>';
567 $new_column_count = 0;
569 for (
570 $colInd = 0;
571 $colInd < $this->_criteria_column_count;
572 $colInd++
574 if (! empty($this->_criteriaColumnInsert)
575 && isset($this->_criteriaColumnInsert[$colInd])
576 && $this->_criteriaColumnInsert[$colInd] == 'on'
578 $html_output .= $this->_getSortSelectCell($new_column_count);
579 $new_column_count++;
580 } // end if
582 if (! empty($this->_criteriaColumnDelete)
583 && isset($this->_criteriaColumnDelete[$colInd])
584 && $this->_criteriaColumnDelete[$colInd] == 'on'
586 continue;
588 // If they have chosen all fields using the * selector,
589 // then sorting is not available, Fix for Bug #570698
590 if (isset($_REQUEST['criteriaSort'][$colInd])
591 && isset($_REQUEST['criteriaColumn'][$colInd])
592 && /*overload*/mb_substr($_REQUEST['criteriaColumn'][$colInd], -2) == '.*'
594 $_REQUEST['criteriaSort'][$colInd] = '';
595 } //end if
597 $asc_selected = ''; $desc_selected = '';
598 if (isset($_REQUEST['criteriaSort'][$colInd])) {
599 $this->_curSort[$new_column_count]
600 = $_REQUEST['criteriaSort'][$colInd];
601 // Set asc_selected
602 if ($_REQUEST['criteriaSort'][$colInd] == 'ASC') {
603 $asc_selected = ' selected="selected"';
604 } // end if
605 // Set desc selected
606 if ($_REQUEST['criteriaSort'][$colInd] == 'DESC') {
607 $desc_selected = ' selected="selected"';
608 } // end if
609 } else {
610 $this->_curSort[$new_column_count] = '';
613 $html_output .= $this->_getSortSelectCell(
614 $new_column_count, $asc_selected, $desc_selected
616 $new_column_count++;
617 } // end for
618 $html_output .= '</tr>';
619 return $html_output;
623 * Provides search form's row containing sort order
625 * @return string HTML for search table's row
627 private function _getSortOrder()
629 $html_output = '<tr class="even noclick">';
630 $html_output .= '<th>' . __('Sort order:') . '</th>';
631 $new_column_count = 0;
633 for (
634 $colInd = 0;
635 $colInd < $this->_criteria_column_count;
636 $colInd++
638 if (! empty($this->_criteriaColumnInsert)
639 && isset($this->_criteriaColumnInsert[$colInd])
640 && $this->_criteriaColumnInsert[$colInd] == 'on'
642 $html_output .= $this->_getSortOrderSelectCell(
643 $new_column_count, null
645 $new_column_count++;
646 } // end if
648 if (! empty($this->_criteriaColumnDelete)
649 && isset($this->_criteriaColumnDelete[$colInd])
650 && $this->_criteriaColumnDelete[$colInd] == 'on'
652 continue;
655 $sortOrder = null;
656 if (! empty($_REQUEST['criteriaSortOrder'][$colInd])) {
657 $sortOrder
658 = $this->_curSortOrder[$new_column_count]
659 = $_REQUEST['criteriaSortOrder'][$colInd];
662 $html_output .= $this->_getSortOrderSelectCell(
663 $new_column_count, $sortOrder
665 $new_column_count++;
666 } // end for
667 $html_output .= '</tr>';
668 return $html_output;
672 * Provides search form's row containing SHOW checkboxes
674 * @return string HTML for search table's row
676 private function _getShowRow()
678 $html_output = '<tr class="odd noclick">';
679 $html_output .= '<th>' . __('Show:') . '</th>';
680 $new_column_count = 0;
681 for (
682 $column_index = 0;
683 $column_index < $this->_criteria_column_count;
684 $column_index++
686 if (! empty($this->_criteriaColumnInsert)
687 && isset($this->_criteriaColumnInsert[$column_index])
688 && $this->_criteriaColumnInsert[$column_index] == 'on'
690 $html_output .= '<td class="center">';
691 $html_output .= '<input type="checkbox"'
692 . ' name="criteriaShow[' . $new_column_count . ']" />';
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'
700 continue;
702 if (isset($_REQUEST['criteriaShow'][$column_index])) {
703 $checked_options = ' checked="checked"';
704 $this->_curShow[$new_column_count]
705 = $_REQUEST['criteriaShow'][$column_index];
706 } else {
707 $checked_options = '';
709 $html_output .= '<td class="center">';
710 $html_output .= '<input type="checkbox"'
711 . ' name="criteriaShow[' . $new_column_count . ']"'
712 . $checked_options . ' />';
713 $html_output .= '</td>';
714 $new_column_count++;
715 } // end for
716 $html_output .= '</tr>';
717 return $html_output;
721 * Provides search form's row containing criteria Inputboxes
723 * @return string HTML for search table's row
725 private function _getCriteriaInputboxRow()
727 $html_output = '<tr class="even noclick">';
728 $html_output .= '<th>' . __('Criteria:') . '</th>';
729 $new_column_count = 0;
730 for (
731 $column_index = 0;
732 $column_index < $this->_criteria_column_count;
733 $column_index++
735 if (! empty($this->_criteriaColumnInsert)
736 && isset($this->_criteriaColumnInsert[$column_index])
737 && $this->_criteriaColumnInsert[$column_index] == 'on'
739 $html_output .= '<td class="center">';
740 $html_output .= '<input type="text"'
741 . ' name="criteria[' . $new_column_count . ']"'
742 . ' value=""'
743 . ' class="textfield"'
744 . ' style="width: ' . $this->_realwidth . '"'
745 . ' size="20" />';
746 $html_output .= '</td>';
747 $new_column_count++;
748 } // end if
749 if (! empty($this->_criteriaColumnDelete)
750 && isset($this->_criteriaColumnDelete[$column_index])
751 && $this->_criteriaColumnDelete[$column_index] == 'on'
753 continue;
755 if (isset($this->_criteria[$column_index])) {
756 $tmp_criteria = $this->_criteria[$column_index];
758 if ((empty($this->_prev_criteria)
759 || ! isset($this->_prev_criteria[$column_index]))
760 || $this->_prev_criteria[$column_index] != htmlspecialchars($tmp_criteria)
762 $this->_curCriteria[$new_column_count] = $tmp_criteria;
763 } else {
764 $this->_curCriteria[$new_column_count]
765 = $this->_prev_criteria[$column_index];
767 $html_output .= '<td class="center">';
768 $html_output .= '<input type="hidden"'
769 . ' name="prev_criteria[' . $new_column_count . ']"'
770 . ' value="'
771 . htmlspecialchars($this->_curCriteria[$new_column_count])
772 . '" />';
773 $html_output .= '<input type="text"'
774 . ' name="criteria[' . $new_column_count . ']"'
775 . ' value="' . htmlspecialchars($tmp_criteria) . '"'
776 . ' class="textfield"'
777 . ' style="width: ' . $this->_realwidth . '"'
778 . ' size="20" />';
779 $html_output .= '</td>';
780 $new_column_count++;
781 } // end for
782 $html_output .= '</tr>';
783 return $html_output;
787 * Provides footer options for adding/deleting row/columns
789 * @param string $type Whether row or column
791 * @return string HTML for footer options
793 private function _getFootersOptions($type)
795 $html_output = '<div class="floatleft">';
796 $html_output .= (($type == 'row')
797 ? __('Add/Delete criteria rows') : __('Add/Delete columns'));
798 $html_output .= ':<select size="1" name="'
799 . (($type == 'row') ? 'criteriaRowAdd' : 'criteriaColumnAdd') . '">';
800 $html_output .= '<option value="-3">-3</option>';
801 $html_output .= '<option value="-2">-2</option>';
802 $html_output .= '<option value="-1">-1</option>';
803 $html_output .= '<option value="0" selected="selected">0</option>';
804 $html_output .= '<option value="1">1</option>';
805 $html_output .= '<option value="2">2</option>';
806 $html_output .= '<option value="3">3</option>';
807 $html_output .= '</select>';
808 $html_output .= '</div>';
809 return $html_output;
813 * Provides search form table's footer options
815 * @return string HTML for table footer
817 private function _getTableFooters()
819 $html_output = '<fieldset class="tblFooters">';
820 $html_output .= $this->_getFootersOptions("row");
821 $html_output .= $this->_getFootersOptions("column");
822 $html_output .= '<div class="floatleft">';
823 $html_output .= '<input type="submit" name="modify"'
824 . 'value="' . __('Update Query') . '" />';
825 $html_output .= '</div>';
826 $html_output .= '</fieldset>';
827 return $html_output;
831 * Provides a select list of database tables
833 * @return string HTML for table select list
835 private function _getTablesList()
837 $html_output = '<div class="floatleft">';
838 $html_output .= '<fieldset>';
839 $html_output .= '<legend>' . __('Use Tables') . '</legend>';
840 // Build the options list for each table name
841 $options = '';
842 $numTableListOptions = 0;
843 foreach ($this->_criteriaTables as $key => $val) {
844 $options .= '<option value="' . htmlspecialchars($key) . '"' . $val . '>'
845 . (str_replace(' ', '&nbsp;', htmlspecialchars($key))) . '</option>';
846 $numTableListOptions++;
848 $html_output .= '<select name="TableList[]"'
849 . ' multiple="multiple" id="listTable"'
850 . ' size="' . (($numTableListOptions > 30) ? '15' : '7') . '">';
851 $html_output .= $options;
852 $html_output .= '</select>';
853 $html_output .= '</fieldset>';
854 $html_output .= '<fieldset class="tblFooters">';
855 $html_output .= '<input type="submit" name="modify" value="'
856 . __('Update Query') . '" />';
857 $html_output .= '</fieldset>';
858 $html_output .= '</div>';
859 return $html_output;
863 * Provides And/Or modification cell along with Insert/Delete options
864 * (For modifying search form's table columns)
866 * @param integer $column_number Column Number (0,1,2) or more
867 * @param array $selected Selected criteria column name
868 * @param bool $last_column Whether this is the last column
870 * @return string HTML for modification cell
872 private function _getAndOrColCell(
873 $column_number, $selected = null, $last_column = false
875 $html_output = '<td class="center">';
876 if (! $last_column) {
877 $html_output .= '<strong>' . __('Or:') . '</strong>';
878 $html_output .= '<input type="radio"'
879 . ' name="criteriaAndOrColumn[' . $column_number . ']"'
880 . ' value="or"' . $selected['or'] . ' />';
881 $html_output .= '&nbsp;&nbsp;<strong>' . __('And:') . '</strong>';
882 $html_output .= '<input type="radio"'
883 . ' name="criteriaAndOrColumn[' . $column_number . ']"'
884 . ' value="and"' . $selected['and'] . ' />';
886 $html_output .= '<br />' . __('Ins');
887 $html_output .= '<input type="checkbox"'
888 . ' name="criteriaColumnInsert[' . $column_number . ']" />';
889 $html_output .= '&nbsp;&nbsp;' . __('Del');
890 $html_output .= '<input type="checkbox"'
891 . ' name="criteriaColumnDelete[' . $column_number . ']" />';
892 $html_output .= '</td>';
893 return $html_output;
897 * Provides search form's row containing column modifications options
898 * (For modifying search form's table columns)
900 * @return string HTML for search table's row
902 private function _getModifyColumnsRow()
904 $html_output = '<tr class="even noclick">';
905 $html_output .= '<th>' . __('Modify:') . '</th>';
906 $new_column_count = 0;
907 for (
908 $column_index = 0;
909 $column_index < $this->_criteria_column_count;
910 $column_index++
912 if (! empty($this->_criteriaColumnInsert)
913 && isset($this->_criteriaColumnInsert[$column_index])
914 && $this->_criteriaColumnInsert[$column_index] == 'on'
916 $html_output .= $this->_getAndOrColCell($new_column_count);
917 $new_column_count++;
918 } // end if
920 if (! empty($this->_criteriaColumnDelete)
921 && isset($this->_criteriaColumnDelete[$column_index])
922 && $this->_criteriaColumnDelete[$column_index] == 'on'
924 continue;
927 if (isset($this->_criteriaAndOrColumn[$column_index])) {
928 $this->_curAndOrCol[$new_column_count]
929 = $this->_criteriaAndOrColumn[$column_index];
931 $checked_options = array();
932 if (isset($this->_criteriaAndOrColumn[$column_index])
933 && $this->_criteriaAndOrColumn[$column_index] == 'or'
935 $checked_options['or'] = ' checked="checked"';
936 $checked_options['and'] = '';
937 } else {
938 $checked_options['and'] = ' checked="checked"';
939 $checked_options['or'] = '';
941 $html_output .= $this->_getAndOrColCell(
942 $new_column_count,
943 $checked_options,
944 ($column_index + 1 == $this->_criteria_column_count)
946 $new_column_count++;
947 } // end for
948 $html_output .= '</tr>';
949 return $html_output;
953 * Provides Insert/Delete options for criteria inputbox
954 * with AND/OR relationship modification options
956 * @param integer $row_index Number of criteria row
957 * @param array $checked_options If checked
959 * @return string HTML
961 private function _getInsDelAndOrCell($row_index, $checked_options)
963 $html_output = '<td class="' . $GLOBALS['cell_align_right'] . ' nowrap">';
964 $html_output .= '<!-- Row controls -->';
965 $html_output .= '<table class="nospacing nopadding">';
966 $html_output .= '<tr>';
967 $html_output .= '<td class="' . $GLOBALS['cell_align_right'] . ' nowrap">';
968 $html_output .= '<small>' . __('Ins:') . '</small>';
969 $html_output .= '<input type="checkbox"'
970 . ' name="criteriaRowInsert[' . $row_index . ']" />';
971 $html_output .= '</td>';
972 $html_output .= '<td class="' . $GLOBALS['cell_align_right'] . '">';
973 $html_output .= '<strong>' . __('And:') . '</strong>';
974 $html_output .= '</td>';
975 $html_output .= '<td>';
976 $html_output .= '<input type="radio"'
977 . ' name="criteriaAndOrRow[' . $row_index . ']" value="and"'
978 . $checked_options['and'] . ' />';
979 $html_output .= '</td>';
980 $html_output .= '</tr>';
981 $html_output .= '<tr>';
982 $html_output .= '<td class="' . $GLOBALS['cell_align_right'] . ' nowrap">';
983 $html_output .= '<small>' . __('Del:') . '</small>';
984 $html_output .= '<input type="checkbox"'
985 . ' name="criteriaRowDelete[' . $row_index . ']" />';
986 $html_output .= '</td>';
987 $html_output .= '<td class="' . $GLOBALS['cell_align_right'] . '">';
988 $html_output .= '<strong>' . __('Or:') . '</strong>';
989 $html_output .= '</td>';
990 $html_output .= '<td>';
991 $html_output .= '<input type="radio"'
992 . ' name="criteriaAndOrRow[' . $row_index . ']"'
993 . ' value="or"' . $checked_options['or'] . ' />';
994 $html_output .= '</td>';
995 $html_output .= '</tr>';
996 $html_output .= '</table>';
997 $html_output .= '</td>';
998 return $html_output;
1002 * Provides rows for criteria inputbox Insert/Delete options
1003 * with AND/OR relationship modification options
1005 * @param integer $new_row_index New row index if rows are added/deleted
1007 * @return string HTML table rows
1009 private function _getInputboxRow($new_row_index)
1011 $html_output = '';
1012 $new_column_count = 0;
1013 for (
1014 $column_index = 0;
1015 $column_index < $this->_criteria_column_count;
1016 $column_index++
1018 if (!empty($this->_criteriaColumnInsert)
1019 && isset($this->_criteriaColumnInsert[$column_index])
1020 && $this->_criteriaColumnInsert[$column_index] == 'on'
1022 $orFieldName = 'Or' . $new_row_index . '[' . $new_column_count . ']';
1023 $html_output .= '<td class="center">';
1024 $html_output .= '<input type="text"'
1025 . ' name="Or' . $orFieldName . '" class="textfield"'
1026 . ' style="width: ' . $this->_realwidth . '" size="20" />';
1027 $html_output .= '</td>';
1028 $new_column_count++;
1029 } // end if
1030 if (!empty($this->_criteriaColumnDelete)
1031 && isset($this->_criteriaColumnDelete[$column_index])
1032 && $this->_criteriaColumnDelete[$column_index] == 'on'
1034 continue;
1036 $or = 'Or' . $new_row_index;
1037 if (! empty($_REQUEST[$or]) && isset($_REQUEST[$or][$column_index])) {
1038 $tmp_or = $_REQUEST[$or][$column_index];
1039 } else {
1040 $tmp_or = '';
1042 $html_output .= '<td class="center">';
1043 $html_output .= '<input type="text"'
1044 . ' name="Or' . $new_row_index . '[' . $new_column_count . ']' . '"'
1045 . ' value="' . htmlspecialchars($tmp_or) . '" class="textfield"'
1046 . ' style="width: ' . $this->_realwidth . '" size="20" />';
1047 $html_output .= '</td>';
1048 if (!empty(${$or}) && isset(${$or}[$column_index])) {
1049 $GLOBALS[${'cur' . $or}][$new_column_count]
1050 = ${$or}[$column_index];
1052 $new_column_count++;
1053 } // end for
1054 return $html_output;
1058 * Provides rows for criteria inputbox Insert/Delete options
1059 * with AND/OR relationship modification options
1061 * @return string HTML table rows
1063 private function _getInsDelAndOrCriteriaRows()
1065 $html_output = '';
1066 $new_row_count = 0;
1067 $odd_row = true;
1068 $checked_options = array();
1069 for (
1070 $row_index = 0;
1071 $row_index <= $this->_criteria_row_count;
1072 $row_index++
1074 if (isset($this->_criteriaRowInsert[$row_index])
1075 && $this->_criteriaRowInsert[$row_index] == 'on'
1077 $checked_options['or'] = ' checked="checked"';
1078 $checked_options['and'] = '';
1079 $html_output .= '<tr class="' . ($odd_row ? 'odd' : 'even')
1080 . ' noclick">';
1081 $html_output .= $this->_getInsDelAndOrCell(
1082 $new_row_count, $checked_options
1084 $html_output .= $this->_getInputboxRow(
1085 $new_row_count
1087 $new_row_count++;
1088 $html_output .= '</tr>';
1089 $odd_row =! $odd_row;
1090 } // end if
1091 if (isset($this->_criteriaRowDelete[$row_index])
1092 && $this->_criteriaRowDelete[$row_index] == 'on'
1094 continue;
1096 if (isset($this->_criteriaAndOrRow[$row_index])) {
1097 $this->_curAndOrRow[$new_row_count]
1098 = $this->_criteriaAndOrRow[$row_index];
1100 if (isset($this->_criteriaAndOrRow[$row_index])
1101 && $this->_criteriaAndOrRow[$row_index] == 'and'
1103 $checked_options['and'] = ' checked="checked"';
1104 $checked_options['or'] = '';
1105 } else {
1106 $checked_options['or'] = ' checked="checked"';
1107 $checked_options['and'] = '';
1109 $html_output .= '<tr class="' . ($odd_row ? 'odd' : 'even')
1110 . ' noclick">';
1111 $html_output .= $this->_getInsDelAndOrCell(
1112 $new_row_count, $checked_options
1114 $html_output .= $this->_getInputboxRow(
1115 $new_row_count
1117 $new_row_count++;
1118 $html_output .= '</tr>';
1119 $odd_row =! $odd_row;
1120 } // end for
1121 $this->_new_row_count = $new_row_count;
1122 return $html_output;
1126 * Provides SELECT clause for building SQL query
1128 * @return string Select clause
1130 private function _getSelectClause()
1132 $select_clause = '';
1133 $select_clauses = array();
1134 for (
1135 $column_index = 0;
1136 $column_index < $this->_criteria_column_count;
1137 $column_index++
1139 if (! empty($this->_curField[$column_index])
1140 && isset($this->_curShow[$column_index])
1141 && $this->_curShow[$column_index] == 'on'
1143 $select = $this->_curField[$column_index];
1144 if (! empty($this->_curAlias[$column_index])) {
1145 $select .= " AS "
1146 . PMA_Util::backquote($this->_curAlias[$column_index]);
1148 $select_clauses[] = $select;
1150 } // end for
1151 if (!empty($select_clauses)) {
1152 $select_clause = 'SELECT '
1153 . htmlspecialchars(implode(", ", $select_clauses)) . "\n";
1155 return $select_clause;
1159 * Provides WHERE clause for building SQL query
1161 * @return string Where clause
1163 private function _getWhereClause()
1165 $where_clause = '';
1166 $criteria_cnt = 0;
1167 for (
1168 $column_index = 0;
1169 $column_index < $this->_criteria_column_count;
1170 $column_index++
1172 if (! empty($this->_curField[$column_index])
1173 && ! empty($this->_curCriteria[$column_index])
1174 && $column_index
1175 && isset($last_where)
1176 && isset($this->_curAndOrCol)
1178 $where_clause .= ' '
1179 . /*overload*/mb_strtoupper($this->_curAndOrCol[$last_where])
1180 . ' ';
1182 if (! empty($this->_curField[$column_index])
1183 && ! empty($this->_curCriteria[$column_index])
1185 $where_clause .= '(' . $this->_curField[$column_index] . ' '
1186 . $this->_curCriteria[$column_index] . ')';
1187 $last_where = $column_index;
1188 $criteria_cnt++;
1190 } // end for
1191 if ($criteria_cnt > 1) {
1192 $where_clause = '(' . $where_clause . ')';
1194 // OR rows ${'cur' . $or}[$column_index]
1195 if (! isset($this->_curAndOrRow)) {
1196 $this->_curAndOrRow = array();
1198 for (
1199 $row_index = 0;
1200 $row_index <= $this->_criteria_row_count;
1201 $row_index++
1203 $criteria_cnt = 0;
1204 $qry_orwhere = '';
1205 $last_orwhere = '';
1206 for (
1207 $column_index = 0;
1208 $column_index < $this->_criteria_column_count;
1209 $column_index++
1211 if (! empty($this->_curField[$column_index])
1212 && ! empty($_REQUEST['Or' . $row_index][$column_index])
1213 && $column_index
1215 $qry_orwhere .= ' '
1216 . /*overload*/mb_strtoupper(
1217 $this->_curAndOrCol[$last_orwhere]
1219 . ' ';
1221 if (! empty($this->_curField[$column_index])
1222 && ! empty($_REQUEST['Or' . $row_index][$column_index])
1224 $qry_orwhere .= '(' . $this->_curField[$column_index]
1225 . ' '
1226 . $_REQUEST['Or' . $row_index][$column_index]
1227 . ')';
1228 $last_orwhere = $column_index;
1229 $criteria_cnt++;
1231 } // end for
1232 if ($criteria_cnt > 1) {
1233 $qry_orwhere = '(' . $qry_orwhere . ')';
1235 if (! empty($qry_orwhere)) {
1236 $where_clause .= "\n"
1237 . /*overload*/mb_strtoupper(
1238 isset($this->_curAndOrRow[$row_index])
1239 ? $this->_curAndOrRow[$row_index] . ' '
1240 : ''
1242 . $qry_orwhere;
1243 } // end if
1244 } // end for
1246 if (! empty($where_clause) && $where_clause != '()') {
1247 $where_clause = 'WHERE ' . htmlspecialchars($where_clause) . "\n";
1248 } // end if
1249 return $where_clause;
1253 * Provides ORDER BY clause for building SQL query
1255 * @return string Order By clause
1257 private function _getOrderByClause()
1259 $orderby_clause = '';
1260 $orderby_clauses = array();
1262 // Create copy of instance variables
1263 $field = $this->_curField;
1264 $sort = $this->_curSort;
1265 $sortOrder = $this->_curSortOrder;
1266 if (!empty($sortOrder)
1267 && count($sortOrder) == count($sort)
1268 && count($sortOrder) == count($field)
1270 // Sort all three arrays based on sort order
1271 array_multisort($sortOrder, $sort, $field);
1274 for (
1275 $column_index = 0;
1276 $column_index < $this->_criteria_column_count;
1277 $column_index++
1279 // if all columns are chosen with * selector,
1280 // then sorting isn't available
1281 // Fix for Bug #570698
1282 if (empty($field[$column_index])
1283 && empty($sort[$column_index])
1285 continue;
1288 if (/*overload*/mb_substr($field[$column_index], -2) == '.*') {
1289 continue;
1292 if (! empty($sort[$column_index])) {
1293 $orderby_clauses[] = $field[$column_index] . ' '
1294 . $sort[$column_index];
1296 } // end for
1297 if (!empty($orderby_clauses)) {
1298 $orderby_clause = 'ORDER BY '
1299 . htmlspecialchars(implode(", ", $orderby_clauses)) . "\n";
1301 return $orderby_clause;
1305 * Provides UNIQUE columns and INDEX columns present in criteria tables
1307 * @param array $search_tables Tables involved in the search
1308 * @param array $search_columns Columns involved in the search
1309 * @param array $where_clause_columns Columns having criteria where clause
1311 * @return array having UNIQUE and INDEX columns
1313 private function _getIndexes($search_tables, $search_columns,
1314 $where_clause_columns
1316 $unique_columns = array();
1317 $index_columns = array();
1319 foreach ($search_tables as $table) {
1320 $indexes = $GLOBALS['dbi']->getTableIndexes($this->_db, $table);
1321 foreach ($indexes as $index) {
1322 $column = $table . '.' . $index['Column_name'];
1323 if (isset($search_columns[$column])) {
1324 if ($index['Non_unique'] == 0) {
1325 if (isset($where_clause_columns[$column])) {
1326 $unique_columns[$column] = 'Y';
1327 } else {
1328 $unique_columns[$column] = 'N';
1330 } else {
1331 if (isset($where_clause_columns[$column])) {
1332 $index_columns[$column] = 'Y';
1333 } else {
1334 $index_columns[$column] = 'N';
1338 } // end while (each index of a table)
1339 } // end while (each table)
1341 return array(
1342 'unique' => $unique_columns,
1343 'index' => $index_columns
1348 * Provides UNIQUE columns and INDEX columns present in criteria tables
1350 * @param array $search_tables Tables involved in the search
1351 * @param array $search_columns Columns involved in the search
1352 * @param array $where_clause_columns Columns having criteria where clause
1354 * @return array having UNIQUE and INDEX columns
1356 private function _getLeftJoinColumnCandidates($search_tables, $search_columns,
1357 $where_clause_columns
1359 $GLOBALS['dbi']->selectDb($this->_db);
1361 // Get unique columns and index columns
1362 $indexes = $this->_getIndexes(
1363 $search_tables, $search_columns, $where_clause_columns
1365 $unique_columns = $indexes['unique'];
1366 $index_columns = $indexes['index'];
1368 list($candidate_columns, $needsort)
1369 = $this->_getLeftJoinColumnCandidatesBest(
1370 $search_tables,
1371 $where_clause_columns,
1372 $unique_columns,
1373 $index_columns
1376 // If we came up with $unique_columns (very good) or $index_columns (still
1377 // good) as $candidate_columns we want to check if we have any 'Y' there
1378 // (that would mean that they were also found in the whereclauses
1379 // which would be great). if yes, we take only those
1380 if ($needsort != 1) {
1381 return $candidate_columns;
1384 $very_good = array();
1385 $still_good = array();
1386 foreach ($candidate_columns as $column => $is_where) {
1387 $table = explode('.', $column);
1388 $table = $table[0];
1389 if ($is_where == 'Y') {
1390 $very_good[$column] = $table;
1391 } else {
1392 $still_good[$column] = $table;
1395 if (count($very_good) > 0) {
1396 $candidate_columns = $very_good;
1397 // Candidates restricted in index+where
1398 } else {
1399 $candidate_columns = $still_good;
1400 // None of the candidates where in a where-clause
1403 return $candidate_columns;
1407 * Provides the main table to form the LEFT JOIN clause
1409 * @param array $search_tables Tables involved in the search
1410 * @param array $search_columns Columns involved in the search
1411 * @param array $where_clause_columns Columns having criteria where clause
1412 * @param array $where_clause_tables Tables having criteria where clause
1414 * @return string table name
1416 private function _getMasterTable($search_tables, $search_columns,
1417 $where_clause_columns, $where_clause_tables
1419 if (count($where_clause_tables) == 1) {
1420 // If there is exactly one column that has a decent where-clause
1421 // we will just use this
1422 $master = key($where_clause_tables);
1423 return $master;
1426 // Now let's find out which of the tables has an index
1427 // (When the control user is the same as the normal user
1428 // because he is using one of his databases as pmadb,
1429 // the last db selected is not always the one where we need to work)
1430 $candidate_columns = $this->_getLeftJoinColumnCandidates(
1431 $search_tables, $search_columns, $where_clause_columns
1434 // Generally, we need to display all the rows of foreign (referenced)
1435 // table, whether they have any matching row in child table or not.
1436 // So we select candidate tables which are foreign tables.
1437 $foreign_tables = array();
1438 foreach ($candidate_columns as $one_table) {
1439 $foreigners = PMA_getForeigners($this->_db, $one_table);
1440 foreach ($foreigners as $key => $foreigner) {
1441 if ($key != 'foreign_keys_data') {
1442 if (in_array($foreigner['foreign_table'], $candidate_columns)) {
1443 $foreign_tables[$foreigner['foreign_table']]
1444 = $foreigner['foreign_table'];
1446 continue;
1448 foreach ($foreigner as $one_key) {
1449 if (in_array($one_key['ref_table_name'], $candidate_columns)) {
1450 $foreign_tables[$one_key['ref_table_name']]
1451 = $one_key['ref_table_name'];
1456 if (count($foreign_tables)) {
1457 $candidate_columns = $foreign_tables;
1460 // If our array of candidates has more than one member we'll just
1461 // find the smallest table.
1462 // Of course the actual query would be faster if we check for
1463 // the Criteria which gives the smallest result set in its table,
1464 // but it would take too much time to check this
1465 if (!(count($candidate_columns) > 1)) {
1466 reset($candidate_columns);
1467 $master = current($candidate_columns); // Only one single candidate
1468 return $master;
1471 // Of course we only want to check each table once
1472 $checked_tables = $candidate_columns;
1473 $tsize = array();
1474 $csize = array();
1475 foreach ($candidate_columns as $table) {
1476 if ($checked_tables[$table] != 1) {
1477 $_table = new PMA_Table($table, $this->_db);
1478 $tsize[$table] = $_table->countRecords();
1479 $checked_tables[$table] = 1;
1481 $csize[$table] = $tsize[$table];
1483 arsort($csize);
1484 reset($csize);
1485 $master = key($csize); // Largest
1487 return $master;
1491 * Provides columns and tables that have valid where clause criteria
1493 * @return array
1495 private function _getWhereClauseTablesAndColumns()
1497 $where_clause_columns = array();
1498 $where_clause_tables = array();
1500 // Now we need all tables that we have in the where clause
1501 for (
1502 $column_index = 0, $nb = count($this->_criteria);
1503 $column_index < $nb;
1504 $column_index++
1506 $current_table = explode('.', $_POST['criteriaColumn'][$column_index]);
1507 if (empty($current_table[0]) || empty($current_table[1])) {
1508 continue;
1509 } // end if
1510 $table = str_replace('`', '', $current_table[0]);
1511 $column = str_replace('`', '', $current_table[1]);
1512 $column = $table . '.' . $column;
1513 // Now we know that our array has the same numbers as $criteria
1514 // we can check which of our columns has a where clause
1515 if (! empty($this->_criteria[$column_index])) {
1516 if (/*overload*/mb_substr($this->_criteria[$column_index], 0, 1) == '='
1517 || /*$pmaString->*/stristr($this->_criteria[$column_index], 'is')
1519 $where_clause_columns[$column] = $column;
1520 $where_clause_tables[$table] = $table;
1522 } // end if
1523 } // end for
1524 return array(
1525 'where_clause_tables' => $where_clause_tables,
1526 'where_clause_columns' => $where_clause_columns
1531 * Provides FROM clause for building SQL query
1533 * @param array $curField List of selected columns
1535 * @return string FROM clause
1537 private function _getFromClause($curField)
1539 $from_clause = '';
1540 if (empty($curField)) {
1541 return $from_clause;
1544 // Initialize some variables
1545 $search_tables = $search_columns = array();
1547 // We only start this if we have fields, otherwise it would be dumb
1548 foreach ($curField as $value) {
1549 $parts = explode('.', $value);
1550 if (! empty($parts[0]) && ! empty($parts[1])) {
1551 $table = str_replace('`', '', $parts[0]);
1552 $search_tables[$table] = $table;
1553 $search_columns[] = $table . '.' . str_replace(
1554 '`', '', $parts[1]
1557 } // end while
1559 // Create LEFT JOINS out of Relations
1560 $from_clause = $this->_getJoinForFromClause(
1561 $search_tables, $search_columns
1564 // In case relations are not defined, just generate the FROM clause
1565 // from the list of tables, however we don't generate any JOIN
1566 if (empty($from_clause)) {
1567 // Create cartesian product
1568 $from_clause = implode(
1569 ", ", array_map('PMA_Util::backquote', $search_tables)
1573 return $from_clause;
1577 * Formulates the WHERE clause by JOINing tables
1579 * @param array $searchTables Tables involved in the search
1580 * @param array $searchColumns Columns involved in the search
1582 * @return string table name
1584 private function _getJoinForFromClause($searchTables, $searchColumns)
1586 // $relations[master_table][foreign_table] => clause
1587 $relations = array();
1589 // Fill $relations with inter table relationship data
1590 foreach ($searchTables as $oneTable) {
1591 $this->_loadRelationsForTable($relations, $oneTable);
1594 // Get tables and columns with valid where clauses
1595 $validWhereClauses = $this->_getWhereClauseTablesAndColumns();
1596 $whereClauseTables = $validWhereClauses['where_clause_tables'];
1597 $whereClauseColumns = $validWhereClauses['where_clause_columns'];
1599 // Get master table
1600 $master = $this->_getMasterTable(
1601 $searchTables, $searchColumns,
1602 $whereClauseColumns, $whereClauseTables
1605 // Will incldue master tables and all tables that can be combined into
1606 // a cluster by their relation
1607 $finalized = array();
1608 if (mb_strlen($master) > 0) {
1609 // Add master tables
1610 $finalized[$master] = '';
1612 // Fill the $finalized array with JOIN clauses for each table
1613 $this->_fillJoinClauses($finalized, $relations, $searchTables);
1615 // JOIN clause
1616 $join = '';
1618 // Tables that can not be combined with the table cluster
1619 // which includes master table
1620 $unfinalized = array_diff($searchTables, array_keys($finalized));
1621 if (count($unfinalized) > 0) {
1623 // We need to look for intermediary tables to JOIN unfinalized tables
1624 // Heuristic to chose intermediary tables is to look for tables
1625 // having relationships with unfinalized tables
1626 foreach ($unfinalized as $oneTable) {
1628 $references = PMA_getChildReferences($this->_db, $oneTable);
1629 foreach ($references as $column => $columnReferences) {
1630 foreach ($columnReferences as $reference) {
1632 // Only from this schema
1633 if ($reference['table_schema'] != $this->_db) {
1634 continue;
1637 $table = $reference['table_name'];
1639 $this->_loadRelationsForTable($relations, $table);
1641 // Make copies
1642 $tempFinalized = $finalized;
1643 $tempSearchTables = $searchTables;
1644 $tempSearchTables[] = $table;
1646 // Try joining with the added table
1647 $this->_fillJoinClauses(
1648 $tempFinalized, $relations, $tempSearchTables
1651 $tempUnfinalized = array_diff(
1652 $tempSearchTables, array_keys($tempFinalized)
1654 // Take greedy approach.
1655 // If the unfinalized count drops we keep the new table
1656 // and switch temporary varibles with the original ones
1657 if (count($tempUnfinalized) < count($unfinalized)) {
1658 $finalized = $tempFinalized;
1659 $searchTables = $tempSearchTables;
1662 // We are done if no unfinalized tables anymore
1663 if (count($tempUnfinalized) == 0) {
1664 break 3;
1670 $unfinalized = array_diff($searchTables, array_keys($finalized));
1671 // If there are still unfinalized tables
1672 if (count($unfinalized) > 0) {
1673 // Add these tables as cartesian product before joined tables
1674 $join .= implode(
1675 ', ', array_map('PMA_Util::backquote', $unfinalized)
1680 $first = true;
1681 // Add joined tables
1682 foreach ($finalized as $table => $clause) {
1683 if ($first) {
1684 if (! empty($join)) {
1685 $join .= ", ";
1687 $join .= PMA_Util::backquote($table);
1688 $first = false;
1689 } else {
1690 $join .= "\n LEFT JOIN " . PMA_Util::backquote(
1691 $table
1692 ) . " ON " . $clause;
1696 return $join;
1700 * Loads relations for a given table into the $relations array
1702 * @param array &$relations array of relations
1703 * @param string $oneTable the table
1705 * @return void
1707 private function _loadRelationsForTable(&$relations, $oneTable)
1709 $relations[$oneTable] = array();
1711 $foreigners = PMA_getForeigners($GLOBALS['db'], $oneTable);
1712 foreach ($foreigners as $field => $foreigner) {
1713 // Foreign keys data
1714 if ($field == 'foreign_keys_data') {
1715 foreach ($foreigner as $oneKey) {
1716 $clauses = array();
1717 // There may be multiple column relations
1718 foreach ($oneKey['index_list'] as $index => $oneField) {
1719 $clauses[]
1720 = PMA_Util::backquote($oneTable) . "."
1721 . PMA_Util::backquote($oneField) . " = "
1722 . PMA_Util::backquote($oneKey['ref_table_name']) . "."
1723 . PMA_Util::backquote($oneKey['ref_index_list'][$index]);
1725 // Combine multiple column relations with AND
1726 $relations[$oneTable][$oneKey['ref_table_name']]
1727 = implode(" AND ", $clauses);
1729 } else { // Internal relations
1730 $relations[$oneTable][$foreigner['foreign_table']]
1731 = PMA_Util::backquote($oneTable) . "."
1732 . PMA_Util::backquote($field) . " = "
1733 . PMA_Util::backquote($foreigner['foreign_table']) . "."
1734 . PMA_Util::backquote($foreigner['foreign_field']);
1740 * Fills the $finalized arrays with JOIN clauses for each of the tables
1742 * @param array &$finalized JOIN clauses for each table
1743 * @param array $relations Relations among tables
1744 * @param array $searchTables Tables involved in the search
1746 * @return void
1748 private function _fillJoinClauses(&$finalized, $relations, $searchTables)
1750 while (true) {
1751 $added = false;
1752 foreach ($searchTables as $masterTable) {
1753 $foreignData = $relations[$masterTable];
1754 foreach ($foreignData as $foreignTable => $clause) {
1755 if (! isset($finalized[$masterTable])
1756 && isset($finalized[$foreignTable])
1758 $finalized[$masterTable] = $clause;
1759 $added = true;
1760 } elseif (! isset($finalized[$foreignTable])
1761 && isset($finalized[$masterTable])
1762 && in_array($foreignTable, $searchTables)
1764 $finalized[$foreignTable] = $clause;
1765 $added = true;
1767 if ($added) {
1768 // We are done if all tables are in $finalized
1769 if (count($finalized) == count($searchTables)) {
1770 return;
1775 // If no new tables were added during this iteration, break;
1776 if (! $added) {
1777 return;
1783 * Provides the generated SQL query
1785 * @param array $curField List of selected columns
1787 * @return string SQL query
1789 private function _getSQLQuery($curField)
1791 $sql_query = '';
1792 // get SELECT clause
1793 $sql_query .= $this->_getSelectClause();
1794 // get FROM clause
1795 $from_clause = $this->_getFromClause($curField);
1796 if (! empty($from_clause)) {
1797 $sql_query .= 'FROM ' . htmlspecialchars($from_clause) . "\n";
1799 // get WHERE clause
1800 $sql_query .= $this->_getWhereClause();
1801 // get ORDER BY clause
1802 $sql_query .= $this->_getOrderByClause();
1803 return $sql_query;
1807 * Provides the generated QBE form
1809 * @return string QBE form
1811 public function getSelectionForm()
1813 $html_output = '<form action="db_qbe.php" method="post" id="formQBE" '
1814 . 'class="lock-page">';
1815 $html_output .= '<fieldset>';
1817 if ($GLOBALS['cfgRelation']['savedsearcheswork']) {
1818 $html_output .= $this->_getSavedSearchesField();
1821 $html_output .= '<table class="data" style="width: 100%;">';
1822 // Get table's <tr> elements
1823 $html_output .= $this->_getColumnNamesRow();
1824 $html_output .= $this->_getColumnAliasRow();
1825 $html_output .= $this->_getShowRow();
1826 $html_output .= $this->_getSortRow();
1827 $html_output .= $this->_getSortOrder();
1828 $html_output .= $this->_getCriteriaInputboxRow();
1829 $html_output .= $this->_getInsDelAndOrCriteriaRows();
1830 $html_output .= $this->_getModifyColumnsRow();
1831 $html_output .= '</table>';
1832 $this->_new_row_count--;
1833 $url_params = array();
1834 $url_params['db'] = $this->_db;
1835 $url_params['criteriaColumnCount'] = $this->_new_column_count;
1836 $url_params['rows'] = $this->_new_row_count;
1837 $html_output .= PMA_URL_getHiddenInputs($url_params);
1838 $html_output .= '</fieldset>';
1839 // get footers
1840 $html_output .= $this->_getTableFooters();
1841 // get tables select list
1842 $html_output .= $this->_getTablesList();
1843 $html_output .= '</form>';
1844 $html_output .= '<form action="db_qbe.php" method="post" class="lock-page">';
1845 $html_output .= PMA_URL_getHiddenInputs(array('db' => $this->_db));
1846 // get SQL query
1847 $html_output .= '<div class="floatleft" style="width:50%">';
1848 $html_output .= '<fieldset>';
1849 $html_output .= '<legend>'
1850 . sprintf(
1851 __('SQL query on database <b>%s</b>:'),
1852 PMA_Util::getDbLink($this->_db)
1854 $html_output .= '</legend>';
1855 $text_dir = 'ltr';
1856 $html_output .= '<textarea cols="80" name="sql_query" id="textSqlquery"'
1857 . ' rows="' . ((count($this->_criteriaTables) > 30) ? '15' : '7') . '"'
1858 . ' dir="' . $text_dir . '">';
1860 if (empty($this->_curField)) {
1861 $this->_curField = array();
1863 $html_output .= $this->_getSQLQuery($this->_curField);
1865 $html_output .= '</textarea>';
1866 $html_output .= '</fieldset>';
1867 // displays form's footers
1868 $html_output .= '<fieldset class="tblFooters">';
1869 $html_output .= '<input type="hidden" name="submit_sql" value="1" />';
1870 $html_output .= '<input type="submit" value="' . __('Submit Query') . '" />';
1871 $html_output .= '</fieldset>';
1872 $html_output .= '</div>';
1873 $html_output .= '</form>';
1874 return $html_output;
1878 * Get fields to display
1880 * @return string
1882 private function _getSavedSearchesField()
1884 $html_output = __('Saved bookmarked search:');
1885 $html_output .= ' <select name="searchId" id="searchId">';
1886 $html_output .= '<option value="">' . __('New bookmark') . '</option>';
1888 $currentSearch = $this->_getCurrentSearch();
1889 $currentSearchId = null;
1890 $currentSearchName = null;
1891 if (null != $currentSearch) {
1892 $currentSearchId = $currentSearch->getId();
1893 $currentSearchName = $currentSearch->getSearchName();
1896 foreach ($this->_savedSearchList as $id => $name) {
1897 $html_output .= '<option value="' . htmlspecialchars($id)
1898 . '" ' . (
1899 $id == $currentSearchId
1900 ? 'selected="selected" '
1901 : ''
1903 . '>'
1904 . htmlspecialchars($name)
1905 . '</option>';
1907 $html_output .= '</select>';
1908 $html_output .= '<input type="text" name="searchName" id="searchName" '
1909 . 'value="' . $currentSearchName . '" />';
1910 $html_output .= '<input type="hidden" name="action" id="action" value="" />';
1911 $html_output .= '<input type="submit" name="saveSearch" id="saveSearch" '
1912 . 'value="' . __('Create bookmark') . '" />';
1913 if (null !== $currentSearchId) {
1914 $html_output .= '<input type="submit" name="updateSearch" '
1915 . 'id="updateSearch" value="' . __('Update bookmark') . '" />';
1916 $html_output .= '<input type="submit" name="deleteSearch" '
1917 . 'id="deleteSearch" value="' . __('Delete bookmark') . '" />';
1920 return $html_output;
1924 * Initialize _criteria_column_count
1926 * @return int Previous number of columns
1928 private function _initializeCriteriasCount()
1930 // sets column count
1931 $criteriaColumnCount = PMA_ifSetOr(
1932 $_REQUEST['criteriaColumnCount'],
1934 'numeric'
1936 $criteriaColumnAdd = PMA_ifSetOr(
1937 $_REQUEST['criteriaColumnAdd'],
1939 'numeric'
1941 $this->_criteria_column_count = max(
1942 $criteriaColumnCount + $criteriaColumnAdd,
1946 // sets row count
1947 $rows = PMA_ifSetOr($_REQUEST['rows'], 0, 'numeric');
1948 $criteriaRowAdd = PMA_ifSetOr($_REQUEST['criteriaRowAdd'], 0, 'numeric');
1949 $this->_criteria_row_count = max($rows + $criteriaRowAdd, 0);
1951 return $criteriaColumnCount;
1955 * Get best
1957 * @param array $search_tables Tables involved in the search
1958 * @param array $where_clause_columns Columns with where clause
1959 * @param array $unique_columns Unique columns
1960 * @param array $index_columns Indexed columns
1962 * @return array
1964 private function _getLeftJoinColumnCandidatesBest(
1965 $search_tables, $where_clause_columns, $unique_columns, $index_columns
1967 // now we want to find the best.
1968 if (isset($unique_columns) && count($unique_columns) > 0) {
1969 $candidate_columns = $unique_columns;
1970 $needsort = 1;
1971 return array($candidate_columns, $needsort);
1972 } elseif (isset($index_columns) && count($index_columns) > 0) {
1973 $candidate_columns = $index_columns;
1974 $needsort = 1;
1975 return array($candidate_columns, $needsort);
1976 } elseif (isset($where_clause_columns) && count($where_clause_columns) > 0) {
1977 $candidate_columns = $where_clause_columns;
1978 $needsort = 0;
1979 return array($candidate_columns, $needsort);
1980 } else {
1981 $candidate_columns = $search_tables;
1982 $needsort = 0;
1983 return array($candidate_columns, $needsort);