PageRenderTime 58ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 1ms

/libraries/classes/Display/Results.php

http://github.com/phpmyadmin/phpmyadmin
PHP | 4765 lines | 2967 code | 549 blank | 1249 comment | 489 complexity | b31e4de92fd72b5b613e9f896aa4caf0 MD5 | raw file
Possible License(s): GPL-2.0, MIT, LGPL-3.0

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. declare(strict_types=1);
  3. namespace PhpMyAdmin\Display;
  4. use PhpMyAdmin\Config\SpecialSchemaLinks;
  5. use PhpMyAdmin\Core;
  6. use PhpMyAdmin\DatabaseInterface;
  7. use PhpMyAdmin\FieldMetadata;
  8. use PhpMyAdmin\Html\Generator;
  9. use PhpMyAdmin\Index;
  10. use PhpMyAdmin\Message;
  11. use PhpMyAdmin\Plugins\Transformations\Output\Text_Octetstream_Sql;
  12. use PhpMyAdmin\Plugins\Transformations\Output\Text_Plain_Json;
  13. use PhpMyAdmin\Plugins\Transformations\Output\Text_Plain_Sql;
  14. use PhpMyAdmin\Plugins\Transformations\Text_Plain_Link;
  15. use PhpMyAdmin\Plugins\TransformationsPlugin;
  16. use PhpMyAdmin\Relation;
  17. use PhpMyAdmin\ResponseRenderer;
  18. use PhpMyAdmin\Sanitize;
  19. use PhpMyAdmin\Sql;
  20. use PhpMyAdmin\SqlParser\Parser;
  21. use PhpMyAdmin\SqlParser\Statements\SelectStatement;
  22. use PhpMyAdmin\SqlParser\Utils\Query;
  23. use PhpMyAdmin\Table;
  24. use PhpMyAdmin\Template;
  25. use PhpMyAdmin\Theme;
  26. use PhpMyAdmin\Transformations;
  27. use PhpMyAdmin\Url;
  28. use PhpMyAdmin\Util;
  29. use PhpMyAdmin\Utils\Gis;
  30. use function __;
  31. use function _pgettext;
  32. use function array_filter;
  33. use function array_keys;
  34. use function array_merge;
  35. use function array_shift;
  36. use function bin2hex;
  37. use function ceil;
  38. use function class_exists;
  39. use function count;
  40. use function explode;
  41. use function file_exists;
  42. use function floor;
  43. use function htmlspecialchars;
  44. use function implode;
  45. use function in_array;
  46. use function intval;
  47. use function is_array;
  48. use function is_numeric;
  49. use function json_encode;
  50. use function mb_check_encoding;
  51. use function mb_strlen;
  52. use function mb_strpos;
  53. use function mb_strtolower;
  54. use function mb_strtoupper;
  55. use function mb_substr;
  56. use function md5;
  57. use function mt_getrandmax;
  58. use function pack;
  59. use function preg_match;
  60. use function preg_replace;
  61. use function random_int;
  62. use function str_contains;
  63. use function str_replace;
  64. use function strcasecmp;
  65. use function strip_tags;
  66. use function stripos;
  67. use function strlen;
  68. use function strpos;
  69. use function strtoupper;
  70. use function substr;
  71. use function trim;
  72. /**
  73. * Handle all the functionalities related to displaying results
  74. * of sql queries, stored procedure, browsing sql processes or
  75. * displaying binary log.
  76. */
  77. class Results
  78. {
  79. // Define constants
  80. public const NO_EDIT_OR_DELETE = 'nn';
  81. public const UPDATE_ROW = 'ur';
  82. public const DELETE_ROW = 'dr';
  83. public const KILL_PROCESS = 'kp';
  84. public const POSITION_LEFT = 'left';
  85. public const POSITION_RIGHT = 'right';
  86. public const POSITION_BOTH = 'both';
  87. public const POSITION_NONE = 'none';
  88. public const DISPLAY_FULL_TEXT = 'F';
  89. public const DISPLAY_PARTIAL_TEXT = 'P';
  90. public const HEADER_FLIP_TYPE_AUTO = 'auto';
  91. public const HEADER_FLIP_TYPE_CSS = 'css';
  92. public const HEADER_FLIP_TYPE_FAKE = 'fake';
  93. public const RELATIONAL_KEY = 'K';
  94. public const RELATIONAL_DISPLAY_COLUMN = 'D';
  95. public const GEOMETRY_DISP_GEOM = 'GEOM';
  96. public const GEOMETRY_DISP_WKT = 'WKT';
  97. public const GEOMETRY_DISP_WKB = 'WKB';
  98. public const SMART_SORT_ORDER = 'SMART';
  99. public const ASCENDING_SORT_DIR = 'ASC';
  100. public const DESCENDING_SORT_DIR = 'DESC';
  101. public const TABLE_TYPE_INNO_DB = 'InnoDB';
  102. public const ALL_ROWS = 'all';
  103. public const QUERY_TYPE_SELECT = 'SELECT';
  104. public const ROUTINE_PROCEDURE = 'procedure';
  105. public const ROUTINE_FUNCTION = 'function';
  106. public const ACTION_LINK_CONTENT_ICONS = 'icons';
  107. public const ACTION_LINK_CONTENT_TEXT = 'text';
  108. /**
  109. * @psalm-var array{
  110. * server: int,
  111. * db: string,
  112. * table: string,
  113. * goto: string,
  114. * sql_query: string,
  115. * unlim_num_rows: int|numeric-string,
  116. * fields_meta: FieldMetadata[],
  117. * is_count: bool|null,
  118. * is_export: bool|null,
  119. * is_func: bool|null,
  120. * is_analyse: bool|null,
  121. * num_rows: int|numeric-string,
  122. * fields_cnt: int,
  123. * querytime: float|null,
  124. * text_dir: string|null,
  125. * is_maint: bool|null,
  126. * is_explain: bool|null,
  127. * is_show: bool|null,
  128. * is_browse_distinct: bool|null,
  129. * showtable: array<string, mixed>|null,
  130. * printview: string|null,
  131. * highlight_columns: array|null,
  132. * display_params: array|null,
  133. * mime_map: array|null,
  134. * editable: bool|null,
  135. * unique_id: int,
  136. * whereClauseMap: array,
  137. * }
  138. */
  139. public $properties = [
  140. /* server id */
  141. 'server' => 0,
  142. /* Database name */
  143. 'db' => '',
  144. /* Table name */
  145. 'table' => '',
  146. /* the URL to go back in case of errors */
  147. 'goto' => '',
  148. /* the SQL query */
  149. 'sql_query' => '',
  150. /* the total number of rows returned by the SQL query without any appended "LIMIT" clause programmatically */
  151. 'unlim_num_rows' => 0,
  152. /* meta information about fields */
  153. 'fields_meta' => [],
  154. 'is_count' => null,
  155. 'is_export' => null,
  156. 'is_func' => null,
  157. 'is_analyse' => null,
  158. /* the total number of rows returned by the SQL query */
  159. 'num_rows' => 0,
  160. /* the total number of fields returned by the SQL query */
  161. 'fields_cnt' => 0,
  162. /* time taken for execute the SQL query */
  163. 'querytime' => null,
  164. 'text_dir' => null,
  165. 'is_maint' => null,
  166. 'is_explain' => null,
  167. 'is_show' => null,
  168. 'is_browse_distinct' => null,
  169. /* table definitions */
  170. 'showtable' => null,
  171. 'printview' => null,
  172. /* column names to highlight */
  173. 'highlight_columns' => null,
  174. /* display information */
  175. 'display_params' => null,
  176. /* mime types information of fields */
  177. 'mime_map' => null,
  178. 'editable' => null,
  179. /* random unique ID to distinguish result set */
  180. 'unique_id' => 0,
  181. /* where clauses for each row, each table in the row */
  182. 'whereClauseMap' => [],
  183. ];
  184. /**
  185. * This variable contains the column transformation information
  186. * for some of the system databases.
  187. * One element of this array represent all relevant columns in all tables in
  188. * one specific database
  189. *
  190. * @var array<string, array<string, array<string, string[]>>>
  191. * @psalm-var array<string, array<string, array<string, array{string, class-string, string}>>> $transformationInfo
  192. */
  193. public $transformationInfo;
  194. /** @var Relation */
  195. private $relation;
  196. /** @var Transformations */
  197. private $transformations;
  198. /** @var Template */
  199. public $template;
  200. /**
  201. * @param string $db the database name
  202. * @param string $table the table name
  203. * @param int $server the server id
  204. * @param string $goto the URL to go back in case of errors
  205. * @param string $sqlQuery the SQL query
  206. *
  207. * @access public
  208. */
  209. public function __construct($db, $table, $server, $goto, $sqlQuery)
  210. {
  211. global $dbi;
  212. $this->relation = new Relation($dbi);
  213. $this->transformations = new Transformations();
  214. $this->template = new Template();
  215. $this->setDefaultTransformations();
  216. $this->properties['db'] = $db;
  217. $this->properties['table'] = $table;
  218. $this->properties['server'] = $server;
  219. $this->properties['goto'] = $goto;
  220. $this->properties['sql_query'] = $sqlQuery;
  221. $this->properties['unique_id'] = random_int(0, mt_getrandmax());
  222. }
  223. /**
  224. * Sets default transformations for some columns
  225. */
  226. private function setDefaultTransformations(): void
  227. {
  228. $jsonHighlightingData = [
  229. 'libraries/classes/Plugins/Transformations/Output/Text_Plain_Json.php',
  230. Text_Plain_Json::class,
  231. 'Text_Plain',
  232. ];
  233. $sqlHighlightingData = [
  234. 'libraries/classes/Plugins/Transformations/Output/Text_Plain_Sql.php',
  235. Text_Plain_Sql::class,
  236. 'Text_Plain',
  237. ];
  238. $blobSqlHighlightingData = [
  239. 'libraries/classes/Plugins/Transformations/Output/Text_Octetstream_Sql.php',
  240. Text_Octetstream_Sql::class,
  241. 'Text_Octetstream',
  242. ];
  243. $linkData = [
  244. 'libraries/classes/Plugins/Transformations/Text_Plain_Link.php',
  245. Text_Plain_Link::class,
  246. 'Text_Plain',
  247. ];
  248. $this->transformationInfo = [
  249. 'information_schema' => [
  250. 'events' => ['event_definition' => $sqlHighlightingData],
  251. 'processlist' => ['info' => $sqlHighlightingData],
  252. 'routines' => ['routine_definition' => $sqlHighlightingData],
  253. 'triggers' => ['action_statement' => $sqlHighlightingData],
  254. 'views' => ['view_definition' => $sqlHighlightingData],
  255. ],
  256. 'mysql' => [
  257. 'event' => [
  258. 'body' => $blobSqlHighlightingData,
  259. 'body_utf8' => $blobSqlHighlightingData,
  260. ],
  261. 'general_log' => ['argument' => $sqlHighlightingData],
  262. 'help_category' => ['url' => $linkData],
  263. 'help_topic' => [
  264. 'example' => $sqlHighlightingData,
  265. 'url' => $linkData,
  266. ],
  267. 'proc' => [
  268. 'param_list' => $blobSqlHighlightingData,
  269. 'returns' => $blobSqlHighlightingData,
  270. 'body' => $blobSqlHighlightingData,
  271. 'body_utf8' => $blobSqlHighlightingData,
  272. ],
  273. 'slow_log' => ['sql_text' => $sqlHighlightingData],
  274. ],
  275. ];
  276. $cfgRelation = $this->relation->getRelationsParam();
  277. if (! $cfgRelation['db']) {
  278. return;
  279. }
  280. $relDb = [];
  281. if (! empty($cfgRelation['history'])) {
  282. $relDb[$cfgRelation['history']] = ['sqlquery' => $sqlHighlightingData];
  283. }
  284. if (! empty($cfgRelation['bookmark'])) {
  285. $relDb[$cfgRelation['bookmark']] = ['query' => $sqlHighlightingData];
  286. }
  287. if (! empty($cfgRelation['tracking'])) {
  288. $relDb[$cfgRelation['tracking']] = [
  289. 'schema_sql' => $sqlHighlightingData,
  290. 'data_sql' => $sqlHighlightingData,
  291. ];
  292. }
  293. if (! empty($cfgRelation['favorite'])) {
  294. $relDb[$cfgRelation['favorite']] = ['tables' => $jsonHighlightingData];
  295. }
  296. if (! empty($cfgRelation['recent'])) {
  297. $relDb[$cfgRelation['recent']] = ['tables' => $jsonHighlightingData];
  298. }
  299. if (! empty($cfgRelation['savedsearches'])) {
  300. $relDb[$cfgRelation['savedsearches']] = ['search_data' => $jsonHighlightingData];
  301. }
  302. if (! empty($cfgRelation['designer_settings'])) {
  303. $relDb[$cfgRelation['designer_settings']] = ['settings_data' => $jsonHighlightingData];
  304. }
  305. if (! empty($cfgRelation['table_uiprefs'])) {
  306. $relDb[$cfgRelation['table_uiprefs']] = ['prefs' => $jsonHighlightingData];
  307. }
  308. if (! empty($cfgRelation['userconfig'])) {
  309. $relDb[$cfgRelation['userconfig']] = ['config_data' => $jsonHighlightingData];
  310. }
  311. if (! empty($cfgRelation['export_templates'])) {
  312. $relDb[$cfgRelation['export_templates']] = ['template_data' => $jsonHighlightingData];
  313. }
  314. $this->transformationInfo[$cfgRelation['db']] = $relDb;
  315. }
  316. /**
  317. * Set properties which were not initialized at the constructor
  318. *
  319. * @param int|string $unlimNumRows the total number of rows returned by the SQL query without
  320. * any appended "LIMIT" clause programmatically
  321. * @param FieldMetadata[] $fieldsMeta meta information about fields
  322. * @param bool $isCount statement is SELECT COUNT
  323. * @param bool $isExport statement contains INTO OUTFILE
  324. * @param bool $isFunction statement contains a function like SUM()
  325. * @param bool $isAnalyse statement contains PROCEDURE ANALYSE
  326. * @param int|string $numRows total no. of rows returned by SQL query
  327. * @param int $fieldsCount total no.of fields returned by SQL query
  328. * @param double $queryTime time taken for execute the SQL query
  329. * @param string $textDirection text direction
  330. * @param bool $isMaintenance statement contains a maintenance command
  331. * @param bool $isExplain statement contains EXPLAIN
  332. * @param bool $isShow statement contains SHOW
  333. * @param array<string, mixed>|null $showTable table definitions
  334. * @param string|null $printView print view was requested
  335. * @param bool $editable whether the results set is editable
  336. * @param bool $isBrowseDistinct whether browsing distinct values
  337. * @psalm-param int|numeric-string $unlimNumRows
  338. * @psalm-param int|numeric-string $numRows
  339. */
  340. public function setProperties(
  341. $unlimNumRows,
  342. array $fieldsMeta,
  343. $isCount,
  344. $isExport,
  345. $isFunction,
  346. $isAnalyse,
  347. $numRows,
  348. $fieldsCount,
  349. $queryTime,
  350. $textDirection,
  351. $isMaintenance,
  352. $isExplain,
  353. $isShow,
  354. ?array $showTable,
  355. $printView,
  356. $editable,
  357. $isBrowseDistinct
  358. ): void {
  359. $this->properties['unlim_num_rows'] = $unlimNumRows;
  360. $this->properties['fields_meta'] = $fieldsMeta;
  361. $this->properties['is_count'] = $isCount;
  362. $this->properties['is_export'] = $isExport;
  363. $this->properties['is_func'] = $isFunction;
  364. $this->properties['is_analyse'] = $isAnalyse;
  365. $this->properties['num_rows'] = $numRows;
  366. $this->properties['fields_cnt'] = $fieldsCount;
  367. $this->properties['querytime'] = $queryTime;
  368. $this->properties['text_dir'] = $textDirection;
  369. $this->properties['is_maint'] = $isMaintenance;
  370. $this->properties['is_explain'] = $isExplain;
  371. $this->properties['is_show'] = $isShow;
  372. $this->properties['showtable'] = $showTable;
  373. $this->properties['printview'] = $printView;
  374. $this->properties['editable'] = $editable;
  375. $this->properties['is_browse_distinct'] = $isBrowseDistinct;
  376. }
  377. /**
  378. * Defines the parts to display for a print view
  379. *
  380. * @param array $displayParts the parts to display
  381. *
  382. * @return array the modified display parts
  383. *
  384. * @access private
  385. */
  386. private function setDisplayPartsForPrintView(array $displayParts)
  387. {
  388. // set all elements to false!
  389. $displayParts['edit_lnk'] = self::NO_EDIT_OR_DELETE; // no edit link
  390. $displayParts['del_lnk'] = self::NO_EDIT_OR_DELETE; // no delete link
  391. $displayParts['sort_lnk'] = '0';
  392. $displayParts['nav_bar'] = '0';
  393. $displayParts['bkm_form'] = '0';
  394. $displayParts['text_btn'] = '0';
  395. $displayParts['pview_lnk'] = '0';
  396. return $displayParts;
  397. }
  398. /**
  399. * Defines the parts to display for a SHOW statement
  400. *
  401. * @param array $displayParts the parts to display
  402. *
  403. * @return array the modified display parts
  404. *
  405. * @access private
  406. */
  407. private function setDisplayPartsForShow(array $displayParts)
  408. {
  409. preg_match(
  410. '@^SHOW[[:space:]]+(VARIABLES|(FULL[[:space:]]+)?'
  411. . 'PROCESSLIST|STATUS|TABLE|GRANTS|CREATE|LOGS|DATABASES|FIELDS'
  412. . ')@i',
  413. $this->properties['sql_query'],
  414. $which
  415. );
  416. $bIsProcessList = isset($which[1]);
  417. if ($bIsProcessList) {
  418. $str = ' ' . strtoupper($which[1]);
  419. $bIsProcessList = $bIsProcessList
  420. && strpos($str, 'PROCESSLIST') > 0;
  421. }
  422. if ($bIsProcessList) {
  423. // no edit link
  424. $displayParts['edit_lnk'] = self::NO_EDIT_OR_DELETE;
  425. // "kill process" type edit link
  426. $displayParts['del_lnk'] = self::KILL_PROCESS;
  427. } else {
  428. // Default case -> no links
  429. // no edit link
  430. $displayParts['edit_lnk'] = self::NO_EDIT_OR_DELETE;
  431. // no delete link
  432. $displayParts['del_lnk'] = self::NO_EDIT_OR_DELETE;
  433. }
  434. // Other settings
  435. $displayParts['sort_lnk'] = '0';
  436. $displayParts['nav_bar'] = '0';
  437. $displayParts['bkm_form'] = '1';
  438. $displayParts['text_btn'] = '1';
  439. $displayParts['pview_lnk'] = '1';
  440. return $displayParts;
  441. }
  442. /**
  443. * Defines the parts to display for statements not related to data
  444. *
  445. * @param array $displayParts the parts to display
  446. *
  447. * @return array the modified display parts
  448. *
  449. * @access private
  450. */
  451. private function setDisplayPartsForNonData(array $displayParts)
  452. {
  453. // Statement is a "SELECT COUNT", a
  454. // "CHECK/ANALYZE/REPAIR/OPTIMIZE/CHECKSUM", an "EXPLAIN" one or
  455. // contains a "PROC ANALYSE" part
  456. $displayParts['edit_lnk'] = self::NO_EDIT_OR_DELETE; // no edit link
  457. $displayParts['del_lnk'] = self::NO_EDIT_OR_DELETE; // no delete link
  458. $displayParts['sort_lnk'] = '0';
  459. $displayParts['nav_bar'] = '0';
  460. $displayParts['bkm_form'] = '1';
  461. if ($this->properties['is_maint']) {
  462. $displayParts['text_btn'] = '1';
  463. } else {
  464. $displayParts['text_btn'] = '0';
  465. }
  466. $displayParts['pview_lnk'] = '1';
  467. return $displayParts;
  468. }
  469. /**
  470. * Defines the parts to display for other statements (probably SELECT)
  471. *
  472. * @param array $displayParts the parts to display
  473. *
  474. * @return array the modified display parts
  475. *
  476. * @access private
  477. */
  478. private function setDisplayPartsForSelect(array $displayParts)
  479. {
  480. // Other statements (ie "SELECT" ones) -> updates
  481. // $displayParts['edit_lnk'], $displayParts['del_lnk'] and
  482. // $displayParts['text_btn'] (keeps other default values)
  483. /** @var FieldMetadata[] $fieldsMeta */
  484. $fieldsMeta = $this->properties['fields_meta'];
  485. $previousTable = '';
  486. $displayParts['text_btn'] = '1';
  487. $numberOfColumns = $this->properties['fields_cnt'];
  488. for ($i = 0; $i < $numberOfColumns; $i++) {
  489. $isLink = ($displayParts['edit_lnk'] != self::NO_EDIT_OR_DELETE)
  490. || ($displayParts['del_lnk'] != self::NO_EDIT_OR_DELETE)
  491. || ($displayParts['sort_lnk'] != '0');
  492. // Displays edit/delete/sort/insert links?
  493. if (
  494. $isLink
  495. && $previousTable != ''
  496. && $fieldsMeta[$i]->table != ''
  497. && $fieldsMeta[$i]->table != $previousTable
  498. ) {
  499. // don't display links
  500. $displayParts['edit_lnk'] = self::NO_EDIT_OR_DELETE;
  501. $displayParts['del_lnk'] = self::NO_EDIT_OR_DELETE;
  502. /**
  503. * @todo May be problematic with same field names
  504. * in two joined table.
  505. */
  506. if ($displayParts['text_btn'] == '1') {
  507. break;
  508. }
  509. }
  510. // Always display print view link
  511. $displayParts['pview_lnk'] = '1';
  512. if ($fieldsMeta[$i]->table == '') {
  513. continue;
  514. }
  515. $previousTable = $fieldsMeta[$i]->table;
  516. }
  517. if ($previousTable == '') { // no table for any of the columns
  518. // don't display links
  519. $displayParts['edit_lnk'] = self::NO_EDIT_OR_DELETE;
  520. $displayParts['del_lnk'] = self::NO_EDIT_OR_DELETE;
  521. }
  522. return $displayParts;
  523. }
  524. /**
  525. * Defines the parts to display for the results of a SQL query
  526. * and the total number of rows
  527. *
  528. * @see getTable()
  529. *
  530. * @param array $displayParts the parts to display (see a few
  531. * lines above for explanations)
  532. *
  533. * @return array the first element is an array with explicit indexes
  534. * for all the display elements
  535. * the second element is the total number of rows returned
  536. * by the SQL query without any programmatically appended
  537. * LIMIT clause (just a copy of $unlim_num_rows if it exists,
  538. * else computed inside this function)
  539. *
  540. * @access private
  541. */
  542. private function setDisplayPartsAndTotal(array $displayParts)
  543. {
  544. global $dbi;
  545. $theTotal = 0;
  546. // 1. Following variables are needed for use in isset/empty or
  547. // use with array indexes or safe use in foreach
  548. $db = $this->properties['db'];
  549. $table = $this->properties['table'];
  550. $unlimNumRows = $this->properties['unlim_num_rows'];
  551. $numRows = $this->properties['num_rows'];
  552. $printView = $this->properties['printview'];
  553. // 2. Updates the display parts
  554. if ($printView == '1') {
  555. $displayParts = $this->setDisplayPartsForPrintView($displayParts);
  556. } elseif (
  557. $this->properties['is_count'] || $this->properties['is_analyse']
  558. || $this->properties['is_maint'] || $this->properties['is_explain']
  559. ) {
  560. $displayParts = $this->setDisplayPartsForNonData($displayParts);
  561. } elseif ($this->properties['is_show']) {
  562. $displayParts = $this->setDisplayPartsForShow($displayParts);
  563. } else {
  564. $displayParts = $this->setDisplayPartsForSelect($displayParts);
  565. }
  566. // 3. Gets the total number of rows if it is unknown
  567. if ($unlimNumRows > 0) {
  568. $theTotal = $unlimNumRows;
  569. } elseif (
  570. ($displayParts['nav_bar'] == '1')
  571. || ($displayParts['sort_lnk'] == '1')
  572. && (strlen($db) > 0 && strlen($table) > 0)
  573. ) {
  574. $theTotal = $dbi->getTable($db, $table)->countRecords();
  575. }
  576. // if for COUNT query, number of rows returned more than 1
  577. // (may be being used GROUP BY)
  578. if ($this->properties['is_count'] && $numRows > 1) {
  579. $displayParts['nav_bar'] = '1';
  580. $displayParts['sort_lnk'] = '1';
  581. }
  582. // 4. If navigation bar or sorting fields names URLs should be
  583. // displayed but there is only one row, change these settings to
  584. // false
  585. if ($displayParts['nav_bar'] == '1' || $displayParts['sort_lnk'] == '1') {
  586. // - Do not display sort links if less than 2 rows.
  587. // - For a VIEW we (probably) did not count the number of rows
  588. // so don't test this number here, it would remove the possibility
  589. // of sorting VIEW results.
  590. $tableObject = new Table($table, $db);
  591. if ($unlimNumRows < 2 && ! $tableObject->isView()) {
  592. $displayParts['sort_lnk'] = '0';
  593. }
  594. }
  595. return [
  596. $displayParts,
  597. $theTotal,
  598. ];
  599. }
  600. /**
  601. * Return true if we are executing a query in the form of
  602. * "SELECT * FROM <a table> ..."
  603. *
  604. * @see getTableHeaders(), getColumnParams()
  605. *
  606. * @param array $analyzedSqlResults analyzed sql results
  607. *
  608. * @access private
  609. */
  610. private function isSelect(array $analyzedSqlResults): bool
  611. {
  612. return ! ($this->properties['is_count']
  613. || $this->properties['is_export']
  614. || $this->properties['is_func']
  615. || $this->properties['is_analyse'])
  616. && ! empty($analyzedSqlResults['select_from'])
  617. && ! empty($analyzedSqlResults['statement']->from)
  618. && (count($analyzedSqlResults['statement']->from) === 1)
  619. && ! empty($analyzedSqlResults['statement']->from[0]->table);
  620. }
  621. /**
  622. * Get a navigation button
  623. *
  624. * @see getMoveBackwardButtonsForTableNavigation(),
  625. * getMoveForwardButtonsForTableNavigation()
  626. *
  627. * @param string $caption iconic caption for button
  628. * @param string $title text for button
  629. * @param int $pos position for next query
  630. * @param string $htmlSqlQuery query ready for display
  631. * @param bool $back whether 'begin' or 'previous'
  632. * @param string $onsubmit optional onsubmit clause
  633. * @param string $inputForRealEnd optional hidden field for special treatment
  634. * @param string $onclick optional onclick clause
  635. *
  636. * @return string html content
  637. *
  638. * @access private
  639. */
  640. private function getTableNavigationButton(
  641. $caption,
  642. $title,
  643. $pos,
  644. $htmlSqlQuery,
  645. $back,
  646. $onsubmit = '',
  647. $inputForRealEnd = '',
  648. $onclick = ''
  649. ) {
  650. $captionOutput = '';
  651. if ($back) {
  652. if (Util::showIcons('TableNavigationLinksMode')) {
  653. $captionOutput .= $caption;
  654. }
  655. if (Util::showText('TableNavigationLinksMode')) {
  656. $captionOutput .= '&nbsp;' . $title;
  657. }
  658. } else {
  659. if (Util::showText('TableNavigationLinksMode')) {
  660. $captionOutput .= $title;
  661. }
  662. if (Util::showIcons('TableNavigationLinksMode')) {
  663. $captionOutput .= '&nbsp;' . $caption;
  664. }
  665. }
  666. return $this->template->render('display/results/table_navigation_button', [
  667. 'db' => $this->properties['db'],
  668. 'table' => $this->properties['table'],
  669. 'sql_query' => $htmlSqlQuery,
  670. 'pos' => $pos,
  671. 'is_browse_distinct' => $this->properties['is_browse_distinct'],
  672. 'goto' => $this->properties['goto'],
  673. 'input_for_real_end' => $inputForRealEnd,
  674. 'caption_output' => $captionOutput,
  675. 'title' => $title,
  676. 'onsubmit' => $onsubmit,
  677. 'onclick' => $onclick,
  678. ]);
  679. }
  680. /**
  681. * Possibly return a page selector for table navigation
  682. *
  683. * @return array ($output, $nbTotalPage)
  684. *
  685. * @access private
  686. */
  687. private function getHtmlPageSelector(): array
  688. {
  689. $pageNow = (int) floor($_SESSION['tmpval']['pos'] / $_SESSION['tmpval']['max_rows']) + 1;
  690. $nbTotalPage = (int) ceil($this->properties['unlim_num_rows'] / $_SESSION['tmpval']['max_rows']);
  691. $output = '';
  692. if ($nbTotalPage > 1) {
  693. $urlParams = [
  694. 'db' => $this->properties['db'],
  695. 'table' => $this->properties['table'],
  696. 'sql_query' => $this->properties['sql_query'],
  697. 'goto' => $this->properties['goto'],
  698. 'is_browse_distinct' => $this->properties['is_browse_distinct'],
  699. ];
  700. $output = $this->template->render('display/results/page_selector', [
  701. 'url_params' => $urlParams,
  702. 'page_selector' => Util::pageselector(
  703. 'pos',
  704. $_SESSION['tmpval']['max_rows'],
  705. $pageNow,
  706. $nbTotalPage,
  707. 200,
  708. 5,
  709. 5,
  710. 20,
  711. 10
  712. ),
  713. ]);
  714. }
  715. return [
  716. $output,
  717. $nbTotalPage,
  718. ];
  719. }
  720. /**
  721. * Get a navigation bar to browse among the results of a SQL query
  722. *
  723. * @see getTable()
  724. *
  725. * @param int $posNext the offset for the "next" page
  726. * @param int $posPrevious the offset for the "previous" page
  727. * @param bool $isInnodb whether its InnoDB or not
  728. * @param array $sortByKeyData the sort by key dialog
  729. *
  730. * @return array
  731. */
  732. private function getTableNavigation(
  733. $posNext,
  734. $posPrevious,
  735. $isInnodb,
  736. array $sortByKeyData
  737. ): array {
  738. $isShowingAll = $_SESSION['tmpval']['max_rows'] === self::ALL_ROWS;
  739. // Move to the beginning or to the previous page
  740. $moveBackwardButtons = '';
  741. if ($_SESSION['tmpval']['pos'] && ! $isShowingAll) {
  742. $moveBackwardButtons = $this->getMoveBackwardButtonsForTableNavigation(
  743. htmlspecialchars($this->properties['sql_query']),
  744. $posPrevious
  745. );
  746. }
  747. $pageSelector = '';
  748. $numberTotalPage = 1;
  749. if (! $isShowingAll) {
  750. [
  751. $pageSelector,
  752. $numberTotalPage,
  753. ] = $this->getHtmlPageSelector();
  754. }
  755. // Move to the next page or to the last one
  756. $moveForwardButtons = '';
  757. if (
  758. $this->properties['unlim_num_rows'] === -1 // view with unknown number of rows
  759. || (! $isShowingAll
  760. && intval($_SESSION['tmpval']['pos']) + intval($_SESSION['tmpval']['max_rows'])
  761. < $this->properties['unlim_num_rows']
  762. && $this->properties['num_rows'] >= $_SESSION['tmpval']['max_rows'])
  763. ) {
  764. $moveForwardButtons = $this->getMoveForwardButtonsForTableNavigation(
  765. htmlspecialchars($this->properties['sql_query']),
  766. $posNext,
  767. $isInnodb
  768. );
  769. }
  770. $hiddenFields = [
  771. 'db' => $this->properties['db'],
  772. 'table' => $this->properties['table'],
  773. 'server' => $this->properties['server'],
  774. 'sql_query' => $this->properties['sql_query'],
  775. 'is_browse_distinct' => $this->properties['is_browse_distinct'],
  776. 'goto' => $this->properties['goto'],
  777. ];
  778. return [
  779. 'move_backward_buttons' => $moveBackwardButtons,
  780. 'page_selector' => $pageSelector,
  781. 'move_forward_buttons' => $moveForwardButtons,
  782. 'number_total_page' => $numberTotalPage,
  783. 'has_show_all' => $GLOBALS['cfg']['ShowAll'] || ($this->properties['unlim_num_rows'] <= 500),
  784. 'hidden_fields' => $hiddenFields,
  785. 'session_max_rows' => $isShowingAll ? $GLOBALS['cfg']['MaxRows'] : 'all',
  786. 'is_showing_all' => $isShowingAll,
  787. 'max_rows' => $_SESSION['tmpval']['max_rows'],
  788. 'pos' => $_SESSION['tmpval']['pos'],
  789. 'sort_by_key' => $sortByKeyData,
  790. ];
  791. }
  792. /**
  793. * Prepare move backward buttons - previous and first
  794. *
  795. * @see getTableNavigation()
  796. *
  797. * @param string $htmlSqlQuery the sql encoded by html special characters
  798. * @param int $posPrev the offset for the "previous" page
  799. *
  800. * @return string html content
  801. *
  802. * @access private
  803. */
  804. private function getMoveBackwardButtonsForTableNavigation(
  805. $htmlSqlQuery,
  806. $posPrev
  807. ) {
  808. return $this->getTableNavigationButton(
  809. '&lt;&lt;',
  810. _pgettext('First page', 'Begin'),
  811. 0,
  812. $htmlSqlQuery,
  813. true
  814. )
  815. . $this->getTableNavigationButton(
  816. '&lt;',
  817. _pgettext('Previous page', 'Previous'),
  818. $posPrev,
  819. $htmlSqlQuery,
  820. true
  821. );
  822. }
  823. /**
  824. * Prepare move forward buttons - next and last
  825. *
  826. * @see getTableNavigation()
  827. *
  828. * @param string $htmlSqlQuery the sql encoded by htmlspecialchars()
  829. * @param int $posNext the offset for the "next" page
  830. * @param bool $isInnodb whether it's InnoDB or not
  831. *
  832. * @return string html content
  833. *
  834. * @access private
  835. */
  836. private function getMoveForwardButtonsForTableNavigation(
  837. $htmlSqlQuery,
  838. $posNext,
  839. $isInnodb
  840. ) {
  841. // display the Next button
  842. $buttonsHtml = $this->getTableNavigationButton(
  843. '&gt;',
  844. _pgettext('Next page', 'Next'),
  845. $posNext,
  846. $htmlSqlQuery,
  847. false
  848. );
  849. // prepare some options for the End button
  850. if ($isInnodb && $this->properties['unlim_num_rows'] > $GLOBALS['cfg']['MaxExactCount']) {
  851. $inputForRealEnd = '<input id="real_end_input" type="hidden" name="find_real_end" value="1">';
  852. // no backquote around this message
  853. $onclick = '';
  854. } else {
  855. $inputForRealEnd = $onclick = '';
  856. }
  857. $maxRows = (int) $_SESSION['tmpval']['max_rows'];
  858. $onsubmit = 'onsubmit="return '
  859. . (intval($_SESSION['tmpval']['pos'])
  860. + $maxRows
  861. < $this->properties['unlim_num_rows']
  862. && $this->properties['num_rows'] >= $maxRows
  863. ? 'true'
  864. : 'false') . '"';
  865. // display the End button
  866. return $buttonsHtml . $this->getTableNavigationButton(
  867. '&gt;&gt;',
  868. _pgettext('Last page', 'End'),
  869. @((int) ceil(
  870. $this->properties['unlim_num_rows']
  871. / $_SESSION['tmpval']['max_rows']
  872. ) - 1) * $maxRows,
  873. $htmlSqlQuery,
  874. false,
  875. $onsubmit,
  876. $inputForRealEnd,
  877. $onclick
  878. );
  879. }
  880. /**
  881. * Get the headers of the results table, for all of the columns
  882. *
  883. * @see getTableHeaders()
  884. *
  885. * @param array $displayParts which elements to display
  886. * @param array $analyzedSqlResults analyzed sql results
  887. * @param array $sortExpression sort expression
  888. * @param array $sortExpressionNoDirection sort expression
  889. * without direction
  890. * @param array $sortDirection sort direction
  891. * @param bool $isLimitedDisplay with limited operations
  892. * or not
  893. * @param string $unsortedSqlQuery query without the sort part
  894. *
  895. * @return string html content
  896. *
  897. * @access private
  898. */
  899. private function getTableHeadersForColumns(
  900. array $displayParts,
  901. array $analyzedSqlResults,
  902. array $sortExpression,
  903. array $sortExpressionNoDirection,
  904. array $sortDirection,
  905. $isLimitedDisplay,
  906. $unsortedSqlQuery
  907. ) {
  908. // required to generate sort links that will remember whether the
  909. // "Show all" button has been clicked
  910. $sqlMd5 = md5($this->properties['server'] . $this->properties['db'] . $this->properties['sql_query']);
  911. $sessionMaxRows = $isLimitedDisplay
  912. ? 0
  913. : $_SESSION['tmpval']['query'][$sqlMd5]['max_rows'];
  914. // Following variable are needed for use in isset/empty or
  915. // use with array indexes/safe use in the for loop
  916. $highlightColumns = $this->properties['highlight_columns'];
  917. /** @var FieldMetadata[] $fieldsMeta */
  918. $fieldsMeta = $this->properties['fields_meta'];
  919. // Prepare Display column comments if enabled
  920. // ($GLOBALS['cfg']['ShowBrowseComments']).
  921. $commentsMap = $this->getTableCommentsArray($analyzedSqlResults);
  922. [$colOrder, $colVisib] = $this->getColumnParams($analyzedSqlResults);
  923. // optimize: avoid calling a method on each iteration
  924. $numberOfColumns = $this->properties['fields_cnt'];
  925. $columns = [];
  926. for ($j = 0; $j < $numberOfColumns; $j++) {
  927. // PHP 7.4 fix for accessing array offset on bool
  928. $colVisibCurrent = is_array($colVisib) && isset($colVisib[$j]) ? $colVisib[$j] : null;
  929. // assign $i with the appropriate column order
  930. $i = $colOrder ? $colOrder[$j] : $j;
  931. // See if this column should get highlight because it's used in the
  932. // where-query.
  933. $name = $fieldsMeta[$i]->name;
  934. $conditionField = isset($highlightColumns[$name])
  935. || isset($highlightColumns[Util::backquote($name)]);
  936. // Prepare comment-HTML-wrappers for each row, if defined/enabled.
  937. $comments = $this->getCommentForRow($commentsMap, $fieldsMeta[$i]);
  938. $displayParams = $this->properties['display_params'] ?? [];
  939. if (($displayParts['sort_lnk'] == '1') && ! $isLimitedDisplay) {
  940. [$orderLink, $sortedHeaderData] = $this->getOrderLinkAndSortedHeaderHtml(
  941. $fieldsMeta[$i],
  942. $sortExpression,
  943. $sortExpressionNoDirection,
  944. $i,
  945. $unsortedSqlQuery,
  946. $sessionMaxRows,
  947. $comments,
  948. $sortDirection,
  949. $colVisib,
  950. $colVisibCurrent
  951. );
  952. $columns[] = $sortedHeaderData;
  953. $displayParams['desc'][] = ' <th '
  954. . 'class="draggable'
  955. . ($conditionField ? ' condition' : '')
  956. . '" data-column="' . htmlspecialchars($fieldsMeta[$i]->name)
  957. . '">' . "\n" . $orderLink . $comments . ' </th>' . "\n";
  958. } else {
  959. // Results can't be sorted
  960. $columns[] = $this->getDraggableClassForNonSortableColumns(
  961. $colVisib,
  962. $colVisibCurrent,
  963. $conditionField,
  964. $fieldsMeta[$i],
  965. $comments
  966. );
  967. $displayParams['desc'][] = ' <th '
  968. . 'class="draggable'
  969. . ($conditionField ? ' condition"' : '')
  970. . '" data-column="' . htmlspecialchars((string) $fieldsMeta[$i]->name)
  971. . '"> '
  972. . htmlspecialchars((string) $fieldsMeta[$i]->name)
  973. . $comments . ' </th>';
  974. }
  975. $this->properties['display_params'] = $displayParams;
  976. }
  977. return $this->template->render('display/results/table_headers_for_columns', [
  978. 'is_sortable' => $displayParts['sort_lnk'] == '1' && ! $isLimitedDisplay,
  979. 'columns' => $columns,
  980. ]);
  981. }
  982. /**
  983. * Get the headers of the results table
  984. *
  985. * @see getTable()
  986. *
  987. * @param array $displayParts which elements to display
  988. * @param array $analyzedSqlResults analyzed sql results
  989. * @param string $unsortedSqlQuery the unsorted sql query
  990. * @param array $sortExpression sort expression
  991. * @param array|string $sortExpressionNoDirection sort expression without direction
  992. * @param array $sortDirection sort direction
  993. * @param bool $isLimitedDisplay with limited operations or not
  994. *
  995. * @return array
  996. */
  997. private function getTableHeaders(
  998. array &$displayParts,
  999. array $analyzedSqlResults,
  1000. $unsortedSqlQuery,
  1001. array $sortExpression = [],
  1002. $sortExpressionNoDirection = '',
  1003. array $sortDirection = [],
  1004. $isLimitedDisplay = false
  1005. ): array {
  1006. // Needed for use in isset/empty or
  1007. // use with array indexes/safe use in foreach
  1008. $printView = $this->properties['printview'];
  1009. $displayParams = $this->properties['display_params'];
  1010. // Output data needed for column reordering and show/hide column
  1011. $columnOrder = $this->getDataForResettingColumnOrder($analyzedSqlResults);
  1012. $displayParams['emptypre'] = 0;
  1013. $displayParams['emptyafter'] = 0;
  1014. $displayParams['textbtn'] = '';
  1015. $fullOrPartialTextLink = '';
  1016. $this->properties['display_params'] = $displayParams;
  1017. // Display options (if we are not in print view)
  1018. $optionsBlock = [];
  1019. if (! (isset($printView) && ($printView == '1')) && ! $isLimitedDisplay) {
  1020. $optionsBlock = $this->getOptionsBlock();
  1021. // prepare full/partial text button or link
  1022. $fullOrPartialTextLink = $this->getFullOrPartialTextButtonOrLink();
  1023. }
  1024. // 1. Set $colspan and generate html with full/partial
  1025. // text button or link
  1026. [$colspan, $buttonHtml] = $this->getFieldVisibilityParams($displayParts, $fullOrPartialTextLink);
  1027. // 2. Displays the fields' name
  1028. // 2.0 If sorting links should be used, checks if the query is a "JOIN"
  1029. // statement (see 2.1.3)
  1030. // See if we have to highlight any header fields of a WHERE query.
  1031. // Uses SQL-Parser results.
  1032. $this->setHighlightedColumnGlobalField($analyzedSqlResults);
  1033. // Get the headers for all of the columns
  1034. $tableHeadersForColumns = $this->getTableHeadersForColumns(
  1035. $displayParts,
  1036. $analyzedSqlResults,
  1037. $sortExpression,
  1038. $sortExpressionNoDirection,
  1039. $sortDirection,
  1040. $isLimitedDisplay,
  1041. $unsortedSqlQuery
  1042. );
  1043. // Display column at rightside - checkboxes or empty column
  1044. $columnAtRightSide = '';
  1045. if (! $printView) {
  1046. $columnAtRightSide = $this->getColumnAtRightSide($displayParts, $fullOrPartialTextLink, $colspan);
  1047. }
  1048. return [
  1049. 'column_order' => $columnOrder,
  1050. 'options' => $optionsBlock,
  1051. 'has_bulk_actions_form' => $displayParts['del_lnk'] === self::DELETE_ROW
  1052. || $displayParts['del_lnk'] === self::KILL_PROCESS,
  1053. 'button' => $buttonHtml,
  1054. 'table_headers_for_columns' => $tableHeadersForColumns,
  1055. 'column_at_right_side' => $columnAtRightSide,
  1056. ];
  1057. }
  1058. /**
  1059. * Prepare unsorted sql query and sort by key drop down
  1060. *
  1061. * @see getTableHeaders()
  1062. *
  1063. * @param array $analyzedSqlResults analyzed sql results
  1064. * @param array|null $sortExpression sort expression
  1065. *
  1066. * @return array two element array - $unsorted_sql_query, $drop_down_html
  1067. *
  1068. * @access private
  1069. */
  1070. private function getUnsortedSqlAndSortByKeyDropDown(
  1071. array $analyzedSqlResults,
  1072. ?array $sortExpression
  1073. ) {
  1074. $dropDownData = [];
  1075. $unsortedSqlQuery = Query::replaceClause(
  1076. $analyzedSqlResults['statement'],
  1077. $analyzedSqlResults['parser']->list,
  1078. 'ORDER BY',
  1079. ''
  1080. );
  1081. // Data is sorted by indexes only if it there is only one table.
  1082. if ($this->isSelect($analyzedSqlResults)) {
  1083. // grab indexes data:
  1084. $indexes = Index::getFromTable($this->properties['table'], $this->properties['db']);
  1085. // do we have any index?
  1086. if (! empty($indexes)) {
  1087. $dropDownData = $this->getSortByKeyDropDown($indexes, $sortExpression, $unsortedSqlQuery);
  1088. }
  1089. }
  1090. return [$unsortedSqlQuery, $dropDownData];
  1091. }
  1092. /**
  1093. * Prepare sort by key dropdown - html code segment
  1094. *
  1095. * @see getTableHeaders()
  1096. *
  1097. * @param Index[] $indexes the indexes of the table for sort criteria
  1098. * @param array|null $sortExpression the sort expression
  1099. * @param string $unsortedSqlQuery the unsorted sql query
  1100. *
  1101. * @return array
  1102. */
  1103. private function getSortByKeyDropDown(
  1104. $indexes,
  1105. ?array $sortExpression,
  1106. $unsortedSqlQuery
  1107. ): array {
  1108. $hiddenFields = [
  1109. 'db' => $this->properties['db'],
  1110. 'table' => $this->properties['table'],
  1111. 'server' => $this->properties['server'],
  1112. 'sort_by_key' => '1',
  1113. ];
  1114. // Keep the number of rows (25, 50, 100, ...) when changing sort key value
  1115. if (isset($_SESSION['tmpval']) && isset($_SESSION['tmpval']['max_rows'])) {
  1116. $hiddenFields['session_max_rows'] = $_SESSION['tmpval']['max_rows'];
  1117. }
  1118. $isIndexUsed = false;
  1119. $localOrder = is_array($sortExpression) ? implode(', ', $sortExpression) : '';
  1120. $options = [];
  1121. foreach ($indexes as $index) {
  1122. $ascSort = '`'
  1123. . implode('` ASC, `', array_keys($index->getColumns()))
  1124. . '` ASC';
  1125. $descSort = '`'
  1126. . implode('` DESC, `', array_keys($index->getColumns()))
  1127. . '` DESC';
  1128. $isIndexUsed = $isIndexUsed
  1129. || $localOrder === $ascSort
  1130. || $localOrder === $descSort;
  1131. $unsortedSqlQueryFirstPart = $unsortedSqlQuery;
  1132. $unsortedSqlQuerySecondPart = '';
  1133. if (
  1134. preg_match(
  1135. '@(.*)([[:space:]](LIMIT (.*)|PROCEDURE (.*)|FOR UPDATE|LOCK IN SHARE MODE))@is',
  1136. $unsortedSqlQuery,
  1137. $myReg
  1138. )
  1139. ) {
  1140. $unsortedSqlQueryFirstPart = $myReg[1];
  1141. $unsortedSqlQuerySecondPart = $myReg[2];
  1142. }
  1143. $options[] = [
  1144. 'value' => $unsortedSqlQueryFirstPart . ' ORDER BY '
  1145. . $ascSort . $unsortedSqlQuerySecondPart,
  1146. 'content' => $index->getName() . ' (ASC)',
  1147. 'is_selected' => $localOrder === $ascSort,
  1148. ];
  1149. $options[] = [
  1150. 'value' => $unsortedSqlQueryFirstPart . ' ORDER BY '
  1151. . $descSort . $unsortedSqlQuerySecondPart,
  1152. 'content' => $index->getName() . ' (DESC)',
  1153. 'is_selected' => $localOrder === $descSort,
  1154. ];
  1155. }
  1156. $options[] = [
  1157. 'value' => $unsortedSqlQuery,
  1158. 'content' => __('None'),
  1159. 'is_selected' => ! $isIndexUsed,
  1160. ];
  1161. return ['hidden_fields' => $hiddenFields, 'options' => $options];
  1162. }
  1163. /**
  1164. * Set column span, row span and prepare html with full/partial
  1165. * text button or link
  1166. *
  1167. * @see getTableHeaders()
  1168. *
  1169. * @param array $displayParts which elements to display
  1170. * @param string $fullOrPartialTextLink full/partial link or text button
  1171. *
  1172. * @return array 2 element array - $colspan, $button_html
  1173. *
  1174. * @access private
  1175. */
  1176. private function getFieldVisibilityParams(
  1177. array &$displayParts,
  1178. $fullOrPartialTextLink
  1179. ) {
  1180. $buttonHtml = '';
  1181. $displayParams = $this->properties['display_params'];
  1182. // 1. Displays the full/partial text button (part 1)...
  1183. $buttonHtml .= '<thead class="table-light"><tr>' . "\n";
  1184. $emptyPreCondition = $displayParts['edit_lnk'] != self::NO_EDIT_OR_DELETE
  1185. && $displayParts['del_lnk'] != self::NO_EDIT_OR_DELETE;
  1186. $colspan = $emptyPreCondition ? ' colspan="4"'
  1187. : '';
  1188. $leftOrBoth = $GLOBALS['cfg']['RowActionLinks'] === self::POSITION_LEFT
  1189. || $GLOBALS['cfg']['RowActionLinks'] === self::POSITION_BOTH;
  1190. // ... before the result table
  1191. if (
  1192. ($displayParts['edit_lnk'] === self::NO_EDIT_OR_DELETE)
  1193. && ($displayParts['del_lnk'] === self::NO_EDIT_OR_DELETE)
  1194. && ($displayParts['text_btn'] == '1')
  1195. ) {
  1196. $displayParams['emptypre'] = $emptyPreCondition ? 4 : 0;
  1197. } elseif ($leftOrBoth && ($displayParts['text_btn'] == '1')) {
  1198. // ... at the left column of the result table header if possible
  1199. // and required
  1200. $displayParams['emptypre'] = $emptyPreCondition ? 4 : 0;
  1201. $buttonHtml .= '<th class="column_action sticky d-print-none" ' . $colspan
  1202. . '>' . $fullOrPartialTextLink . '</th>';
  1203. } elseif (
  1204. $leftOrBoth
  1205. && (($displayParts['edit_lnk'] != self::NO_EDIT_OR_DELETE)
  1206. || ($displayParts['del_lnk'] != self::NO_EDIT_OR_DELETE))
  1207. ) {
  1208. // ... elseif no button, displays empty(ies) col(s) if required
  1209. $displayParams['emptypre'] = $emptyPreCondition ? 4 : 0;
  1210. $buttonHtml .= '<td ' . $colspan . '></td>';
  1211. } elseif ($GLOBALS['cfg']['RowActionLinks'] === self::POSITION_NONE) {
  1212. // ... elseif display an empty column if the actions links are
  1213. // disabled to match the rest of the table
  1214. $buttonHtml .= '<th class="column_action sticky"></th>';
  1215. }
  1216. $this->properties['display_params'] = $displayParams;
  1217. return [
  1218. $colspan,
  1219. $buttonHtml,
  1220. ];
  1221. }
  1222. /**
  1223. * Get table comments as array
  1224. *
  1225. * @see getTableHeaders()
  1226. *
  1227. * @param array $analyzedSqlResults analyzed sql results
  1228. *
  1229. * @return array table comments
  1230. *
  1231. * @access private
  1232. */
  1233. private function getTableCommentsArray(array $analyzedSqlResults)
  1234. {
  1235. if (! $GLOBALS['cfg']['ShowBrowseComments'] || empty($analyzedSqlResults['statement']->from)) {
  1236. return [];
  1237. }
  1238. $ret = [];
  1239. foreach ($analyzedSqlResults['statement']->from as $field) {
  1240. if (empty($field->table)) {
  1241. continue;
  1242. }
  1243. $ret[$field->table] = $this->relation->getComments(
  1244. empty($field->database) ? $this->properties['db'] : $field->database,
  1245. $field->table
  1246. );
  1247. }
  1248. return $ret;
  1249. }
  1250. /**
  1251. * Set global array for store highlighted header fields
  1252. *
  1253. * @see getTableHeaders()
  1254. *
  1255. * @param array $analyzedSqlResults analyzed sql results
  1256. *
  1257. * @access private
  1258. */
  1259. private function setHighlightedColumnGlobalField(array $analyzedSqlResults): void

Large files files are truncated, but you can click here to view the full file