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

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