PluginProbe
WP Data Access – App Builder for Tables, Forms, Charts, Maps & Dashboards / 5.5.32
WP Data Access – App Builder for Tables, Forms, Charts, Maps & Dashboards v5.5.32
5.5.84 5.5.83 5.5.82 5.5.81 5.5.80 5.5.79 5.5.77 5.5.76 5.5.75 5.5.73 5.5.72 5.5.22 5.5.23 5.5.29 5.5.3 5.5.31 5.5.32 5.5.34 5.5.35 5.5.36 5.5.37 5.5.4 5.5.40 5.5.41 5.5.42 All 160 releases
wp-data-access / WPDataAccess / List_Table / WPDA_List_Table.php

WPDA_List_Table.php in WP Data Access – App Builder for Tables, Forms, Charts, Maps & Dashboards 5.5.32, at WPDataAccess/List_Table/WPDA_List_Table.php

2,786 lines 110.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Suppress "error - 0 - No summary was found for this file" on phpdoc generation
5 *
6 * @package WPDataAccess\List_Table
7 */
8 namespace WPDataAccess\List_Table;
9
10 use WPDataAccess\Connection\WPDADB;
11 use WPDataAccess\Dashboard\WPDA_Dashboard;
12 use WPDataAccess\Data_Dictionary\WPDA_Dictionary_Exist;
13 use WPDataAccess\Data_Dictionary\WPDA_Dictionary_Lists;
14 use WPDataAccess\Data_Dictionary\WPDA_List_Columns;
15 use WPDataAccess\Macro\WPDA_Macro;
16 use WPDataAccess\Plugin_Table_Models\WPDA_CSV_Uploads_Model;
17 use WPDataAccess\Plugin_Table_Models\WPDA_Media_Model;
18 use WPDataAccess\Utilities\WPDA_Import;
19 use WPDataAccess\Utilities\WPDA_Message_Box;
20 use WPDataAccess\Utilities\WPDA_Repository;
21 use WPDataAccess\Wordpress_Original;
22 use WPDataAccess\WPDA;
23 use WPDataProjects\WPDP;
24 use WPDataAccess\Plugin_Table_Models\WPDA_Table_Settings_Model;
25 use WPDataAccess\Plugin_Table_Models\WPDP_Project_Design_Table_Model;
26 /**
27 * Class WPDA_List_Table
28 *
29 * This class extends WordPress class WP_List_Table. The WordPress WP_List_Table is contained in the package as
30 * advised by WordPress and might be updated in future releases.
31 *
32 * @author Peter Schulz
33 * @since 1.0.0
34 */
35 class WPDA_List_Table extends Wordpress_Original\WP_List_Table {
36 /**
37 * Base table for list of all tables
38 */
39 const LIST_BASE_TABLE = 'information_schema.tables';
40
41 /**
42 * Default value bulk actions enabled
43 */
44 const DEFAULT_BULK_ACTIONS_ENABLED = true;
45
46 /**
47 * Default value bulk export enabled
48 */
49 const DEFAULT_BULK_EXPORT_ENABLED = true;
50
51 /**
52 * Default value bulk search enabled
53 */
54 const DEFAULT_SEARCH_BOX_ENABLED = true;
55
56 /**
57 * Table row number within the current response
58 *
59 * @var int
60 */
61 protected static $list_number = 0;
62
63 /**
64 * Menu slug of the current page
65 *
66 * @var string
67 */
68 protected $page;
69
70 /**
71 * Page title
72 *
73 * @var string
74 */
75 protected $title;
76
77 /**
78 * Page subtitle
79 *
80 * @var string
81 */
82 protected $subtitle = '';
83
84 protected $show_page_title = true;
85
86 /**
87 * Database schema name
88 *
89 * @var string
90 */
91 protected $schema_name = '';
92
93 /**
94 * Database table name
95 *
96 * @var string
97 */
98 protected $table_name = '';
99
100 /**
101 * Where clause
102 *
103 * @var string
104 */
105 protected $where = '';
106
107 /**
108 * Order by clause
109 *
110 * @var string
111 */
112 protected $orderby = '';
113
114 /**
115 * Columns in query
116 *
117 * @var array
118 */
119 protected $columns_queried;
120
121 /**
122 * Number of rows displayed in list box
123 *
124 * @var int
125 */
126 protected $items_per_page;
127
128 /**
129 * Page number
130 *
131 * @var int
132 */
133 protected $current_page = 1;
134
135 /**
136 * Add search box?
137 *
138 * @var bool
139 */
140 protected $search_box_enabled = self::DEFAULT_SEARCH_BOX_ENABLED;
141
142 /**
143 * Enable bulk actions?
144 *
145 * @var bool
146 */
147 protected $bulk_actions_enabled = self::DEFAULT_BULK_ACTIONS_ENABLED;
148
149 /**
150 * Enable exports?
151 *
152 * @var bool
153 */
154 protected $bulk_export_enabled = self::DEFAULT_BULK_EXPORT_ENABLED;
155
156 /**
157 * Show view link?
158 *
159 * @var string on|off
160 */
161 protected $show_view_link = null;
162
163 /**
164 * Allow inserts?
165 *
166 * @var string on|off
167 */
168 protected $allow_insert = null;
169
170 /**
171 * Allow updates?
172 *
173 * @var string on|off
174 */
175 protected $allow_update = null;
176
177 /**
178 * Allow deletes?
179 *
180 * @var string on|off
181 */
182 protected $allow_delete = null;
183
184 /**
185 * Hides tablenav
186 *
187 * @var bool
188 */
189 protected $hide_navigation = false;
190
191 /**
192 * Reference to column list
193 *
194 * @var WPDA_List_Columns
195 */
196 protected $wpda_list_columns;
197
198 /**
199 * Reference to dictionary object
200 *
201 * @var WPDA_Dictionary_Exist
202 */
203 protected $wpda_data_dictionary;
204
205 /**
206 * Column headers (labels)
207 *
208 * @var array
209 */
210 protected $column_headers;
211
212 /**
213 * Reference to import object
214 *
215 * @var WPDA_Import
216 */
217 protected $wpda_import = null;
218
219 /**
220 * Child tab clicked (used for parent child relationships only)
221 *
222 * @var null
223 */
224 protected $child_tab = '';
225
226 /**
227 * Search string (entered by user or taken from cookie)
228 *
229 * @var null|string
230 */
231 protected $search_value = null;
232
233 /**
234 * Previous search string
235 *
236 * @var null|string
237 */
238 protected $search_value_old = null;
239
240 /**
241 * Page number item name (default 'page_number')
242 *
243 * The name can be changed for pages on multiple levels. This is needed to get back to the right page in
244 * parent-child page.
245 *
246 * @var string
247 */
248 protected $page_number_item_name = 'page_number';
249
250 /**
251 * Real page number
252 *
253 * @var null
254 */
255 protected $page_number_link = '';
256
257 /**
258 * Page number text item
259 *
260 * @var null
261 */
262 protected $page_number_item = '';
263
264 /**
265 * Make the default accessible to other classes
266 */
267 const SEARCH_ITEM_NAME_DEFAULT = 'wpda_s';
268
269 /**
270 * Name of search item (WP default 's')
271 *
272 * @var string
273 */
274 protected $search_item_name = self::SEARCH_ITEM_NAME_DEFAULT;
275
276 /**
277 * Name of the column containing action links
278 *
279 * Default is the first column displayed
280 *
281 * @var string
282 */
283 protected $first_display_column = '';
284
285 /**
286 * Table settings
287 *
288 * @var null|object
289 */
290 protected $wpda_table_settings = null;
291
292 /**
293 * Project table settings
294 *
295 * @var null|object
296 */
297 protected $wpda_project_table_settings = null;
298
299 /**
300 * Show help icon if help url is available
301 *
302 * @var null|string
303 */
304 protected $help_url = null;
305
306 /**
307 * Store columns in array with column name as index for fast access
308 *
309 * @var array
310 */
311 protected $columns_indexed = array();
312
313 /**
314 * Variables used to store table estimate row count data
315 *
316 * @var string
317 */
318 protected $table_engine = '';
319
320 /**
321 * Table rows
322 *
323 * @var mixed|string
324 */
325 protected $table_rows = '';
326
327 /**
328 * Estimated row count
329 *
330 * @var array|int
331 */
332 protected $row_count_estimate = array();
333
334 /**
335 * Project page id
336 *
337 * Helper to hide schema and table name in export links on web pages
338 *
339 * @var null
340 */
341 protected $pid = null;
342
343 /**
344 * Cache columns
345 *
346 * @var null
347 */
348 protected $wpda_cached_columns = null;
349
350 /**
351 * WPDA_List_Table constructor
352 *
353 * A table name must be provided in the constructor call. The table must be a valid MySQL database table to
354 * which access (to the back-end) is granted. If no table name is provided, the table does not exist or access
355 * to the back-end for the given table is not granted, processing is stopped. It makes no sense to continue
356 * without a valid table. A check for table existence is performed to prevent SQL injection.
357 *
358 * There are two types of tables to build a list table on:
359 * + List of tables in the WordPress database schema
360 * + List of rows in a specific table
361 *
362 * To build a list of tables in the WordPress database schema, we query table 'information_schema.tables'
363 * (which is in fact a view). This is the only query allowed outside the WordPress database schema. The
364 * table/view name is stored in constant WPDA_List_Table::LIST_BASE_TABLE for validation purposes.
365 *
366 * A list of rows for a specific table is based on WordPress class WP_List_Table.
367 *
368 * WPDA_List_Table can be used to build list tables for views as well. View based list tables however, do not
369 * support insert, update, delete, import and export actions.
370 *
371 * A table name is not the only thing we need to build a list table. We also need to have access to the
372 * table columns. If no table columns are provided execution is stopped as well.
373 *
374 * @param array $args [
375 *
376 * 'table_name' => (string) Database table name
377 *
378 * 'wpda_list_columns' => (object) Reference to a WPDA_List_Columns object
379 *
380 * 'singular' => (string) Singular object label
381 *
382 * 'plural' => (string) plural object label
383 *
384 * 'ajax' => (string) TRUE = list table support Ajax
385 *
386 * 'column_headers' => (array|string) Column headers
387 *
388 * 'title' => (string) Page title
389 *
390 * 'subtitle' => (string) Page subtitle
391 *
392 * 'bulk_export_enabled' => (boolean)
393 *
394 * 'search_box_enabled' => (boolean)
395 *
396 * 'bulk_actions_enabled' => (boolean)
397 *
398 * 'show_view_link' => (string) on|off
399 *
400 * 'allow_insert' => (string) on|off
401 *
402 * 'allow_update' => (string) on|off
403 *
404 * 'allow_delete' => (string) on|off
405 *
406 * 'allow_import' => (string) on|off
407 *
408 * 'hide_navigation' => (boolean)
409 *
410 * 'default_where' => (string)
411 *
412 * ]
413 * @since 1.0.0
414 */
415 public function __construct( $args = array() ) {
416 global $wpdb;
417 if ( !isset( $args['table_name'] ) ) {
418 // Calling WPDA_List_Table without a table_name doesn't make sense.
419 wp_die( __( 'ERROR: Wrong arguments [no table argument]', 'wp-data-access' ) );
420 }
421 if ( !isset( $args['wpda_list_columns'] ) ) {
422 // Calling WPDA_List_Table without a column list is not allowed.
423 wp_die( __( 'ERROR: Wrong arguments [no columns argument]', 'wp-data-access' ) );
424 }
425 parent::__construct( array(
426 'singular' => ( isset( $args['singular'] ) ? $args['singular'] : __( 'Row', 'wp-data-access' ) ),
427 'plural' => ( isset( $args['plural'] ) ? $args['plural'] : __( 'Rows', 'wp-data-access' ) ),
428 'ajax' => ( isset( $args['ajax'] ) ? $args['ajax'] : false ),
429 ) );
430 // Set schema name is available.
431 if ( isset( $args['wpdaschema_name'] ) ) {
432 $this->schema_name = $args['wpdaschema_name'];
433 }
434 // Set table name (availability already checked).
435 $this->table_name = $args['table_name'];
436 if ( self::LIST_BASE_TABLE !== $this->table_name ) {
437 // Whenever a table name is provided through a URL we are risking an SQL injection attack. Our
438 // defence mechanism here is to check whether the table name provided exists in our database.
439 // If it does not we'll terminate the process with an error.
440 // Although this check is only needed when a table name is provided through the URL we will perform
441 // it in all situations. It is a fast query which makes our application much more safe and reliable.
442 $this->wpda_data_dictionary = new WPDA_Dictionary_Exist($this->schema_name, $this->table_name);
443 if ( !$this->wpda_data_dictionary->table_exists() ) {
444 wp_die( __( 'ERROR: Invalid table name or not authorized', 'wp-data-access' ) );
445 }
446 }
447 $this->pid = ( isset( $args['pid'] ) ? $args['pid'] : '' );
448 // Get menu slag of current page.
449 if ( isset( $_REQUEST['page'] ) ) {
450 $this->page = sanitize_text_field( wp_unslash( $_REQUEST['page'] ) );
451 // input var okay.
452 } else {
453 // In order to show a list table we need a page.
454 wp_die( __( 'ERROR: Wrong arguments [no page argument]', 'wp-data-access' ) );
455 }
456 // Use column list: argument wpda_list_columns (availability already checked).
457 $this->wpda_list_columns =& $args['wpda_list_columns'];
458 // Set pagination.
459 if ( is_admin() ) {
460 $this->items_per_page = WPDA::get_option( WPDA::OPTION_BE_PAGINATION );
461 } else {
462 $this->items_per_page = WPDA::get_option( WPDA::OPTION_FE_PAGINATION );
463 }
464 // Overwrite column header text if column headers were provided.
465 $this->column_headers = ( isset( $args['column_headers'] ) ? $args['column_headers'] : '' );
466 // Set title.
467 if ( isset( $args['title'] ) ) {
468 $this->title = $args['title'];
469 } else {
470 $this->title = __( 'Table', 'wp-data-access' ) . ' ' . strtoupper( $this->table_name );
471 }
472 // Set subtitle.
473 if ( isset( $args['subtitle'] ) ) {
474 $this->subtitle = $args['subtitle'];
475 } else {
476 $wp_tables = $wpdb->tables( 'all', true );
477 if ( isset( $wp_tables[substr( $this->table_name, strlen( $wpdb->prefix ) )] ) ) {
478 $this->subtitle = '<span class="dashicons dashicons-warning"></span> ' . WPDA::get_table_type_text( WPDA::TABLE_TYPE_WP );
479 } elseif ( WPDA::is_wpda_table( $this->table_name ) ) {
480 $this->subtitle = '<span class="dashicons dashicons-warning"></span> ' . WPDA::get_table_type_text( WPDA::TABLE_TYPE_WPDA );
481 }
482 }
483 if ( !(isset( $args['allow_import'] ) && 'off' === $args['allow_import']) ) {
484 try {
485 // Instantiate WPDA_Import.
486 $this->wpda_import = new WPDA_Import(( is_admin() ? "?page={$this->page}" : '' ), $this->schema_name, $this->table_name);
487 } catch ( \Exception $e ) {
488 // If import is turned off instantiation will fail. Handle is set to null (check in future calls).
489 $this->wpda_import = null;
490 }
491 }
492 if ( isset( $args['bulk_export_enabled'] ) ) {
493 $this->bulk_export_enabled = $args['bulk_export_enabled'];
494 }
495 if ( isset( $args['search_box_enabled'] ) ) {
496 $this->search_box_enabled = $args['search_box_enabled'];
497 }
498 if ( isset( $args['bulk_actions_enabled'] ) ) {
499 $this->bulk_actions_enabled = $args['bulk_actions_enabled'];
500 }
501 if ( 'on' !== WPDA::get_option( WPDA::OPTION_BE_VIEW_LINK ) ) {
502 $this->show_view_link = WPDA::get_option( WPDA::OPTION_BE_VIEW_LINK );
503 } else {
504 if ( isset( $args['show_view_link'] ) ) {
505 $this->show_view_link = $args['show_view_link'];
506 } else {
507 $this->show_view_link = WPDA::get_option( WPDA::OPTION_BE_VIEW_LINK );
508 }
509 }
510 if ( 'on' !== WPDA::get_option( WPDA::OPTION_BE_ALLOW_INSERT ) ) {
511 $this->allow_insert = WPDA::get_option( WPDA::OPTION_BE_ALLOW_INSERT );
512 } else {
513 if ( isset( $args['allow_insert'] ) ) {
514 $this->allow_insert = $args['allow_insert'];
515 } else {
516 $this->allow_insert = WPDA::get_option( WPDA::OPTION_BE_ALLOW_INSERT );
517 }
518 }
519 if ( 'on' !== WPDA::get_option( WPDA::OPTION_BE_ALLOW_UPDATE ) ) {
520 $this->allow_update = WPDA::get_option( WPDA::OPTION_BE_ALLOW_UPDATE );
521 } else {
522 if ( isset( $args['allow_update'] ) ) {
523 $this->allow_update = $args['allow_update'];
524 } else {
525 $this->allow_update = WPDA::get_option( WPDA::OPTION_BE_ALLOW_UPDATE );
526 }
527 }
528 if ( 'on' !== WPDA::get_option( WPDA::OPTION_BE_ALLOW_DELETE ) ) {
529 $this->allow_delete = WPDA::get_option( WPDA::OPTION_BE_ALLOW_DELETE );
530 } else {
531 if ( isset( $args['allow_delete'] ) ) {
532 $this->allow_delete = $args['allow_delete'];
533 } else {
534 $this->allow_delete = WPDA::get_option( WPDA::OPTION_BE_ALLOW_DELETE );
535 }
536 }
537 if ( isset( $args['hide_navigation'] ) ) {
538 $this->hide_navigation = $args['hide_navigation'];
539 }
540 $this->search_value = ( is_scalar( $this->get_search_value() ) ? str_replace( "\\'", '', (string) $this->get_search_value() ) : '' );
541 if ( isset( $_REQUEST["{$this->search_item_name}_old_value"] ) ) {
542 $this->search_value_old = sanitize_text_field( wp_unslash( $_REQUEST["{$this->search_item_name}_old_value"] ) );
543 // input var okay.
544 } else {
545 $this->search_value_old = $this->search_value;
546 }
547 // Get page number(s).
548 if ( 'page_number' !== $this->page_number_item_name ) {
549 if ( isset( $_REQUEST['page_number'] ) ) {
550 $requested_page_number = sanitize_text_field( wp_unslash( $_REQUEST['page_number'] ) );
551 // input var okay.
552 $this->page_number_link = '&page_number=' . $requested_page_number;
553 $this->page_number_item = "<input type='hidden' name='page_number' value='" . $requested_page_number . "' />";
554 }
555 }
556 $this->page_number_link .= '&paged=' . $this->get_pagenum();
557 $this->page_number_item .= "<input type='hidden' name='" . $this->page_number_item_name . "' value='" . $this->get_pagenum() . "' />";
558 // Add search arguments to link to return to same page.
559 foreach ( $_REQUEST as $key => $value ) {
560 if ( substr( $key, 0, 19 ) === 'wpda_search_column_' ) {
561 if ( is_array( $value ) ) {
562 foreach ( $value as $elem_key => $elem_value ) {
563 $this->page_number_link .= "&{$elem_key}={$elem_value}";
564 $this->page_number_item .= "<input type='hidden' name='{$key}[]' value='{$elem_value}' />";
565 }
566 } else {
567 $this->page_number_link .= "&{$key}={$value}";
568 $this->page_number_item .= "<input type='hidden' name='{$key}' value='{$value}' />";
569 }
570 }
571 }
572 // Check if a WHERE clause (filter) was defined.
573 if ( isset( $args['default_where'] ) ) {
574 $this->where = $args['default_where'];
575 }
576 // Get table settings.
577 $wpda_table_settings = WPDA_Table_Settings_Model::query( $this->table_name, $this->schema_name );
578 if ( isset( $wpda_table_settings[0]['wpda_table_settings'] ) ) {
579 $this->wpda_table_settings = json_decode( $wpda_table_settings[0]['wpda_table_settings'] );
580 }
581 // Get estimated row count.
582 $this->row_count_estimate = WPDA::get_row_count_estimate( $this->schema_name, $this->table_name, $this->wpda_table_settings );
583 $this->table_rows = $this->row_count_estimate['row_count'];
584 // Get project table settings.
585 global $wpda_project_mode;
586 if ( is_array( $wpda_project_mode ) ) {
587 $setname = $wpda_project_mode['setname'];
588 $wpda_project_table_settings = null;
589 $wpda_project_table_settings_db = WPDP_Project_Design_Table_Model::static_query( $this->schema_name, $this->table_name, $setname );
590 if ( isset( $wpda_project_table_settings_db->tableinfo ) ) {
591 $this->wpda_project_table_settings = $wpda_project_table_settings_db->tableinfo;
592 }
593 }
594 if ( isset( $args['show_page_title'] ) && false === $args['show_page_title'] ) {
595 $this->show_page_title = $args['show_page_title'];
596 }
597 if ( isset( $args['help_url'] ) ) {
598 $this->help_url = $args['help_url'];
599 }
600 foreach ( $this->wpda_list_columns->get_table_columns() as $table_columns ) {
601 $this->columns_indexed[$table_columns['column_name']] = $table_columns;
602 }
603 }
604
605 /**
606 * Overwrite method
607 *
608 * @return int|void
609 */
610 public function get_pagenum() {
611 if ( isset( $_REQUEST[$this->page_number_item_name] ) ) {
612 $pagenum = ( isset( $_REQUEST[$this->page_number_item_name] ) ? absint( $_REQUEST[$this->page_number_item_name] ) : 0 );
613 if ( isset( $this->_pagination_args['total_pages'] ) && $pagenum > $this->_pagination_args['total_pages'] ) {
614 $pagenum = $this->_pagination_args['total_pages'];
615 }
616 return max( 1, $pagenum );
617 } else {
618 return parent::get_pagenum();
619 }
620 }
621
622 /**
623 * Set columns to be queries
624 *
625 * Set columns to be queried and shown in the list table.
626 * By default all columns in the table will be displayed.
627 *
628 * @param mixed $columns_queried Column list.
629 *
630 * @since 1.0.0
631 */
632 public function set_columns_queried( $columns_queried ) {
633 $this->columns_queried = $columns_queried;
634 }
635
636 /**
637 * Enable or disable bulk actions
638 *
639 * @param boolean $bulk_actions_enabled TRUE = allowed, FALSE = not allowed.
640 *
641 * @since 1.0.0
642 */
643 public function set_bulk_actions_enabled( $bulk_actions_enabled ) {
644 $this->bulk_actions_enabled = $bulk_actions_enabled;
645 }
646
647 /**
648 * Enable or disable bulk export options
649 *
650 * @param boolean $bulk_export_enabled TRUE = allowed, FALSE = not allowed.
651 *
652 * @since 1.0.0
653 */
654 public function set_bulk_export_enabled( $bulk_export_enabled ) {
655 $this->bulk_export_enabled = $bulk_export_enabled;
656 }
657
658 /**
659 * Show or hide search box
660 *
661 * @param boolean $search_box_enabled TRUE = show search box, FALSE = do not show search bow.
662 *
663 * @since 1.0.0
664 */
665 public function set_search_box_enabled( $search_box_enabled ) {
666 $this->search_box_enabled = $search_box_enabled;
667 }
668
669 /**
670 * Set page title
671 *
672 * @param string $title Page title.
673 *
674 * @since 1.0.0
675 */
676 public function set_title( $title ) {
677 $this->title = $title;
678 }
679
680 /**
681 * Set page subtitle
682 *
683 * @param string $subtitle Page subtitle.
684 *
685 * @since 1.0.0
686 */
687 public function set_subtitle( $subtitle ) {
688 $this->subtitle = $subtitle;
689 }
690
691 /**
692 * I18n text displayed when no data found
693 *
694 * @since 1.0.0
695 */
696 public function no_items() {
697 echo __( 'No data found', 'wp-data-access' );
698 }
699
700 /**
701 * Use this method to build parent child relationships.
702 *
703 * Overwrite this function if you want to use the list table as a child list table related to some parent
704 * form. You can add parent arguments to calls to make sure you get back to the right parent.
705 *
706 * @param array $item Column info.
707 * @return string String contains parent arguments.
708 * @since 1.5.0
709 */
710 protected function add_parent_args_as_string( $item ) {
711 return '';
712 }
713
714 /**
715 * Render columns
716 *
717 * Three links are provided for every list table row:
718 * + 'View' => Link to view form (readonly data entry form)
719 * + 'Edit' => Link to data entry form (updatable)
720 * + 'Delete' => Link to delete record from database table
721 *
722 * Links to view, edit and delete records are only available if a primary key for the table is found. Without
723 * a primary key unique identification of records is not possible.
724 *
725 * Links to action are offered through hidden forms to prevent long URL's and problem when refreshing pages
726 * manually. More information about how and why is provided in source code at place where I thought the
727 * information could be helpful.
728 *
729 * @param array $item Column info.
730 * @param string $column_name Column name.
731 *
732 * @return mixed Value display in list table column
733 * @since 1.0.0
734 */
735 public function column_default( $item, $column_name ) {
736 if ( $this->wpda_list_columns->get_table_columns()[0]['column_name'] === $column_name || $column_name === $this->first_display_column ) {
737 // First column: add row actions.
738 $count = count( $this->wpda_list_columns->get_table_primary_key() );
739 //phpcs:ignore - 8.1 proof
740 if ( 0 === $count ) {
741 // No actions without a primary key!
742 // This automatically covers view processing correctly.
743 return $this->render_column_content( $item, $column_name ) . $this->row_actions( array(
744 'wpda_hide_column' => '',
745 ) );
746 } else {
747 // Check rights.
748 if ( 'off' === $this->show_view_link && 'off' === $this->allow_update && 'off' === $this->allow_delete ) {
749 // No rights!
750 $actions = array();
751 $this->column_default_add_action( $item, $column_name, $actions );
752 if ( is_array( $actions ) && count( $actions ) > 0 ) {
753 //phpcs:ignore - 8.1 proof
754 return sprintf( '%1$s %2$s', $this->render_column_content( $item, $column_name ), $this->row_actions( $actions ) );
755 } else {
756 return sprintf( '%1$s', $this->render_column_content( $item, $column_name ) );
757 }
758 }
759 // Check if row security is enabled.
760 $row_security = isset( $this->wpda_table_settings->table_settings->row_level_security ) && 'true' === $this->wpda_table_settings->table_settings->row_level_security;
761 // To prevent our URLs containing many arguments, we'll use post to submit row actions. Since our
762 // list table is build within a form and we cannot use nested forms we'll use a container (id =
763 // wpda_invisible_container) defined outside the list table, and add our forms to that container.
764 // We'll use jQuery to add our forms to the container. From the links in our rows we can then just
765 // submit any form in that container with jQuery as well.
766 $form_id = '_' . self::$list_number++;
767 $wp_nonce_keys = '';
768 $row_security_keys = '';
769 // We need to add keys and values for multi-column primary keys.
770 foreach ( $this->wpda_list_columns->get_table_primary_key() as $key ) {
771 $wp_nonce_keys .= "-{$item[$key]}";
772 if ( $row_security ) {
773 $row_security_keys .= "-{$key}-{$item[$key]}";
774 }
775 }
776 if ( $row_security ) {
777 $row_security_nonce = wp_create_nonce( "wpda-row-level-security-{$this->table_name}{$row_security_keys}" );
778 $row_security_nonce_field = "<input type='hidden' name='rownonce' value='" . esc_attr( $row_security_nonce ) . "' />";
779 } else {
780 $row_security_nonce_field = '';
781 }
782 // Prepare url.
783 if ( is_admin() ) {
784 $url = "?page={$this->page}";
785 } else {
786 $url = '';
787 }
788 if ( 'on' === $this->show_view_link ) {
789 $wp_nonce_action = "wpda-query-{$this->table_name}{$wp_nonce_keys}";
790 $wp_nonce = wp_create_nonce( $wp_nonce_action );
791 // Build the row action. Use jQuery to add form to container.
792 // All items escaped in create_post_form().
793 $view_form = $this->create_post_form(
794 $item,
795 "view_form{$form_id}",
796 'view',
797 $url,
798 $wp_nonce,
799 $row_security_nonce_field,
800 $this->schema_name,
801 $this->table_name
802 );
803 ?>
804
805 <script type='text/javascript'>
806 jQuery("#wpda_invisible_container").append("<?php
807 echo $view_form;
808 // phpcs:ignore WordPress.Security.EscapeOutput
809 ?>");
810 </script>
811
812 <?php
813 // Add link to submit form.
814 $actions['view'] = sprintf(
815 '<a href="javascript:void(0)"
816 class="edit wpda_tooltip"
817 title="%s"
818 onclick="jQuery(\'#%s\').submit()">
819 <span style="white-space: nowrap">
820 <i class="fas fa-eye wpda_icon_on_button"></i>
821 %s
822 </span>
823 </a>
824 ',
825 __( 'View row data', 'wp-data-access' ),
826 "view_form{$form_id}",
827 __( 'View', 'wp-data-access' )
828 );
829 }
830 if ( 'on' === $this->allow_update || WPDA::is_wpda_table( $this->table_name ) ) {
831 $wp_nonce_action = "wpda-query-{$this->table_name}{$wp_nonce_keys}";
832 $wp_nonce = wp_create_nonce( $wp_nonce_action );
833 // Build the row action. Use jQuery to add form to container.
834 // All items escaped in create_post_form().
835 $edit_form = $this->create_post_form(
836 $item,
837 "edit_form{$form_id}",
838 'edit',
839 $url,
840 $wp_nonce,
841 $row_security_nonce_field,
842 $this->schema_name,
843 $this->table_name
844 );
845 ?>
846
847 <script type='text/javascript'>
848 jQuery("#wpda_invisible_container").append("<?php
849 echo $edit_form;
850 // phpcs:ignore WordPress.Security.EscapeOutput
851 ?>");
852 </script>
853
854 <?php
855 // Add link to submit form.
856 $actions['edit'] = sprintf(
857 '<a href="javascript:void(0)"
858 class="edit wpda_tooltip"
859 title="%s"
860 onclick="jQuery(\'#%s\').submit()">
861 <span style="white-space: nowrap">
862 <i class="fas fa-pen wpda_icon_on_button"></i>
863 %s
864 </span>
865 </a>
866 ',
867 __( 'Edit row data', 'wp-data-access' ),
868 "edit_form{$form_id}",
869 __( 'Edit', 'wp-data-access' )
870 );
871 }
872 if ( 'on' === $this->allow_delete || WPDA::is_wpda_table( $this->table_name ) ) {
873 $wp_nonce_action = "wpda-delete-{$this->table_name}{$wp_nonce_keys}";
874 $wp_nonce = wp_create_nonce( $wp_nonce_action );
875 // Build the row action. Use jQuery to add form to container.
876 // All items escaped in create_post_form().
877 $delete_form = $this->create_post_form(
878 $item,
879 "delete_form{$form_id}",
880 'delete',
881 $url,
882 $wp_nonce,
883 $row_security_nonce_field,
884 $this->schema_name,
885 $this->table_name
886 );
887 ?>
888
889 <script type='text/javascript'>
890 jQuery("#wpda_invisible_container").append("<?php
891 echo $delete_form;
892 // phpcs:ignore WordPress.Security.EscapeOutput
893 ?>");
894 </script>
895
896 <?php
897 // Add link to submit form.
898 $warning = __( "You are about to permanently delete these items from your site.\\nThis action cannot be undone.\\n\\'Cancel\\' to stop, \\'OK\\' to delete.", 'wp-data-access' );
899 $actions['delete'] = sprintf(
900 '<a href="javascript:void(0)"
901 class="delete wpda_tooltip"
902 title="%s"
903 onclick="if (confirm(\'%s\')) jQuery(\'#%s\').submit()">
904 <span style="white-space: nowrap">
905 <i class="fas fa-trash wpda_icon_on_button"></i>
906 %s
907 </span>
908 </a>
909 ',
910 __( 'Delete row data (this cannot be undone)', 'wp-data-access' ),
911 $warning,
912 "delete_form{$form_id}",
913 __( 'Delete', 'wp-data-access' )
914 );
915 }
916 // Developers can add actions by adding their own implementation of following method.
917 $this->column_default_add_action( $item, $column_name, $actions );
918 if ( has_filter( 'wpda_column_default' ) ) {
919 // Use filter.
920 $filter = apply_filters(
921 'wpda_column_default',
922 '',
923 $item,
924 $column_name,
925 $this->table_name,
926 $this->schema_name,
927 $this->wpda_list_columns,
928 self::$list_number,
929 $this
930 );
931 if ( null !== $filter ) {
932 return sprintf( '%1$s %2$s', $filter, $this->row_actions( $actions ) );
933 }
934 }
935 return sprintf( '%1$s %2$s', $this->render_column_content( $item, $column_name ), $this->row_actions( $actions ) );
936 }
937 } else {
938 if ( substr( $column_name, 0, 15 ) === 'wpda_hyperlink_' ) {
939 $hyperlink_no = substr( $column_name, 15 );
940 if ( isset( $this->wpda_table_settings->hyperlinks[$hyperlink_no] ) ) {
941 $hyperlink = $this->wpda_table_settings->hyperlinks[$hyperlink_no];
942 $hyperlink_html = ( isset( $hyperlink->hyperlink_html ) ? $hyperlink->hyperlink_html : '' );
943 if ( '' !== $hyperlink_html ) {
944 // Substitute values.
945 foreach ( $item as $key => $value ) {
946 $hyperlink_html = str_replace( "\$\${$key}\$\$", str_replace( ' ', '%20', (string) $value ), $hyperlink_html );
947 }
948 }
949 $macro = new WPDA_Macro($hyperlink_html);
950 $hyperlink_html = $macro->exe_macro();
951 if ( '' !== $hyperlink_html ) {
952 // Link is not empty AFTER substitution.
953 if ( false !== strpos( ltrim( $hyperlink_html ), '&lt;' ) ) {
954 return html_entity_decode( $hyperlink_html );
955 } else {
956 $hyperlink_label = ( isset( $hyperlink->hyperlink_label ) ? $hyperlink->hyperlink_label : '' );
957 $hyperlink_target = ( isset( $hyperlink->hyperlink_target ) ? $hyperlink->hyperlink_target : false );
958 $target = ( true === $hyperlink_target ? "target='_blank'" : '' );
959 return "<a href='" . str_replace( ' ', '+', trim( $hyperlink_html ) ) . "' {$target}>{$hyperlink_label}</a>";
960 }
961 } else {
962 return '';
963 }
964 }
965 return 'ERROR';
966 } else {
967 // Check if column is of type media.
968 $media_type = WPDA_Media_Model::get_column_media( $this->table_name, $column_name, $this->schema_name );
969 if ( 'Image' === $media_type ) {
970 $image_ids = explode( ',', (string) $item[$column_name] );
971 //phpcs:ignore - 8.1 proof
972 $image_src = '';
973 foreach ( $image_ids as $image_id ) {
974 $url = wp_get_attachment_url( esc_attr( $image_id ) );
975 if ( false !== $url ) {
976 $title = get_the_title( esc_attr( $image_id ) );
977 $image_src .= ( '' !== $image_src ? '<br/>' : '' );
978 $image_src .= sprintf( '<img src="%s" class="wpda_tooltip" title="%s" width="100%%">', $url, $title );
979 }
980 }
981 return $image_src;
982 } elseif ( 'ImageURL' === $media_type ) {
983 return sprintf( '<img src="%s" class="wpda_tooltip" width="100%%">', $item[$column_name] );
984 } elseif ( 'Attachment' === $media_type ) {
985 $media_ids = explode( ',', (string) $item[$column_name] );
986 //phpcs:ignore - 8.1 proof
987 $media_links = '';
988 foreach ( $media_ids as $media_id ) {
989 $url = wp_get_attachment_url( esc_attr( $media_id ) );
990 if ( false !== $url ) {
991 $mime_type = get_post_mime_type( $media_id );
992 if ( false !== $mime_type ) {
993 $title = get_the_title( esc_attr( $media_id ) );
994 $media_links .= self::column_media_attachment( $url, $title, $mime_type );
995 }
996 }
997 }
998 return $media_links;
999 } elseif ( 'Hyperlink' === $media_type ) {
1000 if ( null === $item[$column_name] || '' === $item[$column_name] ) {
1001 return '';
1002 } else {
1003 if ( !(isset( $this->wpda_table_settings->table_settings->hyperlink_definition ) && 'text' === $this->wpda_table_settings->table_settings->hyperlink_definition) ) {
1004 $hyperlink = json_decode( $item[$column_name], true );
1005 if ( is_array( $hyperlink ) && isset( $hyperlink['label'] ) && isset( $hyperlink['url'] ) && isset( $hyperlink['target'] ) ) {
1006 if ( '' === $hyperlink['url'] ) {
1007 return '';
1008 } else {
1009 return "<a href='{$hyperlink['url']}' target='{$hyperlink['target']}'>{$hyperlink['label']}</a>";
1010 }
1011 } else {
1012 return '';
1013 }
1014 } else {
1015 $hyperlink_label = $this->wpda_list_columns->get_column_label( $column_name );
1016 return "<a href='{$item[$column_name]}' target='_blank'>{$hyperlink_label}</a>";
1017 }
1018 }
1019 } elseif ( 'Audio' === $media_type ) {
1020 $audio_ids = explode( ',', (string) $item[$column_name] );
1021 //phpcs:ignore - 8.1 proof
1022 $audio_src = '';
1023 foreach ( $audio_ids as $audio_id ) {
1024 if ( 'audio' === substr( get_post_mime_type( $audio_id ), 0, 5 ) ) {
1025 $url = wp_get_attachment_url( esc_attr( $audio_id ) );
1026 if ( false !== $url ) {
1027 $title = get_the_title( esc_attr( $audio_id ) );
1028 if ( false !== $url ) {
1029 $audio_src .= '<div title="' . $title . '" class="wpda_tooltip">' . do_shortcode( '[audio src="' . $url . '"]' ) . '</div>';
1030 }
1031 }
1032 }
1033 }
1034 return $audio_src;
1035 } elseif ( 'Video' === $media_type ) {
1036 $video_ids = explode( ',', (string) $item[$column_name] );
1037 //phpcs:ignore - 8.1 proof
1038 $video_src = '';
1039 foreach ( $video_ids as $video_id ) {
1040 if ( 'video' === substr( get_post_mime_type( $video_id ), 0, 5 ) ) {
1041 $url = wp_get_attachment_url( esc_attr( $video_id ) );
1042 if ( false !== $url ) {
1043 if ( false !== $url ) {
1044 $video_src .= do_shortcode( '[video src="' . $url . '"]' );
1045 }
1046 }
1047 }
1048 }
1049 return $video_src;
1050 }
1051 }
1052 if ( has_filter( 'wpda_column_default' ) ) {
1053 // Use filter.
1054 $filter = apply_filters(
1055 'wpda_column_default',
1056 '',
1057 $item,
1058 $column_name,
1059 $this->table_name,
1060 $this->schema_name,
1061 $this->wpda_list_columns,
1062 self::$list_number,
1063 $this
1064 );
1065 if ( null !== $filter ) {
1066 return $filter;
1067 }
1068 }
1069 if ( 'csv' !== WPDA::get_option( WPDA::OPTION_PLUGIN_SET_FORMAT ) && isset( $this->columns_indexed[$column_name]['data_type'] ) && 'set' === $this->columns_indexed[$column_name]['data_type'] ) {
1070 $list = '<' . WPDA::get_option( WPDA::OPTION_PLUGIN_SET_FORMAT ) . '>';
1071 $listarray = explode( ',', (string) $item[$column_name] );
1072 //phpcs:ignore - 8.1 proof
1073 foreach ( $listarray as $listitem ) {
1074 $list .= "<li>{$listitem}</li>";
1075 }
1076 $list .= '</' . WPDA::get_option( WPDA::OPTION_PLUGIN_SET_FORMAT ) . '>';
1077 return $list;
1078 }
1079 return $this->render_column_content( $item, $column_name );
1080 }
1081 }
1082
1083 /**
1084 * Create post form which is added to the invisible container
1085 *
1086 * @param array $item Column info.
1087 * @param string $form_id Form ID.
1088 * @param string $action Form action.
1089 * @param string $url URL admin page.
1090 * @param string $wp_nonce Nonce.
1091 * @param string $row_security_nonce_field Nonce for row level access security.
1092 * @param string $schema_name Database schema name.
1093 * @param string $table_name Database table name.
1094 *
1095 * @return array|string|string[]
1096 */
1097 private function create_post_form(
1098 $item,
1099 $form_id,
1100 $action,
1101 $url,
1102 $wp_nonce,
1103 $row_security_nonce_field,
1104 $schema_name,
1105 $table_name
1106 ) {
1107 // Already escaped: $url, $row_security_nonce_field, get_key_input_fields(), add_parent_args_as_string().
1108 $esc_attr = 'esc_attr';
1109 $get_key_input_fields = $this->get_key_input_fields( $item );
1110 $add_parent_args_as_string = $this->add_parent_args_as_string( $item );
1111 $page_number_item = $this->page_number_item;
1112 $case_sensitive_search = ( isset( $_REQUEST['wpda_c'] ) && 'true' === $_REQUEST['wpda_c'] ? "<input type='hidden' name='wpda_c' value='true'>" : '' );
1113 $add_schema_and_table_name = ( !is_admin() ? '' : "\n\t\t\t\t\t<input type='hidden' name='wpdaschema_name' value='{$esc_attr( $schema_name )}' />\n\t\t\t\t\t<input type='hidden' name='table_name' value='{$esc_attr( $table_name )}' />\n\t\t\t\t" );
1114 // Hide schema and table name on front-end
1115 $form = <<<EOT
1116 \t\t\t\t<form id='{$esc_attr( $form_id )}' action='{$url}' method='post'>
1117 \t\t\t\t\t{$get_key_input_fields}
1118 \t\t\t\t\t{$add_parent_args_as_string}
1119 \t\t\t\t\t{$add_schema_and_table_name}
1120 \t\t\t\t\t<input type='hidden' name='action' value='{$esc_attr( $action )}' />
1121 \t\t\t\t\t<input type='hidden' name='_wpnonce' value='{$esc_attr( $wp_nonce )}'>
1122 \t\t\t\t\t{$row_security_nonce_field}
1123 \t\t\t\t\t{$page_number_item}
1124 \t\t\t\t\t{$case_sensitive_search}
1125 \t\t\t\t</form>
1126 EOT;
1127 return str_replace( array("\n", "\r"), '', $form );
1128 }
1129
1130 /**
1131 * Create media link
1132 *
1133 * @param string $url Media URL.
1134 * @param string $title Media title.
1135 * @param string $mime_type Mime type.
1136 *
1137 * @return string
1138 */
1139 public static function column_media_attachment( $url, $title, $mime_type ) {
1140 if ( 'image' === substr( $mime_type, 0, 5 ) ) {
1141 $class = 'dashicons-format-image';
1142 } elseif ( 'audio' === substr( $mime_type, 0, 5 ) ) {
1143 $class = 'dashicons-playlist-audio';
1144 } elseif ( 'video' === substr( $mime_type, 0, 5 ) ) {
1145 $class = 'dashicons-playlist-video';
1146 } elseif ( 'application' === substr( $mime_type, 0, 11 ) ) {
1147 $class = 'dashicons-media-document';
1148 } else {
1149 $class = 'dashicons-external';
1150 }
1151 return sprintf(
1152 '<a href="%s" title="%s" target="_blank"><span class="dashicons %s wpda_attachment_icon"></span></a>',
1153 $url,
1154 $title,
1155 $class
1156 );
1157 }
1158
1159 /**
1160 * Overwrite method to prevent double row actions
1161 *
1162 * @param object $item Table column.
1163 * @param string $column_name Column name.
1164 * @param string $primary Primary key.
1165 *
1166 * @return string
1167 */
1168 protected function handle_row_actions( $item, $column_name, $primary ) {
1169 return '';
1170 }
1171
1172 /**
1173 * Render column content
1174 *
1175 * Strip content if too long and replace & character.
1176 *
1177 * @param object $item Table column.
1178 * @param string $column_name Database column name.
1179 *
1180 * @return string Rendered column content
1181 */
1182 protected function render_column_content( $item, $column_name, $substitute_newlines = true ) {
1183 $column_content = ( isset( $item["lookup_value_{$column_name}"] ) ? $item["lookup_value_{$column_name}"] : $item[$column_name] );
1184 if ( 'off' === WPDA::get_option( WPDA::OPTION_BE_TEXT_WRAP_SWITCH ) && WPDA::get_option( WPDA::OPTION_BE_TEXT_WRAP ) < strlen( (string) $column_content ) ) {
1185 $title = sprintf( __( 'Output limited to %1$s characters', 'wp-data-access' ), WPDA::get_option( WPDA::OPTION_BE_TEXT_WRAP ) );
1186 if ( $substitute_newlines ) {
1187 return str_replace( "\n", '<br/>', substr( esc_html( str_replace( '&', '&amp;', (string) $column_content ) ), 0, WPDA::get_option( WPDA::OPTION_BE_TEXT_WRAP ) ) . ' <a href="javascript:void(0)" title="' . $title . '">&bull;&bull;&bull;</a>' );
1188 } else {
1189 return substr( esc_html( str_replace( '&', '&amp;', (string) $column_content ) ), 0, WPDA::get_option( WPDA::OPTION_BE_TEXT_WRAP ) ) . ' <a href="javascript:void(0)" title="' . $title . '">&bull;&bull;&bull;</a>';
1190 }
1191 } else {
1192 $column_data_type = $this->wpda_list_columns->get_column_data_type( $column_name );
1193 switch ( $column_data_type ) {
1194 case 'date':
1195 // date only.
1196 if ( '' === $column_content || null === $column_content ) {
1197 $column_content = '';
1198 } else {
1199 $column_content = date_i18n( get_option( 'date_format' ), strtotime( $column_content ) );
1200 }
1201 break;
1202 case 'time':
1203 // time only.
1204 if ( '' === $column_content || null === $column_content ) {
1205 $column_content = '';
1206 } else {
1207 $column_content = date_i18n( get_option( 'time_format' ), strtotime( $column_content ) );
1208 }
1209 break;
1210 case 'datetime':
1211 // date + time.
1212 case 'timestamp':
1213 if ( '' === $column_content || null === $column_content ) {
1214 $column_content = '';
1215 } else {
1216 $column_content = date_i18n( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), strtotime( $column_content ) );
1217 }
1218 }
1219 if ( $substitute_newlines ) {
1220 return str_replace( "\n", '<br/>', esc_html( str_replace( '&', '&amp;', (string) $column_content ) ) );
1221 } else {
1222 return esc_html( str_replace( '&', '&amp;', (string) $column_content ) );
1223 }
1224 }
1225 }
1226
1227 /**
1228 * Generate hidden fields for keys
1229 *
1230 * @param array $item Column info.
1231 *
1232 * @return string String containing key items and values.
1233 */
1234 protected function get_key_input_fields( $item ) {
1235 $key_input_fields = '';
1236 foreach ( $this->wpda_list_columns->get_table_primary_key() as $key ) {
1237 $key_name = esc_attr( $key );
1238 $key_value = esc_attr( $item[$key] );
1239 $key_input_fields .= "<input type='hidden' name='{$key_name}' value='{$key_value}' />";
1240 }
1241 return $key_input_fields;
1242 }
1243
1244 /**
1245 * Override this method to add actions to first column of row
1246 *
1247 * @param array $item Item information.
1248 * @param string $column_name Column name.
1249 * @param array $actions Array of actions to be added to row.
1250 */
1251 protected function column_default_add_action( $item, $column_name, &$actions ) {
1252 if ( has_filter( 'wpda_column_default_add_action' ) ) {
1253 $filter = apply_filters(
1254 'wpda_column_default_add_action',
1255 '',
1256 $item,
1257 $column_name,
1258 $this->table_name,
1259 $this->schema_name,
1260 $this->wpda_list_columns,
1261 self::$list_number,
1262 $actions,
1263 $this
1264 );
1265 if ( null !== $filter ) {
1266 $actions = $filter;
1267 }
1268 }
1269 }
1270
1271 /**
1272 * Render bulk edit checkbox
1273 *
1274 * @param array $item Column list.
1275 *
1276 * @return string Content for checkbox column.
1277 * @since 1.0.0
1278 */
1279 public function column_cb( $item ) {
1280 if ( !$this->bulk_actions_enabled ) {
1281 // Bulk actions disabled.
1282 return '';
1283 }
1284 if ( empty( $this->wpda_list_columns->get_table_primary_key() ) ) {
1285 // Table has no primary key: no bulk actions allowed!
1286 // Primary key is used to ensure uniqueness!
1287 return '';
1288 }
1289 // Build CB value: Use json format for multi column primary keys.
1290 $cb_value = (object) null;
1291 foreach ( $this->wpda_list_columns->get_table_primary_key() as $primary_key_column ) {
1292 // JSON string key and values will be double quoted. Therefore a slash must be added to double quotes in values.
1293 $cb_value->{$primary_key_column} = esc_attr( str_replace( '"', '\\"', $item[$primary_key_column] ) );
1294 }
1295 return "<input type='checkbox' name='bulk-selected[]' value='" . wp_json_encode( $cb_value ) . "' />";
1296 }
1297
1298 /**
1299 * Show page title
1300 *
1301 * @return void
1302 */
1303 protected function show_title() {
1304 if ( self::LIST_BASE_TABLE !== $this->table_name && (substr( $this->page, 0, 13 ) === \WP_Data_Access_Admin::PAGE_EXPLORER || \WP_Data_Access_Admin::PAGE_MAIN === $this->page) ) {
1305 $this->subtitle = $this->title;
1306 $this->title = 'Data Explorer';
1307 }
1308 echo esc_attr( $this->title );
1309 }
1310
1311 /**
1312 * Show page sub title
1313 *
1314 * @return void
1315 */
1316 protected function show_sub_title() {
1317 ?>
1318 <div class="wpda_subtitle"><strong><?php
1319 echo wp_kses( $this->subtitle, array(
1320 'span' => array(
1321 'class' => array(),
1322 ),
1323 ) );
1324 ?></strong></div>
1325 <?php
1326 }
1327
1328 /**
1329 * Show list table page
1330 *
1331 * Inside the list table page, the list table is shown. The necessary functionality to show the list table
1332 * specifically is found in method {@see WPDA_List_Table::display()}.
1333 *
1334 * @since 1.0.0
1335 *
1336 * @see WPDA_List_Table::display()
1337 */
1338 public function show() {
1339 // Check for import requested.
1340 if ( null !== $this->wpda_import ) {
1341 $this->wpda_import->check_post();
1342 }
1343 // Prepare list table items.
1344 $this->prepare_items();
1345 // Show list table.
1346 ?>
1347
1348 <div class="wrap<?php
1349 echo ( is_admin() ? '' : ' wp-core-ui' );
1350 ?>">
1351 <?php
1352 if ( $this->show_page_title ) {
1353 ?>
1354 <h1 class="wp-heading-inline">
1355 <?php
1356 if ( self::LIST_BASE_TABLE !== $this->table_name && (WPDA::current_user_is_admin() && \WP_Data_Access_Admin::PAGE_MAIN === $this->page) ) {
1357 ?>
1358 <a
1359 href="?page=<?php
1360 echo esc_attr( $this->page );
1361 ?>"
1362 class="dashicons dashicons-arrow-left-alt2 wpda_tooltip"
1363 title="Data Explorer"
1364 ></a>
1365 <?php
1366 }
1367 $this->show_title();
1368 ?>
1369 </h1>
1370 <?php
1371 }
1372 ?>
1373
1374 <?php
1375 if ( !is_admin() || 'wpda_wpdp_' === substr( $this->page, 0, 10 ) || WPDP::PAGE_TEMPLATES === $this->page || substr( $this->page, 0, 13 ) === \WP_Data_Access_Admin::PAGE_EXPLORER ) {
1376 $this->add_header_button();
1377 }
1378 $this->add_header_actions();
1379 if ( 'diehard' === $this->page ) {
1380 $action_url = admin_url( 'admin-ajax.php' );
1381 } else {
1382 $action_url = admin_url( 'admin.php' );
1383 }
1384 ?>
1385 <div id="wpda_invisible_container" style="display:none;">
1386 <form id="wpda_main_export_form" method="post" action="<?php
1387 echo esc_url( $action_url );
1388 ?>?action=wpda_export" target="_blank">
1389 <input type="text" name="type" id="wpda_main_export_form__type" value="table" />
1390 <input type="text" name="wpdaschema_name" id="wpda_main_export_form__schema_name" />
1391 <input type="text" name="_wpnonce" id="wpda_main_export_form__wpnonce" />
1392 </form>
1393 <form id="wpda_table_export_form" method="post" action="<?php
1394 echo esc_url( $action_url );
1395 ?>?action=wpda_export" target="_blank">
1396 <input type="text" name="type" id="wpda_table_export_form__type" value="table" />
1397 <input type="text" name="wpdaschema_name" id="wpda_table_export_form__schema_name" />
1398 <input type="text" name="table_names" id="wpda_table_export_form__table_names" />
1399 <input type="text" name="format_type" id="wpda_table_export_form__format_type" />
1400 <input type="text" name="include_table_settings" id="wpda_table_export_form__include_table_settings" />
1401 <input type="text" name="_wpnonce" id="wpda_table_export_form__wpnonce" />
1402 </form>
1403 <form id="wpda_row_export_form" method="post" action="<?php
1404 echo esc_url( $action_url );
1405 ?>?action=wpda_export" target="_blank">
1406 <input type="text" name="type" id="wpda_row_export_form__type" value="row" />
1407 <input type="text" name="pid" id="wpda_row_export_form__pid" />
1408 <input type="text" name="wpdaschema_name" id="wpda_row_export_form__schema_name" />
1409 <input type="text" name="table_names" id="wpda_row_export_form__table_names" />
1410 <input type="text" name="mysql_set" id="wpda_row_export_form__mysql_set" />
1411 <input type="text" name="show_create" id="wpda_row_export_form__show_create" />
1412 <input type="text" name="show_comments" id="wpda_row_export_form__show_comments" />
1413 <input type="text" name="format_type" id="wpda_row_export_form__format_type" />
1414 <input type="text" name="_wpnonce" id="wpda_row_export_form__wpnonce" />
1415 </form>
1416 </div>
1417 <?php
1418 // Add import container.
1419 if ( null !== $this->wpda_import ) {
1420 $this->wpda_import->add_container();
1421 }
1422 // Add custom code before the list table.
1423 do_action_ref_array( 'wpda_before_list_table', array($this) );
1424 // Prepare url.
1425 if ( is_admin() ) {
1426 $url = "?page={$this->page}";
1427 } else {
1428 $url = '';
1429 }
1430 ?>
1431
1432 <form id="wpda_main_form"
1433 method="post"
1434 action="<?php
1435 echo esc_attr( $url );
1436 ?>"
1437 style="
1438 <?php
1439 if ( '' !== $this->subtitle ) {
1440 echo 'margin-top: 10px';
1441 }
1442 ?>
1443 "
1444 >
1445
1446 <?php
1447 $this->show_sub_title();
1448 if ( $this->search_box_enabled ) {
1449 $this->search_box( __( 'search', 'wp-data-access' ), 'search_id' );
1450 }
1451 $this->display();
1452 if ( '' === $this->get_bulk_actions() ) {
1453 // Add action item containing value -1. This will allow sorting if no bulk action listbox is displayed.
1454 ?>
1455 <input type="hidden" name="action" value="-1"/>
1456 <?php
1457 }
1458 if ( self::LIST_BASE_TABLE !== $this->table_name ) {
1459 ?>
1460 <input type="hidden" name="wpdaschema_name"
1461 value="<?php
1462 echo esc_attr( $this->schema_name );
1463 // input var okay.
1464 ?>"/>
1465 <input type="hidden" name="table_name"
1466 value="<?php
1467 echo esc_attr( $this->table_name );
1468 // input var okay.
1469 ?>"/>
1470 <?php
1471 }
1472 ?>
1473 <input id="wpda_main_form_orderby" type="hidden" name="orderby"
1474 value="<?php
1475 echo ( isset( $_REQUEST['orderby'] ) ? esc_attr( sanitize_text_field( wp_unslash( $_REQUEST['orderby'] ) ) ) : '' );
1476 // input var okay.
1477 ?>"/>
1478 <input id="wpda_main_form_order" type="hidden" name="order"
1479 value="<?php
1480 echo ( isset( $_REQUEST['order'] ) ? esc_attr( sanitize_text_field( wp_unslash( $_REQUEST['order'] ) ) ) : '' );
1481 // input var okay.
1482 ?>"/>
1483 <input id="wpda_main_form_post_mime_type" type="hidden" name="post_mime_type"
1484 value="<?php
1485 echo ( isset( $_REQUEST['post_mime_type'] ) ? esc_attr( sanitize_text_field( wp_unslash( $_REQUEST['post_mime_type'] ) ) ) : '' );
1486 // input var okay.
1487 ?>"/>
1488 <input id="wpda_main_form_detached" type="hidden" name="detached"
1489 value="<?php
1490 echo ( isset( $_REQUEST['detached'] ) ? esc_attr( sanitize_text_field( wp_unslash( $_REQUEST['detached'] ) ) ) : '' );
1491 // input var okay.
1492 ?>"/>
1493 <input id="wpda_main_db_schema" type="hidden" name="wpda_main_db_schema"
1494 value="<?php
1495 echo ( isset( $_REQUEST['wpda_main_db_schema'] ) ? esc_attr( sanitize_text_field( wp_unslash( $_REQUEST['wpda_main_db_schema'] ) ) ) : '' );
1496 // input var okay.
1497 ?>"/>
1498 <input id="wpda_main_favourites" type="hidden" name="wpda_main_favourites"
1499 value="<?php
1500 echo ( isset( $_REQUEST['wpda_main_favourites'] ) ? esc_attr( sanitize_text_field( wp_unslash( $_REQUEST['wpda_main_favourites'] ) ) ) : '' );
1501 // input var okay.
1502 ?>"/>
1503 <?php
1504 wp_nonce_field( 'wpda-export-' . wp_json_encode( $this->table_name ), '_wpnonce', false );
1505 ?>
1506 <?php
1507 wp_nonce_field( "wpda-delete-{$this->table_name}", '_wpnonce2', false );
1508 ?>
1509 <?php
1510 if ( self::LIST_BASE_TABLE === $this->table_name ) {
1511 wp_nonce_field( 'wpda-drop-' . WPDA::get_current_user_login(), '_wpnonce3', false );
1512 wp_nonce_field( 'wpda-truncate-' . WPDA::get_current_user_login(), '_wpnonce4', false );
1513 }
1514 ?>
1515 <?php
1516 foreach ( $_REQUEST as $key => $value ) {
1517 if ( substr( $key, 0, 19 ) === 'wpda_search_column_' ) {
1518 if ( is_array( $value ) ) {
1519 foreach ( $value as $elem_key => $elem_value ) {
1520 echo '<input type="hidden" name="' . esc_attr( $key ) . '[]" value="' . esc_attr( $elem_value ) . '"/>';
1521 }
1522 } else {
1523 echo '<input type="hidden" name="' . esc_attr( $key ) . '" value="' . esc_attr( $value ) . '"/>';
1524 }
1525 }
1526 }
1527 ?>
1528 </form>
1529 </div>
1530 <script type='text/javascript'>
1531 jQuery(function() {
1532 jQuery( '.wpda_tooltip' ).tooltip();
1533 // Add toolbar events
1534 jQuery("#wpda_toolbar_icon_go_backup").on("click", function() {
1535 jQuery("#wpda_goto_backup").submit();
1536 });
1537 jQuery("#wpda_toolbar_icon_add_row").on("click", function() {
1538 jQuery("#wpda_new_row_table_name").val("<?php
1539 echo esc_attr( $this->table_name );
1540 ?>");
1541 jQuery("#wpda_new_row").submit();
1542 });
1543 jQuery("#wpda_toolbar_icon_add_publication").on("click", function() {
1544 jQuery("#wpda_new_publication_table_name").val("<?php
1545 echo esc_attr( $this->table_name );
1546 ?>");
1547 jQuery("#wpda_new_publication").submit();
1548 });
1549 });
1550 </script>
1551 <?php
1552 $this->bind_action_buttons();
1553 ?>
1554 <?php
1555 // Add custom code after the list table.
1556 do_action_ref_array( 'wpda_after_list_table', array($this) );
1557 }
1558
1559 /**
1560 * Placeholder for subclasses to add additional actions
1561 *
1562 * @return void
1563 */
1564 protected function add_header_actions() {
1565 }
1566
1567 /**
1568 * Bind javascript code to action buttons
1569 *
1570 * @since 1.6.2
1571 */
1572 protected function bind_action_buttons() {
1573 ?>
1574 <script type='text/javascript'>
1575 jQuery(function () {
1576 jQuery("#doaction").click(function () {
1577 return wpda_action_button();
1578 });
1579 jQuery("#doaction2").click(function () {
1580 return wpda_action_button();
1581 });
1582 });
1583 </script>
1584 <?php
1585 }
1586
1587 /**
1588 * Add button to page header
1589 *
1590 * By default "add new" and "import" buttons are added (depending on the settings). Overwrite this method to
1591 * add your own buttons.
1592 *
1593 * @since 1.0.1
1594 */
1595 protected function add_header_button() {
1596 if ( 'off' === $this->allow_insert ) {
1597 if ( null !== $this->wpda_import ) {
1598 $this->wpda_import->add_button();
1599 }
1600 } else {
1601 //phpcs:ignore - 8.1 proof
1602 if ( WPDA::is_wpda_table( $this->table_name ) || ('on' === WPDA::get_option( WPDA::OPTION_BE_ALLOW_INSERT ) && count( $this->wpda_list_columns->get_table_primary_key() )) > 0 ) {
1603 $storage_type = ( WPDA::is_wpda_table( $this->table_name ) ? __( 'respository', 'wp-data-access' ) : __( 'table', 'wp-data-access' ) );
1604 // Prepare url.
1605 if ( is_admin() ) {
1606 $url = "?page={$this->page}";
1607 } else {
1608 $url = '';
1609 }
1610 ?>
1611 <form
1612 method="post"
1613 action="<?php
1614 echo esc_attr( $url );
1615 ?>"
1616 style="display: inline-block; vertical-align: baseline;"
1617 >
1618 <div>
1619 <input type="hidden" name="wpdaschema_name"
1620 value="<?php
1621 echo esc_attr( $this->schema_name );
1622 // input var okay.
1623 ?>"/>
1624 <input type="hidden" name="table_name"
1625 value="<?php
1626 echo esc_attr( $this->table_name );
1627 // input var okay.
1628 ?>"/>
1629 <input type="hidden" name="action" value="new">
1630 <button type="submit" class="page-title-action wpda_tooltip"
1631 title="<?php
1632 echo sprintf( __( 'Add new %1$s to %2$s', 'wp-data-access' ), esc_attr( $this->_args['singular'] ), esc_attr( $storage_type ) );
1633 ?>"
1634 >
1635 <i class="fas fa-plus-circle wpda_icon_on_button"></i>
1636 <?php
1637 echo __( 'Add New', 'wp-data-access' );
1638 ?>
1639 </button>
1640 <?php
1641 // Add import button to title.
1642 if ( null !== $this->wpda_import ) {
1643 $this->wpda_import->add_button();
1644 }
1645 ?>
1646 </div>
1647 </form>
1648 <?php
1649 } else {
1650 // Add import button to title.
1651 if ( null !== $this->wpda_import ) {
1652 $this->wpda_import->add_button();
1653 }
1654 }
1655 }
1656 }
1657
1658 /**
1659 * Prepares the list of items for displaying
1660 *
1661 * Overwrites WP_List_Table::prepare_items()
1662 *
1663 * @since 1.0.0
1664 */
1665 public function prepare_items() {
1666 // Construct where clause with search values provided in the search box.
1667 // Result (where clause) is written to $this->where.
1668 $this->construct_where_clause();
1669 $this->process_bulk_action();
1670 if ( is_admin() ) {
1671 if ( $this->table_name === self::LIST_BASE_TABLE ) {
1672 $option = 'wpda_rows_per_page_' . str_replace( '.', '_', self::LIST_BASE_TABLE );
1673 } else {
1674 $option = 'wpda_rows_per_page_' . str_replace( '.', '_', $this->schema_name . $this->table_name );
1675 }
1676 $this->items_per_page = $this->get_items_per_page( $option, WPDA::get_option( WPDA::OPTION_BE_PAGINATION ) );
1677 } else {
1678 $this->items_per_page = WPDA::get_option( WPDA::OPTION_FE_PAGINATION );
1679 }
1680 $this->current_page = $this->get_pagenum();
1681 $total_items = $this->record_count();
1682 $total_pages = ceil( $total_items / $this->items_per_page );
1683 if ( $this->search_value !== $this->search_value_old ) {
1684 $this->current_page = 1;
1685 }
1686 if ( $this->current_page > $total_pages ) {
1687 $this->current_page = $total_pages;
1688 }
1689 $this->set_pagination_args( array(
1690 'total_items' => $total_items,
1691 'total_pages' => $total_pages,
1692 'per_page' => $this->items_per_page,
1693 ) );
1694 $this->get_rows();
1695 // Written to $this->items in base class (WP_List_Table).
1696 $columns = $this->get_columns();
1697 $hidden = $this->get_hidden_columns();
1698 if ( false === $hidden ) {
1699 if ( is_admin() ) {
1700 // List table in backend.
1701 if ( self::LIST_BASE_TABLE === $this->table_name ) {
1702 $table_name = str_replace( '.', '_', self::LIST_BASE_TABLE );
1703 } else {
1704 global $wpdb;
1705 if ( $this->schema_name === $wpdb->dbname && WPDA_CSV_Uploads_Model::get_base_table_name() === $this->table_name ) {
1706 $table_name = $this->table_name;
1707 // csv upload = exception.
1708 } else {
1709 $table_name = str_replace( '.', '_', $this->schema_name . $this->table_name );
1710 }
1711 }
1712 $hidden = get_user_option( WPDA_List_View::HIDDENCOLUMNS_PREFIX . get_current_screen()->id . $table_name );
1713 if ( false === $hidden ) {
1714 $hidden = array();
1715 }
1716 }
1717 }
1718 if ( !is_array( $hidden ) ) {
1719 $hidden = array();
1720 }
1721 $sortable = $this->get_sortable_columns();
1722 $primary = $this->get_primary_column();
1723 $this->_column_headers = array(
1724 $columns,
1725 $hidden,
1726 $sortable,
1727 $primary
1728 );
1729 }
1730
1731 /**
1732 * Build where clause
1733 *
1734 * Arguments might come from the URL so we need to check for SQL injection and use prepare. The resulting
1735 * where clause is written directly to member $this->where.
1736 *
1737 * @since 1.0.0
1738 */
1739 protected function construct_where_clause() {
1740 $whereclause = $this->get_where_clause();
1741 if ( '' !== $whereclause ) {
1742 $this->where = ( '' === $this->where ? " where ({$whereclause}) " : " {$this->where} and ({$whereclause}) " );
1743 }
1744 }
1745
1746 /**
1747 * Returns the where clause (uses filters if available)
1748 *
1749 * @return string
1750 */
1751 public function get_where_clause() {
1752 return WPDA::construct_where_clause(
1753 $this->schema_name,
1754 $this->table_name,
1755 $this->wpda_list_columns->get_searchable_table_columns(),
1756 $this->search_value,
1757 isset( $_REQUEST['wpda_c'] ) && 'true' === $_REQUEST['wpda_c']
1758 );
1759 }
1760
1761 /**
1762 * Process bulk actions
1763 *
1764 * Delete and bulk-delete actions use the primary key list as a reference. Before performing the actual delete
1765 * statement(s) we'll check the provided column names against the data dictionary. This will protect us against
1766 * SQL injection attacks.
1767 *
1768 * For export actions table names will not be checked here. They are handed over WPDA_Export where the validity
1769 * and access rights are checked anyway (to be safe).
1770 *
1771 * All actions can be processed only with a valid wpnonce value.
1772 *
1773 * To perform actions the user must have the appropriate rights.
1774 *
1775 * @since 1.0.0
1776 */
1777 public function process_bulk_action() {
1778 switch ( $this->current_action() ) {
1779 case 'delete':
1780 // Check access rights.
1781 if ( 'on' !== $this->allow_delete ) {
1782 // Deleting records from list table is not allowed.
1783 wp_die( __( 'ERROR: Not authorized [delete not allowed]', 'wp-data-access' ) );
1784 }
1785 // Prepare wp_nonce action security check.
1786 $wp_nonce_action = "wpda-delete-{$this->table_name}";
1787 $row_to_be_deleted = array();
1788 // Gonna hold the row to be deleted.
1789 $i = 0;
1790 // Index, necessary for multi column keys.
1791 // Check all key columns.
1792 foreach ( $this->wpda_list_columns->get_table_primary_key() as $key ) {
1793 // Check if key is available.
1794 if ( !isset( $_REQUEST[$key] ) ) {
1795 // input var okay.
1796 wp_die( __( 'ERROR: Invalid URL [missing primary key values]', 'wp-data-access' ) );
1797 }
1798 // Write key value pair to array.
1799 $row_to_be_deleted[$i]['key'] = $key;
1800 $row_to_be_deleted[$i]['value'] = sanitize_text_field( wp_unslash( $_REQUEST[$key] ) );
1801 // input var okay.
1802 $i++;
1803 // Add key values to wp_nonce action.
1804 $wp_nonce_action .= '-' . sanitize_text_field( wp_unslash( $_REQUEST[$key] ) );
1805 // input var okay.
1806 }
1807 // Check if delete is allowed.
1808 $wp_nonce = ( isset( $_REQUEST['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) ) : '' );
1809 // input var okay.
1810 if ( !wp_verify_nonce( $wp_nonce, $wp_nonce_action ) ) {
1811 wp_die( __( 'ERROR: Not authorized', 'wp-data-access' ) );
1812 }
1813 // All key column values available: delete record.
1814 // Prepare named array for delete operation.
1815 $next_row_to_be_deleted = array();
1816 $count_rows = count( $row_to_be_deleted );
1817 //phpcs:ignore - 8.1 proof
1818 for ($i = 0; $i < $count_rows; $i++) {
1819 $next_row_to_be_deleted[$row_to_be_deleted[$i]['key']] = $row_to_be_deleted[$i]['value'];
1820 }
1821 $engine = WPDA::get_table_engine( $this->schema_name, $this->table_name );
1822 if ( $this->delete_row( $next_row_to_be_deleted, $engine ) ) {
1823 $msg = new WPDA_Message_Box(array(
1824 'message_text' => __( 'Row deleted', 'wp-data-access' ),
1825 ));
1826 $msg->box();
1827 } else {
1828 $msg = new WPDA_Message_Box(array(
1829 'message_text' => __( 'Could not delete row', 'wp-data-access' ),
1830 'message_type' => 'error',
1831 'message_is_dismissible' => false,
1832 ));
1833 $msg->box();
1834 }
1835 break;
1836 case 'bulk-delete':
1837 // Check access rights.
1838 if ( 'on' !== $this->allow_delete ) {
1839 // Deleting records from list table is not allowed.
1840 die( __( 'ERROR: Not authorized [delete not allowed]', 'wp-data-access' ) );
1841 }
1842 // We first need to check if all the necessary information is available.
1843 if ( !isset( $_REQUEST['bulk-selected'] ) ) {
1844 // input var okay.
1845 // Nothing to delete.
1846 $msg = new WPDA_Message_Box(array(
1847 'message_text' => __( 'Nothing to delete', 'wp-data-access' ),
1848 ));
1849 $msg->box();
1850 return;
1851 }
1852 // Check if delete is allowed.
1853 $wp_nonce_action = "wpda-delete-{$this->table_name}";
1854 $wp_nonce = ( isset( $_REQUEST['_wpnonce2'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce2'] ) ) : '' );
1855 // input var okay.
1856 if ( !wp_verify_nonce( $wp_nonce, $wp_nonce_action ) ) {
1857 die( __( 'ERROR: Not authorized', 'wp-data-access' ) );
1858 }
1859 $bulk_rows = (array) $_REQUEST['bulk-selected'];
1860 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
1861 $no_rows = count( $bulk_rows );
1862 // # rows to be deleted. //phpcs:ignore - 8.1 proof being safe
1863 $rows_to_be_deleted = array();
1864 // Gonna hold rows to be deleted.
1865 for ($i = 0; $i < $no_rows; $i++) {
1866 // Write "json" to named array. Need to strip slashes twice. Once for the normal conversion
1867 // and once extra for the pre-conversion of double quotes in method column_cb().
1868 $row_object = json_decode( stripslashes( stripslashes( $bulk_rows[$i] ) ), true );
1869 if ( $row_object ) {
1870 $j = 0;
1871 // Index used to build array.
1872 // Check all key columns.
1873 foreach ( $this->wpda_list_columns->get_table_primary_key() as $key ) {
1874 // Check if key is available.
1875 if ( !isset( $row_object[$key] ) ) {
1876 wp_die( __( 'ERROR: Invalid URL [missing primary key values]', 'wp-data-access' ) );
1877 }
1878 // Write key value pair to array.
1879 $rows_to_be_deleted[$i][$j]['key'] = $key;
1880 $rows_to_be_deleted[$i][$j]['value'] = $row_object[$key];
1881 $j++;
1882 }
1883 }
1884 }
1885 // Looks like everything is there. Delete records from table...
1886 $no_key_cols = count( $this->wpda_list_columns->get_table_primary_key() );
1887 //phpcs:ignore - 8.1 proof
1888 $rows_successfully_deleted = 0;
1889 // Number of rows successfully deleted.
1890 $rows_with_errors = 0;
1891 // Number of rows that could not be deleted.
1892 $engine = WPDA::get_table_engine( $this->schema_name, $this->table_name );
1893 for ($i = 0; $i < $no_rows; $i++) {
1894 // Prepare named array for delete operation.
1895 $next_row_to_be_deleted = array();
1896 $row_found = true;
1897 for ($j = 0; $j < $no_key_cols; $j++) {
1898 if ( isset( $rows_to_be_deleted[$i][$j]['key'] ) ) {
1899 $next_row_to_be_deleted[$rows_to_be_deleted[$i][$j]['key']] = $rows_to_be_deleted[$i][$j]['value'];
1900 } else {
1901 $row_found = false;
1902 }
1903 }
1904 if ( $row_found ) {
1905 if ( $this->delete_row( $next_row_to_be_deleted, $engine ) ) {
1906 // Row(s) successfully deleted.
1907 $rows_successfully_deleted++;
1908 } else {
1909 // An error occurred during the delete operation: increase error count.
1910 $rows_with_errors++;
1911 }
1912 } else {
1913 // An error occurred during the delete operation: increase error count.
1914 $rows_with_errors++;
1915 }
1916 }
1917 // Inform user about the results of the operation.
1918 $message = '';
1919 if ( 1 === $rows_successfully_deleted ) {
1920 $message = __( 'Row deleted', 'wp-data-access' );
1921 } elseif ( $rows_successfully_deleted > 1 ) {
1922 $message = "{$rows_successfully_deleted} " . __( 'rows deleted', 'wp-data-access' );
1923 }
1924 if ( '' !== $message ) {
1925 $msg = new WPDA_Message_Box(array(
1926 'message_text' => $message,
1927 ));
1928 $msg->box();
1929 }
1930 $message = '';
1931 if ( $rows_with_errors > 0 ) {
1932 $message = __( 'Not all rows have been deleted', 'wp-data-access' );
1933 }
1934 if ( '' !== $message ) {
1935 $msg = new WPDA_Message_Box(array(
1936 'message_text' => $message,
1937 'message_type' => 'error',
1938 'message_is_dismissible' => false,
1939 ));
1940 $msg->box();
1941 }
1942 break;
1943 case 'bulk-export':
1944 case 'bulk-export-xml':
1945 case 'bulk-export-json':
1946 case 'bulk-export-excel':
1947 case 'bulk-export-csv':
1948 // Check access rights.
1949 if ( !WPDA::is_wpda_table( $this->table_name ) ) {
1950 if ( 'on' !== WPDA::get_option( WPDA::OPTION_BE_EXPORT_ROWS ) ) {
1951 // Exporting rows from list table is not allowed.
1952 die( __( 'ERROR: Not authorized [export not allowed]', 'wp-data-access' ) );
1953 }
1954 }
1955 // We first need to check if all the necessary information is available.
1956 if ( !isset( $_REQUEST['bulk-selected'] ) ) {
1957 // input var okay.
1958 // Nothing to export.
1959 $msg = new WPDA_Message_Box(array(
1960 'message_text' => __( 'Nothing to export', 'wp-data-access' ),
1961 ));
1962 $msg->box();
1963 return;
1964 }
1965 // Check if export is allowed.
1966 $wp_nonce = ( isset( $_REQUEST['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) ) : '' );
1967 // input var okay.
1968 if ( !wp_verify_nonce( $wp_nonce, 'wpda-export-' . wp_json_encode( $this->table_name ) ) ) {
1969 die( __( 'ERROR: Not authorized', 'wp-data-access' ) );
1970 }
1971 $bulk_rows = (array) $_REQUEST['bulk-selected'];
1972 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
1973 $no_rows = count( $bulk_rows );
1974 // # rows to be exported. //phpcs:ignore - 8.1 proof being safe
1975 $format_type = '';
1976 switch ( $this->current_action() ) {
1977 case 'bulk-export-xml':
1978 $format_type = 'xml';
1979 break;
1980 case 'bulk-export-json':
1981 $format_type = 'json';
1982 break;
1983 case 'bulk-export-excel':
1984 $format_type = 'excel';
1985 break;
1986 case 'bulk-export-csv':
1987 $format_type = 'csv';
1988 }
1989 if ( 'diehard' === $this->page && null !== $this->pid ) {
1990 $wp_nonce = wp_create_nonce( "wpda-export-{$this->pid}" );
1991 $submit_schema_name = '';
1992 $submit_table_name = '';
1993 } else {
1994 $submit_schema_name = $this->schema_name;
1995 $submit_table_name = $this->table_name;
1996 }
1997 $j = 0;
1998 $columns = array();
1999 for ($i = 0; $i < $no_rows; $i++) {
2000 // Write "json" to named array. Need to strip slashes twice. Once for the normal conversion
2001 // and once extra for the pre-conversion of double quotes in method column_cb().
2002 $row_object = json_decode( stripslashes( stripslashes( $bulk_rows[$i] ) ), true );
2003 if ( $row_object ) {
2004 // Check all key columns.
2005 foreach ( $this->wpda_list_columns->get_table_primary_key() as $key ) {
2006 // Check if key is available.
2007 if ( !isset( $row_object[$key] ) ) {
2008 wp_die( __( 'ERROR: Invalid URL', 'wp-data-access' ) );
2009 }
2010 if ( !isset( $columns[$key] ) ) {
2011 $columns[$key] = array();
2012 }
2013 $columns[$key][] = $row_object[$key];
2014 }
2015 $j++;
2016 }
2017 }
2018 ?>
2019 <script type="application/javascript">
2020 jQuery(function() {
2021 wpda_row_export(
2022 '<?php
2023 echo esc_attr( $this->pid );
2024 ?>',
2025 '<?php
2026 echo esc_attr( $submit_schema_name );
2027 ?>',
2028 '<?php
2029 echo esc_attr( $submit_table_name );
2030 ?>',
2031 '<?php
2032 echo esc_attr( $wp_nonce );
2033 ?>',
2034 '<?php
2035 echo esc_attr( $format_type );
2036 ?>',
2037 'off',
2038 'off',
2039 'off',
2040 '<?php
2041 echo wp_json_encode( $columns );
2042 ?>'
2043 );
2044 });
2045 </script>
2046 <?php
2047 }
2048 }
2049
2050 /**
2051 * Delete record from database table
2052 *
2053 * The table must have a primary key and the values for all primary key columns must be provided in the
2054 * request. The where clause must be a named array in format: ['column_name'] = 'value'
2055 *
2056 * @param string $where where clause.
2057 * @param string $engine connect or empty.
2058 *
2059 * @return mixed
2060 * @since 1.0.0
2061 */
2062 public function delete_row( $where, $engine = '' ) {
2063 $wpdadb = WPDADB::get_db_connection( $this->schema_name );
2064 if ( null === $wpdadb ) {
2065 if ( is_admin() ) {
2066 wp_die( sprintf( __( 'ERROR - Remote database %s not available', 'wp-data-access' ), esc_attr( $this->schema_name ) ) );
2067 } else {
2068 die( sprintf( __( 'ERROR - Remote database %s not available', 'wp-data-access' ), esc_attr( $this->schema_name ) ) );
2069 }
2070 }
2071 $row_deleted = $wpdadb->delete( $this->table_name, $where );
2072 if ( 'connect' === strtolower( $engine ) ) {
2073 // Connect engine does not return number of rows deleted.
2074 // Presuming delete was successful when no error message was returned.
2075 return '' === $wpdadb->last_error;
2076 }
2077 return $row_deleted;
2078 }
2079
2080 /**
2081 * Number of records in database table
2082 *
2083 * Returns the number of records in the database table. Where clause must be prepared in advance and written
2084 * to $this->where ({@see WPDA_List_Table::construct_where_clause()})
2085 *
2086 * @return null|string Number of rows in the current table
2087 * @since 1.0.0
2088 */
2089 public function record_count() {
2090 if ( !$this->row_count_estimate['do_real_count'] && '' === $this->where ) {
2091 // Use estimate row count.
2092 return $this->table_rows;
2093 }
2094 // Get real row count.
2095 $wpdadb = WPDADB::get_db_connection( $this->schema_name );
2096 if ( null === $wpdadb ) {
2097 if ( is_admin() ) {
2098 wp_die( sprintf( __( 'ERROR - Remote database %s not available', 'wp-data-access' ), esc_attr( $this->schema_name ) ) );
2099 } else {
2100 die( sprintf( __( 'ERROR - Remote database %s not available', 'wp-data-access' ), esc_attr( $this->schema_name ) ) );
2101 }
2102 }
2103 if ( '' === $this->schema_name ) {
2104 $query = "\n\t\t\t\t\tselect count(*)\n\t\t\t\t\tfrom `{$this->table_name}`\n\t\t\t\t\t{$this->where}\n\t\t\t\t";
2105 } else {
2106 if ( self::LIST_BASE_TABLE === $this->table_name ) {
2107 $query = "\n\t\t\t\t\t\tselect count(*)\n\t\t\t\t\t\tfrom {$this->table_name}\n\t\t\t\t\t\t{$this->where}\n\t\t\t\t\t";
2108 } else {
2109 $query = "\n\t\t\t\t\t\tselect count(*)\n\t\t\t\t\t\tfrom `{$wpdadb->dbname}`.`{$this->table_name}`\n\t\t\t\t\t\t{$this->where}\n\t\t\t\t\t";
2110 }
2111 }
2112 return $wpdadb->get_var( $query );
2113 // phpcs:ignore Standard.Category.SniffName.ErrorCode
2114 }
2115
2116 /**
2117 * Perform query to retrieve rows from database
2118 *
2119 * Where clause must be prepared in advance and written to $this->where
2120 * ({@see WPDA_List_Table::construct_where_clause()}).
2121 *
2122 * No return value. Result is directly written to $this->items (base class member).
2123 *
2124 * @since 1.0.0
2125 */
2126 public function get_rows() {
2127 $wpdadb = WPDADB::get_db_connection( $this->schema_name );
2128 if ( null === $wpdadb ) {
2129 if ( is_admin() ) {
2130 wp_die( sprintf( __( 'ERROR - Remote database %s not available', 'wp-data-access' ), esc_attr( $this->schema_name ) ) );
2131 } else {
2132 die( sprintf( __( 'ERROR - Remote database %s not available', 'wp-data-access' ), esc_attr( $this->schema_name ) ) );
2133 }
2134 }
2135 // Selected columns cannot be changed by the user at this time. No check for SQL injection needed now.
2136 // This might change in the future when users are allowed to change or set this value. A method named
2137 // column_exists() in WPDA_Dictionary_Checks is already available for this purpose.
2138 $selected_columns = '*';
2139 if ( isset( $this->columns_queried ) ) {
2140 $selected_columns = implode( ',', $this->columns_queried );
2141 }
2142 if ( isset( $this->wpda_table_settings->hyperlinks ) ) {
2143 $i = 0;
2144 foreach ( $this->wpda_table_settings->hyperlinks as $hyperlink ) {
2145 $skip_column = false;
2146 if ( null !== $this->wpda_project_table_settings ) {
2147 $hyperlink_label = $hyperlink->hyperlink_label;
2148 if ( !property_exists( $this, 'is_child' ) ) {
2149 if ( isset( $this->wpda_project_table_settings->hyperlinks_parent->{$hyperlink_label} ) && !$this->wpda_project_table_settings->hyperlinks_parent->{$hyperlink_label} ) {
2150 $skip_column = true;
2151 }
2152 } else {
2153 if ( isset( $this->wpda_project_table_settings->hyperlinks_child->{$hyperlink_label} ) && !$this->wpda_project_table_settings->hyperlinks_child->{$hyperlink_label} ) {
2154 $skip_column = true;
2155 }
2156 }
2157 }
2158 if ( !$skip_column && isset( $hyperlink->hyperlink_list ) && true === $hyperlink->hyperlink_list ) {
2159 $hyperlink_label = ( isset( $hyperlink->hyperlink_label ) ? $hyperlink->hyperlink_label : '' );
2160 $hyperlink_html = ( isset( $hyperlink->hyperlink_html ) ? $hyperlink->hyperlink_html : '' );
2161 if ( '' !== $hyperlink_label && '' !== $hyperlink_html ) {
2162 // Add hyperlink columns (details are added in method column_default).
2163 $selected_columns .= ", '' as wpda_hyperlink_{$i}";
2164 }
2165 }
2166 $i++;
2167 }
2168 }
2169 if ( '' === $this->schema_name ) {
2170 $query = "\n\t\t\t\t\tselect {$selected_columns}\n\t\t\t\t\tfrom `{$this->table_name}`\n\t\t\t\t\t{$this->where}\n\t\t\t\t";
2171 } else {
2172 if ( self::LIST_BASE_TABLE === $this->table_name ) {
2173 $query = "\n\t\t\t\t\t\tselect {$selected_columns}\n\t\t\t\t\t\tfrom {$this->table_name}\n\t\t\t\t\t\t{$this->where}\n\t\t\t\t\t";
2174 } else {
2175 $query = "\n\t\t\t\t\t\tselect {$selected_columns}\n\t\t\t\t\t\tfrom `{$wpdadb->dbname}`.`{$this->table_name}`\n\t\t\t\t\t\t{$this->where}\n\t\t\t\t\t";
2176 }
2177 }
2178 $query .= $this->get_order_by();
2179 // Add order by.
2180 $query .= " limit {$this->items_per_page}";
2181 $offset = ($this->current_page - 1) * $this->items_per_page;
2182 if ( $offset > 0 ) {
2183 $query .= " offset {$offset}";
2184 }
2185 // Debug query.
2186 // var_dump( $query );
2187 $this->items = $wpdadb->get_results( $query, 'ARRAY_A' );
2188 // phpcs:ignore Standard.Category.SniffName.ErrorCode
2189 }
2190
2191 /**
2192 * Get order by
2193 *
2194 * @return string|void
2195 */
2196 protected function get_order_by() {
2197 if ( !empty( $_REQUEST['orderby'] ) ) {
2198 $orderby_arg = sanitize_sql_orderby( wp_unslash( $_REQUEST['orderby'] ) );
2199 // input var okay.
2200 if ( !empty( $_REQUEST['order'] ) ) {
2201 $order_arg = sanitize_text_field( wp_unslash( $_REQUEST['order'] ) );
2202 // input var okay.
2203 } else {
2204 $order_arg = '';
2205 }
2206 $columns = $this->get_sortable_columns();
2207 // Check column name for SQL injection.
2208 if ( isset( $columns[$orderby_arg] ) || $this->wpda_data_dictionary->column_exists( $orderby_arg ) ) {
2209 // Column name exists in current table, safely continue...
2210 $orderby = " order by {$orderby_arg}";
2211 // Prevent SQL injection for order. If 'desc' is found result will be ordered desc. In all other
2212 // cases we'll order asc.
2213 $orderby .= ( strtolower( trim( $order_arg ) ) === 'desc' ? ' desc' : ' asc' );
2214 return $orderby;
2215 } else {
2216 // The user provided a column name which is not in the table. Most probably the result of a
2217 // SQL injection attack, so let's terminate.
2218 wp_die( __( 'ERROR: Invalid URL [invalid column name]', 'wp-data-access' ) );
2219 }
2220 } else {
2221 return '';
2222 }
2223 }
2224
2225 /**
2226 * Return an associative array of columns
2227 *
2228 * @return array
2229 * @since 1.0.0
2230 */
2231 public function get_columns() {
2232 if ( null !== $this->wpda_cached_columns && is_array( $this->wpda_cached_columns ) ) {
2233 return $this->wpda_cached_columns;
2234 }
2235 $columns = array();
2236 if ( $this->bulk_actions_enabled ) {
2237 if ( !empty( $this->wpda_list_columns->get_table_primary_key() ) ) {
2238 // Tables has primary key: bulk actions allowed!
2239 // Primary key is used to ensure uniqueness.
2240 $actions = $this->get_bulk_actions();
2241 if ( is_array( $actions ) && 0 < count( $actions ) ) {
2242 //phpcs:ignore - 8.1 proof
2243 $columns = array(
2244 'cb' => '<input type="checkbox" />',
2245 );
2246 }
2247 }
2248 }
2249 $columnlist = $this->wpda_list_columns->get_table_column_headers();
2250 foreach ( $columnlist as $key => $value ) {
2251 $columns[$key] = $value;
2252 // Check for alternative column header.
2253 if ( isset( $this->column_headers[$key] ) ) {
2254 // Alternative header found: use it.
2255 $columns[$key] = $this->column_headers[$key];
2256 } else {
2257 // Default behaviour: get column header from generated label.
2258 $columns[$key] = $this->wpda_list_columns->get_column_label( $key );
2259 }
2260 }
2261 if ( isset( $this->wpda_table_settings->hyperlinks ) ) {
2262 $i = 0;
2263 foreach ( $this->wpda_table_settings->hyperlinks as $hyperlink ) {
2264 if ( isset( $hyperlink->hyperlink_list ) && true === $hyperlink->hyperlink_list ) {
2265 $skip_column = false;
2266 if ( null !== $this->wpda_project_table_settings ) {
2267 $hyperlink_label = $hyperlink->hyperlink_label;
2268 if ( !property_exists( $this, 'is_child' ) ) {
2269 if ( isset( $this->wpda_project_table_settings->hyperlinks_parent->{$hyperlink_label} ) && !$this->wpda_project_table_settings->hyperlinks_parent->{$hyperlink_label} ) {
2270 $skip_column = true;
2271 }
2272 } else {
2273 if ( isset( $this->wpda_project_table_settings->hyperlinks_child->{$hyperlink_label} ) && !$this->wpda_project_table_settings->hyperlinks_child->{$hyperlink_label} ) {
2274 $skip_column = true;
2275 }
2276 }
2277 }
2278 if ( !$skip_column ) {
2279 $hyperlink_label = ( isset( $hyperlink->hyperlink_label ) ? $hyperlink->hyperlink_label : '' );
2280 $columns["wpda_hyperlink_{$i}"] = $hyperlink_label;
2281 // Add hyperlink label.
2282 }
2283 }
2284 $i++;
2285 }
2286 }
2287 // Cache columns.
2288 $this->wpda_cached_columns = $columns;
2289 return $columns;
2290 }
2291
2292 /**
2293 * List of columns to make sortable
2294 *
2295 * @return array
2296 * @since 1.0.0
2297 */
2298 public function get_sortable_columns() {
2299 $columns = array();
2300 // Get column names from result set.
2301 if ( $this->items ) {
2302 foreach ( $this->items[0] as $key => $value ) {
2303 if ( 'wpda_hyperlink_' !== substr( $key, 0, 15 ) ) {
2304 $columns[$key] = array($key, false);
2305 }
2306 }
2307 }
2308 return $columns;
2309 }
2310
2311 /**
2312 * Display the search box
2313 *
2314 * @param string $text The 'submit' button label.
2315 * @param string $input_id ID attribute value for the search input field.
2316 *
2317 * @since 1.0.0
2318 */
2319 public function search_box( $text, $input_id ) {
2320 $input_id = $input_id . '-search-input';
2321 // Allow external user to add search actions (like icons) to the search box.
2322 do_action(
2323 'wpda_add_search_actions',
2324 $this->schema_name,
2325 $this->table_name,
2326 $this->wpda_table_settings,
2327 $this->wpda_list_columns
2328 );
2329 ?>
2330
2331 <p class="search-box" <?php
2332 echo ( !$this->has_items() ? 'style="padding-bottom:10px;"' : '' );
2333 ?>>
2334 <input type="search" id="<?php
2335 echo esc_attr( $input_id );
2336 ?>"
2337 name="<?php
2338 echo esc_attr( $this->search_item_name );
2339 ?>"
2340 value="<?php
2341 echo esc_attr( $this->search_value );
2342 ?>"/>
2343 <?php
2344 if ( is_admin() ) {
2345 submit_button(
2346 $text,
2347 '',
2348 '',
2349 false,
2350 array(
2351 'id' => 'search-submit',
2352 )
2353 );
2354 } else {
2355 wpdadiehard_submit_button(
2356 $text,
2357 '',
2358 '',
2359 false,
2360 array(
2361 'id' => 'search-submit',
2362 )
2363 );
2364 }
2365 ?>
2366 <input type="hidden" name="<?php
2367 echo esc_attr( $this->search_item_name );
2368 ?>_old_value"
2369 value="<?php
2370 echo esc_attr( $this->search_value );
2371 ?>"/>
2372 </p>
2373
2374 <?php
2375 // Allow external user to add search html (like extra items) below the search box.
2376 do_action(
2377 'wpda_add_search_filter',
2378 $this->schema_name,
2379 $this->table_name,
2380 $this->wpda_table_settings,
2381 $this->wpda_list_columns
2382 );
2383 }
2384
2385 /**
2386 * Get search value (entered by the user or taken from cookie).
2387 *
2388 * @return string
2389 * @since 1.5.0
2390 */
2391 protected function get_search_value() {
2392 if ( 'off' === WPDA::get_option( WPDA::OPTION_BE_REMEMBER_SEARCH ) ) {
2393 if ( isset( $_REQUEST[$this->search_item_name] ) ) {
2394 return wp_filter_nohtml_kses( wp_unslash( $_REQUEST[$this->search_item_name] ) );
2395 // input var okay.
2396 }
2397 }
2398 if ( 'wpda_wpdp_' === substr( $this->page, 0, 10 ) ) {
2399 $cookie_name = $this->page;
2400 if ( isset( $_REQUEST['child_request'] ) ) {
2401 $cookie_name = 'NOCOOKIESFORCHILDREQUESTS';
2402 // No search values stored for child tables.
2403 }
2404 foreach ( $_REQUEST as $key => $val ) {
2405 if ( strpos( $key, 'WPDA_PARENT_KEY*' ) === 0 ) {
2406 $cookie_name = 'NOCOOKIESFORCHILDREQUESTS';
2407 // No search values stored for child tables.
2408 }
2409 }
2410 } else {
2411 $cookie_name = $this->page . '_search_' . str_replace( '.', '_', $this->table_name );
2412 }
2413 if ( isset( $_REQUEST[$this->search_item_name] ) && '' !== $_REQUEST[$this->search_item_name] ) {
2414 // input var okay.
2415 return wp_filter_nohtml_kses( wp_unslash( $_REQUEST[$this->search_item_name] ) );
2416 // input var okay.
2417 } elseif ( isset( $_COOKIE[$cookie_name] ) ) {
2418 return wp_filter_nohtml_kses( wp_unslash( $_COOKIE[$cookie_name] ) );
2419 // input var okay.
2420 } else {
2421 return null;
2422 }
2423 }
2424
2425 /**
2426 * Print column headers
2427 *
2428 * Overriding original method print_column_headers to support post instead of get.
2429 * Changes are marked!
2430 *
2431 * @param boolean $with_id Whether to set the id attribute or not.
2432 *
2433 * @since 1.0.0
2434 *
2435 * @staticvar $cb_counter int
2436 */
2437 public function print_column_headers( $with_id = true ) {
2438 list( $columns, $hidden, $sortable, $primary ) = $this->get_column_info();
2439 //phpcs:ignore - 8.1 proof
2440 // *********************
2441 // *** BEGIN CHANGES ***
2442 // *********************
2443 // Code removed.
2444 // *******************
2445 // *** END CHANGES ***
2446 // *******************
2447 if ( isset( $_REQUEST['orderby'] ) ) {
2448 $current_orderby = sanitize_text_field( wp_unslash( $_REQUEST['orderby'] ) );
2449 // input var okay.
2450 } else {
2451 $current_orderby = '';
2452 }
2453 if ( isset( $_REQUEST['order'] ) && 'desc' === $_REQUEST['order'] ) {
2454 // input var okay.
2455 $current_order = 'desc';
2456 } else {
2457 $current_order = 'asc';
2458 }
2459 if ( !empty( $columns['cb'] ) ) {
2460 static $cb_counter = 1;
2461 $columns['cb'] = '<label class="screen-reader-text" for="cb-select-all-' . $cb_counter . '">' . __( 'Select All' ) . '</label>' . '<input id="cb-select-all-' . $cb_counter . '" type="checkbox" />';
2462 $cb_counter++;
2463 }
2464 foreach ( $columns as $column_key => $column_display_name ) {
2465 $class = array('manage-column', "column-{$column_key}");
2466 if ( in_array( $column_key, (array) $hidden ) ) {
2467 //phpcs:ignore - 8.1 proof
2468 $class[] = 'hidden';
2469 }
2470 if ( 'cb' === $column_key ) {
2471 $class[] = 'check-column';
2472 } elseif ( in_array( $column_key, array('posts', 'comments', 'links') ) ) {
2473 $class[] = 'num';
2474 }
2475 if ( $column_key === $primary ) {
2476 $class[] = 'column-primary';
2477 }
2478 if ( isset( $sortable[$column_key] ) ) {
2479 list( $orderby, $desc_first ) = (array) $sortable[$column_key];
2480 //phpcs:ignore - 8.1 proof
2481 if ( $current_orderby === $orderby ) {
2482 $order = ( 'asc' === $current_order ? 'desc' : 'asc' );
2483 $class[] = 'sorted';
2484 $class[] = $current_order;
2485 } else {
2486 $order = ( $desc_first ? 'desc' : 'asc' );
2487 $class[] = 'sortable';
2488 $class[] = ( $desc_first ? 'asc' : 'desc' );
2489 }
2490 // *********************
2491 // *** BEGIN CHANGES ***
2492 // *********************
2493 // Code removed.
2494 // *******************
2495 // *** END CHANGES ***
2496 // *******************
2497 }
2498 $tag = ( 'cb' === $column_key ? 'td' : 'th' );
2499 $scope = ( 'th' === $tag ? 'scope="col"' : '' );
2500 $id = ( $with_id ? "id='{$column_key}'" : '' );
2501 if ( !empty( $class ) ) {
2502 $class = "class='" . join( ' ', $class ) . "'";
2503 }
2504 // *********************
2505 // *** BEGIN CHANGES ***
2506 // *********************
2507 echo '<' . esc_attr( $tag ) . ' ' . wp_kses_data( $scope ) . ' ' . wp_kses_data( $id ) . ' ' . wp_kses_data( $class ) . '>';
2508 if ( isset( $sortable[$column_key] ) ) {
2509 ?>
2510 <a href="javascript:void(0)"
2511 onclick="jQuery('#wpda_main_form_orderby').val('<?php
2512 echo esc_attr( $orderby );
2513 ?>'); jQuery('#wpda_main_form_order').val('<?php
2514 echo esc_attr( $order );
2515 ?>'); jQuery('#wpda_main_form').submit();">
2516 <span><?php
2517 echo wp_kses_data( $column_display_name );
2518 ?></span><span
2519 class="sorting-indicator"></span>
2520 </a>
2521 <?php
2522 } else {
2523 echo wp_kses( $column_display_name, array(
2524 'input' => array(
2525 'id' => array(),
2526 'type' => array(),
2527 ),
2528 'label' => array(
2529 'class' => array(),
2530 'for' => array(),
2531 ),
2532 ) );
2533 }
2534 echo '</' . esc_attr( $tag ) . '>';
2535 // *******************
2536 // *** END CHANGES ***
2537 // *******************
2538 }
2539 }
2540
2541 /**
2542 * Returns an associative array containing the bulk action
2543 *
2544 * @return array
2545 * @since 1.0.0
2546 */
2547 public function get_bulk_actions() {
2548 if ( !$this->bulk_actions_enabled ) {
2549 // Bulk actions disabled.
2550 return '';
2551 }
2552 if ( empty( $this->wpda_list_columns->get_table_primary_key() ) ) {
2553 // Tables has no primary key: no bulk actions allowed!
2554 // Primary key is necessary to ensure uniqueness.
2555 return '';
2556 }
2557 $actions = array();
2558 if ( 'on' === $this->allow_delete ) {
2559 $actions = array(
2560 'bulk-delete' => __( 'Delete Permanently', 'wp-data-access' ),
2561 );
2562 }
2563 if ( $this->bulk_export_enabled && (WPDA::is_wpda_table( $this->table_name ) || WPDA::get_option( WPDA::OPTION_BE_EXPORT_ROWS ) === 'on') ) {
2564 if ( 'diehard' !== $this->page ) {
2565 // Not allowed on web page.
2566 $actions['bulk-export'] = __( 'Export to SQL', 'wp-data-access' );
2567 }
2568 $actions['bulk-export-xml'] = __( 'Export to XML', 'wp-data-access' );
2569 $actions['bulk-export-json'] = __( 'Export to JSON', 'wp-data-access' );
2570 $actions['bulk-export-excel'] = __( 'Export to Excel', 'wp-data-access' );
2571 $actions['bulk-export-csv'] = __( 'Export to CSV', 'wp-data-access' );
2572 }
2573 return $actions;
2574 }
2575
2576 /**
2577 * Generates the table navigation
2578 *
2579 * Generates the table navigation above or bellow the table and removes the
2580 * _wp_http_referrer and _wpnonce because it generates a error about URL too large.
2581 *
2582 * @param string $which CSS Class name.
2583 *
2584 * @return void
2585 * @since 1.0.0
2586 */
2587 protected function display_tablenav( $which ) {
2588 if ( !$this->hide_navigation && $this->items ) {
2589 ?>
2590 <div class="tablenav <?php
2591 echo esc_attr( $which );
2592 ?>">
2593 <div class="alignleft actions">
2594 <?php
2595 $this->bulk_actions( $which );
2596 ?>
2597 </div>
2598 <span id="wpda_row_export_anchor"></span>
2599 <?php
2600 $this->add_full_table_downloads();
2601 $this->extra_tablenav( $which );
2602 $this->pagination( $which );
2603 ?>
2604 <br class="clear"/>
2605 </div>
2606 <?php
2607 } else {
2608 ?>
2609 <br class="clear"/>
2610 <?php
2611 }
2612 }
2613
2614 /**
2615 * Add full export to CSV and JSON buttons
2616 * Needs to be granted in project page configuration
2617 */
2618 protected function add_full_table_downloads() {
2619 }
2620
2621 // Override to add arguments to CSV and JSON full table exports.
2622 protected function add_full_table_downloads_add_args() {
2623 }
2624
2625 /**
2626 * Display the pagination
2627 *
2628 * Overriding original method pagination to support post instead of get.
2629 * Changes are marked!
2630 *
2631 * @param string $which CSS Class name.
2632 *
2633 * @since 1.0.0
2634 */
2635 protected function pagination( $which ) {
2636 if ( empty( $this->_pagination_args ) ) {
2637 return;
2638 }
2639 $total_items = $this->_pagination_args['total_items'];
2640 $total_pages = $this->_pagination_args['total_pages'];
2641 $infinite_scroll = false;
2642 if ( isset( $this->_pagination_args['infinite_scroll'] ) ) {
2643 $infinite_scroll = $this->_pagination_args['infinite_scroll'];
2644 }
2645 if ( 'top' === $which && $total_pages > 1 ) {
2646 $this->screen->render_screen_reader_content( 'heading_pagination' );
2647 }
2648 // Add estimate character if row_count_estimate is enabled.
2649 $estimate = ( $this->row_count_estimate['is_estimate'] ? '~' : '' );
2650 /* translators: %s: number of items (2x) */
2651 $output = '<span class="displaying-num">' . sprintf( _n( '%s item', '%s items', $total_items ), $estimate . number_format_i18n( $total_items ) ) . '</span>';
2652 $current = $this->get_pagenum();
2653 if ( $this->search_value !== $this->search_value_old ) {
2654 $current = 1;
2655 }
2656 // *********************
2657 // *** BEGIN CHANGES ***
2658 // *********************
2659 // Code removed.
2660 // *******************
2661 // *** END CHANGES ***
2662 // *******************
2663 $page_links = array();
2664 $total_pages_before = '<span class="paging-input">';
2665 $total_pages_after = '</span></span>';
2666 $disable_first = $disable_last = $disable_prev = $disable_next = false;
2667 if ( 1 === (int) $current ) {
2668 $disable_first = true;
2669 $disable_prev = true;
2670 }
2671 if ( 2 === (int) $current ) {
2672 $disable_first = true;
2673 }
2674 if ( (int) $current === (int) $total_pages ) {
2675 $disable_last = true;
2676 $disable_next = true;
2677 }
2678 if ( (int) $current === (int) $total_pages - 1 ) {
2679 $disable_last = true;
2680 }
2681 // *********************
2682 // *** BEGIN CHANGES ***
2683 // *********************
2684 $link_with_post_support = "\n <a class='%s'\n href='javascript:void(0)'\n onclick='jQuery(\"#current-page-selector\").val(\"%s\"); jQuery(\"#wpda_main_form\").submit();'>\n <span class='screen-reader-text'>%s</span>\n <span aria-hidden='true'>%s</span>\n </a>";
2685 // *******************
2686 // *** END CHANGES ***
2687 // *******************
2688 if ( $disable_first ) {
2689 $page_links[] = '<span class="tablenav-pages-navspan button disabled" aria-hidden="true">&laquo;</span>';
2690 } else {
2691 // *********************
2692 // *** BEGIN CHANGES ***
2693 // *********************
2694 $page_links[] = sprintf(
2695 $link_with_post_support,
2696 'first-page button',
2697 '',
2698 __( 'First page' ),
2699 '&laquo;'
2700 );
2701 // *******************
2702 // *** END CHANGES ***
2703 // *******************
2704 }
2705 if ( $disable_prev ) {
2706 $page_links[] = '<span class="tablenav-pages-navspan button disabled" aria-hidden="true">&lsaquo;</span>';
2707 } else {
2708 // *********************
2709 // *** BEGIN CHANGES ***
2710 // *********************
2711 $page_links[] = sprintf(
2712 $link_with_post_support,
2713 'prev-page button',
2714 max( 1, $current - 1 ),
2715 __( 'Previous page' ),
2716 '&lsaquo;'
2717 );
2718 // *******************
2719 // *** END CHANGES ***
2720 // *******************
2721 }
2722 if ( 'bottom' === $which ) {
2723 $html_current_page = $current;
2724 $total_pages_before = '<span class="screen-reader-text">' . __( 'Current Page' ) . '</span><span id="table-paging" class="paging-input"><span class="tablenav-paging-text">';
2725 } else {
2726 $html_current_page = sprintf(
2727 "%s<input class='current-page' id='current-page-selector' type='text' name='paged' value='%s' size='%d' aria-describedby='table-paging' /><span class='tablenav-paging-text'>",
2728 '<label for="current-page-selector" class="screen-reader-text">' . __( 'Current Page' ) . '</label>',
2729 $current,
2730 strlen( (string) $total_pages )
2731 );
2732 }
2733 $html_total_pages = sprintf( "<span class='total-pages'>%s</span>", number_format_i18n( $total_pages ) );
2734 /* translators: %s: current page/total pages */
2735 $page_links[] = $total_pages_before . sprintf( _x( '%1$s of %2$s', 'paging' ), $html_current_page, $html_total_pages ) . $total_pages_after;
2736 if ( $disable_next ) {
2737 $page_links[] = '<span class="tablenav-pages-navspan button disabled" aria-hidden="true">&rsaquo;</span>';
2738 } else {
2739 // *********************
2740 // *** BEGIN CHANGES ***
2741 // *********************
2742 $page_links[] = sprintf(
2743 $link_with_post_support,
2744 'next-page button',
2745 min( $total_pages, $current + 1 ),
2746 __( 'Next page' ),
2747 '&rsaquo;'
2748 );
2749 // *******************
2750 // *** END CHANGES ***
2751 // *******************
2752 }
2753 if ( $disable_last ) {
2754 $page_links[] = '<span class="tablenav-pages-navspan button disabled" aria-hidden="true">&raquo;</span>';
2755 } else {
2756 // *********************
2757 // *** BEGIN CHANGES ***
2758 // *********************
2759 $page_links[] = sprintf(
2760 $link_with_post_support,
2761 'last-page button',
2762 $total_pages,
2763 __( 'Last page' ),
2764 '&raquo;'
2765 );
2766 // *******************
2767 // *** END CHANGES ***
2768 // *******************
2769 }
2770 $pagination_links_class = 'pagination-links';
2771 if ( !empty( $infinite_scroll ) ) {
2772 $pagination_links_class = ' hide-if-js';
2773 }
2774 $output .= "\n<span class='{$pagination_links_class}'>" . join( "\n", $page_links ) . '</span>';
2775 if ( $total_pages ) {
2776 $page_class = ( $total_pages < 2 ? ' one-page' : '' );
2777 } else {
2778 $page_class = ' no-pages';
2779 }
2780 $this->_pagination = "<div class='tablenav-pages{$page_class}'>{$output}</div>";
2781 echo $this->_pagination;
2782 // phpcs:ignore WordPress.Security.EscapeOutput
2783 }
2784
2785 }
2786