PluginProbe
TablePress – Tables in WordPress made easy / 3.0
TablePress – Tables in WordPress made easy v3.0
3.3.4 3.3.3 3.3.2 3.3.1 trunk 1.12 1.14 1.9.2 2.0.4 2.1.7 2.1.8 2.2 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.3 2.3.1 2.3.2 2.4 2.4.1 2.4.2 2.4.3 2.4.4 All 44 releases
← All changes | classes/class-tablepress.php +589 -80 2.0.43.0 View file →
@@ -26,9 +26,9 @@
26 26 *
27 27 * @since 1.0.0
28 28 * @const string
29 29 */
30 - const version = '2.0.4'; // phpcs:ignore Generic.NamingConventions.UpperCaseConstantName.ClassConstantNotUpperCase
30 + public const version = '3.0'; // phpcs:ignore Generic.NamingConventions.UpperCaseConstantName.ClassConstantNotUpperCase
31 31
32 32 /**
33 33 * TablePress internal plugin version ("options scheme" version).
34 34 *
@@ -36,9 +36,9 @@
36 36 *
37 37 * @since 1.0.0
38 38 * @const int
39 39 */
40 - const db_version = 53; // phpcs:ignore Generic.NamingConventions.UpperCaseConstantName.ClassConstantNotUpperCase
40 + public const db_version = 96; // phpcs:ignore Generic.NamingConventions.UpperCaseConstantName.ClassConstantNotUpperCase
41 41
42 42 /**
43 43 * TablePress "table scheme" (data format structure) version.
44 44 *
@@ -47,33 +47,30 @@
47 47 *
48 48 * @since 1.0.0
49 49 * @const int
50 50 */
51 - const table_scheme_version = 3; // phpcs:ignore Generic.NamingConventions.UpperCaseConstantName.ClassConstantNotUpperCase
51 + public const table_scheme_version = 3; // phpcs:ignore Generic.NamingConventions.UpperCaseConstantName.ClassConstantNotUpperCase
52 52
53 53 /**
54 54 * Instance of the Options Model.
55 55 *
56 56 * @since 1.3.0
57 - * @var TablePress_Options_Model
58 57 */
59 - public static $model_options;
58 + public static \TablePress_Options_Model $model_options;
60 59
61 60 /**
62 61 * Instance of the Table Model.
63 62 *
64 63 * @since 1.3.0
65 - * @var TablePress_Table_Model
66 64 */
67 - public static $model_table;
65 + public static \TablePress_Table_Model $model_table;
68 66
69 67 /**
70 68 * Instance of the controller.
71 69 *
72 70 * @since 1.0.0
73 - * @var TablePress_*_Controller
74 71 */
75 - public static $controller;
72 + public static \TablePress_Frontend_Controller $controller;
76 73
77 74 /**
78 75 * Name of the Shortcode to show a TablePress table.
79 76 *
@@ -79,11 +76,10 @@
79 76 *
80 77 * Should only be modified through the filter hook 'tablepress_table_shortcode'.
81 78 *
82 79 * @since 1.0.0
83 - * @var string
84 80 */
85 - public static $shortcode = 'table';
81 + public static string $shortcode = 'table';
86 82
87 83 /**
88 84 * Name of the Shortcode to show extra information of a TablePress table.
89 85 *
@@ -89,18 +85,25 @@
89 85 *
90 86 * Should only be modified through the filter hook 'tablepress_table_info_shortcode'.
91 87 *
92 88 * @since 1.0.0
93 - * @var string
94 89 */
95 - public static $shortcode_info = 'table-info';
90 + public static string $shortcode_info = 'table-info';
96 91
97 92 /**
93 + * List of TablePress premium modules.
94 + *
95 + * @since 2.1.0
96 + * @var array<string, array{name: string, description: string, category: string, class: string, incompatible_classes: string[], minimum_plan: string, default_active: bool}>
97 + */
98 + public static array $modules = array();
99 +
100 + /**
98 101 * Start-up TablePress (run on WordPress "init") and load the controller for the current state.
99 102 *
100 103 * @since 1.0.0
101 104 */
102 - public static function run() {
105 + public static function run(): void {
103 106 /**
104 107 * Fires before TablePress is loaded.
105 108 *
106 109 * The `tablepress_loaded` action hook might be a better choice in most situations, as TablePress options will then be available.
@@ -108,19 +111,8 @@
108 111 * @since 1.0.0
109 112 */
110 113 do_action( 'tablepress_run' );
111 114
112 - // Check if minimum requirements are fulfilled, currently WordPress 5.8.
113 - include ABSPATH . WPINC . '/version.php'; // Include an unmodified $wp_version.
114 - if ( version_compare( str_replace( '-src', '', $wp_version ), '5.8', '<' ) ) {
115 - // Show error notice to admins, if WP is not installed in the minimum required version, in which case TablePress will not work.
116 - if ( current_user_can( 'update_plugins' ) ) {
117 - add_action( 'admin_notices', array( 'TablePress', 'show_minimum_requirements_error_notice' ) );
118 - }
119 - // And exit TablePress.
120 - return;
121 - }
122 -
123 115 /**
124 116 * Filters the string that is used as the [table] Shortcode.
125 117 *
126 118 * @since 1.0.0
@@ -161,9 +153,9 @@
161 153
162 154 if ( is_admin() ) {
163 155 $controller = 'admin';
164 156 if ( wp_doing_ajax() ) {
165 - $controller .= '_ajax';
157 + $controller = 'admin_ajax';
166 158 }
167 159 self::load_controller( $controller );
168 160 }
169 161 // Load the frontend controller in all scenarios, so that Shortcode render functions are always available.
@@ -168,8 +160,13 @@
168 160 }
169 161 // Load the frontend controller in all scenarios, so that Shortcode render functions are always available.
170 162 self::$controller = self::load_controller( 'frontend' );
171 163
164 + // Add filters and actions for the integration into the WP WXR exporter and importer.
165 + add_action( 'wp_import_insert_post', array( TablePress::$model_table, 'add_table_id_on_wp_import' ), 10, 4 ); // phpcs:ignore Squiz.Classes.SelfMemberReference.NotUsed
166 + add_filter( 'wp_import_post_meta', array( TablePress::$model_table, 'prevent_table_id_post_meta_import_on_wp_import' ), 10, 3 ); // phpcs:ignore Squiz.Classes.SelfMemberReference.NotUsed
167 + add_filter( 'wxr_export_skip_postmeta', array( TablePress::$model_table, 'add_table_id_to_wp_export' ), 10, 3 ); // phpcs:ignore Squiz.Classes.SelfMemberReference.NotUsed
168 +
172 169 /**
173 170 * Fires after TablePress is loaded.
174 171 *
175 172 * The `tablepress_run` action hook can be used if code has to run before TablePress is loaded.
@@ -186,9 +183,9 @@
186 183 *
187 184 * @param string $file Name of the PHP file.
188 185 * @param string $folder Name of the folder with the file.
189 186 */
190 - public static function load_file( $file, $folder ) {
187 + public static function load_file( string $file, string $folder ): void {
191 188 $full_path = TABLEPRESS_ABSPATH . $folder . '/' . $file;
192 189 /**
193 190 * Filters the full path of a file that shall be loaded.
194 191 *
@@ -209,15 +206,15 @@
209 206 * of the plugin's directory.
210 207 *
211 208 * @since 1.0.0
212 209 *
213 - * @param string $class_name Name of the class.
214 - * @param string $file Name of the PHP file with the class.
215 - * @param string $folder Name of the folder with $class_name's $file.
216 - * @param mixed $params Optional. Parameters that are passed to the constructor of $class_name.
210 + * @param string $class_name Name of the class.
211 + * @param string $file Name of the PHP file with the class.
212 + * @param string $folder Name of the folder with $class_name's $file.
213 + * @param mixed[]|string|null $params Optional. Parameters that are passed to the constructor of $class_name.
217 214 * @return object Initialized instance of the class.
218 215 */
219 - public static function load_class( $class_name, $file, $folder, $params = null ) {
216 + public static function load_class( string $class_name, string $file, string $folder, /* ?array|string */ $params = null ): object {
220 217 /**
221 218 * Filters name of the class that shall be loaded.
222 219 *
223 220 * @since 1.0.0
@@ -239,9 +236,9 @@
239 236 *
240 237 * @param string $model Name of the model.
241 238 * @return object Instance of the initialized model.
242 239 */
243 - public static function load_model( $model ) {
240 + public static function load_model( string $model ): object {
244 241 // Model Base Class.
245 242 self::load_file( 'class-model.php', 'classes' );
246 243 // Make first letter uppercase for a better looking naming pattern.
247 244 $ucmodel = ucfirst( $model );
@@ -253,13 +250,13 @@
253 250 * Create a new instance of the $view, which is stored in the "views" subfolder, and set it up with $data.
254 251 *
255 252 * @since 1.0.0
256 253 *
257 - * @param string $view Name of the view to load.
258 - * @param array $data Optional. Parameters/PHP variables that shall be available to the view.
254 + * @param string $view Name of the view to load.
255 + * @param array<string, mixed> $data Optional. Parameters/PHP variables that shall be available to the view.
259 256 * @return object Instance of the initialized view, already set up, just needs to be rendered.
260 257 */
261 - public static function load_view( $view, array $data = array() ) {
258 + public static function load_view( string $view, array $data = array() ): object {
262 259 // View Base Class.
263 260 self::load_file( 'class-view.php', 'classes' );
264 261 // Make first letter uppercase for a better looking naming pattern.
265 262 $ucview = ucfirst( $view );
@@ -275,9 +272,9 @@
275 272 *
276 273 * @param string $controller Name of the controller.
277 274 * @return object Instance of the initialized controller.
278 275 */
279 - public static function load_controller( $controller ) {
276 + public static function load_controller( string $controller ): object {
280 277 // Controller Base Class.
281 278 self::load_file( 'class-controller.php', 'classes' );
282 279 // Make first letter uppercase for a better looking naming pattern.
283 280 $uccontroller = ucfirst( $controller );
@@ -289,13 +286,13 @@
289 286 * Generate the complete nonce string, from the nonce base, the action and an item, e.g. tablepress_delete_table_3.
290 287 *
291 288 * @since 1.0.0
292 289 *
293 - * @param string $action Action for which the nonce is needed.
294 - * @param string|bool $item Optional. Item for which the action will be performed, like "table".
290 + * @param string $action Action for which the nonce is needed.
291 + * @param string|false $item Optional. Item for which the action will be performed, like "table". false if no item should be used in the nonce.
295 292 * @return string The resulting nonce string.
296 293 */
297 - public static function nonce( $action, $item = false ) {
294 + public static function nonce( string $action, /* string|false */ $item = false ): string {
298 295 $nonce = "tablepress_{$action}";
299 296 if ( $item ) {
300 297 $nonce .= "_{$item}";
301 298 }
@@ -306,14 +303,14 @@
306 303 * Check whether a nonce string is valid.
307 304 *
308 305 * @since 1.0.0
309 306 *
310 - * @param string $action Action for which the nonce should be checked.
311 - * @param string|bool $item Optional. Item for which the action should be performed, like "table".
312 - * @param string $query_arg Optional. Name of the nonce query string argument in $_POST.
313 - * @param bool $ajax Whether the nonce comes from an AJAX request.
307 + * @param string $action Action for which the nonce should be checked.
308 + * @param string|false $item Optional. Item for which the action should be performed, like "table". false if no item should be used in the nonce.
309 + * @param string $query_arg Optional. Name of the nonce query string argument in $_POST.
310 + * @param bool $ajax Whether the nonce comes from an AJAX request.
314 311 */
315 - public static function check_nonce( $action, $item = false, $query_arg = '_wpnonce', $ajax = false ) {
312 + public static function check_nonce( string $action, /* string|false */ $item = false, string $query_arg = '_wpnonce', bool $ajax = false ): void {
316 313 $nonce_action = self::nonce( $action, $item );
317 314 if ( $ajax ) {
318 315 check_ajax_referer( $nonce_action, $query_arg );
319 316 } else {
@@ -330,14 +327,15 @@
330 327 *
331 328 * @param string $column Column string.
332 329 * @return int Column number, 1-based.
333 330 */
334 - public static function letter_to_number( $column ) {
331 + public static function letter_to_number( string $column ): int {
332 + $column = (string) preg_replace( '/[^A-Za-z]/', '', $column );
335 333 $column = strtoupper( $column );
336 334 $count = strlen( $column );
337 335 $number = 0;
338 336 for ( $i = 0; $i < $count; $i++ ) {
339 - $number += ( ord( $column[ $count - 1 - $i ] ) - 64 ) * pow( 26, $i );
337 + $number += ( ord( $column[ $count - 1 - $i ] ) - 64 ) * 26 ** $i;
340 338 }
341 339 return $number;
342 340 }
343 341
@@ -350,13 +348,13 @@
350 348 *
351 349 * @param int $number Column number, 1-based.
352 350 * @return string Column string.
353 351 */
354 - public static function number_to_letter( $number ) {
352 + public static function number_to_letter( int $number ): string {
355 353 $column = '';
356 354 while ( $number > 0 ) {
357 355 $column = chr( 65 + ( ( $number - 1 ) % 26 ) ) . $column;
358 - $number = floor( ( $number - 1 ) / 26 );
356 + $number = intdiv( $number - 1, 26 );
359 357 }
360 358 return $column;
361 359 }
362 360
@@ -368,22 +366,27 @@
368 366 * @param string $datetime_string DateTime string, often in mySQL format..
369 367 * @param string $separator_or_format Optional. Separator between date and time, or format string.
370 368 * @return string Nice looking string with the date and time.
371 369 */
372 - public static function format_datetime( $datetime_string, $separator_or_format = ' ' ) {
370 + public static function format_datetime( string $datetime_string, string $separator_or_format = ' ' ): string {
373 371 $timezone = wp_timezone();
374 372 $datetime = date_create( $datetime_string, $timezone );
373 + if ( false === $datetime ) {
374 + return $datetime_string;
375 + }
375 376 $timestamp = $datetime->getTimestamp();
376 377
377 378 switch ( $separator_or_format ) {
378 379 case ' ':
379 380 case '<br />':
381 + case '<br/>':
382 + case '<br>':
380 383 $date = wp_date( get_option( 'date_format' ), $timestamp, $timezone );
381 384 $time = wp_date( get_option( 'time_format' ), $timestamp, $timezone );
382 385 $output = "{$date}{$separator_or_format}{$time}";
383 386 break;
384 387 default:
385 - $output = wp_date( $separator_or_format, $timestamp, $timezone );
388 + $output = (string) wp_date( $separator_or_format, $timestamp, $timezone );
386 389 break;
387 390 }
388 391
389 392 return $output;
@@ -396,11 +399,11 @@
396 399 *
397 400 * @param int $user_id WP user ID.
398 401 * @return string Nickname of the WP user with the $user_id.
399 402 */
400 - public static function get_user_display_name( $user_id ) {
403 + public static function get_user_display_name( int $user_id ): string {
401 404 $user = get_userdata( $user_id );
402 - return ( isset( $user->display_name ) ) ? $user->display_name : sprintf( '<em>%s</em>', __( 'unknown', 'tablepress' ) );
405 + return $user->display_name ?? sprintf( '<em>%s</em>', __( 'unknown', 'tablepress' ) );
403 406 }
404 407
405 408 /**
406 409 * Sanitizes a CSS class to ensure it only contains valid characters.
@@ -412,17 +415,265 @@
412 415 *
413 416 * @param string $css_class The CSS class name to be sanitized.
414 417 * @return string The sanitized CSS class.
415 418 */
416 - public static function sanitize_css_class( $css_class ) {
419 + public static function sanitize_css_class( string $css_class ): string {
417 420 // Strip out any %-encoded octets.
418 - $sanitized_css_class = preg_replace( '|%[a-fA-F0-9][a-fA-F0-9]|', '', $css_class );
421 + $sanitized_css_class = (string) preg_replace( '|%[a-fA-F0-9][a-fA-F0-9]|', '', $css_class );
419 422 // Limit to A-Z, a-z, 0-9, ':', '_', and '-'.
420 - $sanitized_css_class = preg_replace( '/[^A-Za-z0-9:_-]/', '', $sanitized_css_class );
423 + $sanitized_css_class = (string) preg_replace( '/[^A-Za-z0-9:_-]/', '', $sanitized_css_class );
421 424 return $sanitized_css_class;
422 425 }
423 426
424 427 /**
428 + * Extracts the top-level keys from a JavaScript object string.
429 + *
430 + * This function is used to extract the keys of the "Custom Commands" JavaScript object string, to check for overrides.
431 + * It covers most cases, like normal object properties with and without quotes, shorthand properties, and shorthand methods,
432 + * and also ignores single-line and multi-line comments.
433 + * It does not cover all possible JavaScript syntax (like template literals, special characters, ...),
434 + * but should be sufficient for the use case.
435 + *
436 + * @since 3.0.0
437 + *
438 + * @param string $js_object_string A JavaScript object as a string.
439 + * @return string[] Array of top-level keys of the object.
440 + */
441 + public static function extract_keys_from_js_object_string( string $js_object_string ): array {
442 + $object_keys = array();
443 + $length = strlen( $js_object_string );
444 + $depth = 0;
445 + $key_expected = true;
446 + $in_quotes = false;
447 + $quote_char = '';
448 + $in_function_declaration = false;
449 + $in_single_line_comment = false;
450 + $in_multi_line_comment = false;
451 + $object_key = '';
452 +
453 + for ( $i = 0; $i < $length; $i++ ) {
454 + $char = $js_object_string[ $i ];
455 +
456 + // Skip parsing single-line comments.
457 + if ( $in_single_line_comment ) {
458 + if ( "\n" === $char ) {
459 + $in_single_line_comment = false;
460 + }
461 + continue;
462 + } else { // phpcs:ignore Universal.ControlStructures.DisallowLonelyIf.Found
463 + if ( '/' === $char && $i + 1 < $length && '/' === $js_object_string[ $i + 1 ] ) {
464 + $in_single_line_comment = true;
465 + ++$i; // Skip the second '/'.
466 + continue;
467 + }
468 + }
469 +
470 + // Skip parsing multi-line comments.
471 + if ( $in_multi_line_comment ) {
472 + if ( '*' === $char && $i + 1 < $length && '/' === $js_object_string[ $i + 1 ] ) {
473 + $in_multi_line_comment = false;
474 + ++$i; // Skip the '/' that ends the multi-line comment.
475 + }
476 + continue;
477 + } else { // phpcs:ignore Universal.ControlStructures.DisallowLonelyIf.Found
478 + if ( '/' === $char && $i + 1 < $length && '*' === $js_object_string[ $i + 1 ] ) {
479 + $in_multi_line_comment = true;
480 + ++$i; // Skip the '*'.
481 + continue;
482 + }
483 + }
484 +
485 + // Skip parsing while inside a quoted string.
486 + if ( $in_quotes ) {
487 + if ( $quote_char === $char ) {
488 + $in_quotes = false;
489 + }
490 + continue;
491 + } else { // phpcs:ignore Universal.ControlStructures.DisallowLonelyIf.Found
492 + if ( '"' === $char || "'" === $char ) {
493 + $in_quotes = true;
494 + $quote_char = $char;
495 + continue;
496 + }
497 + }
498 +
499 + /*
500 + * Skip parsing while inside a `function abc( ... )` declaration string.
501 + * The `$key_expected` check limits search the "function" string to object values.
502 + * The check for the plain `f` reduces expensive `substr()` calls.
503 + */
504 + if ( ! $key_expected ) {
505 + if ( $in_function_declaration ) {
506 + if ( ')' === $char ) {
507 + $in_function_declaration = false;
508 + }
509 + continue;
510 + } else { // phpcs:ignore Universal.ControlStructures.DisallowLonelyIf.Found
511 + if ( 'f' === $char && 'function' === substr( $js_object_string, $i, 8 ) ) {
512 + $in_function_declaration = true;
513 + $i += 7; // Skip the rest of the "function" string.
514 + continue;
515 + }
516 + }
517 + }
518 +
519 + // Handle object depth, so that most parsing can be limited to the top level.
520 + if ( '{' === $char || '[' === $char ) {
521 + ++$depth;
522 + }
523 +
524 + // Extract only keys at the top level.
525 + if ( 1 === $depth ) {
526 + if ( $key_expected ) {
527 + if ( ':' === $char ) {
528 + // Check for normal keys, with value after :.
529 +
530 + // Go backwards to find the start of the key.
531 + $j = $i - 1;
532 + while ( $j >= 0 && preg_match( '/\s/', $js_object_string[ $j ] ) ) {
533 + --$j;
534 + }
535 + $key_end = $j; // Position of the last character of the key (potentially with quote).
536 + if ( '"' === $js_object_string[ $j ] || "'" === $js_object_string[ $j ] ) {
537 + // Quoted key.
538 + $quote_char = $js_object_string[ $j ];
539 + --$j;
540 + while ( $j >= 0 && $quote_char !== $js_object_string[ $j ] ) {
541 + --$j;
542 + }
543 + $key_start = $j + 1;
544 + } else {
545 + // Unquoted key.
546 + while ( $j >= 0 && preg_match( '/[\w]/', $js_object_string[ $j ] ) ) {
547 + --$j;
548 + }
549 + $key_start = $j + 1;
550 + }
551 + $object_key = substr( $js_object_string, $key_start, $key_end - $key_start + 1 );
552 + $object_key = trim( $object_key, "\"'" );
553 + if ( '' !== $object_key && ! in_array( $object_key, $object_keys, true ) ) {
554 + $object_keys[] = $object_key;
555 + }
556 + $key_expected = false;
557 + } elseif ( ( ',' === $char || '}' === $char ) ) { // The `}` case is for the last key.
558 + // Check for shorthand properties (which must be unquoted).
559 +
560 + // Go backwards to find the start of the shorthand key.
561 + $j = $i - 1;
562 + while ( $j >= 0 && preg_match( '/\s/', $js_object_string[ $j ] ) ) {
563 + --$j;
564 + }
565 + $key_end = $j; // Position of the last character of the key (without a quote).
566 + while ( $j >= 0 && preg_match( '/[\w]/', $js_object_string[ $j ] ) ) {
567 + --$j;
568 + }
569 + $key_start = $j + 1;
570 + $object_key = substr( $js_object_string, $key_start, $key_end - $key_start + 1 );
571 + if ( '' !== $object_key && ! in_array( $object_key, $object_keys, true ) ) {
572 + $object_keys[] = $object_key;
573 + }
574 + } elseif ( '(' === $char ) {
575 + // Detect shorthand method definitions.
576 +
577 + // Go back to find the start of the method name.
578 + $j = $i - 1;
579 + while ( $j >= 0 && preg_match( '/\s/', $js_object_string[ $j ] ) ) {
580 + --$j;
581 + }
582 + $key_end = $j;
583 + while ( $j >= 0 && preg_match( '/[\w]/', $js_object_string[ $j ] ) ) {
584 + --$j;
585 + }
586 + $key_start = $j + 1;
587 + $object_key = substr( $js_object_string, $key_start, $key_end - $key_start + 1 );
588 + if ( '' !== $object_key && ! in_array( $object_key, $object_keys, true ) ) {
589 + $object_keys[] = $object_key;
590 + }
591 + }
592 + }
593 +
594 + // Reset the "key expected" flag after a comma or closing brace.
595 + if ( ',' === $char || '}' === $char ) {
596 + $key_expected = true;
597 + }
598 + }
599 +
600 + // Handle object depth.
601 + if ( '}' === $char || ']' === $char ) {
602 + --$depth;
603 + }
604 + }
605 +
606 + return $object_keys;
607 + }
608 +
609 + /**
610 + * Converts old DataTables 1.x CSS classes and parameters to the DataTables 2 variants.
611 + *
612 + * This function is used to modernize "Custom CSS" and "Custom Commands" for compatibility with DataTables 2.x.
613 + * It probably does not catch all possible cases.
614 + *
615 + * @since 3.0.0
616 + *
617 + * @param string $code Code that contains DataTables 1.x CSS classes and parameters.
618 + * @return string Updated code with DataTables 2.x CSS classes and parameters.
619 + */
620 + public static function convert_datatables_api_data( string $code ): string {
621 + /**
622 + * Mappings for DataTables 1.x CSS class or parameter to DataTables 2 variants.
623 + * As this array is used in `strtr()`, it's pre-sorted for descending string length of the array keys.
624 + */
625 + static $datatables_api_data_mappings = array(
626 + // CSS classes.
627 + '.tablepress thead .sorting:hover' => '.tablepress thead .dt-orderable-asc:hover,.tablepress thead .dt-orderable-desc:hover',
628 + '.tablepress thead .sorting_desc' => '.tablepress thead .dt-ordering-desc',
629 + '.dataTables_filter label input' => '.dt-container .dt-search input',
630 + '.tablepress thead .sorting_asc' => '.tablepress thead .dt-ordering-asc',
631 + '.dataTables_scrollFootInner' => '.dt-scroll-footInner',
632 + '.dataTables_scrollHeadInner' => '.dt-scroll-headInner',
633 + '.tablepress thead .sorting' => '.tablepress thead .dt-orderable-asc,.tablepress thead .dt-orderable-desc',
634 + '.dataTables_processing' => '.dt-processing',
635 + '.dataTables_scrollBody' => '.dt-scroll-body',
636 + '.dataTables_scrollFoot' => '.dt-scroll-foot',
637 + '.dataTables_scrollHead' => '.dt-scroll-head',
638 + '.dataTables_paginate' => '.dt-paging',
639 + '.tablepress .even td' => '.tablepress>:where(tbody.row-striping)>:nth-child(odd)>*',
640 + '.dataTables_wrapper' => '.dt-container',
641 + '.tablepress .odd td' => '.tablepress>:where(tbody.row-striping)>:nth-child(even)>*',
642 + '.dataTables_filter' => '.dt-search',
643 + '.dataTables_length' => '.dt-length',
644 + '.dataTables_scroll' => '.dt-scroll',
645 + '.dataTables_empty' => '.dt-empty',
646 + '.dataTables_info' => '.dt-info',
647 + '.paginate_button' => '.dt-paging-button',
648 + // DataTables API functions.
649 + '$.fn.dataTable.' => 'DataTable.',
650 + );
651 + $code = strtr( $code, $datatables_api_data_mappings );
652 +
653 + // HTML ID mappings, which were removed.
654 + if ( str_contains( $code, '#tablepress-' ) ) {
655 + $code = (string) preg_replace(
656 + array(
657 + '/#tablepress-([A-Za-z1-9_-]|[A-Za-z0-9_-]{2,})_paginate/',
658 + '/#tablepress-([A-Za-z1-9_-]|[A-Za-z0-9_-]{2,})_filter/',
659 + '/#tablepress-([A-Za-z1-9_-]|[A-Za-z0-9_-]{2,})_length/',
660 + '/#tablepress-([A-Za-z1-9_-]|[A-Za-z0-9_-]{2,})_info/',
661 + ),
662 + array(
663 + '#tablepress-$1_wrapper .dt-paging',
664 + '#tablepress-$1_wrapper .dt-search',
665 + '#tablepress-$1_wrapper .dt-length',
666 + '#tablepress-$1_wrapper .dt-info',
667 + ),
668 + $code,
669 + );
670 + }
671 +
672 + return $code;
673 + }
674 +
675 + /**
425 676 * Retrieves all information of a WP_Error object as a string.
426 677 *
427 678 * @since 1.4.0
428 679 *
@@ -428,9 +679,9 @@
428 679 *
429 680 * @param WP_Error $wp_error A WP_Error object.
430 681 * @return string All error codes, messages, and data of the WP_Error.
431 682 */
432 - public static function get_wp_error_string( $wp_error ) {
683 + public static function get_wp_error_string( WP_Error $wp_error ): string {
433 684 $error_strings = array();
434 685 $error_codes = $wp_error->get_error_codes();
435 686 // Reverse order to get latest errors first.
436 687 $error_codes = array_reverse( $error_codes );
@@ -441,10 +692,16 @@
441 692 if ( ! empty( $error_messages ) ) {
442 693 $error_strings[ $error_code ] .= " ({$error_messages})";
443 694 }
444 695 $error_data = $wp_error->get_error_data( $error_code );
445 - if ( ! is_null( $error_data ) ) {
696 + if ( is_string( $error_data ) ) {
446 697 $error_strings[ $error_code ] .= " [{$error_data}]";
698 + } elseif ( is_array( $error_data ) ) {
699 + foreach ( $error_data as $key => $value ) {
700 + $error_data[ $key ] = "{$key}: {$value}";
701 + }
702 + $error_data = implode( ', ', $error_data );
703 + $error_strings[ $error_code ] .= " [{$error_data}]";
447 704 }
448 705 }
449 706 return implode( ";\n", $error_strings );
450 707 }
@@ -453,14 +710,14 @@
453 710 * Generate the action URL, to be used as a link within the plugin (e.g. in the submenu navigation or List of Tables).
454 711 *
455 712 * @since 1.0.0
456 713 *
457 - * @param array $params Optional. Parameters to form the query string of the URL.
458 - * @param bool $add_nonce Optional. Whether the URL shall be nonced by WordPress.
459 - * @param string $target Optional. Target File, e.g. "admin-post.php" for POST requests.
714 + * @param array<string, mixed> $params Optional. Parameters to form the query string of the URL.
715 + * @param bool $add_nonce Optional. Whether the URL shall be nonced by WordPress.
716 + * @param string $target Optional. Target File, e.g. "admin-post.php" for POST requests.
460 717 * @return string The URL for the given parameters (already run through esc_url() with $add_nonce === true!).
461 718 */
462 - public static function url( array $params = array(), $add_nonce = false, $target = '' ) {
719 + public static function url( array $params = array(), bool $add_nonce = false, string $target = '' ): string {
463 720 // Default action is "list", if no action given.
464 721 if ( ! isset( $params['action'] ) ) {
465 722 $params['action'] = 'list';
466 723 }
@@ -503,12 +760,12 @@
503 760 * Create a redirect URL from the $target_parameters and redirect the user.
504 761 *
505 762 * @since 1.0.0
506 763 *
507 - * @param array $params Optional. Parameters from which the target URL is constructed.
508 - * @param bool $add_nonce Optional. Whether the URL shall be nonced by WordPress.
764 + * @param array<string, mixed> $params Optional. Parameters from which the target URL is constructed.
765 + * @param bool $add_nonce Optional. Whether the URL shall be nonced by WordPress.
509 766 */
510 - public static function redirect( array $params = array(), $add_nonce = false ) {
767 + public static function redirect( array $params = array(), bool $add_nonce = false ): void {
511 768 $redirect = self::url( $params );
512 769 if ( $add_nonce ) {
513 770 if ( ! isset( $params['item'] ) ) {
514 771 $params['item'] = false;
@@ -520,21 +777,8 @@
520 777 exit;
521 778 }
522 779
523 780 /**
524 - * Show an error notice to admins, if TablePress's minimum requirements are not reached.
525 - *
526 - * @since 1.0.0
527 - */
528 - public static function show_minimum_requirements_error_notice() {
529 - // Message is not translated as it is shown on every admin screen, for which we don't want to load translations.
530 - echo '<div class="notice notice-error form-invalid"><p>' .
531 - '<strong>Attention:</strong> ' .
532 - 'The installed version of WordPress is too old for the TablePress plugin! TablePress requires an up-to-date version! <strong>Please <a href="' . esc_url( admin_url( 'update-core.php' ) ) . '">update your WordPress installation</a></strong>!' .
533 - "</p></div>\n";
534 - }
535 -
536 - /**
537 781 * Determines whether the site uses the block editor, so that certain text and input fields referring to Shortcodes can be displayed or not.
538 782 *
539 783 * @since 2.0.1
540 784 *
@@ -539,9 +783,9 @@
539 783 * @since 2.0.1
540 784 *
541 785 * @return bool True if the site uses the block editor, false otherwise.
542 786 */
543 - public static function site_uses_block_editor() {
787 + public static function site_uses_block_editor(): bool {
544 788 $site_uses_block_editor = use_block_editor_for_post_type( 'post' )
545 789 && ! is_plugin_active( 'beaver-builder-lite-version/fl-builder.php' )
546 790 && ! is_plugin_active( 'classic-editor/classic-editor.php' )
547 791 && ! is_plugin_active( 'classic-editor-addon/classic-editor-addon.php' )
@@ -556,10 +800,275 @@
556 800 * @since 2.0.1
557 801 *
558 802 * @param bool $site_uses_block_editor True if the site uses the block editor, false otherwise.
559 803 */
560 - $site_uses_block_editor = apply_filters( 'tablepress_site_uses_block_editor', $site_uses_block_editor );
804 + $site_uses_block_editor = (bool) apply_filters( 'tablepress_site_uses_block_editor', $site_uses_block_editor );
561 805
562 806 return $site_uses_block_editor;
807 + }
808 +
809 + /**
810 + * Initializes the list of TablePress premium modules.
811 + *
812 + * @since 2.1.0
813 + */
814 + public static function init_modules(): void {
815 + self::$modules = array(
816 + 'advanced-access-rights' => array(
817 + 'name' => __( 'Advanced Access Rights', 'tablepress' ),
818 + 'description' => __( 'Restrict access to individual tables for individual users.', 'tablepress' ),
819 + 'category' => 'backend',
820 + 'class' => 'TablePress_Module_Advanced_Access_Rights',
821 + 'incompatible_classes' => array( 'TablePress_Advanced_Access_Rights_Controller' ),
822 + 'minimum_plan' => 'max',
823 + 'default_active' => false,
824 + ),
825 + 'automatic-periodic-table-import' => array(
826 + 'name' => __( 'Automatic Periodic Table Import', 'tablepress' ),
827 + 'description' => __( 'Periodically update tables from a configured import source.', 'tablepress' ),
828 + 'category' => 'backend',
829 + 'class' => 'TablePress_Module_Automatic_Periodic_Table_Import',
830 + 'incompatible_classes' => array( 'TablePress_Table_Auto_Update' ),
831 + 'minimum_plan' => 'max',
832 + 'default_active' => true,
833 + ),
834 + 'automatic-table-export' => array(
835 + 'name' => __( 'Automatic Table Export', 'tablepress' ),
836 + 'description' => __( 'Export and save tables to files on the server after they were modified.', 'tablepress' ),
837 + 'category' => 'backend',
838 + 'class' => 'TablePress_Module_Automatic_Table_Export',
839 + 'incompatible_classes' => array(),
840 + 'minimum_plan' => 'pro',
841 + 'default_active' => false,
842 + ),
843 + 'cell-highlighting' => array(
844 + 'name' => __( 'Cell Highlighting', 'tablepress' ),
845 + 'description' => __( 'Add CSS classes to cells for highlighting based on their content.', 'tablepress' ),
846 + 'category' => 'frontend',
847 + 'class' => 'TablePress_Module_Cell_Highlighting',
848 + 'incompatible_classes' => array( 'TablePress_Cell_Highlighting' ),
849 + 'minimum_plan' => 'pro',
850 + 'default_active' => false,
851 + ),
852 + 'column-order' => array(
853 + 'name' => __( 'Column Order', 'tablepress' ),
854 + 'description' => __( 'Order the columns in different ways when a table is shown.', 'tablepress' ),
855 + 'category' => 'data-management',
856 + 'class' => 'TablePress_Module_Column_Order',
857 + 'incompatible_classes' => array( 'TablePress_Column_Order' ),
858 + 'minimum_plan' => 'pro',
859 + 'default_active' => false,
860 + ),
861 + 'datatables-advanced-loading' => array(
862 + 'name' => __( 'Advanced Loading', 'tablepress' ),
863 + 'description' => __( 'Load the table data from a JSON array for faster loading.', 'tablepress' ),
864 + 'category' => 'backend',
865 + 'class' => 'TablePress_Module_DataTables_Advanced_Loading',
866 + 'incompatible_classes' => array( 'TablePress_DataTables_Advanced_Loading' ),
867 + 'minimum_plan' => 'max',
868 + 'default_active' => false,
869 + ),
870 + 'datatables-alphabetsearch' => array(
871 + 'name' => __( 'Alphabet Search', 'tablepress' ),
872 + 'description' => __( 'Show Alphabet buttons above the table to filter rows by their first letter.', 'tablepress' ),
873 + 'category' => 'search-filter',
874 + 'class' => 'TablePress_Module_DataTables_Alphabetsearch',
875 + 'incompatible_classes' => array(),
876 + 'minimum_plan' => 'pro',
877 + 'default_active' => false,
878 + ),
879 + 'datatables-auto-filter' => array(
880 + 'name' => __( 'Automatic Filter', 'tablepress' ),
881 + 'description' => __( 'Pre-filter a table when it is shown.', 'tablepress' ),
882 + 'category' => 'search-filter',
883 + 'class' => 'TablePress_Module_DataTables_Auto_Filter',
884 + 'incompatible_classes' => array( 'TablePress_DataTables_Auto_Filter' ),
885 + 'minimum_plan' => 'pro',
886 + 'default_active' => false,
887 + ),
888 + 'datatables-buttons' => array(
889 + 'name' => __( 'User Action Buttons', 'tablepress' ),
890 + 'description' => __( 'Add buttons for downloading, copying, printing, and changing column visibility of tables.', 'tablepress' ),
891 + 'category' => 'frontend',
892 + 'class' => 'TablePress_Module_DataTables_Buttons',
893 + 'incompatible_classes' => array( 'TablePress_DataTables_Buttons' ),
894 + 'minimum_plan' => 'pro',
895 + 'default_active' => true,
896 + ),
897 + 'datatables-columnfilterwidgets' => array(
898 + 'name' => __( 'Column Filter Dropdowns', 'tablepress' ),
899 + 'description' => __( 'Add a search dropdown for each column above the table.', 'tablepress' ),
900 + 'category' => 'search-filter',
901 + 'class' => 'TablePress_Module_DataTables_ColumnFilterWidgets',
902 + 'incompatible_classes' => array(),
903 + 'minimum_plan' => 'pro',
904 + 'default_active' => true,
905 + ),
906 + 'datatables-column-filter' => array(
907 + 'name' => __( 'Individual Column Filtering', 'tablepress' ),
908 + 'description' => __( 'Add a search field for each column to the table head or foot row.', 'tablepress' ),
909 + 'category' => 'search-filter',
910 + 'class' => 'TablePress_Module_DataTables_Column_Filter',
911 + 'incompatible_classes' => array(),
912 + 'minimum_plan' => 'pro',
913 + 'default_active' => false,
914 + ),
915 + 'datatables-counter-column' => array(
916 + 'name' => __( 'Index Column', 'tablepress' ),
917 + 'description' => __( 'Make the first column an index or counter column with the row position.', 'tablepress' ),
918 + 'category' => 'frontend',
919 + 'class' => 'TablePress_Module_DataTables_Counter_Column',
920 + 'incompatible_classes' => array(),
921 + 'minimum_plan' => 'pro',
922 + 'default_active' => false,
923 + ),
924 + 'datatables-fixedheader-fixedcolumns' => array(
925 + 'name' => __( 'Fixed Rows and Columns', 'tablepress' ),
926 + 'description' => __( 'Fix the header and footer row and the first and last column when scrolling the table.', 'tablepress' ),
927 + 'category' => 'frontend',
928 + 'class' => 'TablePress_Module_DataTables_FixedHeader_FixedColumns',
929 + 'incompatible_classes' => array(
930 + 'TablePress_DataTables_FixedHeader',
931 + 'TablePress_DataTables_FixedColumns',
932 + ),
933 + 'minimum_plan' => 'pro',
934 + 'default_active' => true,
935 + ),
936 + 'datatables-layout' => array(
937 + 'name' => __( 'Table Layout', 'tablepress' ),
938 + 'description' => __( 'Customize the layout and position of features around a table.', 'tablepress' ),
939 + 'category' => 'frontend',
940 + 'class' => 'TablePress_Module_DataTables_Layout',
941 + 'incompatible_classes' => array(),
942 + 'minimum_plan' => 'pro',
943 + 'default_active' => true,
944 + ),
945 + 'datatables-fuzzysearch' => array(
946 + 'name' => __( 'Fuzzy Search', 'tablepress' ),
947 + 'description' => __( 'Let the search account for spelling mistakes and typos and find similar matches.', 'tablepress' ),
948 + 'category' => 'search-filter',
949 + 'class' => 'TablePress_Module_DataTables_FuzzySearch',
950 + 'incompatible_classes' => array(),
951 + 'minimum_plan' => 'max',
952 + 'default_active' => false,
953 + ),
954 + 'datatables-inverted-filter' => array(
955 + 'name' => __( 'Inverted Filtering', 'tablepress' ),
956 + 'description' => __( 'Turn the filtering into a search and hide the table if no search term is entered.', 'tablepress' ),
957 + 'category' => 'search-filter',
958 + 'class' => 'TablePress_Module_DataTables_Inverted_Filter',
959 + 'incompatible_classes' => array(),
960 + 'minimum_plan' => 'max',
961 + 'default_active' => false,
962 + ),
963 + 'datatables-pagination' => array(
964 + 'name' => __( 'Advanced Pagination Settings', 'tablepress' ),
965 + 'description' => __( 'Customize the pagination settings of the table.', 'tablepress' ),
966 + 'category' => 'frontend',
967 + 'class' => 'TablePress_Module_DataTables_Pagination',
968 + 'incompatible_classes' => array(),
969 + 'minimum_plan' => 'pro',
970 + 'default_active' => false,
971 + ),
972 + 'datatables-rowgroup' => array(
973 + 'name' => __( 'Row Grouping', 'tablepress' ),
974 + 'description' => __( 'Group table rows by a common keyword, category, or title.', 'tablepress' ),
975 + 'category' => 'frontend',
976 + 'class' => 'TablePress_Module_DataTables_RowGroup',
977 + 'incompatible_classes' => array( 'TablePress_DataTables_RowGroup' ),
978 + 'minimum_plan' => 'pro',
979 + 'default_active' => false,
980 + ),
981 + 'datatables-searchbuilder' => array(
982 + 'name' => __( 'Custom Search Builder', 'tablepress' ),
983 + 'description' => __( 'Show a search builder interface for filtering from groups and using conditions.', 'tablepress' ),
984 + 'category' => 'search-filter',
985 + 'class' => 'TablePress_Module_DataTables_SearchBuilder',
986 + 'incompatible_classes' => array(),
987 + 'minimum_plan' => 'max',
988 + 'default_active' => false,
989 + ),
990 + 'datatables-searchhighlight' => array(
991 + 'name' => __( 'Search Highlighting', 'tablepress' ),
992 + 'description' => __( 'Highlight found search terms in the table.', 'tablepress' ),
993 + 'category' => 'search-filter',
994 + 'class' => 'TablePress_Module_DataTables_SearchHighlight',
995 + 'incompatible_classes' => array(),
996 + 'minimum_plan' => 'pro',
997 + 'default_active' => false,
998 + ),
999 + 'datatables-searchpanes' => array(
1000 + 'name' => __( 'Search Panes', 'tablepress' ),
1001 + 'description' => __( 'Show panes for filtering the columns.', 'tablepress' ),
1002 + 'category' => 'search-filter',
1003 + 'class' => 'TablePress_Module_DataTables_SearchPanes',
1004 + 'incompatible_classes' => array(),
1005 + 'minimum_plan' => 'pro',
1006 + 'default_active' => false,
1007 + ),
1008 + 'datatables-serverside-processing' => array(
1009 + 'name' => __( 'Server-side Processing', 'tablepress' ),
1010 + 'description' => __( 'Process sorting, filtering, and pagination on the server for faster loading of large tables.', 'tablepress' ),
1011 + 'category' => 'backend',
1012 + 'class' => 'TablePress_Module_DataTables_ServerSide_Processing',
1013 + 'incompatible_classes' => array(),
1014 + 'minimum_plan' => 'max',
1015 + 'default_active' => true,
1016 + ),
1017 + 'default-style-customizer' => array(
1018 + 'name' => __( 'Default Style Customizer', 'tablepress' ),
1019 + 'description' => __( 'Change the default styling of your tables in the visual style customizer.', 'tablepress' ),
1020 + 'category' => 'frontend',
1021 + 'class' => 'TablePress_Module_Default_Style_Customizer',
1022 + 'incompatible_classes' => array(),
1023 + 'minimum_plan' => 'pro',
1024 + 'default_active' => true,
1025 + ),
1026 + 'responsive-tables' => array(
1027 + 'name' => __( 'Responsive Tables', 'tablepress' ),
1028 + 'description' => __( 'Make your tables look good on different screen sizes.', 'tablepress' ),
1029 + 'category' => 'frontend',
1030 + 'class' => 'TablePress_Module_Responsive_Tables',
1031 + 'incompatible_classes' => array( 'TablePress_Responsive_Tables' ),
1032 + 'minimum_plan' => 'pro',
1033 + 'default_active' => true,
1034 + ),
1035 + 'rest-api' => array(
1036 + 'name' => __( 'REST API', 'tablepress' ),
1037 + 'description' => __( 'Read table data via the WordPress REST API, e.g. in external apps.', 'tablepress' ),
1038 + 'category' => 'backend',
1039 + 'class' => 'TablePress_Module_REST_API',
1040 + 'incompatible_classes' => array( 'TablePress_REST_API_Controller' ),
1041 + 'minimum_plan' => 'max',
1042 + 'default_active' => false,
1043 + ),
1044 + 'row-filtering' => array(
1045 + 'name' => __( 'Row Filtering', 'tablepress' ),
1046 + 'description' => __( 'Show only table rows that contain defined keywords.', 'tablepress' ),
1047 + 'category' => 'data-management',
1048 + 'class' => 'TablePress_Module_Row_Filtering',
1049 + 'incompatible_classes' => array( 'TablePress_Row_Filter' ),
1050 + 'minimum_plan' => 'pro',
1051 + 'default_active' => true,
1052 + ),
1053 + 'row-highlighting' => array(
1054 + 'name' => __( 'Row Highlighting', 'tablepress' ),
1055 + 'description' => __( 'Add CSS classes to rows for highlighting based on their content.', 'tablepress' ),
1056 + 'category' => 'frontend',
1057 + 'class' => 'TablePress_Module_Row_Highlighting',
1058 + 'incompatible_classes' => array( 'TablePress_Row_Highlighting' ),
1059 + 'minimum_plan' => 'pro',
1060 + 'default_active' => false,
1061 + ),
1062 + 'row-order' => array(
1063 + 'name' => __( 'Row Order', 'tablepress' ),
1064 + 'description' => __( 'Order the rows in different ways when a table is shown.', 'tablepress' ),
1065 + 'category' => 'data-management',
1066 + 'class' => 'TablePress_Module_Row_Order',
1067 + 'incompatible_classes' => array( 'TablePress_Row_Order' ),
1068 + 'minimum_plan' => 'pro',
1069 + 'default_active' => false,
1070 + ),
1071 + );
563 1072 }
564 1073
565 1074 } // class TablePress