PluginProbe
WP Data Access – App Builder for Tables, Forms, Charts, Maps & Dashboards / 5.5.76
WP Data Access – App Builder for Tables, Forms, Charts, Maps & Dashboards v5.5.76
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.76, at WPDataAccess/List_Table/WPDA_List_Table.php

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