1 | <?php |
||||
2 | /** |
||||
3 | * Hold the PhpMyAdmin\Display\Results class |
||||
4 | */ |
||||
5 | |||||
6 | declare(strict_types=1); |
||||
7 | |||||
8 | namespace PhpMyAdmin\Display; |
||||
9 | |||||
10 | use PhpMyAdmin\Config\SpecialSchemaLinks; |
||||
11 | use PhpMyAdmin\Core; |
||||
12 | use PhpMyAdmin\DatabaseInterface; |
||||
13 | use PhpMyAdmin\Html\Generator; |
||||
14 | use PhpMyAdmin\Index; |
||||
15 | use PhpMyAdmin\Message; |
||||
16 | use PhpMyAdmin\Plugins\Transformations\Output\Text_Octetstream_Sql; |
||||
17 | use PhpMyAdmin\Plugins\Transformations\Output\Text_Plain_Json; |
||||
18 | use PhpMyAdmin\Plugins\Transformations\Output\Text_Plain_Sql; |
||||
19 | use PhpMyAdmin\Plugins\Transformations\Text_Plain_Link; |
||||
20 | use PhpMyAdmin\Plugins\TransformationsPlugin; |
||||
21 | use PhpMyAdmin\Relation; |
||||
22 | use PhpMyAdmin\Response; |
||||
23 | use PhpMyAdmin\Sanitize; |
||||
24 | use PhpMyAdmin\Sql; |
||||
25 | use PhpMyAdmin\SqlParser\Statements\SelectStatement; |
||||
26 | use PhpMyAdmin\SqlParser\Utils\Query; |
||||
27 | use PhpMyAdmin\Table; |
||||
28 | use PhpMyAdmin\Template; |
||||
29 | use PhpMyAdmin\Transformations; |
||||
30 | use PhpMyAdmin\Url; |
||||
31 | use PhpMyAdmin\Util; |
||||
32 | use stdClass; |
||||
33 | use const MYSQLI_TYPE_BIT; |
||||
34 | use function array_filter; |
||||
35 | use function array_key_exists; |
||||
36 | use function array_keys; |
||||
37 | use function array_merge; |
||||
38 | use function array_shift; |
||||
39 | use function bin2hex; |
||||
40 | use function ceil; |
||||
41 | use function class_exists; |
||||
42 | use function count; |
||||
43 | use function explode; |
||||
44 | use function file_exists; |
||||
45 | use function floor; |
||||
46 | use function function_exists; |
||||
47 | use function htmlspecialchars; |
||||
48 | use function implode; |
||||
49 | use function intval; |
||||
50 | use function is_array; |
||||
51 | use function is_object; |
||||
52 | use function json_encode; |
||||
53 | use function mb_check_encoding; |
||||
54 | use function mb_strlen; |
||||
55 | use function mb_strpos; |
||||
56 | use function mb_strtolower; |
||||
57 | use function mb_strtoupper; |
||||
58 | use function mb_substr; |
||||
59 | use function md5; |
||||
60 | use function mt_rand; |
||||
61 | use function pack; |
||||
62 | use function preg_match; |
||||
63 | use function preg_replace; |
||||
64 | use function str_replace; |
||||
65 | use function strcasecmp; |
||||
66 | use function strip_tags; |
||||
67 | use function stripos; |
||||
68 | use function strlen; |
||||
69 | use function strpos; |
||||
70 | use function strtoupper; |
||||
71 | use function substr; |
||||
72 | use function trim; |
||||
73 | |||||
74 | /** |
||||
75 | * Handle all the functionalities related to displaying results |
||||
76 | * of sql queries, stored procedure, browsing sql processes or |
||||
77 | * displaying binary log. |
||||
78 | */ |
||||
79 | class Results |
||||
80 | { |
||||
81 | // Define constants |
||||
82 | public const NO_EDIT_OR_DELETE = 'nn'; |
||||
83 | public const UPDATE_ROW = 'ur'; |
||||
84 | public const DELETE_ROW = 'dr'; |
||||
85 | public const KILL_PROCESS = 'kp'; |
||||
86 | |||||
87 | public const POSITION_LEFT = 'left'; |
||||
88 | public const POSITION_RIGHT = 'right'; |
||||
89 | public const POSITION_BOTH = 'both'; |
||||
90 | public const POSITION_NONE = 'none'; |
||||
91 | |||||
92 | public const DISPLAY_FULL_TEXT = 'F'; |
||||
93 | public const DISPLAY_PARTIAL_TEXT = 'P'; |
||||
94 | |||||
95 | public const HEADER_FLIP_TYPE_AUTO = 'auto'; |
||||
96 | public const HEADER_FLIP_TYPE_CSS = 'css'; |
||||
97 | public const HEADER_FLIP_TYPE_FAKE = 'fake'; |
||||
98 | |||||
99 | public const DATE_FIELD = 'date'; |
||||
100 | public const DATETIME_FIELD = 'datetime'; |
||||
101 | public const TIMESTAMP_FIELD = 'timestamp'; |
||||
102 | public const TIME_FIELD = 'time'; |
||||
103 | public const STRING_FIELD = 'string'; |
||||
104 | public const GEOMETRY_FIELD = 'geometry'; |
||||
105 | public const BLOB_FIELD = 'BLOB'; |
||||
106 | public const BINARY_FIELD = 'BINARY'; |
||||
107 | |||||
108 | public const RELATIONAL_KEY = 'K'; |
||||
109 | public const RELATIONAL_DISPLAY_COLUMN = 'D'; |
||||
110 | |||||
111 | public const GEOMETRY_DISP_GEOM = 'GEOM'; |
||||
112 | public const GEOMETRY_DISP_WKT = 'WKT'; |
||||
113 | public const GEOMETRY_DISP_WKB = 'WKB'; |
||||
114 | |||||
115 | public const SMART_SORT_ORDER = 'SMART'; |
||||
116 | public const ASCENDING_SORT_DIR = 'ASC'; |
||||
117 | public const DESCENDING_SORT_DIR = 'DESC'; |
||||
118 | |||||
119 | public const TABLE_TYPE_INNO_DB = 'InnoDB'; |
||||
120 | public const ALL_ROWS = 'all'; |
||||
121 | public const QUERY_TYPE_SELECT = 'SELECT'; |
||||
122 | |||||
123 | public const ROUTINE_PROCEDURE = 'procedure'; |
||||
124 | public const ROUTINE_FUNCTION = 'function'; |
||||
125 | |||||
126 | public const ACTION_LINK_CONTENT_ICONS = 'icons'; |
||||
127 | public const ACTION_LINK_CONTENT_TEXT = 'text'; |
||||
128 | |||||
129 | // Declare global fields |
||||
130 | |||||
131 | /** array with properties of the class */ |
||||
132 | private $_property_array = [ |
||||
133 | |||||
134 | /** integer server id */ |
||||
135 | 'server' => null, |
||||
136 | |||||
137 | /** string Database name */ |
||||
138 | 'db' => null, |
||||
139 | |||||
140 | /** string Table name */ |
||||
141 | 'table' => null, |
||||
142 | |||||
143 | /** string the URL to go back in case of errors */ |
||||
144 | 'goto' => null, |
||||
145 | |||||
146 | /** string the SQL query */ |
||||
147 | 'sql_query' => null, |
||||
148 | |||||
149 | /** |
||||
150 | * integer the total number of rows returned by the SQL query without any |
||||
151 | * appended "LIMIT" clause programmatically |
||||
152 | */ |
||||
153 | 'unlim_num_rows' => null, |
||||
154 | |||||
155 | /** array meta information about fields */ |
||||
156 | 'fields_meta' => null, |
||||
157 | |||||
158 | /** boolean */ |
||||
159 | 'is_count' => null, |
||||
160 | |||||
161 | /** integer */ |
||||
162 | 'is_export' => null, |
||||
163 | |||||
164 | /** boolean */ |
||||
165 | 'is_func' => null, |
||||
166 | |||||
167 | /** integer */ |
||||
168 | 'is_analyse' => null, |
||||
169 | |||||
170 | /** integer the total number of rows returned by the SQL query */ |
||||
171 | 'num_rows' => null, |
||||
172 | |||||
173 | /** integer the total number of fields returned by the SQL query */ |
||||
174 | 'fields_cnt' => null, |
||||
175 | |||||
176 | /** double time taken for execute the SQL query */ |
||||
177 | 'querytime' => null, |
||||
178 | |||||
179 | /** string path for theme images directory */ |
||||
180 | 'pma_theme_image' => null, |
||||
181 | |||||
182 | /** string */ |
||||
183 | 'text_dir' => null, |
||||
184 | |||||
185 | /** boolean */ |
||||
186 | 'is_maint' => null, |
||||
187 | |||||
188 | /** boolean */ |
||||
189 | 'is_explain' => null, |
||||
190 | |||||
191 | /** boolean */ |
||||
192 | 'is_show' => null, |
||||
193 | |||||
194 | /** boolean */ |
||||
195 | 'is_browse_distinct' => null, |
||||
196 | |||||
197 | /** array table definitions */ |
||||
198 | 'showtable' => null, |
||||
199 | |||||
200 | /** string */ |
||||
201 | 'printview' => null, |
||||
202 | |||||
203 | /** string URL query */ |
||||
204 | 'url_query' => null, |
||||
205 | |||||
206 | /** array column names to highlight */ |
||||
207 | 'highlight_columns' => null, |
||||
208 | |||||
209 | /** array holding various display information */ |
||||
210 | 'display_params' => null, |
||||
211 | |||||
212 | /** array mime types information of fields */ |
||||
213 | 'mime_map' => null, |
||||
214 | |||||
215 | /** boolean */ |
||||
216 | 'editable' => null, |
||||
217 | |||||
218 | /** random unique ID to distinguish result set */ |
||||
219 | 'unique_id' => null, |
||||
220 | |||||
221 | /** where clauses for each row, each table in the row */ |
||||
222 | 'whereClauseMap' => [], |
||||
223 | ]; |
||||
224 | |||||
225 | /** |
||||
226 | * This variable contains the column transformation information |
||||
227 | * for some of the system databases. |
||||
228 | * One element of this array represent all relevant columns in all tables in |
||||
229 | * one specific database |
||||
230 | */ |
||||
231 | public $transformation_info; |
||||
232 | |||||
233 | /** @var Relation */ |
||||
234 | private $relation; |
||||
235 | |||||
236 | /** @var Transformations */ |
||||
237 | private $transformations; |
||||
238 | |||||
239 | /** @var Template */ |
||||
240 | public $template; |
||||
241 | |||||
242 | /** |
||||
243 | * @param string $db the database name |
||||
244 | * @param string $table the table name |
||||
245 | * @param int $server the server id |
||||
246 | * @param string $goto the URL to go back in case of errors |
||||
247 | * @param string $sql_query the SQL query |
||||
248 | * |
||||
249 | * @access public |
||||
250 | */ |
||||
251 | 108 | public function __construct($db, $table, $server, $goto, $sql_query) |
|||
252 | { |
||||
253 | 108 | $this->relation = new Relation($GLOBALS['dbi']); |
|||
254 | 108 | $this->transformations = new Transformations(); |
|||
255 | 108 | $this->template = new Template(); |
|||
256 | |||||
257 | 108 | $this->setDefaultTransformations(); |
|||
258 | |||||
259 | 108 | $this->__set('db', $db); |
|||
260 | 108 | $this->__set('table', $table); |
|||
261 | 108 | $this->__set('server', $server); |
|||
262 | 108 | $this->__set('goto', $goto); |
|||
263 | 108 | $this->__set('sql_query', $sql_query); |
|||
264 | 108 | $this->__set('unique_id', mt_rand()); |
|||
265 | 108 | } |
|||
266 | |||||
267 | /** |
||||
268 | * Get any property of this class |
||||
269 | * |
||||
270 | * @param string $property name of the property |
||||
271 | * |
||||
272 | * @return mixed|null if property exist, value of the relevant property |
||||
273 | */ |
||||
274 | 60 | public function __get($property) |
|||
275 | { |
||||
276 | 60 | return $this->_property_array[$property] ?? null; |
|||
277 | } |
||||
278 | |||||
279 | /** |
||||
280 | * Set values for any property of this class |
||||
281 | * |
||||
282 | * @param string $property name of the property |
||||
283 | * @param mixed $value value to set |
||||
284 | * |
||||
285 | * @return void |
||||
286 | */ |
||||
287 | 108 | public function __set($property, $value) |
|||
288 | { |
||||
289 | 108 | if (! array_key_exists($property, $this->_property_array)) { |
|||
290 | return; |
||||
291 | } |
||||
292 | |||||
293 | 108 | $this->_property_array[$property] = $value; |
|||
294 | 108 | } |
|||
295 | |||||
296 | /** |
||||
297 | * Sets default transformations for some columns |
||||
298 | * |
||||
299 | * @return void |
||||
300 | */ |
||||
301 | 108 | private function setDefaultTransformations() |
|||
302 | { |
||||
303 | $json_highlighting_data = [ |
||||
304 | 108 | 'libraries/classes/Plugins/Transformations/Output/Text_Plain_Json.php', |
|||
305 | Text_Plain_Json::class, |
||||
306 | 'Text_Plain', |
||||
307 | ]; |
||||
308 | $sql_highlighting_data = [ |
||||
309 | 108 | 'libraries/classes/Plugins/Transformations/Output/Text_Plain_Sql.php', |
|||
310 | Text_Plain_Sql::class, |
||||
311 | 'Text_Plain', |
||||
312 | ]; |
||||
313 | $blob_sql_highlighting_data = [ |
||||
314 | 108 | 'libraries/classes/Plugins/Transformations/Output/Text_Octetstream_Sql.php', |
|||
315 | Text_Octetstream_Sql::class, |
||||
316 | 'Text_Octetstream', |
||||
317 | ]; |
||||
318 | $link_data = [ |
||||
319 | 108 | 'libraries/classes/Plugins/Transformations/Text_Plain_Link.php', |
|||
320 | Text_Plain_Link::class, |
||||
321 | 'Text_Plain', |
||||
322 | ]; |
||||
323 | 108 | $this->transformation_info = [ |
|||
324 | 'information_schema' => [ |
||||
325 | 108 | 'events' => ['event_definition' => $sql_highlighting_data], |
|||
326 | 108 | 'processlist' => ['info' => $sql_highlighting_data], |
|||
327 | 108 | 'routines' => ['routine_definition' => $sql_highlighting_data], |
|||
328 | 108 | 'triggers' => ['action_statement' => $sql_highlighting_data], |
|||
329 | 108 | 'views' => ['view_definition' => $sql_highlighting_data], |
|||
330 | ], |
||||
331 | 'mysql' => [ |
||||
332 | 'event' => [ |
||||
333 | 108 | 'body' => $blob_sql_highlighting_data, |
|||
334 | 108 | 'body_utf8' => $blob_sql_highlighting_data, |
|||
335 | ], |
||||
336 | 108 | 'general_log' => ['argument' => $sql_highlighting_data], |
|||
337 | 108 | 'help_category' => ['url' => $link_data], |
|||
338 | 'help_topic' => [ |
||||
339 | 108 | 'example' => $sql_highlighting_data, |
|||
340 | 108 | 'url' => $link_data, |
|||
341 | ], |
||||
342 | 'proc' => [ |
||||
343 | 108 | 'param_list' => $blob_sql_highlighting_data, |
|||
344 | 108 | 'returns' => $blob_sql_highlighting_data, |
|||
345 | 108 | 'body' => $blob_sql_highlighting_data, |
|||
346 | 108 | 'body_utf8' => $blob_sql_highlighting_data, |
|||
347 | ], |
||||
348 | 108 | 'slow_log' => ['sql_text' => $sql_highlighting_data], |
|||
349 | ], |
||||
350 | ]; |
||||
351 | |||||
352 | 108 | $cfgRelation = $this->relation->getRelationsParam(); |
|||
353 | 108 | if (! $cfgRelation['db']) { |
|||
354 | 108 | return; |
|||
355 | } |
||||
356 | |||||
357 | $this->transformation_info[$cfgRelation['db']] = []; |
||||
358 | $relDb = &$this->transformation_info[$cfgRelation['db']]; |
||||
359 | if (! empty($cfgRelation['history'])) { |
||||
360 | $relDb[$cfgRelation['history']] = ['sqlquery' => $sql_highlighting_data]; |
||||
361 | } |
||||
362 | if (! empty($cfgRelation['bookmark'])) { |
||||
363 | $relDb[$cfgRelation['bookmark']] = ['query' => $sql_highlighting_data]; |
||||
364 | } |
||||
365 | if (! empty($cfgRelation['tracking'])) { |
||||
366 | $relDb[$cfgRelation['tracking']] = [ |
||||
367 | 'schema_sql' => $sql_highlighting_data, |
||||
368 | 'data_sql' => $sql_highlighting_data, |
||||
369 | ]; |
||||
370 | } |
||||
371 | if (! empty($cfgRelation['favorite'])) { |
||||
372 | $relDb[$cfgRelation['favorite']] = ['tables' => $json_highlighting_data]; |
||||
373 | } |
||||
374 | if (! empty($cfgRelation['recent'])) { |
||||
375 | $relDb[$cfgRelation['recent']] = ['tables' => $json_highlighting_data]; |
||||
376 | } |
||||
377 | if (! empty($cfgRelation['savedsearches'])) { |
||||
378 | $relDb[$cfgRelation['savedsearches']] = ['search_data' => $json_highlighting_data]; |
||||
379 | } |
||||
380 | if (! empty($cfgRelation['designer_settings'])) { |
||||
381 | $relDb[$cfgRelation['designer_settings']] = ['settings_data' => $json_highlighting_data]; |
||||
382 | } |
||||
383 | if (! empty($cfgRelation['table_uiprefs'])) { |
||||
384 | $relDb[$cfgRelation['table_uiprefs']] = ['prefs' => $json_highlighting_data]; |
||||
385 | } |
||||
386 | if (! empty($cfgRelation['userconfig'])) { |
||||
387 | $relDb[$cfgRelation['userconfig']] = ['config_data' => $json_highlighting_data]; |
||||
388 | } |
||||
389 | if (empty($cfgRelation['export_templates'])) { |
||||
390 | return; |
||||
391 | } |
||||
392 | |||||
393 | $relDb[$cfgRelation['export_templates']] = ['template_data' => $json_highlighting_data]; |
||||
394 | } |
||||
395 | |||||
396 | /** |
||||
397 | * Set properties which were not initialized at the constructor |
||||
398 | * |
||||
399 | * @param int $unlim_num_rows the total number of rows returned by |
||||
400 | * the SQL query without any appended |
||||
401 | * "LIMIT" clause programmatically |
||||
402 | * @param stdClass $fields_meta meta information about fields |
||||
403 | * @param bool $is_count statement is SELECT COUNT |
||||
404 | * @param int $is_export statement contains INTO OUTFILE |
||||
405 | * @param bool $is_func statement contains a function like SUM() |
||||
406 | * @param int $is_analyse statement contains PROCEDURE ANALYSE |
||||
407 | * @param int $num_rows total no. of rows returned by SQL query |
||||
408 | * @param int $fields_cnt total no.of fields returned by SQL query |
||||
409 | * @param double $querytime time taken for execute the SQL query |
||||
410 | * @param string $pmaThemeImage path for theme images directory |
||||
411 | * @param string $text_dir text direction |
||||
412 | * @param bool $is_maint statement contains a maintenance command |
||||
413 | * @param bool $is_explain statement contains EXPLAIN |
||||
414 | * @param bool $is_show statement contains SHOW |
||||
415 | * @param array $showtable table definitions |
||||
416 | * @param string $printview print view was requested |
||||
417 | * @param string $url_query URL query |
||||
418 | * @param bool $editable whether the results set is editable |
||||
419 | * @param bool $is_browse_dist whether browsing distinct values |
||||
420 | * |
||||
421 | * @return void |
||||
422 | */ |
||||
423 | public function setProperties( |
||||
424 | $unlim_num_rows, |
||||
425 | $fields_meta, |
||||
426 | $is_count, |
||||
427 | $is_export, |
||||
428 | $is_func, |
||||
429 | $is_analyse, |
||||
430 | $num_rows, |
||||
431 | $fields_cnt, |
||||
432 | $querytime, |
||||
433 | $pmaThemeImage, |
||||
434 | $text_dir, |
||||
435 | $is_maint, |
||||
436 | $is_explain, |
||||
437 | $is_show, |
||||
438 | $showtable, |
||||
439 | $printview, |
||||
440 | $url_query, |
||||
441 | $editable, |
||||
442 | $is_browse_dist |
||||
443 | ) { |
||||
444 | $this->__set('unlim_num_rows', $unlim_num_rows); |
||||
445 | $this->__set('fields_meta', $fields_meta); |
||||
446 | $this->__set('is_count', $is_count); |
||||
447 | $this->__set('is_export', $is_export); |
||||
448 | $this->__set('is_func', $is_func); |
||||
449 | $this->__set('is_analyse', $is_analyse); |
||||
450 | $this->__set('num_rows', $num_rows); |
||||
451 | $this->__set('fields_cnt', $fields_cnt); |
||||
452 | $this->__set('querytime', $querytime); |
||||
453 | $this->__set('pma_theme_image', $pmaThemeImage); |
||||
454 | $this->__set('text_dir', $text_dir); |
||||
455 | $this->__set('is_maint', $is_maint); |
||||
456 | $this->__set('is_explain', $is_explain); |
||||
457 | $this->__set('is_show', $is_show); |
||||
458 | $this->__set('showtable', $showtable); |
||||
459 | $this->__set('printview', $printview); |
||||
460 | $this->__set('url_query', $url_query); |
||||
461 | $this->__set('editable', $editable); |
||||
462 | $this->__set('is_browse_distinct', $is_browse_dist); |
||||
463 | } |
||||
464 | |||||
465 | /** |
||||
466 | * Defines the parts to display for a print view |
||||
467 | * |
||||
468 | * @param array $displayParts the parts to display |
||||
469 | * |
||||
470 | * @return array the modified display parts |
||||
471 | * |
||||
472 | * @access private |
||||
473 | */ |
||||
474 | private function setDisplayPartsForPrintView(array $displayParts) |
||||
475 | { |
||||
476 | // set all elements to false! |
||||
477 | $displayParts['edit_lnk'] = self::NO_EDIT_OR_DELETE; // no edit link |
||||
478 | $displayParts['del_lnk'] = self::NO_EDIT_OR_DELETE; // no delete link |
||||
479 | $displayParts['sort_lnk'] = (string) '0'; |
||||
480 | $displayParts['nav_bar'] = (string) '0'; |
||||
481 | $displayParts['bkm_form'] = (string) '0'; |
||||
482 | $displayParts['text_btn'] = (string) '0'; |
||||
483 | $displayParts['pview_lnk'] = (string) '0'; |
||||
484 | |||||
485 | return $displayParts; |
||||
486 | } |
||||
487 | |||||
488 | /** |
||||
489 | * Defines the parts to display for a SHOW statement |
||||
490 | * |
||||
491 | * @param array $displayParts the parts to display |
||||
492 | * |
||||
493 | * @return array the modified display parts |
||||
494 | * |
||||
495 | * @access private |
||||
496 | */ |
||||
497 | private function setDisplayPartsForShow(array $displayParts) |
||||
498 | { |
||||
499 | preg_match( |
||||
500 | '@^SHOW[[:space:]]+(VARIABLES|(FULL[[:space:]]+)?' |
||||
501 | . 'PROCESSLIST|STATUS|TABLE|GRANTS|CREATE|LOGS|DATABASES|FIELDS' |
||||
502 | . ')@i', |
||||
503 | $this->__get('sql_query'), |
||||
504 | $which |
||||
505 | ); |
||||
506 | |||||
507 | $bIsProcessList = isset($which[1]); |
||||
508 | if ($bIsProcessList) { |
||||
509 | $str = ' ' . strtoupper($which[1]); |
||||
510 | $bIsProcessList = $bIsProcessList |
||||
511 | && strpos($str, 'PROCESSLIST') > 0; |
||||
512 | } |
||||
513 | |||||
514 | if ($bIsProcessList) { |
||||
515 | // no edit link |
||||
516 | $displayParts['edit_lnk'] = self::NO_EDIT_OR_DELETE; |
||||
517 | // "kill process" type edit link |
||||
518 | $displayParts['del_lnk'] = self::KILL_PROCESS; |
||||
519 | } else { |
||||
520 | // Default case -> no links |
||||
521 | // no edit link |
||||
522 | $displayParts['edit_lnk'] = self::NO_EDIT_OR_DELETE; |
||||
523 | // no delete link |
||||
524 | $displayParts['del_lnk'] = self::NO_EDIT_OR_DELETE; |
||||
525 | } |
||||
526 | // Other settings |
||||
527 | $displayParts['sort_lnk'] = (string) '0'; |
||||
528 | $displayParts['nav_bar'] = (string) '0'; |
||||
529 | $displayParts['bkm_form'] = (string) '1'; |
||||
530 | $displayParts['text_btn'] = (string) '1'; |
||||
531 | $displayParts['pview_lnk'] = (string) '1'; |
||||
532 | |||||
533 | return $displayParts; |
||||
534 | } |
||||
535 | |||||
536 | /** |
||||
537 | * Defines the parts to display for statements not related to data |
||||
538 | * |
||||
539 | * @param array $displayParts the parts to display |
||||
540 | * |
||||
541 | * @return array the modified display parts |
||||
542 | * |
||||
543 | * @access private |
||||
544 | */ |
||||
545 | private function setDisplayPartsForNonData(array $displayParts) |
||||
546 | { |
||||
547 | // Statement is a "SELECT COUNT", a |
||||
548 | // "CHECK/ANALYZE/REPAIR/OPTIMIZE/CHECKSUM", an "EXPLAIN" one or |
||||
549 | // contains a "PROC ANALYSE" part |
||||
550 | $displayParts['edit_lnk'] = self::NO_EDIT_OR_DELETE; // no edit link |
||||
551 | $displayParts['del_lnk'] = self::NO_EDIT_OR_DELETE; // no delete link |
||||
552 | $displayParts['sort_lnk'] = (string) '0'; |
||||
553 | $displayParts['nav_bar'] = (string) '0'; |
||||
554 | $displayParts['bkm_form'] = (string) '1'; |
||||
555 | |||||
556 | if ($this->__get('is_maint')) { |
||||
557 | $displayParts['text_btn'] = (string) '1'; |
||||
558 | } else { |
||||
559 | $displayParts['text_btn'] = (string) '0'; |
||||
560 | } |
||||
561 | $displayParts['pview_lnk'] = (string) '1'; |
||||
562 | |||||
563 | return $displayParts; |
||||
564 | } |
||||
565 | |||||
566 | /** |
||||
567 | * Defines the parts to display for other statements (probably SELECT) |
||||
568 | * |
||||
569 | * @param array $displayParts the parts to display |
||||
570 | * |
||||
571 | * @return array the modified display parts |
||||
572 | * |
||||
573 | * @access private |
||||
574 | */ |
||||
575 | private function setDisplayPartsForSelect(array $displayParts) |
||||
576 | { |
||||
577 | // Other statements (ie "SELECT" ones) -> updates |
||||
578 | // $displayParts['edit_lnk'], $displayParts['del_lnk'] and |
||||
579 | // $displayParts['text_btn'] (keeps other default values) |
||||
580 | |||||
581 | $fields_meta = $this->__get('fields_meta'); |
||||
582 | $prev_table = ''; |
||||
583 | $displayParts['text_btn'] = (string) '1'; |
||||
584 | $number_of_columns = $this->__get('fields_cnt'); |
||||
585 | |||||
586 | for ($i = 0; $i < $number_of_columns; $i++) { |
||||
587 | $is_link = ($displayParts['edit_lnk'] != self::NO_EDIT_OR_DELETE) |
||||
588 | || ($displayParts['del_lnk'] != self::NO_EDIT_OR_DELETE) |
||||
589 | || ($displayParts['sort_lnk'] != '0'); |
||||
590 | |||||
591 | // Displays edit/delete/sort/insert links? |
||||
592 | if ($is_link |
||||
593 | && $prev_table != '' |
||||
594 | && $fields_meta[$i]->table != '' |
||||
595 | && $fields_meta[$i]->table != $prev_table |
||||
596 | ) { |
||||
597 | // don't display links |
||||
598 | $displayParts['edit_lnk'] = self::NO_EDIT_OR_DELETE; |
||||
599 | $displayParts['del_lnk'] = self::NO_EDIT_OR_DELETE; |
||||
600 | /** |
||||
601 | * @todo May be problematic with same field names |
||||
602 | * in two joined table. |
||||
603 | */ |
||||
604 | // $displayParts['sort_lnk'] = (string) '0'; |
||||
605 | if ($displayParts['text_btn'] == '1') { |
||||
606 | break; |
||||
607 | } |
||||
608 | } // end if |
||||
609 | |||||
610 | // Always display print view link |
||||
611 | $displayParts['pview_lnk'] = (string) '1'; |
||||
612 | if ($fields_meta[$i]->table == '') { |
||||
613 | continue; |
||||
614 | } |
||||
615 | |||||
616 | $prev_table = $fields_meta[$i]->table; |
||||
617 | } // end for |
||||
618 | |||||
619 | if ($prev_table == '') { // no table for any of the columns |
||||
620 | // don't display links |
||||
621 | $displayParts['edit_lnk'] = self::NO_EDIT_OR_DELETE; |
||||
622 | $displayParts['del_lnk'] = self::NO_EDIT_OR_DELETE; |
||||
623 | } |
||||
624 | |||||
625 | return $displayParts; |
||||
626 | } |
||||
627 | |||||
628 | /** |
||||
629 | * Defines the parts to display for the results of a SQL query |
||||
630 | * and the total number of rows |
||||
631 | * |
||||
632 | * @see getTable() |
||||
633 | * |
||||
634 | * @param array $displayParts the parts to display (see a few |
||||
635 | * lines above for explanations) |
||||
636 | * |
||||
637 | * @return array the first element is an array with explicit indexes |
||||
638 | * for all the display elements |
||||
639 | * the second element is the total number of rows returned |
||||
640 | * by the SQL query without any programmatically appended |
||||
641 | * LIMIT clause (just a copy of $unlim_num_rows if it exists, |
||||
642 | * else computed inside this function) |
||||
643 | * |
||||
644 | * @access private |
||||
645 | */ |
||||
646 | private function setDisplayPartsAndTotal(array $displayParts) |
||||
647 | { |
||||
648 | $the_total = 0; |
||||
649 | |||||
650 | // 1. Following variables are needed for use in isset/empty or |
||||
651 | // use with array indexes or safe use in foreach |
||||
652 | $db = $this->__get('db'); |
||||
653 | $table = $this->__get('table'); |
||||
654 | $unlim_num_rows = $this->__get('unlim_num_rows'); |
||||
655 | $num_rows = $this->__get('num_rows'); |
||||
656 | $printview = $this->__get('printview'); |
||||
657 | |||||
658 | // 2. Updates the display parts |
||||
659 | if ($printview == '1') { |
||||
660 | $displayParts = $this->setDisplayPartsForPrintView($displayParts); |
||||
661 | } elseif ($this->__get('is_count') || $this->__get('is_analyse') |
||||
662 | || $this->__get('is_maint') || $this->__get('is_explain') |
||||
663 | ) { |
||||
664 | $displayParts = $this->setDisplayPartsForNonData($displayParts); |
||||
665 | } elseif ($this->__get('is_show')) { |
||||
666 | $displayParts = $this->setDisplayPartsForShow($displayParts); |
||||
667 | } else { |
||||
668 | $displayParts = $this->setDisplayPartsForSelect($displayParts); |
||||
669 | } // end if..elseif...else |
||||
670 | |||||
671 | // 3. Gets the total number of rows if it is unknown |
||||
672 | if (isset($unlim_num_rows) && $unlim_num_rows != '') { |
||||
673 | $the_total = $unlim_num_rows; |
||||
674 | } elseif (($displayParts['nav_bar'] == '1') |
||||
675 | || ($displayParts['sort_lnk'] == '1') |
||||
676 | && (strlen($db) > 0 && strlen($table) > 0) |
||||
677 | ) { |
||||
678 | $the_total = $GLOBALS['dbi']->getTable($db, $table)->countRecords(); |
||||
679 | } |
||||
680 | |||||
681 | // if for COUNT query, number of rows returned more than 1 |
||||
682 | // (may be being used GROUP BY) |
||||
683 | if ($this->__get('is_count') && isset($num_rows) && $num_rows > 1) { |
||||
684 | $displayParts['nav_bar'] = (string) '1'; |
||||
685 | $displayParts['sort_lnk'] = (string) '1'; |
||||
686 | } |
||||
687 | // 4. If navigation bar or sorting fields names URLs should be |
||||
688 | // displayed but there is only one row, change these settings to |
||||
689 | // false |
||||
690 | if ($displayParts['nav_bar'] == '1' || $displayParts['sort_lnk'] == '1') { |
||||
691 | // - Do not display sort links if less than 2 rows. |
||||
692 | // - For a VIEW we (probably) did not count the number of rows |
||||
693 | // so don't test this number here, it would remove the possibility |
||||
694 | // of sorting VIEW results. |
||||
695 | $_table = new Table($table, $db); |
||||
696 | if (isset($unlim_num_rows) |
||||
697 | && ($unlim_num_rows < 2) |
||||
698 | && ! $_table->isView() |
||||
699 | ) { |
||||
700 | $displayParts['sort_lnk'] = (string) '0'; |
||||
701 | } |
||||
702 | } // end if (3) |
||||
703 | |||||
704 | return [ |
||||
705 | $displayParts, |
||||
706 | $the_total, |
||||
707 | ]; |
||||
708 | } |
||||
709 | |||||
710 | /** |
||||
711 | * Return true if we are executing a query in the form of |
||||
712 | * "SELECT * FROM <a table> ..." |
||||
713 | * |
||||
714 | * @see getTableHeaders(), getColumnParams() |
||||
715 | * |
||||
716 | * @param array $analyzed_sql_results analyzed sql results |
||||
717 | * |
||||
718 | * @return bool |
||||
719 | * |
||||
720 | * @access private |
||||
721 | */ |
||||
722 | 4 | private function isSelect(array $analyzed_sql_results) |
|||
723 | { |
||||
724 | 4 | return ! ($this->__get('is_count') |
|||
725 | 4 | || $this->__get('is_export') |
|||
726 | 4 | || $this->__get('is_func') |
|||
727 | 4 | || $this->__get('is_analyse')) |
|||
728 | 4 | && ! empty($analyzed_sql_results['select_from']) |
|||
729 | 4 | && ! empty($analyzed_sql_results['statement']->from) |
|||
730 | 4 | && (count($analyzed_sql_results['statement']->from) === 1) |
|||
731 | 4 | && ! empty($analyzed_sql_results['statement']->from[0]->table); |
|||
732 | } |
||||
733 | |||||
734 | /** |
||||
735 | * Get a navigation button |
||||
736 | * |
||||
737 | * @see getMoveBackwardButtonsForTableNavigation(), |
||||
738 | * getMoveForwardButtonsForTableNavigation() |
||||
739 | * |
||||
740 | * @param string $caption iconic caption for button |
||||
741 | * @param string $title text for button |
||||
742 | * @param int $pos position for next query |
||||
743 | * @param string $html_sql_query query ready for display |
||||
744 | * @param bool $back whether 'begin' or 'previous' |
||||
745 | * @param string $onsubmit optional onsubmit clause |
||||
746 | * @param string $input_for_real_end optional hidden field for special treatment |
||||
747 | * @param string $onclick optional onclick clause |
||||
748 | * |
||||
749 | * @return string html content |
||||
750 | * |
||||
751 | * @access private |
||||
752 | */ |
||||
753 | 4 | private function getTableNavigationButton( |
|||
754 | $caption, |
||||
755 | $title, |
||||
756 | $pos, |
||||
757 | $html_sql_query, |
||||
758 | $back, |
||||
759 | $onsubmit = '', |
||||
760 | $input_for_real_end = '', |
||||
761 | $onclick = '' |
||||
762 | ) { |
||||
763 | 4 | $caption_output = ''; |
|||
764 | 4 | if ($back) { |
|||
765 | 4 | if (Util::showIcons('TableNavigationLinksMode')) { |
|||
766 | 4 | $caption_output .= $caption; |
|||
767 | } |
||||
768 | 4 | if (Util::showText('TableNavigationLinksMode')) { |
|||
769 | 4 | $caption_output .= ' ' . $title; |
|||
770 | } |
||||
771 | } else { |
||||
772 | if (Util::showText('TableNavigationLinksMode')) { |
||||
773 | $caption_output .= $title; |
||||
774 | } |
||||
775 | if (Util::showIcons('TableNavigationLinksMode')) { |
||||
776 | $caption_output .= ' ' . $caption; |
||||
777 | } |
||||
778 | } |
||||
779 | |||||
780 | 4 | return $this->template->render('display/results/table_navigation_button', [ |
|||
781 | 4 | 'db' => $this->__get('db'), |
|||
782 | 4 | 'table' => $this->__get('table'), |
|||
783 | 4 | 'sql_query' => $html_sql_query, |
|||
784 | 4 | 'pos' => $pos, |
|||
785 | 4 | 'is_browse_distinct' => $this->__get('is_browse_distinct'), |
|||
786 | 4 | 'goto' => $this->__get('goto'), |
|||
787 | 4 | 'input_for_real_end' => $input_for_real_end, |
|||
788 | 4 | 'caption_output' => $caption_output, |
|||
789 | 4 | 'title' => $title, |
|||
790 | 4 | 'onsubmit' => $onsubmit, |
|||
791 | 4 | 'onclick' => $onclick, |
|||
792 | ]); |
||||
793 | } |
||||
794 | |||||
795 | /** |
||||
796 | * Possibly return a page selector for table navigation |
||||
797 | * |
||||
798 | * @return array ($output, $nbTotalPage) |
||||
799 | * |
||||
800 | * @access private |
||||
801 | */ |
||||
802 | private function getHtmlPageSelector(): array |
||||
803 | { |
||||
804 | $pageNow = @floor( |
||||
805 | $_SESSION['tmpval']['pos'] |
||||
806 | / $_SESSION['tmpval']['max_rows'] |
||||
807 | ) + 1; |
||||
808 | |||||
809 | $nbTotalPage = @ceil( |
||||
810 | $this->__get('unlim_num_rows') |
||||
811 | / $_SESSION['tmpval']['max_rows'] |
||||
812 | ); |
||||
813 | |||||
814 | $output = ''; |
||||
815 | if ($nbTotalPage > 1) { |
||||
816 | $_url_params = [ |
||||
817 | 'db' => $this->__get('db'), |
||||
818 | 'table' => $this->__get('table'), |
||||
819 | 'sql_query' => $this->__get('sql_query'), |
||||
820 | 'goto' => $this->__get('goto'), |
||||
821 | 'is_browse_distinct' => $this->__get('is_browse_distinct'), |
||||
822 | ]; |
||||
823 | |||||
824 | $output = $this->template->render('display/results/page_selector', [ |
||||
825 | 'url_params' => $_url_params, |
||||
826 | 'page_selector' => Util::pageselector( |
||||
827 | 'pos', |
||||
828 | $_SESSION['tmpval']['max_rows'], |
||||
829 | $pageNow, |
||||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||||
830 | $nbTotalPage, |
||||
0 ignored issues
–
show
$nbTotalPage of type double is incompatible with the type integer expected by parameter $nbTotalPage of PhpMyAdmin\Util::pageselector() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
831 | 200, |
||||
832 | 5, |
||||
833 | 5, |
||||
834 | 20, |
||||
835 | 10 |
||||
836 | ), |
||||
837 | ]); |
||||
838 | } |
||||
839 | |||||
840 | return [ |
||||
841 | $output, |
||||
842 | $nbTotalPage, |
||||
843 | ]; |
||||
844 | } |
||||
845 | |||||
846 | /** |
||||
847 | * Get a navigation bar to browse among the results of a SQL query |
||||
848 | * |
||||
849 | * @see getTable() |
||||
850 | * |
||||
851 | * @param int $posNext the offset for the "next" page |
||||
852 | * @param int $posPrevious the offset for the "previous" page |
||||
853 | * @param bool $isInnodb whether its InnoDB or not |
||||
854 | * @param string $sortByKeyHtml the sort by key dialog |
||||
855 | * |
||||
856 | * @return array |
||||
857 | */ |
||||
858 | private function getTableNavigation( |
||||
859 | $posNext, |
||||
860 | $posPrevious, |
||||
861 | $isInnodb, |
||||
862 | $sortByKeyHtml |
||||
863 | ): array { |
||||
864 | $isShowingAll = $_SESSION['tmpval']['max_rows'] === self::ALL_ROWS; |
||||
865 | |||||
866 | // Move to the beginning or to the previous page |
||||
867 | $moveBackwardButtons = ''; |
||||
868 | if ($_SESSION['tmpval']['pos'] && ! $isShowingAll) { |
||||
869 | $moveBackwardButtons = $this->getMoveBackwardButtonsForTableNavigation( |
||||
870 | htmlspecialchars($this->__get('sql_query')), |
||||
871 | $posPrevious |
||||
872 | ); |
||||
873 | } |
||||
874 | |||||
875 | $pageSelector = ''; |
||||
876 | $numberTotalPage = 1; |
||||
877 | if (! $isShowingAll) { |
||||
878 | [ |
||||
879 | $pageSelector, |
||||
880 | $numberTotalPage, |
||||
881 | ] = $this->getHtmlPageSelector(); |
||||
882 | } |
||||
883 | |||||
884 | // Move to the next page or to the last one |
||||
885 | $moveForwardButtons = ''; |
||||
886 | if ($this->__get('unlim_num_rows') === false // view with unknown number of rows |
||||
887 | || (! $isShowingAll |
||||
888 | && $_SESSION['tmpval']['pos'] + $_SESSION['tmpval']['max_rows'] < $this->__get('unlim_num_rows') |
||||
889 | && $this->__get('num_rows') >= $_SESSION['tmpval']['max_rows']) |
||||
890 | ) { |
||||
891 | $moveForwardButtons = $this->getMoveForwardButtonsForTableNavigation( |
||||
892 | htmlspecialchars($this->__get('sql_query')), |
||||
893 | $posNext, |
||||
894 | $isInnodb |
||||
895 | ); |
||||
896 | } |
||||
897 | |||||
898 | $hiddenFields = [ |
||||
899 | 'db' => $this->__get('db'), |
||||
900 | 'table' => $this->__get('table'), |
||||
901 | 'server' => $this->__get('server'), |
||||
902 | 'sql_query' => $this->__get('sql_query'), |
||||
903 | 'is_browse_distinct' => $this->__get('is_browse_distinct'), |
||||
904 | 'goto' => $this->__get('goto'), |
||||
905 | ]; |
||||
906 | |||||
907 | return [ |
||||
908 | 'move_backward_buttons' => $moveBackwardButtons, |
||||
909 | 'page_selector' => $pageSelector, |
||||
910 | 'move_forward_buttons' => $moveForwardButtons, |
||||
911 | 'number_total_page' => $numberTotalPage, |
||||
912 | 'has_show_all' => $GLOBALS['cfg']['ShowAll'] || ($this->__get('unlim_num_rows') <= 500), |
||||
913 | 'hidden_fields' => $hiddenFields, |
||||
914 | 'session_max_rows' => $isShowingAll ? $GLOBALS['cfg']['MaxRows'] : 'all', |
||||
915 | 'is_showing_all' => $isShowingAll, |
||||
916 | 'max_rows' => $_SESSION['tmpval']['max_rows'], |
||||
917 | 'pos' => $_SESSION['tmpval']['pos'], |
||||
918 | 'sort_by_key' => $sortByKeyHtml, |
||||
919 | ]; |
||||
920 | } |
||||
921 | |||||
922 | /** |
||||
923 | * Prepare move backward buttons - previous and first |
||||
924 | * |
||||
925 | * @see getTableNavigation() |
||||
926 | * |
||||
927 | * @param string $html_sql_query the sql encoded by html special characters |
||||
928 | * @param int $pos_prev the offset for the "previous" page |
||||
929 | * |
||||
930 | * @return string html content |
||||
931 | * |
||||
932 | * @access private |
||||
933 | */ |
||||
934 | private function getMoveBackwardButtonsForTableNavigation( |
||||
935 | $html_sql_query, |
||||
936 | $pos_prev |
||||
937 | ) { |
||||
938 | return $this->getTableNavigationButton( |
||||
939 | '<<', |
||||
940 | _pgettext('First page', 'Begin'), |
||||
941 | 0, |
||||
942 | $html_sql_query, |
||||
943 | true |
||||
944 | ) |
||||
945 | . $this->getTableNavigationButton( |
||||
946 | '<', |
||||
947 | _pgettext('Previous page', 'Previous'), |
||||
948 | $pos_prev, |
||||
949 | $html_sql_query, |
||||
950 | true |
||||
951 | ); |
||||
952 | } |
||||
953 | |||||
954 | /** |
||||
955 | * Prepare move forward buttons - next and last |
||||
956 | * |
||||
957 | * @see getTableNavigation() |
||||
958 | * |
||||
959 | * @param string $html_sql_query the sql encoded by htmlspecialchars() |
||||
960 | * @param int $pos_next the offset for the "next" page |
||||
961 | * @param bool $is_innodb whether it's InnoDB or not |
||||
962 | * |
||||
963 | * @return string html content |
||||
964 | * |
||||
965 | * @access private |
||||
966 | */ |
||||
967 | private function getMoveForwardButtonsForTableNavigation( |
||||
968 | $html_sql_query, |
||||
969 | $pos_next, |
||||
970 | $is_innodb |
||||
971 | ) { |
||||
972 | // display the Next button |
||||
973 | $buttons_html = $this->getTableNavigationButton( |
||||
974 | '>', |
||||
975 | _pgettext('Next page', 'Next'), |
||||
976 | $pos_next, |
||||
977 | $html_sql_query, |
||||
978 | false |
||||
979 | ); |
||||
980 | |||||
981 | // prepare some options for the End button |
||||
982 | if ($is_innodb |
||||
983 | && $this->__get('unlim_num_rows') > $GLOBALS['cfg']['MaxExactCount'] |
||||
984 | ) { |
||||
985 | $input_for_real_end = '<input id="real_end_input" type="hidden" ' |
||||
986 | . 'name="find_real_end" value="1">'; |
||||
987 | // no backquote around this message |
||||
988 | $onclick = ''; |
||||
989 | } else { |
||||
990 | $input_for_real_end = $onclick = ''; |
||||
991 | } |
||||
992 | |||||
993 | $maxRows = $_SESSION['tmpval']['max_rows']; |
||||
994 | $onsubmit = 'onsubmit="return ' |
||||
995 | . ($_SESSION['tmpval']['pos'] |
||||
996 | + $maxRows |
||||
997 | < $this->__get('unlim_num_rows') |
||||
998 | && $this->__get('num_rows') >= $maxRows |
||||
999 | ? 'true' |
||||
1000 | : 'false') . '"'; |
||||
1001 | |||||
1002 | // display the End button |
||||
1003 | return $buttons_html . $this->getTableNavigationButton( |
||||
1004 | '>>', |
||||
1005 | _pgettext('Last page', 'End'), |
||||
1006 | @(ceil( |
||||
1007 | $this->__get('unlim_num_rows') |
||||
1008 | / $_SESSION['tmpval']['max_rows'] |
||||
1009 | ) - 1) * $maxRows, |
||||
1010 | $html_sql_query, |
||||
1011 | false, |
||||
1012 | $onsubmit, |
||||
1013 | $input_for_real_end, |
||||
1014 | $onclick |
||||
1015 | ); |
||||
1016 | } |
||||
1017 | |||||
1018 | /** |
||||
1019 | * Get the headers of the results table, for all of the columns |
||||
1020 | * |
||||
1021 | * @see getTableHeaders() |
||||
1022 | * |
||||
1023 | * @param array $displayParts which elements to display |
||||
1024 | * @param array $analyzed_sql_results analyzed sql results |
||||
1025 | * @param array $sort_expression sort expression |
||||
1026 | * @param array $sort_expression_nodirection sort expression |
||||
1027 | * without direction |
||||
1028 | * @param array $sort_direction sort direction |
||||
1029 | * @param bool $is_limited_display with limited operations |
||||
1030 | * or not |
||||
1031 | * @param string $unsorted_sql_query query without the sort part |
||||
1032 | * |
||||
1033 | * @return string html content |
||||
1034 | * |
||||
1035 | * @access private |
||||
1036 | */ |
||||
1037 | private function getTableHeadersForColumns( |
||||
1038 | array $displayParts, |
||||
1039 | array $analyzed_sql_results, |
||||
1040 | array $sort_expression, |
||||
1041 | array $sort_expression_nodirection, |
||||
1042 | array $sort_direction, |
||||
1043 | $is_limited_display, |
||||
1044 | $unsorted_sql_query |
||||
1045 | ) { |
||||
1046 | $html = ''; |
||||
1047 | |||||
1048 | // required to generate sort links that will remember whether the |
||||
1049 | // "Show all" button has been clicked |
||||
1050 | $sql_md5 = md5($this->__get('sql_query')); |
||||
1051 | $session_max_rows = $is_limited_display |
||||
1052 | ? 0 |
||||
1053 | : $_SESSION['tmpval']['query'][$sql_md5]['max_rows']; |
||||
1054 | |||||
1055 | // Following variable are needed for use in isset/empty or |
||||
1056 | // use with array indexes/safe use in the for loop |
||||
1057 | $highlight_columns = $this->__get('highlight_columns'); |
||||
1058 | $fields_meta = $this->__get('fields_meta'); |
||||
1059 | |||||
1060 | // Prepare Display column comments if enabled |
||||
1061 | // ($GLOBALS['cfg']['ShowBrowseComments']). |
||||
1062 | $comments_map = $this->getTableCommentsArray($analyzed_sql_results); |
||||
1063 | |||||
1064 | [$col_order, $col_visib] = $this->getColumnParams( |
||||
1065 | $analyzed_sql_results |
||||
1066 | ); |
||||
1067 | |||||
1068 | // optimize: avoid calling a method on each iteration |
||||
1069 | $number_of_columns = $this->__get('fields_cnt'); |
||||
1070 | |||||
1071 | for ($j = 0; $j < $number_of_columns; $j++) { |
||||
1072 | // PHP 7.4 fix for accessing array offset on bool |
||||
1073 | $col_visib_current = is_array($col_visib) && isset($col_visib[$j]) ? $col_visib[$j] : null; |
||||
1074 | |||||
1075 | // assign $i with the appropriate column order |
||||
1076 | $i = $col_order ? $col_order[$j] : $j; |
||||
1077 | |||||
1078 | // See if this column should get highlight because it's used in the |
||||
1079 | // where-query. |
||||
1080 | $name = $fields_meta[$i]->name; |
||||
1081 | $condition_field = isset($highlight_columns[$name]) |
||||
1082 | || isset($highlight_columns[Util::backquote($name)]); |
||||
1083 | |||||
1084 | // Prepare comment-HTML-wrappers for each row, if defined/enabled. |
||||
1085 | $comments = $this->getCommentForRow($comments_map, $fields_meta[$i]); |
||||
1086 | $display_params = $this->__get('display_params'); |
||||
1087 | |||||
1088 | if (($displayParts['sort_lnk'] == '1') && ! $is_limited_display) { |
||||
1089 | [$order_link, $sorted_header_html] |
||||
1090 | = $this->getOrderLinkAndSortedHeaderHtml( |
||||
1091 | $fields_meta[$i], |
||||
1092 | $sort_expression, |
||||
1093 | $sort_expression_nodirection, |
||||
1094 | $i, |
||||
1095 | $unsorted_sql_query, |
||||
1096 | $session_max_rows, |
||||
1097 | $comments, |
||||
1098 | $sort_direction, |
||||
1099 | $col_visib, |
||||
1100 | $col_visib_current |
||||
1101 | ); |
||||
1102 | |||||
1103 | $html .= $sorted_header_html; |
||||
1104 | |||||
1105 | $display_params['desc'][] = ' <th ' |
||||
1106 | . 'class="draggable' |
||||
1107 | . ($condition_field ? ' condition' : '') |
||||
1108 | . '" data-column="' . htmlspecialchars($fields_meta[$i]->name) |
||||
1109 | . '">' . "\n" . $order_link . $comments . ' </th>' . "\n"; |
||||
1110 | } else { |
||||
1111 | // Results can't be sorted |
||||
1112 | $html |
||||
1113 | .= $this->getDraggableClassForNonSortableColumns( |
||||
1114 | $col_visib, |
||||
1115 | $col_visib_current, |
||||
1116 | $condition_field, |
||||
1117 | $fields_meta[$i], |
||||
1118 | $comments |
||||
1119 | ); |
||||
1120 | |||||
1121 | $display_params['desc'][] = ' <th ' |
||||
1122 | . 'class="draggable' |
||||
1123 | . ($condition_field ? ' condition"' : '') |
||||
1124 | . '" data-column="' . htmlspecialchars((string) $fields_meta[$i]->name) |
||||
1125 | . '"> ' |
||||
1126 | . htmlspecialchars((string) $fields_meta[$i]->name) |
||||
1127 | . $comments . ' </th>'; |
||||
1128 | } // end else |
||||
1129 | |||||
1130 | $this->__set('display_params', $display_params); |
||||
1131 | } // end for |
||||
1132 | |||||
1133 | return $html; |
||||
1134 | } |
||||
1135 | |||||
1136 | /** |
||||
1137 | * Get the headers of the results table |
||||
1138 | * |
||||
1139 | * @see getTable() |
||||
1140 | * |
||||
1141 | * @param array $displayParts which elements to display |
||||
1142 | * @param array $analyzedSqlResults analyzed sql results |
||||
1143 | * @param string $unsortedSqlQuery the unsorted sql query |
||||
1144 | * @param array $sortExpression sort expression |
||||
1145 | * @param array|string $sortExpressionNoDirection sort expression without direction |
||||
1146 | * @param array $sortDirection sort direction |
||||
1147 | * @param bool $isLimitedDisplay with limited operations or not |
||||
1148 | * |
||||
1149 | * @return array |
||||
1150 | */ |
||||
1151 | private function getTableHeaders( |
||||
1152 | array &$displayParts, |
||||
1153 | array $analyzedSqlResults, |
||||
1154 | $unsortedSqlQuery, |
||||
1155 | array $sortExpression = [], |
||||
1156 | $sortExpressionNoDirection = '', |
||||
1157 | array $sortDirection = [], |
||||
1158 | $isLimitedDisplay = false |
||||
1159 | ): array { |
||||
1160 | // Needed for use in isset/empty or |
||||
1161 | // use with array indexes/safe use in foreach |
||||
1162 | $printView = $this->__get('printview'); |
||||
1163 | $displayParams = $this->__get('display_params'); |
||||
1164 | |||||
1165 | // Output data needed for column reordering and show/hide column |
||||
1166 | $columnOrder = $this->getDataForResettingColumnOrder($analyzedSqlResults); |
||||
1167 | |||||
1168 | $displayParams['emptypre'] = 0; |
||||
1169 | $displayParams['emptyafter'] = 0; |
||||
1170 | $displayParams['textbtn'] = ''; |
||||
1171 | $fullOrPartialTextLink = ''; |
||||
1172 | |||||
1173 | $this->__set('display_params', $displayParams); |
||||
1174 | |||||
1175 | // Display options (if we are not in print view) |
||||
1176 | $optionsBlock = []; |
||||
1177 | if (! (isset($printView) && ($printView == '1')) && ! $isLimitedDisplay) { |
||||
1178 | $optionsBlock = $this->getOptionsBlock(); |
||||
1179 | |||||
1180 | // prepare full/partial text button or link |
||||
1181 | $fullOrPartialTextLink = $this->getFullOrPartialTextButtonOrLink(); |
||||
1182 | } |
||||
1183 | |||||
1184 | // 1. Set $colspan and generate html with full/partial |
||||
1185 | // text button or link |
||||
1186 | [$colspan, $buttonHtml] = $this->getFieldVisibilityParams( |
||||
1187 | $displayParts, |
||||
1188 | $fullOrPartialTextLink |
||||
1189 | ); |
||||
1190 | |||||
1191 | // 2. Displays the fields' name |
||||
1192 | // 2.0 If sorting links should be used, checks if the query is a "JOIN" |
||||
1193 | // statement (see 2.1.3) |
||||
1194 | |||||
1195 | // See if we have to highlight any header fields of a WHERE query. |
||||
1196 | // Uses SQL-Parser results. |
||||
1197 | $this->setHighlightedColumnGlobalField($analyzedSqlResults); |
||||
1198 | |||||
1199 | // Get the headers for all of the columns |
||||
1200 | $tableHeadersForColumns = $this->getTableHeadersForColumns( |
||||
1201 | $displayParts, |
||||
1202 | $analyzedSqlResults, |
||||
1203 | $sortExpression, |
||||
1204 | $sortExpressionNoDirection, |
||||
1205 | $sortDirection, |
||||
1206 | $isLimitedDisplay, |
||||
1207 | $unsortedSqlQuery |
||||
1208 | ); |
||||
1209 | |||||
1210 | // Display column at rightside - checkboxes or empty column |
||||
1211 | $columnAtRightSide = ''; |
||||
1212 | if (! $printView) { |
||||
1213 | $columnAtRightSide = $this->getColumnAtRightSide( |
||||
1214 | $displayParts, |
||||
1215 | $fullOrPartialTextLink, |
||||
1216 | $colspan |
||||
1217 | ); |
||||
1218 | } |
||||
1219 | |||||
1220 | return [ |
||||
1221 | 'column_order' => $columnOrder, |
||||
1222 | 'options' => $optionsBlock, |
||||
1223 | 'has_bulk_actions_form' => $displayParts['del_lnk'] === self::DELETE_ROW |
||||
1224 | || $displayParts['del_lnk'] === self::KILL_PROCESS, |
||||
1225 | 'button' => $buttonHtml, |
||||
1226 | 'table_headers_for_columns' => $tableHeadersForColumns, |
||||
1227 | 'column_at_right_side' => $columnAtRightSide, |
||||
1228 | ]; |
||||
1229 | } |
||||
1230 | |||||
1231 | /** |
||||
1232 | * Prepare unsorted sql query and sort by key drop down |
||||
1233 | * |
||||
1234 | * @see getTableHeaders() |
||||
1235 | * |
||||
1236 | * @param array $analyzed_sql_results analyzed sql results |
||||
1237 | * @param array|null $sort_expression sort expression |
||||
1238 | * |
||||
1239 | * @return array two element array - $unsorted_sql_query, $drop_down_html |
||||
1240 | * |
||||
1241 | * @access private |
||||
1242 | */ |
||||
1243 | private function getUnsortedSqlAndSortByKeyDropDown( |
||||
1244 | array $analyzed_sql_results, |
||||
1245 | ?array $sort_expression |
||||
1246 | ) { |
||||
1247 | $drop_down_html = ''; |
||||
1248 | |||||
1249 | $unsorted_sql_query = Query::replaceClause( |
||||
1250 | $analyzed_sql_results['statement'], |
||||
1251 | $analyzed_sql_results['parser']->list, |
||||
1252 | 'ORDER BY', |
||||
1253 | '' |
||||
1254 | ); |
||||
1255 | |||||
1256 | // Data is sorted by indexes only if it there is only one table. |
||||
1257 | if ($this->isSelect($analyzed_sql_results)) { |
||||
1258 | // grab indexes data: |
||||
1259 | $indexes = Index::getFromTable( |
||||
1260 | $this->__get('table'), |
||||
1261 | $this->__get('db') |
||||
1262 | ); |
||||
1263 | |||||
1264 | // do we have any index? |
||||
1265 | if (! empty($indexes)) { |
||||
1266 | $drop_down_html = $this->getSortByKeyDropDown( |
||||
1267 | $indexes, |
||||
1268 | $sort_expression, |
||||
1269 | $unsorted_sql_query |
||||
1270 | ); |
||||
1271 | } |
||||
1272 | } |
||||
1273 | |||||
1274 | return [ |
||||
1275 | $unsorted_sql_query, |
||||
1276 | $drop_down_html, |
||||
1277 | ]; |
||||
1278 | } |
||||
1279 | |||||
1280 | /** |
||||
1281 | * Prepare sort by key dropdown - html code segment |
||||
1282 | * |
||||
1283 | * @see getTableHeaders() |
||||
1284 | * |
||||
1285 | * @param Index[] $indexes the indexes of the table for sort criteria |
||||
1286 | * @param array|null $sortExpression the sort expression |
||||
1287 | * @param string $unsortedSqlQuery the unsorted sql query |
||||
1288 | * |
||||
1289 | * @return string html content |
||||
1290 | * |
||||
1291 | * @access private |
||||
1292 | */ |
||||
1293 | private function getSortByKeyDropDown( |
||||
1294 | $indexes, |
||||
1295 | ?array $sortExpression, |
||||
1296 | $unsortedSqlQuery |
||||
1297 | ): string { |
||||
1298 | $hiddenFields = [ |
||||
1299 | 'db' => $this->__get('db'), |
||||
1300 | 'table' => $this->__get('table'), |
||||
1301 | 'server' => $this->__get('server'), |
||||
1302 | 'sort_by_key' => '1', |
||||
1303 | ]; |
||||
1304 | |||||
1305 | $isIndexUsed = false; |
||||
1306 | $localOrder = is_array($sortExpression) ? implode(', ', $sortExpression) : ''; |
||||
1307 | |||||
1308 | $options = []; |
||||
1309 | foreach ($indexes as $index) { |
||||
1310 | $ascSort = '`' |
||||
1311 | . implode('` ASC, `', array_keys($index->getColumns())) |
||||
1312 | . '` ASC'; |
||||
1313 | |||||
1314 | $descSort = '`' |
||||
1315 | . implode('` DESC, `', array_keys($index->getColumns())) |
||||
1316 | . '` DESC'; |
||||
1317 | |||||
1318 | $isIndexUsed = $isIndexUsed |
||||
1319 | || $localOrder === $ascSort |
||||
1320 | || $localOrder === $descSort; |
||||
1321 | |||||
1322 | $unsortedSqlQueryFirstPart = $unsortedSqlQuery; |
||||
1323 | $unsortedSqlQuerySecondPart = ''; |
||||
1324 | if (preg_match( |
||||
1325 | '@(.*)([[:space:]](LIMIT (.*)|PROCEDURE (.*)|' |
||||
1326 | . 'FOR UPDATE|LOCK IN SHARE MODE))@is', |
||||
1327 | $unsortedSqlQuery, |
||||
1328 | $myReg |
||||
1329 | )) { |
||||
1330 | $unsortedSqlQueryFirstPart = $myReg[1]; |
||||
1331 | $unsortedSqlQuerySecondPart = $myReg[2]; |
||||
1332 | } |
||||
1333 | |||||
1334 | $options[] = [ |
||||
1335 | 'value' => $unsortedSqlQueryFirstPart . ' ORDER BY ' |
||||
1336 | . $ascSort . $unsortedSqlQuerySecondPart, |
||||
1337 | 'content' => $index->getName() . ' (ASC)', |
||||
1338 | 'is_selected' => $localOrder === $ascSort, |
||||
1339 | ]; |
||||
1340 | $options[] = [ |
||||
1341 | 'value' => $unsortedSqlQueryFirstPart . ' ORDER BY ' |
||||
1342 | . $descSort . $unsortedSqlQuerySecondPart, |
||||
1343 | 'content' => $index->getName() . ' (DESC)', |
||||
1344 | 'is_selected' => $localOrder === $descSort, |
||||
1345 | ]; |
||||
1346 | } |
||||
1347 | $options[] = [ |
||||
1348 | 'value' => $unsortedSqlQuery, |
||||
1349 | 'content' => __('None'), |
||||
1350 | 'is_selected' => ! $isIndexUsed, |
||||
1351 | ]; |
||||
1352 | |||||
1353 | return $this->template->render('display/results/sort_by_key', [ |
||||
1354 | 'hidden_fields' => $hiddenFields, |
||||
1355 | 'options' => $options, |
||||
1356 | ]); |
||||
1357 | } |
||||
1358 | |||||
1359 | /** |
||||
1360 | * Set column span, row span and prepare html with full/partial |
||||
1361 | * text button or link |
||||
1362 | * |
||||
1363 | * @see getTableHeaders() |
||||
1364 | * |
||||
1365 | * @param array $displayParts which elements to display |
||||
1366 | * @param string $full_or_partial_text_link full/partial link or text button |
||||
1367 | * |
||||
1368 | * @return array 2 element array - $colspan, $button_html |
||||
1369 | * |
||||
1370 | * @access private |
||||
1371 | */ |
||||
1372 | private function getFieldVisibilityParams( |
||||
1373 | array &$displayParts, |
||||
1374 | $full_or_partial_text_link |
||||
1375 | ) { |
||||
1376 | $button_html = ''; |
||||
1377 | $display_params = $this->__get('display_params'); |
||||
1378 | |||||
1379 | // 1. Displays the full/partial text button (part 1)... |
||||
1380 | $button_html .= '<thead><tr>' . "\n"; |
||||
1381 | |||||
1382 | $emptyPreCondition = $displayParts['edit_lnk'] != self::NO_EDIT_OR_DELETE |
||||
1383 | && $displayParts['del_lnk'] != self::NO_EDIT_OR_DELETE; |
||||
1384 | |||||
1385 | $colspan = $emptyPreCondition ? ' colspan="4"' |
||||
1386 | : ''; |
||||
1387 | |||||
1388 | $leftOrBoth = $GLOBALS['cfg']['RowActionLinks'] === self::POSITION_LEFT |
||||
1389 | || $GLOBALS['cfg']['RowActionLinks'] === self::POSITION_BOTH; |
||||
1390 | |||||
1391 | // ... before the result table |
||||
1392 | if (($displayParts['edit_lnk'] == self::NO_EDIT_OR_DELETE) |
||||
1393 | && ($displayParts['del_lnk'] == self::NO_EDIT_OR_DELETE) |
||||
1394 | && ($displayParts['text_btn'] == '1') |
||||
1395 | ) { |
||||
1396 | $display_params['emptypre'] = $emptyPreCondition ? 4 : 0; |
||||
1397 | } elseif ($leftOrBoth && ($displayParts['text_btn'] == '1') |
||||
1398 | ) { |
||||
1399 | // ... at the left column of the result table header if possible |
||||
1400 | // and required |
||||
1401 | |||||
1402 | $display_params['emptypre'] = $emptyPreCondition ? 4 : 0; |
||||
1403 | |||||
1404 | $button_html .= '<th class="column_action print_ignore" ' . $colspan |
||||
1405 | . '>' . $full_or_partial_text_link . '</th>'; |
||||
1406 | } elseif ($leftOrBoth |
||||
1407 | && (($displayParts['edit_lnk'] != self::NO_EDIT_OR_DELETE) |
||||
1408 | || ($displayParts['del_lnk'] != self::NO_EDIT_OR_DELETE)) |
||||
1409 | ) { |
||||
1410 | // ... elseif no button, displays empty(ies) col(s) if required |
||||
1411 | |||||
1412 | $display_params['emptypre'] = $emptyPreCondition ? 4 : 0; |
||||
1413 | |||||
1414 | $button_html .= '<td ' . $colspan . '></td>'; |
||||
1415 | } elseif ($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_NONE) { |
||||
1416 | // ... elseif display an empty column if the actions links are |
||||
1417 | // disabled to match the rest of the table |
||||
1418 | $button_html .= '<th class="column_action"></th>'; |
||||
1419 | } |
||||
1420 | |||||
1421 | $this->__set('display_params', $display_params); |
||||
1422 | |||||
1423 | return [ |
||||
1424 | $colspan, |
||||
1425 | $button_html, |
||||
1426 | ]; |
||||
1427 | } |
||||
1428 | |||||
1429 | /** |
||||
1430 | * Get table comments as array |
||||
1431 | * |
||||
1432 | * @see getTableHeaders() |
||||
1433 | * |
||||
1434 | * @param array $analyzed_sql_results analyzed sql results |
||||
1435 | * |
||||
1436 | * @return array table comments |
||||
1437 | * |
||||
1438 | * @access private |
||||
1439 | */ |
||||
1440 | private function getTableCommentsArray(array $analyzed_sql_results) |
||||
1441 | { |
||||
1442 | if (! $GLOBALS['cfg']['ShowBrowseComments'] |
||||
1443 | || empty($analyzed_sql_results['statement']->from) |
||||
1444 | ) { |
||||
1445 | return []; |
||||
1446 | } |
||||
1447 | |||||
1448 | $ret = []; |
||||
1449 | foreach ($analyzed_sql_results['statement']->from as $field) { |
||||
1450 | if (empty($field->table)) { |
||||
1451 | continue; |
||||
1452 | } |
||||
1453 | $ret[$field->table] = $this->relation->getComments( |
||||
1454 | empty($field->database) ? $this->__get('db') : $field->database, |
||||
1455 | $field->table |
||||
1456 | ); |
||||
1457 | } |
||||
1458 | |||||
1459 | return $ret; |
||||
1460 | } |
||||
1461 | |||||
1462 | /** |
||||
1463 | * Set global array for store highlighted header fields |
||||
1464 | * |
||||
1465 | * @see getTableHeaders() |
||||
1466 | * |
||||
1467 | * @param array $analyzed_sql_results analyzed sql results |
||||
1468 | * |
||||
1469 | * @return void |
||||
1470 | * |
||||
1471 | * @access private |
||||
1472 | */ |
||||
1473 | 4 | private function setHighlightedColumnGlobalField(array $analyzed_sql_results) |
|||
1474 | { |
||||
1475 | 4 | $highlight_columns = []; |
|||
1476 | |||||
1477 | 4 | if (! empty($analyzed_sql_results['statement']->where)) { |
|||
1478 | 4 | foreach ($analyzed_sql_results['statement']->where as $expr) { |
|||
1479 | 4 | foreach ($expr->identifiers as $identifier) { |
|||
1480 | 4 | $highlight_columns[$identifier] = 'true'; |
|||
1481 | } |
||||
1482 | } |
||||
1483 | } |
||||
1484 | |||||
1485 | 4 | $this->__set('highlight_columns', $highlight_columns); |
|||
1486 | 4 | } |
|||
1487 | |||||
1488 | /** |
||||
1489 | * Prepare data for column restoring and show/hide |
||||
1490 | * |
||||
1491 | * @see getTableHeaders() |
||||
1492 | * |
||||
1493 | * @param array $analyzedSqlResults analyzed sql results |
||||
1494 | * |
||||
1495 | * @return array |
||||
1496 | */ |
||||
1497 | private function getDataForResettingColumnOrder(array $analyzedSqlResults): array |
||||
1498 | { |
||||
1499 | if (! $this->isSelect($analyzedSqlResults)) { |
||||
1500 | return []; |
||||
1501 | } |
||||
1502 | |||||
1503 | [$columnOrder, $columnVisibility] = $this->getColumnParams( |
||||
1504 | $analyzedSqlResults |
||||
1505 | ); |
||||
1506 | |||||
1507 | $tableCreateTime = ''; |
||||
1508 | $table = new Table($this->__get('table'), $this->__get('db')); |
||||
1509 | if (! $table->isView()) { |
||||
1510 | $tableCreateTime = $GLOBALS['dbi']->getTable( |
||||
1511 | $this->__get('db'), |
||||
1512 | $this->__get('table') |
||||
1513 | )->getStatusInfo('Create_time'); |
||||
1514 | } |
||||
1515 | |||||
1516 | return [ |
||||
1517 | 'order' => $columnOrder, |
||||
1518 | 'visibility' => $columnVisibility, |
||||
1519 | 'is_view' => $table->isView(), |
||||
1520 | 'table_create_time' => $tableCreateTime, |
||||
1521 | ]; |
||||
1522 | } |
||||
1523 | |||||
1524 | /** |
||||
1525 | * Prepare option fields block |
||||
1526 | * |
||||
1527 | * @see getTableHeaders() |
||||
1528 | * |
||||
1529 | * @return array |
||||
1530 | */ |
||||
1531 | private function getOptionsBlock(): array |
||||
1532 | { |
||||
1533 | if (isset($_SESSION['tmpval']['possible_as_geometry']) |
||||
1534 | && $_SESSION['tmpval']['possible_as_geometry'] == false |
||||
1535 | ) { |
||||
1536 | if ($_SESSION['tmpval']['geoOption'] == self::GEOMETRY_DISP_GEOM) { |
||||
1537 | $_SESSION['tmpval']['geoOption'] = self::GEOMETRY_DISP_WKT; |
||||
1538 | } |
||||
1539 | } |
||||
1540 | |||||
1541 | return [ |
||||
1542 | 'geo_option' => $_SESSION['tmpval']['geoOption'], |
||||
1543 | 'hide_transformation' => $_SESSION['tmpval']['hide_transformation'], |
||||
1544 | 'display_blob' => $_SESSION['tmpval']['display_blob'], |
||||
1545 | 'display_binary' => $_SESSION['tmpval']['display_binary'], |
||||
1546 | 'relational_display' => $_SESSION['tmpval']['relational_display'], |
||||
1547 | 'possible_as_geometry' => $_SESSION['tmpval']['possible_as_geometry'], |
||||
1548 | 'pftext' => $_SESSION['tmpval']['pftext'], |
||||
1549 | ]; |
||||
1550 | } |
||||
1551 | |||||
1552 | /** |
||||
1553 | * Get full/partial text button or link |
||||
1554 | * |
||||
1555 | * @see getTableHeaders() |
||||
1556 | * |
||||
1557 | * @return string html content |
||||
1558 | * |
||||
1559 | * @access private |
||||
1560 | */ |
||||
1561 | private function getFullOrPartialTextButtonOrLink() |
||||
1562 | { |
||||
1563 | $url_params_full_text = [ |
||||
1564 | 'db' => $this->__get('db'), |
||||
1565 | 'table' => $this->__get('table'), |
||||
1566 | 'sql_query' => $this->__get('sql_query'), |
||||
1567 | 'goto' => $this->__get('goto'), |
||||
1568 | 'full_text_button' => 1, |
||||
1569 | ]; |
||||
1570 | |||||
1571 | if ($_SESSION['tmpval']['pftext'] == self::DISPLAY_FULL_TEXT) { |
||||
1572 | // currently in fulltext mode so show the opposite link |
||||
1573 | $tmp_image_file = $this->__get('pma_theme_image') . 's_partialtext.png'; |
||||
1574 | $tmp_txt = __('Partial texts'); |
||||
1575 | $url_params_full_text['pftext'] = self::DISPLAY_PARTIAL_TEXT; |
||||
1576 | } else { |
||||
1577 | $tmp_image_file = $this->__get('pma_theme_image') . 's_fulltext.png'; |
||||
1578 | $tmp_txt = __('Full texts'); |
||||
1579 | $url_params_full_text['pftext'] = self::DISPLAY_FULL_TEXT; |
||||
1580 | } |
||||
1581 | |||||
1582 | $tmp_image = '<img class="fulltext" src="' . $tmp_image_file . '" alt="' |
||||
1583 | . $tmp_txt . '" title="' . $tmp_txt . '">'; |
||||
1584 | $tmp_url = Url::getFromRoute('/sql', $url_params_full_text); |
||||
1585 | |||||
1586 | return Generator::linkOrButton($tmp_url, $tmp_image); |
||||
1587 | } |
||||
1588 | |||||
1589 | /** |
||||
1590 | * Get comment for row |
||||
1591 | * |
||||
1592 | * @see getTableHeaders() |
||||
1593 | * |
||||
1594 | * @param array $commentsMap comments array |
||||
1595 | * @param array $fieldsMeta set of field properties |
||||
1596 | * |
||||
1597 | * @return string html content |
||||
1598 | * |
||||
1599 | * @access private |
||||
1600 | */ |
||||
1601 | private function getCommentForRow(array $commentsMap, $fieldsMeta) |
||||
1602 | { |
||||
1603 | return $this->template->render('display/results/comment_for_row', [ |
||||
1604 | 'comments_map' => $commentsMap, |
||||
1605 | 'fields_meta' => $fieldsMeta, |
||||
1606 | 'limit_chars' => $GLOBALS['cfg']['LimitChars'], |
||||
1607 | ]); |
||||
1608 | } |
||||
1609 | |||||
1610 | /** |
||||
1611 | * Prepare parameters and html for sorted table header fields |
||||
1612 | * |
||||
1613 | * @see getTableHeaders() |
||||
1614 | * |
||||
1615 | * @param stdClass $fields_meta set of field properties |
||||
1616 | * @param array $sort_expression sort expression |
||||
1617 | * @param array $sort_expression_nodirection sort expression without direction |
||||
1618 | * @param int $column_index the index of the column |
||||
1619 | * @param string $unsorted_sql_query the unsorted sql query |
||||
1620 | * @param int $session_max_rows maximum rows resulted by sql |
||||
1621 | * @param string $comments comment for row |
||||
1622 | * @param array $sort_direction sort direction |
||||
1623 | * @param bool $col_visib column is visible(false) or column isn't visible(string array) |
||||
1624 | * @param string $col_visib_j element of $col_visib array |
||||
1625 | * |
||||
1626 | * @return array 2 element array - $order_link, $sorted_header_html |
||||
1627 | * |
||||
1628 | * @access private |
||||
1629 | */ |
||||
1630 | private function getOrderLinkAndSortedHeaderHtml( |
||||
1631 | $fields_meta, |
||||
1632 | array $sort_expression, |
||||
1633 | array $sort_expression_nodirection, |
||||
1634 | $column_index, |
||||
1635 | $unsorted_sql_query, |
||||
1636 | $session_max_rows, |
||||
1637 | $comments, |
||||
1638 | array $sort_direction, |
||||
1639 | $col_visib, |
||||
1640 | $col_visib_j |
||||
1641 | ) { |
||||
1642 | $sorted_header_html = ''; |
||||
1643 | |||||
1644 | // Checks if the table name is required; it's the case |
||||
1645 | // for a query with a "JOIN" statement and if the column |
||||
1646 | // isn't aliased, or in queries like |
||||
1647 | // SELECT `1`.`master_field` , `2`.`master_field` |
||||
1648 | // FROM `PMA_relation` AS `1` , `PMA_relation` AS `2` |
||||
1649 | |||||
1650 | $sort_tbl = isset($fields_meta->table) |
||||
1651 | && strlen($fields_meta->table) > 0 |
||||
1652 | && $fields_meta->orgname == $fields_meta->name |
||||
1653 | ? Util::backquote( |
||||
1654 | $fields_meta->table |
||||
1655 | ) . '.' |
||||
1656 | : ''; |
||||
1657 | |||||
1658 | $name_to_use_in_sort = $fields_meta->name; |
||||
1659 | |||||
1660 | // Generates the orderby clause part of the query which is part |
||||
1661 | // of URL |
||||
1662 | [$single_sort_order, $multi_sort_order, $order_img] |
||||
1663 | = $this->getSingleAndMultiSortUrls( |
||||
1664 | $sort_expression, |
||||
1665 | $sort_expression_nodirection, |
||||
1666 | $sort_tbl, |
||||
1667 | $name_to_use_in_sort, |
||||
1668 | $sort_direction, |
||||
1669 | $fields_meta |
||||
1670 | ); |
||||
1671 | |||||
1672 | if (preg_match( |
||||
1673 | '@(.*)([[:space:]](LIMIT (.*)|PROCEDURE (.*)|FOR UPDATE|' |
||||
1674 | . 'LOCK IN SHARE MODE))@is', |
||||
1675 | $unsorted_sql_query, |
||||
1676 | $regs3 |
||||
1677 | )) { |
||||
1678 | $single_sorted_sql_query = $regs3[1] . $single_sort_order . $regs3[2]; |
||||
1679 | $multi_sorted_sql_query = $regs3[1] . $multi_sort_order . $regs3[2]; |
||||
1680 | } else { |
||||
1681 | $single_sorted_sql_query = $unsorted_sql_query . $single_sort_order; |
||||
1682 | $multi_sorted_sql_query = $unsorted_sql_query . $multi_sort_order; |
||||
1683 | } |
||||
1684 | |||||
1685 | $_single_url_params = [ |
||||
1686 | 'db' => $this->__get('db'), |
||||
1687 | 'table' => $this->__get('table'), |
||||
1688 | 'sql_query' => $single_sorted_sql_query, |
||||
1689 | 'sql_signature' => Core::signSqlQuery($single_sorted_sql_query), |
||||
1690 | 'session_max_rows' => $session_max_rows, |
||||
1691 | 'is_browse_distinct' => $this->__get('is_browse_distinct'), |
||||
1692 | ]; |
||||
1693 | |||||
1694 | $_multi_url_params = [ |
||||
1695 | 'db' => $this->__get('db'), |
||||
1696 | 'table' => $this->__get('table'), |
||||
1697 | 'sql_query' => $multi_sorted_sql_query, |
||||
1698 | 'sql_signature' => Core::signSqlQuery($multi_sorted_sql_query), |
||||
1699 | 'session_max_rows' => $session_max_rows, |
||||
1700 | 'is_browse_distinct' => $this->__get('is_browse_distinct'), |
||||
1701 | ]; |
||||
1702 | $single_order_url = Url::getFromRoute('/sql', $_single_url_params); |
||||
1703 | $multi_order_url = Url::getFromRoute('/sql', $_multi_url_params); |
||||
1704 | |||||
1705 | // Displays the sorting URL |
||||
1706 | // enable sort order swapping for image |
||||
1707 | $order_link = $this->getSortOrderLink( |
||||
1708 | $order_img, |
||||
1709 | $fields_meta, |
||||
1710 | $single_order_url, |
||||
1711 | $multi_order_url |
||||
1712 | ); |
||||
1713 | |||||
1714 | $sorted_header_html .= $this->getDraggableClassForSortableColumns( |
||||
1715 | $col_visib, |
||||
1716 | $col_visib_j, |
||||
1717 | $fields_meta, |
||||
1718 | $order_link, |
||||
1719 | $comments |
||||
1720 | ); |
||||
1721 | |||||
1722 | return [ |
||||
1723 | $order_link, |
||||
1724 | $sorted_header_html, |
||||
1725 | ]; |
||||
1726 | } |
||||
1727 | |||||
1728 | /** |
||||
1729 | * Prepare parameters and html for sorted table header fields |
||||
1730 | * |
||||
1731 | * @see getOrderLinkAndSortedHeaderHtml() |
||||
1732 | * |
||||
1733 | * @param array $sort_expression sort expression |
||||
1734 | * @param array $sort_expression_nodirection sort expression without direction |
||||
1735 | * @param string $sort_tbl The name of the table to which |
||||
1736 | * the current column belongs to |
||||
1737 | * @param string $name_to_use_in_sort The current column under |
||||
1738 | * consideration |
||||
1739 | * @param array $sort_direction sort direction |
||||
1740 | * @param stdClass $fields_meta set of field properties |
||||
1741 | * |
||||
1742 | * @return array 3 element array - $single_sort_order, $sort_order, $order_img |
||||
1743 | * |
||||
1744 | * @access private |
||||
1745 | */ |
||||
1746 | private function getSingleAndMultiSortUrls( |
||||
1747 | array $sort_expression, |
||||
1748 | array $sort_expression_nodirection, |
||||
1749 | $sort_tbl, |
||||
1750 | $name_to_use_in_sort, |
||||
1751 | array $sort_direction, |
||||
1752 | $fields_meta |
||||
1753 | ) { |
||||
1754 | $sort_order = ''; |
||||
1755 | // Check if the current column is in the order by clause |
||||
1756 | $is_in_sort = $this->isInSorted( |
||||
1757 | $sort_expression, |
||||
1758 | $sort_expression_nodirection, |
||||
1759 | $sort_tbl, |
||||
1760 | $name_to_use_in_sort |
||||
1761 | ); |
||||
1762 | $current_name = $name_to_use_in_sort; |
||||
1763 | if ($sort_expression_nodirection[0] == '' || ! $is_in_sort) { |
||||
1764 | $special_index = $sort_expression_nodirection[0] == '' |
||||
1765 | ? 0 |
||||
1766 | : count($sort_expression_nodirection); |
||||
1767 | $sort_expression_nodirection[$special_index] |
||||
1768 | = Util::backquote( |
||||
1769 | $current_name |
||||
1770 | ); |
||||
1771 | $sort_direction[$special_index] = preg_match( |
||||
1772 | '@time|date@i', |
||||
1773 | $fields_meta->type ?? '' |
||||
1774 | ) ? self::DESCENDING_SORT_DIR : self::ASCENDING_SORT_DIR; |
||||
1775 | } |
||||
1776 | |||||
1777 | $sort_expression_nodirection = array_filter($sort_expression_nodirection); |
||||
1778 | $single_sort_order = null; |
||||
1779 | foreach ($sort_expression_nodirection as $index => $expression) { |
||||
1780 | // check if this is the first clause, |
||||
1781 | // if it is then we have to add "order by" |
||||
1782 | $is_first_clause = ($index == 0); |
||||
1783 | $name_to_use_in_sort = $expression; |
||||
1784 | $sort_tbl_new = $sort_tbl; |
||||
1785 | // Test to detect if the column name is a standard name |
||||
1786 | // Standard name has the table name prefixed to the column name |
||||
1787 | if (mb_strpos($name_to_use_in_sort, '.') !== false) { |
||||
1788 | $matches = explode('.', $name_to_use_in_sort); |
||||
1789 | // Matches[0] has the table name |
||||
1790 | // Matches[1] has the column name |
||||
1791 | $name_to_use_in_sort = $matches[1]; |
||||
1792 | $sort_tbl_new = $matches[0]; |
||||
1793 | } |
||||
1794 | |||||
1795 | // $name_to_use_in_sort might contain a space due to |
||||
1796 | // formatting of function expressions like "COUNT(name )" |
||||
1797 | // so we remove the space in this situation |
||||
1798 | $name_to_use_in_sort = str_replace([' )', '``'], [')', '`'], $name_to_use_in_sort); |
||||
1799 | $name_to_use_in_sort = trim($name_to_use_in_sort, '`'); |
||||
1800 | |||||
1801 | // If this the first column name in the order by clause add |
||||
1802 | // order by clause to the column name |
||||
1803 | $query_head = $is_first_clause ? "\nORDER BY " : ''; |
||||
1804 | // Again a check to see if the given column is a aggregate column |
||||
1805 | if (mb_strpos($name_to_use_in_sort, '(') !== false) { |
||||
1806 | $sort_order .= $query_head . $name_to_use_in_sort . ' '; |
||||
1807 | } else { |
||||
1808 | if (strlen($sort_tbl_new) > 0) { |
||||
1809 | $sort_tbl_new .= '.'; |
||||
1810 | } |
||||
1811 | $sort_order .= $query_head . $sort_tbl_new |
||||
1812 | . Util::backquote( |
||||
1813 | $name_to_use_in_sort |
||||
1814 | ) . ' '; |
||||
1815 | } |
||||
1816 | |||||
1817 | // For a special case where the code generates two dots between |
||||
1818 | // column name and table name. |
||||
1819 | $sort_order = preg_replace('/\.\./', '.', $sort_order); |
||||
1820 | // Incase this is the current column save $single_sort_order |
||||
1821 | if ($current_name == $name_to_use_in_sort) { |
||||
1822 | if (mb_strpos($current_name, '(') !== false) { |
||||
1823 | $single_sort_order = "\n" . 'ORDER BY ' . Util::backquote($current_name) . ' '; |
||||
1824 | } else { |
||||
1825 | $single_sort_order = "\n" . 'ORDER BY ' . $sort_tbl |
||||
1826 | . Util::backquote( |
||||
1827 | $current_name |
||||
1828 | ) . ' '; |
||||
1829 | } |
||||
1830 | if ($is_in_sort) { |
||||
1831 | [$single_sort_order, $order_img] |
||||
1832 | = $this->getSortingUrlParams( |
||||
1833 | $sort_direction, |
||||
1834 | $single_sort_order, |
||||
1835 | $index |
||||
1836 | ); |
||||
1837 | } else { |
||||
1838 | $single_sort_order .= strtoupper($sort_direction[$index]); |
||||
1839 | } |
||||
1840 | } |
||||
1841 | if ($current_name == $name_to_use_in_sort && $is_in_sort) { |
||||
1842 | // We need to generate the arrow button and related html |
||||
1843 | [$sort_order, $order_img] = $this->getSortingUrlParams( |
||||
1844 | $sort_direction, |
||||
1845 | $sort_order, |
||||
1846 | $index |
||||
1847 | ); |
||||
1848 | $order_img .= ' <small>' . ($index + 1) . '</small>'; |
||||
1849 | } else { |
||||
1850 | $sort_order .= strtoupper($sort_direction[$index]); |
||||
1851 | } |
||||
1852 | // Separate columns by a comma |
||||
1853 | $sort_order .= ', '; |
||||
1854 | } |
||||
1855 | // remove the comma from the last column name in the newly |
||||
1856 | // constructed clause |
||||
1857 | $sort_order = mb_substr( |
||||
1858 | $sort_order, |
||||
1859 | 0, |
||||
1860 | mb_strlen($sort_order) - 2 |
||||
1861 | ); |
||||
1862 | if (empty($order_img)) { |
||||
1863 | $order_img = ''; |
||||
1864 | } |
||||
1865 | |||||
1866 | return [ |
||||
1867 | $single_sort_order, |
||||
1868 | $sort_order, |
||||
1869 | $order_img, |
||||
1870 | ]; |
||||
1871 | } |
||||
1872 | |||||
1873 | /** |
||||
1874 | * Check whether the column is sorted |
||||
1875 | * |
||||
1876 | * @see getTableHeaders() |
||||
1877 | * |
||||
1878 | * @param array $sort_expression sort expression |
||||
1879 | * @param array $sort_expression_nodirection sort expression without direction |
||||
1880 | * @param string $sort_tbl the table name |
||||
1881 | * @param string $name_to_use_in_sort the sorting column name |
||||
1882 | * |
||||
1883 | * @return bool the column sorted or not |
||||
1884 | * |
||||
1885 | * @access private |
||||
1886 | */ |
||||
1887 | private function isInSorted( |
||||
1888 | array $sort_expression, |
||||
1889 | array $sort_expression_nodirection, |
||||
1890 | $sort_tbl, |
||||
1891 | $name_to_use_in_sort |
||||
1892 | ) { |
||||
1893 | $index_in_expression = 0; |
||||
1894 | |||||
1895 | foreach ($sort_expression_nodirection as $index => $clause) { |
||||
1896 | if (mb_strpos($clause, '.') !== false) { |
||||
1897 | $fragments = explode('.', $clause); |
||||
1898 | $clause2 = $fragments[0] . '.' . str_replace('`', '', $fragments[1]); |
||||
1899 | } else { |
||||
1900 | $clause2 = $sort_tbl . str_replace('`', '', $clause); |
||||
1901 | } |
||||
1902 | if ($clause2 === $sort_tbl . $name_to_use_in_sort) { |
||||
1903 | $index_in_expression = $index; |
||||
1904 | break; |
||||
1905 | } |
||||
1906 | } |
||||
1907 | if (empty($sort_expression[$index_in_expression])) { |
||||
1908 | $is_in_sort = false; |
||||
1909 | } else { |
||||
1910 | // Field name may be preceded by a space, or any number |
||||
1911 | // of characters followed by a dot (tablename.fieldname) |
||||
1912 | // so do a direct comparison for the sort expression; |
||||
1913 | // this avoids problems with queries like |
||||
1914 | // "SELECT id, count(id)..." and clicking to sort |
||||
1915 | // on id or on count(id). |
||||
1916 | // Another query to test this: |
||||
1917 | // SELECT p.*, FROM_UNIXTIME(p.temps) FROM mytable AS p |
||||
1918 | // (and try clicking on each column's header twice) |
||||
1919 | $noSortTable = empty($sort_tbl) || mb_strpos( |
||||
1920 | $sort_expression_nodirection[$index_in_expression], |
||||
1921 | $sort_tbl |
||||
1922 | ) === false; |
||||
1923 | $noOpenParenthesis = mb_strpos( |
||||
1924 | $sort_expression_nodirection[$index_in_expression], |
||||
1925 | '(' |
||||
1926 | ) === false; |
||||
1927 | if (! empty($sort_tbl) && $noSortTable && $noOpenParenthesis) { |
||||
1928 | $new_sort_expression_nodirection = $sort_tbl |
||||
1929 | . $sort_expression_nodirection[$index_in_expression]; |
||||
1930 | } else { |
||||
1931 | $new_sort_expression_nodirection |
||||
1932 | = $sort_expression_nodirection[$index_in_expression]; |
||||
1933 | } |
||||
1934 | |||||
1935 | //Back quotes are removed in next comparison, so remove them from value |
||||
1936 | //to compare. |
||||
1937 | $name_to_use_in_sort = str_replace('`', '', $name_to_use_in_sort); |
||||
1938 | |||||
1939 | $is_in_sort = false; |
||||
1940 | $sort_name = str_replace('`', '', $sort_tbl) . $name_to_use_in_sort; |
||||
1941 | |||||
1942 | if ($sort_name == str_replace('`', '', $new_sort_expression_nodirection) |
||||
1943 | || $sort_name == str_replace('`', '', $sort_expression_nodirection[$index_in_expression]) |
||||
1944 | ) { |
||||
1945 | $is_in_sort = true; |
||||
1946 | } |
||||
1947 | } |
||||
1948 | |||||
1949 | return $is_in_sort; |
||||
1950 | } |
||||
1951 | |||||
1952 | /** |
||||
1953 | * Get sort url parameters - sort order and order image |
||||
1954 | * |
||||
1955 | * @see getSingleAndMultiSortUrls() |
||||
1956 | * |
||||
1957 | * @param array $sort_direction the sort direction |
||||
1958 | * @param string $sort_order the sorting order |
||||
1959 | * @param int $index the index of sort direction array. |
||||
1960 | * |
||||
1961 | * @return array 2 element array - $sort_order, $order_img |
||||
1962 | * |
||||
1963 | * @access private |
||||
1964 | */ |
||||
1965 | private function getSortingUrlParams(array $sort_direction, $sort_order, $index) |
||||
1966 | { |
||||
1967 | if (strtoupper(trim($sort_direction[$index])) == self::DESCENDING_SORT_DIR) { |
||||
1968 | $sort_order .= ' ASC'; |
||||
1969 | $order_img = ' ' . Generator::getImage( |
||||
1970 | 's_desc', |
||||
1971 | __('Descending'), |
||||
1972 | [ |
||||
1973 | 'class' => 'soimg', |
||||
1974 | 'title' => '', |
||||
1975 | ] |
||||
1976 | ); |
||||
1977 | $order_img .= ' ' . Generator::getImage( |
||||
1978 | 's_asc', |
||||
1979 | __('Ascending'), |
||||
1980 | [ |
||||
1981 | 'class' => 'soimg hide', |
||||
1982 | 'title' => '', |
||||
1983 | ] |
||||
1984 | ); |
||||
1985 | } else { |
||||
1986 | $sort_order .= ' DESC'; |
||||
1987 | $order_img = ' ' . Generator::getImage( |
||||
1988 | 's_asc', |
||||
1989 | __('Ascending'), |
||||
1990 | [ |
||||
1991 | 'class' => 'soimg', |
||||
1992 | 'title' => '', |
||||
1993 | ] |
||||
1994 | ); |
||||
1995 | $order_img .= ' ' . Generator::getImage( |
||||
1996 | 's_desc', |
||||
1997 | __('Descending'), |
||||
1998 | [ |
||||
1999 | 'class' => 'soimg hide', |
||||
2000 | 'title' => '', |
||||
2001 | ] |
||||
2002 | ); |
||||
2003 | } |
||||
2004 | |||||
2005 | return [ |
||||
2006 | $sort_order, |
||||
2007 | $order_img, |
||||
2008 | ]; |
||||
2009 | } |
||||
2010 | |||||
2011 | /** |
||||
2012 | * Get sort order link |
||||
2013 | * |
||||
2014 | * @see getTableHeaders() |
||||
2015 | * |
||||
2016 | * @param string $order_img the sort order image |
||||
2017 | * @param stdClass $fields_meta set of field properties |
||||
2018 | * @param string $order_url the url for sort |
||||
2019 | * @param string $multi_order_url the url for sort |
||||
2020 | * |
||||
2021 | * @return string the sort order link |
||||
2022 | * |
||||
2023 | * @access private |
||||
2024 | */ |
||||
2025 | private function getSortOrderLink( |
||||
2026 | $order_img, |
||||
2027 | $fields_meta, |
||||
2028 | $order_url, |
||||
2029 | $multi_order_url |
||||
2030 | ) { |
||||
2031 | $order_link_params = ['class' => 'sortlink']; |
||||
2032 | |||||
2033 | $order_link_content = htmlspecialchars($fields_meta->name); |
||||
2034 | $inner_link_content = $order_link_content . $order_img |
||||
2035 | . '<input type="hidden" value="' . $multi_order_url . '">'; |
||||
2036 | |||||
2037 | return Generator::linkOrButton( |
||||
2038 | $order_url, |
||||
2039 | $inner_link_content, |
||||
2040 | $order_link_params |
||||
2041 | ); |
||||
2042 | } |
||||
2043 | |||||
2044 | /** |
||||
2045 | * Check if the column contains numeric data. If yes, then set the |
||||
2046 | * column header's alignment right |
||||
2047 | * |
||||
2048 | * @see getDraggableClassForSortableColumns() |
||||
2049 | * |
||||
2050 | * @param stdClass $fields_meta set of field properties |
||||
2051 | * @param array $th_class array containing classes |
||||
2052 | * |
||||
2053 | * @return void |
||||
2054 | */ |
||||
2055 | private function getClassForNumericColumnType($fields_meta, array &$th_class) |
||||
2056 | { |
||||
2057 | if (! preg_match( |
||||
2058 | '@int|decimal|float|double|real|bit|boolean|serial@i', |
||||
2059 | (string) $fields_meta->type |
||||
2060 | )) { |
||||
2061 | return; |
||||
2062 | } |
||||
2063 | |||||
2064 | $th_class[] = 'right'; |
||||
2065 | } |
||||
2066 | |||||
2067 | /** |
||||
2068 | * Prepare columns to draggable effect for sortable columns |
||||
2069 | * |
||||
2070 | * @see getTableHeaders() |
||||
2071 | * |
||||
2072 | * @param bool $col_visib the column is visible (false) |
||||
2073 | * array the column is not visible (string array) |
||||
2074 | * @param string $col_visib_j element of $col_visib array |
||||
2075 | * @param stdClass $fields_meta set of field properties |
||||
2076 | * @param string $order_link the order link |
||||
2077 | * @param string $comments the comment for the column |
||||
2078 | * |
||||
2079 | * @return string html content |
||||
2080 | * |
||||
2081 | * @access private |
||||
2082 | */ |
||||
2083 | private function getDraggableClassForSortableColumns( |
||||
2084 | $col_visib, |
||||
2085 | $col_visib_j, |
||||
2086 | $fields_meta, |
||||
2087 | $order_link, |
||||
2088 | $comments |
||||
2089 | ) { |
||||
2090 | $draggable_html = '<th'; |
||||
2091 | $th_class = []; |
||||
2092 | $th_class[] = 'draggable'; |
||||
2093 | $this->getClassForNumericColumnType($fields_meta, $th_class); |
||||
2094 | if ($col_visib && ! $col_visib_j) { |
||||
2095 | $th_class[] = 'hide'; |
||||
2096 | } |
||||
2097 | |||||
2098 | $th_class[] = 'column_heading'; |
||||
2099 | if ($GLOBALS['cfg']['BrowsePointerEnable'] == true) { |
||||
2100 | $th_class[] = 'pointer'; |
||||
2101 | } |
||||
2102 | |||||
2103 | if ($GLOBALS['cfg']['BrowseMarkerEnable'] == true) { |
||||
2104 | $th_class[] = 'marker'; |
||||
2105 | } |
||||
2106 | |||||
2107 | $draggable_html .= ' class="' . implode(' ', $th_class) . '"'; |
||||
2108 | |||||
2109 | $draggable_html .= ' data-column="' . htmlspecialchars($fields_meta->name) |
||||
2110 | . '">' . $order_link . $comments . '</th>'; |
||||
2111 | |||||
2112 | return $draggable_html; |
||||
2113 | } |
||||
2114 | |||||
2115 | /** |
||||
2116 | * Prepare columns to draggable effect for non sortable columns |
||||
2117 | * |
||||
2118 | * @see getTableHeaders() |
||||
2119 | * |
||||
2120 | * @param bool $col_visib the column is visible (false) |
||||
2121 | * array the column is not visible (string array) |
||||
2122 | * @param string $col_visib_j element of $col_visib array |
||||
2123 | * @param bool $condition_field whether to add CSS class condition |
||||
2124 | * @param stdClass $fields_meta set of field properties |
||||
2125 | * @param string $comments the comment for the column |
||||
2126 | * |
||||
2127 | * @return string html content |
||||
2128 | * |
||||
2129 | * @access private |
||||
2130 | */ |
||||
2131 | private function getDraggableClassForNonSortableColumns( |
||||
2132 | $col_visib, |
||||
2133 | $col_visib_j, |
||||
2134 | $condition_field, |
||||
2135 | $fields_meta, |
||||
2136 | $comments |
||||
2137 | ) { |
||||
2138 | $draggable_html = '<th'; |
||||
2139 | $th_class = []; |
||||
2140 | $th_class[] = 'draggable'; |
||||
2141 | $this->getClassForNumericColumnType($fields_meta, $th_class); |
||||
2142 | if ($col_visib && ! $col_visib_j) { |
||||
2143 | $th_class[] = 'hide'; |
||||
2144 | } |
||||
2145 | |||||
2146 | if ($condition_field) { |
||||
2147 | $th_class[] = 'condition'; |
||||
2148 | } |
||||
2149 | |||||
2150 | $draggable_html .= ' class="' . implode(' ', $th_class) . '"'; |
||||
2151 | |||||
2152 | $draggable_html .= ' data-column="' |
||||
2153 | . htmlspecialchars((string) $fields_meta->name) . '">'; |
||||
2154 | |||||
2155 | $draggable_html .= htmlspecialchars((string) $fields_meta->name); |
||||
2156 | |||||
2157 | $draggable_html .= "\n" . $comments . '</th>'; |
||||
2158 | |||||
2159 | return $draggable_html; |
||||
2160 | } |
||||
2161 | |||||
2162 | /** |
||||
2163 | * Prepare column to show at right side - check boxes or empty column |
||||
2164 | * |
||||
2165 | * @see getTableHeaders() |
||||
2166 | * |
||||
2167 | * @param array $displayParts which elements to display |
||||
2168 | * @param string $full_or_partial_text_link full/partial link or text button |
||||
2169 | * @param string $colspan column span of table header |
||||
2170 | * |
||||
2171 | * @return string html content |
||||
2172 | * |
||||
2173 | * @access private |
||||
2174 | */ |
||||
2175 | private function getColumnAtRightSide( |
||||
2176 | array &$displayParts, |
||||
2177 | $full_or_partial_text_link, |
||||
2178 | $colspan |
||||
2179 | ) { |
||||
2180 | $right_column_html = ''; |
||||
2181 | $display_params = $this->__get('display_params'); |
||||
2182 | |||||
2183 | // Displays the needed checkboxes at the right |
||||
2184 | // column of the result table header if possible and required... |
||||
2185 | if (($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_RIGHT) |
||||
2186 | || ($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_BOTH) |
||||
2187 | && (($displayParts['edit_lnk'] != self::NO_EDIT_OR_DELETE) |
||||
2188 | || ($displayParts['del_lnk'] != self::NO_EDIT_OR_DELETE)) |
||||
2189 | && ($displayParts['text_btn'] == '1') |
||||
2190 | ) { |
||||
2191 | $display_params['emptyafter'] |
||||
2192 | = ($displayParts['edit_lnk'] != self::NO_EDIT_OR_DELETE) |
||||
2193 | && ($displayParts['del_lnk'] != self::NO_EDIT_OR_DELETE) ? 4 : 1; |
||||
2194 | |||||
2195 | $right_column_html .= "\n" |
||||
2196 | . '<th class="column_action print_ignore" ' . $colspan . '>' |
||||
2197 | . $full_or_partial_text_link |
||||
2198 | . '</th>'; |
||||
2199 | } elseif (($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_LEFT) |
||||
2200 | || ($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_BOTH) |
||||
2201 | && (($displayParts['edit_lnk'] == self::NO_EDIT_OR_DELETE) |
||||
2202 | && ($displayParts['del_lnk'] == self::NO_EDIT_OR_DELETE)) |
||||
2203 | && (! isset($GLOBALS['is_header_sent']) || ! $GLOBALS['is_header_sent']) |
||||
2204 | ) { |
||||
2205 | // ... elseif no button, displays empty columns if required |
||||
2206 | // (unless coming from Browse mode print view) |
||||
2207 | |||||
2208 | $display_params['emptyafter'] |
||||
2209 | = ($displayParts['edit_lnk'] != self::NO_EDIT_OR_DELETE) |
||||
2210 | && ($displayParts['del_lnk'] != self::NO_EDIT_OR_DELETE) ? 4 : 1; |
||||
2211 | |||||
2212 | $right_column_html .= "\n" . '<td class="print_ignore" ' . $colspan |
||||
2213 | . '></td>'; |
||||
2214 | } |
||||
2215 | |||||
2216 | $this->__set('display_params', $display_params); |
||||
2217 | |||||
2218 | return $right_column_html; |
||||
2219 | } |
||||
2220 | |||||
2221 | /** |
||||
2222 | * Prepares the display for a value |
||||
2223 | * |
||||
2224 | * @see getDataCellForGeometryColumns(), |
||||
2225 | * getDataCellForNonNumericColumns() |
||||
2226 | * |
||||
2227 | * @param string $class class of table cell |
||||
2228 | * @param bool $conditionField whether to add CSS class condition |
||||
2229 | * @param string $value value to display |
||||
2230 | * |
||||
2231 | * @return string the td |
||||
2232 | * |
||||
2233 | * @access private |
||||
2234 | */ |
||||
2235 | 8 | private function buildValueDisplay($class, $conditionField, $value) |
|||
2236 | { |
||||
2237 | 8 | return $this->template->render('display/results/value_display', [ |
|||
2238 | 8 | 'class' => $class, |
|||
2239 | 8 | 'condition_field' => $conditionField, |
|||
2240 | 8 | 'value' => $value, |
|||
2241 | ]); |
||||
2242 | } |
||||
2243 | |||||
2244 | /** |
||||
2245 | * Prepares the display for a null value |
||||
2246 | * |
||||
2247 | * @see getDataCellForNumericColumns(), |
||||
2248 | * getDataCellForGeometryColumns(), |
||||
2249 | * getDataCellForNonNumericColumns() |
||||
2250 | * |
||||
2251 | * @param string $class class of table cell |
||||
2252 | * @param bool $conditionField whether to add CSS class condition |
||||
2253 | * @param stdClass $meta the meta-information about this field |
||||
2254 | * @param string $align cell alignment |
||||
2255 | * |
||||
2256 | * @return string the td |
||||
2257 | * |
||||
2258 | * @access private |
||||
2259 | */ |
||||
2260 | 4 | private function buildNullDisplay($class, $conditionField, $meta, $align = '') |
|||
2261 | { |
||||
2262 | 4 | $classes = $this->addClass($class, $conditionField, $meta, ''); |
|||
2263 | |||||
2264 | 4 | return $this->template->render('display/results/null_display', [ |
|||
2265 | 4 | 'align' => $align, |
|||
2266 | 4 | 'meta' => $meta, |
|||
2267 | 4 | 'classes' => $classes, |
|||
2268 | ]); |
||||
2269 | } |
||||
2270 | |||||
2271 | /** |
||||
2272 | * Prepares the display for an empty value |
||||
2273 | * |
||||
2274 | * @see getDataCellForNumericColumns(), |
||||
2275 | * getDataCellForGeometryColumns(), |
||||
2276 | * getDataCellForNonNumericColumns() |
||||
2277 | * |
||||
2278 | * @param string $class class of table cell |
||||
2279 | * @param bool $conditionField whether to add CSS class condition |
||||
2280 | * @param stdClass $meta the meta-information about this field |
||||
2281 | * @param string $align cell alignment |
||||
2282 | * |
||||
2283 | * @return string the td |
||||
2284 | * |
||||
2285 | * @access private |
||||
2286 | */ |
||||
2287 | private function buildEmptyDisplay($class, $conditionField, $meta, $align = '') |
||||
2288 | { |
||||
2289 | $classes = $this->addClass($class, $conditionField, $meta, 'nowrap'); |
||||
2290 | |||||
2291 | return $this->template->render('display/results/empty_display', [ |
||||
2292 | 'align' => $align, |
||||
2293 | 'classes' => $classes, |
||||
2294 | ]); |
||||
2295 | } |
||||
2296 | |||||
2297 | /** |
||||
2298 | * Adds the relevant classes. |
||||
2299 | * |
||||
2300 | * @see buildNullDisplay(), getRowData() |
||||
2301 | * |
||||
2302 | * @param string $class class of table cell |
||||
2303 | * @param bool $condition_field whether to add CSS class |
||||
2304 | * condition |
||||
2305 | * @param stdClass $meta the meta-information about the |
||||
2306 | * field |
||||
2307 | * @param string $nowrap avoid wrapping |
||||
2308 | * @param bool $is_field_truncated is field truncated (display ...) |
||||
2309 | * @param TransformationsPlugin|string $transformation_plugin transformation plugin. |
||||
2310 | * Can also be the default function: |
||||
2311 | * Core::mimeDefaultFunction |
||||
2312 | * @param string $default_function default transformation function |
||||
2313 | * |
||||
2314 | * @return string the list of classes |
||||
2315 | * |
||||
2316 | * @access private |
||||
2317 | */ |
||||
2318 | 20 | private function addClass( |
|||
2319 | $class, |
||||
2320 | $condition_field, |
||||
2321 | $meta, |
||||
2322 | $nowrap, |
||||
2323 | $is_field_truncated = false, |
||||
2324 | $transformation_plugin = '', |
||||
2325 | $default_function = '' |
||||
2326 | ) { |
||||
2327 | $classes = [ |
||||
2328 | 20 | $class, |
|||
2329 | 20 | $nowrap, |
|||
2330 | ]; |
||||
2331 | |||||
2332 | 20 | if (isset($meta->mimetype)) { |
|||
2333 | 4 | $classes[] = preg_replace('/\//', '_', $meta->mimetype); |
|||
2334 | } |
||||
2335 | |||||
2336 | 20 | if ($condition_field) { |
|||
2337 | $classes[] = 'condition'; |
||||
2338 | } |
||||
2339 | |||||
2340 | 20 | if ($is_field_truncated) { |
|||
2341 | $classes[] = 'truncated'; |
||||
2342 | } |
||||
2343 | |||||
2344 | 20 | $mime_map = $this->__get('mime_map'); |
|||
2345 | 20 | $orgFullColName = $this->__get('db') . '.' . $meta->orgtable |
|||
2346 | 20 | . '.' . $meta->orgname; |
|||
2347 | 20 | if ($transformation_plugin != $default_function |
|||
2348 | 20 | || ! empty($mime_map[$orgFullColName]['input_transformation']) |
|||
2349 | ) { |
||||
2350 | 8 | $classes[] = 'transformed'; |
|||
2351 | } |
||||
2352 | |||||
2353 | // Define classes to be added to this data field based on the type of data |
||||
2354 | $matches = [ |
||||
2355 | 20 | 'enum' => 'enum', |
|||
2356 | 'set' => 'set', |
||||
2357 | 'binary' => 'hex', |
||||
2358 | ]; |
||||
2359 | |||||
2360 | 20 | foreach ($matches as $key => $value) { |
|||
2361 | 20 | if (mb_strpos($meta->flags, $key) === false) { |
|||
2362 | 20 | continue; |
|||
2363 | } |
||||
2364 | |||||
2365 | 8 | $classes[] = $value; |
|||
2366 | } |
||||
2367 | |||||
2368 | 20 | if (mb_strpos($meta->type, 'bit') !== false) { |
|||
2369 | $classes[] = 'bit'; |
||||
2370 | } |
||||
2371 | |||||
2372 | 20 | return implode(' ', $classes); |
|||
2373 | } |
||||
2374 | |||||
2375 | /** |
||||
2376 | * Prepare the body of the results table |
||||
2377 | * |
||||
2378 | * @see getTable() |
||||
2379 | * |
||||
2380 | * @param int $dt_result the link id associated to the query |
||||
2381 | * which results have to be displayed |
||||
2382 | * @param array $displayParts which elements to display |
||||
2383 | * @param array $map the list of relations |
||||
2384 | * @param array $analyzed_sql_results analyzed sql results |
||||
2385 | * @param bool $is_limited_display with limited operations or not |
||||
2386 | * |
||||
2387 | * @return string html content |
||||
2388 | * |
||||
2389 | * @global array $row current row data |
||||
2390 | * @access private |
||||
2391 | */ |
||||
2392 | private function getTableBody( |
||||
2393 | &$dt_result, |
||||
2394 | array &$displayParts, |
||||
2395 | array $map, |
||||
2396 | array $analyzed_sql_results, |
||||
2397 | $is_limited_display = false |
||||
2398 | ) { |
||||
2399 | global $row; // mostly because of browser transformations, |
||||
2400 | // to make the row-data accessible in a plugin |
||||
2401 | |||||
2402 | $table_body_html = ''; |
||||
2403 | |||||
2404 | // query without conditions to shorten URLs when needed, 200 is just |
||||
2405 | // guess, it should depend on remaining URL length |
||||
2406 | $url_sql_query = $this->getUrlSqlQuery($analyzed_sql_results); |
||||
2407 | |||||
2408 | $display_params = $this->__get('display_params'); |
||||
2409 | |||||
2410 | if (! is_array($map)) { |
||||
2411 | $map = []; |
||||
2412 | } |
||||
2413 | |||||
2414 | $row_no = 0; |
||||
2415 | $display_params['edit'] = []; |
||||
2416 | $display_params['copy'] = []; |
||||
2417 | $display_params['delete'] = []; |
||||
2418 | $display_params['data'] = []; |
||||
2419 | $display_params['row_delete'] = []; |
||||
2420 | $this->__set('display_params', $display_params); |
||||
2421 | |||||
2422 | // name of the class added to all grid editable elements; |
||||
2423 | // if we don't have all the columns of a unique key in the result set, |
||||
2424 | // do not permit grid editing |
||||
2425 | if ($is_limited_display || ! $this->__get('editable')) { |
||||
2426 | $grid_edit_class = ''; |
||||
2427 | } else { |
||||
2428 | switch ($GLOBALS['cfg']['GridEditing']) { |
||||
2429 | case 'double-click': |
||||
2430 | // trying to reduce generated HTML by using shorter |
||||
2431 | // classes like click1 and click2 |
||||
2432 | $grid_edit_class = 'grid_edit click2'; |
||||
2433 | break; |
||||
2434 | case 'click': |
||||
2435 | $grid_edit_class = 'grid_edit click1'; |
||||
2436 | break; |
||||
2437 | default: // 'disabled' |
||||
2438 | $grid_edit_class = ''; |
||||
2439 | break; |
||||
2440 | } |
||||
2441 | } |
||||
2442 | |||||
2443 | // prepare to get the column order, if available |
||||
2444 | [$col_order, $col_visib] = $this->getColumnParams( |
||||
2445 | $analyzed_sql_results |
||||
2446 | ); |
||||
2447 | |||||
2448 | // Correction University of Virginia 19991216 in the while below |
||||
2449 | // Previous code assumed that all tables have keys, specifically that |
||||
2450 | // the phpMyAdmin GUI should support row delete/edit only for such |
||||
2451 | // tables. |
||||
2452 | // Although always using keys is arguably the prescribed way of |
||||
2453 | // defining a relational table, it is not required. This will in |
||||
2454 | // particular be violated by the novice. |
||||
2455 | // We want to encourage phpMyAdmin usage by such novices. So the code |
||||
2456 | // below has been changed to conditionally work as before when the |
||||
2457 | // table being displayed has one or more keys; but to display |
||||
2458 | // delete/edit options correctly for tables without keys. |
||||
2459 | |||||
2460 | $whereClauseMap = $this->__get('whereClauseMap'); |
||||
2461 | while ($row = $GLOBALS['dbi']->fetchRow($dt_result)) { |
||||
2462 | // add repeating headers |
||||
2463 | if (($row_no != 0) && ($_SESSION['tmpval']['repeat_cells'] != 0) |
||||
2464 | && ! $row_no % $_SESSION['tmpval']['repeat_cells'] |
||||
2465 | ) { |
||||
2466 | $table_body_html .= $this->getRepeatingHeaders( |
||||
2467 | $display_params |
||||
2468 | ); |
||||
2469 | } |
||||
2470 | |||||
2471 | $tr_class = []; |
||||
2472 | if ($GLOBALS['cfg']['BrowsePointerEnable'] != true) { |
||||
2473 | $tr_class[] = 'nopointer'; |
||||
2474 | } |
||||
2475 | if ($GLOBALS['cfg']['BrowseMarkerEnable'] != true) { |
||||
2476 | $tr_class[] = 'nomarker'; |
||||
2477 | } |
||||
2478 | |||||
2479 | // pointer code part |
||||
2480 | $classes = (empty($tr_class) ? ' ' : 'class="' . implode(' ', $tr_class) . '"'); |
||||
2481 | $table_body_html .= '<tr ' . $classes . ' >'; |
||||
2482 | |||||
2483 | // 1. Prepares the row |
||||
2484 | |||||
2485 | // In print view these variable needs to be initialized |
||||
2486 | $del_url = null; |
||||
2487 | $del_str = null; |
||||
2488 | $edit_str = null; |
||||
2489 | $js_conf = null; |
||||
2490 | $copy_url = null; |
||||
2491 | $copy_str = null; |
||||
2492 | $edit_url = null; |
||||
2493 | |||||
2494 | // 1.2 Defines the URLs for the modify/delete link(s) |
||||
2495 | |||||
2496 | if (($displayParts['edit_lnk'] != self::NO_EDIT_OR_DELETE) |
||||
2497 | || ($displayParts['del_lnk'] != self::NO_EDIT_OR_DELETE) |
||||
2498 | ) { |
||||
2499 | $expressions = []; |
||||
2500 | |||||
2501 | if (isset($analyzed_sql_results['statement']) |
||||
2502 | && $analyzed_sql_results['statement'] instanceof SelectStatement |
||||
2503 | ) { |
||||
2504 | $expressions = $analyzed_sql_results['statement']->expr; |
||||
2505 | } |
||||
2506 | |||||
2507 | // Results from a "SELECT" statement -> builds the |
||||
2508 | // WHERE clause to use in links (a unique key if possible) |
||||
2509 | /** |
||||
2510 | * @todo $where_clause could be empty, for example a table |
||||
2511 | * with only one field and it's a BLOB; in this case, |
||||
2512 | * avoid to display the delete and edit links |
||||
2513 | */ |
||||
2514 | [$where_clause, $clause_is_unique, $condition_array] = Util::getUniqueCondition( |
||||
2515 | $dt_result, |
||||
2516 | $this->__get('fields_cnt'), |
||||
2517 | $this->__get('fields_meta'), |
||||
2518 | $row, |
||||
2519 | false, |
||||
2520 | $this->__get('table'), |
||||
2521 | $expressions |
||||
2522 | ); |
||||
2523 | $whereClauseMap[$row_no][$this->__get('table')] = $where_clause; |
||||
2524 | $this->__set('whereClauseMap', $whereClauseMap); |
||||
2525 | |||||
2526 | // 1.2.1 Modify link(s) - update row case |
||||
2527 | if ($displayParts['edit_lnk'] == self::UPDATE_ROW) { |
||||
2528 | [ |
||||
2529 | $edit_url, |
||||
2530 | $copy_url, |
||||
2531 | $edit_str, |
||||
2532 | $copy_str, |
||||
2533 | ] |
||||
2534 | = $this->getModifiedLinks( |
||||
2535 | $where_clause, |
||||
2536 | $clause_is_unique, |
||||
2537 | $url_sql_query |
||||
2538 | ); |
||||
2539 | } // end if (1.2.1) |
||||
2540 | |||||
2541 | // 1.2.2 Delete/Kill link(s) |
||||
2542 | [$del_url, $del_str, $js_conf] |
||||
2543 | = $this->getDeleteAndKillLinks( |
||||
2544 | $where_clause, |
||||
2545 | $clause_is_unique, |
||||
2546 | $url_sql_query, |
||||
2547 | $displayParts['del_lnk'], |
||||
2548 | $row |
||||
2549 | ); |
||||
2550 | |||||
2551 | // 1.3 Displays the links at left if required |
||||
2552 | if (($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_LEFT) |
||||
2553 | || ($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_BOTH) |
||||
2554 | ) { |
||||
2555 | $table_body_html .= $this->template->render('display/results/checkbox_and_links', [ |
||||
2556 | 'position' => self::POSITION_LEFT, |
||||
2557 | 'has_checkbox' => ! empty($del_url) && $displayParts['del_lnk'] !== self::KILL_PROCESS, |
||||
2558 | 'edit' => ['url' => $edit_url, 'string' => $edit_str, 'clause_is_unique' => $clause_is_unique], |
||||
2559 | 'copy' => ['url' => $copy_url, 'string' => $copy_str], |
||||
2560 | 'delete' => ['url' => $del_url, 'string' => $del_str], |
||||
2561 | 'row_number' => $row_no, |
||||
2562 | 'where_clause' => $where_clause, |
||||
2563 | 'condition' => json_encode($condition_array), |
||||
2564 | 'is_ajax' => Response::getInstance()->isAjax(), |
||||
2565 | 'js_conf' => $js_conf ?? '', |
||||
2566 | ]); |
||||
2567 | } elseif ($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_NONE) { |
||||
2568 | $table_body_html .= $this->template->render('display/results/checkbox_and_links', [ |
||||
2569 | 'position' => self::POSITION_NONE, |
||||
2570 | 'has_checkbox' => ! empty($del_url) && $displayParts['del_lnk'] !== self::KILL_PROCESS, |
||||
2571 | 'edit' => ['url' => $edit_url, 'string' => $edit_str, 'clause_is_unique' => $clause_is_unique], |
||||
2572 | 'copy' => ['url' => $copy_url, 'string' => $copy_str], |
||||
2573 | 'delete' => ['url' => $del_url, 'string' => $del_str], |
||||
2574 | 'row_number' => $row_no, |
||||
2575 | 'where_clause' => $where_clause, |
||||
2576 | 'condition' => json_encode($condition_array), |
||||
2577 | 'is_ajax' => Response::getInstance()->isAjax(), |
||||
2578 | 'js_conf' => $js_conf ?? '', |
||||
2579 | ]); |
||||
2580 | } // end if (1.3) |
||||
2581 | } // end if (1) |
||||
2582 | |||||
2583 | // 2. Displays the rows' values |
||||
2584 | if ($this->__get('mime_map') === null) { |
||||
2585 | $this->setMimeMap(); |
||||
2586 | } |
||||
2587 | $table_body_html .= $this->getRowValues( |
||||
2588 | $dt_result, |
||||
2589 | $row, |
||||
2590 | $row_no, |
||||
2591 | $col_order, |
||||
2592 | $map, |
||||
2593 | $grid_edit_class, |
||||
2594 | $col_visib, |
||||
2595 | $url_sql_query, |
||||
2596 | $analyzed_sql_results |
||||
2597 | ); |
||||
2598 | |||||
2599 | // 3. Displays the modify/delete links on the right if required |
||||
2600 | if (($displayParts['edit_lnk'] != self::NO_EDIT_OR_DELETE) |
||||
2601 | || ($displayParts['del_lnk'] != self::NO_EDIT_OR_DELETE) |
||||
2602 | ) { |
||||
2603 | if (($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_RIGHT) |
||||
2604 | || ($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_BOTH) |
||||
2605 | ) { |
||||
2606 | $table_body_html .= $this->template->render('display/results/checkbox_and_links', [ |
||||
2607 | 'position' => self::POSITION_RIGHT, |
||||
2608 | 'has_checkbox' => ! empty($del_url) && $displayParts['del_lnk'] !== self::KILL_PROCESS, |
||||
2609 | 'edit' => [ |
||||
2610 | 'url' => $edit_url, |
||||
2611 | 'string' => $edit_str, |
||||
2612 | 'clause_is_unique' => $clause_is_unique ?? true, |
||||
2613 | ], |
||||
2614 | 'copy' => ['url' => $copy_url, 'string' => $copy_str], |
||||
2615 | 'delete' => ['url' => $del_url, 'string' => $del_str], |
||||
2616 | 'row_number' => $row_no, |
||||
2617 | 'where_clause' => $where_clause ?? '', |
||||
2618 | 'condition' => json_encode($condition_array ?? []), |
||||
2619 | 'is_ajax' => Response::getInstance()->isAjax(), |
||||
2620 | 'js_conf' => $js_conf ?? '', |
||||
2621 | ]); |
||||
2622 | } |
||||
2623 | } // end if (3) |
||||
2624 | |||||
2625 | $table_body_html .= '</tr>'; |
||||
2626 | $table_body_html .= "\n"; |
||||
2627 | $row_no++; |
||||
2628 | } // end while |
||||
2629 | |||||
2630 | return $table_body_html; |
||||
2631 | } |
||||
2632 | |||||
2633 | /** |
||||
2634 | * Sets the MIME details of the columns in the results set |
||||
2635 | * |
||||
2636 | * @return void |
||||
2637 | */ |
||||
2638 | private function setMimeMap() |
||||
2639 | { |
||||
2640 | $fields_meta = $this->__get('fields_meta'); |
||||
2641 | $mimeMap = []; |
||||
2642 | $added = []; |
||||
2643 | |||||
2644 | for ($currentColumn = 0; $currentColumn < $this->__get('fields_cnt'); ++$currentColumn) { |
||||
2645 | $meta = $fields_meta[$currentColumn]; |
||||
2646 | $orgFullTableName = $this->__get('db') . '.' . $meta->orgtable; |
||||
2647 | |||||
2648 | if (! $GLOBALS['cfgRelation']['commwork'] |
||||
2649 | || ! $GLOBALS['cfgRelation']['mimework'] |
||||
2650 | || ! $GLOBALS['cfg']['BrowseMIME'] |
||||
2651 | || $_SESSION['tmpval']['hide_transformation'] |
||||
2652 | || ! empty($added[$orgFullTableName]) |
||||
2653 | ) { |
||||
2654 | continue; |
||||
2655 | } |
||||
2656 | |||||
2657 | $mimeMap = array_merge( |
||||
2658 | $mimeMap, |
||||
2659 | $this->transformations->getMime($this->__get('db'), $meta->orgtable, false, true) ?? [] |
||||
2660 | ); |
||||
2661 | $added[$orgFullTableName] = true; |
||||
2662 | } |
||||
2663 | |||||
2664 | // special browser transformation for some SHOW statements |
||||
2665 | if ($this->__get('is_show') |
||||
2666 | && ! $_SESSION['tmpval']['hide_transformation'] |
||||
2667 | ) { |
||||
2668 | preg_match( |
||||
2669 | '@^SHOW[[:space:]]+(VARIABLES|(FULL[[:space:]]+)?' |
||||
2670 | . 'PROCESSLIST|STATUS|TABLE|GRANTS|CREATE|LOGS|DATABASES|FIELDS' |
||||
2671 | . ')@i', |
||||
2672 | $this->__get('sql_query'), |
||||
2673 | $which |
||||
2674 | ); |
||||
2675 | |||||
2676 | if (isset($which[1])) { |
||||
2677 | $str = ' ' . strtoupper($which[1]); |
||||
2678 | $isShowProcessList = strpos($str, 'PROCESSLIST') > 0; |
||||
2679 | if ($isShowProcessList) { |
||||
2680 | $mimeMap['..Info'] = [ |
||||
2681 | 'mimetype' => 'Text_Plain', |
||||
2682 | 'transformation' => 'output/Text_Plain_Sql.php', |
||||
2683 | ]; |
||||
2684 | } |
||||
2685 | |||||
2686 | $isShowCreateTable = preg_match( |
||||
2687 | '@CREATE[[:space:]]+TABLE@i', |
||||
2688 | $this->__get('sql_query') |
||||
2689 | ); |
||||
2690 | if ($isShowCreateTable) { |
||||
2691 | $mimeMap['..Create Table'] = [ |
||||
2692 | 'mimetype' => 'Text_Plain', |
||||
2693 | 'transformation' => 'output/Text_Plain_Sql.php', |
||||
2694 | ]; |
||||
2695 | } |
||||
2696 | } |
||||
2697 | } |
||||
2698 | |||||
2699 | $this->__set('mime_map', $mimeMap); |
||||
2700 | } |
||||
2701 | |||||
2702 | /** |
||||
2703 | * Get the values for one data row |
||||
2704 | * |
||||
2705 | * @see getTableBody() |
||||
2706 | * |
||||
2707 | * @param int $dt_result the link id associated to the query |
||||
2708 | * which results have to be displayed |
||||
2709 | * @param array $row current row data |
||||
2710 | * @param int $row_no the index of current row |
||||
2711 | * @param array|false $col_order the column order false when |
||||
2712 | * a property not found false |
||||
2713 | * when a property not found |
||||
2714 | * @param array $map the list of relations |
||||
2715 | * @param string $grid_edit_class the class for all editable |
||||
2716 | * columns |
||||
2717 | * @param bool|array|string $col_visib column is visible(false); |
||||
2718 | * column isn't visible(string |
||||
2719 | * array) |
||||
2720 | * @param string $url_sql_query the analyzed sql query |
||||
2721 | * @param array $analyzed_sql_results analyzed sql results |
||||
2722 | * |
||||
2723 | * @return string html content |
||||
2724 | * |
||||
2725 | * @access private |
||||
2726 | */ |
||||
2727 | 4 | private function getRowValues( |
|||
2728 | &$dt_result, |
||||
2729 | array $row, |
||||
2730 | $row_no, |
||||
2731 | $col_order, |
||||
2732 | array $map, |
||||
2733 | $grid_edit_class, |
||||
2734 | $col_visib, |
||||
2735 | $url_sql_query, |
||||
2736 | array $analyzed_sql_results |
||||
2737 | ) { |
||||
2738 | 4 | $row_values_html = ''; |
|||
2739 | |||||
2740 | // Following variable are needed for use in isset/empty or |
||||
2741 | // use with array indexes/safe use in foreach |
||||
2742 | 4 | $sql_query = $this->__get('sql_query'); |
|||
2743 | 4 | $fields_meta = $this->__get('fields_meta'); |
|||
2744 | 4 | $highlight_columns = $this->__get('highlight_columns'); |
|||
2745 | 4 | $mime_map = $this->__get('mime_map'); |
|||
2746 | |||||
2747 | 4 | $row_info = $this->getRowInfoForSpecialLinks($row, $col_order); |
|||
2748 | |||||
2749 | 4 | $whereClauseMap = $this->__get('whereClauseMap'); |
|||
2750 | |||||
2751 | 4 | $columnCount = $this->__get('fields_cnt'); |
|||
2752 | 4 | for ($currentColumn = 0; $currentColumn < $columnCount; ++$currentColumn) { |
|||
2753 | // assign $i with appropriate column order |
||||
2754 | 4 | $i = is_array($col_order) ? $col_order[$currentColumn] : $currentColumn; |
|||
2755 | |||||
2756 | 4 | $meta = $fields_meta[$i]; |
|||
2757 | $orgFullColName |
||||
2758 | 4 | = $this->__get('db') . '.' . $meta->orgtable . '.' . $meta->orgname; |
|||
2759 | |||||
2760 | 4 | $not_null_class = $meta->not_null ? 'not_null' : ''; |
|||
2761 | 4 | $relation_class = isset($map[$meta->name]) ? 'relation' : ''; |
|||
2762 | 4 | $hide_class = is_array($col_visib) && isset($col_visib[$currentColumn]) && ! $col_visib[$currentColumn] |
|||
2763 | ? 'hide' |
||||
2764 | 4 | : ''; |
|||
2765 | 4 | $grid_edit = $meta->orgtable != '' ? $grid_edit_class : ''; |
|||
2766 | |||||
2767 | // handle datetime-related class, for grid editing |
||||
2768 | $field_type_class |
||||
2769 | 4 | = $this->getClassForDateTimeRelatedFields($meta->type); |
|||
2770 | |||||
2771 | 4 | $is_field_truncated = false; |
|||
2772 | // combine all the classes applicable to this column's value |
||||
2773 | 4 | $class = $this->getClassesForColumn( |
|||
2774 | 4 | $grid_edit, |
|||
2775 | 2 | $not_null_class, |
|||
2776 | 2 | $relation_class, |
|||
2777 | 2 | $hide_class, |
|||
2778 | 2 | $field_type_class |
|||
2779 | ); |
||||
2780 | |||||
2781 | // See if this column should get highlight because it's used in the |
||||
2782 | // where-query. |
||||
2783 | 4 | $condition_field = isset($highlight_columns) |
|||
2784 | && (isset($highlight_columns[$meta->name]) |
||||
2785 | 4 | || isset($highlight_columns[Util::backquote($meta->name)])); |
|||
2786 | |||||
2787 | // Wrap MIME-transformations. [MIME] |
||||
2788 | $default_function = [ |
||||
2789 | 4 | Core::class, |
|||
2790 | 'mimeDefaultFunction', |
||||
2791 | ]; // default_function |
||||
2792 | 4 | $transformation_plugin = $default_function; |
|||
2793 | 4 | $transform_options = []; |
|||
2794 | |||||
2795 | 4 | if ($GLOBALS['cfgRelation']['mimework'] |
|||
2796 | 4 | && $GLOBALS['cfg']['BrowseMIME'] |
|||
2797 | ) { |
||||
2798 | 4 | if (isset($mime_map[$orgFullColName]['mimetype']) |
|||
2799 | 4 | && ! empty($mime_map[$orgFullColName]['transformation']) |
|||
2800 | ) { |
||||
2801 | 4 | $file = $mime_map[$orgFullColName]['transformation']; |
|||
2802 | 4 | $include_file = 'libraries/classes/Plugins/Transformations/' . $file; |
|||
2803 | |||||
2804 | 4 | if (@file_exists($include_file)) { |
|||
2805 | 4 | $class_name = $this->transformations->getClassName($include_file); |
|||
2806 | 4 | if (class_exists($class_name)) { |
|||
2807 | // todo add $plugin_manager |
||||
2808 | 4 | $plugin_manager = null; |
|||
2809 | 4 | $transformation_plugin = new $class_name( |
|||
2810 | 4 | $plugin_manager |
|||
2811 | ); |
||||
2812 | |||||
2813 | 4 | $transform_options = $this->transformations->getOptions( |
|||
2814 | 4 | $mime_map[$orgFullColName]['transformation_options'] ?? '' |
|||
2815 | ); |
||||
2816 | |||||
2817 | 4 | $meta->mimetype = str_replace( |
|||
2818 | 4 | '_', |
|||
2819 | 4 | '/', |
|||
2820 | 4 | $mime_map[$orgFullColName]['mimetype'] |
|||
2821 | ); |
||||
2822 | } |
||||
2823 | } // end if file_exists |
||||
2824 | } // end if transformation is set |
||||
2825 | } // end if mime/transformation works. |
||||
2826 | |||||
2827 | // Check whether the field needs to display with syntax highlighting |
||||
2828 | |||||
2829 | 4 | $dbLower = mb_strtolower($this->__get('db')); |
|||
2830 | 4 | $tblLower = mb_strtolower($meta->orgtable); |
|||
2831 | 4 | $nameLower = mb_strtolower($meta->orgname); |
|||
2832 | 4 | if (! empty($this->transformation_info[$dbLower][$tblLower][$nameLower]) |
|||
2833 | 4 | && isset($row[$i]) |
|||
2834 | 4 | && (trim($row[$i]) != '') |
|||
2835 | 4 | && ! $_SESSION['tmpval']['hide_transformation'] |
|||
2836 | ) { |
||||
2837 | include_once $this->transformation_info[$dbLower][$tblLower][$nameLower][0]; |
||||
2838 | $transformation_plugin = new $this->transformation_info[$dbLower][$tblLower][$nameLower][1](null); |
||||
2839 | |||||
2840 | $transform_options = $this->transformations->getOptions( |
||||
2841 | $mime_map[$orgFullColName]['transformation_options'] ?? '' |
||||
2842 | ); |
||||
2843 | |||||
2844 | $orgTable = mb_strtolower($meta->orgtable); |
||||
2845 | $orgName = mb_strtolower($meta->orgname); |
||||
2846 | |||||
2847 | $meta->mimetype = str_replace( |
||||
2848 | '_', |
||||
2849 | '/', |
||||
2850 | $this->transformation_info[$dbLower][$orgTable][$orgName][2] |
||||
2851 | ); |
||||
2852 | } |
||||
2853 | |||||
2854 | // Check for the predefined fields need to show as link in schemas |
||||
2855 | 4 | $specialSchemaLinks = SpecialSchemaLinks::get(); |
|||
2856 | |||||
2857 | 4 | if (! empty($specialSchemaLinks[$dbLower][$tblLower][$nameLower])) { |
|||
2858 | $linking_url = $this->getSpecialLinkUrl( |
||||
2859 | $specialSchemaLinks, |
||||
2860 | $row[$i], |
||||
2861 | $row_info, |
||||
2862 | mb_strtolower($meta->orgname) |
||||
2863 | ); |
||||
2864 | $transformation_plugin = new Text_Plain_Link(); |
||||
2865 | |||||
2866 | $transform_options = [ |
||||
2867 | 0 => $linking_url, |
||||
2868 | 2 => true, |
||||
2869 | ]; |
||||
2870 | |||||
2871 | $meta->mimetype = str_replace( |
||||
2872 | '_', |
||||
2873 | '/', |
||||
2874 | 'Text/Plain' |
||||
2875 | ); |
||||
2876 | } |
||||
2877 | |||||
2878 | 4 | $expressions = []; |
|||
2879 | |||||
2880 | 4 | if (isset($analyzed_sql_results['statement']) |
|||
2881 | 4 | && $analyzed_sql_results['statement'] instanceof SelectStatement |
|||
2882 | ) { |
||||
2883 | 4 | $expressions = $analyzed_sql_results['statement']->expr; |
|||
2884 | } |
||||
2885 | |||||
2886 | /** |
||||
2887 | * The result set can have columns from more than one table, |
||||
2888 | * this is why we have to check for the unique conditions |
||||
2889 | * related to this table; however getUniqueCondition() is |
||||
2890 | * costly and does not need to be called if we already know |
||||
2891 | * the conditions for the current table. |
||||
2892 | */ |
||||
2893 | 4 | if (! isset($whereClauseMap[$row_no][$meta->orgtable])) { |
|||
2894 | 4 | $unique_conditions = Util::getUniqueCondition( |
|||
2895 | 4 | $dt_result, |
|||
2896 | 4 | $this->__get('fields_cnt'), |
|||
2897 | 4 | $this->__get('fields_meta'), |
|||
2898 | 4 | $row, |
|||
2899 | 4 | false, |
|||
2900 | 4 | $meta->orgtable, |
|||
2901 | 4 | $expressions |
|||
2902 | ); |
||||
2903 | 4 | $whereClauseMap[$row_no][$meta->orgtable] = $unique_conditions[0]; |
|||
2904 | } |
||||
2905 | |||||
2906 | $_url_params = [ |
||||
2907 | 4 | 'db' => $this->__get('db'), |
|||
2908 | 4 | 'table' => $meta->orgtable, |
|||
2909 | 4 | 'where_clause' => $whereClauseMap[$row_no][$meta->orgtable], |
|||
2910 | 4 | 'transform_key' => $meta->orgname, |
|||
2911 | ]; |
||||
2912 | |||||
2913 | 4 | if (! empty($sql_query)) { |
|||
2914 | $_url_params['sql_query'] = $url_sql_query; |
||||
2915 | } |
||||
2916 | |||||
2917 | 4 | $transform_options['wrapper_link'] = Url::getCommon($_url_params); |
|||
2918 | 4 | $transform_options['wrapper_params'] = $_url_params; |
|||
2919 | |||||
2920 | 4 | $display_params = $this->__get('display_params'); |
|||
2921 | |||||
2922 | // in some situations (issue 11406), numeric returns 1 |
||||
2923 | // even for a string type |
||||
2924 | // for decimal numeric is returning 1 |
||||
2925 | // have to improve logic |
||||
2926 | 4 | if (($meta->numeric == 1 && $meta->type != 'string') || $meta->type == 'real') { |
|||
2927 | // n u m e r i c |
||||
2928 | |||||
2929 | 4 | $display_params['data'][$row_no][$i] |
|||
2930 | 4 | = $this->getDataCellForNumericColumns( |
|||
2931 | 4 | $row[$i] === null ? null : (string) $row[$i], |
|||
2932 | 2 | $class, |
|||
2933 | 2 | $condition_field, |
|||
2934 | 2 | $meta, |
|||
2935 | 2 | $map, |
|||
2936 | 2 | $is_field_truncated, |
|||
2937 | 2 | $analyzed_sql_results, |
|||
2938 | 2 | $transformation_plugin, |
|||
2939 | 2 | $default_function, |
|||
2940 | 2 | $transform_options |
|||
2941 | ); |
||||
2942 | } elseif ($meta->type == self::GEOMETRY_FIELD) { |
||||
2943 | // g e o m e t r y |
||||
2944 | |||||
2945 | // Remove 'grid_edit' from $class as we do not allow to |
||||
2946 | // inline-edit geometry data. |
||||
2947 | $class = str_replace('grid_edit', '', $class); |
||||
2948 | |||||
2949 | $display_params['data'][$row_no][$i] |
||||
2950 | = $this->getDataCellForGeometryColumns( |
||||
2951 | $row[$i], |
||||
2952 | $class, |
||||
2953 | $meta, |
||||
2954 | $map, |
||||
2955 | $_url_params, |
||||
2956 | $condition_field, |
||||
2957 | $transformation_plugin, |
||||
2958 | $default_function, |
||||
2959 | $transform_options, |
||||
2960 | $analyzed_sql_results |
||||
2961 | ); |
||||
2962 | } else { |
||||
2963 | // n o t n u m e r i c |
||||
2964 | |||||
2965 | $display_params['data'][$row_no][$i] |
||||
2966 | = $this->getDataCellForNonNumericColumns( |
||||
2967 | $row[$i], |
||||
2968 | $class, |
||||
2969 | $meta, |
||||
2970 | $map, |
||||
2971 | $_url_params, |
||||
2972 | $condition_field, |
||||
2973 | $transformation_plugin, |
||||
2974 | $default_function, |
||||
2975 | $transform_options, |
||||
2976 | $is_field_truncated, |
||||
2977 | $analyzed_sql_results, |
||||
2978 | $dt_result, |
||||
2979 | $i |
||||
2980 | ); |
||||
2981 | } |
||||
2982 | |||||
2983 | // output stored cell |
||||
2984 | 4 | $row_values_html .= $display_params['data'][$row_no][$i]; |
|||
2985 | |||||
2986 | 4 | if (isset($display_params['rowdata'][$i][$row_no])) { |
|||
2987 | $display_params['rowdata'][$i][$row_no] |
||||
2988 | .= $display_params['data'][$row_no][$i]; |
||||
2989 | } else { |
||||
2990 | 4 | $display_params['rowdata'][$i][$row_no] |
|||
2991 | 4 | = $display_params['data'][$row_no][$i]; |
|||
2992 | } |
||||
2993 | |||||
2994 | 4 | $this->__set('display_params', $display_params); |
|||
2995 | } // end for |
||||
2996 | |||||
2997 | 4 | return $row_values_html; |
|||
2998 | } |
||||
2999 | |||||
3000 | /** |
||||
3001 | * Get link for display special schema links |
||||
3002 | * |
||||
3003 | * @param array $specialSchemaLinks special schema links |
||||
3004 | * @param string $column_value column value |
||||
3005 | * @param array $row_info information about row |
||||
3006 | * @param string $field_name column name |
||||
3007 | * |
||||
3008 | * @return string generated link |
||||
3009 | */ |
||||
3010 | 12 | private function getSpecialLinkUrl( |
|||
3011 | array $specialSchemaLinks, |
||||
3012 | $column_value, |
||||
3013 | array $row_info, |
||||
3014 | $field_name |
||||
3015 | ) { |
||||
3016 | 12 | $linking_url_params = []; |
|||
3017 | 12 | $db = mb_strtolower($this->__get('db')); |
|||
3018 | 12 | $table = mb_strtolower($this->__get('table')); |
|||
3019 | 12 | $link_relations = $specialSchemaLinks[$db][$table][$field_name]; |
|||
3020 | |||||
3021 | 12 | if (! is_array($link_relations['link_param'])) { |
|||
3022 | 8 | $linking_url_params[$link_relations['link_param']] = $column_value; |
|||
3023 | } else { |
||||
3024 | // Consider only the case of creating link for column field |
||||
3025 | // sql query that needs to be passed as url param |
||||
3026 | 4 | $sql = 'SELECT `' . $column_value . '` FROM `' |
|||
3027 | 4 | . $row_info[$link_relations['link_param'][1]] . '`.`' |
|||
3028 | 4 | . $row_info[$link_relations['link_param'][2]] . '`'; |
|||
3029 | 4 | $linking_url_params[$link_relations['link_param'][0]] = $sql; |
|||
3030 | } |
||||
3031 | |||||
3032 | 12 | $divider = strpos($link_relations['default_page'], '?') ? '&' : '?'; |
|||
3033 | 12 | if (empty($link_relations['link_dependancy_params'])) { |
|||
3034 | return $link_relations['default_page'] |
||||
3035 | . Url::getCommonRaw($linking_url_params, $divider); |
||||
3036 | } |
||||
3037 | |||||
3038 | 12 | foreach ($link_relations['link_dependancy_params'] as $new_param) { |
|||
3039 | // If param_info is an array, set the key and value |
||||
3040 | // from that array |
||||
3041 | 12 | if (is_array($new_param['param_info'])) { |
|||
3042 | 4 | $linking_url_params[$new_param['param_info'][0]] |
|||
3043 | 4 | = $new_param['param_info'][1]; |
|||
3044 | 4 | continue; |
|||
3045 | } |
||||
3046 | |||||
3047 | 12 | $linking_url_params[$new_param['param_info']] |
|||
3048 | 12 | = $row_info[mb_strtolower($new_param['column_name'])]; |
|||
3049 | |||||
3050 | // Special case 1 - when executing routines, according |
||||
3051 | // to the type of the routine, url param changes |
||||
3052 | 12 | if (empty($row_info['routine_type'])) { |
|||
3053 | 6 | continue; |
|||
3054 | } |
||||
3055 | } |
||||
3056 | |||||
3057 | 12 | return $link_relations['default_page'] |
|||
3058 | 12 | . Url::getCommonRaw($linking_url_params, $divider); |
|||
3059 | } |
||||
3060 | |||||
3061 | /** |
||||
3062 | * Prepare row information for display special links |
||||
3063 | * |
||||
3064 | * @param array $row current row data |
||||
3065 | * @param array|bool $col_order the column order |
||||
3066 | * |
||||
3067 | * @return array associative array with column nama -> value |
||||
3068 | */ |
||||
3069 | 8 | private function getRowInfoForSpecialLinks(array $row, $col_order) |
|||
3070 | { |
||||
3071 | 8 | $row_info = []; |
|||
3072 | 8 | $fields_meta = $this->__get('fields_meta'); |
|||
3073 | |||||
3074 | 8 | for ($n = 0; $n < $this->__get('fields_cnt'); ++$n) { |
|||
3075 | 8 | $m = is_array($col_order) ? $col_order[$n] : $n; |
|||
3076 | 8 | $row_info[mb_strtolower($fields_meta[$m]->orgname)] |
|||
3077 | 8 | = $row[$m]; |
|||
3078 | } |
||||
3079 | |||||
3080 | 8 | return $row_info; |
|||
3081 | } |
||||
3082 | |||||
3083 | /** |
||||
3084 | * Get url sql query without conditions to shorten URLs |
||||
3085 | * |
||||
3086 | * @see getTableBody() |
||||
3087 | * |
||||
3088 | * @param array $analyzed_sql_results analyzed sql results |
||||
3089 | * |
||||
3090 | * @return string analyzed sql query |
||||
3091 | * |
||||
3092 | * @access private |
||||
3093 | */ |
||||
3094 | private function getUrlSqlQuery(array $analyzed_sql_results) |
||||
3095 | { |
||||
3096 | if (($analyzed_sql_results['querytype'] != 'SELECT') |
||||
3097 | || (mb_strlen($this->__get('sql_query')) < 200) |
||||
3098 | ) { |
||||
3099 | return $this->__get('sql_query'); |
||||
3100 | } |
||||
3101 | |||||
3102 | $query = 'SELECT ' . Query::getClause( |
||||
3103 | $analyzed_sql_results['statement'], |
||||
3104 | $analyzed_sql_results['parser']->list, |
||||
3105 | 'SELECT' |
||||
3106 | ); |
||||
3107 | |||||
3108 | $from_clause = Query::getClause( |
||||
3109 | $analyzed_sql_results['statement'], |
||||
3110 | $analyzed_sql_results['parser']->list, |
||||
3111 | 'FROM' |
||||
3112 | ); |
||||
3113 | |||||
3114 | if (! empty($from_clause)) { |
||||
3115 | $query .= ' FROM ' . $from_clause; |
||||
3116 | } |
||||
3117 | |||||
3118 | return $query; |
||||
3119 | } |
||||
3120 | |||||
3121 | /** |
||||
3122 | * Get column order and column visibility |
||||
3123 | * |
||||
3124 | * @see getTableBody() |
||||
3125 | * |
||||
3126 | * @param array $analyzed_sql_results analyzed sql results |
||||
3127 | * |
||||
3128 | * @return array 2 element array - $col_order, $col_visib |
||||
3129 | * |
||||
3130 | * @access private |
||||
3131 | */ |
||||
3132 | private function getColumnParams(array $analyzed_sql_results) |
||||
3133 | { |
||||
3134 | if ($this->isSelect($analyzed_sql_results)) { |
||||
3135 | $pmatable = new Table($this->__get('table'), $this->__get('db')); |
||||
3136 | $col_order = $pmatable->getUiProp(Table::PROP_COLUMN_ORDER); |
||||
3137 | /* Validate the value */ |
||||
3138 | if ($col_order !== false) { |
||||
3139 | $fields_cnt = $this->__get('fields_cnt'); |
||||
3140 | foreach ($col_order as $value) { |
||||
3141 | if ($value < $fields_cnt) { |
||||
3142 | continue; |
||||
3143 | } |
||||
3144 | |||||
3145 | $pmatable->removeUiProp(Table::PROP_COLUMN_ORDER); |
||||
3146 | $fields_cnt = false; |
||||
3147 | } |
||||
3148 | } |
||||
3149 | $col_visib = $pmatable->getUiProp(Table::PROP_COLUMN_VISIB); |
||||
3150 | } else { |
||||
3151 | $col_order = false; |
||||
3152 | $col_visib = false; |
||||
3153 | } |
||||
3154 | |||||
3155 | return [ |
||||
3156 | $col_order, |
||||
3157 | $col_visib, |
||||
3158 | ]; |
||||
3159 | } |
||||
3160 | |||||
3161 | /** |
||||
3162 | * Get HTML for repeating headers |
||||
3163 | * |
||||
3164 | * @see getTableBody() |
||||
3165 | * |
||||
3166 | * @param array $display_params holds various display info |
||||
3167 | * |
||||
3168 | * @return string html content |
||||
3169 | * |
||||
3170 | * @access private |
||||
3171 | */ |
||||
3172 | private function getRepeatingHeaders( |
||||
3173 | array $display_params |
||||
3174 | ) { |
||||
3175 | $header_html = '<tr>' . "\n"; |
||||
3176 | |||||
3177 | if ($display_params['emptypre'] > 0) { |
||||
3178 | $header_html .= ' <th colspan="' |
||||
3179 | . $display_params['emptypre'] . '">' |
||||
3180 | . "\n" . ' </th>' . "\n"; |
||||
3181 | } elseif ($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_NONE) { |
||||
3182 | $header_html .= ' <th></th>' . "\n"; |
||||
3183 | } |
||||
3184 | |||||
3185 | foreach ($display_params['desc'] as $val) { |
||||
3186 | $header_html .= $val; |
||||
3187 | } |
||||
3188 | |||||
3189 | if ($display_params['emptyafter'] > 0) { |
||||
3190 | $header_html |
||||
3191 | .= ' <th colspan="' . $display_params['emptyafter'] |
||||
3192 | . '">' |
||||
3193 | . "\n" . ' </th>' . "\n"; |
||||
3194 | } |
||||
3195 | $header_html .= '</tr>' . "\n"; |
||||
3196 | |||||
3197 | return $header_html; |
||||
3198 | } |
||||
3199 | |||||
3200 | /** |
||||
3201 | * Get modified links |
||||
3202 | * |
||||
3203 | * @see getTableBody() |
||||
3204 | * |
||||
3205 | * @param string $where_clause the where clause of the sql |
||||
3206 | * @param bool $clause_is_unique the unique condition of clause |
||||
3207 | * @param string $url_sql_query the analyzed sql query |
||||
3208 | * |
||||
3209 | * @return array<int,string> 5 element array - $edit_url, $copy_url, |
||||
3210 | * $edit_str, $copy_str |
||||
3211 | * |
||||
3212 | * @access private |
||||
3213 | */ |
||||
3214 | private function getModifiedLinks( |
||||
3215 | $where_clause, |
||||
3216 | $clause_is_unique, |
||||
3217 | $url_sql_query |
||||
3218 | ) { |
||||
3219 | $_url_params = [ |
||||
3220 | 'db' => $this->__get('db'), |
||||
3221 | 'table' => $this->__get('table'), |
||||
3222 | 'where_clause' => $where_clause, |
||||
3223 | 'clause_is_unique' => $clause_is_unique, |
||||
3224 | 'sql_query' => $url_sql_query, |
||||
3225 | 'goto' => Url::getFromRoute('/sql'), |
||||
3226 | ]; |
||||
3227 | |||||
3228 | $edit_url = Url::getFromRoute( |
||||
3229 | '/table/change', |
||||
3230 | $_url_params + ['default_action' => 'update'] |
||||
3231 | ); |
||||
3232 | |||||
3233 | $copy_url = Url::getFromRoute( |
||||
3234 | '/table/change', |
||||
3235 | $_url_params + ['default_action' => 'insert'] |
||||
3236 | ); |
||||
3237 | |||||
3238 | $edit_str = $this->getActionLinkContent( |
||||
3239 | 'b_edit', |
||||
3240 | __('Edit') |
||||
3241 | ); |
||||
3242 | $copy_str = $this->getActionLinkContent( |
||||
3243 | 'b_insrow', |
||||
3244 | __('Copy') |
||||
3245 | ); |
||||
3246 | |||||
3247 | return [ |
||||
3248 | $edit_url, |
||||
3249 | $copy_url, |
||||
3250 | $edit_str, |
||||
3251 | $copy_str, |
||||
3252 | ]; |
||||
3253 | } |
||||
3254 | |||||
3255 | /** |
||||
3256 | * Get delete and kill links |
||||
3257 | * |
||||
3258 | * @see getTableBody() |
||||
3259 | * |
||||
3260 | * @param string $where_clause the where clause of the sql |
||||
3261 | * @param bool $clause_is_unique the unique condition of clause |
||||
3262 | * @param string $url_sql_query the analyzed sql query |
||||
3263 | * @param string $del_lnk the delete link of current row |
||||
3264 | * @param array $row the current row |
||||
3265 | * |
||||
3266 | * @return array 3 element array |
||||
3267 | * $del_url, $del_str, $js_conf |
||||
3268 | * |
||||
3269 | * @access private |
||||
3270 | */ |
||||
3271 | private function getDeleteAndKillLinks( |
||||
3272 | $where_clause, |
||||
3273 | $clause_is_unique, |
||||
3274 | $url_sql_query, |
||||
3275 | $del_lnk, |
||||
3276 | array $row |
||||
3277 | ) { |
||||
3278 | $goto = $this->__get('goto'); |
||||
3279 | |||||
3280 | if ($del_lnk == self::DELETE_ROW) { // delete row case |
||||
3281 | $_url_params = [ |
||||
3282 | 'db' => $this->__get('db'), |
||||
3283 | 'table' => $this->__get('table'), |
||||
3284 | 'sql_query' => $url_sql_query, |
||||
3285 | 'message_to_show' => __('The row has been deleted.'), |
||||
3286 | 'goto' => empty($goto) ? Url::getFromRoute('/table/sql') : $goto, |
||||
3287 | ]; |
||||
3288 | |||||
3289 | $lnk_goto = Url::getFromRoute('/sql', $_url_params); |
||||
3290 | |||||
3291 | $del_query = 'DELETE FROM ' |
||||
3292 | . Util::backquote($this->__get('table')) |
||||
3293 | . ' WHERE ' . $where_clause . |
||||
3294 | ($clause_is_unique ? '' : ' LIMIT 1'); |
||||
3295 | |||||
3296 | $_url_params = [ |
||||
3297 | 'db' => $this->__get('db'), |
||||
3298 | 'table' => $this->__get('table'), |
||||
3299 | 'sql_query' => $del_query, |
||||
3300 | 'message_to_show' => __('The row has been deleted.'), |
||||
3301 | 'goto' => $lnk_goto, |
||||
3302 | ]; |
||||
3303 | $del_url = Url::getFromRoute('/sql', $_url_params); |
||||
3304 | |||||
3305 | $js_conf = 'DELETE FROM ' . Sanitize::jsFormat($this->__get('table')) |
||||
3306 | . ' WHERE ' . Sanitize::jsFormat($where_clause, false) |
||||
3307 | . ($clause_is_unique ? '' : ' LIMIT 1'); |
||||
3308 | |||||
3309 | $del_str = $this->getActionLinkContent('b_drop', __('Delete')); |
||||
3310 | } elseif ($del_lnk == self::KILL_PROCESS) { // kill process case |
||||
3311 | $_url_params = [ |
||||
3312 | 'db' => $this->__get('db'), |
||||
3313 | 'table' => $this->__get('table'), |
||||
3314 | 'sql_query' => $url_sql_query, |
||||
3315 | 'goto' => Url::getFromRoute('/'), |
||||
3316 | ]; |
||||
3317 | |||||
3318 | $lnk_goto = Url::getFromRoute('/sql', $_url_params); |
||||
3319 | |||||
3320 | $kill = $GLOBALS['dbi']->getKillQuery((int) $row[0]); |
||||
3321 | |||||
3322 | $_url_params = [ |
||||
3323 | 'db' => 'mysql', |
||||
3324 | 'sql_query' => $kill, |
||||
3325 | 'goto' => $lnk_goto, |
||||
3326 | ]; |
||||
3327 | |||||
3328 | $del_url = Url::getFromRoute('/sql', $_url_params); |
||||
3329 | $js_conf = $kill; |
||||
3330 | $del_str = Generator::getIcon( |
||||
3331 | 'b_drop', |
||||
3332 | __('Kill') |
||||
3333 | ); |
||||
3334 | } else { |
||||
3335 | $del_url = $del_str = $js_conf = null; |
||||
3336 | } |
||||
3337 | |||||
3338 | return [ |
||||
3339 | $del_url, |
||||
3340 | $del_str, |
||||
3341 | $js_conf, |
||||
3342 | ]; |
||||
3343 | } |
||||
3344 | |||||
3345 | /** |
||||
3346 | * Get content inside the table row action links (Edit/Copy/Delete) |
||||
3347 | * |
||||
3348 | * @see getModifiedLinks(), getDeleteAndKillLinks() |
||||
3349 | * |
||||
3350 | * @param string $icon The name of the file to get |
||||
3351 | * @param string $display_text The text displaying after the image icon |
||||
3352 | * |
||||
3353 | * @return string |
||||
3354 | * |
||||
3355 | * @access private |
||||
3356 | */ |
||||
3357 | private function getActionLinkContent($icon, $display_text) |
||||
3358 | { |
||||
3359 | $linkContent = ''; |
||||
3360 | |||||
3361 | if (isset($GLOBALS['cfg']['RowActionType']) |
||||
3362 | && $GLOBALS['cfg']['RowActionType'] == self::ACTION_LINK_CONTENT_ICONS |
||||
3363 | ) { |
||||
3364 | $linkContent .= '<span class="nowrap">' |
||||
3365 | . Generator::getImage( |
||||
3366 | $icon, |
||||
3367 | $display_text |
||||
3368 | ) |
||||
3369 | . '</span>'; |
||||
3370 | } elseif (isset($GLOBALS['cfg']['RowActionType']) |
||||
3371 | && $GLOBALS['cfg']['RowActionType'] == self::ACTION_LINK_CONTENT_TEXT |
||||
3372 | ) { |
||||
3373 | $linkContent .= '<span class="nowrap">' . $display_text . '</span>'; |
||||
3374 | } else { |
||||
3375 | $linkContent .= Generator::getIcon( |
||||
3376 | $icon, |
||||
3377 | $display_text |
||||
3378 | ); |
||||
3379 | } |
||||
3380 | |||||
3381 | return $linkContent; |
||||
3382 | } |
||||
3383 | |||||
3384 | /** |
||||
3385 | * Get the combined classes for a column |
||||
3386 | * |
||||
3387 | * @see getTableBody() |
||||
3388 | * |
||||
3389 | * @param string $grid_edit_class the class for all editable columns |
||||
3390 | * @param string $not_null_class the class for not null columns |
||||
3391 | * @param string $relation_class the class for relations in a column |
||||
3392 | * @param string $hide_class the class for visibility of a column |
||||
3393 | * @param string $field_type_class the class related to type of the field |
||||
3394 | * |
||||
3395 | * @return string the combined classes |
||||
3396 | * |
||||
3397 | * @access private |
||||
3398 | */ |
||||
3399 | 8 | private function getClassesForColumn( |
|||
3400 | $grid_edit_class, |
||||
3401 | $not_null_class, |
||||
3402 | $relation_class, |
||||
3403 | $hide_class, |
||||
3404 | $field_type_class |
||||
3405 | ) { |
||||
3406 | 8 | return 'data ' . $grid_edit_class . ' ' . $not_null_class . ' ' |
|||
3407 | 8 | . $relation_class . ' ' . $hide_class . ' ' . $field_type_class; |
|||
3408 | } |
||||
3409 | |||||
3410 | /** |
||||
3411 | * Get class for datetime related fields |
||||
3412 | * |
||||
3413 | * @see getTableBody() |
||||
3414 | * |
||||
3415 | * @param string $type the type of the column field |
||||
3416 | * |
||||
3417 | * @return string the class for the column |
||||
3418 | * |
||||
3419 | * @access private |
||||
3420 | */ |
||||
3421 | 16 | private function getClassForDateTimeRelatedFields($type) |
|||
3422 | { |
||||
3423 | 16 | if ((substr($type, 0, 9) == self::TIMESTAMP_FIELD) |
|||
3424 | 16 | || ($type == self::DATETIME_FIELD) |
|||
3425 | ) { |
||||
3426 | 4 | $field_type_class = 'datetimefield'; |
|||
3427 | 12 | } elseif ($type == self::DATE_FIELD) { |
|||
3428 | 4 | $field_type_class = 'datefield'; |
|||
3429 | 8 | } elseif ($type == self::TIME_FIELD) { |
|||
3430 | $field_type_class = 'timefield'; |
||||
3431 | 8 | } elseif ($type == self::STRING_FIELD) { |
|||
3432 | 4 | $field_type_class = 'text'; |
|||
3433 | } else { |
||||
3434 | 4 | $field_type_class = ''; |
|||
3435 | } |
||||
3436 | |||||
3437 | 16 | return $field_type_class; |
|||
3438 | } |
||||
3439 | |||||
3440 | /** |
||||
3441 | * Prepare data cell for numeric type fields |
||||
3442 | * |
||||
3443 | * @see getTableBody() |
||||
3444 | * |
||||
3445 | * @param string|null $column the column's value |
||||
3446 | * @param string $class the html class for column |
||||
3447 | * @param bool $condition_field the column should highlighted |
||||
3448 | * or not |
||||
3449 | * @param stdClass $meta the meta-information about this |
||||
3450 | * field |
||||
3451 | * @param array $map the list of relations |
||||
3452 | * @param bool $is_field_truncated the condition for blob data |
||||
3453 | * replacements |
||||
3454 | * @param array $analyzed_sql_results the analyzed query |
||||
3455 | * @param TransformationsPlugin $transformation_plugin the name of transformation plugin |
||||
3456 | * @param string $default_function the default transformation |
||||
3457 | * function |
||||
3458 | * @param array $transform_options the transformation parameters |
||||
3459 | * |
||||
3460 | * @return string the prepared cell, html content |
||||
3461 | * |
||||
3462 | * @access private |
||||
3463 | */ |
||||
3464 | 4 | private function getDataCellForNumericColumns( |
|||
3465 | ?string $column, |
||||
3466 | $class, |
||||
3467 | $condition_field, |
||||
3468 | $meta, |
||||
3469 | array $map, |
||||
3470 | $is_field_truncated, |
||||
3471 | array $analyzed_sql_results, |
||||
3472 | $transformation_plugin, |
||||
3473 | $default_function, |
||||
3474 | array $transform_options |
||||
3475 | ) { |
||||
3476 | 4 | if (! isset($column) || $column === null) { |
|||
3477 | $cell = $this->buildNullDisplay( |
||||
3478 | 'right ' . $class, |
||||
3479 | $condition_field, |
||||
3480 | $meta, |
||||
3481 | '' |
||||
3482 | ); |
||||
3483 | 4 | } elseif ($column != '') { |
|||
3484 | 4 | $nowrap = ' nowrap'; |
|||
3485 | 4 | $where_comparison = ' = ' . $column; |
|||
3486 | |||||
3487 | 4 | $cell = $this->getRowData( |
|||
3488 | 4 | 'right ' . $class, |
|||
3489 | 2 | $condition_field, |
|||
3490 | 2 | $analyzed_sql_results, |
|||
3491 | 2 | $meta, |
|||
3492 | 2 | $map, |
|||
3493 | 2 | $column, |
|||
3494 | 2 | $column, |
|||
3495 | 2 | $transformation_plugin, |
|||
3496 | 2 | $default_function, |
|||
3497 | 2 | $nowrap, |
|||
3498 | 2 | $where_comparison, |
|||
3499 | 2 | $transform_options, |
|||
3500 | 2 | $is_field_truncated, |
|||
3501 | 4 | '' |
|||
3502 | ); |
||||
3503 | } else { |
||||
3504 | $cell = $this->buildEmptyDisplay( |
||||
3505 | 'right ' . $class, |
||||
3506 | $condition_field, |
||||
3507 | $meta, |
||||
3508 | '' |
||||
3509 | ); |
||||
3510 | } |
||||
3511 | |||||
3512 | 4 | return $cell; |
|||
3513 | } |
||||
3514 | |||||
3515 | /** |
||||
3516 | * Get data cell for geometry type fields |
||||
3517 | * |
||||
3518 | * @see getTableBody() |
||||
3519 | * |
||||
3520 | * @param string|null $column the relevant column in data row |
||||
3521 | * @param string $class the html class for column |
||||
3522 | * @param stdClass $meta the meta-information about |
||||
3523 | * this field |
||||
3524 | * @param array $map the list of relations |
||||
3525 | * @param array $_url_params the parameters for generate url |
||||
3526 | * @param bool $condition_field the column should highlighted |
||||
3527 | * or not |
||||
3528 | * @param TransformationsPlugin $transformation_plugin the name of transformation |
||||
3529 | * function |
||||
3530 | * @param string $default_function the default transformation |
||||
3531 | * function |
||||
3532 | * @param string $transform_options the transformation parameters |
||||
3533 | * @param array $analyzed_sql_results the analyzed query |
||||
3534 | * |
||||
3535 | * @return string the prepared data cell, html content |
||||
3536 | * |
||||
3537 | * @access private |
||||
3538 | */ |
||||
3539 | private function getDataCellForGeometryColumns( |
||||
3540 | ?string $column, |
||||
3541 | $class, |
||||
3542 | $meta, |
||||
3543 | array $map, |
||||
3544 | array $_url_params, |
||||
3545 | $condition_field, |
||||
3546 | $transformation_plugin, |
||||
3547 | $default_function, |
||||
3548 | $transform_options, |
||||
3549 | array $analyzed_sql_results |
||||
3550 | ) { |
||||
3551 | if (! isset($column) || $column === null) { |
||||
3552 | return $this->buildNullDisplay($class, $condition_field, $meta); |
||||
3553 | } |
||||
3554 | |||||
3555 | if ($column == '') { |
||||
3556 | return $this->buildEmptyDisplay($class, $condition_field, $meta); |
||||
3557 | } |
||||
3558 | |||||
3559 | // Display as [GEOMETRY - (size)] |
||||
3560 | if ($_SESSION['tmpval']['geoOption'] == self::GEOMETRY_DISP_GEOM) { |
||||
3561 | $geometry_text = $this->handleNonPrintableContents( |
||||
3562 | strtoupper(self::GEOMETRY_FIELD), |
||||
3563 | $column, |
||||
3564 | $transformation_plugin, |
||||
3565 | $transform_options, |
||||
3566 | $default_function, |
||||
3567 | $meta, |
||||
3568 | $_url_params |
||||
3569 | ); |
||||
3570 | |||||
3571 | return $this->buildValueDisplay( |
||||
3572 | $class, |
||||
3573 | $condition_field, |
||||
3574 | $geometry_text |
||||
3575 | ); |
||||
3576 | } |
||||
3577 | |||||
3578 | if ($_SESSION['tmpval']['geoOption'] == self::GEOMETRY_DISP_WKT) { |
||||
3579 | // Prepare in Well Known Text(WKT) format. |
||||
3580 | $where_comparison = ' = ' . $column; |
||||
3581 | |||||
3582 | // Convert to WKT format |
||||
3583 | $wktval = Util::asWKT($column); |
||||
3584 | [ |
||||
3585 | $is_field_truncated, |
||||
3586 | $displayedColumn, |
||||
3587 | // skip 3rd param |
||||
3588 | ] = $this->getPartialText($wktval); |
||||
3589 | |||||
3590 | return $this->getRowData( |
||||
3591 | $class, |
||||
3592 | $condition_field, |
||||
3593 | $analyzed_sql_results, |
||||
3594 | $meta, |
||||
3595 | $map, |
||||
3596 | $wktval, |
||||
3597 | $displayedColumn, |
||||
3598 | $transformation_plugin, |
||||
3599 | $default_function, |
||||
3600 | '', |
||||
3601 | $where_comparison, |
||||
3602 | $transform_options, |
||||
3603 | $is_field_truncated, |
||||
3604 | '' |
||||
3605 | ); |
||||
3606 | } |
||||
3607 | |||||
3608 | // Prepare in Well Known Binary (WKB) format. |
||||
3609 | |||||
3610 | if ($_SESSION['tmpval']['display_binary']) { |
||||
3611 | $where_comparison = ' = ' . $column; |
||||
3612 | |||||
3613 | $wkbval = substr(bin2hex($column), 8); |
||||
3614 | [ |
||||
3615 | $is_field_truncated, |
||||
3616 | $displayedColumn, |
||||
3617 | // skip 3rd param |
||||
3618 | ] = $this->getPartialText($wkbval); |
||||
3619 | |||||
3620 | return $this->getRowData( |
||||
3621 | $class, |
||||
3622 | $condition_field, |
||||
3623 | $analyzed_sql_results, |
||||
3624 | $meta, |
||||
3625 | $map, |
||||
3626 | $wkbval, |
||||
3627 | $displayedColumn, |
||||
3628 | $transformation_plugin, |
||||
3629 | $default_function, |
||||
3630 | '', |
||||
3631 | $where_comparison, |
||||
3632 | $transform_options, |
||||
3633 | $is_field_truncated, |
||||
3634 | '' |
||||
3635 | ); |
||||
3636 | } |
||||
3637 | |||||
3638 | $wkbval = $this->handleNonPrintableContents( |
||||
3639 | self::BINARY_FIELD, |
||||
3640 | $column, |
||||
3641 | $transformation_plugin, |
||||
3642 | $transform_options, |
||||
3643 | $default_function, |
||||
3644 | $meta, |
||||
3645 | $_url_params |
||||
3646 | ); |
||||
3647 | |||||
3648 | return $this->buildValueDisplay( |
||||
3649 | $class, |
||||
3650 | $condition_field, |
||||
3651 | $wkbval |
||||
3652 | ); |
||||
3653 | } |
||||
3654 | |||||
3655 | /** |
||||
3656 | * Get data cell for non numeric type fields |
||||
3657 | * |
||||
3658 | * @see getTableBody() |
||||
3659 | * |
||||
3660 | * @param string|null $column the relevant column in data row |
||||
3661 | * @param string $class the html class for column |
||||
3662 | * @param stdClass $meta the meta-information about |
||||
3663 | * the field |
||||
3664 | * @param array $map the list of relations |
||||
3665 | * @param array $_url_params the parameters for generate |
||||
3666 | * url |
||||
3667 | * @param bool $condition_field the column should highlighted |
||||
3668 | * or not |
||||
3669 | * @param TransformationsPlugin $transformation_plugin the name of transformation |
||||
3670 | * function |
||||
3671 | * @param string $default_function the default transformation |
||||
3672 | * function |
||||
3673 | * @param string $transform_options the transformation parameters |
||||
3674 | * @param bool $is_field_truncated is data truncated due to |
||||
3675 | * LimitChars |
||||
3676 | * @param array $analyzed_sql_results the analyzed query |
||||
3677 | * @param int $dt_result the link id associated to |
||||
3678 | * the query which results |
||||
3679 | * have to be displayed |
||||
3680 | * @param int $col_index the column index |
||||
3681 | * |
||||
3682 | * @return string the prepared data cell, html content |
||||
3683 | * |
||||
3684 | * @access private |
||||
3685 | */ |
||||
3686 | 16 | private function getDataCellForNonNumericColumns( |
|||
3687 | ?string $column, |
||||
3688 | $class, |
||||
3689 | $meta, |
||||
3690 | array $map, |
||||
3691 | array $_url_params, |
||||
3692 | $condition_field, |
||||
3693 | $transformation_plugin, |
||||
3694 | $default_function, |
||||
3695 | $transform_options, |
||||
3696 | $is_field_truncated, |
||||
3697 | array $analyzed_sql_results, |
||||
3698 | &$dt_result, |
||||
3699 | $col_index |
||||
3700 | ) { |
||||
3701 | 16 | $original_length = 0; |
|||
3702 | |||||
3703 | 16 | $is_analyse = $this->__get('is_analyse'); |
|||
3704 | 16 | $field_flags = $GLOBALS['dbi']->fieldFlags($dt_result, $col_index); |
|||
3705 | |||||
3706 | 16 | $bIsText = is_object($transformation_plugin) |
|||
3707 | 8 | && strpos($transformation_plugin->getMIMEType(), 'Text') |
|||
3708 | 16 | === false; |
|||
3709 | |||||
3710 | // disable inline grid editing |
||||
3711 | // if binary fields are protected |
||||
3712 | // or transformation plugin is of non text type |
||||
3713 | // such as image |
||||
3714 | 16 | if ((stripos($field_flags, self::BINARY_FIELD) !== false |
|||
3715 | 8 | && ($GLOBALS['cfg']['ProtectBinary'] === 'all' |
|||
3716 | 4 | || ($GLOBALS['cfg']['ProtectBinary'] === 'noblob' |
|||
3717 | 4 | && stripos($meta->type, self::BLOB_FIELD) === false) |
|||
3718 | 4 | || ($GLOBALS['cfg']['ProtectBinary'] === 'blob' |
|||
3719 | 8 | && stripos($meta->type, self::BLOB_FIELD) !== false))) |
|||
3720 | 16 | || $bIsText |
|||
3721 | ) { |
||||
3722 | 4 | $class = str_replace('grid_edit', '', $class); |
|||
3723 | } |
||||
3724 | |||||
3725 | 16 | if (! isset($column) || $column === null) { |
|||
3726 | 4 | return $this->buildNullDisplay($class, $condition_field, $meta); |
|||
3727 | } |
||||
3728 | |||||
3729 | 12 | if ($column == '') { |
|||
3730 | return $this->buildEmptyDisplay($class, $condition_field, $meta); |
||||
3731 | } |
||||
3732 | |||||
3733 | // Cut all fields to $GLOBALS['cfg']['LimitChars'] |
||||
3734 | // (unless it's a link-type transformation or binary) |
||||
3735 | 12 | $displayedColumn = $column; |
|||
3736 | 12 | if (! (is_object($transformation_plugin) |
|||
3737 | 12 | && strpos($transformation_plugin->getName(), 'Link') !== false) |
|||
3738 | 12 | && stripos($field_flags, self::BINARY_FIELD) === false |
|||
3739 | ) { |
||||
3740 | [ |
||||
3741 | $is_field_truncated, |
||||
3742 | $column, |
||||
3743 | $original_length, |
||||
3744 | 4 | ] = $this->getPartialText($column); |
|||
3745 | } |
||||
3746 | |||||
3747 | 12 | $formatted = false; |
|||
3748 | 12 | if (isset($meta->_type) && $meta->_type === MYSQLI_TYPE_BIT) { |
|||
3749 | $displayedColumn = Util::printableBitValue( |
||||
3750 | (int) $displayedColumn, |
||||
3751 | (int) $meta->length |
||||
3752 | ); |
||||
3753 | |||||
3754 | // some results of PROCEDURE ANALYSE() are reported as |
||||
3755 | // being BINARY but they are quite readable, |
||||
3756 | // so don't treat them as BINARY |
||||
3757 | 12 | } elseif (stripos($field_flags, self::BINARY_FIELD) !== false |
|||
3758 | 12 | && ! (isset($is_analyse) && $is_analyse) |
|||
3759 | ) { |
||||
3760 | // we show the BINARY or BLOB message and field's size |
||||
3761 | // (or maybe use a transformation) |
||||
3762 | 8 | $binary_or_blob = self::BLOB_FIELD; |
|||
3763 | 8 | if ($meta->type === self::STRING_FIELD) { |
|||
3764 | $binary_or_blob = self::BINARY_FIELD; |
||||
3765 | } |
||||
3766 | 8 | $displayedColumn = $this->handleNonPrintableContents( |
|||
3767 | 8 | $binary_or_blob, |
|||
3768 | 4 | $displayedColumn, |
|||
3769 | 4 | $transformation_plugin, |
|||
3770 | 4 | $transform_options, |
|||
3771 | 4 | $default_function, |
|||
3772 | 4 | $meta, |
|||
3773 | 4 | $_url_params, |
|||
3774 | 4 | $is_field_truncated |
|||
3775 | ); |
||||
3776 | 8 | $class = $this->addClass( |
|||
3777 | 8 | $class, |
|||
3778 | 8 | $condition_field, |
|||
3779 | 8 | $meta, |
|||
3780 | 8 | '', |
|||
3781 | 8 | $is_field_truncated, |
|||
3782 | 8 | $transformation_plugin, |
|||
3783 | 8 | $default_function |
|||
3784 | ); |
||||
3785 | 8 | $result = strip_tags($column); |
|||
3786 | // disable inline grid editing |
||||
3787 | // if binary or blob data is not shown |
||||
3788 | 8 | if (stripos($result, $binary_or_blob) !== false) { |
|||
3789 | $class = str_replace('grid_edit', '', $class); |
||||
3790 | } |
||||
3791 | 8 | $formatted = true; |
|||
3792 | } |
||||
3793 | |||||
3794 | 12 | if ($formatted) { |
|||
3795 | 8 | return $this->buildValueDisplay( |
|||
3796 | 8 | $class, |
|||
3797 | 8 | $condition_field, |
|||
3798 | 8 | $displayedColumn |
|||
3799 | ); |
||||
3800 | } |
||||
3801 | |||||
3802 | // transform functions may enable no-wrapping: |
||||
3803 | 4 | $function_nowrap = 'applyTransformationNoWrap'; |
|||
3804 | |||||
3805 | 4 | $bool_nowrap = ($default_function != $transformation_plugin) |
|||
3806 | 4 | && function_exists((string) $transformation_plugin->$function_nowrap()) |
|||
3807 | ? $transformation_plugin->$function_nowrap($transform_options) |
||||
3808 | 4 | : false; |
|||
3809 | |||||
3810 | // do not wrap if date field type |
||||
3811 | 4 | $nowrap = preg_match('@DATE|TIME@i', $meta->type) |
|||
3812 | 4 | || $bool_nowrap ? ' nowrap' : ''; |
|||
3813 | |||||
3814 | $where_comparison = ' = \'' |
||||
3815 | 4 | . $GLOBALS['dbi']->escapeString($column) |
|||
3816 | 4 | . '\''; |
|||
3817 | |||||
3818 | 4 | return $this->getRowData( |
|||
3819 | 4 | $class, |
|||
3820 | 2 | $condition_field, |
|||
3821 | 2 | $analyzed_sql_results, |
|||
3822 | 2 | $meta, |
|||
3823 | 2 | $map, |
|||
3824 | 2 | $column, |
|||
3825 | 2 | $displayedColumn, |
|||
3826 | 2 | $transformation_plugin, |
|||
3827 | 2 | $default_function, |
|||
3828 | 2 | $nowrap, |
|||
3829 | 2 | $where_comparison, |
|||
3830 | 2 | $transform_options, |
|||
3831 | 2 | $is_field_truncated, |
|||
3832 | 2 | $original_length |
|||
3833 | ); |
||||
3834 | } |
||||
3835 | |||||
3836 | /** |
||||
3837 | * Checks the posted options for viewing query results |
||||
3838 | * and sets appropriate values in the session. |
||||
3839 | * |
||||
3840 | * @return void |
||||
3841 | * |
||||
3842 | * @todo make maximum remembered queries configurable |
||||
3843 | * @todo move/split into SQL class!? |
||||
3844 | * @todo currently this is called twice unnecessary |
||||
3845 | * @todo ignore LIMIT and ORDER in query!? |
||||
3846 | * @access public |
||||
3847 | */ |
||||
3848 | public function setConfigParamsForDisplayTable() |
||||
3849 | { |
||||
3850 | $sql_md5 = md5($this->__get('sql_query')); |
||||
3851 | $query = []; |
||||
3852 | if (isset($_SESSION['tmpval']['query'][$sql_md5])) { |
||||
3853 | $query = $_SESSION['tmpval']['query'][$sql_md5]; |
||||
3854 | } |
||||
3855 | |||||
3856 | $query['sql'] = $this->__get('sql_query'); |
||||
3857 | |||||
3858 | if (empty($query['repeat_cells'])) { |
||||
3859 | $query['repeat_cells'] = $GLOBALS['cfg']['RepeatCells']; |
||||
3860 | } |
||||
3861 | |||||
3862 | // as this is a form value, the type is always string so we cannot |
||||
3863 | // use Core::isValid($_POST['session_max_rows'], 'integer') |
||||
3864 | if (Core::isValid($_POST['session_max_rows'], 'numeric')) { |
||||
3865 | $query['max_rows'] = (int) $_POST['session_max_rows']; |
||||
3866 | unset($_POST['session_max_rows']); |
||||
3867 | } elseif ($_POST['session_max_rows'] == self::ALL_ROWS) { |
||||
3868 | $query['max_rows'] = self::ALL_ROWS; |
||||
3869 | unset($_POST['session_max_rows']); |
||||
3870 | } elseif (empty($query['max_rows'])) { |
||||
3871 | $query['max_rows'] = intval($GLOBALS['cfg']['MaxRows']); |
||||
3872 | } |
||||
3873 | |||||
3874 | if (Core::isValid($_REQUEST['pos'], 'numeric')) { |
||||
3875 | $query['pos'] = $_REQUEST['pos']; |
||||
3876 | unset($_REQUEST['pos']); |
||||
3877 | } elseif (empty($query['pos'])) { |
||||
3878 | $query['pos'] = 0; |
||||
3879 | } |
||||
3880 | |||||
3881 | if (Core::isValid( |
||||
3882 | $_REQUEST['pftext'], |
||||
3883 | [ |
||||
3884 | self::DISPLAY_PARTIAL_TEXT, |
||||
3885 | self::DISPLAY_FULL_TEXT, |
||||
3886 | ] |
||||
3887 | ) |
||||
3888 | ) { |
||||
3889 | $query['pftext'] = $_REQUEST['pftext']; |
||||
3890 | unset($_REQUEST['pftext']); |
||||
3891 | } elseif (empty($query['pftext'])) { |
||||
3892 | $query['pftext'] = self::DISPLAY_PARTIAL_TEXT; |
||||
3893 | } |
||||
3894 | |||||
3895 | if (Core::isValid( |
||||
3896 | $_REQUEST['relational_display'], |
||||
3897 | [ |
||||
3898 | self::RELATIONAL_KEY, |
||||
3899 | self::RELATIONAL_DISPLAY_COLUMN, |
||||
3900 | ] |
||||
3901 | ) |
||||
3902 | ) { |
||||
3903 | $query['relational_display'] = $_REQUEST['relational_display']; |
||||
3904 | unset($_REQUEST['relational_display']); |
||||
3905 | } elseif (empty($query['relational_display'])) { |
||||
3906 | // The current session value has priority over a |
||||
3907 | // change via Settings; this change will be apparent |
||||
3908 | // starting from the next session |
||||
3909 | $query['relational_display'] = $GLOBALS['cfg']['RelationalDisplay']; |
||||
3910 | } |
||||
3911 | |||||
3912 | if (Core::isValid( |
||||
3913 | $_REQUEST['geoOption'], |
||||
3914 | [ |
||||
3915 | self::GEOMETRY_DISP_WKT, |
||||
3916 | self::GEOMETRY_DISP_WKB, |
||||
3917 | self::GEOMETRY_DISP_GEOM, |
||||
3918 | ] |
||||
3919 | ) |
||||
3920 | ) { |
||||
3921 | $query['geoOption'] = $_REQUEST['geoOption']; |
||||
3922 | unset($_REQUEST['geoOption']); |
||||
3923 | } elseif (empty($query['geoOption'])) { |
||||
3924 | $query['geoOption'] = self::GEOMETRY_DISP_GEOM; |
||||
3925 | } |
||||
3926 | |||||
3927 | if (isset($_REQUEST['display_binary'])) { |
||||
3928 | $query['display_binary'] = true; |
||||
3929 | unset($_REQUEST['display_binary']); |
||||
3930 | } elseif (isset($_REQUEST['display_options_form'])) { |
||||
3931 | // we know that the checkbox was unchecked |
||||
3932 | unset($query['display_binary']); |
||||
3933 | } elseif (! isset($_REQUEST['full_text_button'])) { |
||||
3934 | // selected by default because some operations like OPTIMIZE TABLE |
||||
3935 | // and all queries involving functions return "binary" contents, |
||||
3936 | // according to low-level field flags |
||||
3937 | $query['display_binary'] = true; |
||||
3938 | } |
||||
3939 | |||||
3940 | if (isset($_REQUEST['display_blob'])) { |
||||
3941 | $query['display_blob'] = true; |
||||
3942 | unset($_REQUEST['display_blob']); |
||||
3943 | } elseif (isset($_REQUEST['display_options_form'])) { |
||||
3944 | // we know that the checkbox was unchecked |
||||
3945 | unset($query['display_blob']); |
||||
3946 | } |
||||
3947 | |||||
3948 | if (isset($_REQUEST['hide_transformation'])) { |
||||
3949 | $query['hide_transformation'] = true; |
||||
3950 | unset($_REQUEST['hide_transformation']); |
||||
3951 | } elseif (isset($_REQUEST['display_options_form'])) { |
||||
3952 | // we know that the checkbox was unchecked |
||||
3953 | unset($query['hide_transformation']); |
||||
3954 | } |
||||
3955 | |||||
3956 | // move current query to the last position, to be removed last |
||||
3957 | // so only least executed query will be removed if maximum remembered |
||||
3958 | // queries limit is reached |
||||
3959 | unset($_SESSION['tmpval']['query'][$sql_md5]); |
||||
3960 | $_SESSION['tmpval']['query'][$sql_md5] = $query; |
||||
3961 | |||||
3962 | // do not exceed a maximum number of queries to remember |
||||
3963 | if (count($_SESSION['tmpval']['query']) > 10) { |
||||
3964 | array_shift($_SESSION['tmpval']['query']); |
||||
3965 | //echo 'deleting one element ...'; |
||||
3966 | } |
||||
3967 | |||||
3968 | // populate query configuration |
||||
3969 | $_SESSION['tmpval']['pftext'] |
||||
3970 | = $query['pftext']; |
||||
3971 | $_SESSION['tmpval']['relational_display'] |
||||
3972 | = $query['relational_display']; |
||||
3973 | $_SESSION['tmpval']['geoOption'] |
||||
3974 | = $query['geoOption']; |
||||
3975 | $_SESSION['tmpval']['display_binary'] = isset( |
||||
3976 | $query['display_binary'] |
||||
3977 | ); |
||||
3978 | $_SESSION['tmpval']['display_blob'] = isset( |
||||
3979 | $query['display_blob'] |
||||
3980 | ); |
||||
3981 | $_SESSION['tmpval']['hide_transformation'] = isset( |
||||
3982 | $query['hide_transformation'] |
||||
3983 | ); |
||||
3984 | $_SESSION['tmpval']['pos'] |
||||
3985 | = $query['pos']; |
||||
3986 | $_SESSION['tmpval']['max_rows'] |
||||
3987 | = $query['max_rows']; |
||||
3988 | $_SESSION['tmpval']['repeat_cells'] |
||||
3989 | = $query['repeat_cells']; |
||||
3990 | } |
||||
3991 | |||||
3992 | /** |
||||
3993 | * Prepare a table of results returned by a SQL query. |
||||
3994 | * |
||||
3995 | * @param int $dt_result the link id associated to the query |
||||
3996 | * which results have to be displayed |
||||
3997 | * @param array $displayParts the parts to display |
||||
3998 | * @param array $analyzed_sql_results analyzed sql results |
||||
3999 | * @param bool $is_limited_display With limited operations or not |
||||
4000 | * |
||||
4001 | * @return string Generated HTML content for resulted table |
||||
4002 | * |
||||
4003 | * @access public |
||||
4004 | */ |
||||
4005 | public function getTable( |
||||
4006 | &$dt_result, |
||||
4007 | array &$displayParts, |
||||
4008 | array $analyzed_sql_results, |
||||
4009 | $is_limited_display = false |
||||
4010 | ) { |
||||
4011 | // The statement this table is built for. |
||||
4012 | if (isset($analyzed_sql_results['statement'])) { |
||||
4013 | /** @var SelectStatement $statement */ |
||||
4014 | $statement = $analyzed_sql_results['statement']; |
||||
4015 | } else { |
||||
4016 | $statement = null; |
||||
4017 | } |
||||
4018 | |||||
4019 | // Following variable are needed for use in isset/empty or |
||||
4020 | // use with array indexes/safe use in foreach |
||||
4021 | $fields_meta = $this->__get('fields_meta'); |
||||
4022 | $showtable = $this->__get('showtable'); |
||||
4023 | $printview = $this->__get('printview'); |
||||
4024 | |||||
4025 | /** |
||||
4026 | * @todo move this to a central place |
||||
4027 | * @todo for other future table types |
||||
4028 | */ |
||||
4029 | $is_innodb = (isset($showtable['Type']) |
||||
4030 | && $showtable['Type'] == self::TABLE_TYPE_INNO_DB); |
||||
4031 | |||||
4032 | $sql = new Sql(); |
||||
4033 | if ($is_innodb && $sql->isJustBrowsing($analyzed_sql_results, true)) { |
||||
4034 | $pre_count = '~'; |
||||
4035 | $after_count = Generator::showHint( |
||||
4036 | Sanitize::sanitizeMessage( |
||||
4037 | __('May be approximate. See [doc@faq3-11]FAQ 3.11[/doc].') |
||||
4038 | ) |
||||
4039 | ); |
||||
4040 | } else { |
||||
4041 | $pre_count = ''; |
||||
4042 | $after_count = ''; |
||||
4043 | } |
||||
4044 | |||||
4045 | // 1. ----- Prepares the work ----- |
||||
4046 | |||||
4047 | // 1.1 Gets the information about which functionalities should be |
||||
4048 | // displayed |
||||
4049 | |||||
4050 | [ |
||||
4051 | $displayParts, |
||||
4052 | $total, |
||||
4053 | ] = $this->setDisplayPartsAndTotal($displayParts); |
||||
4054 | |||||
4055 | // 1.2 Defines offsets for the next and previous pages |
||||
4056 | $pos_next = 0; |
||||
4057 | $pos_prev = 0; |
||||
4058 | if ($displayParts['nav_bar'] == '1') { |
||||
4059 | [$pos_next, $pos_prev] = $this->getOffsets(); |
||||
4060 | } // end if |
||||
4061 | |||||
4062 | // 1.3 Extract sorting expressions. |
||||
4063 | // we need $sort_expression and $sort_expression_nodirection |
||||
4064 | // even if there are many table references |
||||
4065 | $sort_expression = []; |
||||
4066 | $sort_expression_nodirection = []; |
||||
4067 | $sort_direction = []; |
||||
4068 | |||||
4069 | if ($statement !== null && ! empty($statement->order)) { |
||||
4070 | foreach ($statement->order as $o) { |
||||
4071 | $sort_expression[] = $o->expr->expr . ' ' . $o->type; |
||||
4072 | $sort_expression_nodirection[] = $o->expr->expr; |
||||
4073 | $sort_direction[] = $o->type; |
||||
4074 | } |
||||
4075 | } else { |
||||
4076 | $sort_expression[] = ''; |
||||
4077 | $sort_expression_nodirection[] = ''; |
||||
4078 | $sort_direction[] = ''; |
||||
4079 | } |
||||
4080 | |||||
4081 | $number_of_columns = count($sort_expression_nodirection); |
||||
4082 | |||||
4083 | // 1.4 Prepares display of first and last value of the sorted column |
||||
4084 | $sorted_column_message = ''; |
||||
4085 | for ($i = 0; $i < $number_of_columns; $i++) { |
||||
4086 | $sorted_column_message .= $this->getSortedColumnMessage( |
||||
4087 | $dt_result, |
||||
4088 | $sort_expression_nodirection[$i] |
||||
4089 | ); |
||||
4090 | } |
||||
4091 | |||||
4092 | // 2. ----- Prepare to display the top of the page ----- |
||||
4093 | |||||
4094 | // 2.1 Prepares a messages with position information |
||||
4095 | $sqlQueryMessage = ''; |
||||
4096 | if (($displayParts['nav_bar'] == '1') && $pos_next !== null) { |
||||
4097 | $message = $this->setMessageInformation( |
||||
4098 | $sorted_column_message, |
||||
4099 | $analyzed_sql_results, |
||||
4100 | $total, |
||||
4101 | $pos_next, |
||||
4102 | $pre_count, |
||||
4103 | $after_count |
||||
4104 | ); |
||||
4105 | |||||
4106 | $sqlQueryMessage = Generator::getMessage( |
||||
4107 | $message, |
||||
4108 | $this->__get('sql_query'), |
||||
4109 | 'success' |
||||
4110 | ); |
||||
4111 | } elseif ((! isset($printview) || ($printview != '1')) && ! $is_limited_display) { |
||||
4112 | $sqlQueryMessage = Generator::getMessage( |
||||
4113 | __('Your SQL query has been executed successfully.'), |
||||
4114 | $this->__get('sql_query'), |
||||
4115 | 'success' |
||||
4116 | ); |
||||
4117 | } |
||||
4118 | |||||
4119 | // 2.3 Prepare the navigation bars |
||||
4120 | if (strlen($this->__get('table')) === 0) { |
||||
4121 | if ($analyzed_sql_results['querytype'] == 'SELECT') { |
||||
4122 | // table does not always contain a real table name, |
||||
4123 | // for example in MySQL 5.0.x, the query SHOW STATUS |
||||
4124 | // returns STATUS as a table name |
||||
4125 | $this->__set('table', $fields_meta[0]->table); |
||||
4126 | } else { |
||||
4127 | $this->__set('table', ''); |
||||
4128 | } |
||||
4129 | } |
||||
4130 | |||||
4131 | // can the result be sorted? |
||||
4132 | if ($displayParts['sort_lnk'] == '1' && $analyzed_sql_results['statement'] !== null) { |
||||
4133 | // At this point, $sort_expression is an array |
||||
4134 | [$unsorted_sql_query, $sort_by_key_html] |
||||
4135 | = $this->getUnsortedSqlAndSortByKeyDropDown( |
||||
4136 | $analyzed_sql_results, |
||||
4137 | $sort_expression |
||||
4138 | ); |
||||
4139 | } else { |
||||
4140 | $sort_by_key_html = $unsorted_sql_query = ''; |
||||
4141 | } |
||||
4142 | |||||
4143 | $navigation = []; |
||||
4144 | if ($displayParts['nav_bar'] == '1' && $statement !== null && empty($statement->limit)) { |
||||
4145 | $navigation = $this->getTableNavigation( |
||||
4146 | $pos_next, |
||||
4147 | $pos_prev, |
||||
4148 | $is_innodb, |
||||
4149 | $sort_by_key_html |
||||
4150 | ); |
||||
4151 | } |
||||
4152 | |||||
4153 | // 2b ----- Get field references from Database ----- |
||||
4154 | // (see the 'relation' configuration variable) |
||||
4155 | |||||
4156 | // initialize map |
||||
4157 | $map = []; |
||||
4158 | |||||
4159 | if (strlen($this->__get('table')) > 0) { |
||||
4160 | // This method set the values for $map array |
||||
4161 | $this->setParamForLinkForeignKeyRelatedTables($map); |
||||
4162 | |||||
4163 | // Coming from 'Distinct values' action of structure page |
||||
4164 | // We manipulate relations mechanism to show a link to related rows. |
||||
4165 | if ($this->__get('is_browse_distinct')) { |
||||
4166 | $map[$fields_meta[1]->name] = [ |
||||
4167 | $this->__get('table'), |
||||
4168 | $fields_meta[1]->name, |
||||
4169 | '', |
||||
4170 | $this->__get('db'), |
||||
4171 | ]; |
||||
4172 | } |
||||
4173 | } // end if |
||||
4174 | // end 2b |
||||
4175 | |||||
4176 | // 3. ----- Prepare the results table ----- |
||||
4177 | $headers = $this->getTableHeaders( |
||||
4178 | $displayParts, |
||||
4179 | $analyzed_sql_results, |
||||
4180 | $unsorted_sql_query, |
||||
4181 | $sort_expression, |
||||
4182 | $sort_expression_nodirection, |
||||
4183 | $sort_direction, |
||||
4184 | $is_limited_display |
||||
4185 | ); |
||||
4186 | |||||
4187 | $body = $this->getTableBody( |
||||
4188 | $dt_result, |
||||
4189 | $displayParts, |
||||
4190 | $map, |
||||
4191 | $analyzed_sql_results, |
||||
4192 | $is_limited_display |
||||
4193 | ); |
||||
4194 | |||||
4195 | $this->__set('display_params', null); |
||||
4196 | |||||
4197 | // 4. ----- Prepares the link for multi-fields edit and delete |
||||
4198 | $bulkLinks = $this->getBulkLinks( |
||||
4199 | $dt_result, |
||||
4200 | $analyzed_sql_results, |
||||
4201 | $displayParts['del_lnk'] |
||||
4202 | ); |
||||
4203 | |||||
4204 | // 5. ----- Prepare "Query results operations" |
||||
4205 | $operations = []; |
||||
4206 | if ((! isset($printview) || ($printview != '1')) && ! $is_limited_display) { |
||||
4207 | $operations = $this->getResultsOperations( |
||||
4208 | $displayParts, |
||||
4209 | $analyzed_sql_results |
||||
4210 | ); |
||||
4211 | } |
||||
4212 | |||||
4213 | return $this->template->render('display/results/table', [ |
||||
4214 | 'sql_query_message' => $sqlQueryMessage, |
||||
4215 | 'navigation' => $navigation, |
||||
4216 | 'headers' => $headers, |
||||
4217 | 'body' => $body, |
||||
4218 | 'bulk_links' => $bulkLinks, |
||||
4219 | 'operations' => $operations, |
||||
4220 | 'db' => $this->__get('db'), |
||||
4221 | 'table' => $this->__get('table'), |
||||
4222 | 'unique_id' => $this->__get('unique_id'), |
||||
4223 | 'sql_query' => $this->__get('sql_query'), |
||||
4224 | 'url_query' => $this->__get('url_query'), |
||||
4225 | 'goto' => $this->__get('goto'), |
||||
4226 | 'unlim_num_rows' => $this->__get('unlim_num_rows'), |
||||
4227 | 'displaywork' => $GLOBALS['cfgRelation']['displaywork'], |
||||
4228 | 'relwork' => $GLOBALS['cfgRelation']['relwork'], |
||||
4229 | 'save_cells_at_once' => $GLOBALS['cfg']['SaveCellsAtOnce'], |
||||
4230 | 'default_sliders_state' => $GLOBALS['cfg']['InitialSlidersState'], |
||||
4231 | 'select_all_arrow' => $this->__get('pma_theme_image') . 'arrow_' |
||||
4232 | . $this->__get('text_dir') . '.png', |
||||
4233 | ]); |
||||
4234 | } |
||||
4235 | |||||
4236 | /** |
||||
4237 | * Get offsets for next page and previous page |
||||
4238 | * |
||||
4239 | * @see getTable() |
||||
4240 | * |
||||
4241 | * @return int[] array with two elements - $pos_next, $pos_prev |
||||
4242 | * |
||||
4243 | * @access private |
||||
4244 | */ |
||||
4245 | 8 | private function getOffsets() |
|||
4246 | { |
||||
4247 | 8 | if ($_SESSION['tmpval']['max_rows'] == self::ALL_ROWS) { |
|||
4248 | 4 | $pos_next = 0; |
|||
4249 | 4 | $pos_prev = 0; |
|||
4250 | } else { |
||||
4251 | 4 | $pos_next = $_SESSION['tmpval']['pos'] |
|||
4252 | 4 | + $_SESSION['tmpval']['max_rows']; |
|||
4253 | |||||
4254 | 4 | $pos_prev = $_SESSION['tmpval']['pos'] |
|||
4255 | 4 | - $_SESSION['tmpval']['max_rows']; |
|||
4256 | |||||
4257 | 4 | if ($pos_prev < 0) { |
|||
4258 | 4 | $pos_prev = 0; |
|||
4259 | } |
||||
4260 | } |
||||
4261 | |||||
4262 | return [ |
||||
4263 | 8 | $pos_next, |
|||
4264 | 8 | $pos_prev, |
|||
4265 | ]; |
||||
4266 | } |
||||
4267 | |||||
4268 | /** |
||||
4269 | * Prepare sorted column message |
||||
4270 | * |
||||
4271 | * @see getTable() |
||||
4272 | * |
||||
4273 | * @param int $dt_result the link id associated to the |
||||
4274 | * query which results have to |
||||
4275 | * be displayed |
||||
4276 | * @param string $sort_expression_nodirection sort expression without direction |
||||
4277 | * |
||||
4278 | * @return string|null html content, null if not found sorted column |
||||
4279 | * |
||||
4280 | * @access private |
||||
4281 | */ |
||||
4282 | private function getSortedColumnMessage( |
||||
4283 | &$dt_result, |
||||
4284 | $sort_expression_nodirection |
||||
4285 | ) { |
||||
4286 | $fields_meta = $this->__get('fields_meta'); // To use array indexes |
||||
4287 | |||||
4288 | if (empty($sort_expression_nodirection)) { |
||||
4289 | return null; |
||||
4290 | } |
||||
4291 | |||||
4292 | if (mb_strpos($sort_expression_nodirection, '.') === false) { |
||||
4293 | $sort_table = $this->__get('table'); |
||||
4294 | $sort_column = $sort_expression_nodirection; |
||||
4295 | } else { |
||||
4296 | [$sort_table, $sort_column] |
||||
4297 | = explode('.', $sort_expression_nodirection); |
||||
4298 | } |
||||
4299 | |||||
4300 | $sort_table = Util::unQuote($sort_table); |
||||
4301 | $sort_column = Util::unQuote($sort_column); |
||||
4302 | |||||
4303 | // find the sorted column index in row result |
||||
4304 | // (this might be a multi-table query) |
||||
4305 | $sorted_column_index = false; |
||||
4306 | |||||
4307 | foreach ($fields_meta as $key => $meta) { |
||||
4308 | if (($meta->table == $sort_table) && ($meta->name == $sort_column)) { |
||||
4309 | $sorted_column_index = $key; |
||||
4310 | break; |
||||
4311 | } |
||||
4312 | } |
||||
4313 | |||||
4314 | if ($sorted_column_index === false) { |
||||
4315 | return null; |
||||
4316 | } |
||||
4317 | |||||
4318 | // fetch first row of the result set |
||||
4319 | $row = $GLOBALS['dbi']->fetchRow($dt_result); |
||||
4320 | |||||
4321 | // initializing default arguments |
||||
4322 | $default_function = [ |
||||
4323 | Core::class, |
||||
4324 | 'mimeDefaultFunction', |
||||
4325 | ]; |
||||
4326 | $transformation_plugin = $default_function; |
||||
4327 | $transform_options = []; |
||||
4328 | |||||
4329 | // check for non printable sorted row data |
||||
4330 | $meta = $fields_meta[$sorted_column_index]; |
||||
4331 | |||||
4332 | if (stripos($meta->type, self::BLOB_FIELD) !== false |
||||
4333 | || ($meta->type == self::GEOMETRY_FIELD) |
||||
4334 | ) { |
||||
4335 | $column_for_first_row = $this->handleNonPrintableContents( |
||||
4336 | $meta->type, |
||||
4337 | $row[$sorted_column_index], |
||||
4338 | $transformation_plugin, |
||||
4339 | $transform_options, |
||||
4340 | $default_function, |
||||
4341 | $meta |
||||
4342 | ); |
||||
4343 | } else { |
||||
4344 | $column_for_first_row = $row !== null ? $row[$sorted_column_index] : ''; |
||||
4345 | } |
||||
4346 | |||||
4347 | $column_for_first_row = mb_strtoupper( |
||||
4348 | mb_substr( |
||||
4349 | (string) $column_for_first_row, |
||||
4350 | 0, |
||||
4351 | (int) $GLOBALS['cfg']['LimitChars'] |
||||
4352 | ) . '...' |
||||
4353 | ); |
||||
4354 | |||||
4355 | // fetch last row of the result set |
||||
4356 | $GLOBALS['dbi']->dataSeek($dt_result, $this->__get('num_rows') - 1); |
||||
4357 | $row = $GLOBALS['dbi']->fetchRow($dt_result); |
||||
4358 | |||||
4359 | // check for non printable sorted row data |
||||
4360 | $meta = $fields_meta[$sorted_column_index]; |
||||
4361 | if (stripos($meta->type, self::BLOB_FIELD) !== false |
||||
4362 | || ($meta->type == self::GEOMETRY_FIELD) |
||||
4363 | ) { |
||||
4364 | $column_for_last_row = $this->handleNonPrintableContents( |
||||
4365 | $meta->type, |
||||
4366 | $row[$sorted_column_index], |
||||
4367 | $transformation_plugin, |
||||
4368 | $transform_options, |
||||
4369 | $default_function, |
||||
4370 | $meta |
||||
4371 | ); |
||||
4372 | } else { |
||||
4373 | $column_for_last_row = $row !== null ? $row[$sorted_column_index] : ''; |
||||
4374 | } |
||||
4375 | |||||
4376 | $column_for_last_row = mb_strtoupper( |
||||
4377 | mb_substr( |
||||
4378 | (string) $column_for_last_row, |
||||
4379 | 0, |
||||
4380 | (int) $GLOBALS['cfg']['LimitChars'] |
||||
4381 | ) . '...' |
||||
4382 | ); |
||||
4383 | |||||
4384 | // reset to first row for the loop in getTableBody() |
||||
4385 | $GLOBALS['dbi']->dataSeek($dt_result, 0); |
||||
4386 | |||||
4387 | // we could also use here $sort_expression_nodirection |
||||
4388 | return ' [' . htmlspecialchars($sort_column) |
||||
4389 | . ': <strong>' . htmlspecialchars($column_for_first_row) . ' - ' |
||||
4390 | . htmlspecialchars($column_for_last_row) . '</strong>]'; |
||||
4391 | } |
||||
4392 | |||||
4393 | /** |
||||
4394 | * Set the content that needs to be shown in message |
||||
4395 | * |
||||
4396 | * @see getTable() |
||||
4397 | * |
||||
4398 | * @param string $sorted_column_message the message for sorted column |
||||
4399 | * @param array $analyzed_sql_results the analyzed query |
||||
4400 | * @param int $total the total number of rows returned by |
||||
4401 | * the SQL query without any |
||||
4402 | * programmatically appended LIMIT clause |
||||
4403 | * @param int $pos_next the offset for next page |
||||
4404 | * @param string $pre_count the string renders before row count |
||||
4405 | * @param string $after_count the string renders after row count |
||||
4406 | * |
||||
4407 | * @return Message an object of Message |
||||
4408 | * |
||||
4409 | * @access private |
||||
4410 | */ |
||||
4411 | private function setMessageInformation( |
||||
4412 | $sorted_column_message, |
||||
4413 | array $analyzed_sql_results, |
||||
4414 | $total, |
||||
4415 | $pos_next, |
||||
4416 | $pre_count, |
||||
4417 | $after_count |
||||
4418 | ) { |
||||
4419 | $unlim_num_rows = $this->__get('unlim_num_rows'); // To use in isset() |
||||
4420 | |||||
4421 | if (! empty($analyzed_sql_results['statement']->limit)) { |
||||
4422 | $first_shown_rec = $analyzed_sql_results['statement']->limit->offset; |
||||
4423 | $row_count = $analyzed_sql_results['statement']->limit->rowCount; |
||||
4424 | |||||
4425 | if ($row_count < $total) { |
||||
4426 | $last_shown_rec = $first_shown_rec + $row_count - 1; |
||||
4427 | } else { |
||||
4428 | $last_shown_rec = $first_shown_rec + $total - 1; |
||||
4429 | } |
||||
4430 | } elseif (($_SESSION['tmpval']['max_rows'] == self::ALL_ROWS) |
||||
4431 | || ($pos_next > $total) |
||||
4432 | ) { |
||||
4433 | $first_shown_rec = $_SESSION['tmpval']['pos']; |
||||
4434 | $last_shown_rec = $total - 1; |
||||
4435 | } else { |
||||
4436 | $first_shown_rec = $_SESSION['tmpval']['pos']; |
||||
4437 | $last_shown_rec = $pos_next - 1; |
||||
4438 | } |
||||
4439 | |||||
4440 | $table = new Table($this->__get('table'), $this->__get('db')); |
||||
4441 | if ($table->isView() |
||||
4442 | && ($total == $GLOBALS['cfg']['MaxExactCountViews']) |
||||
4443 | ) { |
||||
4444 | $message = Message::notice( |
||||
4445 | __( |
||||
4446 | 'This view has at least this number of rows. ' |
||||
4447 | . 'Please refer to %sdocumentation%s.' |
||||
4448 | ) |
||||
4449 | ); |
||||
4450 | |||||
4451 | $message->addParam('[doc@cfg_MaxExactCount]'); |
||||
4452 | $message->addParam('[/doc]'); |
||||
4453 | $message_view_warning = Generator::showHint($message); |
||||
4454 | } else { |
||||
4455 | $message_view_warning = false; |
||||
4456 | } |
||||
4457 | |||||
4458 | $message = Message::success(__('Showing rows %1s - %2s')); |
||||
4459 | $message->addParam($first_shown_rec); |
||||
4460 | |||||
4461 | if ($message_view_warning !== false) { |
||||
4462 | $message->addParamHtml('... ' . $message_view_warning); |
||||
4463 | } else { |
||||
4464 | $message->addParam($last_shown_rec); |
||||
4465 | } |
||||
4466 | |||||
4467 | $message->addText('('); |
||||
4468 | |||||
4469 | if ($message_view_warning === false) { |
||||
4470 | if (isset($unlim_num_rows) && ($unlim_num_rows != $total)) { |
||||
4471 | $message_total = Message::notice( |
||||
4472 | $pre_count . __('%1$d total, %2$d in query') |
||||
4473 | ); |
||||
4474 | $message_total->addParam($total); |
||||
4475 | $message_total->addParam($unlim_num_rows); |
||||
4476 | } else { |
||||
4477 | $message_total = Message::notice($pre_count . __('%d total')); |
||||
4478 | $message_total->addParam($total); |
||||
4479 | } |
||||
4480 | |||||
4481 | if (! empty($after_count)) { |
||||
4482 | $message_total->addHtml($after_count); |
||||
4483 | } |
||||
4484 | $message->addMessage($message_total, ''); |
||||
4485 | |||||
4486 | $message->addText(', ', ''); |
||||
4487 | } |
||||
4488 | |||||
4489 | $message_qt = Message::notice(__('Query took %01.4f seconds.') . ')'); |
||||
4490 | $message_qt->addParam($this->__get('querytime')); |
||||
4491 | |||||
4492 | $message->addMessage($message_qt, ''); |
||||
4493 | if ($sorted_column_message !== null) { |
||||
4494 | $message->addHtml($sorted_column_message, ''); |
||||
4495 | } |
||||
4496 | |||||
4497 | return $message; |
||||
4498 | } |
||||
4499 | |||||
4500 | /** |
||||
4501 | * Set the value of $map array for linking foreign key related tables |
||||
4502 | * |
||||
4503 | * @see getTable() |
||||
4504 | * |
||||
4505 | * @param array $map the list of relations |
||||
4506 | * |
||||
4507 | * @return void |
||||
4508 | * |
||||
4509 | * @access private |
||||
4510 | */ |
||||
4511 | private function setParamForLinkForeignKeyRelatedTables(array &$map) |
||||
4512 | { |
||||
4513 | // To be able to later display a link to the related table, |
||||
4514 | // we verify both types of relations: either those that are |
||||
4515 | // native foreign keys or those defined in the phpMyAdmin |
||||
4516 | // configuration storage. If no PMA storage, we won't be able |
||||
4517 | // to use the "column to display" notion (for example show |
||||
4518 | // the name related to a numeric id). |
||||
4519 | $exist_rel = $this->relation->getForeigners( |
||||
4520 | $this->__get('db'), |
||||
4521 | $this->__get('table'), |
||||
4522 | '', |
||||
4523 | self::POSITION_BOTH |
||||
4524 | ); |
||||
4525 | |||||
4526 | if (empty($exist_rel)) { |
||||
4527 | return; |
||||
4528 | } |
||||
4529 | |||||
4530 | foreach ($exist_rel as $master_field => $rel) { |
||||
4531 | if ($master_field != 'foreign_keys_data') { |
||||
4532 | $display_field = $this->relation->getDisplayField( |
||||
4533 | $rel['foreign_db'], |
||||
4534 | $rel['foreign_table'] |
||||
4535 | ); |
||||
4536 | $map[$master_field] = [ |
||||
4537 | $rel['foreign_table'], |
||||
4538 | $rel['foreign_field'], |
||||
4539 | $display_field, |
||||
4540 | $rel['foreign_db'], |
||||
4541 | ]; |
||||
4542 | } else { |
||||
4543 | foreach ($rel as $key => $one_key) { |
||||
4544 | foreach ($one_key['index_list'] as $index => $one_field) { |
||||
4545 | $display_field = $this->relation->getDisplayField( |
||||
4546 | $one_key['ref_db_name'] ?? $GLOBALS['db'], |
||||
4547 | $one_key['ref_table_name'] |
||||
4548 | ); |
||||
4549 | |||||
4550 | $map[$one_field] = [ |
||||
4551 | $one_key['ref_table_name'], |
||||
4552 | $one_key['ref_index_list'][$index], |
||||
4553 | $display_field, |
||||
4554 | $one_key['ref_db_name'] ?? $GLOBALS['db'], |
||||
4555 | ]; |
||||
4556 | } |
||||
4557 | } |
||||
4558 | } |
||||
4559 | } |
||||
4560 | } |
||||
4561 | |||||
4562 | /** |
||||
4563 | * Prepare multi field edit/delete links |
||||
4564 | * |
||||
4565 | * @see getTable() |
||||
4566 | * |
||||
4567 | * @param int $dt_result the link id associated to the query which |
||||
4568 | * results have to be displayed |
||||
4569 | * @param array $analyzed_sql_results analyzed sql results |
||||
4570 | * @param string $del_link the display element - 'del_link' |
||||
4571 | * |
||||
4572 | * @return array |
||||
4573 | */ |
||||
4574 | private function getBulkLinks( |
||||
4575 | &$dt_result, |
||||
4576 | array $analyzed_sql_results, |
||||
4577 | $del_link |
||||
4578 | ): array { |
||||
4579 | global $dbi; |
||||
4580 | |||||
4581 | if ($del_link !== self::DELETE_ROW) { |
||||
4582 | return []; |
||||
4583 | } |
||||
4584 | |||||
4585 | // fetch last row of the result set |
||||
4586 | $dbi->dataSeek($dt_result, $this->__get('num_rows') - 1); |
||||
4587 | $row = $dbi->fetchRow($dt_result); |
||||
4588 | |||||
4589 | // @see DbiMysqi::fetchRow & DatabaseInterface::fetchRow |
||||
4590 | if (! is_array($row)) { |
||||
4591 | $row = []; |
||||
4592 | } |
||||
4593 | |||||
4594 | $expressions = []; |
||||
4595 | |||||
4596 | if (isset($analyzed_sql_results['statement']) |
||||
4597 | && $analyzed_sql_results['statement'] instanceof SelectStatement |
||||
4598 | ) { |
||||
4599 | $expressions = $analyzed_sql_results['statement']->expr; |
||||
4600 | } |
||||
4601 | |||||
4602 | /** |
||||
4603 | * $clause_is_unique is needed by getTable() to generate the proper param |
||||
4604 | * in the multi-edit and multi-delete form |
||||
4605 | */ |
||||
4606 | [, $clause_is_unique] = Util::getUniqueCondition( |
||||
4607 | $dt_result, |
||||
4608 | $this->__get('fields_cnt'), |
||||
4609 | $this->__get('fields_meta'), |
||||
4610 | $row, |
||||
4611 | false, |
||||
4612 | false, |
||||
4613 | $expressions |
||||
4614 | ); |
||||
4615 | |||||
4616 | // reset to first row for the loop in getTableBody() |
||||
4617 | $dbi->dataSeek($dt_result, 0); |
||||
4618 | |||||
4619 | return [ |
||||
4620 | 'has_export_button' => $analyzed_sql_results['querytype'] === 'SELECT', |
||||
4621 | 'clause_is_unique' => $clause_is_unique, |
||||
4622 | ]; |
||||
4623 | } |
||||
4624 | |||||
4625 | /** |
||||
4626 | * @param array $analyzed_sql_results analyzed sql results |
||||
4627 | * |
||||
4628 | * @return string |
||||
4629 | * |
||||
4630 | * @access public |
||||
4631 | */ |
||||
4632 | public function getCreateViewQueryResultOp(array $analyzed_sql_results) |
||||
4633 | { |
||||
4634 | if (! empty($analyzed_sql_results['procedure'])) { |
||||
4635 | return ''; |
||||
4636 | } |
||||
4637 | |||||
4638 | $results_operations_html = '<fieldset class="print_ignore" ><legend>' |
||||
4639 | . __('Query results operations') . '</legend>'; |
||||
4640 | $results_operations_html .= '<span>'; |
||||
4641 | $results_operations_html .= Generator::linkOrButton( |
||||
4642 | Url::getFromRoute('/view/create', [ |
||||
4643 | 'db' => $this->__get('db'), |
||||
4644 | 'table' => $this->__get('table'), |
||||
4645 | 'printview' => '1', |
||||
4646 | 'sql_query' => $this->__get('sql_query'), |
||||
4647 | ]), |
||||
4648 | Generator::getIcon( |
||||
4649 | 'b_view_add', |
||||
4650 | __('Create view'), |
||||
4651 | true |
||||
4652 | ), |
||||
4653 | ['class' => 'create_view ajax btn'] |
||||
4654 | ); |
||||
4655 | $results_operations_html .= '</span>' . "\n"; |
||||
4656 | $results_operations_html .= '</fieldset><br>'; |
||||
4657 | |||||
4658 | return $results_operations_html; |
||||
4659 | } |
||||
4660 | |||||
4661 | /** |
||||
4662 | * Get operations that are available on results. |
||||
4663 | * |
||||
4664 | * @see getTable() |
||||
4665 | * |
||||
4666 | * @param array $displayParts the parts to display |
||||
4667 | * @param array $analyzed_sql_results analyzed sql results |
||||
4668 | * |
||||
4669 | * @return array<string, bool|array<string, string>> |
||||
4670 | */ |
||||
4671 | private function getResultsOperations( |
||||
4672 | array $displayParts, |
||||
4673 | array $analyzed_sql_results |
||||
4674 | ): array { |
||||
4675 | global $printview; |
||||
4676 | |||||
4677 | $_url_params = [ |
||||
4678 | 'db' => $this->__get('db'), |
||||
4679 | 'table' => $this->__get('table'), |
||||
4680 | 'printview' => '1', |
||||
4681 | 'sql_query' => $this->__get('sql_query'), |
||||
4682 | ]; |
||||
4683 | |||||
4684 | $geometry_found = false; |
||||
4685 | |||||
4686 | // Export link |
||||
4687 | // (the url_query has extra parameters that won't be used to export) |
||||
4688 | // (the single_table parameter is used in \PhpMyAdmin\Export->getDisplay() |
||||
4689 | // to hide the SQL and the structure export dialogs) |
||||
4690 | // If the parser found a PROCEDURE clause |
||||
4691 | // (most probably PROCEDURE ANALYSE()) it makes no sense to |
||||
4692 | // display the Export link). |
||||
4693 | if (($analyzed_sql_results['querytype'] == self::QUERY_TYPE_SELECT) |
||||
4694 | && ! isset($printview) |
||||
4695 | && empty($analyzed_sql_results['procedure']) |
||||
4696 | ) { |
||||
4697 | if (count($analyzed_sql_results['select_tables']) === 1) { |
||||
4698 | $_url_params['single_table'] = 'true'; |
||||
4699 | } |
||||
4700 | |||||
4701 | // In case this query doesn't involve any tables, |
||||
4702 | // implies only raw query is to be exported |
||||
4703 | if (! $analyzed_sql_results['select_tables']) { |
||||
4704 | $_url_params['raw_query'] = 'true'; |
||||
4705 | } |
||||
4706 | |||||
4707 | $_url_params['unlim_num_rows'] = $this->__get('unlim_num_rows'); |
||||
4708 | |||||
4709 | /** |
||||
4710 | * At this point we don't know the table name; this can happen |
||||
4711 | * for example with a query like |
||||
4712 | * SELECT bike_code FROM (SELECT bike_code FROM bikes) tmp |
||||
4713 | * As a workaround we set in the table parameter the name of the |
||||
4714 | * first table of this database, so that /table/export and |
||||
4715 | * the script it calls do not fail |
||||
4716 | */ |
||||
4717 | if (empty($_url_params['table']) && ! empty($_url_params['db'])) { |
||||
4718 | $_url_params['table'] = $GLOBALS['dbi']->fetchValue('SHOW TABLES'); |
||||
4719 | /* No result (probably no database selected) */ |
||||
4720 | if ($_url_params['table'] === false) { |
||||
4721 | unset($_url_params['table']); |
||||
4722 | } |
||||
4723 | } |
||||
4724 | |||||
4725 | $fields_meta = $this->__get('fields_meta'); |
||||
4726 | foreach ($fields_meta as $meta) { |
||||
4727 | if ($meta->type == self::GEOMETRY_FIELD) { |
||||
4728 | $geometry_found = true; |
||||
4729 | break; |
||||
4730 | } |
||||
4731 | } |
||||
4732 | } |
||||
4733 | |||||
4734 | return [ |
||||
4735 | 'has_procedure' => ! empty($analyzed_sql_results['procedure']), |
||||
4736 | 'has_geometry' => $geometry_found, |
||||
4737 | 'has_print_link' => $displayParts['pview_lnk'] == '1', |
||||
4738 | 'has_export_link' => $analyzed_sql_results['querytype'] === self::QUERY_TYPE_SELECT && ! isset($printview), |
||||
4739 | 'url_params' => $_url_params, |
||||
4740 | ]; |
||||
4741 | } |
||||
4742 | |||||
4743 | /** |
||||
4744 | * Verifies what to do with non-printable contents (binary or BLOB) |
||||
4745 | * in Browse mode. |
||||
4746 | * |
||||
4747 | * @see getDataCellForGeometryColumns(), getDataCellForNonNumericColumns(), getSortedColumnMessage() |
||||
4748 | * |
||||
4749 | * @param string $category BLOB|BINARY|GEOMETRY |
||||
4750 | * @param string|null $content the binary content |
||||
4751 | * @param mixed $transformation_plugin transformation plugin. |
||||
4752 | * Can also be the |
||||
4753 | * default function: |
||||
4754 | * Core::mimeDefaultFunction |
||||
4755 | * @param string $transform_options transformation parameters |
||||
4756 | * @param string $default_function default transformation function |
||||
4757 | * @param stdClass $meta the meta-information about the field |
||||
4758 | * @param array $url_params parameters that should go to the |
||||
4759 | * download link |
||||
4760 | * @param bool $is_truncated the result is truncated or not |
||||
4761 | * |
||||
4762 | * @return mixed string or float |
||||
4763 | * |
||||
4764 | * @access private |
||||
4765 | */ |
||||
4766 | 28 | private function handleNonPrintableContents( |
|||
4767 | $category, |
||||
4768 | ?string $content, |
||||
4769 | $transformation_plugin, |
||||
4770 | $transform_options, |
||||
4771 | $default_function, |
||||
4772 | $meta, |
||||
4773 | array $url_params = [], |
||||
4774 | &$is_truncated = null |
||||
4775 | ) { |
||||
4776 | 28 | $is_truncated = false; |
|||
4777 | 28 | $result = '[' . $category; |
|||
4778 | |||||
4779 | 28 | if ($content !== null) { |
|||
4780 | 24 | $size = strlen($content); |
|||
4781 | 24 | $display_size = Util::formatByteDown($size, 3, 1); |
|||
4782 | 24 | $result .= ' - ' . $display_size[0] . ' ' . $display_size[1]; |
|||
4783 | } else { |
||||
4784 | 4 | $result .= ' - NULL'; |
|||
4785 | 4 | $size = 0; |
|||
4786 | 4 | $content = ''; |
|||
4787 | } |
||||
4788 | |||||
4789 | 28 | $result .= ']'; |
|||
4790 | |||||
4791 | // if we want to use a text transformation on a BLOB column |
||||
4792 | 28 | if (is_object($transformation_plugin)) { |
|||
4793 | 8 | $posMimeOctetstream = strpos( |
|||
4794 | 8 | $transformation_plugin->getMIMESubtype(), |
|||
4795 | 8 | 'Octetstream' |
|||
4796 | ); |
||||
4797 | 8 | $posMimeText = strpos($transformation_plugin->getMIMEtype(), 'Text'); |
|||
4798 | 8 | if ($posMimeOctetstream |
|||
4799 | 8 | || $posMimeText !== false |
|||
4800 | ) { |
||||
4801 | // Applying Transformations on hex string of binary data |
||||
4802 | // seems more appropriate |
||||
4803 | 8 | $result = pack('H*', bin2hex($content)); |
|||
4804 | } |
||||
4805 | } |
||||
4806 | |||||
4807 | 28 | if ($size <= 0) { |
|||
4808 | 4 | return $result; |
|||
4809 | } |
||||
4810 | |||||
4811 | 24 | if ($default_function != $transformation_plugin) { |
|||
4812 | 8 | $result = $transformation_plugin->applyTransformation( |
|||
4813 | 8 | $result, |
|||
4814 | 4 | $transform_options, |
|||
4815 | 4 | $meta |
|||
4816 | ); |
||||
4817 | |||||
4818 | 8 | return $result; |
|||
4819 | } |
||||
4820 | |||||
4821 | 16 | $result = $default_function($result, [], $meta); |
|||
4822 | 16 | if (($_SESSION['tmpval']['display_binary'] |
|||
4823 | 16 | && $meta->type === self::STRING_FIELD) |
|||
4824 | 16 | || ($_SESSION['tmpval']['display_blob'] |
|||
4825 | 16 | && stripos($meta->type, self::BLOB_FIELD) !== false) |
|||
4826 | ) { |
||||
4827 | // in this case, restart from the original $content |
||||
4828 | 8 | if (mb_check_encoding($content, 'utf-8') |
|||
4829 | 8 | && ! preg_match('/[\x00-\x08\x0B\x0C\x0E-\x1F\x80-\x9F]/u', $content) |
|||
4830 | ) { |
||||
4831 | // show as text if it's valid utf-8 |
||||
4832 | 4 | $result = htmlspecialchars($content); |
|||
4833 | } else { |
||||
4834 | 4 | $result = '0x' . bin2hex($content); |
|||
4835 | } |
||||
4836 | [ |
||||
4837 | $is_truncated, |
||||
4838 | $result, |
||||
4839 | // skip 3rd param |
||||
4840 | 8 | ] = $this->getPartialText($result); |
|||
4841 | } |
||||
4842 | |||||
4843 | /* Create link to download */ |
||||
4844 | |||||
4845 | // in PHP < 5.5, empty() only checks variables |
||||
4846 | 16 | $tmpdb = $this->__get('db'); |
|||
4847 | 16 | if (count($url_params) > 0 |
|||
4848 | 16 | && (! empty($tmpdb) && ! empty($meta->orgtable)) |
|||
4849 | ) { |
||||
4850 | 16 | $url_params['where_clause_sign'] = Core::signSqlQuery($url_params['where_clause']); |
|||
4851 | $result = '<a href="' |
||||
4852 | 16 | . Url::getFromRoute('/table/get-field', $url_params) |
|||
4853 | 16 | . '" class="disableAjax">' |
|||
4854 | 16 | . $result . '</a>'; |
|||
4855 | } |
||||
4856 | |||||
4857 | 16 | return $result; |
|||
4858 | } |
||||
4859 | |||||
4860 | /** |
||||
4861 | * Retrieves the associated foreign key info for a data cell |
||||
4862 | * |
||||
4863 | * @param array $map the list of relations |
||||
4864 | * @param stdClass $meta the meta-information about the field |
||||
4865 | * @param string $where_comparison data for the where clause |
||||
4866 | * |
||||
4867 | * @return string|null formatted data |
||||
4868 | * |
||||
4869 | * @access private |
||||
4870 | */ |
||||
4871 | private function getFromForeign(array $map, $meta, $where_comparison) |
||||
4872 | { |
||||
4873 | $dispsql = 'SELECT ' |
||||
4874 | . Util::backquote($map[$meta->name][2]) |
||||
4875 | . ' FROM ' |
||||
4876 | . Util::backquote($map[$meta->name][3]) |
||||
4877 | . '.' |
||||
4878 | . Util::backquote($map[$meta->name][0]) |
||||
4879 | . ' WHERE ' |
||||
4880 | . Util::backquote($map[$meta->name][1]) |
||||
4881 | . $where_comparison; |
||||
4882 | |||||
4883 | $dispresult = $GLOBALS['dbi']->tryQuery( |
||||
4884 | $dispsql, |
||||
4885 | DatabaseInterface::CONNECT_USER, |
||||
4886 | DatabaseInterface::QUERY_STORE |
||||
4887 | ); |
||||
4888 | |||||
4889 | if ($dispresult && $GLOBALS['dbi']->numRows($dispresult) > 0) { |
||||
4890 | [$dispval] = $GLOBALS['dbi']->fetchRow($dispresult, 0); |
||||
4891 | } else { |
||||
4892 | $dispval = __('Link not found!'); |
||||
4893 | } |
||||
4894 | |||||
4895 | $GLOBALS['dbi']->freeResult($dispresult); |
||||
4896 | |||||
4897 | return $dispval; |
||||
4898 | } |
||||
4899 | |||||
4900 | /** |
||||
4901 | * Prepares the displayable content of a data cell in Browse mode, |
||||
4902 | * taking into account foreign key description field and transformations |
||||
4903 | * |
||||
4904 | * @see getDataCellForNumericColumns(), getDataCellForGeometryColumns(), |
||||
4905 | * getDataCellForNonNumericColumns(), |
||||
4906 | * |
||||
4907 | * @param string $class css classes for the td element |
||||
4908 | * @param bool $condition_field whether the column is a part of |
||||
4909 | * the where clause |
||||
4910 | * @param array $analyzed_sql_results the analyzed query |
||||
4911 | * @param stdClass $meta the meta-information about the |
||||
4912 | * field |
||||
4913 | * @param array $map the list of relations |
||||
4914 | * @param string $data data |
||||
4915 | * @param string $displayedData data that will be displayed (maybe be chunked) |
||||
4916 | * @param TransformationsPlugin $transformation_plugin transformation plugin. |
||||
4917 | * Can also be the default function: |
||||
4918 | * Core::mimeDefaultFunction |
||||
4919 | * @param string $default_function default function |
||||
4920 | * @param string $nowrap 'nowrap' if the content should |
||||
4921 | * not be wrapped |
||||
4922 | * @param string $where_comparison data for the where clause |
||||
4923 | * @param array $transform_options options for transformation |
||||
4924 | * @param bool $is_field_truncated whether the field is truncated |
||||
4925 | * @param string $original_length of a truncated column, or '' |
||||
4926 | * |
||||
4927 | * @return string formatted data |
||||
4928 | * |
||||
4929 | * @access private |
||||
4930 | */ |
||||
4931 | 8 | private function getRowData( |
|||
4932 | $class, |
||||
4933 | $condition_field, |
||||
4934 | array $analyzed_sql_results, |
||||
4935 | $meta, |
||||
4936 | array $map, |
||||
4937 | $data, |
||||
4938 | $displayedData, |
||||
4939 | $transformation_plugin, |
||||
4940 | $default_function, |
||||
4941 | $nowrap, |
||||
4942 | $where_comparison, |
||||
4943 | array $transform_options, |
||||
4944 | $is_field_truncated, |
||||
4945 | $original_length = '' |
||||
4946 | ) { |
||||
4947 | 8 | $relational_display = $_SESSION['tmpval']['relational_display']; |
|||
4948 | 8 | $printview = $this->__get('printview'); |
|||
4949 | 8 | $decimals = $meta->decimals ?? '-1'; |
|||
4950 | 8 | $result = '<td data-decimals="' . $decimals . '"' |
|||
4951 | 8 | . ' data-type="' . $meta->type . '"'; |
|||
4952 | |||||
4953 | 8 | if (! empty($original_length)) { |
|||
4954 | // cannot use data-original-length |
||||
4955 | 4 | $result .= ' data-originallength="' . $original_length . '"'; |
|||
4956 | } |
||||
4957 | |||||
4958 | $result .= ' class="' |
||||
4959 | 8 | . $this->addClass( |
|||
4960 | 8 | $class, |
|||
4961 | 8 | $condition_field, |
|||
4962 | 8 | $meta, |
|||
4963 | 8 | $nowrap, |
|||
4964 | 8 | $is_field_truncated, |
|||
4965 | 8 | $transformation_plugin, |
|||
4966 | 8 | $default_function |
|||
4967 | ) |
||||
4968 | 8 | . '">'; |
|||
4969 | |||||
4970 | 8 | if (! empty($analyzed_sql_results['statement']->expr)) { |
|||
4971 | 4 | foreach ($analyzed_sql_results['statement']->expr as $expr) { |
|||
4972 | 4 | if (empty($expr->alias) || empty($expr->column)) { |
|||
4973 | 4 | continue; |
|||
4974 | } |
||||
4975 | if (strcasecmp($meta->name, $expr->alias) != 0) { |
||||
4976 | continue; |
||||
4977 | } |
||||
4978 | |||||
4979 | $meta->name = $expr->column; |
||||
4980 | } |
||||
4981 | } |
||||
4982 | |||||
4983 | 8 | if (isset($map[$meta->name])) { |
|||
4984 | // Field to display from the foreign table? |
||||
4985 | if (isset($map[$meta->name][2]) |
||||
4986 | && strlen((string) $map[$meta->name][2]) > 0 |
||||
4987 | ) { |
||||
4988 | $dispval = $this->getFromForeign( |
||||
4989 | $map, |
||||
4990 | $meta, |
||||
4991 | $where_comparison |
||||
4992 | ); |
||||
4993 | } else { |
||||
4994 | $dispval = ''; |
||||
4995 | } // end if... else... |
||||
4996 | |||||
4997 | if (isset($printview) && ($printview == '1')) { |
||||
4998 | $result .= ($transformation_plugin != $default_function |
||||
4999 | ? $transformation_plugin->applyTransformation( |
||||
5000 | $data, |
||||
5001 | $transform_options, |
||||
5002 | $meta |
||||
5003 | ) |
||||
5004 | : $default_function($data) |
||||
5005 | ) |
||||
5006 | . ' <code>[->' . $dispval . ']</code>'; |
||||
5007 | } else { |
||||
5008 | if ($relational_display == self::RELATIONAL_KEY) { |
||||
5009 | // user chose "relational key" in the display options, so |
||||
5010 | // the title contains the display field |
||||
5011 | $title = ! empty($dispval) |
||||
5012 | ? htmlspecialchars($dispval) |
||||
5013 | : ''; |
||||
5014 | } else { |
||||
5015 | $title = htmlspecialchars($data); |
||||
5016 | } |
||||
5017 | |||||
5018 | $sqlQuery = 'SELECT * FROM ' |
||||
5019 | . Util::backquote($map[$meta->name][3]) . '.' |
||||
5020 | . Util::backquote($map[$meta->name][0]) |
||||
5021 | . ' WHERE ' |
||||
5022 | . Util::backquote($map[$meta->name][1]) |
||||
5023 | . $where_comparison; |
||||
5024 | |||||
5025 | $_url_params = [ |
||||
5026 | 'db' => $map[$meta->name][3], |
||||
5027 | 'table' => $map[$meta->name][0], |
||||
5028 | 'pos' => '0', |
||||
5029 | 'sql_signature' => Core::signSqlQuery($sqlQuery), |
||||
5030 | 'sql_query' => $sqlQuery, |
||||
5031 | ]; |
||||
5032 | |||||
5033 | if ($transformation_plugin != $default_function) { |
||||
5034 | // always apply a transformation on the real data, |
||||
5035 | // not on the display field |
||||
5036 | $displayedData = $transformation_plugin->applyTransformation( |
||||
5037 | $data, |
||||
5038 | $transform_options, |
||||
5039 | $meta |
||||
5040 | ); |
||||
5041 | } else { |
||||
5042 | if ($relational_display == self::RELATIONAL_DISPLAY_COLUMN |
||||
5043 | && ! empty($map[$meta->name][2]) |
||||
5044 | ) { |
||||
5045 | // user chose "relational display field" in the |
||||
5046 | // display options, so show display field in the cell |
||||
5047 | $displayedData = $dispval === null ? '<em>NULL</em>' : $default_function($dispval); |
||||
5048 | } else { |
||||
5049 | // otherwise display data in the cell |
||||
5050 | $displayedData = $default_function($displayedData); |
||||
5051 | } |
||||
5052 | } |
||||
5053 | |||||
5054 | $tag_params = ['title' => $title]; |
||||
5055 | if (strpos($class, 'grid_edit') !== false) { |
||||
5056 | $tag_params['class'] = 'ajax'; |
||||
5057 | } |
||||
5058 | $result .= Generator::linkOrButton( |
||||
5059 | Url::getFromRoute('/sql', $_url_params), |
||||
5060 | $displayedData, |
||||
5061 | $tag_params |
||||
5062 | ); |
||||
5063 | } |
||||
5064 | } else { |
||||
5065 | 8 | $result .= ($transformation_plugin != $default_function |
|||
5066 | 4 | ? $transformation_plugin->applyTransformation( |
|||
5067 | 4 | $data, |
|||
5068 | 2 | $transform_options, |
|||
5069 | 2 | $meta |
|||
5070 | ) |
||||
5071 | 7 | : $default_function($data) |
|||
5072 | ); |
||||
5073 | } |
||||
5074 | |||||
5075 | 8 | $result .= '</td>' . "\n"; |
|||
5076 | |||||
5077 | 8 | return $result; |
|||
5078 | } |
||||
5079 | |||||
5080 | /** |
||||
5081 | * Truncates given string based on LimitChars configuration |
||||
5082 | * and Session pftext variable |
||||
5083 | * (string is truncated only if necessary) |
||||
5084 | * |
||||
5085 | * @see handleNonPrintableContents(), getDataCellForGeometryColumns(), getDataCellForNonNumericColumns |
||||
5086 | * |
||||
5087 | * @param string $str string to be truncated |
||||
5088 | * |
||||
5089 | * @return array |
||||
5090 | * |
||||
5091 | * @access private |
||||
5092 | */ |
||||
5093 | 28 | private function getPartialText($str): array |
|||
5094 | { |
||||
5095 | 28 | $original_length = mb_strlen($str); |
|||
5096 | 28 | if ($original_length > $GLOBALS['cfg']['LimitChars'] |
|||
5097 | 28 | && $_SESSION['tmpval']['pftext'] === self::DISPLAY_PARTIAL_TEXT |
|||
5098 | ) { |
||||
5099 | 4 | $str = mb_substr( |
|||
5100 | 4 | $str, |
|||
5101 | 4 | 0, |
|||
5102 | 4 | (int) $GLOBALS['cfg']['LimitChars'] |
|||
5103 | 4 | ) . '...'; |
|||
5104 | 4 | $truncated = true; |
|||
5105 | } else { |
||||
5106 | 24 | $truncated = false; |
|||
5107 | } |
||||
5108 | |||||
5109 | return [ |
||||
5110 | 28 | $truncated, |
|||
5111 | 28 | $str, |
|||
5112 | 28 | $original_length, |
|||
5113 | ]; |
||||
5114 | } |
||||
5115 | } |
||||
5116 |