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 +724 -103 1.9.23.0 View file →
@@ -11,8 +11,9 @@
11 11 defined( 'ABSPATH' ) || die( 'No direct script access allowed!' );
12 12
13 13 /**
14 14 * TablePress class
15 + *
15 16 * @package TablePress
16 17 * @author Tobias Bäthge
17 18 * @since 1.0.0
18 19 */
@@ -25,9 +26,9 @@
25 26 *
26 27 * @since 1.0.0
27 28 * @const string
28 29 */
29 - const version = '1.9.2';
30 + public const version = '3.0'; // phpcs:ignore Generic.NamingConventions.UpperCaseConstantName.ClassConstantNotUpperCase
30 31
31 32 /**
32 33 * TablePress internal plugin version ("options scheme" version).
33 34 *
@@ -35,9 +36,9 @@
35 36 *
36 37 * @since 1.0.0
37 38 * @const int
38 39 */
39 - const db_version = 38;
40 + public const db_version = 96; // phpcs:ignore Generic.NamingConventions.UpperCaseConstantName.ClassConstantNotUpperCase
40 41
41 42 /**
42 43 * TablePress "table scheme" (data format structure) version.
43 44 *
@@ -46,33 +47,30 @@
46 47 *
47 48 * @since 1.0.0
48 49 * @const int
49 50 */
50 - const table_scheme_version = 3;
51 + public const table_scheme_version = 3; // phpcs:ignore Generic.NamingConventions.UpperCaseConstantName.ClassConstantNotUpperCase
51 52
52 53 /**
53 54 * Instance of the Options Model.
54 55 *
55 56 * @since 1.3.0
56 - * @var TablePress_Options_Model
57 57 */
58 - public static $model_options;
58 + public static \TablePress_Options_Model $model_options;
59 59
60 60 /**
61 61 * Instance of the Table Model.
62 62 *
63 63 * @since 1.3.0
64 - * @var TablePress_Table_Model
65 64 */
66 - public static $model_table;
65 + public static \TablePress_Table_Model $model_table;
67 66
68 67 /**
69 68 * Instance of the controller.
70 69 *
71 70 * @since 1.0.0
72 - * @var TablePress_*_Controller
73 71 */
74 - public static $controller;
72 + public static \TablePress_Frontend_Controller $controller;
75 73
76 74 /**
77 75 * Name of the Shortcode to show a TablePress table.
78 76 *
@@ -78,11 +76,10 @@
78 76 *
79 77 * Should only be modified through the filter hook 'tablepress_table_shortcode'.
80 78 *
81 79 * @since 1.0.0
82 - * @var string
83 80 */
84 - public static $shortcode = 'table';
81 + public static string $shortcode = 'table';
85 82
86 83 /**
87 84 * Name of the Shortcode to show extra information of a TablePress table.
88 85 *
@@ -88,45 +85,36 @@
88 85 *
89 86 * Should only be modified through the filter hook 'tablepress_table_info_shortcode'.
90 87 *
91 88 * @since 1.0.0
92 - * @var string
93 89 */
94 - public static $shortcode_info = 'table-info';
90 + public static string $shortcode_info = 'table-info';
95 91
96 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 + /**
97 101 * Start-up TablePress (run on WordPress "init") and load the controller for the current state.
98 102 *
99 103 * @since 1.0.0
100 104 */
101 - public static function run() {
105 + public static function run(): void {
102 106 /**
103 - * Fires when TablePress is loaded.
107 + * Fires before TablePress is loaded.
104 108 *
109 + * The `tablepress_loaded` action hook might be a better choice in most situations, as TablePress options will then be available.
110 + *
105 111 * @since 1.0.0
106 112 */
107 113 do_action( 'tablepress_run' );
108 114
109 - // Exit early if TablePress doesn't have to be loaded.
110 - if ( ( 'wp-login.php' === basename( $_SERVER['SCRIPT_FILENAME'] ) ) // Login screen
111 - || ( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST )
112 - || ( defined( 'DOING_CRON' ) && DOING_CRON ) ) {
113 - return;
114 - }
115 -
116 - // Check if minimum requirements are fulfilled, currently WordPress 4.9.1.
117 - include( ABSPATH . WPINC . '/version.php' ); // Include an unmodified $wp_version.
118 - if ( version_compare( str_replace( '-src', '', $wp_version ), '4.9.1', '<' ) ) {
119 - // Show error notice to admins, if WP is not installed in the minimum required version, in which case TablePress will not work.
120 - if ( current_user_can( 'update_plugins' ) ) {
121 - add_action( 'admin_notices', array( 'TablePress', 'show_minimum_requirements_error_notice' ) );
122 - }
123 - // And exit TablePress.
124 - return;
125 - }
126 -
127 115 /**
128 - * Filter the string that is used as the [table] Shortcode.
116 + * Filters the string that is used as the [table] Shortcode.
129 117 *
130 118 * @since 1.0.0
131 119 *
132 120 * @param string $shortcode The [table] Shortcode string.
@@ -132,9 +120,9 @@
132 120 * @param string $shortcode The [table] Shortcode string.
133 121 */
134 122 self::$shortcode = apply_filters( 'tablepress_table_shortcode', self::$shortcode );
135 123 /**
136 - * Filter the string that is used as the [table-info] Shortcode.
124 + * Filters the string that is used as the [table-info] Shortcode.
137 125 *
138 126 * @since 1.0.0
139 127 *
140 128 * @param string $shortcode_info The [table-info] Shortcode string.
@@ -144,17 +132,49 @@
144 132 // Load modals for table and options, to be accessible from everywhere via `TablePress::$model_options` and `TablePress::$model_table`.
145 133 self::$model_options = self::load_model( 'options' );
146 134 self::$model_table = self::load_model( 'table' );
147 135
136 + // Exit early, i.e. before a controller is loaded, if TablePress functionality is likely not needed.
137 + $exit_early = false;
138 + if ( ( isset( $_SERVER['SCRIPT_FILENAME'] ) && 'wp-login.php' === basename( $_SERVER['SCRIPT_FILENAME'] ) ) // Detect the WordPress Login screen.
139 + || ( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST )
140 + || wp_doing_cron() ) {
141 + $exit_early = true;
142 + }
143 + /**
144 + * Filters whether TablePress should exit early, e.g. during wp-login.php, XML-RPC, and WP-Cron requests.
145 + *
146 + * @since 2.0.0
147 + *
148 + * @param bool $exit_early Whether TablePress should exit early.
149 + */
150 + if ( apply_filters( 'tablepress_exit_early', $exit_early ) ) {
151 + return;
152 + }
153 +
148 154 if ( is_admin() ) {
149 155 $controller = 'admin';
150 - if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
151 - $controller .= '_ajax';
156 + if ( wp_doing_ajax() ) {
157 + $controller = 'admin_ajax';
152 158 }
153 - } else {
154 - $controller = 'frontend';
159 + self::load_controller( $controller );
155 160 }
156 - self::$controller = self::load_controller( $controller );
161 + // Load the frontend controller in all scenarios, so that Shortcode render functions are always available.
162 + self::$controller = self::load_controller( 'frontend' );
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 + /**
170 + * Fires after TablePress is loaded.
171 + *
172 + * The `tablepress_run` action hook can be used if code has to run before TablePress is loaded.
173 + *
174 + * @since 2.0.0
175 + */
176 + do_action( 'tablepress_loaded' );
157 177 }
158 178
159 179 /**
160 180 * Load a file with require_once(), after running it through a filter.
@@ -160,15 +180,15 @@
160 180 * Load a file with require_once(), after running it through a filter.
161 181 *
162 182 * @since 1.0.0
163 183 *
164 - * @param string $file Name of the PHP file with the class.
165 - * @param string $folder Name of the folder with $class's $file.
184 + * @param string $file Name of the PHP file.
185 + * @param string $folder Name of the folder with the file.
166 186 */
167 - public static function load_file( $file, $folder ) {
187 + public static function load_file( string $file, string $folder ): void {
168 188 $full_path = TABLEPRESS_ABSPATH . $folder . '/' . $file;
169 189 /**
170 - * Filter the full path of a file that shall be loaded.
190 + * Filters the full path of a file that shall be loaded.
171 191 *
172 192 * @since 1.0.0
173 193 *
174 194 * @param string $full_path Full path of the file that shall be loaded.
@@ -181,32 +201,32 @@
181 201 }
182 202 }
183 203
184 204 /**
185 - * Create a new instance of the $class, which is stored in $file in the $folder subfolder
205 + * Create a new instance of the $class_name, which is stored in $file in the $folder subfolder
186 206 * of the plugin's directory.
187 207 *
188 208 * @since 1.0.0
189 209 *
190 - * @param string $class Name of the class.
191 - * @param string $file Name of the PHP file with the class.
192 - * @param string $folder Name of the folder with $class's $file.
193 - * @param mixed $params Optional. Parameters that are passed to the constructor of $class.
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.
194 214 * @return object Initialized instance of the class.
195 215 */
196 - public static function load_class( $class, $file, $folder, $params = null ) {
216 + public static function load_class( string $class_name, string $file, string $folder, /* ?array|string */ $params = null ): object {
197 217 /**
198 - * Filter name of the class that shall be loaded.
218 + * Filters name of the class that shall be loaded.
199 219 *
200 220 * @since 1.0.0
201 221 *
202 - * @param string $class Name of the class that shall be loaded.
222 + * @param string $class_name Name of the class that shall be loaded.
203 223 */
204 - $class = apply_filters( 'tablepress_load_class_name', $class );
205 - if ( ! class_exists( $class, false ) ) {
224 + $class_name = apply_filters( 'tablepress_load_class_name', $class_name );
225 + if ( ! class_exists( $class_name, false ) ) {
206 226 self::load_file( $file, $folder );
207 227 }
208 - $the_class = new $class( $params );
228 + $the_class = new $class_name( $params );
209 229 return $the_class;
210 230 }
211 231
212 232 /**
@@ -216,9 +236,9 @@
216 236 *
217 237 * @param string $model Name of the model.
218 238 * @return object Instance of the initialized model.
219 239 */
220 - public static function load_model( $model ) {
240 + public static function load_model( string $model ): object {
221 241 // Model Base Class.
222 242 self::load_file( 'class-model.php', 'classes' );
223 243 // Make first letter uppercase for a better looking naming pattern.
224 244 $ucmodel = ucfirst( $model );
@@ -230,13 +250,13 @@
230 250 * Create a new instance of the $view, which is stored in the "views" subfolder, and set it up with $data.
231 251 *
232 252 * @since 1.0.0
233 253 *
234 - * @param string $view Name of the view to load.
235 - * @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.
236 256 * @return object Instance of the initialized view, already set up, just needs to be rendered.
237 257 */
238 - public static function load_view( $view, array $data = array() ) {
258 + public static function load_view( string $view, array $data = array() ): object {
239 259 // View Base Class.
240 260 self::load_file( 'class-view.php', 'classes' );
241 261 // Make first letter uppercase for a better looking naming pattern.
242 262 $ucview = ucfirst( $view );
@@ -252,9 +272,9 @@
252 272 *
253 273 * @param string $controller Name of the controller.
254 274 * @return object Instance of the initialized controller.
255 275 */
256 - public static function load_controller( $controller ) {
276 + public static function load_controller( string $controller ): object {
257 277 // Controller Base Class.
258 278 self::load_file( 'class-controller.php', 'classes' );
259 279 // Make first letter uppercase for a better looking naming pattern.
260 280 $uccontroller = ucfirst( $controller );
@@ -266,13 +286,13 @@
266 286 * Generate the complete nonce string, from the nonce base, the action and an item, e.g. tablepress_delete_table_3.
267 287 *
268 288 * @since 1.0.0
269 289 *
270 - * @param string $action Action for which the nonce is needed.
271 - * @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.
272 292 * @return string The resulting nonce string.
273 293 */
274 - public static function nonce( $action, $item = false ) {
294 + public static function nonce( string $action, /* string|false */ $item = false ): string {
275 295 $nonce = "tablepress_{$action}";
276 296 if ( $item ) {
277 297 $nonce .= "_{$item}";
278 298 }
@@ -283,14 +303,14 @@
283 303 * Check whether a nonce string is valid.
284 304 *
285 305 * @since 1.0.0
286 306 *
287 - * @param string $action Action for which the nonce should be checked.
288 - * @param string|bool $item Optional. Item for which the action should be performed, like "table".
289 - * @param string $query_arg Optional. Name of the nonce query string argument in $_POST.
290 - * @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.
291 311 */
292 - 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 {
293 313 $nonce_action = self::nonce( $action, $item );
294 314 if ( $ajax ) {
295 315 check_ajax_referer( $nonce_action, $query_arg );
296 316 } else {
@@ -305,16 +325,17 @@
305 325 *
306 326 * @since 1.0.0
307 327 *
308 328 * @param string $column Column string.
309 - * @return int $number Column number, 1-based.
329 + * @return int Column number, 1-based.
310 330 */
311 - 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 );
312 333 $column = strtoupper( $column );
313 334 $count = strlen( $column );
314 335 $number = 0;
315 336 for ( $i = 0; $i < $count; $i++ ) {
316 - $number += ( ord( $column[ $count - 1 - $i ] ) - 64 ) * pow( 26, $i );
337 + $number += ( ord( $column[ $count - 1 - $i ] ) - 64 ) * 26 ** $i;
317 338 }
318 339 return $number;
319 340 }
320 341
@@ -325,15 +346,15 @@
325 346 *
326 347 * @since 1.0.0
327 348 *
328 349 * @param int $number Column number, 1-based.
329 - * @return string $column Column string.
350 + * @return string Column string.
330 351 */
331 - public static function number_to_letter( $number ) {
352 + public static function number_to_letter( int $number ): string {
332 353 $column = '';
333 354 while ( $number > 0 ) {
334 355 $column = chr( 65 + ( ( $number - 1 ) % 26 ) ) . $column;
335 - $number = floor( ( $number - 1 ) / 26 );
356 + $number = intdiv( $number - 1, 26 );
336 357 }
337 358 return $column;
338 359 }
339 360
@@ -339,45 +360,364 @@
339 360
340 361 /**
341 362 * Get a nice looking date and time string from the mySQL format of datetime strings for output.
342 363 *
343 - * @param string $datetime DateTime string in mySQL format or a Unix timestamp.
344 - * @param string $type Optional. Type of $datetime, 'mysql' or 'timestamp'.
345 - * @param string $separator Optional. Separator between date and time.
364 + * @since 1.0.0
365 + *
366 + * @param string $datetime_string DateTime string, often in mySQL format..
367 + * @param string $separator_or_format Optional. Separator between date and time, or format string.
346 368 * @return string Nice looking string with the date and time.
347 369 */
348 - public static function format_datetime( $datetime, $type = 'mysql', $separator = ' ' ) {
349 - // @TODO: Maybe change from using the stored WP Options to translated date/time schemes, like in https://core.trac.wordpress.org/changeset/35811.
350 - if ( 'mysql' === $type ) {
351 - return mysql2date( get_option( 'date_format' ), $datetime ) . $separator . mysql2date( get_option( 'time_format' ), $datetime );
352 - } else {
353 - return date_i18n( get_option( 'date_format' ), $datetime ) . $separator . date_i18n( get_option( 'time_format' ), $datetime );
370 + public static function format_datetime( string $datetime_string, string $separator_or_format = ' ' ): string {
371 + $timezone = wp_timezone();
372 + $datetime = date_create( $datetime_string, $timezone );
373 + if ( false === $datetime ) {
374 + return $datetime_string;
354 375 }
376 + $timestamp = $datetime->getTimestamp();
377 +
378 + switch ( $separator_or_format ) {
379 + case ' ':
380 + case '<br />':
381 + case '<br/>':
382 + case '<br>':
383 + $date = wp_date( get_option( 'date_format' ), $timestamp, $timezone );
384 + $time = wp_date( get_option( 'time_format' ), $timestamp, $timezone );
385 + $output = "{$date}{$separator_or_format}{$time}";
386 + break;
387 + default:
388 + $output = (string) wp_date( $separator_or_format, $timestamp, $timezone );
389 + break;
390 + }
391 +
392 + return $output;
355 393 }
356 394
357 395 /**
358 396 * Get the name from a WP user ID (used to store information on last editor of a table).
359 397 *
398 + * @since 1.0.0
399 + *
360 400 * @param int $user_id WP user ID.
361 401 * @return string Nickname of the WP user with the $user_id.
362 402 */
363 - public static function get_user_display_name( $user_id ) {
403 + public static function get_user_display_name( int $user_id ): string {
364 404 $user = get_userdata( $user_id );
365 - return ( $user && isset( $user->display_name ) ) ? $user->display_name : sprintf( '<em>%s</em>', __( 'unknown', 'tablepress' ) );
405 + return $user->display_name ?? sprintf( '<em>%s</em>', __( 'unknown', 'tablepress' ) );
366 406 }
367 407
368 408 /**
409 + * Sanitizes a CSS class to ensure it only contains valid characters.
410 + *
411 + * Strips the string down to A-Z, a-z, 0-9, :, _, -.
412 + * This is an extension to WP's `sanitize_html_class()`, to also allow `:` which are used in some CSS frameworks.
413 + *
414 + * @since 1.11.0
415 + *
416 + * @param string $css_class The CSS class name to be sanitized.
417 + * @return string The sanitized CSS class.
418 + */
419 + public static function sanitize_css_class( string $css_class ): string {
420 + // Strip out any %-encoded octets.
421 + $sanitized_css_class = (string) preg_replace( '|%[a-fA-F0-9][a-fA-F0-9]|', '', $css_class );
422 + // Limit to A-Z, a-z, 0-9, ':', '_', and '-'.
423 + $sanitized_css_class = (string) preg_replace( '/[^A-Za-z0-9:_-]/', '', $sanitized_css_class );
424 + return $sanitized_css_class;
425 + }
426 +
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 + /**
676 + * Retrieves all information of a WP_Error object as a string.
677 + *
678 + * @since 1.4.0
679 + *
680 + * @param WP_Error $wp_error A WP_Error object.
681 + * @return string All error codes, messages, and data of the WP_Error.
682 + */
683 + public static function get_wp_error_string( WP_Error $wp_error ): string {
684 + $error_strings = array();
685 + $error_codes = $wp_error->get_error_codes();
686 + // Reverse order to get latest errors first.
687 + $error_codes = array_reverse( $error_codes );
688 + foreach ( $error_codes as $error_code ) {
689 + $error_strings[ $error_code ] = $error_code;
690 + $error_messages = $wp_error->get_error_messages( $error_code );
691 + $error_messages = implode( ', ', $error_messages );
692 + if ( ! empty( $error_messages ) ) {
693 + $error_strings[ $error_code ] .= " ({$error_messages})";
694 + }
695 + $error_data = $wp_error->get_error_data( $error_code );
696 + if ( is_string( $error_data ) ) {
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}]";
704 + }
705 + }
706 + return implode( ";\n", $error_strings );
707 + }
708 +
709 + /**
369 710 * Generate the action URL, to be used as a link within the plugin (e.g. in the submenu navigation or List of Tables).
370 711 *
371 712 * @since 1.0.0
372 713 *
373 - * @param array $params Optional. Parameters to form the query string of the URL.
374 - * @param bool $add_nonce Optional. Whether the URL shall be nonced by WordPress.
375 - * @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.
376 717 * @return string The URL for the given parameters (already run through esc_url() with $add_nonce === true!).
377 718 */
378 - public static function url( array $params = array(), $add_nonce = false, $target = '' ) {
379 -
719 + public static function url( array $params = array(), bool $add_nonce = false, string $target = '' ): string {
380 720 // Default action is "list", if no action given.
381 721 if ( ! isset( $params['action'] ) ) {
382 722 $params['action'] = 'list';
383 723 }
@@ -382,9 +722,9 @@
382 722 $params['action'] = 'list';
383 723 }
384 724 $nonce_action = $params['action'];
385 725
386 - if ( $target ) {
726 + if ( '' !== $target ) {
387 727 $params['action'] = "tablepress_{$params['action']}";
388 728 } else {
389 729 $params['page'] = 'tablepress';
390 730 // Top-level parent page needs special treatment for better action strings.
@@ -410,9 +750,9 @@
410 750 $params = array_merge( $default_params, $params );
411 751
412 752 $url = add_query_arg( $params, admin_url( $target ) );
413 753 if ( $add_nonce ) {
414 - $url = wp_nonce_url( $url, self::nonce( $nonce_action, $params['item'] ) ); // wp_nonce_url() does esc_html()
754 + $url = wp_nonce_url( $url, self::nonce( $nonce_action, $params['item'] ) ); // wp_nonce_url() does esc_html().
415 755 }
416 756 return $url;
417 757 }
418 758
@@ -420,12 +760,12 @@
420 760 * Create a redirect URL from the $target_parameters and redirect the user.
421 761 *
422 762 * @since 1.0.0
423 763 *
424 - * @param array $params Optional. Parameters from which the target URL is constructed.
425 - * @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.
426 766 */
427 - public static function redirect( array $params = array(), $add_nonce = false ) {
767 + public static function redirect( array $params = array(), bool $add_nonce = false ): void {
428 768 $redirect = self::url( $params );
429 769 if ( $add_nonce ) {
430 770 if ( ! isset( $params['item'] ) ) {
431 771 $params['item'] = false;
@@ -437,17 +777,298 @@
437 777 exit;
438 778 }
439 779
440 780 /**
441 - * Show an error notice to admins, if TablePress's minimum requirements are not reached.
781 + * Determines whether the site uses the block editor, so that certain text and input fields referring to Shortcodes can be displayed or not.
442 782 *
443 - * @since 1.0.0
783 + * @since 2.0.1
784 + *
785 + * @return bool True if the site uses the block editor, false otherwise.
444 786 */
445 - public static function show_minimum_requirements_error_notice() {
446 - // Message is not translated as it is shown on every admin screen, for which we don't want to load translations.
447 - echo '<div class="notice notice-error form-invalid"><p>' .
448 - '<strong>Attention:</strong> ' .
449 - '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>!' .
450 - "</p></div>\n";
787 + public static function site_uses_block_editor(): bool {
788 + $site_uses_block_editor = use_block_editor_for_post_type( 'post' )
789 + && ! is_plugin_active( 'beaver-builder-lite-version/fl-builder.php' )
790 + && ! is_plugin_active( 'classic-editor/classic-editor.php' )
791 + && ! is_plugin_active( 'classic-editor-addon/classic-editor-addon.php' )
792 + && ! is_plugin_active( 'elementor/elementor.php' )
793 + && ! is_plugin_active( 'siteorigin-panels/siteorigin-panels.php' );
794 +
795 + /**
796 + * Filters the outcome of the check whether the site uses the block editor.
797 + *
798 + * This can be used when certain conditions (e.g. new site builders) are not (yet) accounted for.
799 + *
800 + * @since 2.0.1
801 + *
802 + * @param bool $site_uses_block_editor True if the site uses the block editor, false otherwise.
803 + */
804 + $site_uses_block_editor = (bool) apply_filters( 'tablepress_site_uses_block_editor', $site_uses_block_editor );
805 +
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 + );
451 1072 }
452 1073
453 1074 } // class TablePress