PluginProbe
TablePress – Tables in WordPress made easy / 3.2
TablePress – Tables in WordPress made easy v3.2
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 +345 -29 2.2.33.2 View file →
@@ -26,9 +26,9 @@
26 26 *
27 27 * @since 1.0.0
28 28 * @const string
29 29 */
30 - public const version = '2.2.3'; // phpcs:ignore Generic.NamingConventions.UpperCaseConstantName.ClassConstantNotUpperCase
30 + public const version = '3.2'; // 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 - public const db_version = 69; // phpcs:ignore Generic.NamingConventions.UpperCaseConstantName.ClassConstantNotUpperCase
40 + public const db_version = 113; // phpcs:ignore Generic.NamingConventions.UpperCaseConstantName.ClassConstantNotUpperCase
41 41
42 42 /**
43 43 * TablePress "table scheme" (data format structure) version.
44 44 *
@@ -53,27 +53,24 @@
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_Frontend_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,11 +85,10 @@
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 /**
98 93 * List of TablePress premium modules.
99 94 *
@@ -99,9 +94,9 @@
99 94 *
100 95 * @since 2.1.0
101 96 * @var array<string, array{name: string, description: string, category: string, class: string, incompatible_classes: string[], minimum_plan: string, default_active: bool}>
102 97 */
103 - public static $modules = array();
98 + public static array $modules = array();
104 99
105 100 /**
106 101 * Start-up TablePress (run on WordPress "init") and load the controller for the current state.
107 102 *
@@ -158,9 +153,9 @@
158 153
159 154 if ( is_admin() ) {
160 155 $controller = 'admin';
161 156 if ( wp_doing_ajax() ) {
162 - $controller .= '_ajax';
157 + $controller = 'admin_ajax';
163 158 }
164 159 self::load_controller( $controller );
165 160 }
166 161 // Load the frontend controller in all scenarios, so that Shortcode render functions are always available.
@@ -165,8 +160,13 @@
165 160 }
166 161 // Load the frontend controller in all scenarios, so that Shortcode render functions are always available.
167 162 self::$controller = self::load_controller( 'frontend' );
168 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 +
169 169 /**
170 170 * Fires after TablePress is loaded.
171 171 *
172 172 * The `tablepress_run` action hook can be used if code has to run before TablePress is loaded.
@@ -328,13 +328,14 @@
328 328 * @param string $column Column string.
329 329 * @return int Column number, 1-based.
330 330 */
331 331 public static function letter_to_number( string $column ): int {
332 + $column = (string) preg_replace( '/[^A-Za-z]/', '', $column );
332 333 $column = strtoupper( $column );
333 334 $count = strlen( $column );
334 335 $number = 0;
335 336 for ( $i = 0; $i < $count; $i++ ) {
336 - $number += ( ord( $column[ $count - 1 - $i ] ) - 64 ) * pow( 26, $i );
337 + $number += ( ord( $column[ $count - 1 - $i ] ) - 64 ) * 26 ** $i;
337 338 }
338 339 return $number;
339 340 }
340 341
@@ -368,13 +369,18 @@
368 369 */
369 370 public static function format_datetime( string $datetime_string, string $separator_or_format = ' ' ): string {
370 371 $timezone = wp_timezone();
371 372 $datetime = date_create( $datetime_string, $timezone );
372 - $timestamp = $datetime->getTimestamp(); // @phpstan-ignore-line
373 + if ( false === $datetime ) {
374 + return $datetime_string;
375 + }
376 + $timestamp = $datetime->getTimestamp();
373 377
374 378 switch ( $separator_or_format ) {
375 379 case ' ':
376 380 case '<br />':
381 + case '<br/>':
382 + case '<br>':
377 383 $date = wp_date( get_option( 'date_format' ), $timestamp, $timezone );
378 384 $time = wp_date( get_option( 'time_format' ), $timestamp, $timezone );
379 385 $output = "{$date}{$separator_or_format}{$time}";
380 386 break;
@@ -395,9 +401,10 @@
395 401 * @return string Nickname of the WP user with the $user_id.
396 402 */
397 403 public static function get_user_display_name( int $user_id ): string {
398 404 $user = get_userdata( $user_id );
399 - return ( isset( $user->display_name ) ) ? $user->display_name : sprintf( '<em>%s</em>', __( 'unknown', 'tablepress' ) );
405 + /* translators: %s: Label for unknown user */
406 + return $user->display_name ?? sprintf( '<em>%s</em>', __( 'unknown', 'tablepress' ) );
400 407 }
401 408
402 409 /**
403 410 * Sanitizes a CSS class to ensure it only contains valid characters.
@@ -418,8 +425,256 @@
418 425 return $sanitized_css_class;
419 426 }
420 427
421 428 /**
429 + * Extracts the top-level keys from a JavaScript object string.
430 + *
431 + * This function is used to extract the keys of the "Custom Commands" JavaScript object string, to check for overrides.
432 + * It covers most cases, like normal object properties with and without quotes, shorthand properties, and shorthand methods,
433 + * and also ignores single-line and multi-line comments.
434 + * It does not cover all possible JavaScript syntax (like template literals, special characters, ...),
435 + * but should be sufficient for the use case.
436 + *
437 + * @since 3.0.0
438 + *
439 + * @param string $js_object_string A JavaScript object as a string.
440 + * @return string[] Array of top-level keys of the object.
441 + */
442 + public static function extract_keys_from_js_object_string( string $js_object_string ): array {
443 + $object_keys = array();
444 + $length = strlen( $js_object_string );
445 + $depth = 0;
446 + $key_expected = true;
447 + $in_quotes = false;
448 + $quote_char = '';
449 + $in_function_declaration = false;
450 + $in_single_line_comment = false;
451 + $in_multi_line_comment = false;
452 + $object_key = '';
453 +
454 + for ( $i = 0; $i < $length; $i++ ) {
455 + $char = $js_object_string[ $i ];
456 +
457 + // Skip parsing single-line comments.
458 + if ( $in_single_line_comment ) {
459 + if ( "\n" === $char ) {
460 + $in_single_line_comment = false;
461 + }
462 + continue;
463 + } else { // phpcs:ignore Universal.ControlStructures.DisallowLonelyIf.Found
464 + if ( '/' === $char && $i + 1 < $length && '/' === $js_object_string[ $i + 1 ] ) {
465 + $in_single_line_comment = true;
466 + ++$i; // Skip the second '/'.
467 + continue;
468 + }
469 + }
470 +
471 + // Skip parsing multi-line comments.
472 + if ( $in_multi_line_comment ) {
473 + if ( '*' === $char && $i + 1 < $length && '/' === $js_object_string[ $i + 1 ] ) {
474 + $in_multi_line_comment = false;
475 + ++$i; // Skip the '/' that ends the multi-line comment.
476 + }
477 + continue;
478 + } else { // phpcs:ignore Universal.ControlStructures.DisallowLonelyIf.Found
479 + if ( '/' === $char && $i + 1 < $length && '*' === $js_object_string[ $i + 1 ] ) {
480 + $in_multi_line_comment = true;
481 + ++$i; // Skip the '*'.
482 + continue;
483 + }
484 + }
485 +
486 + // Skip parsing while inside a quoted string.
487 + if ( $in_quotes ) {
488 + if ( $quote_char === $char ) {
489 + $in_quotes = false;
490 + }
491 + continue;
492 + } else { // phpcs:ignore Universal.ControlStructures.DisallowLonelyIf.Found
493 + if ( '"' === $char || "'" === $char ) {
494 + $in_quotes = true;
495 + $quote_char = $char;
496 + continue;
497 + }
498 + }
499 +
500 + /*
501 + * Skip parsing while inside a `function abc( ... )` declaration string.
502 + * The `$key_expected` check limits search the "function" string to object values.
503 + * The check for the plain `f` reduces expensive `substr()` calls.
504 + */
505 + if ( ! $key_expected ) {
506 + if ( $in_function_declaration ) {
507 + if ( ')' === $char ) {
508 + $in_function_declaration = false;
509 + }
510 + continue;
511 + } else { // phpcs:ignore Universal.ControlStructures.DisallowLonelyIf.Found
512 + if ( 'f' === $char && 'function' === substr( $js_object_string, $i, 8 ) ) {
513 + $in_function_declaration = true;
514 + $i += 7; // Skip the rest of the "function" string.
515 + continue;
516 + }
517 + }
518 + }
519 +
520 + // Handle object depth, so that most parsing can be limited to the top level.
521 + if ( '{' === $char || '[' === $char ) {
522 + ++$depth;
523 + }
524 +
525 + // Extract only keys at the top level.
526 + if ( 1 === $depth ) {
527 + if ( $key_expected ) {
528 + if ( ':' === $char ) {
529 + // Check for normal keys, with value after :.
530 +
531 + // Go backwards to find the start of the key.
532 + $j = $i - 1;
533 + while ( $j >= 0 && preg_match( '/\s/', $js_object_string[ $j ] ) ) {
534 + --$j;
535 + }
536 + $key_end = $j; // Position of the last character of the key (potentially with quote).
537 + if ( '"' === $js_object_string[ $j ] || "'" === $js_object_string[ $j ] ) {
538 + // Quoted key.
539 + $quote_char = $js_object_string[ $j ];
540 + --$j;
541 + while ( $j >= 0 && $quote_char !== $js_object_string[ $j ] ) {
542 + --$j;
543 + }
544 + $key_start = $j + 1;
545 + } else {
546 + // Unquoted key.
547 + while ( $j >= 0 && preg_match( '/[\w]/', $js_object_string[ $j ] ) ) {
548 + --$j;
549 + }
550 + $key_start = $j + 1;
551 + }
552 + $object_key = substr( $js_object_string, $key_start, $key_end - $key_start + 1 );
553 + $object_key = trim( $object_key, "\"'" );
554 + if ( '' !== $object_key && ! in_array( $object_key, $object_keys, true ) ) {
555 + $object_keys[] = $object_key;
556 + }
557 + $key_expected = false;
558 + } elseif ( ( ',' === $char || '}' === $char ) ) { // The `}` case is for the last key.
559 + // Check for shorthand properties (which must be unquoted).
560 +
561 + // Go backwards to find the start of the shorthand key.
562 + $j = $i - 1;
563 + while ( $j >= 0 && preg_match( '/\s/', $js_object_string[ $j ] ) ) {
564 + --$j;
565 + }
566 + $key_end = $j; // Position of the last character of the key (without a quote).
567 + while ( $j >= 0 && preg_match( '/[\w]/', $js_object_string[ $j ] ) ) {
568 + --$j;
569 + }
570 + $key_start = $j + 1;
571 + $object_key = substr( $js_object_string, $key_start, $key_end - $key_start + 1 );
572 + if ( '' !== $object_key && ! in_array( $object_key, $object_keys, true ) ) {
573 + $object_keys[] = $object_key;
574 + }
575 + } elseif ( '(' === $char ) {
576 + // Detect shorthand method definitions.
577 +
578 + // Go back to find the start of the method name.
579 + $j = $i - 1;
580 + while ( $j >= 0 && preg_match( '/\s/', $js_object_string[ $j ] ) ) {
581 + --$j;
582 + }
583 + $key_end = $j;
584 + while ( $j >= 0 && preg_match( '/[\w]/', $js_object_string[ $j ] ) ) {
585 + --$j;
586 + }
587 + $key_start = $j + 1;
588 + $object_key = substr( $js_object_string, $key_start, $key_end - $key_start + 1 );
589 + if ( '' !== $object_key && ! in_array( $object_key, $object_keys, true ) ) {
590 + $object_keys[] = $object_key;
591 + }
592 + }
593 + }
594 +
595 + // Reset the "key expected" flag after a comma or closing brace.
596 + if ( ',' === $char || '}' === $char ) {
597 + $key_expected = true;
598 + }
599 + }
600 +
601 + // Handle object depth.
602 + if ( '}' === $char || ']' === $char ) {
603 + --$depth;
604 + }
605 + }
606 +
607 + return $object_keys;
608 + }
609 +
610 + /**
611 + * Converts old DataTables 1.x CSS classes and parameters to the DataTables 2 variants.
612 + *
613 + * This function is used to modernize "Custom CSS" and "Custom Commands" for compatibility with DataTables 2.x.
614 + * It probably does not catch all possible cases.
615 + *
616 + * @since 3.0.0
617 + *
618 + * @param string $code Code that contains DataTables 1.x CSS classes and parameters.
619 + * @return string Updated code with DataTables 2.x CSS classes and parameters.
620 + */
621 + public static function convert_datatables_api_data( string $code ): string {
622 + /**
623 + * Mappings for DataTables 1.x CSS class or parameter to DataTables 2 variants.
624 + * As this array is used in `strtr()`, it's pre-sorted for descending string length of the array keys.
625 + */
626 + static $datatables_api_data_mappings = array(
627 + // CSS classes.
628 + '.tablepress thead .sorting:hover' => '.tablepress thead .dt-orderable-asc:hover,.tablepress thead .dt-orderable-desc:hover',
629 + '.tablepress thead .sorting_desc' => '.tablepress thead .dt-ordering-desc',
630 + '.dataTables_filter label input' => '.dt-container .dt-search input',
631 + '.tablepress thead .sorting_asc' => '.tablepress thead .dt-ordering-asc',
632 + '.dataTables_scrollFootInner' => '.dt-scroll-footInner',
633 + '.dataTables_scrollHeadInner' => '.dt-scroll-headInner',
634 + '.tablepress thead .sorting' => '.tablepress thead .dt-orderable-asc,.tablepress thead .dt-orderable-desc',
635 + '.dataTables_processing' => '.dt-processing',
636 + '.dataTables_scrollBody' => '.dt-scroll-body',
637 + '.dataTables_scrollFoot' => '.dt-scroll-foot',
638 + '.dataTables_scrollHead' => '.dt-scroll-head',
639 + '.dataTables_paginate' => '.dt-paging',
640 + '.tablepress .even td' => '.tablepress>:where(tbody.row-striping)>:nth-child(odd)>*',
641 + '.dataTables_wrapper' => '.dt-container',
642 + '.tablepress .odd td' => '.tablepress>:where(tbody.row-striping)>:nth-child(even)>*',
643 + '.dataTables_filter' => '.dt-search',
644 + '.dataTables_length' => '.dt-length',
645 + '.dataTables_scroll' => '.dt-scroll',
646 + '.dataTables_empty' => '.dt-empty',
647 + '.dataTables_info' => '.dt-info',
648 + '.paginate_button' => '.dt-paging-button',
649 + // DataTables API functions.
650 + '$.fn.dataTable.' => 'DataTable.',
651 + );
652 + $code = strtr( $code, $datatables_api_data_mappings );
653 +
654 + // HTML ID mappings, which were removed.
655 + if ( str_contains( $code, '#tablepress-' ) ) {
656 + $code = (string) preg_replace(
657 + array(
658 + '/#tablepress-([A-Za-z1-9_-]|[A-Za-z0-9_-]{2,})_paginate/',
659 + '/#tablepress-([A-Za-z1-9_-]|[A-Za-z0-9_-]{2,})_filter/',
660 + '/#tablepress-([A-Za-z1-9_-]|[A-Za-z0-9_-]{2,})_length/',
661 + '/#tablepress-([A-Za-z1-9_-]|[A-Za-z0-9_-]{2,})_info/',
662 + ),
663 + array(
664 + '#tablepress-$1_wrapper .dt-paging',
665 + '#tablepress-$1_wrapper .dt-search',
666 + '#tablepress-$1_wrapper .dt-length',
667 + '#tablepress-$1_wrapper .dt-info',
668 + ),
669 + $code,
670 + );
671 + }
672 +
673 + return $code;
674 + }
675 +
676 + /**
422 677 * Retrieves all information of a WP_Error object as a string.
423 678 *
424 679 * @since 1.4.0
425 680 *
@@ -438,10 +693,16 @@
438 693 if ( ! empty( $error_messages ) ) {
439 694 $error_strings[ $error_code ] .= " ({$error_messages})";
440 695 }
441 696 $error_data = $wp_error->get_error_data( $error_code );
442 - if ( ! is_null( $error_data ) ) {
697 + if ( is_string( $error_data ) ) {
443 698 $error_strings[ $error_code ] .= " [{$error_data}]";
699 + } elseif ( is_array( $error_data ) ) {
700 + foreach ( $error_data as $key => $value ) {
701 + $error_data[ $key ] = "{$key}: {$value}";
702 + }
703 + $error_data = implode( ', ', $error_data );
704 + $error_strings[ $error_code ] .= " [{$error_data}]";
444 705 }
445 706 }
446 707 return implode( ";\n", $error_strings );
447 708 }
@@ -517,22 +778,25 @@
517 778 exit;
518 779 }
519 780
520 781 /**
521 - * Determines whether the site uses the block editor, so that certain text and input fields referring to Shortcodes can be displayed or not.
782 + * Determines the editor that the site uses, so that certain text and input fields referring to Shortcodes can be displayed or not.
522 783 *
523 - * @since 2.0.1
784 + * @since 3.1.0
524 785 *
525 - * @return bool True if the site uses the block editor, false otherwise.
786 + * @return string The editor that the site uses, either "block", "elementor", or "other".
526 787 */
527 - public static function site_uses_block_editor(): bool {
788 + public static function site_used_editor(): string {
789 + if ( is_plugin_active( 'elementor/elementor.php' ) ) {
790 + return 'elementor';
791 + }
792 +
793 + // Checking for Elementor is not needed anymore in this condition.
528 794 $site_uses_block_editor = use_block_editor_for_post_type( 'post' )
529 - && ! is_plugin_active( 'beaver-builder-lite-version/fl-builder.php' )
530 795 && ! is_plugin_active( 'classic-editor/classic-editor.php' )
531 796 && ! is_plugin_active( 'classic-editor-addon/classic-editor-addon.php' )
532 - && ! is_plugin_active( 'elementor/elementor.php' )
533 - && ! is_plugin_active( 'siteorigin-panels/siteorigin-panels.php' );
534 -
797 + && ! is_plugin_active( 'siteorigin-panels/siteorigin-panels.php' )
798 + && ! is_plugin_active( 'beaver-builder-lite-version/fl-builder.php' );
535 799 /**
536 800 * Filters the outcome of the check whether the site uses the block editor.
537 801 *
538 802 * This can be used when certain conditions (e.g. new site builders) are not (yet) accounted for.
@@ -541,10 +805,13 @@
541 805 *
542 806 * @param bool $site_uses_block_editor True if the site uses the block editor, false otherwise.
543 807 */
544 808 $site_uses_block_editor = (bool) apply_filters( 'tablepress_site_uses_block_editor', $site_uses_block_editor );
809 + if ( $site_uses_block_editor ) {
810 + return 'block';
811 + }
545 812
546 - return $site_uses_block_editor;
813 + return 'other';
547 814 }
548 815
549 816 /**
550 817 * Initializes the list of TablePress premium modules.
@@ -551,8 +818,12 @@
551 818 *
552 819 * @since 2.1.0
553 820 */
554 821 public static function init_modules(): void {
822 + if ( ! empty( self::$modules ) ) {
823 + return;
824 + }
825 +
555 826 self::$modules = array(
556 827 'advanced-access-rights' => array(
557 828 'name' => __( 'Advanced Access Rights', 'tablepress' ),
558 829 'description' => __( 'Restrict access to individual tables for individual users.', 'tablepress' ),
@@ -625,9 +896,9 @@
625 896 'minimum_plan' => 'pro',
626 897 'default_active' => false,
627 898 ),
628 899 'datatables-buttons' => array(
629 - 'name' => __( 'Buttons', 'tablepress' ),
900 + 'name' => __( 'User Action Buttons', 'tablepress' ),
630 901 'description' => __( 'Add buttons for downloading, copying, printing, and changing column visibility of tables.', 'tablepress' ),
631 902 'category' => 'frontend',
632 903 'class' => 'TablePress_Module_DataTables_Buttons',
633 904 'incompatible_classes' => array( 'TablePress_DataTables_Buttons' ),
@@ -652,9 +923,9 @@
652 923 'minimum_plan' => 'pro',
653 924 'default_active' => false,
654 925 ),
655 926 'datatables-counter-column' => array(
656 - 'name' => __( 'Counter Column', 'tablepress' ),
927 + 'name' => __( 'Index Column', 'tablepress' ),
657 928 'description' => __( 'Make the first column an index or counter column with the row position.', 'tablepress' ),
658 929 'category' => 'frontend',
659 930 'class' => 'TablePress_Module_DataTables_Counter_Column',
660 931 'incompatible_classes' => array(),
@@ -672,8 +943,44 @@
672 943 ),
673 944 'minimum_plan' => 'pro',
674 945 'default_active' => true,
675 946 ),
947 + 'datatables-layout' => array(
948 + 'name' => __( 'Table Layout', 'tablepress' ),
949 + 'description' => __( 'Customize the layout and position of features around a table.', 'tablepress' ),
950 + 'category' => 'frontend',
951 + 'class' => 'TablePress_Module_DataTables_Layout',
952 + 'incompatible_classes' => array(),
953 + 'minimum_plan' => 'pro',
954 + 'default_active' => true,
955 + ),
956 + 'datatables-fuzzysearch' => array(
957 + 'name' => __( 'Fuzzy Search', 'tablepress' ),
958 + 'description' => __( 'Let the search account for spelling mistakes and typos and find similar matches.', 'tablepress' ),
959 + 'category' => 'search-filter',
960 + 'class' => 'TablePress_Module_DataTables_FuzzySearch',
961 + 'incompatible_classes' => array(),
962 + 'minimum_plan' => 'max',
963 + 'default_active' => false,
964 + ),
965 + 'datatables-inverted-filter' => array(
966 + 'name' => __( 'Inverted Filtering', 'tablepress' ),
967 + 'description' => __( 'Turn the filtering into a search and hide the table if no search term is entered.', 'tablepress' ),
968 + 'category' => 'search-filter',
969 + 'class' => 'TablePress_Module_DataTables_Inverted_Filter',
970 + 'incompatible_classes' => array(),
971 + 'minimum_plan' => 'max',
972 + 'default_active' => false,
973 + ),
974 + 'datatables-pagination' => array(
975 + 'name' => __( 'Advanced Pagination Settings', 'tablepress' ),
976 + 'description' => __( 'Customize the pagination settings of the table.', 'tablepress' ),
977 + 'category' => 'frontend',
978 + 'class' => 'TablePress_Module_DataTables_Pagination',
979 + 'incompatible_classes' => array(),
980 + 'minimum_plan' => 'pro',
981 + 'default_active' => false,
982 + ),
676 983 'datatables-rowgroup' => array(
677 984 'name' => __( 'Row Grouping', 'tablepress' ),
678 985 'description' => __( 'Group table rows by a common keyword, category, or title.', 'tablepress' ),
679 986 'category' => 'frontend',
@@ -725,8 +1032,17 @@
725 1032 'class' => 'TablePress_Module_Default_Style_Customizer',
726 1033 'incompatible_classes' => array(),
727 1034 'minimum_plan' => 'pro',
728 1035 'default_active' => true,
1036 + ),
1037 + 'email-notifications' => array(
1038 + 'name' => __( 'Email Notifications', 'tablepress' ),
1039 + 'description' => __( 'Get email notifications when certain actions are performed on tables.', 'tablepress' ),
1040 + 'category' => 'backend',
1041 + 'class' => 'TablePress_Module_Email_Notifications',
1042 + 'incompatible_classes' => array(),
1043 + 'minimum_plan' => 'max',
1044 + 'default_active' => false,
729 1045 ),
730 1046 'responsive-tables' => array(
731 1047 'name' => __( 'Responsive Tables', 'tablepress' ),
732 1048 'description' => __( 'Make your tables look good on different screen sizes.', 'tablepress' ),