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