Passed
Push — master ( 4bc08b...df48b1 )
by Maurício
08:59
created

libraries/classes/Display/Results.php (1 issue)

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