aboutsummaryrefslogtreecommitdiff
path: root/srcs/phpmyadmin/libraries/classes/Display/Results.php
diff options
context:
space:
mode:
Diffstat (limited to 'srcs/phpmyadmin/libraries/classes/Display/Results.php')
-rw-r--r--srcs/phpmyadmin/libraries/classes/Display/Results.php5698
1 files changed, 5698 insertions, 0 deletions
diff --git a/srcs/phpmyadmin/libraries/classes/Display/Results.php b/srcs/phpmyadmin/libraries/classes/Display/Results.php
new file mode 100644
index 0000000..0fcc5c6
--- /dev/null
+++ b/srcs/phpmyadmin/libraries/classes/Display/Results.php
@@ -0,0 +1,5698 @@
+<?php
+/* vim: set expandtab sw=4 ts=4 sts=4: */
+/**
+ * Hold the PhpMyAdmin\Display\Results class
+ *
+ * @package PhpMyAdmin
+ */
+declare(strict_types=1);
+
+namespace PhpMyAdmin\Display;
+
+use PhpMyAdmin\Config\SpecialSchemaLinks;
+use PhpMyAdmin\Core;
+use PhpMyAdmin\DatabaseInterface;
+use PhpMyAdmin\Index;
+use PhpMyAdmin\Message;
+use PhpMyAdmin\Plugins\Transformations\Output\Text_Octetstream_Sql;
+use PhpMyAdmin\Plugins\Transformations\Output\Text_Plain_Json;
+use PhpMyAdmin\Plugins\Transformations\Output\Text_Plain_Sql;
+use PhpMyAdmin\Plugins\Transformations\Text_Plain_Link;
+use PhpMyAdmin\Plugins\TransformationsPlugin;
+use PhpMyAdmin\Relation;
+use PhpMyAdmin\Response;
+use PhpMyAdmin\Sanitize;
+use PhpMyAdmin\Sql;
+use PhpMyAdmin\SqlParser\Statements\SelectStatement;
+use PhpMyAdmin\SqlParser\Utils\Query;
+use PhpMyAdmin\Table;
+use PhpMyAdmin\Template;
+use PhpMyAdmin\Transformations;
+use PhpMyAdmin\Url;
+use PhpMyAdmin\Util;
+use stdClass;
+
+/**
+ * Handle all the functionalities related to displaying results
+ * of sql queries, stored procedure, browsing sql processes or
+ * displaying binary log.
+ *
+ * @package PhpMyAdmin
+ */
+class Results
+{
+ // Define constants
+ public const NO_EDIT_OR_DELETE = 'nn';
+ public const UPDATE_ROW = 'ur';
+ public const DELETE_ROW = 'dr';
+ public const KILL_PROCESS = 'kp';
+
+ public const POSITION_LEFT = 'left';
+ public const POSITION_RIGHT = 'right';
+ public const POSITION_BOTH = 'both';
+ public const POSITION_NONE = 'none';
+
+ public const DISPLAY_FULL_TEXT = 'F';
+ public const DISPLAY_PARTIAL_TEXT = 'P';
+
+ public const HEADER_FLIP_TYPE_AUTO = 'auto';
+ public const HEADER_FLIP_TYPE_CSS = 'css';
+ public const HEADER_FLIP_TYPE_FAKE = 'fake';
+
+ public const DATE_FIELD = 'date';
+ public const DATETIME_FIELD = 'datetime';
+ public const TIMESTAMP_FIELD = 'timestamp';
+ public const TIME_FIELD = 'time';
+ public const STRING_FIELD = 'string';
+ public const GEOMETRY_FIELD = 'geometry';
+ public const BLOB_FIELD = 'BLOB';
+ public const BINARY_FIELD = 'BINARY';
+
+ public const RELATIONAL_KEY = 'K';
+ public const RELATIONAL_DISPLAY_COLUMN = 'D';
+
+ public const GEOMETRY_DISP_GEOM = 'GEOM';
+ public const GEOMETRY_DISP_WKT = 'WKT';
+ public const GEOMETRY_DISP_WKB = 'WKB';
+
+ public const SMART_SORT_ORDER = 'SMART';
+ public const ASCENDING_SORT_DIR = 'ASC';
+ public const DESCENDING_SORT_DIR = 'DESC';
+
+ public const TABLE_TYPE_INNO_DB = 'InnoDB';
+ public const ALL_ROWS = 'all';
+ public const QUERY_TYPE_SELECT = 'SELECT';
+
+ public const ROUTINE_PROCEDURE = 'procedure';
+ public const ROUTINE_FUNCTION = 'function';
+
+ public const ACTION_LINK_CONTENT_ICONS = 'icons';
+ public const ACTION_LINK_CONTENT_TEXT = 'text';
+
+ // Declare global fields
+
+ /** array with properties of the class */
+ private $_property_array = [
+
+ /** integer server id */
+ 'server' => null,
+
+ /** string Database name */
+ 'db' => null,
+
+ /** string Table name */
+ 'table' => null,
+
+ /** string the URL to go back in case of errors */
+ 'goto' => null,
+
+ /** string the SQL query */
+ 'sql_query' => null,
+
+ /**
+ * integer the total number of rows returned by the SQL query without any
+ * appended "LIMIT" clause programmatically
+ */
+ 'unlim_num_rows' => null,
+
+ /** array meta information about fields */
+ 'fields_meta' => null,
+
+ /** boolean */
+ 'is_count' => null,
+
+ /** integer */
+ 'is_export' => null,
+
+ /** boolean */
+ 'is_func' => null,
+
+ /** integer */
+ 'is_analyse' => null,
+
+ /** integer the total number of rows returned by the SQL query */
+ 'num_rows' => null,
+
+ /** integer the total number of fields returned by the SQL query */
+ 'fields_cnt' => null,
+
+ /** double time taken for execute the SQL query */
+ 'querytime' => null,
+
+ /** string path for theme images directory */
+ 'pma_theme_image' => null,
+
+ /** string */
+ 'text_dir' => null,
+
+ /** boolean */
+ 'is_maint' => null,
+
+ /** boolean */
+ 'is_explain' => null,
+
+ /** boolean */
+ 'is_show' => null,
+
+ /** boolean */
+ 'is_browse_distinct' => null,
+
+ /** array table definitions */
+ 'showtable' => null,
+
+ /** string */
+ 'printview' => null,
+
+ /** string URL query */
+ 'url_query' => null,
+
+ /** array column names to highlight */
+ 'highlight_columns' => null,
+
+ /** array holding various display information */
+ 'display_params' => null,
+
+ /** array mime types information of fields */
+ 'mime_map' => null,
+
+ /** boolean */
+ 'editable' => null,
+
+ /** random unique ID to distinguish result set */
+ 'unique_id' => null,
+
+ /** where clauses for each row, each table in the row */
+ 'whereClauseMap' => [],
+ ];
+
+ /**
+ * This variable contains the column transformation information
+ * for some of the system databases.
+ * One element of this array represent all relevant columns in all tables in
+ * one specific database
+ */
+ public $transformation_info;
+
+ /**
+ * @var Relation
+ */
+ private $relation;
+
+ /**
+ * @var Transformations
+ */
+ private $transformations;
+
+ /**
+ * @var Template
+ */
+ public $template;
+
+ /**
+ * Constructor for PhpMyAdmin\Display\Results class
+ *
+ * @param string $db the database name
+ * @param string $table the table name
+ * @param int $server the server id
+ * @param string $goto the URL to go back in case of errors
+ * @param string $sql_query the SQL query
+ *
+ * @access public
+ */
+ public function __construct($db, $table, $server, $goto, $sql_query)
+ {
+ $this->relation = new Relation($GLOBALS['dbi']);
+ $this->transformations = new Transformations();
+ $this->template = new Template();
+
+ $this->_setDefaultTransformations();
+
+ $this->__set('db', $db);
+ $this->__set('table', $table);
+ $this->__set('server', $server);
+ $this->__set('goto', $goto);
+ $this->__set('sql_query', $sql_query);
+ $this->__set('unique_id', mt_rand());
+ }
+
+ /**
+ * Get any property of this class
+ *
+ * @param string $property name of the property
+ *
+ * @return mixed|void if property exist, value of the relevant property
+ */
+ public function __get($property)
+ {
+ return $this->_property_array[$property] ?? null;
+ }
+
+ /**
+ * Set values for any property of this class
+ *
+ * @param string $property name of the property
+ * @param mixed $value value to set
+ *
+ * @return void
+ */
+ public function __set($property, $value)
+ {
+ if (array_key_exists($property, $this->_property_array)) {
+ $this->_property_array[$property] = $value;
+ }
+ }
+
+ /**
+ * Sets default transformations for some columns
+ *
+ * @return void
+ */
+ private function _setDefaultTransformations()
+ {
+ $json_highlighting_data = [
+ 'libraries/classes/Plugins/Transformations/Output/Text_Plain_Json.php',
+ Text_Plain_Json::class,
+ 'Text_Plain',
+ ];
+ $sql_highlighting_data = [
+ 'libraries/classes/Plugins/Transformations/Output/Text_Plain_Sql.php',
+ Text_Plain_Sql::class,
+ 'Text_Plain',
+ ];
+ $blob_sql_highlighting_data = [
+ 'libraries/classes/Plugins/Transformations/Output/Text_Octetstream_Sql.php',
+ Text_Octetstream_Sql::class,
+ 'Text_Octetstream',
+ ];
+ $link_data = [
+ 'libraries/classes/Plugins/Transformations/Text_Plain_Link.php',
+ Text_Plain_Link::class,
+ 'Text_Plain',
+ ];
+ $this->transformation_info = [
+ 'information_schema' => [
+ 'events' => [
+ 'event_definition' => $sql_highlighting_data,
+ ],
+ 'processlist' => [
+ 'info' => $sql_highlighting_data,
+ ],
+ 'routines' => [
+ 'routine_definition' => $sql_highlighting_data,
+ ],
+ 'triggers' => [
+ 'action_statement' => $sql_highlighting_data,
+ ],
+ 'views' => [
+ 'view_definition' => $sql_highlighting_data,
+ ],
+ ],
+ 'mysql' => [
+ 'event' => [
+ 'body' => $blob_sql_highlighting_data,
+ 'body_utf8' => $blob_sql_highlighting_data,
+ ],
+ 'general_log' => [
+ 'argument' => $sql_highlighting_data,
+ ],
+ 'help_category' => [
+ 'url' => $link_data,
+ ],
+ 'help_topic' => [
+ 'example' => $sql_highlighting_data,
+ 'url' => $link_data,
+ ],
+ 'proc' => [
+ 'param_list' => $blob_sql_highlighting_data,
+ 'returns' => $blob_sql_highlighting_data,
+ 'body' => $blob_sql_highlighting_data,
+ 'body_utf8' => $blob_sql_highlighting_data,
+ ],
+ 'slow_log' => [
+ 'sql_text' => $sql_highlighting_data,
+ ],
+ ],
+ ];
+
+ $cfgRelation = $this->relation->getRelationsParam();
+ if ($cfgRelation['db']) {
+ $this->transformation_info[$cfgRelation['db']] = [];
+ $relDb = &$this->transformation_info[$cfgRelation['db']];
+ if (! empty($cfgRelation['history'])) {
+ $relDb[$cfgRelation['history']] = [
+ 'sqlquery' => $sql_highlighting_data,
+ ];
+ }
+ if (! empty($cfgRelation['bookmark'])) {
+ $relDb[$cfgRelation['bookmark']] = [
+ 'query' => $sql_highlighting_data,
+ ];
+ }
+ if (! empty($cfgRelation['tracking'])) {
+ $relDb[$cfgRelation['tracking']] = [
+ 'schema_sql' => $sql_highlighting_data,
+ 'data_sql' => $sql_highlighting_data,
+ ];
+ }
+ if (! empty($cfgRelation['favorite'])) {
+ $relDb[$cfgRelation['favorite']] = [
+ 'tables' => $json_highlighting_data,
+ ];
+ }
+ if (! empty($cfgRelation['recent'])) {
+ $relDb[$cfgRelation['recent']] = [
+ 'tables' => $json_highlighting_data,
+ ];
+ }
+ if (! empty($cfgRelation['savedsearches'])) {
+ $relDb[$cfgRelation['savedsearches']] = [
+ 'search_data' => $json_highlighting_data,
+ ];
+ }
+ if (! empty($cfgRelation['designer_settings'])) {
+ $relDb[$cfgRelation['designer_settings']] = [
+ 'settings_data' => $json_highlighting_data,
+ ];
+ }
+ if (! empty($cfgRelation['table_uiprefs'])) {
+ $relDb[$cfgRelation['table_uiprefs']] = [
+ 'prefs' => $json_highlighting_data,
+ ];
+ }
+ if (! empty($cfgRelation['userconfig'])) {
+ $relDb[$cfgRelation['userconfig']] = [
+ 'config_data' => $json_highlighting_data,
+ ];
+ }
+ if (! empty($cfgRelation['export_templates'])) {
+ $relDb[$cfgRelation['export_templates']] = [
+ 'template_data' => $json_highlighting_data,
+ ];
+ }
+ }
+ }
+
+ /**
+ * Set properties which were not initialized at the constructor
+ *
+ * @param integer $unlim_num_rows the total number of rows returned by
+ * the SQL query without any appended
+ * "LIMIT" clause programmatically
+ * @param stdClass $fields_meta meta information about fields
+ * @param boolean $is_count statement is SELECT COUNT
+ * @param integer $is_export statement contains INTO OUTFILE
+ * @param boolean $is_func statement contains a function like SUM()
+ * @param integer $is_analyse statement contains PROCEDURE ANALYSE
+ * @param integer $num_rows total no. of rows returned by SQL query
+ * @param integer $fields_cnt total no.of fields returned by SQL query
+ * @param double $querytime time taken for execute the SQL query
+ * @param string $pmaThemeImage path for theme images directory
+ * @param string $text_dir text direction
+ * @param boolean $is_maint statement contains a maintenance command
+ * @param boolean $is_explain statement contains EXPLAIN
+ * @param boolean $is_show statement contains SHOW
+ * @param array $showtable table definitions
+ * @param string $printview print view was requested
+ * @param string $url_query URL query
+ * @param boolean $editable whether the results set is editable
+ * @param boolean $is_browse_dist whether browsing distinct values
+ *
+ * @return void
+ *
+ * @see sql.php
+ */
+ public function setProperties(
+ $unlim_num_rows,
+ $fields_meta,
+ $is_count,
+ $is_export,
+ $is_func,
+ $is_analyse,
+ $num_rows,
+ $fields_cnt,
+ $querytime,
+ $pmaThemeImage,
+ $text_dir,
+ $is_maint,
+ $is_explain,
+ $is_show,
+ $showtable,
+ $printview,
+ $url_query,
+ $editable,
+ $is_browse_dist
+ ) {
+
+ $this->__set('unlim_num_rows', $unlim_num_rows);
+ $this->__set('fields_meta', $fields_meta);
+ $this->__set('is_count', $is_count);
+ $this->__set('is_export', $is_export);
+ $this->__set('is_func', $is_func);
+ $this->__set('is_analyse', $is_analyse);
+ $this->__set('num_rows', $num_rows);
+ $this->__set('fields_cnt', $fields_cnt);
+ $this->__set('querytime', $querytime);
+ $this->__set('pma_theme_image', $pmaThemeImage);
+ $this->__set('text_dir', $text_dir);
+ $this->__set('is_maint', $is_maint);
+ $this->__set('is_explain', $is_explain);
+ $this->__set('is_show', $is_show);
+ $this->__set('showtable', $showtable);
+ $this->__set('printview', $printview);
+ $this->__set('url_query', $url_query);
+ $this->__set('editable', $editable);
+ $this->__set('is_browse_distinct', $is_browse_dist);
+ }
+
+ /**
+ * Defines the parts to display for a print view
+ *
+ * @param array $displayParts the parts to display
+ *
+ * @return array the modified display parts
+ *
+ * @access private
+ *
+ */
+ private function _setDisplayPartsForPrintView(array $displayParts)
+ {
+ // set all elements to false!
+ $displayParts['edit_lnk'] = self::NO_EDIT_OR_DELETE; // no edit link
+ $displayParts['del_lnk'] = self::NO_EDIT_OR_DELETE; // no delete link
+ $displayParts['sort_lnk'] = (string) '0';
+ $displayParts['nav_bar'] = (string) '0';
+ $displayParts['bkm_form'] = (string) '0';
+ $displayParts['text_btn'] = (string) '0';
+ $displayParts['pview_lnk'] = (string) '0';
+
+ return $displayParts;
+ }
+
+ /**
+ * Defines the parts to display for a SHOW statement
+ *
+ * @param array $displayParts the parts to display
+ *
+ * @return array the modified display parts
+ *
+ * @access private
+ *
+ */
+ private function _setDisplayPartsForShow(array $displayParts)
+ {
+ preg_match(
+ '@^SHOW[[:space:]]+(VARIABLES|(FULL[[:space:]]+)?'
+ . 'PROCESSLIST|STATUS|TABLE|GRANTS|CREATE|LOGS|DATABASES|FIELDS'
+ . ')@i',
+ $this->__get('sql_query'),
+ $which
+ );
+
+ $bIsProcessList = isset($which[1]);
+ if ($bIsProcessList) {
+ $str = ' ' . strtoupper($which[1]);
+ $bIsProcessList = $bIsProcessList
+ && strpos($str, 'PROCESSLIST') > 0;
+ }
+
+ if ($bIsProcessList) {
+ // no edit link
+ $displayParts['edit_lnk'] = self::NO_EDIT_OR_DELETE;
+ // "kill process" type edit link
+ $displayParts['del_lnk'] = self::KILL_PROCESS;
+ } else {
+ // Default case -> no links
+ // no edit link
+ $displayParts['edit_lnk'] = self::NO_EDIT_OR_DELETE;
+ // no delete link
+ $displayParts['del_lnk'] = self::NO_EDIT_OR_DELETE;
+ }
+ // Other settings
+ $displayParts['sort_lnk'] = (string) '0';
+ $displayParts['nav_bar'] = (string) '0';
+ $displayParts['bkm_form'] = (string) '1';
+ $displayParts['text_btn'] = (string) '1';
+ $displayParts['pview_lnk'] = (string) '1';
+
+ return $displayParts;
+ }
+
+ /**
+ * Defines the parts to display for statements not related to data
+ *
+ * @param array $displayParts the parts to display
+ *
+ * @return array the modified display parts
+ *
+ * @access private
+ *
+ */
+ private function _setDisplayPartsForNonData(array $displayParts)
+ {
+ // Statement is a "SELECT COUNT", a
+ // "CHECK/ANALYZE/REPAIR/OPTIMIZE/CHECKSUM", an "EXPLAIN" one or
+ // contains a "PROC ANALYSE" part
+ $displayParts['edit_lnk'] = self::NO_EDIT_OR_DELETE; // no edit link
+ $displayParts['del_lnk'] = self::NO_EDIT_OR_DELETE; // no delete link
+ $displayParts['sort_lnk'] = (string) '0';
+ $displayParts['nav_bar'] = (string) '0';
+ $displayParts['bkm_form'] = (string) '1';
+
+ if ($this->__get('is_maint')) {
+ $displayParts['text_btn'] = (string) '1';
+ } else {
+ $displayParts['text_btn'] = (string) '0';
+ }
+ $displayParts['pview_lnk'] = (string) '1';
+
+ return $displayParts;
+ }
+
+ /**
+ * Defines the parts to display for other statements (probably SELECT)
+ *
+ * @param array $displayParts the parts to display
+ *
+ * @return array the modified display parts
+ *
+ * @access private
+ *
+ */
+ private function _setDisplayPartsForSelect(array $displayParts)
+ {
+ // Other statements (ie "SELECT" ones) -> updates
+ // $displayParts['edit_lnk'], $displayParts['del_lnk'] and
+ // $displayParts['text_btn'] (keeps other default values)
+
+ $fields_meta = $this->__get('fields_meta');
+ $prev_table = '';
+ $displayParts['text_btn'] = (string) '1';
+ $number_of_columns = $this->__get('fields_cnt');
+
+ for ($i = 0; $i < $number_of_columns; $i++) {
+ $is_link = ($displayParts['edit_lnk'] != self::NO_EDIT_OR_DELETE)
+ || ($displayParts['del_lnk'] != self::NO_EDIT_OR_DELETE)
+ || ($displayParts['sort_lnk'] != '0');
+
+ // Displays edit/delete/sort/insert links?
+ if ($is_link
+ && $prev_table != ''
+ && $fields_meta[$i]->table != ''
+ && $fields_meta[$i]->table != $prev_table
+ ) {
+ // don't display links
+ $displayParts['edit_lnk'] = self::NO_EDIT_OR_DELETE;
+ $displayParts['del_lnk'] = self::NO_EDIT_OR_DELETE;
+ /**
+ * @todo May be problematic with same field names
+ * in two joined table.
+ */
+ // $displayParts['sort_lnk'] = (string) '0';
+ if ($displayParts['text_btn'] == '1') {
+ break;
+ }
+ } // end if
+
+ // Always display print view link
+ $displayParts['pview_lnk'] = (string) '1';
+ if ($fields_meta[$i]->table != '') {
+ $prev_table = $fields_meta[$i]->table;
+ }
+ } // end for
+
+ if ($prev_table == '') { // no table for any of the columns
+ // don't display links
+ $displayParts['edit_lnk'] = self::NO_EDIT_OR_DELETE;
+ $displayParts['del_lnk'] = self::NO_EDIT_OR_DELETE;
+ }
+
+ return $displayParts;
+ }
+
+ /**
+ * Defines the parts to display for the results of a SQL query
+ * and the total number of rows
+ *
+ * @param array $displayParts the parts to display (see a few
+ * lines above for explanations)
+ *
+ * @return array the first element is an array with explicit indexes
+ * for all the display elements
+ * the second element is the total number of rows returned
+ * by the SQL query without any programmatically appended
+ * LIMIT clause (just a copy of $unlim_num_rows if it exists,
+ * else computed inside this function)
+ *
+ *
+ * @access private
+ *
+ * @see getTable()
+ */
+ private function _setDisplayPartsAndTotal(array $displayParts)
+ {
+ $the_total = 0;
+
+ // 1. Following variables are needed for use in isset/empty or
+ // use with array indexes or safe use in foreach
+ $db = $this->__get('db');
+ $table = $this->__get('table');
+ $unlim_num_rows = $this->__get('unlim_num_rows');
+ $num_rows = $this->__get('num_rows');
+ $printview = $this->__get('printview');
+
+ // 2. Updates the display parts
+ if ($printview == '1') {
+ $displayParts = $this->_setDisplayPartsForPrintView($displayParts);
+ } elseif ($this->__get('is_count') || $this->__get('is_analyse')
+ || $this->__get('is_maint') || $this->__get('is_explain')
+ ) {
+ $displayParts = $this->_setDisplayPartsForNonData($displayParts);
+ } elseif ($this->__get('is_show')) {
+ $displayParts = $this->_setDisplayPartsForShow($displayParts);
+ } else {
+ $displayParts = $this->_setDisplayPartsForSelect($displayParts);
+ } // end if..elseif...else
+
+ // 3. Gets the total number of rows if it is unknown
+ if (isset($unlim_num_rows) && $unlim_num_rows != '') {
+ $the_total = $unlim_num_rows;
+ } elseif (($displayParts['nav_bar'] == '1')
+ || ($displayParts['sort_lnk'] == '1')
+ && (strlen($db) > 0 && strlen($table) > 0)
+ ) {
+ $the_total = $GLOBALS['dbi']->getTable($db, $table)->countRecords();
+ }
+
+ // if for COUNT query, number of rows returned more than 1
+ // (may be being used GROUP BY)
+ if ($this->__get('is_count') && isset($num_rows) && $num_rows > 1) {
+ $displayParts['nav_bar'] = (string) '1';
+ $displayParts['sort_lnk'] = (string) '1';
+ }
+ // 4. If navigation bar or sorting fields names URLs should be
+ // displayed but there is only one row, change these settings to
+ // false
+ if ($displayParts['nav_bar'] == '1' || $displayParts['sort_lnk'] == '1') {
+ // - Do not display sort links if less than 2 rows.
+ // - For a VIEW we (probably) did not count the number of rows
+ // so don't test this number here, it would remove the possibility
+ // of sorting VIEW results.
+ $_table = new Table($table, $db);
+ if (isset($unlim_num_rows)
+ && ($unlim_num_rows < 2)
+ && ! $_table->isView()
+ ) {
+ $displayParts['sort_lnk'] = (string) '0';
+ }
+ } // end if (3)
+
+ return [
+ $displayParts,
+ $the_total,
+ ];
+ }
+
+ /**
+ * Return true if we are executing a query in the form of
+ * "SELECT * FROM <a table> ..."
+ *
+ * @param array $analyzed_sql_results analyzed sql results
+ *
+ * @return boolean
+ *
+ * @access private
+ *
+ * @see _getTableHeaders(), _getColumnParams()
+ */
+ private function _isSelect(array $analyzed_sql_results)
+ {
+ return ! ($this->__get('is_count')
+ || $this->__get('is_export')
+ || $this->__get('is_func')
+ || $this->__get('is_analyse'))
+ && ! empty($analyzed_sql_results['select_from'])
+ && ! empty($analyzed_sql_results['statement']->from)
+ && (count($analyzed_sql_results['statement']->from) === 1)
+ && ! empty($analyzed_sql_results['statement']->from[0]->table);
+ }
+
+ /**
+ * Get a navigation button
+ *
+ * @param string $caption iconic caption for button
+ * @param string $title text for button
+ * @param integer $pos position for next query
+ * @param string $html_sql_query query ready for display
+ * @param boolean $back whether 'begin' or 'previous'
+ * @param string $onsubmit optional onsubmit clause
+ * @param string $input_for_real_end optional hidden field for special treatment
+ * @param string $onclick optional onclick clause
+ *
+ * @return string html content
+ *
+ * @access private
+ *
+ * @see _getMoveBackwardButtonsForTableNavigation(),
+ * _getMoveForwardButtonsForTableNavigation()
+ */
+ private function _getTableNavigationButton(
+ $caption,
+ $title,
+ $pos,
+ $html_sql_query,
+ $back,
+ $onsubmit = '',
+ $input_for_real_end = '',
+ $onclick = ''
+ ) {
+ $caption_output = '';
+ if ($back) {
+ if (Util::showIcons('TableNavigationLinksMode')) {
+ $caption_output .= $caption;
+ }
+ if (Util::showText('TableNavigationLinksMode')) {
+ $caption_output .= '&nbsp;' . $title;
+ }
+ } else {
+ if (Util::showText('TableNavigationLinksMode')) {
+ $caption_output .= $title;
+ }
+ if (Util::showIcons('TableNavigationLinksMode')) {
+ $caption_output .= '&nbsp;' . $caption;
+ }
+ }
+
+ return $this->template->render('display/results/table_navigation_button', [
+ 'db' => $this->__get('db'),
+ 'table' => $this->__get('table'),
+ 'sql_query' => $html_sql_query,
+ 'pos' => $pos,
+ 'is_browse_distinct' => $this->__get('is_browse_distinct'),
+ 'goto' => $this->__get('goto'),
+ 'input_for_real_end' => $input_for_real_end,
+ 'caption_output' => $caption_output,
+ 'title' => $title,
+ 'onsubmit' => $onsubmit,
+ 'onclick' => $onclick,
+ ]);
+ }
+
+ /**
+ * Possibly return a page selector for table navigation
+ *
+ * @return array ($output, $nbTotalPage)
+ *
+ * @access private
+ */
+ private function _getHtmlPageSelector(): array
+ {
+ $pageNow = @floor(
+ $_SESSION['tmpval']['pos']
+ / $_SESSION['tmpval']['max_rows']
+ ) + 1;
+
+ $nbTotalPage = @ceil(
+ $this->__get('unlim_num_rows')
+ / $_SESSION['tmpval']['max_rows']
+ );
+
+ $output = '';
+ if ($nbTotalPage > 1) {
+ $_url_params = [
+ 'db' => $this->__get('db'),
+ 'table' => $this->__get('table'),
+ 'sql_query' => $this->__get('sql_query'),
+ 'goto' => $this->__get('goto'),
+ 'is_browse_distinct' => $this->__get('is_browse_distinct'),
+ ];
+
+ $output = $this->template->render('display/results/page_selector', [
+ 'url_params' => $_url_params,
+ 'page_selector' => Util::pageselector(
+ 'pos',
+ $_SESSION['tmpval']['max_rows'],
+ $pageNow,
+ $nbTotalPage,
+ 200,
+ 5,
+ 5,
+ 20,
+ 10
+ ),
+ ]);
+ }
+ return [
+ $output,
+ $nbTotalPage,
+ ];
+ }
+
+ /**
+ * Get a navigation bar to browse among the results of a SQL query
+ *
+ * @param integer $posNext the offset for the "next" page
+ * @param integer $posPrevious the offset for the "previous" page
+ * @param boolean $isInnodb whether its InnoDB or not
+ * @param string $sortByKeyHtml the sort by key dialog
+ *
+ * @return string html content
+ *
+ * @access private
+ *
+ * @see getTable()
+ */
+ private function _getTableNavigation(
+ $posNext,
+ $posPrevious,
+ $isInnodb,
+ $sortByKeyHtml
+ ): string {
+ $isShowingAll = $_SESSION['tmpval']['max_rows'] === self::ALL_ROWS;
+
+ // Move to the beginning or to the previous page
+ $moveBackwardButtons = '';
+ if ($_SESSION['tmpval']['pos'] && ! $isShowingAll) {
+ $moveBackwardButtons = $this->_getMoveBackwardButtonsForTableNavigation(
+ htmlspecialchars($this->__get('sql_query')),
+ $posPrevious
+ );
+ }
+
+ $pageSelector = '';
+ $numberTotalPage = 1;
+ if (! $isShowingAll) {
+ list(
+ $pageSelector,
+ $numberTotalPage
+ ) = $this->_getHtmlPageSelector();
+ }
+
+ // Move to the next page or to the last one
+ $moveForwardButtons = '';
+ if ($this->__get('unlim_num_rows') === false // view with unknown number of rows
+ || (! $isShowingAll
+ && $_SESSION['tmpval']['pos'] + $_SESSION['tmpval']['max_rows'] < $this->__get('unlim_num_rows')
+ && $this->__get('num_rows') >= $_SESSION['tmpval']['max_rows'])
+ ) {
+ $moveForwardButtons = $this->_getMoveForwardButtonsForTableNavigation(
+ htmlspecialchars($this->__get('sql_query')),
+ $posNext,
+ $isInnodb
+ );
+ }
+
+ $hiddenFields = [
+ 'db' => $this->__get('db'),
+ 'table' => $this->__get('table'),
+ 'server' => $this->__get('server'),
+ 'sql_query' => $this->__get('sql_query'),
+ 'is_browse_distinct' => $this->__get('is_browse_distinct'),
+ 'goto' => $this->__get('goto'),
+ ];
+
+ return $this->template->render('display/results/table_navigation', [
+ 'move_backward_buttons' => $moveBackwardButtons,
+ 'page_selector' => $pageSelector,
+ 'move_forward_buttons' => $moveForwardButtons,
+ 'number_total_page' => $numberTotalPage,
+ 'has_show_all' => $GLOBALS['cfg']['ShowAll'] || ($this->__get('unlim_num_rows') <= 500),
+ 'hidden_fields' => $hiddenFields,
+ 'session_max_rows' => $isShowingAll ? $GLOBALS['cfg']['MaxRows'] : 'all',
+ 'unique_id' => $this->__get('unique_id'),
+ 'is_showing_all' => $isShowingAll,
+ 'unlim_num_rows' => $this->__get('unlim_num_rows'),
+ 'max_rows' => $_SESSION['tmpval']['max_rows'],
+ 'pos' => $_SESSION['tmpval']['pos'],
+ 'sort_by_key' => $sortByKeyHtml,
+ ]);
+ }
+
+ /**
+ * Prepare move backward buttons - previous and first
+ *
+ * @param string $html_sql_query the sql encoded by html special characters
+ * @param integer $pos_prev the offset for the "previous" page
+ *
+ * @return string html content
+ *
+ * @access private
+ *
+ * @see _getTableNavigation()
+ */
+ private function _getMoveBackwardButtonsForTableNavigation(
+ $html_sql_query,
+ $pos_prev
+ ) {
+ return $this->_getTableNavigationButton(
+ '&lt;&lt;',
+ _pgettext('First page', 'Begin'),
+ 0,
+ $html_sql_query,
+ true
+ )
+ . $this->_getTableNavigationButton(
+ '&lt;',
+ _pgettext('Previous page', 'Previous'),
+ $pos_prev,
+ $html_sql_query,
+ true
+ );
+ }
+
+ /**
+ * Prepare move forward buttons - next and last
+ *
+ * @param string $html_sql_query the sql encoded by htmlspecialchars()
+ * @param integer $pos_next the offset for the "next" page
+ * @param boolean $is_innodb whether it's InnoDB or not
+ *
+ * @return string html content
+ *
+ * @access private
+ *
+ * @see _getTableNavigation()
+ */
+ private function _getMoveForwardButtonsForTableNavigation(
+ $html_sql_query,
+ $pos_next,
+ $is_innodb
+ ) {
+ // display the Next button
+ $buttons_html = $this->_getTableNavigationButton(
+ '&gt;',
+ _pgettext('Next page', 'Next'),
+ $pos_next,
+ $html_sql_query,
+ false
+ );
+
+ // prepare some options for the End button
+ if ($is_innodb
+ && $this->__get('unlim_num_rows') > $GLOBALS['cfg']['MaxExactCount']
+ ) {
+ $input_for_real_end = '<input id="real_end_input" type="hidden" '
+ . 'name="find_real_end" value="1">';
+ // no backquote around this message
+ $onclick = '';
+ } else {
+ $input_for_real_end = $onclick = '';
+ }
+
+ $maxRows = $_SESSION['tmpval']['max_rows'];
+ $onsubmit = 'onsubmit="return '
+ . ($_SESSION['tmpval']['pos']
+ + $maxRows
+ < $this->__get('unlim_num_rows')
+ && $this->__get('num_rows') >= $maxRows
+ ? 'true'
+ : 'false') . '"';
+
+ // display the End button
+ $buttons_html .= $this->_getTableNavigationButton(
+ '&gt;&gt;',
+ _pgettext('Last page', 'End'),
+ @((ceil(
+ $this->__get('unlim_num_rows')
+ / $_SESSION['tmpval']['max_rows']
+ ) - 1) * $maxRows),
+ $html_sql_query,
+ false,
+ $onsubmit,
+ $input_for_real_end,
+ $onclick
+ );
+
+ return $buttons_html;
+ }
+
+ /**
+ * Get the headers of the results table, for all of the columns
+ *
+ * @param array $displayParts which elements to display
+ * @param array $analyzed_sql_results analyzed sql results
+ * @param array $sort_expression sort expression
+ * @param array $sort_expression_nodirection sort expression
+ * without direction
+ * @param array $sort_direction sort direction
+ * @param boolean $is_limited_display with limited operations
+ * or not
+ * @param string $unsorted_sql_query query without the sort part
+ *
+ * @return string html content
+ *
+ * @access private
+ *
+ * @see getTableHeaders()
+ */
+ private function _getTableHeadersForColumns(
+ array $displayParts,
+ array $analyzed_sql_results,
+ array $sort_expression,
+ array $sort_expression_nodirection,
+ array $sort_direction,
+ $is_limited_display,
+ $unsorted_sql_query
+ ) {
+ $html = '';
+
+ // required to generate sort links that will remember whether the
+ // "Show all" button has been clicked
+ $sql_md5 = md5($this->__get('sql_query'));
+ $session_max_rows = $is_limited_display
+ ? 0
+ : $_SESSION['tmpval']['query'][$sql_md5]['max_rows'];
+
+ // Following variable are needed for use in isset/empty or
+ // use with array indexes/safe use in the for loop
+ $highlight_columns = $this->__get('highlight_columns');
+ $fields_meta = $this->__get('fields_meta');
+
+ // Prepare Display column comments if enabled
+ // ($GLOBALS['cfg']['ShowBrowseComments']).
+ $comments_map = $this->_getTableCommentsArray($analyzed_sql_results);
+
+ list($col_order, $col_visib) = $this->_getColumnParams(
+ $analyzed_sql_results
+ );
+
+ // optimize: avoid calling a method on each iteration
+ $number_of_columns = $this->__get('fields_cnt');
+
+ for ($j = 0; $j < $number_of_columns; $j++) {
+ // PHP 7.4 fix for accessing array offset on bool
+ $col_visib_current = is_array($col_visib) && isset($col_visib[$j]) ? $col_visib[$j] : null;
+
+ // assign $i with the appropriate column order
+ $i = $col_order ? $col_order[$j] : $j;
+
+ // See if this column should get highlight because it's used in the
+ // where-query.
+ $name = $fields_meta[$i]->name;
+ $condition_field = isset($highlight_columns[$name])
+ || isset($highlight_columns[Util::backquote($name)])
+ ? true
+ : false;
+
+ // Prepare comment-HTML-wrappers for each row, if defined/enabled.
+ $comments = $this->_getCommentForRow($comments_map, $fields_meta[$i]);
+ $display_params = $this->__get('display_params');
+
+ if (($displayParts['sort_lnk'] == '1') && ! $is_limited_display) {
+ list($order_link, $sorted_header_html)
+ = $this->_getOrderLinkAndSortedHeaderHtml(
+ $fields_meta[$i],
+ $sort_expression,
+ $sort_expression_nodirection,
+ $i,
+ $unsorted_sql_query,
+ $session_max_rows,
+ $comments,
+ $sort_direction,
+ $col_visib,
+ $col_visib_current
+ );
+
+ $html .= $sorted_header_html;
+
+ $display_params['desc'][] = ' <th '
+ . 'class="draggable'
+ . ($condition_field ? ' condition' : '')
+ . '" data-column="' . htmlspecialchars($fields_meta[$i]->name)
+ . '">' . "\n" . $order_link . $comments . ' </th>' . "\n";
+ } else {
+ // Results can't be sorted
+ $html
+ .= $this->_getDraggableClassForNonSortableColumns(
+ $col_visib,
+ $col_visib_current,
+ $condition_field,
+ $fields_meta[$i],
+ $comments
+ );
+
+ $display_params['desc'][] = ' <th '
+ . 'class="draggable'
+ . ($condition_field ? ' condition"' : '')
+ . '" data-column="' . htmlspecialchars((string) $fields_meta[$i]->name)
+ . '"> '
+ . htmlspecialchars((string) $fields_meta[$i]->name)
+ . $comments . ' </th>';
+ } // end else
+
+ $this->__set('display_params', $display_params);
+ } // end for
+ return $html;
+ }
+
+ /**
+ * Get the headers of the results table
+ *
+ * @param array $displayParts which elements to display
+ * @param array $analyzedSqlResults analyzed sql results
+ * @param string $unsortedSqlQuery the unsorted sql query
+ * @param array $sortExpression sort expression
+ * @param array|string $sortExpressionNoDirection sort expression without direction
+ * @param array $sortDirection sort direction
+ * @param boolean $isLimitedDisplay with limited operations or not
+ *
+ * @return string html content
+ *
+ * @access private
+ *
+ * @see getTable()
+ */
+ private function _getTableHeaders(
+ array &$displayParts,
+ array $analyzedSqlResults,
+ $unsortedSqlQuery,
+ array $sortExpression = [],
+ $sortExpressionNoDirection = '',
+ array $sortDirection = [],
+ $isLimitedDisplay = false
+ ): string {
+ // Needed for use in isset/empty or
+ // use with array indexes/safe use in foreach
+ $printView = $this->__get('printview');
+ $displayParams = $this->__get('display_params');
+
+ // Output data needed for column reordering and show/hide column
+ $dataForResettingColumnOrder = $this->_getDataForResettingColumnOrder($analyzedSqlResults);
+
+ $displayParams['emptypre'] = 0;
+ $displayParams['emptyafter'] = 0;
+ $displayParams['textbtn'] = '';
+ $fullOrPartialTextLink = '';
+
+ $this->__set('display_params', $displayParams);
+
+ // Display options (if we are not in print view)
+ $optionsBlock = '';
+ if (! (isset($printView) && ($printView == '1')) && ! $isLimitedDisplay) {
+ $optionsBlock = $this->_getOptionsBlock();
+
+ // prepare full/partial text button or link
+ $fullOrPartialTextLink = $this->_getFullOrPartialTextButtonOrLink();
+ }
+
+ // 1. Set $colspan and generate html with full/partial
+ // text button or link
+ list($colspan, $buttonHtml) = $this->_getFieldVisibilityParams(
+ $displayParts,
+ $fullOrPartialTextLink
+ );
+
+ // 2. Displays the fields' name
+ // 2.0 If sorting links should be used, checks if the query is a "JOIN"
+ // statement (see 2.1.3)
+
+ // See if we have to highlight any header fields of a WHERE query.
+ // Uses SQL-Parser results.
+ $this->_setHighlightedColumnGlobalField($analyzedSqlResults);
+
+ // Get the headers for all of the columns
+ $tableHeadersForColumns = $this->_getTableHeadersForColumns(
+ $displayParts,
+ $analyzedSqlResults,
+ $sortExpression,
+ $sortExpressionNoDirection,
+ $sortDirection,
+ $isLimitedDisplay,
+ $unsortedSqlQuery
+ );
+
+ // Display column at rightside - checkboxes or empty column
+ $columnAtRightSide = '';
+ if (! $printView) {
+ $columnAtRightSide = $this->_getColumnAtRightSide(
+ $displayParts,
+ $fullOrPartialTextLink,
+ $colspan
+ );
+ }
+
+ return $this->template->render('display/results/table_headers', [
+ 'db' => $this->__get('db'),
+ 'table' => $this->__get('table'),
+ 'unique_id' => $this->__get('unique_id'),
+ 'save_cells_at_once' => $GLOBALS['cfg']['SaveCellsAtOnce'],
+ 'data_for_resetting_column_order' => $dataForResettingColumnOrder,
+ 'options_block' => $optionsBlock,
+ 'delete_link' => $displayParts['del_lnk'],
+ 'delete_row' => self::DELETE_ROW,
+ 'kill_process' => self::KILL_PROCESS,
+ 'button' => $buttonHtml,
+ 'table_headers_for_columns' => $tableHeadersForColumns,
+ 'column_at_right_side' => $columnAtRightSide,
+ ]);
+ }
+
+ /**
+ * Prepare unsorted sql query and sort by key drop down
+ *
+ * @param array $analyzed_sql_results analyzed sql results
+ * @param array|null $sort_expression sort expression
+ *
+ * @return array two element array - $unsorted_sql_query, $drop_down_html
+ *
+ * @access private
+ *
+ * @see _getTableHeaders()
+ */
+ private function _getUnsortedSqlAndSortByKeyDropDown(
+ array $analyzed_sql_results,
+ ?array $sort_expression
+ ) {
+ $drop_down_html = '';
+
+ $unsorted_sql_query = Query::replaceClause(
+ $analyzed_sql_results['statement'],
+ $analyzed_sql_results['parser']->list,
+ 'ORDER BY',
+ ''
+ );
+
+ // Data is sorted by indexes only if it there is only one table.
+ if ($this->_isSelect($analyzed_sql_results)) {
+ // grab indexes data:
+ $indexes = Index::getFromTable(
+ $this->__get('table'),
+ $this->__get('db')
+ );
+
+ // do we have any index?
+ if (! empty($indexes)) {
+ $drop_down_html = $this->_getSortByKeyDropDown(
+ $indexes,
+ $sort_expression,
+ $unsorted_sql_query
+ );
+ }
+ }
+
+ return [
+ $unsorted_sql_query,
+ $drop_down_html,
+ ];
+ }
+
+ /**
+ * Prepare sort by key dropdown - html code segment
+ *
+ * @param Index[] $indexes the indexes of the table for sort criteria
+ * @param array|null $sortExpression the sort expression
+ * @param string $unsortedSqlQuery the unsorted sql query
+ *
+ * @return string html content
+ *
+ * @access private
+ *
+ * @see _getTableHeaders()
+ */
+ private function _getSortByKeyDropDown(
+ $indexes,
+ ?array $sortExpression,
+ $unsortedSqlQuery
+ ): string {
+ $hiddenFields = [
+ 'db' => $this->__get('db'),
+ 'table' => $this->__get('table'),
+ 'server' => $this->__get('server'),
+ 'sort_by_key' => '1',
+ ];
+
+ $isIndexUsed = false;
+ $localOrder = is_array($sortExpression) ? implode(', ', $sortExpression) : '';
+
+ $options = [];
+ foreach ($indexes as $index) {
+ $ascSort = '`'
+ . implode('` ASC, `', array_keys($index->getColumns()))
+ . '` ASC';
+
+ $descSort = '`'
+ . implode('` DESC, `', array_keys($index->getColumns()))
+ . '` DESC';
+
+ $isIndexUsed = $isIndexUsed
+ || $localOrder === $ascSort
+ || $localOrder === $descSort;
+
+ $unsortedSqlQueryFirstPart = $unsortedSqlQuery;
+ $unsortedSqlQuerySecondPart = '';
+ if (preg_match(
+ '@(.*)([[:space:]](LIMIT (.*)|PROCEDURE (.*)|'
+ . 'FOR UPDATE|LOCK IN SHARE MODE))@is',
+ $unsortedSqlQuery,
+ $myReg
+ )) {
+ $unsortedSqlQueryFirstPart = $myReg[1];
+ $unsortedSqlQuerySecondPart = $myReg[2];
+ }
+
+ $options[] = [
+ 'value' => $unsortedSqlQueryFirstPart . ' ORDER BY '
+ . $ascSort . $unsortedSqlQuerySecondPart,
+ 'content' => $index->getName() . ' (ASC)',
+ 'is_selected' => $localOrder === $ascSort,
+ ];
+ $options[] = [
+ 'value' => $unsortedSqlQueryFirstPart . ' ORDER BY '
+ . $descSort . $unsortedSqlQuerySecondPart,
+ 'content' => $index->getName() . ' (DESC)',
+ 'is_selected' => $localOrder === $descSort,
+ ];
+ }
+ $options[] = [
+ 'value' => $unsortedSqlQuery,
+ 'content' => __('None'),
+ 'is_selected' => ! $isIndexUsed,
+ ];
+
+ return $this->template->render('display/results/sort_by_key', [
+ 'hidden_fields' => $hiddenFields,
+ 'options' => $options,
+ ]);
+ }
+
+ /**
+ * Set column span, row span and prepare html with full/partial
+ * text button or link
+ *
+ * @param array $displayParts which elements to display
+ * @param string $full_or_partial_text_link full/partial link or text button
+ *
+ * @return array 2 element array - $colspan, $button_html
+ *
+ * @access private
+ *
+ * @see _getTableHeaders()
+ */
+ private function _getFieldVisibilityParams(
+ array &$displayParts,
+ $full_or_partial_text_link
+ ) {
+
+ $button_html = '';
+ $display_params = $this->__get('display_params');
+
+ // 1. Displays the full/partial text button (part 1)...
+ $button_html .= '<thead><tr>' . "\n";
+
+ $emptyPreCondition = $displayParts['edit_lnk'] != self::NO_EDIT_OR_DELETE
+ && $displayParts['del_lnk'] != self::NO_EDIT_OR_DELETE;
+
+ $colspan = $emptyPreCondition ? ' colspan="4"'
+ : '';
+
+ $leftOrBoth = $GLOBALS['cfg']['RowActionLinks'] === self::POSITION_LEFT
+ || $GLOBALS['cfg']['RowActionLinks'] === self::POSITION_BOTH;
+
+ // ... before the result table
+ if (($displayParts['edit_lnk'] == self::NO_EDIT_OR_DELETE)
+ && ($displayParts['del_lnk'] == self::NO_EDIT_OR_DELETE)
+ && ($displayParts['text_btn'] == '1')
+ ) {
+ $display_params['emptypre'] = $emptyPreCondition ? 4 : 0;
+ } elseif ($leftOrBoth && ($displayParts['text_btn'] == '1')
+ ) {
+ // ... at the left column of the result table header if possible
+ // and required
+
+ $display_params['emptypre'] = $emptyPreCondition ? 4 : 0;
+
+ $button_html .= '<th class="column_action print_ignore" ' . $colspan
+ . '>' . $full_or_partial_text_link . '</th>';
+ } elseif ($leftOrBoth
+ && (($displayParts['edit_lnk'] != self::NO_EDIT_OR_DELETE)
+ || ($displayParts['del_lnk'] != self::NO_EDIT_OR_DELETE))
+ ) {
+ // ... elseif no button, displays empty(ies) col(s) if required
+
+ $display_params['emptypre'] = $emptyPreCondition ? 4 : 0;
+
+ $button_html .= '<td ' . $colspan . '></td>';
+ } elseif ($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_NONE) {
+ // ... elseif display an empty column if the actions links are
+ // disabled to match the rest of the table
+ $button_html .= '<th class="column_action"></th>';
+ }
+
+ $this->__set('display_params', $display_params);
+
+ return [
+ $colspan,
+ $button_html,
+ ];
+ }
+
+ /**
+ * Get table comments as array
+ *
+ * @param array $analyzed_sql_results analyzed sql results
+ *
+ * @return array table comments
+ *
+ * @access private
+ *
+ * @see _getTableHeaders()
+ */
+ private function _getTableCommentsArray(array $analyzed_sql_results)
+ {
+ if (! $GLOBALS['cfg']['ShowBrowseComments']
+ || empty($analyzed_sql_results['statement']->from)
+ ) {
+ return [];
+ }
+
+ $ret = [];
+ foreach ($analyzed_sql_results['statement']->from as $field) {
+ if (empty($field->table)) {
+ continue;
+ }
+ $ret[$field->table] = $this->relation->getComments(
+ empty($field->database) ? $this->__get('db') : $field->database,
+ $field->table
+ );
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Set global array for store highlighted header fields
+ *
+ * @param array $analyzed_sql_results analyzed sql results
+ *
+ * @return void
+ *
+ * @access private
+ *
+ * @see _getTableHeaders()
+ */
+ private function _setHighlightedColumnGlobalField(array $analyzed_sql_results)
+ {
+ $highlight_columns = [];
+
+ if (! empty($analyzed_sql_results['statement']->where)) {
+ foreach ($analyzed_sql_results['statement']->where as $expr) {
+ foreach ($expr->identifiers as $identifier) {
+ $highlight_columns[$identifier] = 'true';
+ }
+ }
+ }
+
+ $this->__set('highlight_columns', $highlight_columns);
+ }
+
+ /**
+ * Prepare data for column restoring and show/hide
+ *
+ * @param array $analyzedSqlResults analyzed sql results
+ *
+ * @return string html content
+ *
+ * @access private
+ *
+ * @see _getTableHeaders()
+ */
+ private function _getDataForResettingColumnOrder(array $analyzedSqlResults): string
+ {
+ if (! $this->_isSelect($analyzedSqlResults)) {
+ return '';
+ }
+
+ list($columnOrder, $columnVisibility) = $this->_getColumnParams(
+ $analyzedSqlResults
+ );
+
+ $tableCreateTime = '';
+ $table = new Table($this->__get('table'), $this->__get('db'));
+ if (! $table->isView()) {
+ $tableCreateTime = $GLOBALS['dbi']->getTable(
+ $this->__get('db'),
+ $this->__get('table')
+ )->getStatusInfo('Create_time');
+ }
+
+ return $this->template->render('display/results/data_for_resetting_column_order', [
+ 'column_order' => $columnOrder,
+ 'column_visibility' => $columnVisibility,
+ 'is_view' => $table->isView(),
+ 'table_create_time' => $tableCreateTime,
+ ]);
+ }
+
+ /**
+ * Prepare option fields block
+ *
+ * @return string html content
+ *
+ * @access private
+ *
+ * @see _getTableHeaders()
+ */
+ private function _getOptionsBlock()
+ {
+ if (isset($_SESSION['tmpval']['possible_as_geometry']) && $_SESSION['tmpval']['possible_as_geometry'] == false) {
+ if ($_SESSION['tmpval']['geoOption'] == self::GEOMETRY_DISP_GEOM) {
+ $_SESSION['tmpval']['geoOption'] = self::GEOMETRY_DISP_WKT;
+ }
+ }
+ return $this->template->render('display/results/options_block', [
+ 'unique_id' => $this->__get('unique_id'),
+ 'geo_option' => $_SESSION['tmpval']['geoOption'],
+ 'hide_transformation' => $_SESSION['tmpval']['hide_transformation'],
+ 'display_blob' => $_SESSION['tmpval']['display_blob'],
+ 'display_binary' => $_SESSION['tmpval']['display_binary'],
+ 'relational_display' => $_SESSION['tmpval']['relational_display'],
+ 'displaywork' => $GLOBALS['cfgRelation']['displaywork'],
+ 'relwork' => $GLOBALS['cfgRelation']['relwork'],
+ 'possible_as_geometry' => $_SESSION['tmpval']['possible_as_geometry'],
+ 'pftext' => $_SESSION['tmpval']['pftext'],
+ 'db' => $this->__get('db'),
+ 'table' => $this->__get('table'),
+ 'sql_query' => $this->__get('sql_query'),
+ 'goto' => $this->__get('goto'),
+ 'default_sliders_state' => $GLOBALS['cfg']['InitialSlidersState'],
+ ]);
+ }
+
+ /**
+ * Get full/partial text button or link
+ *
+ * @return string html content
+ *
+ * @access private
+ *
+ * @see _getTableHeaders()
+ */
+ private function _getFullOrPartialTextButtonOrLink()
+ {
+
+ $url_params_full_text = [
+ 'db' => $this->__get('db'),
+ 'table' => $this->__get('table'),
+ 'sql_query' => $this->__get('sql_query'),
+ 'goto' => $this->__get('goto'),
+ 'full_text_button' => 1,
+ ];
+
+ if ($_SESSION['tmpval']['pftext'] == self::DISPLAY_FULL_TEXT) {
+ // currently in fulltext mode so show the opposite link
+ $tmp_image_file = $this->__get('pma_theme_image') . 's_partialtext.png';
+ $tmp_txt = __('Partial texts');
+ $url_params_full_text['pftext'] = self::DISPLAY_PARTIAL_TEXT;
+ } else {
+ $tmp_image_file = $this->__get('pma_theme_image') . 's_fulltext.png';
+ $tmp_txt = __('Full texts');
+ $url_params_full_text['pftext'] = self::DISPLAY_FULL_TEXT;
+ }
+
+ $tmp_image = '<img class="fulltext" src="' . $tmp_image_file . '" alt="'
+ . $tmp_txt . '" title="' . $tmp_txt . '">';
+ $tmp_url = 'sql.php' . Url::getCommon($url_params_full_text);
+
+ return Util::linkOrButton($tmp_url, $tmp_image);
+ }
+
+ /**
+ * Get comment for row
+ *
+ * @param array $commentsMap comments array
+ * @param array $fieldsMeta set of field properties
+ *
+ * @return string html content
+ *
+ * @access private
+ *
+ * @see _getTableHeaders()
+ */
+ private function _getCommentForRow(array $commentsMap, $fieldsMeta)
+ {
+ return $this->template->render('display/results/comment_for_row', [
+ 'comments_map' => $commentsMap,
+ 'fields_meta' => $fieldsMeta,
+ 'limit_chars' => $GLOBALS['cfg']['LimitChars'],
+ ]);
+ }
+
+ /**
+ * Prepare parameters and html for sorted table header fields
+ *
+ * @param stdClass $fields_meta set of field properties
+ * @param array $sort_expression sort expression
+ * @param array $sort_expression_nodirection sort expression without direction
+ * @param integer $column_index the index of the column
+ * @param string $unsorted_sql_query the unsorted sql query
+ * @param integer $session_max_rows maximum rows resulted by sql
+ * @param string $comments comment for row
+ * @param array $sort_direction sort direction
+ * @param boolean $col_visib column is visible(false)
+ * array column isn't visible(string array)
+ * @param string $col_visib_j element of $col_visib array
+ *
+ * @return array 2 element array - $order_link, $sorted_header_html
+ *
+ * @access private
+ *
+ * @see _getTableHeaders()
+ */
+ private function _getOrderLinkAndSortedHeaderHtml(
+ $fields_meta,
+ array $sort_expression,
+ array $sort_expression_nodirection,
+ $column_index,
+ $unsorted_sql_query,
+ $session_max_rows,
+ $comments,
+ array $sort_direction,
+ $col_visib,
+ $col_visib_j
+ ) {
+
+ $sorted_header_html = '';
+
+ // Checks if the table name is required; it's the case
+ // for a query with a "JOIN" statement and if the column
+ // isn't aliased, or in queries like
+ // SELECT `1`.`master_field` , `2`.`master_field`
+ // FROM `PMA_relation` AS `1` , `PMA_relation` AS `2`
+
+ $sort_tbl = isset($fields_meta->table)
+ && strlen($fields_meta->table) > 0
+ && $fields_meta->orgname == $fields_meta->name
+ ? Util::backquote(
+ $fields_meta->table
+ ) . '.'
+ : '';
+
+ $name_to_use_in_sort = $fields_meta->name;
+
+ // Generates the orderby clause part of the query which is part
+ // of URL
+ list($single_sort_order, $multi_sort_order, $order_img)
+ = $this->_getSingleAndMultiSortUrls(
+ $sort_expression,
+ $sort_expression_nodirection,
+ $sort_tbl,
+ $name_to_use_in_sort,
+ $sort_direction,
+ $fields_meta
+ );
+
+ if (preg_match(
+ '@(.*)([[:space:]](LIMIT (.*)|PROCEDURE (.*)|FOR UPDATE|'
+ . 'LOCK IN SHARE MODE))@is',
+ $unsorted_sql_query,
+ $regs3
+ )) {
+ $single_sorted_sql_query = $regs3[1] . $single_sort_order . $regs3[2];
+ $multi_sorted_sql_query = $regs3[1] . $multi_sort_order . $regs3[2];
+ } else {
+ $single_sorted_sql_query = $unsorted_sql_query . $single_sort_order;
+ $multi_sorted_sql_query = $unsorted_sql_query . $multi_sort_order;
+ }
+
+ $_single_url_params = [
+ 'db' => $this->__get('db'),
+ 'table' => $this->__get('table'),
+ 'sql_query' => $single_sorted_sql_query,
+ 'sql_signature' => Core::signSqlQuery($single_sorted_sql_query),
+ 'session_max_rows' => $session_max_rows,
+ 'is_browse_distinct' => $this->__get('is_browse_distinct'),
+ ];
+
+ $_multi_url_params = [
+ 'db' => $this->__get('db'),
+ 'table' => $this->__get('table'),
+ 'sql_query' => $multi_sorted_sql_query,
+ 'sql_signature' => Core::signSqlQuery($multi_sorted_sql_query),
+ 'session_max_rows' => $session_max_rows,
+ 'is_browse_distinct' => $this->__get('is_browse_distinct'),
+ ];
+ $single_order_url = 'sql.php' . Url::getCommon($_single_url_params);
+ $multi_order_url = 'sql.php' . Url::getCommon($_multi_url_params);
+
+ // Displays the sorting URL
+ // enable sort order swapping for image
+ $order_link = $this->_getSortOrderLink(
+ $order_img,
+ $fields_meta,
+ $single_order_url,
+ $multi_order_url
+ );
+
+ $sorted_header_html .= $this->_getDraggableClassForSortableColumns(
+ $col_visib,
+ $col_visib_j,
+ $fields_meta,
+ $order_link,
+ $comments
+ );
+
+ return [
+ $order_link,
+ $sorted_header_html,
+ ];
+ }
+
+ /**
+ * Prepare parameters and html for sorted table header fields
+ *
+ * @param array $sort_expression sort expression
+ * @param array $sort_expression_nodirection sort expression without direction
+ * @param string $sort_tbl The name of the table to which
+ * the current column belongs to
+ * @param string $name_to_use_in_sort The current column under
+ * consideration
+ * @param array $sort_direction sort direction
+ * @param stdClass $fields_meta set of field properties
+ *
+ * @return array 3 element array - $single_sort_order, $sort_order, $order_img
+ *
+ * @access private
+ *
+ * @see _getOrderLinkAndSortedHeaderHtml()
+ */
+ private function _getSingleAndMultiSortUrls(
+ array $sort_expression,
+ array $sort_expression_nodirection,
+ $sort_tbl,
+ $name_to_use_in_sort,
+ array $sort_direction,
+ $fields_meta
+ ) {
+ $sort_order = "";
+ // Check if the current column is in the order by clause
+ $is_in_sort = $this->_isInSorted(
+ $sort_expression,
+ $sort_expression_nodirection,
+ $sort_tbl,
+ $name_to_use_in_sort
+ );
+ $current_name = $name_to_use_in_sort;
+ if ($sort_expression_nodirection[0] == '' || ! $is_in_sort) {
+ $special_index = $sort_expression_nodirection[0] == ''
+ ? 0
+ : count($sort_expression_nodirection);
+ $sort_expression_nodirection[$special_index]
+ = Util::backquote(
+ $current_name
+ );
+ $sort_direction[$special_index] = preg_match(
+ '@time|date@i',
+ $fields_meta->type
+ ) ? self::DESCENDING_SORT_DIR : self::ASCENDING_SORT_DIR;
+ }
+
+ $sort_expression_nodirection = array_filter($sort_expression_nodirection);
+ $single_sort_order = null;
+ foreach ($sort_expression_nodirection as $index => $expression) {
+ // check if this is the first clause,
+ // if it is then we have to add "order by"
+ $is_first_clause = ($index == 0);
+ $name_to_use_in_sort = $expression;
+ $sort_tbl_new = $sort_tbl;
+ // Test to detect if the column name is a standard name
+ // Standard name has the table name prefixed to the column name
+ if (mb_strpos($name_to_use_in_sort, '.') !== false) {
+ $matches = explode('.', $name_to_use_in_sort);
+ // Matches[0] has the table name
+ // Matches[1] has the column name
+ $name_to_use_in_sort = $matches[1];
+ $sort_tbl_new = $matches[0];
+ }
+
+ // $name_to_use_in_sort might contain a space due to
+ // formatting of function expressions like "COUNT(name )"
+ // so we remove the space in this situation
+ $name_to_use_in_sort = str_replace([' )', '``'], [')', '`'], $name_to_use_in_sort);
+ $name_to_use_in_sort = trim($name_to_use_in_sort, '`');
+
+ // If this the first column name in the order by clause add
+ // order by clause to the column name
+ $query_head = $is_first_clause ? "\nORDER BY " : "";
+ // Again a check to see if the given column is a aggregate column
+ if (mb_strpos($name_to_use_in_sort, '(') !== false) {
+ $sort_order .= $query_head . $name_to_use_in_sort . ' ' ;
+ } else {
+ if (strlen($sort_tbl_new) > 0) {
+ $sort_tbl_new .= ".";
+ }
+ $sort_order .= $query_head . $sort_tbl_new
+ . Util::backquote(
+ $name_to_use_in_sort
+ ) . ' ' ;
+ }
+
+ // For a special case where the code generates two dots between
+ // column name and table name.
+ $sort_order = preg_replace("/\.\./", ".", $sort_order);
+ // Incase this is the current column save $single_sort_order
+ if ($current_name == $name_to_use_in_sort) {
+ if (mb_strpos($current_name, '(') !== false) {
+ $single_sort_order = "\n" . 'ORDER BY ' . Util::backquote($current_name) . ' ';
+ } else {
+ $single_sort_order = "\n" . 'ORDER BY ' . $sort_tbl
+ . Util::backquote(
+ $current_name
+ ) . ' ';
+ }
+ if ($is_in_sort) {
+ list($single_sort_order, $order_img)
+ = $this->_getSortingUrlParams(
+ $sort_direction,
+ $single_sort_order,
+ $index
+ );
+ } else {
+ $single_sort_order .= strtoupper($sort_direction[$index]);
+ }
+ }
+ if ($current_name == $name_to_use_in_sort && $is_in_sort) {
+ // We need to generate the arrow button and related html
+ list($sort_order, $order_img) = $this->_getSortingUrlParams(
+ $sort_direction,
+ $sort_order,
+ $index
+ );
+ $order_img .= " <small>" . ($index + 1) . "</small>";
+ } else {
+ $sort_order .= strtoupper($sort_direction[$index]);
+ }
+ // Separate columns by a comma
+ $sort_order .= ", ";
+ }
+ // remove the comma from the last column name in the newly
+ // constructed clause
+ $sort_order = mb_substr(
+ $sort_order,
+ 0,
+ mb_strlen($sort_order) - 2
+ );
+ if (empty($order_img)) {
+ $order_img = '';
+ }
+ return [
+ $single_sort_order,
+ $sort_order,
+ $order_img,
+ ];
+ }
+
+ /**
+ * Check whether the column is sorted
+ *
+ * @param array $sort_expression sort expression
+ * @param array $sort_expression_nodirection sort expression without direction
+ * @param string $sort_tbl the table name
+ * @param string $name_to_use_in_sort the sorting column name
+ *
+ * @return boolean the column sorted or not
+ *
+ * @access private
+ *
+ * @see _getTableHeaders()
+ */
+ private function _isInSorted(
+ array $sort_expression,
+ array $sort_expression_nodirection,
+ $sort_tbl,
+ $name_to_use_in_sort
+ ) {
+
+ $index_in_expression = 0;
+
+ foreach ($sort_expression_nodirection as $index => $clause) {
+ if (mb_strpos($clause, '.') !== false) {
+ $fragments = explode('.', $clause);
+ $clause2 = $fragments[0] . "." . str_replace('`', '', $fragments[1]);
+ } else {
+ $clause2 = $sort_tbl . str_replace('`', '', $clause);
+ }
+ if ($clause2 === $sort_tbl . $name_to_use_in_sort) {
+ $index_in_expression = $index;
+ break;
+ }
+ }
+ if (empty($sort_expression[$index_in_expression])) {
+ $is_in_sort = false;
+ } else {
+ // Field name may be preceded by a space, or any number
+ // of characters followed by a dot (tablename.fieldname)
+ // so do a direct comparison for the sort expression;
+ // this avoids problems with queries like
+ // "SELECT id, count(id)..." and clicking to sort
+ // on id or on count(id).
+ // Another query to test this:
+ // SELECT p.*, FROM_UNIXTIME(p.temps) FROM mytable AS p
+ // (and try clicking on each column's header twice)
+ $noSortTable = empty($sort_tbl) || mb_strpos(
+ $sort_expression_nodirection[$index_in_expression],
+ $sort_tbl
+ ) === false;
+ $noOpenParenthesis = mb_strpos(
+ $sort_expression_nodirection[$index_in_expression],
+ '('
+ ) === false;
+ if (! empty($sort_tbl) && $noSortTable && $noOpenParenthesis) {
+ $new_sort_expression_nodirection = $sort_tbl
+ . $sort_expression_nodirection[$index_in_expression];
+ } else {
+ $new_sort_expression_nodirection
+ = $sort_expression_nodirection[$index_in_expression];
+ }
+
+ //Back quotes are removed in next comparison, so remove them from value
+ //to compare.
+ $name_to_use_in_sort = str_replace('`', '', $name_to_use_in_sort);
+
+ $is_in_sort = false;
+ $sort_name = str_replace('`', '', $sort_tbl) . $name_to_use_in_sort;
+
+ if ($sort_name == str_replace('`', '', $new_sort_expression_nodirection)
+ || $sort_name == str_replace('`', '', $sort_expression_nodirection[$index_in_expression])
+ ) {
+ $is_in_sort = true;
+ }
+ }
+
+ return $is_in_sort;
+ }
+
+ /**
+ * Get sort url parameters - sort order and order image
+ *
+ * @param array $sort_direction the sort direction
+ * @param string $sort_order the sorting order
+ * @param integer $index the index of sort direction array.
+ *
+ * @return array 2 element array - $sort_order, $order_img
+ *
+ * @access private
+ *
+ * @see _getSingleAndMultiSortUrls()
+ */
+ private function _getSortingUrlParams(array $sort_direction, $sort_order, $index)
+ {
+ if (strtoupper(trim($sort_direction[$index])) == self::DESCENDING_SORT_DIR) {
+ $sort_order .= ' ASC';
+ $order_img = ' ' . Util::getImage(
+ 's_desc',
+ __('Descending'),
+ [
+ 'class' => "soimg",
+ 'title' => '',
+ ]
+ );
+ $order_img .= ' ' . Util::getImage(
+ 's_asc',
+ __('Ascending'),
+ [
+ 'class' => "soimg hide",
+ 'title' => '',
+ ]
+ );
+ } else {
+ $sort_order .= ' DESC';
+ $order_img = ' ' . Util::getImage(
+ 's_asc',
+ __('Ascending'),
+ [
+ 'class' => "soimg",
+ 'title' => '',
+ ]
+ );
+ $order_img .= ' ' . Util::getImage(
+ 's_desc',
+ __('Descending'),
+ [
+ 'class' => "soimg hide",
+ 'title' => '',
+ ]
+ );
+ }
+ return [
+ $sort_order,
+ $order_img,
+ ];
+ }
+
+ /**
+ * Get sort order link
+ *
+ * @param string $order_img the sort order image
+ * @param stdClass $fields_meta set of field properties
+ * @param string $order_url the url for sort
+ * @param string $multi_order_url the url for sort
+ *
+ * @return string the sort order link
+ *
+ * @access private
+ *
+ * @see _getTableHeaders()
+ */
+ private function _getSortOrderLink(
+ $order_img,
+ $fields_meta,
+ $order_url,
+ $multi_order_url
+ ) {
+ $order_link_params = [
+ 'class' => 'sortlink',
+ ];
+
+ $order_link_content = htmlspecialchars($fields_meta->name);
+ $inner_link_content = $order_link_content . $order_img
+ . '<input type="hidden" value="' . $multi_order_url . '">';
+
+ return Util::linkOrButton(
+ $order_url,
+ $inner_link_content,
+ $order_link_params
+ );
+ }
+
+ /**
+ * Check if the column contains numeric data. If yes, then set the
+ * column header's alignment right
+ *
+ * @param stdClass $fields_meta set of field properties
+ * @param array $th_class array containing classes
+ *
+ * @return void
+ *
+ * @see _getDraggableClassForSortableColumns()
+ */
+ private function _getClassForNumericColumnType($fields_meta, array &$th_class)
+ {
+ if (preg_match(
+ '@int|decimal|float|double|real|bit|boolean|serial@i',
+ (string) $fields_meta->type
+ )) {
+ $th_class[] = 'right';
+ }
+ }
+
+ /**
+ * Prepare columns to draggable effect for sortable columns
+ *
+ * @param boolean $col_visib the column is visible (false)
+ * array the column is not visible (string array)
+ * @param string $col_visib_j element of $col_visib array
+ * @param stdClass $fields_meta set of field properties
+ * @param string $order_link the order link
+ * @param string $comments the comment for the column
+ *
+ * @return string html content
+ *
+ * @access private
+ *
+ * @see _getTableHeaders()
+ */
+ private function _getDraggableClassForSortableColumns(
+ $col_visib,
+ $col_visib_j,
+ $fields_meta,
+ $order_link,
+ $comments
+ ) {
+
+ $draggable_html = '<th';
+ $th_class = [];
+ $th_class[] = 'draggable';
+ $this->_getClassForNumericColumnType($fields_meta, $th_class);
+ if ($col_visib && ! $col_visib_j) {
+ $th_class[] = 'hide';
+ }
+
+ $th_class[] = 'column_heading';
+ if ($GLOBALS['cfg']['BrowsePointerEnable'] == true) {
+ $th_class[] = 'pointer';
+ }
+
+ if ($GLOBALS['cfg']['BrowseMarkerEnable'] == true) {
+ $th_class[] = 'marker';
+ }
+
+ $draggable_html .= ' class="' . implode(' ', $th_class) . '"';
+
+ $draggable_html .= ' data-column="' . htmlspecialchars($fields_meta->name)
+ . '">' . $order_link . $comments . '</th>';
+
+ return $draggable_html;
+ }
+
+ /**
+ * Prepare columns to draggable effect for non sortable columns
+ *
+ * @param boolean $col_visib the column is visible (false)
+ * array the column is not visible (string array)
+ * @param string $col_visib_j element of $col_visib array
+ * @param boolean $condition_field whether to add CSS class condition
+ * @param stdClass $fields_meta set of field properties
+ * @param string $comments the comment for the column
+ *
+ * @return string html content
+ *
+ * @access private
+ *
+ * @see _getTableHeaders()
+ */
+ private function _getDraggableClassForNonSortableColumns(
+ $col_visib,
+ $col_visib_j,
+ $condition_field,
+ $fields_meta,
+ $comments
+ ) {
+
+ $draggable_html = '<th';
+ $th_class = [];
+ $th_class[] = 'draggable';
+ $this->_getClassForNumericColumnType($fields_meta, $th_class);
+ if ($col_visib && ! $col_visib_j) {
+ $th_class[] = 'hide';
+ }
+
+ if ($condition_field) {
+ $th_class[] = 'condition';
+ }
+
+ $draggable_html .= ' class="' . implode(' ', $th_class) . '"';
+
+ $draggable_html .= ' data-column="'
+ . htmlspecialchars((string) $fields_meta->name) . '">';
+
+ $draggable_html .= htmlspecialchars((string) $fields_meta->name);
+
+ $draggable_html .= "\n" . $comments . '</th>';
+
+ return $draggable_html;
+ }
+
+ /**
+ * Prepare column to show at right side - check boxes or empty column
+ *
+ * @param array $displayParts which elements to display
+ * @param string $full_or_partial_text_link full/partial link or text button
+ * @param string $colspan column span of table header
+ *
+ * @return string html content
+ *
+ * @access private
+ *
+ * @see _getTableHeaders()
+ */
+ private function _getColumnAtRightSide(
+ array &$displayParts,
+ $full_or_partial_text_link,
+ $colspan
+ ) {
+
+ $right_column_html = '';
+ $display_params = $this->__get('display_params');
+
+ // Displays the needed checkboxes at the right
+ // column of the result table header if possible and required...
+ if (($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_RIGHT)
+ || ($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_BOTH)
+ && (($displayParts['edit_lnk'] != self::NO_EDIT_OR_DELETE)
+ || ($displayParts['del_lnk'] != self::NO_EDIT_OR_DELETE))
+ && ($displayParts['text_btn'] == '1')
+ ) {
+ $display_params['emptyafter']
+ = ($displayParts['edit_lnk'] != self::NO_EDIT_OR_DELETE)
+ && ($displayParts['del_lnk'] != self::NO_EDIT_OR_DELETE) ? 4 : 1;
+
+ $right_column_html .= "\n"
+ . '<th class="column_action print_ignore" ' . $colspan . '>'
+ . $full_or_partial_text_link
+ . '</th>';
+ } elseif (($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_LEFT)
+ || ($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_BOTH)
+ && (($displayParts['edit_lnk'] == self::NO_EDIT_OR_DELETE)
+ && ($displayParts['del_lnk'] == self::NO_EDIT_OR_DELETE))
+ && (! isset($GLOBALS['is_header_sent']) || ! $GLOBALS['is_header_sent'])
+ ) {
+ // ... elseif no button, displays empty columns if required
+ // (unless coming from Browse mode print view)
+
+ $display_params['emptyafter']
+ = ($displayParts['edit_lnk'] != self::NO_EDIT_OR_DELETE)
+ && ($displayParts['del_lnk'] != self::NO_EDIT_OR_DELETE) ? 4 : 1;
+
+ $right_column_html .= "\n" . '<td class="print_ignore" ' . $colspan
+ . '></td>';
+ }
+
+ $this->__set('display_params', $display_params);
+
+ return $right_column_html;
+ }
+
+ /**
+ * Prepares the display for a value
+ *
+ * @param string $class class of table cell
+ * @param bool $conditionField whether to add CSS class condition
+ * @param string $value value to display
+ *
+ * @return string the td
+ *
+ * @access private
+ *
+ * @see _getDataCellForGeometryColumns(),
+ * _getDataCellForNonNumericColumns()
+ */
+ private function _buildValueDisplay($class, $conditionField, $value)
+ {
+ return $this->template->render('display/results/value_display', [
+ 'class' => $class,
+ 'condition_field' => $conditionField,
+ 'value' => $value,
+ ]);
+ }
+
+ /**
+ * Prepares the display for a null value
+ *
+ * @param string $class class of table cell
+ * @param bool $conditionField whether to add CSS class condition
+ * @param stdClass $meta the meta-information about this field
+ * @param string $align cell alignment
+ *
+ * @return string the td
+ *
+ * @access private
+ *
+ * @see _getDataCellForNumericColumns(),
+ * _getDataCellForGeometryColumns(),
+ * _getDataCellForNonNumericColumns()
+ */
+ private function _buildNullDisplay($class, $conditionField, $meta, $align = '')
+ {
+ $classes = $this->_addClass($class, $conditionField, $meta, '');
+
+ return $this->template->render('display/results/null_display', [
+ 'align' => $align,
+ 'meta' => $meta,
+ 'classes' => $classes,
+ ]);
+ }
+
+ /**
+ * Prepares the display for an empty value
+ *
+ * @param string $class class of table cell
+ * @param bool $conditionField whether to add CSS class condition
+ * @param stdClass $meta the meta-information about this field
+ * @param string $align cell alignment
+ *
+ * @return string the td
+ *
+ * @access private
+ *
+ * @see _getDataCellForNumericColumns(),
+ * _getDataCellForGeometryColumns(),
+ * _getDataCellForNonNumericColumns()
+ */
+ private function _buildEmptyDisplay($class, $conditionField, $meta, $align = '')
+ {
+ $classes = $this->_addClass($class, $conditionField, $meta, 'nowrap');
+
+ return $this->template->render('display/results/empty_display', [
+ 'align' => $align,
+ 'classes' => $classes,
+ ]);
+ }
+
+ /**
+ * Adds the relevant classes.
+ *
+ * @param string $class class of table cell
+ * @param bool $condition_field whether to add CSS class
+ * condition
+ * @param stdClass $meta the meta-information about the
+ * field
+ * @param string $nowrap avoid wrapping
+ * @param bool $is_field_truncated is field truncated (display ...)
+ * @param TransformationsPlugin|string $transformation_plugin transformation plugin.
+ * Can also be the default function:
+ * Core::mimeDefaultFunction
+ * @param string $default_function default transformation function
+ *
+ * @return string the list of classes
+ *
+ * @access private
+ *
+ * @see _buildNullDisplay(), _getRowData()
+ */
+ private function _addClass(
+ $class,
+ $condition_field,
+ $meta,
+ $nowrap,
+ $is_field_truncated = false,
+ $transformation_plugin = '',
+ $default_function = ''
+ ) {
+ $classes = [
+ $class,
+ $nowrap,
+ ];
+
+ if (isset($meta->mimetype)) {
+ $classes[] = preg_replace('/\//', '_', $meta->mimetype);
+ }
+
+ if ($condition_field) {
+ $classes[] = 'condition';
+ }
+
+ if ($is_field_truncated) {
+ $classes[] = 'truncated';
+ }
+
+ $mime_map = $this->__get('mime_map');
+ $orgFullColName = $this->__get('db') . '.' . $meta->orgtable
+ . '.' . $meta->orgname;
+ if ($transformation_plugin != $default_function
+ || ! empty($mime_map[$orgFullColName]['input_transformation'])
+ ) {
+ $classes[] = 'transformed';
+ }
+
+ // Define classes to be added to this data field based on the type of data
+ $matches = [
+ 'enum' => 'enum',
+ 'set' => 'set',
+ 'binary' => 'hex',
+ ];
+
+ foreach ($matches as $key => $value) {
+ if (mb_strpos($meta->flags, $key) !== false) {
+ $classes[] = $value;
+ }
+ }
+
+ if (mb_strpos($meta->type, 'bit') !== false) {
+ $classes[] = 'bit';
+ }
+
+ return implode(' ', $classes);
+ }
+
+ /**
+ * Prepare the body of the results table
+ *
+ * @param integer $dt_result the link id associated to the query
+ * which results have to be displayed
+ * @param array $displayParts which elements to display
+ * @param array $map the list of relations
+ * @param array $analyzed_sql_results analyzed sql results
+ * @param boolean $is_limited_display with limited operations or not
+ *
+ * @return string html content
+ *
+ * @global array $row current row data
+ *
+ * @access private
+ *
+ * @see getTable()
+ */
+ private function _getTableBody(
+ &$dt_result,
+ array &$displayParts,
+ array $map,
+ array $analyzed_sql_results,
+ $is_limited_display = false
+ ) {
+ global $row; // mostly because of browser transformations,
+ // to make the row-data accessible in a plugin
+
+ $table_body_html = '';
+
+ // query without conditions to shorten URLs when needed, 200 is just
+ // guess, it should depend on remaining URL length
+ $url_sql_query = $this->_getUrlSqlQuery($analyzed_sql_results);
+
+ $display_params = $this->__get('display_params');
+
+ if (! is_array($map)) {
+ $map = [];
+ }
+
+ $row_no = 0;
+ $display_params['edit'] = [];
+ $display_params['copy'] = [];
+ $display_params['delete'] = [];
+ $display_params['data'] = [];
+ $display_params['row_delete'] = [];
+ $this->__set('display_params', $display_params);
+
+ // name of the class added to all grid editable elements;
+ // if we don't have all the columns of a unique key in the result set,
+ // do not permit grid editing
+ if ($is_limited_display || ! $this->__get('editable')) {
+ $grid_edit_class = '';
+ } else {
+ switch ($GLOBALS['cfg']['GridEditing']) {
+ case 'double-click':
+ // trying to reduce generated HTML by using shorter
+ // classes like click1 and click2
+ $grid_edit_class = 'grid_edit click2';
+ break;
+ case 'click':
+ $grid_edit_class = 'grid_edit click1';
+ break;
+ default: // 'disabled'
+ $grid_edit_class = '';
+ break;
+ }
+ }
+
+ // prepare to get the column order, if available
+ list($col_order, $col_visib) = $this->_getColumnParams(
+ $analyzed_sql_results
+ );
+
+ // Correction University of Virginia 19991216 in the while below
+ // Previous code assumed that all tables have keys, specifically that
+ // the phpMyAdmin GUI should support row delete/edit only for such
+ // tables.
+ // Although always using keys is arguably the prescribed way of
+ // defining a relational table, it is not required. This will in
+ // particular be violated by the novice.
+ // We want to encourage phpMyAdmin usage by such novices. So the code
+ // below has been changed to conditionally work as before when the
+ // table being displayed has one or more keys; but to display
+ // delete/edit options correctly for tables without keys.
+
+ $whereClauseMap = $this->__get('whereClauseMap');
+ while ($row = $GLOBALS['dbi']->fetchRow($dt_result)) {
+ // add repeating headers
+ if (($row_no != 0) && ($_SESSION['tmpval']['repeat_cells'] != 0)
+ && ! ($row_no % $_SESSION['tmpval']['repeat_cells'])
+ ) {
+ $table_body_html .= $this->_getRepeatingHeaders(
+ $display_params
+ );
+ }
+
+ $tr_class = [];
+ if ($GLOBALS['cfg']['BrowsePointerEnable'] != true) {
+ $tr_class[] = 'nopointer';
+ }
+ if ($GLOBALS['cfg']['BrowseMarkerEnable'] != true) {
+ $tr_class[] = 'nomarker';
+ }
+
+ // pointer code part
+ $classes = (empty($tr_class) ? ' ' : 'class="' . implode(' ', $tr_class) . '"');
+ $table_body_html .= '<tr ' . $classes . ' >';
+
+ // 1. Prepares the row
+
+ // In print view these variable needs to be initialized
+ $del_url = $del_str = $edit_anchor_class
+ = $edit_str = $js_conf = $copy_url = $copy_str = $edit_url = null;
+
+ // 1.2 Defines the URLs for the modify/delete link(s)
+
+ if (($displayParts['edit_lnk'] != self::NO_EDIT_OR_DELETE)
+ || ($displayParts['del_lnk'] != self::NO_EDIT_OR_DELETE)
+ ) {
+ // Results from a "SELECT" statement -> builds the
+ // WHERE clause to use in links (a unique key if possible)
+ /**
+ * @todo $where_clause could be empty, for example a table
+ * with only one field and it's a BLOB; in this case,
+ * avoid to display the delete and edit links
+ */
+ list($where_clause, $clause_is_unique, $condition_array)
+ = Util::getUniqueCondition(
+ $dt_result, // handle
+ $this->__get('fields_cnt'), // fields_cnt
+ $this->__get('fields_meta'), // fields_meta
+ $row, // row
+ false, // force_unique
+ $this->__get('table'), // restrict_to_table
+ $analyzed_sql_results // analyzed_sql_results
+ );
+ $whereClauseMap[$row_no][$this->__get('table')] = $where_clause;
+ $this->__set('whereClauseMap', $whereClauseMap);
+
+ $where_clause_html = htmlspecialchars($where_clause);
+
+ // 1.2.1 Modify link(s) - update row case
+ if ($displayParts['edit_lnk'] == self::UPDATE_ROW) {
+ list($edit_url, $copy_url, $edit_str, $copy_str,
+ $edit_anchor_class)
+ = $this->_getModifiedLinks(
+ $where_clause,
+ $clause_is_unique,
+ $url_sql_query
+ );
+ } // end if (1.2.1)
+
+ // 1.2.2 Delete/Kill link(s)
+ list($del_url, $del_str, $js_conf)
+ = $this->_getDeleteAndKillLinks(
+ $where_clause,
+ $clause_is_unique,
+ $url_sql_query,
+ $displayParts['del_lnk'],
+ $row
+ );
+
+ // 1.3 Displays the links at left if required
+ if (($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_LEFT)
+ || ($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_BOTH)
+ ) {
+ $table_body_html .= $this->_getPlacedLinks(
+ self::POSITION_LEFT,
+ $del_url,
+ $displayParts,
+ $row_no,
+ $where_clause,
+ $where_clause_html,
+ $condition_array,
+ $edit_url,
+ $copy_url,
+ $edit_anchor_class,
+ $edit_str,
+ $copy_str,
+ $del_str,
+ $js_conf
+ );
+ } elseif ($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_NONE) {
+ $table_body_html .= $this->_getPlacedLinks(
+ self::POSITION_NONE,
+ $del_url,
+ $displayParts,
+ $row_no,
+ $where_clause,
+ $where_clause_html,
+ $condition_array,
+ $edit_url,
+ $copy_url,
+ $edit_anchor_class,
+ $edit_str,
+ $copy_str,
+ $del_str,
+ $js_conf
+ );
+ } // end if (1.3)
+ } // end if (1)
+
+ // 2. Displays the rows' values
+ if ($this->__get('mime_map') === null) {
+ $this->_setMimeMap();
+ }
+ $table_body_html .= $this->_getRowValues(
+ $dt_result,
+ $row,
+ $row_no,
+ $col_order,
+ $map,
+ $grid_edit_class,
+ $col_visib,
+ $url_sql_query,
+ $analyzed_sql_results
+ );
+
+ // 3. Displays the modify/delete links on the right if required
+ if (($displayParts['edit_lnk'] != self::NO_EDIT_OR_DELETE)
+ || ($displayParts['del_lnk'] != self::NO_EDIT_OR_DELETE)
+ ) {
+ if (($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_RIGHT)
+ || ($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_BOTH)
+ ) {
+ $table_body_html .= $this->_getPlacedLinks(
+ self::POSITION_RIGHT,
+ $del_url,
+ $displayParts,
+ $row_no,
+ $where_clause,
+ $where_clause_html,
+ $condition_array,
+ $edit_url,
+ $copy_url,
+ $edit_anchor_class,
+ $edit_str,
+ $copy_str,
+ $del_str,
+ $js_conf
+ );
+ }
+ } // end if (3)
+
+ $table_body_html .= '</tr>';
+ $table_body_html .= "\n";
+ $row_no++;
+ } // end while
+
+ return $table_body_html;
+ }
+
+ /**
+ * Sets the MIME details of the columns in the results set
+ *
+ * @return void
+ */
+ private function _setMimeMap()
+ {
+ $fields_meta = $this->__get('fields_meta');
+ $mimeMap = [];
+ $added = [];
+
+ for ($currentColumn = 0; $currentColumn < $this->__get('fields_cnt'); ++$currentColumn) {
+ $meta = $fields_meta[$currentColumn];
+ $orgFullTableName = $this->__get('db') . '.' . $meta->orgtable;
+
+ if ($GLOBALS['cfgRelation']['commwork']
+ && $GLOBALS['cfgRelation']['mimework']
+ && $GLOBALS['cfg']['BrowseMIME']
+ && ! $_SESSION['tmpval']['hide_transformation']
+ && empty($added[$orgFullTableName])
+ ) {
+ $mimeMap = array_merge(
+ $mimeMap,
+ $this->transformations->getMime($this->__get('db'), $meta->orgtable, false, true)
+ );
+ $added[$orgFullTableName] = true;
+ }
+ }
+
+ // special browser transformation for some SHOW statements
+ if ($this->__get('is_show')
+ && ! $_SESSION['tmpval']['hide_transformation']
+ ) {
+ preg_match(
+ '@^SHOW[[:space:]]+(VARIABLES|(FULL[[:space:]]+)?'
+ . 'PROCESSLIST|STATUS|TABLE|GRANTS|CREATE|LOGS|DATABASES|FIELDS'
+ . ')@i',
+ $this->__get('sql_query'),
+ $which
+ );
+
+ if (isset($which[1])) {
+ $str = ' ' . strtoupper($which[1]);
+ $isShowProcessList = strpos($str, 'PROCESSLIST') > 0;
+ if ($isShowProcessList) {
+ $mimeMap['..Info'] = [
+ 'mimetype' => 'Text_Plain',
+ 'transformation' => 'output/Text_Plain_Sql.php',
+ ];
+ }
+
+ $isShowCreateTable = preg_match(
+ '@CREATE[[:space:]]+TABLE@i',
+ $this->__get('sql_query')
+ );
+ if ($isShowCreateTable) {
+ $mimeMap['..Create Table'] = [
+ 'mimetype' => 'Text_Plain',
+ 'transformation' => 'output/Text_Plain_Sql.php',
+ ];
+ }
+ }
+ }
+
+ $this->__set('mime_map', $mimeMap);
+ }
+
+ /**
+ * Get the values for one data row
+ *
+ * @param integer $dt_result the link id associated to
+ * the query which results
+ * have to be displayed
+ * @param array $row current row data
+ * @param integer $row_no the index of current row
+ * @param array|boolean $col_order the column order false when
+ * a property not found false
+ * when a property not found
+ * @param array $map the list of relations
+ * @param string $grid_edit_class the class for all editable
+ * columns
+ * @param boolean|array|string $col_visib column is visible(false);
+ * column isn't visible(string
+ * array)
+ * @param string $url_sql_query the analyzed sql query
+ * @param array $analyzed_sql_results analyzed sql results
+ *
+ * @return string html content
+ *
+ * @access private
+ *
+ * @see _getTableBody()
+ */
+ private function _getRowValues(
+ &$dt_result,
+ array $row,
+ $row_no,
+ $col_order,
+ array $map,
+ $grid_edit_class,
+ $col_visib,
+ $url_sql_query,
+ array $analyzed_sql_results
+ ) {
+ $row_values_html = '';
+
+ // Following variable are needed for use in isset/empty or
+ // use with array indexes/safe use in foreach
+ $sql_query = $this->__get('sql_query');
+ $fields_meta = $this->__get('fields_meta');
+ $highlight_columns = $this->__get('highlight_columns');
+ $mime_map = $this->__get('mime_map');
+
+ $row_info = $this->_getRowInfoForSpecialLinks($row, $col_order);
+
+ $whereClauseMap = $this->__get('whereClauseMap');
+
+ $columnCount = $this->__get('fields_cnt');
+ for ($currentColumn = 0; $currentColumn < $columnCount; ++$currentColumn) {
+ // assign $i with appropriate column order
+ $i = $col_order ? $col_order[$currentColumn] : $currentColumn;
+
+ $meta = $fields_meta[$i];
+ $orgFullColName
+ = $this->__get('db') . '.' . $meta->orgtable . '.' . $meta->orgname;
+
+ $not_null_class = $meta->not_null ? 'not_null' : '';
+ $relation_class = isset($map[$meta->name]) ? 'relation' : '';
+ $hide_class = $col_visib && ! $col_visib[$currentColumn]
+ ? 'hide'
+ : '';
+ $grid_edit = $meta->orgtable != '' ? $grid_edit_class : '';
+
+ // handle datetime-related class, for grid editing
+ $field_type_class
+ = $this->_getClassForDateTimeRelatedFields($meta->type);
+
+ $is_field_truncated = false;
+ // combine all the classes applicable to this column's value
+ $class = $this->_getClassesForColumn(
+ $grid_edit,
+ $not_null_class,
+ $relation_class,
+ $hide_class,
+ $field_type_class
+ );
+
+ // See if this column should get highlight because it's used in the
+ // where-query.
+ $condition_field = isset($highlight_columns)
+ && (isset($highlight_columns[$meta->name])
+ || isset($highlight_columns[Util::backquote($meta->name)]))
+ ? true
+ : false;
+
+ // Wrap MIME-transformations. [MIME]
+ $default_function = [
+ Core::class,
+ 'mimeDefaultFunction',
+ ]; // default_function
+ $transformation_plugin = $default_function;
+ $transform_options = [];
+
+ if ($GLOBALS['cfgRelation']['mimework']
+ && $GLOBALS['cfg']['BrowseMIME']
+ ) {
+ if (isset($mime_map[$orgFullColName]['mimetype'])
+ && ! empty($mime_map[$orgFullColName]['transformation'])
+ ) {
+ $file = $mime_map[$orgFullColName]['transformation'];
+ $include_file = 'libraries/classes/Plugins/Transformations/' . $file;
+
+ if (@file_exists($include_file)) {
+ $class_name = $this->transformations->getClassName($include_file);
+ if (class_exists($class_name)) {
+ // todo add $plugin_manager
+ $plugin_manager = null;
+ $transformation_plugin = new $class_name(
+ $plugin_manager
+ );
+
+ $transform_options = $this->transformations->getOptions(
+ isset(
+ $mime_map[$orgFullColName]['transformation_options']
+ )
+ ? $mime_map[$orgFullColName]['transformation_options']
+ : ''
+ );
+
+ $meta->mimetype = str_replace(
+ '_',
+ '/',
+ $mime_map[$orgFullColName]['mimetype']
+ );
+ }
+ } // end if file_exists
+ } // end if transformation is set
+ } // end if mime/transformation works.
+
+ // Check whether the field needs to display with syntax highlighting
+
+ $dbLower = mb_strtolower($this->__get('db'));
+ $tblLower = mb_strtolower($meta->orgtable);
+ $nameLower = mb_strtolower($meta->orgname);
+ if (! empty($this->transformation_info[$dbLower][$tblLower][$nameLower])
+ && (trim($row[$i]) != '')
+ && ! $_SESSION['tmpval']['hide_transformation']
+ ) {
+ include_once $this->transformation_info[$dbLower][$tblLower][$nameLower][0];
+ $transformation_plugin = new $this->transformation_info[$dbLower][$tblLower][$nameLower][1](null);
+
+ $transform_options = $this->transformations->getOptions(
+ isset($mime_map[$orgFullColName]['transformation_options'])
+ ? $mime_map[$orgFullColName]['transformation_options']
+ : ''
+ );
+
+ $meta->mimetype = str_replace(
+ '_',
+ '/',
+ $this->transformation_info[$dbLower][mb_strtolower($meta->orgtable)][mb_strtolower($meta->orgname)][2]
+ );
+ }
+
+ // Check for the predefined fields need to show as link in schemas
+ $specialSchemaLinks = SpecialSchemaLinks::get();
+
+ if (! empty($specialSchemaLinks[$dbLower][$tblLower][$nameLower])) {
+ $linking_url = $this->_getSpecialLinkUrl(
+ $specialSchemaLinks,
+ $row[$i],
+ $row_info,
+ mb_strtolower($meta->orgname)
+ );
+ $transformation_plugin = new Text_Plain_Link();
+
+ $transform_options = [
+ 0 => $linking_url,
+ 2 => true,
+ ];
+
+ $meta->mimetype = str_replace(
+ '_',
+ '/',
+ 'Text/Plain'
+ );
+ }
+
+ /*
+ * The result set can have columns from more than one table,
+ * this is why we have to check for the unique conditions
+ * related to this table; however getUniqueCondition() is
+ * costly and does not need to be called if we already know
+ * the conditions for the current table.
+ */
+ if (! isset($whereClauseMap[$row_no][$meta->orgtable])) {
+ $unique_conditions = Util::getUniqueCondition(
+ $dt_result, // handle
+ $this->__get('fields_cnt'), // fields_cnt
+ $this->__get('fields_meta'), // fields_meta
+ $row, // row
+ false, // force_unique
+ $meta->orgtable, // restrict_to_table
+ $analyzed_sql_results // analyzed_sql_results
+ );
+ $whereClauseMap[$row_no][$meta->orgtable] = $unique_conditions[0];
+ }
+
+ $_url_params = [
+ 'db' => $this->__get('db'),
+ 'table' => $meta->orgtable,
+ 'where_clause' => $whereClauseMap[$row_no][$meta->orgtable],
+ 'transform_key' => $meta->orgname,
+ ];
+
+ if (! empty($sql_query)) {
+ $_url_params['sql_query'] = $url_sql_query;
+ }
+
+ $transform_options['wrapper_link'] = Url::getCommon($_url_params);
+
+ $display_params = $this->__get('display_params');
+
+ // in some situations (issue 11406), numeric returns 1
+ // even for a string type
+ // for decimal numeric is returning 1
+ // have to improve logic
+ if (($meta->numeric == 1 && $meta->type != 'string') || $meta->type == 'real') {
+ // n u m e r i c
+
+ $display_params['data'][$row_no][$i]
+ = $this->_getDataCellForNumericColumns(
+ $row[$i] === null ? null : (string) $row[$i],
+ $class,
+ $condition_field,
+ $meta,
+ $map,
+ $is_field_truncated,
+ $analyzed_sql_results,
+ $transformation_plugin,
+ $default_function,
+ $transform_options
+ );
+ } elseif ($meta->type == self::GEOMETRY_FIELD) {
+ // g e o m e t r y
+
+ // Remove 'grid_edit' from $class as we do not allow to
+ // inline-edit geometry data.
+ $class = str_replace('grid_edit', '', $class);
+
+ $display_params['data'][$row_no][$i]
+ = $this->_getDataCellForGeometryColumns(
+ $row[$i],
+ $class,
+ $meta,
+ $map,
+ $_url_params,
+ $condition_field,
+ $transformation_plugin,
+ $default_function,
+ $transform_options,
+ $analyzed_sql_results
+ );
+ } else {
+ // n o t n u m e r i c
+
+ $display_params['data'][$row_no][$i]
+ = $this->_getDataCellForNonNumericColumns(
+ $row[$i],
+ $class,
+ $meta,
+ $map,
+ $_url_params,
+ $condition_field,
+ $transformation_plugin,
+ $default_function,
+ $transform_options,
+ $is_field_truncated,
+ $analyzed_sql_results,
+ $dt_result,
+ $i
+ );
+ }
+
+ // output stored cell
+ $row_values_html .= $display_params['data'][$row_no][$i];
+
+ if (isset($display_params['rowdata'][$i][$row_no])) {
+ $display_params['rowdata'][$i][$row_no]
+ .= $display_params['data'][$row_no][$i];
+ } else {
+ $display_params['rowdata'][$i][$row_no]
+ = $display_params['data'][$row_no][$i];
+ }
+
+ $this->__set('display_params', $display_params);
+ } // end for
+
+ return $row_values_html;
+ }
+
+ /**
+ * Get link for display special schema links
+ *
+ * @param array $specialSchemaLinks special schema links
+ * @param string $column_value column value
+ * @param array $row_info information about row
+ * @param string $field_name column name
+ *
+ * @return string generated link
+ */
+ private function _getSpecialLinkUrl(
+ array $specialSchemaLinks,
+ $column_value,
+ array $row_info,
+ $field_name
+ ) {
+ $linking_url_params = [];
+ $link_relations = $specialSchemaLinks[mb_strtolower($this->__get('db'))][mb_strtolower($this->__get('table'))][$field_name];
+
+ if (! is_array($link_relations['link_param'])) {
+ $linking_url_params[$link_relations['link_param']] = $column_value;
+ } else {
+ // Consider only the case of creating link for column field
+ // sql query that needs to be passed as url param
+ $sql = 'SELECT `' . $column_value . '` FROM `'
+ . $row_info[$link_relations['link_param'][1]] . '`.`'
+ . $row_info[$link_relations['link_param'][2]] . '`';
+ $linking_url_params[$link_relations['link_param'][0]] = $sql;
+ }
+
+ $divider = strpos($link_relations['default_page'], '?') ? '&' : '?';
+ if (empty($link_relations['link_dependancy_params'])) {
+ return $link_relations['default_page']
+ . Url::getCommonRaw($linking_url_params, $divider);
+ }
+
+ foreach ($link_relations['link_dependancy_params'] as $new_param) {
+ // If param_info is an array, set the key and value
+ // from that array
+ if (is_array($new_param['param_info'])) {
+ $linking_url_params[$new_param['param_info'][0]]
+ = $new_param['param_info'][1];
+ continue;
+ }
+
+ $linking_url_params[$new_param['param_info']]
+ = $row_info[mb_strtolower($new_param['column_name'])];
+
+ // Special case 1 - when executing routines, according
+ // to the type of the routine, url param changes
+ if (empty($row_info['routine_type'])) {
+ continue;
+ }
+ }
+
+ return $link_relations['default_page']
+ . Url::getCommonRaw($linking_url_params, $divider);
+ }
+
+ /**
+ * Prepare row information for display special links
+ *
+ * @param array $row current row data
+ * @param array|boolean $col_order the column order
+ *
+ * @return array associative array with column nama -> value
+ */
+ private function _getRowInfoForSpecialLinks(array $row, $col_order)
+ {
+
+ $row_info = [];
+ $fields_meta = $this->__get('fields_meta');
+
+ for ($n = 0; $n < $this->__get('fields_cnt'); ++$n) {
+ $m = $col_order ? $col_order[$n] : $n;
+ $row_info[mb_strtolower($fields_meta[$m]->orgname)]
+ = $row[$m];
+ }
+
+ return $row_info;
+ }
+
+ /**
+ * Get url sql query without conditions to shorten URLs
+ *
+ * @param array $analyzed_sql_results analyzed sql results
+ *
+ * @return string analyzed sql query
+ *
+ * @access private
+ *
+ * @see _getTableBody()
+ */
+ private function _getUrlSqlQuery(array $analyzed_sql_results)
+ {
+ if (($analyzed_sql_results['querytype'] != 'SELECT')
+ || (mb_strlen($this->__get('sql_query')) < 200)
+ ) {
+ return $this->__get('sql_query');
+ }
+
+ $query = 'SELECT ' . Query::getClause(
+ $analyzed_sql_results['statement'],
+ $analyzed_sql_results['parser']->list,
+ 'SELECT'
+ );
+
+ $from_clause = Query::getClause(
+ $analyzed_sql_results['statement'],
+ $analyzed_sql_results['parser']->list,
+ 'FROM'
+ );
+
+ if (! empty($from_clause)) {
+ $query .= ' FROM ' . $from_clause;
+ }
+
+ return $query;
+ }
+
+ /**
+ * Get column order and column visibility
+ *
+ * @param array $analyzed_sql_results analyzed sql results
+ *
+ * @return array 2 element array - $col_order, $col_visib
+ *
+ * @access private
+ *
+ * @see _getTableBody()
+ */
+ private function _getColumnParams(array $analyzed_sql_results)
+ {
+ if ($this->_isSelect($analyzed_sql_results)) {
+ $pmatable = new Table($this->__get('table'), $this->__get('db'));
+ $col_order = $pmatable->getUiProp(Table::PROP_COLUMN_ORDER);
+ /* Validate the value */
+ if ($col_order !== false) {
+ $fields_cnt = $this->__get('fields_cnt');
+ foreach ($col_order as $value) {
+ if ($value >= $fields_cnt) {
+ $pmatable->removeUiProp(Table::PROP_COLUMN_ORDER);
+ $fields_cnt = false;
+ }
+ }
+ }
+ $col_visib = $pmatable->getUiProp(Table::PROP_COLUMN_VISIB);
+ } else {
+ $col_order = false;
+ $col_visib = false;
+ }
+
+ return [
+ $col_order,
+ $col_visib,
+ ];
+ }
+
+ /**
+ * Get HTML for repeating headers
+ *
+ * @param array $display_params holds various display info
+ *
+ * @return string html content
+ *
+ * @access private
+ *
+ * @see _getTableBody()
+ */
+ private function _getRepeatingHeaders(
+ array $display_params
+ ) {
+ $header_html = '<tr>' . "\n";
+
+ if ($display_params['emptypre'] > 0) {
+ $header_html .= ' <th colspan="'
+ . $display_params['emptypre'] . '">'
+ . "\n" . ' &nbsp;</th>' . "\n";
+ } elseif ($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_NONE) {
+ $header_html .= ' <th></th>' . "\n";
+ }
+
+ foreach ($display_params['desc'] as $val) {
+ $header_html .= $val;
+ }
+
+ if ($display_params['emptyafter'] > 0) {
+ $header_html
+ .= ' <th colspan="' . $display_params['emptyafter']
+ . '">'
+ . "\n" . ' &nbsp;</th>' . "\n";
+ }
+ $header_html .= '</tr>' . "\n";
+
+ return $header_html;
+ }
+
+ /**
+ * Get modified links
+ *
+ * @param string $where_clause the where clause of the sql
+ * @param boolean $clause_is_unique the unique condition of clause
+ * @param string $url_sql_query the analyzed sql query
+ *
+ * @return array 5 element array - $edit_url, $copy_url,
+ * $edit_str, $copy_str, $edit_anchor_class
+ *
+ * @access private
+ *
+ * @see _getTableBody()
+ */
+ private function _getModifiedLinks(
+ $where_clause,
+ $clause_is_unique,
+ $url_sql_query
+ ) {
+
+ $_url_params = [
+ 'db' => $this->__get('db'),
+ 'table' => $this->__get('table'),
+ 'where_clause' => $where_clause,
+ 'clause_is_unique' => $clause_is_unique,
+ 'sql_query' => $url_sql_query,
+ 'goto' => 'sql.php',
+ ];
+
+ $edit_url = 'tbl_change.php'
+ . Url::getCommon(
+ $_url_params + ['default_action' => 'update']
+ );
+
+ $copy_url = 'tbl_change.php'
+ . Url::getCommon(
+ $_url_params + ['default_action' => 'insert']
+ );
+
+ $edit_str = $this->_getActionLinkContent(
+ 'b_edit',
+ __('Edit')
+ );
+ $copy_str = $this->_getActionLinkContent(
+ 'b_insrow',
+ __('Copy')
+ );
+
+ // Class definitions required for grid editing jQuery scripts
+ $edit_anchor_class = "edit_row_anchor";
+ if ($clause_is_unique == 0) {
+ $edit_anchor_class .= ' nonunique';
+ }
+
+ return [
+ $edit_url,
+ $copy_url,
+ $edit_str,
+ $copy_str,
+ $edit_anchor_class,
+ ];
+ }
+
+ /**
+ * Get delete and kill links
+ *
+ * @param string $where_clause the where clause of the sql
+ * @param boolean $clause_is_unique the unique condition of clause
+ * @param string $url_sql_query the analyzed sql query
+ * @param string $del_lnk the delete link of current row
+ * @param array $row the current row
+ *
+ * @return array 3 element array
+ * $del_url, $del_str, $js_conf
+ *
+ * @access private
+ *
+ * @see _getTableBody()
+ */
+ private function _getDeleteAndKillLinks(
+ $where_clause,
+ $clause_is_unique,
+ $url_sql_query,
+ $del_lnk,
+ array $row
+ ) {
+
+ $goto = $this->__get('goto');
+
+ if ($del_lnk == self::DELETE_ROW) { // delete row case
+ $_url_params = [
+ 'db' => $this->__get('db'),
+ 'table' => $this->__get('table'),
+ 'sql_query' => $url_sql_query,
+ 'message_to_show' => __('The row has been deleted.'),
+ 'goto' => empty($goto) ? 'tbl_sql.php' : $goto,
+ ];
+
+ $lnk_goto = 'sql.php' . Url::getCommonRaw($_url_params);
+
+ $del_query = 'DELETE FROM '
+ . Util::backquote($this->__get('table'))
+ . ' WHERE ' . $where_clause .
+ ($clause_is_unique ? '' : ' LIMIT 1');
+
+ $_url_params = [
+ 'db' => $this->__get('db'),
+ 'table' => $this->__get('table'),
+ 'sql_query' => $del_query,
+ 'message_to_show' => __('The row has been deleted.'),
+ 'goto' => $lnk_goto,
+ ];
+ $del_url = 'sql.php' . Url::getCommon($_url_params);
+
+ $js_conf = 'DELETE FROM ' . Sanitize::jsFormat($this->__get('table'))
+ . ' WHERE ' . Sanitize::jsFormat($where_clause, false)
+ . ($clause_is_unique ? '' : ' LIMIT 1');
+
+ $del_str = $this->_getActionLinkContent('b_drop', __('Delete'));
+ } elseif ($del_lnk == self::KILL_PROCESS) { // kill process case
+ $_url_params = [
+ 'db' => $this->__get('db'),
+ 'table' => $this->__get('table'),
+ 'sql_query' => $url_sql_query,
+ 'goto' => 'index.php',
+ ];
+
+ $lnk_goto = 'sql.php' . Url::getCommonRaw($_url_params);
+
+ $kill = $GLOBALS['dbi']->getKillQuery((int) $row[0]);
+
+ $_url_params = [
+ 'db' => 'mysql',
+ 'sql_query' => $kill,
+ 'goto' => $lnk_goto,
+ ];
+
+ $del_url = 'sql.php' . Url::getCommon($_url_params);
+ $js_conf = $kill;
+ $del_str = Util::getIcon(
+ 'b_drop',
+ __('Kill')
+ );
+ } else {
+ $del_url = $del_str = $js_conf = null;
+ }
+
+ return [
+ $del_url,
+ $del_str,
+ $js_conf,
+ ];
+ }
+
+ /**
+ * Get content inside the table row action links (Edit/Copy/Delete)
+ *
+ * @param string $icon The name of the file to get
+ * @param string $display_text The text displaying after the image icon
+ *
+ * @return string
+ *
+ * @access private
+ *
+ * @see _getModifiedLinks(), _getDeleteAndKillLinks()
+ */
+ private function _getActionLinkContent($icon, $display_text)
+ {
+
+ $linkContent = '';
+
+ if (isset($GLOBALS['cfg']['RowActionType'])
+ && $GLOBALS['cfg']['RowActionType'] == self::ACTION_LINK_CONTENT_ICONS
+ ) {
+ $linkContent .= '<span class="nowrap">'
+ . Util::getImage(
+ $icon,
+ $display_text
+ )
+ . '</span>';
+ } elseif (isset($GLOBALS['cfg']['RowActionType'])
+ && $GLOBALS['cfg']['RowActionType'] == self::ACTION_LINK_CONTENT_TEXT
+ ) {
+ $linkContent .= '<span class="nowrap">' . $display_text . '</span>';
+ } else {
+ $linkContent .= Util::getIcon(
+ $icon,
+ $display_text
+ );
+ }
+
+ return $linkContent;
+ }
+
+ /**
+ * Prepare placed links
+ *
+ * @param string $dir the direction of links should place
+ * @param string $del_url the url for delete row
+ * @param array $displayParts which elements to display
+ * @param integer $row_no the index of current row
+ * @param string $where_clause the where clause of the sql
+ * @param string $where_clause_html the html encoded where clause
+ * @param array $condition_array array of keys (primary, unique, condition)
+ * @param string $edit_url the url for edit row
+ * @param string $copy_url the url for copy row
+ * @param string $edit_anchor_class the class for html element for edit
+ * @param string $edit_str the label for edit row
+ * @param string $copy_str the label for copy row
+ * @param string $del_str the label for delete row
+ * @param string|null $js_conf text for the JS confirmation
+ *
+ * @return string html content
+ *
+ * @access private
+ *
+ * @see _getTableBody()
+ */
+ private function _getPlacedLinks(
+ $dir,
+ $del_url,
+ array $displayParts,
+ $row_no,
+ $where_clause,
+ $where_clause_html,
+ array $condition_array,
+ $edit_url,
+ $copy_url,
+ $edit_anchor_class,
+ $edit_str,
+ $copy_str,
+ $del_str,
+ ?string $js_conf
+ ) {
+
+ if (! isset($js_conf)) {
+ $js_conf = '';
+ }
+
+ return $this->_getCheckboxAndLinks(
+ $dir,
+ $del_url,
+ $displayParts,
+ $row_no,
+ $where_clause,
+ $where_clause_html,
+ $condition_array,
+ $edit_url,
+ $copy_url,
+ $edit_anchor_class,
+ $edit_str,
+ $copy_str,
+ $del_str,
+ $js_conf
+ );
+ }
+
+ /**
+ * Get the combined classes for a column
+ *
+ * @param string $grid_edit_class the class for all editable columns
+ * @param string $not_null_class the class for not null columns
+ * @param string $relation_class the class for relations in a column
+ * @param string $hide_class the class for visibility of a column
+ * @param string $field_type_class the class related to type of the field
+ *
+ * @return string the combined classes
+ *
+ * @access private
+ *
+ * @see _getTableBody()
+ */
+ private function _getClassesForColumn(
+ $grid_edit_class,
+ $not_null_class,
+ $relation_class,
+ $hide_class,
+ $field_type_class
+ ) {
+ return 'data ' . $grid_edit_class . ' ' . $not_null_class . ' '
+ . $relation_class . ' ' . $hide_class . ' ' . $field_type_class;
+ }
+
+ /**
+ * Get class for datetime related fields
+ *
+ * @param string $type the type of the column field
+ *
+ * @return string the class for the column
+ *
+ * @access private
+ *
+ * @see _getTableBody()
+ */
+ private function _getClassForDateTimeRelatedFields($type)
+ {
+ if ((substr($type, 0, 9) == self::TIMESTAMP_FIELD)
+ || ($type == self::DATETIME_FIELD)
+ ) {
+ $field_type_class = 'datetimefield';
+ } elseif ($type == self::DATE_FIELD) {
+ $field_type_class = 'datefield';
+ } elseif ($type == self::TIME_FIELD) {
+ $field_type_class = 'timefield';
+ } elseif ($type == self::STRING_FIELD) {
+ $field_type_class = 'text';
+ } else {
+ $field_type_class = '';
+ }
+ return $field_type_class;
+ }
+
+ /**
+ * Prepare data cell for numeric type fields
+ *
+ * @param string|null $column the column's value
+ * @param string $class the html class for column
+ * @param boolean $condition_field the column should highlighted
+ * or not
+ * @param stdClass $meta the meta-information about this
+ * field
+ * @param array $map the list of relations
+ * @param boolean $is_field_truncated the condition for blob data
+ * replacements
+ * @param array $analyzed_sql_results the analyzed query
+ * @param TransformationsPlugin $transformation_plugin the name of transformation plugin
+ * @param string $default_function the default transformation
+ * function
+ * @param array $transform_options the transformation parameters
+ *
+ * @return string the prepared cell, html content
+ *
+ * @access private
+ *
+ * @see _getTableBody()
+ */
+ private function _getDataCellForNumericColumns(
+ ?string $column,
+ $class,
+ $condition_field,
+ $meta,
+ array $map,
+ $is_field_truncated,
+ array $analyzed_sql_results,
+ $transformation_plugin,
+ $default_function,
+ array $transform_options
+ ) {
+
+ if (! isset($column) || $column === null) {
+ $cell = $this->_buildNullDisplay(
+ 'right ' . $class,
+ $condition_field,
+ $meta,
+ ''
+ );
+ } elseif ($column != '') {
+ $nowrap = ' nowrap';
+ $where_comparison = ' = ' . $column;
+
+ $cell = $this->_getRowData(
+ 'right ' . $class,
+ $condition_field,
+ $analyzed_sql_results,
+ $meta,
+ $map,
+ $column,
+ $column,
+ $transformation_plugin,
+ $default_function,
+ $nowrap,
+ $where_comparison,
+ $transform_options,
+ $is_field_truncated,
+ ''
+ );
+ } else {
+ $cell = $this->_buildEmptyDisplay(
+ 'right ' . $class,
+ $condition_field,
+ $meta,
+ ''
+ );
+ }
+
+ return $cell;
+ }
+
+ /**
+ * Get data cell for geometry type fields
+ *
+ * @param string|null $column the relevant column in data row
+ * @param string $class the html class for column
+ * @param stdClass $meta the meta-information about
+ * this field
+ * @param array $map the list of relations
+ * @param array $_url_params the parameters for generate url
+ * @param boolean $condition_field the column should highlighted
+ * or not
+ * @param TransformationsPlugin $transformation_plugin the name of transformation
+ * function
+ * @param string $default_function the default transformation
+ * function
+ * @param string $transform_options the transformation parameters
+ * @param array $analyzed_sql_results the analyzed query
+ *
+ * @return string the prepared data cell, html content
+ *
+ * @access private
+ *
+ * @see _getTableBody()
+ */
+ private function _getDataCellForGeometryColumns(
+ ?string $column,
+ $class,
+ $meta,
+ array $map,
+ array $_url_params,
+ $condition_field,
+ $transformation_plugin,
+ $default_function,
+ $transform_options,
+ array $analyzed_sql_results
+ ) {
+ if (! isset($column) || $column === null) {
+ $cell = $this->_buildNullDisplay($class, $condition_field, $meta);
+ return $cell;
+ }
+
+ if ($column == '') {
+ $cell = $this->_buildEmptyDisplay($class, $condition_field, $meta);
+ return $cell;
+ }
+
+ // Display as [GEOMETRY - (size)]
+ if ($_SESSION['tmpval']['geoOption'] == self::GEOMETRY_DISP_GEOM) {
+ $geometry_text = $this->_handleNonPrintableContents(
+ strtoupper(self::GEOMETRY_FIELD),
+ $column,
+ $transformation_plugin,
+ $transform_options,
+ $default_function,
+ $meta,
+ $_url_params
+ );
+
+ $cell = $this->_buildValueDisplay(
+ $class,
+ $condition_field,
+ $geometry_text
+ );
+ return $cell;
+ }
+
+ if ($_SESSION['tmpval']['geoOption'] == self::GEOMETRY_DISP_WKT) {
+ // Prepare in Well Known Text(WKT) format.
+ $where_comparison = ' = ' . $column;
+
+ // Convert to WKT format
+ $wktval = Util::asWKT($column);
+ list(
+ $is_field_truncated,
+ $displayedColumn,
+ // skip 3rd param
+ ) = $this->_getPartialText($wktval);
+
+ $cell = $this->_getRowData(
+ $class,
+ $condition_field,
+ $analyzed_sql_results,
+ $meta,
+ $map,
+ $wktval,
+ $displayedColumn,
+ $transformation_plugin,
+ $default_function,
+ '',
+ $where_comparison,
+ $transform_options,
+ $is_field_truncated,
+ ''
+ );
+ return $cell;
+ }
+
+ // Prepare in Well Known Binary (WKB) format.
+
+ if ($_SESSION['tmpval']['display_binary']) {
+ $where_comparison = ' = ' . $column;
+
+ $wkbval = substr(bin2hex($column), 8);
+ list(
+ $is_field_truncated,
+ $displayedColumn,
+ // skip 3rd param
+ ) = $this->_getPartialText($wkbval);
+
+ $cell = $this->_getRowData(
+ $class,
+ $condition_field,
+ $analyzed_sql_results,
+ $meta,
+ $map,
+ $wkbval,
+ $displayedColumn,
+ $transformation_plugin,
+ $default_function,
+ '',
+ $where_comparison,
+ $transform_options,
+ $is_field_truncated,
+ ''
+ );
+ return $cell;
+ }
+
+ $wkbval = $this->_handleNonPrintableContents(
+ self::BINARY_FIELD,
+ $column,
+ $transformation_plugin,
+ $transform_options,
+ $default_function,
+ $meta,
+ $_url_params
+ );
+
+ $cell = $this->_buildValueDisplay(
+ $class,
+ $condition_field,
+ $wkbval
+ );
+
+ return $cell;
+ }
+
+ /**
+ * Get data cell for non numeric type fields
+ *
+ * @param string|null $column the relevant column in data row
+ * @param string $class the html class for column
+ * @param stdClass $meta the meta-information about
+ * the field
+ * @param array $map the list of relations
+ * @param array $_url_params the parameters for generate
+ * url
+ * @param boolean $condition_field the column should highlighted
+ * or not
+ * @param TransformationsPlugin $transformation_plugin the name of transformation
+ * function
+ * @param string $default_function the default transformation
+ * function
+ * @param string $transform_options the transformation parameters
+ * @param boolean $is_field_truncated is data truncated due to
+ * LimitChars
+ * @param array $analyzed_sql_results the analyzed query
+ * @param integer $dt_result the link id associated to
+ * the query which results
+ * have to be displayed
+ * @param integer $col_index the column index
+ *
+ * @return string the prepared data cell, html content
+ *
+ * @access private
+ *
+ * @see _getTableBody()
+ */
+ private function _getDataCellForNonNumericColumns(
+ ?string $column,
+ $class,
+ $meta,
+ array $map,
+ array $_url_params,
+ $condition_field,
+ $transformation_plugin,
+ $default_function,
+ $transform_options,
+ $is_field_truncated,
+ array $analyzed_sql_results,
+ &$dt_result,
+ $col_index
+ ) {
+ $original_length = 0;
+
+ $is_analyse = $this->__get('is_analyse');
+ $field_flags = $GLOBALS['dbi']->fieldFlags($dt_result, $col_index);
+
+ $bIsText = gettype($transformation_plugin) === 'object'
+ && strpos($transformation_plugin->getMIMEType(), 'Text')
+ === false;
+
+ // disable inline grid editing
+ // if binary fields are protected
+ // or transformation plugin is of non text type
+ // such as image
+ if ((false !== stripos($field_flags, self::BINARY_FIELD)
+ && ($GLOBALS['cfg']['ProtectBinary'] === 'all'
+ || ($GLOBALS['cfg']['ProtectBinary'] === 'noblob'
+ && false === stripos($meta->type, self::BLOB_FIELD))
+ || ($GLOBALS['cfg']['ProtectBinary'] === 'blob'
+ && false !== stripos($meta->type, self::BLOB_FIELD))))
+ || $bIsText
+ ) {
+ $class = str_replace('grid_edit', '', $class);
+ }
+
+ if (! isset($column) || $column === null) {
+ $cell = $this->_buildNullDisplay($class, $condition_field, $meta);
+ return $cell;
+ }
+
+ if ($column == '') {
+ $cell = $this->_buildEmptyDisplay($class, $condition_field, $meta);
+ return $cell;
+ }
+
+ // Cut all fields to $GLOBALS['cfg']['LimitChars']
+ // (unless it's a link-type transformation or binary)
+ $displayedColumn = $column;
+ if (! (gettype($transformation_plugin) === "object"
+ && strpos($transformation_plugin->getName(), 'Link') !== false)
+ && false === stripos($field_flags, self::BINARY_FIELD)
+ ) {
+ list(
+ $is_field_truncated,
+ $column,
+ $original_length
+ ) = $this->_getPartialText($column);
+ }
+
+ $formatted = false;
+ if (isset($meta->_type) && $meta->_type === MYSQLI_TYPE_BIT) {
+ $displayedColumn = Util::printableBitValue(
+ (int) $displayedColumn,
+ (int) $meta->length
+ );
+
+ // some results of PROCEDURE ANALYSE() are reported as
+ // being BINARY but they are quite readable,
+ // so don't treat them as BINARY
+ } elseif (false !== stripos($field_flags, self::BINARY_FIELD)
+ && ! (isset($is_analyse) && $is_analyse)
+ ) {
+ // we show the BINARY or BLOB message and field's size
+ // (or maybe use a transformation)
+ $binary_or_blob = self::BLOB_FIELD;
+ if ($meta->type === self::STRING_FIELD) {
+ $binary_or_blob = self::BINARY_FIELD;
+ }
+ $displayedColumn = $this->_handleNonPrintableContents(
+ $binary_or_blob,
+ $displayedColumn,
+ $transformation_plugin,
+ $transform_options,
+ $default_function,
+ $meta,
+ $_url_params,
+ $is_field_truncated
+ );
+ $class = $this->_addClass(
+ $class,
+ $condition_field,
+ $meta,
+ '',
+ $is_field_truncated,
+ $transformation_plugin,
+ $default_function
+ );
+ $result = strip_tags($column);
+ // disable inline grid editing
+ // if binary or blob data is not shown
+ if (false !== stripos($result, $binary_or_blob)) {
+ $class = str_replace('grid_edit', '', $class);
+ }
+ $formatted = true;
+ }
+
+ if ($formatted) {
+ $cell = $this->_buildValueDisplay(
+ $class,
+ $condition_field,
+ $displayedColumn
+ );
+ return $cell;
+ }
+
+ // transform functions may enable no-wrapping:
+ $function_nowrap = 'applyTransformationNoWrap';
+
+ $bool_nowrap = ($default_function != $transformation_plugin)
+ && function_exists((string) $transformation_plugin->$function_nowrap())
+ ? $transformation_plugin->$function_nowrap($transform_options)
+ : false;
+
+ // do not wrap if date field type
+ $nowrap = preg_match('@DATE|TIME@i', $meta->type)
+ || $bool_nowrap ? ' nowrap' : '';
+
+ $where_comparison = ' = \''
+ . $GLOBALS['dbi']->escapeString($column)
+ . '\'';
+
+ $cell = $this->_getRowData(
+ $class,
+ $condition_field,
+ $analyzed_sql_results,
+ $meta,
+ $map,
+ $column,
+ $displayedColumn,
+ $transformation_plugin,
+ $default_function,
+ $nowrap,
+ $where_comparison,
+ $transform_options,
+ $is_field_truncated,
+ $original_length
+ );
+
+ return $cell;
+ }
+
+ /**
+ * Checks the posted options for viewing query results
+ * and sets appropriate values in the session.
+ *
+ * @todo make maximum remembered queries configurable
+ * @todo move/split into SQL class!?
+ * @todo currently this is called twice unnecessary
+ * @todo ignore LIMIT and ORDER in query!?
+ *
+ * @return void
+ *
+ * @access public
+ *
+ * @see sql.php file
+ */
+ public function setConfigParamsForDisplayTable()
+ {
+
+ $sql_md5 = md5($this->__get('sql_query'));
+ $query = [];
+ if (isset($_SESSION['tmpval']['query'][$sql_md5])) {
+ $query = $_SESSION['tmpval']['query'][$sql_md5];
+ }
+
+ $query['sql'] = $this->__get('sql_query');
+
+ if (empty($query['repeat_cells'])) {
+ $query['repeat_cells'] = $GLOBALS['cfg']['RepeatCells'];
+ }
+
+ // as this is a form value, the type is always string so we cannot
+ // use Core::isValid($_POST['session_max_rows'], 'integer')
+ if (Core::isValid($_POST['session_max_rows'], 'numeric')) {
+ $query['max_rows'] = (int) $_POST['session_max_rows'];
+ unset($_POST['session_max_rows']);
+ } elseif ($_POST['session_max_rows'] == self::ALL_ROWS) {
+ $query['max_rows'] = self::ALL_ROWS;
+ unset($_POST['session_max_rows']);
+ } elseif (empty($query['max_rows'])) {
+ $query['max_rows'] = intval($GLOBALS['cfg']['MaxRows']);
+ }
+
+ if (Core::isValid($_REQUEST['pos'], 'numeric')) {
+ $query['pos'] = $_REQUEST['pos'];
+ unset($_REQUEST['pos']);
+ } elseif (empty($query['pos'])) {
+ $query['pos'] = 0;
+ }
+
+ if (Core::isValid(
+ $_REQUEST['pftext'],
+ [
+ self::DISPLAY_PARTIAL_TEXT,
+ self::DISPLAY_FULL_TEXT,
+ ]
+ )
+ ) {
+ $query['pftext'] = $_REQUEST['pftext'];
+ unset($_REQUEST['pftext']);
+ } elseif (empty($query['pftext'])) {
+ $query['pftext'] = self::DISPLAY_PARTIAL_TEXT;
+ }
+
+ if (Core::isValid(
+ $_REQUEST['relational_display'],
+ [
+ self::RELATIONAL_KEY,
+ self::RELATIONAL_DISPLAY_COLUMN,
+ ]
+ )
+ ) {
+ $query['relational_display'] = $_REQUEST['relational_display'];
+ unset($_REQUEST['relational_display']);
+ } elseif (empty($query['relational_display'])) {
+ // The current session value has priority over a
+ // change via Settings; this change will be apparent
+ // starting from the next session
+ $query['relational_display'] = $GLOBALS['cfg']['RelationalDisplay'];
+ }
+
+ if (Core::isValid(
+ $_REQUEST['geoOption'],
+ [
+ self::GEOMETRY_DISP_WKT,
+ self::GEOMETRY_DISP_WKB,
+ self::GEOMETRY_DISP_GEOM,
+ ]
+ )
+ ) {
+ $query['geoOption'] = $_REQUEST['geoOption'];
+ unset($_REQUEST['geoOption']);
+ } elseif (empty($query['geoOption'])) {
+ $query['geoOption'] = self::GEOMETRY_DISP_GEOM;
+ }
+
+ if (isset($_REQUEST['display_binary'])) {
+ $query['display_binary'] = true;
+ unset($_REQUEST['display_binary']);
+ } elseif (isset($_REQUEST['display_options_form'])) {
+ // we know that the checkbox was unchecked
+ unset($query['display_binary']);
+ } elseif (! isset($_REQUEST['full_text_button'])) {
+ // selected by default because some operations like OPTIMIZE TABLE
+ // and all queries involving functions return "binary" contents,
+ // according to low-level field flags
+ $query['display_binary'] = true;
+ }
+
+ if (isset($_REQUEST['display_blob'])) {
+ $query['display_blob'] = true;
+ unset($_REQUEST['display_blob']);
+ } elseif (isset($_REQUEST['display_options_form'])) {
+ // we know that the checkbox was unchecked
+ unset($query['display_blob']);
+ }
+
+ if (isset($_REQUEST['hide_transformation'])) {
+ $query['hide_transformation'] = true;
+ unset($_REQUEST['hide_transformation']);
+ } elseif (isset($_REQUEST['display_options_form'])) {
+ // we know that the checkbox was unchecked
+ unset($query['hide_transformation']);
+ }
+
+ // move current query to the last position, to be removed last
+ // so only least executed query will be removed if maximum remembered
+ // queries limit is reached
+ unset($_SESSION['tmpval']['query'][$sql_md5]);
+ $_SESSION['tmpval']['query'][$sql_md5] = $query;
+
+ // do not exceed a maximum number of queries to remember
+ if (count($_SESSION['tmpval']['query']) > 10) {
+ array_shift($_SESSION['tmpval']['query']);
+ //echo 'deleting one element ...';
+ }
+
+ // populate query configuration
+ $_SESSION['tmpval']['pftext']
+ = $query['pftext'];
+ $_SESSION['tmpval']['relational_display']
+ = $query['relational_display'];
+ $_SESSION['tmpval']['geoOption']
+ = $query['geoOption'];
+ $_SESSION['tmpval']['display_binary'] = isset(
+ $query['display_binary']
+ );
+ $_SESSION['tmpval']['display_blob'] = isset(
+ $query['display_blob']
+ );
+ $_SESSION['tmpval']['hide_transformation'] = isset(
+ $query['hide_transformation']
+ );
+ $_SESSION['tmpval']['pos']
+ = $query['pos'];
+ $_SESSION['tmpval']['max_rows']
+ = $query['max_rows'];
+ $_SESSION['tmpval']['repeat_cells']
+ = $query['repeat_cells'];
+ }
+
+ /**
+ * Prepare a table of results returned by a SQL query.
+ *
+ * @param integer $dt_result the link id associated to the query
+ * which results have to be displayed
+ * @param array $displayParts the parts to display
+ * @param array $analyzed_sql_results analyzed sql results
+ * @param boolean $is_limited_display With limited operations or not
+ *
+ * @return string Generated HTML content for resulted table
+ *
+ * @access public
+ *
+ * @see sql.php file
+ */
+ public function getTable(
+ &$dt_result,
+ array &$displayParts,
+ array $analyzed_sql_results,
+ $is_limited_display = false
+ ) {
+ /**
+ * The statement this table is built for.
+ * @var SelectStatement
+ */
+ if (isset($analyzed_sql_results['statement'])) {
+ $statement = $analyzed_sql_results['statement'];
+ } else {
+ $statement = null;
+ }
+
+ // Following variable are needed for use in isset/empty or
+ // use with array indexes/safe use in foreach
+ $fields_meta = $this->__get('fields_meta');
+ $showtable = $this->__get('showtable');
+ $printview = $this->__get('printview');
+
+ /**
+ * @todo move this to a central place
+ * @todo for other future table types
+ */
+ $is_innodb = (isset($showtable['Type'])
+ && $showtable['Type'] == self::TABLE_TYPE_INNO_DB);
+
+ $sql = new Sql();
+ if ($is_innodb && $sql->isJustBrowsing($analyzed_sql_results, true)) {
+ $pre_count = '~';
+ $after_count = Util::showHint(
+ Sanitize::sanitizeMessage(
+ __('May be approximate. See [doc@faq3-11]FAQ 3.11[/doc].')
+ )
+ );
+ } else {
+ $pre_count = '';
+ $after_count = '';
+ }
+
+ // 1. ----- Prepares the work -----
+
+ // 1.1 Gets the information about which functionalities should be
+ // displayed
+
+ list(
+ $displayParts,
+ $total
+ ) = $this->_setDisplayPartsAndTotal($displayParts);
+
+ // 1.2 Defines offsets for the next and previous pages
+ if ($displayParts['nav_bar'] == '1') {
+ list($pos_next, $pos_prev) = $this->_getOffsets();
+ } // end if
+
+ // 1.3 Extract sorting expressions.
+ // we need $sort_expression and $sort_expression_nodirection
+ // even if there are many table references
+ $sort_expression = [];
+ $sort_expression_nodirection = [];
+ $sort_direction = [];
+
+ if ($statement !== null && ! empty($statement->order)) {
+ foreach ($statement->order as $o) {
+ $sort_expression[] = $o->expr->expr . ' ' . $o->type;
+ $sort_expression_nodirection[] = $o->expr->expr;
+ $sort_direction[] = $o->type;
+ }
+ } else {
+ $sort_expression[] = '';
+ $sort_expression_nodirection[] = '';
+ $sort_direction[] = '';
+ }
+
+ $number_of_columns = count($sort_expression_nodirection);
+
+ // 1.4 Prepares display of first and last value of the sorted column
+ $sorted_column_message = '';
+ for ($i = 0; $i < $number_of_columns; $i++) {
+ $sorted_column_message .= $this->_getSortedColumnMessage(
+ $dt_result,
+ $sort_expression_nodirection[$i]
+ );
+ }
+
+ // 2. ----- Prepare to display the top of the page -----
+
+ // 2.1 Prepares a messages with position information
+ $sqlQueryMessage = '';
+ if (($displayParts['nav_bar'] == '1') && isset($pos_next)) {
+ $message = $this->_setMessageInformation(
+ $sorted_column_message,
+ $analyzed_sql_results,
+ $total,
+ $pos_next,
+ $pre_count,
+ $after_count
+ );
+
+ $sqlQueryMessage = Util::getMessage(
+ $message,
+ $this->__get('sql_query'),
+ 'success'
+ );
+ } elseif ((! isset($printview) || ($printview != '1')) && ! $is_limited_display) {
+ $sqlQueryMessage = Util::getMessage(
+ __('Your SQL query has been executed successfully.'),
+ $this->__get('sql_query'),
+ 'success'
+ );
+ }
+
+ // 2.3 Prepare the navigation bars
+ if (strlen($this->__get('table')) === 0) {
+ if ($analyzed_sql_results['querytype'] == 'SELECT') {
+ // table does not always contain a real table name,
+ // for example in MySQL 5.0.x, the query SHOW STATUS
+ // returns STATUS as a table name
+ $this->__set('table', $fields_meta[0]->table);
+ } else {
+ $this->__set('table', '');
+ }
+ }
+
+ // can the result be sorted?
+ if ($displayParts['sort_lnk'] == '1' && $analyzed_sql_results['statement'] !== null) {
+ // At this point, $sort_expression is an array
+ list($unsorted_sql_query, $sort_by_key_html)
+ = $this->_getUnsortedSqlAndSortByKeyDropDown(
+ $analyzed_sql_results,
+ $sort_expression
+ );
+ } else {
+ $sort_by_key_html = $unsorted_sql_query = '';
+ }
+
+ $navigation = '';
+ if ($displayParts['nav_bar'] == '1' && $statement !== null && empty($statement->limit)) {
+ $navigation = $this->_getTableNavigation(
+ $pos_next,
+ $pos_prev,
+ $is_innodb,
+ $sort_by_key_html
+ );
+ }
+
+ // 2b ----- Get field references from Database -----
+ // (see the 'relation' configuration variable)
+
+ // initialize map
+ $map = [];
+
+ if (strlen($this->__get('table')) > 0) {
+ // This method set the values for $map array
+ $this->_setParamForLinkForeignKeyRelatedTables($map);
+
+ // Coming from 'Distinct values' action of structure page
+ // We manipulate relations mechanism to show a link to related rows.
+ if ($this->__get('is_browse_distinct')) {
+ $map[$fields_meta[1]->name] = [
+ $this->__get('table'),
+ $fields_meta[1]->name,
+ '',
+ $this->__get('db'),
+ ];
+ }
+ } // end if
+ // end 2b
+
+ // 3. ----- Prepare the results table -----
+ $headers = $this->_getTableHeaders(
+ $displayParts,
+ $analyzed_sql_results,
+ $unsorted_sql_query,
+ $sort_expression,
+ $sort_expression_nodirection,
+ $sort_direction,
+ $is_limited_display
+ );
+
+ $body = $this->_getTableBody(
+ $dt_result,
+ $displayParts,
+ $map,
+ $analyzed_sql_results,
+ $is_limited_display
+ );
+
+ $this->__set('display_params', null);
+
+ // 4. ----- Prepares the link for multi-fields edit and delete
+ $multiRowOperationLinks = '';
+ if ($displayParts['del_lnk'] == self::DELETE_ROW
+ && $displayParts['del_lnk'] != self::KILL_PROCESS
+ ) {
+ $multiRowOperationLinks = $this->_getMultiRowOperationLinks(
+ $dt_result,
+ $analyzed_sql_results,
+ $displayParts['del_lnk']
+ );
+ }
+
+ // 5. ----- Prepare "Query results operations"
+ $operations = '';
+ if ((! isset($printview) || ($printview != '1')) && ! $is_limited_display) {
+ $operations = $this->_getResultsOperations(
+ $displayParts,
+ $analyzed_sql_results
+ );
+ }
+
+ return $this->template->render('display/results/table', [
+ 'sql_query_message' => $sqlQueryMessage,
+ 'navigation' => $navigation,
+ 'headers' => $headers,
+ 'body' => $body,
+ 'multi_row_operation_links' => $multiRowOperationLinks,
+ 'operations' => $operations,
+ ]);
+ }
+
+ /**
+ * Get offsets for next page and previous page
+ *
+ * @return array array with two elements - $pos_next, $pos_prev
+ *
+ * @access private
+ *
+ * @see getTable()
+ */
+ private function _getOffsets()
+ {
+
+ if ($_SESSION['tmpval']['max_rows'] == self::ALL_ROWS) {
+ $pos_next = 0;
+ $pos_prev = 0;
+ } else {
+ $pos_next = $_SESSION['tmpval']['pos']
+ + $_SESSION['tmpval']['max_rows'];
+
+ $pos_prev = $_SESSION['tmpval']['pos']
+ - $_SESSION['tmpval']['max_rows'];
+
+ if ($pos_prev < 0) {
+ $pos_prev = 0;
+ }
+ }
+
+ return [
+ $pos_next,
+ $pos_prev,
+ ];
+ }
+
+ /**
+ * Prepare sorted column message
+ *
+ * @param integer $dt_result the link id associated to the
+ * query which results have to
+ * be displayed
+ * @param string $sort_expression_nodirection sort expression without direction
+ *
+ * @return string|null html content, null if not found sorted column
+ *
+ * @access private
+ *
+ * @see getTable()
+ */
+ private function _getSortedColumnMessage(
+ &$dt_result,
+ $sort_expression_nodirection
+ ) {
+ $fields_meta = $this->__get('fields_meta'); // To use array indexes
+
+ if (empty($sort_expression_nodirection)) {
+ return null;
+ }
+
+ if (mb_strpos($sort_expression_nodirection, '.') === false) {
+ $sort_table = $this->__get('table');
+ $sort_column = $sort_expression_nodirection;
+ } else {
+ list($sort_table, $sort_column)
+ = explode('.', $sort_expression_nodirection);
+ }
+
+ $sort_table = Util::unQuote($sort_table);
+ $sort_column = Util::unQuote($sort_column);
+
+ // find the sorted column index in row result
+ // (this might be a multi-table query)
+ $sorted_column_index = false;
+
+ foreach ($fields_meta as $key => $meta) {
+ if (($meta->table == $sort_table) && ($meta->name == $sort_column)) {
+ $sorted_column_index = $key;
+ break;
+ }
+ }
+
+ if ($sorted_column_index === false) {
+ return null;
+ }
+
+ // fetch first row of the result set
+ $row = $GLOBALS['dbi']->fetchRow($dt_result);
+
+ // initializing default arguments
+ $default_function = [
+ Core::class,
+ 'mimeDefaultFunction',
+ ];
+ $transformation_plugin = $default_function;
+ $transform_options = [];
+
+ // check for non printable sorted row data
+ $meta = $fields_meta[$sorted_column_index];
+
+ if (false !== stripos($meta->type, self::BLOB_FIELD)
+ || ($meta->type == self::GEOMETRY_FIELD)
+ ) {
+ $column_for_first_row = $this->_handleNonPrintableContents(
+ $meta->type,
+ $row[$sorted_column_index],
+ $transformation_plugin,
+ $transform_options,
+ $default_function,
+ $meta
+ );
+ } else {
+ $column_for_first_row = $row[$sorted_column_index];
+ }
+
+ $column_for_first_row = mb_strtoupper(
+ mb_substr(
+ (string) $column_for_first_row,
+ 0,
+ (int) $GLOBALS['cfg']['LimitChars']
+ ) . '...'
+ );
+
+ // fetch last row of the result set
+ $GLOBALS['dbi']->dataSeek($dt_result, $this->__get('num_rows') - 1);
+ $row = $GLOBALS['dbi']->fetchRow($dt_result);
+
+ // check for non printable sorted row data
+ $meta = $fields_meta[$sorted_column_index];
+ if (false !== stripos($meta->type, self::BLOB_FIELD)
+ || ($meta->type == self::GEOMETRY_FIELD)
+ ) {
+ $column_for_last_row = $this->_handleNonPrintableContents(
+ $meta->type,
+ $row[$sorted_column_index],
+ $transformation_plugin,
+ $transform_options,
+ $default_function,
+ $meta
+ );
+ } else {
+ $column_for_last_row = $row[$sorted_column_index];
+ }
+
+ $column_for_last_row = mb_strtoupper(
+ mb_substr(
+ (string) $column_for_last_row,
+ 0,
+ (int) $GLOBALS['cfg']['LimitChars']
+ ) . '...'
+ );
+
+ // reset to first row for the loop in _getTableBody()
+ $GLOBALS['dbi']->dataSeek($dt_result, 0);
+
+ // we could also use here $sort_expression_nodirection
+ return ' [' . htmlspecialchars($sort_column)
+ . ': <strong>' . htmlspecialchars($column_for_first_row) . ' - '
+ . htmlspecialchars($column_for_last_row) . '</strong>]';
+ }
+
+ /**
+ * Set the content that needs to be shown in message
+ *
+ * @param string $sorted_column_message the message for sorted column
+ * @param array $analyzed_sql_results the analyzed query
+ * @param integer $total the total number of rows returned by
+ * the SQL query without any
+ * programmatically appended LIMIT clause
+ * @param integer $pos_next the offset for next page
+ * @param string $pre_count the string renders before row count
+ * @param string $after_count the string renders after row count
+ *
+ * @return Message an object of Message
+ *
+ * @access private
+ *
+ * @see getTable()
+ */
+ private function _setMessageInformation(
+ $sorted_column_message,
+ array $analyzed_sql_results,
+ $total,
+ $pos_next,
+ $pre_count,
+ $after_count
+ ) {
+
+ $unlim_num_rows = $this->__get('unlim_num_rows'); // To use in isset()
+
+ if (! empty($analyzed_sql_results['statement']->limit)) {
+ $first_shown_rec = $analyzed_sql_results['statement']->limit->offset;
+ $row_count = $analyzed_sql_results['statement']->limit->rowCount;
+
+ if ($row_count < $total) {
+ $last_shown_rec = $first_shown_rec + $row_count - 1;
+ } else {
+ $last_shown_rec = $first_shown_rec + $total - 1;
+ }
+ } elseif (($_SESSION['tmpval']['max_rows'] == self::ALL_ROWS)
+ || ($pos_next > $total)
+ ) {
+ $first_shown_rec = $_SESSION['tmpval']['pos'];
+ $last_shown_rec = $total - 1;
+ } else {
+ $first_shown_rec = $_SESSION['tmpval']['pos'];
+ $last_shown_rec = $pos_next - 1;
+ }
+
+ $table = new Table($this->__get('table'), $this->__get('db'));
+ if ($table->isView()
+ && ($total == $GLOBALS['cfg']['MaxExactCountViews'])
+ ) {
+ $message = Message::notice(
+ __(
+ 'This view has at least this number of rows. '
+ . 'Please refer to %sdocumentation%s.'
+ )
+ );
+
+ $message->addParam('[doc@cfg_MaxExactCount]');
+ $message->addParam('[/doc]');
+ $message_view_warning = Util::showHint($message);
+ } else {
+ $message_view_warning = false;
+ }
+
+ $message = Message::success(__('Showing rows %1s - %2s'));
+ $message->addParam($first_shown_rec);
+
+ if ($message_view_warning !== false) {
+ $message->addParamHtml('... ' . $message_view_warning);
+ } else {
+ $message->addParam($last_shown_rec);
+ }
+
+ $message->addText('(');
+
+ if ($message_view_warning === false) {
+ if (isset($unlim_num_rows) && ($unlim_num_rows != $total)) {
+ $message_total = Message::notice(
+ $pre_count . __('%1$d total, %2$d in query')
+ );
+ $message_total->addParam($total);
+ $message_total->addParam($unlim_num_rows);
+ } else {
+ $message_total = Message::notice($pre_count . __('%d total'));
+ $message_total->addParam($total);
+ }
+
+ if (! empty($after_count)) {
+ $message_total->addHtml($after_count);
+ }
+ $message->addMessage($message_total, '');
+
+ $message->addText(', ', '');
+ }
+
+ $message_qt = Message::notice(__('Query took %01.4f seconds.') . ')');
+ $message_qt->addParam($this->__get('querytime'));
+
+ $message->addMessage($message_qt, '');
+ if ($sorted_column_message !== null) {
+ $message->addHtml($sorted_column_message, '');
+ }
+
+ return $message;
+ }
+
+ /**
+ * Set the value of $map array for linking foreign key related tables
+ *
+ * @param array $map the list of relations
+ *
+ * @return void
+ *
+ * @access private
+ *
+ * @see getTable()
+ */
+ private function _setParamForLinkForeignKeyRelatedTables(array &$map)
+ {
+ // To be able to later display a link to the related table,
+ // we verify both types of relations: either those that are
+ // native foreign keys or those defined in the phpMyAdmin
+ // configuration storage. If no PMA storage, we won't be able
+ // to use the "column to display" notion (for example show
+ // the name related to a numeric id).
+ $exist_rel = $this->relation->getForeigners(
+ $this->__get('db'),
+ $this->__get('table'),
+ '',
+ self::POSITION_BOTH
+ );
+
+ if (! empty($exist_rel)) {
+ foreach ($exist_rel as $master_field => $rel) {
+ if ($master_field != 'foreign_keys_data') {
+ $display_field = $this->relation->getDisplayField(
+ $rel['foreign_db'],
+ $rel['foreign_table']
+ );
+ $map[$master_field] = [
+ $rel['foreign_table'],
+ $rel['foreign_field'],
+ $display_field,
+ $rel['foreign_db'],
+ ];
+ } else {
+ foreach ($rel as $key => $one_key) {
+ foreach ($one_key['index_list'] as $index => $one_field) {
+ $display_field = $this->relation->getDisplayField(
+ isset($one_key['ref_db_name'])
+ ? $one_key['ref_db_name']
+ : $GLOBALS['db'],
+ $one_key['ref_table_name']
+ );
+
+ $map[$one_field] = [
+ $one_key['ref_table_name'],
+ $one_key['ref_index_list'][$index],
+ $display_field,
+ isset($one_key['ref_db_name'])
+ ? $one_key['ref_db_name']
+ : $GLOBALS['db'],
+ ];
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Prepare multi field edit/delete links
+ *
+ * @param integer $dt_result the link id associated to the query which
+ * results have to be displayed
+ * @param array $analyzed_sql_results analyzed sql results
+ * @param string $del_link the display element - 'del_link'
+ *
+ * @return string html content
+ *
+ * @access private
+ *
+ * @see getTable()
+ */
+ private function _getMultiRowOperationLinks(
+ &$dt_result,
+ array $analyzed_sql_results,
+ $del_link
+ ) {
+ $links_html = '<div class="print_ignore" >';
+ $url_query = $this->__get('url_query');
+ $delete_text = $del_link == self::DELETE_ROW ? __('Delete') : __('Kill');
+
+ $links_html .= $this->template->render('select_all', [
+ 'pma_theme_image' => $this->__get('pma_theme_image'),
+ 'text_dir' => $this->__get('text_dir'),
+ 'form_name' => 'resultsForm_' . $this->__get('unique_id'),
+ ]);
+
+ $links_html .= Util::getButtonOrImage(
+ 'submit_mult',
+ 'mult_submit',
+ __('Edit'),
+ 'b_edit',
+ 'edit'
+ );
+
+ $links_html .= Util::getButtonOrImage(
+ 'submit_mult',
+ 'mult_submit',
+ __('Copy'),
+ 'b_insrow',
+ 'copy'
+ );
+
+ $links_html .= Util::getButtonOrImage(
+ 'submit_mult',
+ 'mult_submit',
+ $delete_text,
+ 'b_drop',
+ 'delete'
+ );
+
+ if ($analyzed_sql_results['querytype'] == 'SELECT') {
+ $links_html .= Util::getButtonOrImage(
+ 'submit_mult',
+ 'mult_submit',
+ __('Export'),
+ 'b_tblexport',
+ 'export'
+ );
+ }
+
+ $links_html .= "</div>\n";
+
+ $links_html .= '<input type="hidden" name="sql_query"'
+ . ' value="' . htmlspecialchars($this->__get('sql_query')) . '">'
+ . "\n";
+
+ if (! empty($url_query)) {
+ $links_html .= '<input type="hidden" name="url_query"'
+ . ' value="' . $url_query . '">' . "\n";
+ }
+
+ // fetch last row of the result set
+ $GLOBALS['dbi']->dataSeek($dt_result, $this->__get('num_rows') - 1);
+ $row = $GLOBALS['dbi']->fetchRow($dt_result);
+
+ // @see DbiMysqi::fetchRow & DatabaseInterface::fetchRow
+ if (! is_array($row)) {
+ $row = [];
+ }
+
+ // $clause_is_unique is needed by getTable() to generate the proper param
+ // in the multi-edit and multi-delete form
+ list($where_clause, $clause_is_unique, $condition_array)
+ = Util::getUniqueCondition(
+ $dt_result, // handle
+ $this->__get('fields_cnt'), // fields_cnt
+ $this->__get('fields_meta'), // fields_meta
+ $row, // row
+ false, // force_unique
+ false, // restrict_to_table
+ $analyzed_sql_results // analyzed_sql_results
+ );
+ unset($where_clause, $condition_array);
+
+ // reset to first row for the loop in _getTableBody()
+ $GLOBALS['dbi']->dataSeek($dt_result, 0);
+
+ $links_html .= '<input type="hidden" name="clause_is_unique"'
+ . ' value="' . $clause_is_unique . '">' . "\n";
+
+ $links_html .= '</form>' . "\n";
+
+ return $links_html;
+ }
+
+ /**
+ * Generates HTML to display the Create view in span tag
+ *
+ * @param array $analyzed_sql_results analyzed sql results
+ * @param string $url_query String with URL Parameters
+ *
+ * @return string
+ *
+ * @access private
+ *
+ * @see _getResultsOperations()
+ */
+ private function _getLinkForCreateView(array $analyzed_sql_results, $url_query)
+ {
+ $results_operations_html = '';
+ if (empty($analyzed_sql_results['procedure'])) {
+ $results_operations_html .= '<span>'
+ . Util::linkOrButton(
+ 'view_create.php' . $url_query,
+ Util::getIcon(
+ 'b_view_add',
+ __('Create view'),
+ true
+ ),
+ ['class' => 'create_view ajax']
+ )
+ . '</span>' . "\n";
+ }
+ return $results_operations_html;
+ }
+
+ /**
+ * Calls the _getResultsOperations with $only_view as true
+ *
+ * @param array $analyzed_sql_results analyzed sql results
+ *
+ * @return string
+ *
+ * @access public
+ *
+ */
+ public function getCreateViewQueryResultOp(array $analyzed_sql_results)
+ {
+ $results_operations_html = '';
+ //calling to _getResultOperations with a fake $displayParts
+ //and setting only_view parameter to be true to generate just view
+ $results_operations_html .= $this->_getResultsOperations(
+ [],
+ $analyzed_sql_results,
+ true
+ );
+ return $results_operations_html;
+ }
+
+ /**
+ * Get copy to clipboard links for results operations
+ *
+ * @return string
+ *
+ * @access private
+ */
+ private function _getCopytoclipboardLinks()
+ {
+ return Util::linkOrButton(
+ '#',
+ Util::getIcon(
+ 'b_insrow',
+ __('Copy to clipboard'),
+ true
+ ),
+ ['id' => 'copyToClipBoard']
+ );
+ }
+
+ /**
+ * Get printview links for results operations
+ *
+ * @return string
+ *
+ * @access private
+ */
+ private function _getPrintviewLinks()
+ {
+ return Util::linkOrButton(
+ '#',
+ Util::getIcon(
+ 'b_print',
+ __('Print'),
+ true
+ ),
+ ['id' => 'printView'],
+ 'print_view'
+ );
+ }
+
+ /**
+ * Get operations that are available on results.
+ *
+ * @param array $displayParts the parts to display
+ * @param array $analyzed_sql_results analyzed sql results
+ * @param boolean $only_view Whether to show only view
+ *
+ * @return string html content
+ *
+ * @access private
+ *
+ * @see getTable()
+ */
+ private function _getResultsOperations(
+ array $displayParts,
+ array $analyzed_sql_results,
+ $only_view = false
+ ) {
+ global $printview;
+
+ $results_operations_html = '';
+ $fields_meta = $this->__get('fields_meta'); // To safe use in foreach
+ $header_shown = false;
+ $header = '<fieldset class="print_ignore" ><legend>'
+ . __('Query results operations') . '</legend>';
+
+ $_url_params = [
+ 'db' => $this->__get('db'),
+ 'table' => $this->__get('table'),
+ 'printview' => '1',
+ 'sql_query' => $this->__get('sql_query'),
+ ];
+ $url_query = Url::getCommon($_url_params);
+
+ if (! $header_shown) {
+ $results_operations_html .= $header;
+ $header_shown = true;
+ }
+ // if empty result set was produced we need to
+ // show only view and not other options
+ if ($only_view) {
+ $results_operations_html .= $this->_getLinkForCreateView(
+ $analyzed_sql_results,
+ $url_query
+ );
+
+ if ($header_shown) {
+ $results_operations_html .= '</fieldset><br>';
+ }
+ return $results_operations_html;
+ }
+
+ // Displays "printable view" link if required
+ if ($displayParts['pview_lnk'] == '1') {
+ $results_operations_html .= $this->_getPrintviewLinks();
+ $results_operations_html .= $this->_getCopytoclipboardLinks();
+ } // end displays "printable view"
+
+ // Export link
+ // (the url_query has extra parameters that won't be used to export)
+ // (the single_table parameter is used in \PhpMyAdmin\Export->getDisplay()
+ // to hide the SQL and the structure export dialogs)
+ // If the parser found a PROCEDURE clause
+ // (most probably PROCEDURE ANALYSE()) it makes no sense to
+ // display the Export link).
+ if (($analyzed_sql_results['querytype'] == self::QUERY_TYPE_SELECT)
+ && ! isset($printview)
+ && empty($analyzed_sql_results['procedure'])
+ ) {
+ if (count($analyzed_sql_results['select_tables']) === 1) {
+ $_url_params['single_table'] = 'true';
+ }
+
+ if (! $header_shown) {
+ $results_operations_html .= $header;
+ $header_shown = true;
+ }
+
+ $_url_params['unlim_num_rows'] = $this->__get('unlim_num_rows');
+
+ /**
+ * At this point we don't know the table name; this can happen
+ * for example with a query like
+ * SELECT bike_code FROM (SELECT bike_code FROM bikes) tmp
+ * As a workaround we set in the table parameter the name of the
+ * first table of this database, so that tbl_export.php and
+ * the script it calls do not fail
+ */
+ if (empty($_url_params['table']) && ! empty($_url_params['db'])) {
+ $_url_params['table'] = $GLOBALS['dbi']->fetchValue("SHOW TABLES");
+ /* No result (probably no database selected) */
+ if ($_url_params['table'] === false) {
+ unset($_url_params['table']);
+ }
+ }
+
+ $results_operations_html .= Util::linkOrButton(
+ 'tbl_export.php' . Url::getCommon($_url_params),
+ Util::getIcon(
+ 'b_tblexport',
+ __('Export'),
+ true
+ )
+ )
+ . "\n";
+
+ // prepare chart
+ $results_operations_html .= Util::linkOrButton(
+ 'tbl_chart.php' . Url::getCommon($_url_params),
+ Util::getIcon(
+ 'b_chart',
+ __('Display chart'),
+ true
+ )
+ )
+ . "\n";
+
+ // prepare GIS chart
+ $geometry_found = false;
+ // If at least one geometry field is found
+ foreach ($fields_meta as $meta) {
+ if ($meta->type == self::GEOMETRY_FIELD) {
+ $geometry_found = true;
+ break;
+ }
+ }
+
+ if ($geometry_found) {
+ $results_operations_html
+ .= Util::linkOrButton(
+ 'tbl_gis_visualization.php'
+ . Url::getCommon($_url_params),
+ Util::getIcon(
+ 'b_globe',
+ __('Visualize GIS data'),
+ true
+ )
+ )
+ . "\n";
+ }
+ }
+
+ // CREATE VIEW
+ /**
+ *
+ * @todo detect privileges to create a view
+ * (but see 2006-01-19 note in PhpMyAdmin\Display\CreateTable,
+ * I think we cannot detect db-specific privileges reliably)
+ * Note: we don't display a Create view link if we found a PROCEDURE clause
+ */
+ if (! $header_shown) {
+ $results_operations_html .= $header;
+ $header_shown = true;
+ }
+
+ $results_operations_html .= $this->_getLinkForCreateView(
+ $analyzed_sql_results,
+ $url_query
+ );
+
+ if ($header_shown) {
+ $results_operations_html .= '</fieldset><br>';
+ }
+
+ return $results_operations_html;
+ }
+
+ /**
+ * Verifies what to do with non-printable contents (binary or BLOB)
+ * in Browse mode.
+ *
+ * @param string $category BLOB|BINARY|GEOMETRY
+ * @param string|null $content the binary content
+ * @param mixed $transformation_plugin transformation plugin.
+ * Can also be the
+ * default function:
+ * Core::mimeDefaultFunction
+ * @param string $transform_options transformation parameters
+ * @param string $default_function default transformation function
+ * @param stdClass $meta the meta-information about the field
+ * @param array $url_params parameters that should go to the
+ * download link
+ * @param boolean $is_truncated the result is truncated or not
+ *
+ * @return mixed string or float
+ *
+ * @access private
+ *
+ * @see _getDataCellForGeometryColumns(),
+ * _getDataCellForNonNumericColumns(),
+ * _getSortedColumnMessage()
+ */
+ private function _handleNonPrintableContents(
+ $category,
+ ?string $content,
+ $transformation_plugin,
+ $transform_options,
+ $default_function,
+ $meta,
+ array $url_params = [],
+ &$is_truncated = null
+ ) {
+ $is_truncated = false;
+ $result = '[' . $category;
+
+ if (isset($content)) {
+ $size = strlen($content);
+ $display_size = Util::formatByteDown($size, 3, 1);
+ $result .= ' - ' . $display_size[0] . ' ' . $display_size[1];
+ } else {
+ $result .= ' - NULL';
+ $size = 0;
+ }
+
+ $result .= ']';
+
+ // if we want to use a text transformation on a BLOB column
+ if (gettype($transformation_plugin) === "object") {
+ $posMimeOctetstream = strpos(
+ $transformation_plugin->getMIMESubtype(),
+ 'Octetstream'
+ );
+ $posMimeText = strpos($transformation_plugin->getMIMEtype(), 'Text');
+ if ($posMimeOctetstream
+ || $posMimeText !== false
+ ) {
+ // Applying Transformations on hex string of binary data
+ // seems more appropriate
+ $result = pack("H*", bin2hex($content));
+ }
+ }
+
+ if ($size <= 0) {
+ return $result;
+ }
+
+ if ($default_function != $transformation_plugin) {
+ $result = $transformation_plugin->applyTransformation(
+ $result,
+ $transform_options,
+ $meta
+ );
+ return $result;
+ }
+
+ $result = $default_function($result, [], $meta);
+ if (($_SESSION['tmpval']['display_binary']
+ && $meta->type === self::STRING_FIELD)
+ || ($_SESSION['tmpval']['display_blob']
+ && false !== stripos($meta->type, self::BLOB_FIELD))
+ ) {
+ // in this case, restart from the original $content
+ if (mb_check_encoding($content, 'utf-8')
+ && ! preg_match('/[\x00-\x08\x0B\x0C\x0E-\x1F\x80-\x9F]/u', $content)
+ ) {
+ // show as text if it's valid utf-8
+ $result = htmlspecialchars($content);
+ } else {
+ $result = '0x' . bin2hex($content);
+ }
+ list(
+ $is_truncated,
+ $result,
+ // skip 3rd param
+ ) = $this->_getPartialText($result);
+ }
+
+ /* Create link to download */
+
+ // in PHP < 5.5, empty() only checks variables
+ $tmpdb = $this->__get('db');
+ if (count($url_params) > 0
+ && (! empty($tmpdb) && ! empty($meta->orgtable))
+ ) {
+ $result = '<a href="tbl_get_field.php'
+ . Url::getCommon($url_params)
+ . '" class="disableAjax">'
+ . $result . '</a>';
+ }
+
+ return $result;
+ }
+
+ /**
+ * Retrieves the associated foreign key info for a data cell
+ *
+ * @param array $map the list of relations
+ * @param stdClass $meta the meta-information about the field
+ * @param string $where_comparison data for the where clause
+ *
+ * @return string formatted data
+ *
+ * @access private
+ *
+ */
+ private function _getFromForeign(array $map, $meta, $where_comparison)
+ {
+ $dispsql = 'SELECT '
+ . Util::backquote($map[$meta->name][2])
+ . ' FROM '
+ . Util::backquote($map[$meta->name][3])
+ . '.'
+ . Util::backquote($map[$meta->name][0])
+ . ' WHERE '
+ . Util::backquote($map[$meta->name][1])
+ . $where_comparison;
+
+ $dispresult = $GLOBALS['dbi']->tryQuery(
+ $dispsql,
+ DatabaseInterface::CONNECT_USER,
+ DatabaseInterface::QUERY_STORE
+ );
+
+ if ($dispresult && $GLOBALS['dbi']->numRows($dispresult) > 0) {
+ list($dispval) = $GLOBALS['dbi']->fetchRow($dispresult, 0);
+ } else {
+ $dispval = __('Link not found!');
+ }
+
+ $GLOBALS['dbi']->freeResult($dispresult);
+
+ return $dispval;
+ }
+
+ /**
+ * Prepares the displayable content of a data cell in Browse mode,
+ * taking into account foreign key description field and transformations
+ *
+ * @param string $class css classes for the td element
+ * @param bool $condition_field whether the column is a part of
+ * the where clause
+ * @param array $analyzed_sql_results the analyzed query
+ * @param stdClass $meta the meta-information about the
+ * field
+ * @param array $map the list of relations
+ * @param string $data data
+ * @param string $displayedData data that will be displayed (maybe be chunked)
+ * @param TransformationsPlugin $transformation_plugin transformation plugin.
+ * Can also be the default function:
+ * Core::mimeDefaultFunction
+ * @param string $default_function default function
+ * @param string $nowrap 'nowrap' if the content should
+ * not be wrapped
+ * @param string $where_comparison data for the where clause
+ * @param array $transform_options options for transformation
+ * @param bool $is_field_truncated whether the field is truncated
+ * @param string $original_length of a truncated column, or ''
+ *
+ * @return string formatted data
+ *
+ * @access private
+ *
+ * @see _getDataCellForNumericColumns(), _getDataCellForGeometryColumns(),
+ * _getDataCellForNonNumericColumns(),
+ *
+ */
+ private function _getRowData(
+ $class,
+ $condition_field,
+ array $analyzed_sql_results,
+ $meta,
+ array $map,
+ $data,
+ $displayedData,
+ $transformation_plugin,
+ $default_function,
+ $nowrap,
+ $where_comparison,
+ array $transform_options,
+ $is_field_truncated,
+ $original_length = ''
+ ) {
+ $relational_display = $_SESSION['tmpval']['relational_display'];
+ $printview = $this->__get('printview');
+ $decimals = isset($meta->decimals) ? $meta->decimals : '-1';
+ $result = '<td data-decimals="' . $decimals . '"'
+ . ' data-type="' . $meta->type . '"';
+
+ if (! empty($original_length)) {
+ // cannot use data-original-length
+ $result .= ' data-originallength="' . $original_length . '"';
+ }
+
+ $result .= ' class="'
+ . $this->_addClass(
+ $class,
+ $condition_field,
+ $meta,
+ $nowrap,
+ $is_field_truncated,
+ $transformation_plugin,
+ $default_function
+ )
+ . '">';
+
+ if (! empty($analyzed_sql_results['statement']->expr)) {
+ foreach ($analyzed_sql_results['statement']->expr as $expr) {
+ if (empty($expr->alias) || empty($expr->column)) {
+ continue;
+ }
+ if (strcasecmp($meta->name, $expr->alias) == 0) {
+ $meta->name = $expr->column;
+ }
+ }
+ }
+
+ if (isset($map[$meta->name])) {
+ // Field to display from the foreign table?
+ if (isset($map[$meta->name][2])
+ && strlen((string) $map[$meta->name][2]) > 0
+ ) {
+ $dispval = $this->_getFromForeign(
+ $map,
+ $meta,
+ $where_comparison
+ );
+ } else {
+ $dispval = '';
+ } // end if... else...
+
+ if (isset($printview) && ($printview == '1')) {
+ $result .= ($transformation_plugin != $default_function
+ ? $transformation_plugin->applyTransformation(
+ $data,
+ $transform_options,
+ $meta
+ )
+ : $default_function($data)
+ )
+ . ' <code>[-&gt;' . $dispval . ']</code>';
+ } else {
+ if ($relational_display == self::RELATIONAL_KEY) {
+ // user chose "relational key" in the display options, so
+ // the title contains the display field
+ $title = ! empty($dispval)
+ ? htmlspecialchars($dispval)
+ : '';
+ } else {
+ $title = htmlspecialchars($data);
+ }
+
+ $sqlQuery = 'SELECT * FROM '
+ . Util::backquote($map[$meta->name][3]) . '.'
+ . Util::backquote($map[$meta->name][0])
+ . ' WHERE '
+ . Util::backquote($map[$meta->name][1])
+ . $where_comparison;
+
+ $_url_params = [
+ 'db' => $map[$meta->name][3],
+ 'table' => $map[$meta->name][0],
+ 'pos' => '0',
+ 'sql_signature' => Core::signSqlQuery($sqlQuery),
+ 'sql_query' => $sqlQuery,
+ ];
+
+ if ($transformation_plugin != $default_function) {
+ // always apply a transformation on the real data,
+ // not on the display field
+ $displayedData = $transformation_plugin->applyTransformation(
+ $data,
+ $transform_options,
+ $meta
+ );
+ } else {
+ if ($relational_display == self::RELATIONAL_DISPLAY_COLUMN
+ && ! empty($map[$meta->name][2])
+ ) {
+ // user chose "relational display field" in the
+ // display options, so show display field in the cell
+ $displayedData = $default_function($dispval);
+ } else {
+ // otherwise display data in the cell
+ $displayedData = $default_function($displayedData);
+ }
+ }
+
+ $tag_params = ['title' => $title];
+ if (strpos($class, 'grid_edit') !== false) {
+ $tag_params['class'] = 'ajax';
+ }
+ $result .= Util::linkOrButton(
+ 'sql.php' . Url::getCommon($_url_params),
+ $displayedData,
+ $tag_params
+ );
+ }
+ } else {
+ $result .= ($transformation_plugin != $default_function
+ ? $transformation_plugin->applyTransformation(
+ $data,
+ $transform_options,
+ $meta
+ )
+ : $default_function($data)
+ );
+ }
+
+ $result .= '</td>' . "\n";
+
+ return $result;
+ }
+
+ /**
+ * Prepares a checkbox for multi-row submits
+ *
+ * @param string $del_url delete url
+ * @param array $displayParts array with explicit indexes for all
+ * the display elements
+ * @param string $row_no the row number
+ * @param string $where_clause_html url encoded where clause
+ * @param array $condition_array array of conditions in the where clause
+ * @param string $id_suffix suffix for the id
+ * @param string $class css classes for the td element
+ *
+ * @return string the generated HTML
+ *
+ * @access private
+ *
+ * @see _getTableBody(), _getCheckboxAndLinks()
+ */
+ private function _getCheckboxForMultiRowSubmissions(
+ $del_url,
+ array $displayParts,
+ $row_no,
+ $where_clause_html,
+ array $condition_array,
+ $id_suffix,
+ $class
+ ) {
+ $ret = '';
+
+ if (! empty($del_url) && $displayParts['del_lnk'] != self::KILL_PROCESS) {
+ $ret .= '<td ';
+ if (! empty($class)) {
+ $ret .= 'class="' . $class . '"';
+ }
+
+ $ret .= ' class="center print_ignore">'
+ . '<input type="checkbox" id="id_rows_to_delete'
+ . $row_no . $id_suffix
+ . '" name="rows_to_delete[' . $row_no . ']"'
+ . ' class="multi_checkbox checkall"'
+ . ' value="' . $where_clause_html . '">'
+ . '<input type="hidden" class="condition_array" value="'
+ . htmlspecialchars(json_encode($condition_array)) . '">'
+ . ' </td>';
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Prepares an Edit link
+ *
+ * @param string $edit_url edit url
+ * @param string $class css classes for td element
+ * @param string $edit_str text for the edit link
+ * @param string $where_clause where clause
+ * @param string $where_clause_html url encoded where clause
+ *
+ * @return string the generated HTML
+ *
+ * @access private
+ *
+ * @see _getTableBody(), _getCheckboxAndLinks()
+ */
+ private function _getEditLink(
+ $edit_url,
+ $class,
+ $edit_str,
+ $where_clause,
+ $where_clause_html
+ ) {
+ $ret = '';
+ if (! empty($edit_url)) {
+ $ret .= '<td class="' . $class . ' center print_ignore">'
+ . '<span class="nowrap">'
+ . Util::linkOrButton($edit_url, $edit_str);
+ /*
+ * Where clause for selecting this row uniquely is provided as
+ * a hidden input. Used by jQuery scripts for handling grid editing
+ */
+ if (! empty($where_clause)) {
+ $ret .= '<input type="hidden" class="where_clause" value ="'
+ . $where_clause_html . '">';
+ }
+ $ret .= '</span></td>';
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Prepares an Copy link
+ *
+ * @param string $copy_url copy url
+ * @param string $copy_str text for the copy link
+ * @param string $where_clause where clause
+ * @param string $where_clause_html url encoded where clause
+ * @param string $class css classes for the td element
+ *
+ * @return string the generated HTML
+ *
+ * @access private
+ *
+ * @see _getTableBody(), _getCheckboxAndLinks()
+ */
+ private function _getCopyLink(
+ $copy_url,
+ $copy_str,
+ $where_clause,
+ $where_clause_html,
+ $class
+ ) {
+ $ret = '';
+ if (! empty($copy_url)) {
+ $ret .= '<td class="';
+ if (! empty($class)) {
+ $ret .= $class . ' ';
+ }
+
+ $ret .= 'center print_ignore"><span class="nowrap">'
+ . Util::linkOrButton($copy_url, $copy_str);
+
+ /*
+ * Where clause for selecting this row uniquely is provided as
+ * a hidden input. Used by jQuery scripts for handling grid editing
+ */
+ if (! empty($where_clause)) {
+ $ret .= '<input type="hidden" class="where_clause" value="'
+ . $where_clause_html . '">';
+ }
+ $ret .= '</span></td>';
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Prepares a Delete link
+ *
+ * @param string $del_url delete url
+ * @param string $del_str text for the delete link
+ * @param string $js_conf text for the JS confirmation
+ * @param string $class css classes for the td element
+ *
+ * @return string the generated HTML
+ *
+ * @access private
+ *
+ * @see _getTableBody(), _getCheckboxAndLinks()
+ */
+ private function _getDeleteLink($del_url, $del_str, $js_conf, $class)
+ {
+
+ $ret = '';
+ if (empty($del_url)) {
+ return $ret;
+ }
+
+ $ret .= '<td class="';
+ if (! empty($class)) {
+ $ret .= $class . ' ';
+ }
+ $ajax = Response::getInstance()->isAjax() ? ' ajax' : '';
+ $ret .= 'center print_ignore">'
+ . Util::linkOrButton(
+ $del_url,
+ $del_str,
+ ['class' => 'delete_row requireConfirm' . $ajax]
+ )
+ . '<div class="hide">' . $js_conf . '</div>'
+ . '</td>';
+
+ return $ret;
+ }
+
+ /**
+ * Prepare checkbox and links at some position (left or right)
+ * (only called for horizontal mode)
+ *
+ * @param string $position the position of the checkbox and links
+ * @param string $del_url delete url
+ * @param array $displayParts array with explicit indexes for all the
+ * display elements
+ * @param string $row_no row number
+ * @param string $where_clause where clause
+ * @param string $where_clause_html url encoded where clause
+ * @param array $condition_array array of conditions in the where clause
+ * @param string $edit_url edit url
+ * @param string $copy_url copy url
+ * @param string $class css classes for the td elements
+ * @param string $edit_str text for the edit link
+ * @param string $copy_str text for the copy link
+ * @param string $del_str text for the delete link
+ * @param string $js_conf text for the JS confirmation
+ *
+ * @return string the generated HTML
+ *
+ * @access private
+ *
+ * @see _getPlacedLinks()
+ */
+ private function _getCheckboxAndLinks(
+ $position,
+ $del_url,
+ array $displayParts,
+ $row_no,
+ $where_clause,
+ $where_clause_html,
+ array $condition_array,
+ $edit_url,
+ $copy_url,
+ $class,
+ $edit_str,
+ $copy_str,
+ $del_str,
+ $js_conf
+ ) {
+ $ret = '';
+
+ if ($position == self::POSITION_LEFT) {
+ $ret .= $this->_getCheckboxForMultiRowSubmissions(
+ $del_url,
+ $displayParts,
+ $row_no,
+ $where_clause_html,
+ $condition_array,
+ '_left',
+ ''
+ );
+
+ $ret .= $this->_getEditLink(
+ $edit_url,
+ $class,
+ $edit_str,
+ $where_clause,
+ $where_clause_html
+ );
+
+ $ret .= $this->_getCopyLink(
+ $copy_url,
+ $copy_str,
+ $where_clause,
+ $where_clause_html,
+ ''
+ );
+
+ $ret .= $this->_getDeleteLink($del_url, $del_str, $js_conf, '');
+ } elseif ($position == self::POSITION_RIGHT) {
+ $ret .= $this->_getDeleteLink($del_url, $del_str, $js_conf, '');
+
+ $ret .= $this->_getCopyLink(
+ $copy_url,
+ $copy_str,
+ $where_clause,
+ $where_clause_html,
+ ''
+ );
+
+ $ret .= $this->_getEditLink(
+ $edit_url,
+ $class,
+ $edit_str,
+ $where_clause,
+ $where_clause_html
+ );
+
+ $ret .= $this->_getCheckboxForMultiRowSubmissions(
+ $del_url,
+ $displayParts,
+ $row_no,
+ $where_clause_html,
+ $condition_array,
+ '_right',
+ ''
+ );
+ } else { // $position == self::POSITION_NONE
+ $ret .= $this->_getCheckboxForMultiRowSubmissions(
+ $del_url,
+ $displayParts,
+ $row_no,
+ $where_clause_html,
+ $condition_array,
+ '_left',
+ ''
+ );
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Truncates given string based on LimitChars configuration
+ * and Session pftext variable
+ * (string is truncated only if necessary)
+ *
+ * @param string $str string to be truncated
+ *
+ * @return mixed
+ *
+ * @access private
+ *
+ * @see _handleNonPrintableContents(), _getDataCellForGeometryColumns(),
+ * _getDataCellForNonNumericColumns
+ */
+ private function _getPartialText($str)
+ {
+ $original_length = mb_strlen($str);
+ if ($original_length > $GLOBALS['cfg']['LimitChars']
+ && $_SESSION['tmpval']['pftext'] === self::DISPLAY_PARTIAL_TEXT
+ ) {
+ $str = mb_substr(
+ $str,
+ 0,
+ (int) $GLOBALS['cfg']['LimitChars']
+ ) . '...';
+ $truncated = true;
+ } else {
+ $truncated = false;
+ }
+
+ return [
+ $truncated,
+ $str,
+ $original_length,
+ ];
+ }
+}