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

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