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 | controllers/controller-frontend.php +411 -178 2.23.2 View file →
@@ -21,18 +21,47 @@
21 21 */
22 22 class TablePress_Frontend_Controller extends TablePress_Controller {
23 23
24 24 /**
25 + * Whether to use the legacy CSS loading method of enqueuing all CSS files on all pages.
26 + *
27 + * @since 3.0.1
28 + */
29 + public bool $use_legacy_css_loading = false;
30 +
31 + /**
32 + * File name of the admin screens' parent page in the admin menu.
33 + *
34 + * @since 1.0.0
35 + */
36 + public string $parent_page = 'middle';
37 +
38 + /**
39 + * Whether TablePress admin screens are a top-level menu item in the admin menu.
40 + *
41 + * @since 1.0.0
42 + */
43 + public bool $is_top_level_page = false;
44 +
45 + /**
25 46 * List of tables that are shown for the current request.
26 47 *
27 48 * @since 1.0.0
28 49 * @var array<string, array{count: int, instances: array<string, array<string, mixed>>}>
29 50 */
30 - protected $shown_tables = array();
51 + protected array $shown_tables = array();
31 52
32 53 /**
33 - * Initiate Frontend functionality.
54 + * List of registered DataTables datetime formats.
34 55 *
56 + * @since 3.0.0
57 + * @var string[]
58 + */
59 + protected array $datatables_datetime_formats = array();
60 +
61 + /**
62 + * Initiates Frontend functionality.
63 + *
35 64 * @since 1.0.0
36 65 */
37 66 public function __construct() {
38 67 parent::__construct();
@@ -37,20 +66,33 @@
37 66 public function __construct() {
38 67 parent::__construct();
39 68
40 69 /**
41 - * Filters whether the TablePress Default CSS code shall be loaded.
70 + * Filters the admin menu parent page, which is needed for the construction of plugin URLs.
42 71 *
43 72 * @since 1.0.0
44 73 *
45 - * @param bool $use Whether the Default CSS shall be loaded. Default true.
74 + * @param string $parent_page Current admin menu parent page.
46 75 */
47 - if ( apply_filters( 'tablepress_use_default_css', true ) || TablePress::$model_options->get( 'use_custom_css' ) ) {
76 + $this->parent_page = apply_filters( 'tablepress_admin_menu_parent_page', TablePress::$model_options->get( 'admin_menu_parent_page' ) );
77 + $this->is_top_level_page = in_array( $this->parent_page, array( 'top', 'middle', 'bottom' ), true );
78 +
79 + /**
80 + * Filters whether TablePress should load its frontend CSS files on all pages.
81 + * For block themes, the default behavior is to only load the CSS files when a table is encountered on the page.
82 + * If Elementor is active, the CSS is also loaded on the editor page.
83 + *
84 + * @since 3.0.1
85 + *
86 + * @param bool $use_legacy_css_loading Whether TablePress should load its frontend CSS files on all pages.
87 + */
88 + $this->use_legacy_css_loading = apply_filters( 'tablepress_frontend_legacy_css_loading', ! wp_is_block_theme() || ( isset( $_GET['elementor-preview'] ) && is_plugin_active( 'elementor/elementor.php' ) ) );
89 +
90 + if ( $this->use_legacy_css_loading ) {
48 91 add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_css' ) );
49 92 }
50 93
51 - // Add DataTables invocation calls.
52 - add_action( 'wp_print_footer_scripts', array( $this, 'add_datatables_calls' ), 11 ); // After inclusion of files.
94 + add_action( 'wp_print_footer_scripts', array( $this, 'add_datatables_calls' ), 9 ); // Priority 9 so that this runs before `_wp_footer_scripts()`.
53 95
54 96 // Register TablePress Shortcodes. Priority 20 is kept for backwards-compatibility purposes.
55 97 add_action( 'init', array( $this, 'init_shortcodes' ), 20 );
56 98
@@ -73,18 +115,31 @@
73 115
74 116 /**
75 117 * Register the tablepress/table block and its dependencies.
76 118 */
77 - register_block_type(
78 - TABLEPRESS_ABSPATH . 'blocks/table/',
119 + if ( function_exists( 'wp_register_block_metadata_collection' ) ) {
120 + // wp_register_block_metadata_collection() is only available since WP 6.7.
121 + wp_register_block_metadata_collection(
122 + TABLEPRESS_ABSPATH . 'blocks',
123 + TABLEPRESS_ABSPATH . 'blocks/blocks-manifest.php',
124 + );
125 + }
126 + register_block_type_from_metadata(
127 + TABLEPRESS_ABSPATH . 'blocks/table/block.json',
79 128 array(
80 129 'render_callback' => array( $this, 'table_block_render_callback' ),
81 - )
130 + ),
82 131 );
132 +
133 + /**
134 + * Register the TablePress Elementor widgets.
135 + */
136 + add_action( 'elementor/widgets/register', array( $this, 'register_elementor_widgets' ) );
137 + add_action( 'elementor/editor/after_enqueue_styles', array( $this, 'enqueue_elementor_editor_styles' ), 10, 0 );
83 138 }
84 139
85 140 /**
86 - * Register TablePress Shortcodes.
141 + * Registers TablePress Shortcodes.
87 142 *
88 143 * @since 1.0.0
89 144 */
90 145 public function init_shortcodes(): void {
@@ -92,18 +147,67 @@
92 147 add_shortcode( TablePress::$shortcode_info, array( $this, 'shortcode_table_info' ) );
93 148 }
94 149
95 150 /**
96 - * Enqueue CSS files for default CSS and "Custom CSS" (if desired).
151 + * Checks if the CSS files for TablePress default CSS and "Custom CSS" should be loaded.
97 152 *
153 + * This function is only called when a [table /] Shortcode or "TablePress Table" block is evaluated, so that CSS files are only loaded when needed.
154 + *
155 + * @since 3.0.0
156 + */
157 + public function maybe_enqueue_css(): void {
158 + // Bail early if the legacy CSS loading mechanism is used, as the files will then have been enqueued already.
159 + if ( $this->use_legacy_css_loading && ! doing_action( 'enqueue_block_assets' ) ) {
160 + return;
161 + }
162 +
163 + /*
164 + * Bail early if the function is called from some action hook outside of the normal rendering process.
165 + * These are often used by e.g. SEO plugins that render the content in additional contexts, e.g. to get an excerpt via an output buffer.
166 + * In these cases, we don't want to enqueue the CSS, as it would likely not be printed on the page.
167 + */
168 + if ( doing_action( 'wp_head' ) || doing_action( 'wp_footer' ) ) {
169 + return;
170 + }
171 +
172 + // Prevent repeated execution via a static variable.
173 + static $css_enqueued = false;
174 + if ( $css_enqueued && ! doing_action( 'enqueue_block_assets' ) ) {
175 + return;
176 + }
177 + $css_enqueued = true;
178 +
179 + $this->enqueue_css();
180 + }
181 +
182 + /**
183 + * Enqueues CSS files for TablePress default CSS and "Custom CSS" (if desired).
184 + *
185 + * If styles have not been printed to the page (in the `<head>`), the TablePress CSS files will be enqueued.
186 + * If styles have already been printed to the page, the TablePress CSS files will be printed right away (likely in the `<body`>).
187 + *
98 188 * @since 1.0.0
99 189 */
100 190 public function enqueue_css(): void {
101 - /** This filter is documented in controllers/controller-frontend.php */
191 + /**
192 + * Filters whether the TablePress Default CSS code shall be loaded.
193 + *
194 + * @since 1.0.0
195 + *
196 + * @param bool $use Whether the Default CSS shall be loaded. Default true.
197 + */
102 198 $use_default_css = apply_filters( 'tablepress_use_default_css', true );
199 + $use_custom_css = TablePress::$model_options->get( 'use_custom_css' );
200 +
201 + if ( ! $use_default_css && ! $use_custom_css ) {
202 + // Register a placeholder dependency, so that the handle is known for other styles.
203 + wp_register_style( 'tablepress-default', false ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
204 + return;
205 + }
206 +
103 207 $custom_css = TablePress::$model_options->get( 'custom_css' );
104 - $use_custom_css = ( TablePress::$model_options->get( 'use_custom_css' ) && '' !== $custom_css );
105 - $use_custom_css_file = ( $use_custom_css && TablePress::$model_options->get( 'use_custom_css_file' ) );
208 + $use_custom_css = $use_custom_css && '' !== $custom_css;
209 + $use_custom_css_file = $use_custom_css && TablePress::$model_options->get( 'use_custom_css_file' );
106 210 /**
107 211 * Filters the "Custom CSS" version number that is appended to the enqueued CSS files
108 212 *
109 213 * @since 1.0.0
@@ -131,81 +235,71 @@
131 235 if ( $use_custom_css_combined_file ) {
132 236 $custom_css_combined_url = $tablepress_css->get_custom_css_location( 'combined', 'url' );
133 237 // Need to use 'tablepress-default' instead of 'tablepress-combined' to not break existing TablePress Extensions.
134 238 wp_enqueue_style( 'tablepress-default', $custom_css_combined_url, array(), $custom_css_version );
239 + if ( did_action( 'wp_print_styles' ) ) {
240 + wp_print_styles( 'tablepress-default' );
241 + }
242 + return;
243 + }
244 +
245 + if ( $use_default_css ) {
246 + wp_enqueue_style( 'tablepress-default', $default_css_url, array(), TablePress::version );
135 247 } else {
136 - $custom_css_dependencies = array();
137 - if ( $use_default_css ) {
138 - wp_enqueue_style( 'tablepress-default', $default_css_url, array(), TablePress::version );
139 - // Add dependency to make sure that Custom CSS is printed after Default CSS.
140 - $custom_css_dependencies[] = 'tablepress-default';
248 + // Register a placeholder dependency, so that the handle is known for other styles.
249 + wp_register_style( 'tablepress-default', false ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
250 + }
251 +
252 + $use_custom_css_minified_file = ( $use_custom_css_file && ! SCRIPT_DEBUG && $tablepress_css->load_custom_css_from_file( 'minified' ) );
253 + if ( $use_custom_css_minified_file ) {
254 + $custom_css_minified_url = $tablepress_css->get_custom_css_location( 'minified', 'url' );
255 + wp_enqueue_style( 'tablepress-custom', $custom_css_minified_url, array( 'tablepress-default' ), $custom_css_version );
256 + if ( did_action( 'wp_print_styles' ) ) {
257 + wp_print_styles( 'tablepress-custom' );
141 258 }
259 + return;
260 + }
142 261
143 - $use_custom_css_minified_file = ( $use_custom_css_file && ! SCRIPT_DEBUG && $tablepress_css->load_custom_css_from_file( 'minified' ) );
144 - if ( $use_custom_css_minified_file ) {
145 - $custom_css_minified_url = $tablepress_css->get_custom_css_location( 'minified', 'url' );
146 - wp_enqueue_style( 'tablepress-custom', $custom_css_minified_url, $custom_css_dependencies, $custom_css_version );
147 - return;
262 + $use_custom_css_normal_file = ( $use_custom_css_file && $tablepress_css->load_custom_css_from_file( 'normal' ) );
263 + if ( $use_custom_css_normal_file ) {
264 + $custom_css_normal_url = $tablepress_css->get_custom_css_location( 'normal', 'url' );
265 + wp_enqueue_style( 'tablepress-custom', $custom_css_normal_url, array( 'tablepress-default' ), $custom_css_version );
266 + if ( did_action( 'wp_print_styles' ) ) {
267 + wp_print_styles( 'tablepress-custom' );
148 268 }
269 + return;
270 + }
149 271
150 - $use_custom_css_normal_file = ( $use_custom_css_file && $tablepress_css->load_custom_css_from_file( 'normal' ) );
151 - if ( $use_custom_css_normal_file ) {
152 - $custom_css_normal_url = $tablepress_css->get_custom_css_location( 'normal', 'url' );
153 - wp_enqueue_style( 'tablepress-custom', $custom_css_normal_url, $custom_css_dependencies, $custom_css_version );
154 - return;
272 + if ( $use_custom_css ) {
273 + // Get "Custom CSS" from options, try minified Custom CSS first.
274 + $custom_css_minified = TablePress::$model_options->get( 'custom_css_minified' );
275 + if ( ! empty( $custom_css_minified ) ) {
276 + $custom_css = $custom_css_minified;
155 277 }
156 -
157 - if ( $use_custom_css ) {
158 - // Get "Custom CSS" from options, try minified Custom CSS first.
159 - $custom_css_minified = TablePress::$model_options->get( 'custom_css_minified' );
160 - if ( ! empty( $custom_css_minified ) ) {
161 - $custom_css = $custom_css_minified;
278 + /**
279 + * Filters the "Custom CSS" code that is to be loaded as inline CSS.
280 + *
281 + * @since 1.0.0
282 + *
283 + * @param string $custom_css The "Custom CSS" code.
284 + */
285 + $custom_css = apply_filters( 'tablepress_custom_css', $custom_css );
286 + if ( ! empty( $custom_css ) ) {
287 + wp_add_inline_style( 'tablepress-default', $custom_css );
288 + if ( did_action( 'wp_print_styles' ) ) {
289 + wp_print_styles( 'tablepress-default' );
162 290 }
163 - /**
164 - * Filters the "Custom CSS" code that is to be loaded as inline CSS.
165 - *
166 - * @since 1.0.0
167 - *
168 - * @param string $custom_css The "Custom CSS" code.
169 - */
170 - $custom_css = apply_filters( 'tablepress_custom_css', $custom_css );
171 - if ( ! empty( $custom_css ) ) {
172 - // wp_add_inline_style() requires a loaded CSS file, so we have to work around that if "Default CSS" is disabled.
173 - if ( $use_default_css ) {
174 - // Handle of the file to which the <style> shall be appended.
175 - wp_add_inline_style( 'tablepress-default', $custom_css );
176 - } else {
177 - add_action( 'wp_head', array( $this, '_print_custom_css' ), 8 ); // Priority 8 to hook in right after WP_Styles has been processed.
178 - }
179 - }
291 + return;
180 292 }
181 293 }
182 294 }
183 295
184 296 /**
185 - * Print "Custom CSS" to "wp_head" inline.
297 + * Enqueues the DataTables JavaScript library and its dependencies.
186 298 *
187 - * This is necessary if "Default CSS" is off, and saving "Custom CSS" to a file is not possible.
188 - *
189 - * @since 1.0.0
299 + * @since 3.0.0
190 300 */
191 - public function _print_custom_css(): void {
192 - // Get "Custom CSS" from options, try minified Custom CSS first.
193 - $custom_css = TablePress::$model_options->get( 'custom_css_minified' );
194 - if ( empty( $custom_css ) ) {
195 - $custom_css = TablePress::$model_options->get( 'custom_css' );
196 - }
197 - /** This filter is documented in controllers/controller-frontend.php */
198 - $custom_css = apply_filters( 'tablepress_custom_css', $custom_css );
199 - echo "<style>\n{$custom_css}\n</style>\n";
200 - }
201 -
202 - /**
203 - * Enqueue the DataTables JavaScript library and its dependencies.
204 - *
205 - * @since 1.0.0
206 - */
207 - protected function _enqueue_datatables(): void {
301 + protected function enqueue_datatables_files(): void {
208 302 $js_file = 'js/jquery.datatables.min.js';
209 303 $js_url = plugins_url( $js_file, TABLEPRESS__FILE__ );
210 304 /**
211 305 * Filters the URL from which the DataTables JavaScript library file is loaded.
@@ -215,13 +309,27 @@
215 309 * @param string $js_url URL of the DataTables JS library file.
216 310 * @param string $js_file Path and file name of the DataTables JS library file.
217 311 */
218 312 $js_url = apply_filters( 'tablepress_datatables_js_url', $js_url, $js_file );
219 - wp_enqueue_script( 'tablepress-datatables', $js_url, array( 'jquery-core' ), TablePress::version, true );
313 +
314 + $dependencies = array( 'jquery-core' );
315 + if ( ! empty( $this->datatables_datetime_formats ) ) {
316 + $dependencies[] = 'moment';
317 + }
318 + /**
319 + * Filters the dependencies for the DataTables JavaScript library.
320 + *
321 + * @since 3.0.0
322 + *
323 + * @param string[] $dependencies The dependencies for the DataTables JS library.
324 + */
325 + $dependencies = apply_filters( 'tablepress_datatables_js_dependencies', $dependencies );
326 +
327 + wp_enqueue_script( 'tablepress-datatables', $js_url, $dependencies, TablePress::version, true );
220 328 }
221 329
222 330 /**
223 - * Add JS code for invocation of DataTables JS library.
331 + * Adds the JavaScript code for the invocation of the DataTables JS library.
224 332 *
225 333 * @since 1.0.0
226 334 */
227 335 public function add_datatables_calls(): void {
@@ -230,16 +338,16 @@
230 338 if ( $datatables_calls_printed ) {
231 339 return;
232 340 }
233 341
342 + // Bail early if there are no TablePress tables on the page.
234 343 if ( empty( $this->shown_tables ) ) {
235 - // There are no tables with activated DataTables on the page that is currently rendered.
236 344 return;
237 345 }
238 346
239 347 /*
240 - * Don't add the DataTables function calls in the scope of the block editor.
241 - * Otherwise, this causes a script error in the block editor iframe.
348 + * Don't add the DataTables function calls in the scope of the block editor iframe.
349 + * This is necessary for non-block themes, for others, the repeated execution check above is sufficient.
242 350 */
243 351 if ( function_exists( 'get_current_screen' ) ) {
244 352 $current_screen = get_current_screen();
245 353 if ( ( $current_screen instanceof WP_Screen ) && $current_screen->is_block_editor() ) {
@@ -246,17 +354,30 @@
246 354 return;
247 355 }
248 356 }
249 357
358 + // Filter out all tables that use DataTables.
359 + $shown_tables_with_datatables = array();
360 + foreach ( $this->shown_tables as $table_id => $table_store ) {
361 + if ( ! empty( $table_store['instances'] ) ) {
362 + $shown_tables_with_datatables[ (string) $table_id ] = $table_store;
363 + }
364 + }
365 +
366 + // Bail early if there are no tables with activated DataTables on the page.
367 + if ( empty( $shown_tables_with_datatables ) ) {
368 + return;
369 + }
370 +
371 + $this->enqueue_datatables_files();
372 +
250 373 // Storage for the DataTables language strings.
251 374 $datatables_language = array();
252 375 // Generate the specific JS commands, depending on chosen features on the "Edit" screen and the Shortcode parameters.
253 376 $commands = array();
254 377
255 - foreach ( $this->shown_tables as $table_id => $table_store ) {
256 - if ( empty( $table_store['instances'] ) ) {
257 - continue;
258 - }
378 + foreach ( $shown_tables_with_datatables as $table_id => $table_store ) {
379 + $table_id = (string) $table_id; // Ensure that the table ID is a string, as it comes from an array key where numeric strings are converted to integers.
259 380
260 381 foreach ( $table_store['instances'] as $html_id => $js_options ) {
261 382 $parameters = array();
262 383
@@ -312,9 +433,9 @@
312 433 $datatables_strings = array();
313 434 }
314 435 } elseif ( str_ends_with( $language_file, '.json' ) ) {
315 436 $datatables_strings = file_get_contents( $language_file );
316 - $datatables_strings = json_decode( $datatables_strings, true ); // @phpstan-ignore-line
437 + $datatables_strings = json_decode( $datatables_strings, true ); // @phpstan-ignore argument.type
317 438 // Check if JSON could be decoded.
318 439 if ( is_null( $datatables_strings ) ) {
319 440 $datatables_strings = array();
320 441 }
@@ -338,58 +459,56 @@
338 459 * @param string $datatables_locale Current locale/language for the DataTables JS library.
339 460 */
340 461 $datatables_language[ $datatables_locale ] = apply_filters( 'tablepress_datatables_language_strings', $datatables_strings, $datatables_locale );
341 462 }
342 - $parameters['language'] = '"language":DT_language["' . $datatables_locale . '"]';
463 + $parameters['language'] = "language:DT_language['{$datatables_locale}']";
343 464
344 465 // These parameters need to be added for performance gain or to overwrite unwanted default behavior.
345 466 if ( $js_options['datatables_sort'] ) {
346 467 // No initial sort.
347 - $parameters['order'] = '"order":[]';
468 + $parameters['order'] = 'order:[]';
348 469 // Don't add additional classes, to speed up sorting.
349 - $parameters['orderClasses'] = '"orderClasses":false';
470 + $parameters['orderClasses'] = 'orderClasses:false';
350 471 }
351 472
352 - // Alternating row colors is default, so remove them if not wanted with [].
353 - $parameters['stripeClasses'] = '"stripeClasses":' . ( ( $js_options['alternating_row_colors'] ) ? '["even","odd"]' : '[]' );
354 -
355 473 // The following options are activated by default, so we only need to "false" them if we don't want them, but don't need to "true" them if we do.
356 474 if ( ! $js_options['datatables_sort'] ) {
357 - $parameters['ordering'] = '"ordering":false';
475 + $parameters['ordering'] = 'ordering:false';
358 476 }
359 477 if ( $js_options['datatables_paginate'] ) {
360 - $parameters['pagingType'] = '"pagingType":"simple"';
478 + $parameters['pagingType'] = "pagingType:'simple_numbers'";
361 479 if ( $js_options['datatables_lengthchange'] ) {
362 480 $length_menu = array( 10, 25, 50, 100 );
363 481 if ( ! in_array( $js_options['datatables_paginate_entries'], $length_menu, true ) ) {
364 482 $length_menu[] = $js_options['datatables_paginate_entries'];
365 483 sort( $length_menu, SORT_NUMERIC );
366 - $parameters['lengthMenu'] = '"lengthMenu":[' . implode( ',', $length_menu ) . ']';
484 + $parameters['lengthMenu'] = 'lengthMenu:[' . implode( ',', $length_menu ) . ']';
367 485 }
368 486 } else {
369 - $parameters['lengthChange'] = '"lengthChange":false';
487 + $parameters['lengthChange'] = 'lengthChange:false';
370 488 }
371 489 if ( 10 !== $js_options['datatables_paginate_entries'] ) {
372 - $parameters['pageLength'] = '"pageLength":' . $js_options['datatables_paginate_entries'];
490 + $parameters['pageLength'] = "pageLength:{$js_options['datatables_paginate_entries']}";
373 491 }
374 492 } else {
375 - $parameters['paging'] = '"paging":false';
493 + $parameters['paging'] = 'paging:false';
376 494 }
377 495 if ( ! $js_options['datatables_filter'] ) {
378 - $parameters['searching'] = '"searching":false';
496 + $parameters['searching'] = 'searching:false';
379 497 }
380 498 if ( ! $js_options['datatables_info'] ) {
381 - $parameters['info'] = '"info":false';
499 + $parameters['info'] = 'info:false';
382 500 }
383 501 if ( $js_options['datatables_scrollx'] ) {
384 - $parameters['scrollX'] = '"scrollX":true';
502 + $parameters['scrollX'] = 'scrollX:true';
385 503 }
386 504 if ( false !== $js_options['datatables_scrolly'] ) {
387 - $parameters['scrollY'] = '"scrollY":"' . preg_replace( '#[^0-9a-z.%]#', '', $js_options['datatables_scrolly'] ) . '"';
388 - $parameters['scrollCollapse'] = '"scrollCollapse":true';
505 + $parameters['scrollY'] = 'scrollY:"' . preg_replace( '#[^0-9a-z.%]#', '', $js_options['datatables_scrolly'] ) . '"';
506 + $parameters['scrollCollapse'] = 'scrollCollapse:true';
389 507 }
390 - if ( ! empty( $js_options['datatables_custom_commands'] ) ) {
391 - $parameters['custom_commands'] = $js_options['datatables_custom_commands'];
508 + if ( '' !== $js_options['datatables_custom_commands'] ) {
509 + $parameters['custom_commands'] = trim( $js_options['datatables_custom_commands'] ); // Remove leading and trailing whitespace.
510 + $parameters['custom_commands'] = trim( $parameters['custom_commands'], ',' ); // Remove potentially leading and trailing commas to prevent JS script errors.
392 511 }
393 512
394 513 /**
395 514 * Filters the parameters that are passed to the DataTables JavaScript library.
@@ -402,21 +521,22 @@
402 521 * @param array<string, mixed> $js_options The options for the JS library.
403 522 */
404 523 $parameters = apply_filters( 'tablepress_datatables_parameters', $parameters, $table_id, $html_id, $js_options );
405 524
406 - // If an existing parameter (in the from `"parameter":`) is set in the "Custom Commands", remove its default value.
407 - if ( isset( $parameters['custom_commands'] ) ) {
408 - foreach ( array_keys( $parameters ) as $maybe_overwritten_parameter ) {
409 - if ( str_contains( $parameters['custom_commands'], "\"{$maybe_overwritten_parameter}\":" ) ) {
410 - unset( $parameters[ $maybe_overwritten_parameter ] );
411 - }
525 + // If an existing parameter is set as an object key in the "Custom Commands", remove its separate value, to allow for full overrides.
526 + if ( isset( $parameters['custom_commands'] ) && '' !== $parameters['custom_commands'] ) {
527 + $parameters_in_custom_commands = TablePress::extract_keys_from_js_object_string( '{' . $parameters['custom_commands'] . '}' );
528 + foreach ( $parameters_in_custom_commands as $parameter_in_custom_commands ) {
529 + unset( $parameters[ $parameter_in_custom_commands ] );
412 530 }
413 531 }
414 532
533 + $name = substr( $html_id, 11 ); // Remove "tablepress-" from the HTML ID.
534 + $name = "DT_TP['" . str_replace( '-', '_', $name ) . "']";
415 535 $parameters = implode( ',', $parameters );
416 536 $parameters = ( ! empty( $parameters ) ) ? '{' . $parameters . '}' : '';
417 537
418 - $command = "$('#{$html_id}').DataTable({$parameters});";
538 + $command = "{$name} = new DataTable('#{$html_id}',{$parameters});";
419 539 /**
420 540 * Filters the JavaScript command that invokes the DataTables JavaScript library on one table.
421 541 *
422 542 * @since 1.0.0
@@ -425,10 +545,11 @@
425 545 * @param string $html_id The ID of the table HTML element.
426 546 * @param string $parameters The parameters for the DataTables JS library.
427 547 * @param string $table_id The current table ID.
428 548 * @param array<string, mixed> $js_options The options for the JS library.
549 + * @param string $name The name of the DataTable instance.
429 550 */
430 - $command = apply_filters( 'tablepress_datatables_command', $command, $html_id, $parameters, $table_id, $js_options );
551 + $command = apply_filters( 'tablepress_datatables_command', $command, $html_id, $parameters, $table_id, $js_options, $name );
431 552 if ( ! empty( $command ) ) {
432 553 $commands[] = $command;
433 554 }
434 555 } // foreach table instance
@@ -435,14 +556,40 @@
435 556 } // foreach table ID
436 557
437 558 // DataTables language/translation handling.
438 559 if ( ! empty( $datatables_language ) ) {
439 - $datatables_language = wp_json_encode( $datatables_language, JSON_UNESCAPED_UNICODE | JSON_FORCE_OBJECT );
440 - $datatables_language = "var DT_language={$datatables_language};\n";
560 + $datatables_language_command = wp_json_encode( $datatables_language, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_FORCE_OBJECT );
561 + $datatables_language_command = "var DT_language={$datatables_language_command};\n";
441 562 } else {
442 - $datatables_language = '';
563 + $datatables_language_command = '';
443 564 }
444 565
566 + // DataTables datetime format string handling.
567 + if ( ! empty( $this->datatables_datetime_formats ) ) {
568 + // Create a command like `DataTable.datetime('MM/DD/YYYY');DataTable.datetime('DD.MM.YYYY');`.
569 + $datatables_datetime_command = implode(
570 + '',
571 + array_map(
572 + static fn( string $datetime_format ): string => "DataTable.datetime('{$datetime_format}');",
573 + $this->datatables_datetime_formats,
574 + )
575 + ) . "\n";
576 + } else {
577 + $datatables_datetime_command = '';
578 + }
579 +
580 + /**
581 + * Filters the JavaScript code for the DataTables JavaScript library that initializes the automatically detected date/time formats via moment.js.
582 + *
583 + * @since 3.0.0
584 + *
585 + * @param string $datatables_datetime_command The JS code for the DataTables JS library that initializes the date/time formats.
586 + * @param string[] $datatables_datetime_formats The date/time formats for moment.js.
587 + */
588 + $datatables_datetime_command = apply_filters( 'tablepress_datatables_datetime_command', $datatables_datetime_command, $this->datatables_datetime_formats );
589 +
590 + $datatables_pre_commands = $datatables_language_command . $datatables_datetime_command;
591 +
445 592 $commands = implode( "\n", $commands );
446 593 /**
447 594 * Filters the JavaScript commands that invoke the DataTables JavaScript library on all tables on the page.
448 595 *
@@ -454,33 +601,32 @@
454 601 if ( '' === $commands ) {
455 602 return;
456 603 }
457 604
458 - $script_type_attr = current_theme_supports( 'html5', 'script' ) ? '' : ' type="text/javascript"';
459 -
460 - $js_wrapper = <<<'JS'
461 -<script%3$s>
462 -jQuery(function($){
463 -%1$s%2$s
464 -});
465 -</script>
466 -JS;
605 + $script_template = <<<'JS'
606 + var DT_TP = {};
607 + jQuery(($)=>{
608 + %1$s%2$s
609 + });
610 + JS;
467 611 /**
468 612 * Filters the script/jQuery wrapper code for the DataTables commands calls.
469 613 *
470 614 * @since 1.14.0
471 615 *
472 - * @param string $js_wrapper Default script/jQuery wrapper code for the DataTables commands calls.
616 + * @param string $script_template Default script/jQuery wrapper code for the DataTables commands calls.
473 617 */
474 - $js_wrapper = apply_filters( 'tablepress_all_datatables_commands_wrapper', $js_wrapper );
475 - printf( $js_wrapper, $datatables_language, $commands, $script_type_attr );
618 + $script_template = apply_filters( 'tablepress_all_datatables_commands_wrapper', $script_template );
476 619
620 + $script = sprintf( $script_template, $datatables_pre_commands, $commands );
621 + wp_add_inline_script( 'tablepress-datatables', $script );
622 +
477 623 // Prevent repeated execution (which would lead to DataTables error messages) via a static variable.
478 624 $datatables_calls_printed = true;
479 625 }
480 626
481 627 /**
482 - * Handle Shortcode [table id=<ID> /].
628 + * Handles the Shortcode [table id=<ID> /].
483 629 *
484 630 * @since 1.0.0
485 631 *
486 632 * @param array<string, mixed>|string $shortcode_atts List of attributes that where included in the Shortcode. An empty string for empty Shortcodes like [table] or [table /].
@@ -488,8 +634,10 @@
488 634 */
489 635 public function shortcode_table( /* array|string */ $shortcode_atts ): string {
490 636 $shortcode_atts = (array) $shortcode_atts;
491 637
638 + $this->maybe_enqueue_css();
639 +
492 640 $_render = TablePress::load_class( 'TablePress_Render', 'class-render.php', 'classes' );
493 641
494 642 $default_shortcode_atts = $_render->get_default_render_options();
495 643 /**
@@ -513,9 +661,9 @@
513 661
514 662 // Check, if a table with the given ID exists.
515 663 $table_id = (string) preg_replace( '/[^a-zA-Z0-9_-]/', '', $shortcode_atts['id'] );
516 664 if ( ! TablePress::$model_table->table_exists( $table_id ) ) {
517 - $message = "[table &#8220;{$table_id}&#8221; not found /]<br />\n";
665 + $message = "&#91;table “{$table_id}” not found /&#93;<br />\n";
518 666 /**
519 667 * Filters the "Table not found" message.
520 668 *
521 669 * @since 1.0.0
@@ -529,9 +677,9 @@
529 677
530 678 // Load table, with table data, options, and visibility settings.
531 679 $table = TablePress::$model_table->load( $table_id, true, true );
532 680 if ( is_wp_error( $table ) ) {
533 - $message = "[table &#8220;{$table_id}&#8221; could not be loaded /]<br />\n";
681 + $message = "&#91;table “{$table_id}” could not be loaded /&#93;<br />\n";
534 682 /**
535 683 * Filters the "Table could not be loaded" message.
536 684 *
537 685 * @since 1.0.0
@@ -543,9 +691,9 @@
543 691 $message = apply_filters( 'tablepress_table_load_error_message', $message, $table_id, $table );
544 692 return $message;
545 693 }
546 694 if ( isset( $table['is_corrupted'] ) && $table['is_corrupted'] ) {
547 - $message = "<div>Attention: The internal data of table &#8220;{$table_id}&#8221; is corrupted!</div>";
695 + $message = "<div>Attention: The internal data of table “{$table_id}” is corrupted!</div>";
548 696 /**
549 697 * Filters the "Table data is corrupted" message.
550 698 *
551 699 * @since 1.0.0
@@ -557,36 +705,54 @@
557 705 $message = apply_filters( 'tablepress_table_corrupted_message', $message, $table_id, $table['json_error'] );
558 706 return $message;
559 707 }
560 708
561 - /**
562 - * Filters whether the "datatables_custom_commands" Shortcode parameter is disabled.
563 - *
564 - * By default, the "datatables_custom_commands" Shortcode parameter is disabled for security reasons.
565 - *
566 - * @since 1.0.0
567 - *
568 - * @param bool $disable Whether to disable the "datatables_custom_commands" Shortcode parameter. Default true.
569 - */
570 - if ( ! is_null( $shortcode_atts['datatables_custom_commands'] ) && apply_filters( 'tablepress_disable_custom_commands_shortcode_parameter', true ) ) {
571 - $shortcode_atts['datatables_custom_commands'] = null;
709 + if ( ! is_null( $shortcode_atts['datatables_custom_commands'] ) ) {
710 + /**
711 + * Filters whether the "datatables_custom_commands" Shortcode parameter is disabled.
712 + *
713 + * By default, the "datatables_custom_commands" Shortcode parameter is disabled for security reasons.
714 + *
715 + * @since 1.0.0
716 + *
717 + * @param bool $disable Whether to disable the "datatables_custom_commands" Shortcode parameter. Default true.
718 + */
719 + if ( apply_filters( 'tablepress_disable_custom_commands_shortcode_parameter', true ) ) {
720 + $shortcode_atts['datatables_custom_commands'] = null;
721 + } else {
722 + // Convert the HTML entity `&amp;` back to `&` manually, as entities in Shortcodes in normal text paragraphs are sometimes double-encoded.
723 + $shortcode_atts['datatables_custom_commands'] = str_replace( '&amp;', '&', $shortcode_atts['datatables_custom_commands'] );
724 + // Convert HTML entities like `&lt;`, `&lsqb;`, `&#91;`, and `&amp;` back to their respective characters.
725 + $shortcode_atts['datatables_custom_commands'] = html_entity_decode( $shortcode_atts['datatables_custom_commands'], ENT_QUOTES | ENT_HTML5, get_option( 'blog_charset' ) );
726 + }
572 727 }
573 728
574 729 // Determine options to use (if set in Shortcode, use those, otherwise use stored options, from the "Edit" screen).
575 730 $render_options = array();
576 731 foreach ( $shortcode_atts as $key => $value ) {
577 - // We have to check this, because strings 'true' or 'false' are not recognized as boolean!
578 - if ( is_string( $value ) && 'true' === strtolower( $value ) ) {
579 - $render_options[ $key ] = true;
580 - } elseif ( is_string( $value ) && 'false' === strtolower( $value ) ) {
581 - $render_options[ $key ] = false;
582 - } elseif ( is_null( $value ) && isset( $table['options'][ $key ] ) ) {
732 + if ( is_null( $value ) && isset( $table['options'][ $key ] ) ) {
733 + // Use the table's stored option value, if the Shortcode parameter was not set.
583 734 $render_options[ $key ] = $table['options'][ $key ];
735 + } elseif ( is_string( $value ) ) {
736 + // Convert strings 'true' or 'false' to boolean, keep others.
737 + $value_lowercase = strtolower( $value );
738 + if ( 'true' === $value_lowercase ) {
739 + $render_options[ $key ] = true;
740 + } elseif ( 'false' === $value_lowercase ) {
741 + $render_options[ $key ] = false;
742 + } else {
743 + $render_options[ $key ] = $value;
744 + }
584 745 } else {
746 + // Keep all other values.
585 747 $render_options[ $key ] = $value;
586 748 }
587 749 }
588 750
751 + // Backward compatibility: Convert boolean or numeric string "table_head" and "table_foot" options to integer.
752 + $render_options['table_head'] = absint( $render_options['table_head'] );
753 + $render_options['table_foot'] = absint( $render_options['table_foot'] );
754 +
589 755 // Generate unique HTML ID, depending on how often this table has already been shown on this page.
590 756 if ( ! isset( $this->shown_tables[ $table_id ] ) ) {
591 757 $this->shown_tables[ $table_id ] = array(
592 758 'count' => 0,
@@ -621,9 +787,9 @@
621 787 *
622 788 * @param bool $show Whether to show the "Edit" link below the table. Default true.
623 789 * @param string $table_id The current table ID.
624 790 */
625 - if ( is_user_logged_in() && apply_filters( 'tablepress_edit_link_below_table', true, $table['id'] ) && current_user_can( 'tablepress_edit_table', $table['id'] ) ) {
791 + if ( is_user_logged_in() && ! $render_options['block_preview'] && apply_filters( 'tablepress_edit_link_below_table', true, $table['id'] ) && current_user_can( 'tablepress_edit_table', $table['id'] ) ) {
626 792 $render_options['edit_table_url'] = TablePress::url( array( 'action' => 'edit', 'table_id' => $table['id'] ) );
627 793 }
628 794
629 795 /**
@@ -637,18 +803,22 @@
637 803 * @param array<string, mixed> $table The current table.
638 804 */
639 805 $render_options = apply_filters( 'tablepress_table_render_options', $render_options, $table );
640 806
807 + // Backward compatibility: Convert boolean "table_head" and "table_foot" options to integer, in case they were overwritten via the filter hook.
808 + $render_options['table_head'] = absint( $render_options['table_head'] );
809 + $render_options['table_foot'] = absint( $render_options['table_foot'] );
810 +
641 811 // Check if table output shall and can be loaded from the transient cache, otherwise generate the output.
642 812 if ( $render_options['cache_table_output'] && ! is_user_logged_in() ) {
643 813 // Hash the Render Options array to get a unique cache identifier.
644 - $table_hash = md5( wp_json_encode( $render_options, TABLEPRESS_JSON_OPTIONS ) ); // @phpstan-ignore-line
814 + $table_hash = md5( wp_json_encode( $render_options, TABLEPRESS_JSON_OPTIONS ) ); // @phpstan-ignore argument.type
645 815 $transient_name = 'tablepress_' . $table_hash; // Attention: This string must not be longer than 45 characters!
646 816 $output = get_transient( $transient_name );
647 817 if ( false === $output || '' === $output ) {
648 818 // Render/generate the table HTML, as it was not found in the cache.
649 819 $_render->set_input( $table, $render_options );
650 - $output = $_render->get_output();
820 + $output = $_render->get_output( 'html' );
651 821 // Save render output in a transient, set cache timeout to 24 hours.
652 822 set_transient( $transient_name, $output, DAY_IN_SECONDS );
653 823 // Update output caches list transient (necessary for cache invalidation upon table saving).
654 824 $caches_list_transient_name = 'tablepress_c_' . md5( $table_id );
@@ -674,18 +844,16 @@
674 844 }
675 845 } else {
676 846 // Render/generate the table HTML, as no cache is to be used.
677 847 $_render->set_input( $table, $render_options );
678 - $output = $_render->get_output();
848 + $output = $_render->get_output( 'html' );
679 849 }
680 850
681 851 // If DataTables is to be and can be used with this instance of a table, process its parameters and register the call for inclusion in the footer.
682 852 if ( $render_options['use_datatables']
683 - && $render_options['table_head']
684 - && str_contains( $output, '<thead' ) // A `<thead>` tag is required.
685 - && ! str_contains( $output, ' colspan="' ) // `colspan` attributes are forbidden.
686 - && ! str_contains( $output, ' rowspan="' ) // `rowspan` attributes are forbidden.
687 - ) {
853 + && 0 < $render_options['table_head']
854 + && ! str_contains( $output, 'tbody-has-connected-cells' ) // The Render class adds this CSS class to the `<table>` element if the table has connected cells in the `<tbody>`.
855 + ) {
688 856 // Get options for the DataTables JavaScript library from the table's render options.
689 857 $js_options = array();
690 858 foreach ( array(
691 859 'alternating_row_colors',
@@ -715,10 +883,21 @@
715 883 * @param string $table_id The current table ID.
716 884 * @param array<string, mixed> $render_options The render options for the table.
717 885 */
718 886 $js_options = apply_filters( 'tablepress_table_js_options', $js_options, $table_id, $render_options );
887 +
719 888 $this->shown_tables[ $table_id ]['instances'][ (string) $render_options['html_id'] ] = $js_options;
720 - $this->_enqueue_datatables();
889 +
890 + // DataTables datetime format string handling.
891 + if ( '' !== $render_options['datatables_datetime'] ) {
892 + $render_options['datatables_datetime'] = explode( '|', $render_options['datatables_datetime'] );
893 + foreach ( $render_options['datatables_datetime'] as $datetime_format ) {
894 + $datetime_format = trim( $datetime_format );
895 + if ( '' !== $datetime_format && ! in_array( $datetime_format, $this->datatables_datetime_formats, true ) ) {
896 + $this->datatables_datetime_formats[] = $datetime_format;
897 + }
898 + }
899 + }
721 900 }
722 901
723 902 // Maybe print a list of used render options.
724 903 if ( $render_options['shortcode_debug'] && is_user_logged_in() ) {
@@ -728,9 +907,9 @@
728 907 return $output;
729 908 }
730 909
731 910 /**
732 - * Handle Shortcode [table-info id=<ID> field=<name> /].
911 + * Handles the Shortcode [table-info id=<ID> field=<name> /].
733 912 *
734 913 * @since 1.0.0
735 914 *
736 915 * @param array<string, mixed>|string $shortcode_atts List of attributes that where included in the Shortcode. An empty string for empty Shortcodes like [table] or [table /].
@@ -778,9 +957,9 @@
778 957
779 958 // Check, if a table with the given ID exists.
780 959 $table_id = preg_replace( '/[^a-zA-Z0-9_-]/', '', $shortcode_atts['id'] );
781 960 if ( ! TablePress::$model_table->table_exists( $table_id ) ) {
782 - $message = "[table &#8220;{$table_id}&#8221; not found /]<br />\n";
961 + $message = "&#91;table “{$table_id}” not found /&#93;<br />\n";
783 962 /** This filter is documented in controllers/controller-frontend.php */
784 963 $message = apply_filters( 'tablepress_table_not_found_message', $message, $table_id );
785 964 return $message;
786 965 }
@@ -787,9 +966,9 @@
787 966
788 967 // Load table, with table data, options, and visibility settings.
789 968 $table = TablePress::$model_table->load( $table_id, true, true );
790 969 if ( is_wp_error( $table ) ) {
791 - $message = "[table &#8220;{$table_id}&#8221; could not be loaded /]<br />\n";
970 + $message = "&#91;table “{$table_id}” could not be loaded /&#93;<br />\n";
792 971 /** This filter is documented in controllers/controller-frontend.php */
793 972 $message = apply_filters( 'tablepress_table_load_error_message', $message, $table_id, $table );
794 973 return $message;
795 974 }
@@ -810,9 +989,13 @@
810 989 $output = $table['last_modified'];
811 990 break;
812 991 case 'human':
813 992 $modified_timestamp = date_create( $table['last_modified'], wp_timezone() );
814 - $modified_timestamp = $modified_timestamp->getTimestamp(); // @phpstan-ignore-line
993 + if ( false === $modified_timestamp ) {
994 + $modified_timestamp = $table['last_modified'];
995 + } else {
996 + $modified_timestamp = $modified_timestamp->getTimestamp();
997 + }
815 998 $current_timestamp = time();
816 999 $time_diff = $current_timestamp - $modified_timestamp;
817 1000 // Time difference is only shown up to one week.
818 1001 if ( $time_diff >= 0 && $time_diff < WEEK_IN_SECONDS ) {
@@ -840,14 +1023,10 @@
840 1023 break;
841 1024 case 'number_rows':
842 1025 $output = count( $table['data'] );
843 1026 if ( 'raw' !== $format ) {
844 - if ( $table['options']['table_head'] ) {
845 - --$output;
846 - }
847 - if ( $table['options']['table_foot'] ) {
848 - --$output;
849 - }
1027 + $output -= $table['options']['table_head'];
1028 + $output -= $table['options']['table_foot'];
850 1029 }
851 1030 break;
852 1031 case 'number_columns':
853 1032 $output = count( $table['data'][0] );
@@ -852,9 +1031,9 @@
852 1031 case 'number_columns':
853 1032 $output = count( $table['data'][0] );
854 1033 break;
855 1034 default:
856 - $output = "[table-info field &#8220;{$field}&#8221; not found in table &#8220;{$table_id}&#8221; /]<br />\n";
1035 + $output = "&#91;table-info field “{$field}” not found in table “{$table_id}” /&#93;<br />\n";
857 1036 /**
858 1037 * Filters the "table info field not found" message.
859 1038 *
860 1039 * @since 1.0.0
@@ -880,9 +1059,9 @@
880 1059 return $output;
881 1060 }
882 1061
883 1062 /**
884 - * Expand WP Search to also find posts and pages that have a search term in a table that is shown in them.
1063 + * Expands the WP Search to also find posts and pages that have a search term in a table that is shown in them.
885 1064 *
886 1065 * This is done by looping through all search terms and TablePress tables and searching there for the search term,
887 1066 * saving all tables's IDs that have a search term and then expanding the WP query to search for posts or pages that have the
888 1067 * Shortcode for one of these tables in their content.
@@ -893,11 +1072,18 @@
893 1072 *
894 1073 * @param string $search_sql Current part of the "WHERE" clause of the SQL statement used to get posts/pages from the WP database that is related to searching.
895 1074 * @return string Eventually extended SQL "WHERE" clause, to also find posts/pages with Shortcodes in them.
896 1075 */
897 - public function posts_search_filter( string $search_sql ): string {
1076 + public function posts_search_filter( /* string */ $search_sql ): string {
1077 + // Don't use a type hint in the method declaration as there can be cases where `null` is passed to the filter hook callback somehow.
1078 +
898 1079 global $wpdb;
899 1080
1081 + // Protect against cases where `null` is somehow passed to the filter hook callback.
1082 + if ( ! is_string( $search_sql ) ) { // @phpstan-ignore function.alreadyNarrowedType (The `is_string()` check is needed as the input is coming from a filter hook.)
1083 + return '';
1084 + }
1085 +
900 1086 if ( ! is_search() || ! is_main_query() ) {
901 1087 return $search_sql;
902 1088 }
903 1089
@@ -911,20 +1097,27 @@
911 1097 $table_ids = TablePress::$model_table->load_all( true, false );
912 1098 // Array of all search words that were found, and the table IDs where they were found.
913 1099 $query_result = array();
914 1100
1101 + $fn_stripos = function_exists( 'mb_stripos' ) ? 'mb_stripos' : 'stripos';
1102 +
915 1103 foreach ( $table_ids as $table_id ) {
916 1104 // Load table, with table data, options, and visibility settings.
917 1105 $table = TablePress::$model_table->load( $table_id, true, true );
918 1106
1107 + // Skip tables that could not be loaded.
1108 + if ( is_wp_error( $table ) ) {
1109 + continue;
1110 + }
1111 +
1112 + // Do not search in corrupted tables.
919 1113 if ( isset( $table['is_corrupted'] ) && $table['is_corrupted'] ) {
920 - // Do not search in corrupted tables.
921 1114 continue;
922 1115 }
923 1116
924 1117 foreach ( $search_terms as $search_term ) {
925 - if ( ( $table['options']['print_name'] && false !== stripos( $table['name'], $search_term ) ) // @phpstan-ignore-line
926 - || ( $table['options']['print_description'] && false !== stripos( $table['description'], $search_term ) ) ) { // @phpstan-ignore-line
1118 + if ( ( $table['options']['print_name'] && false !== $fn_stripos( $table['name'], (string) $search_term ) )
1119 + || ( $table['options']['print_description'] && false !== $fn_stripos( $table['description'], (string) $search_term ) ) ) {
927 1120 // Found the search term in the name or description (and they are shown).
928 1121 $query_result[ $search_term ][] = $table_id; // Add table ID to result list.
929 1122 // No need to continue searching this search term in this table.
930 1123 continue;
@@ -930,9 +1123,9 @@
930 1123 continue;
931 1124 }
932 1125
933 1126 // Search search term in visible table cells (without taking Shortcode parameters into account!).
934 - foreach ( $table['data'] as $row_idx => $table_row ) { // @phpstan-ignore-line
1127 + foreach ( $table['data'] as $row_idx => $table_row ) {
935 1128 if ( 0 === $table['visibility']['rows'][ $row_idx ] ) {
936 1129 // Row is hidden, so don't search in it.
937 1130 continue;
938 1131 }
@@ -940,10 +1133,10 @@
940 1133 if ( 0 === $table['visibility']['columns'][ $col_idx ] ) {
941 1134 // Column is hidden, so don't search in it.
942 1135 continue;
943 1136 }
944 - // @TODO: Cells are not evaluated here, so math formulas are searched.
945 - if ( false !== stripos( $table_cell, $search_term ) ) {
1137 + // @todo Cells are not evaluated here, so math formulas are searched.
1138 + if ( false !== $fn_stripos( $table_cell, (string) $search_term ) ) {
946 1139 // Found the search term in the cell content.
947 1140 $query_result[ $search_term ][] = $table_id; // Add table ID to result list
948 1141 // No need to continue searching this search term in this table.
949 1142 continue 3;
@@ -960,9 +1153,9 @@
960 1153 $n = ( empty( $exact ) ) ? '%' : '';
961 1154 $search_sql = $wpdb->remove_placeholder_escape( $search_sql );
962 1155 foreach ( $query_result as $search_term => $table_ids ) {
963 1156 $search_term = esc_sql( $wpdb->esc_like( $search_term ) );
964 - $old_or = "OR ({$wpdb->posts}.post_content LIKE '{$n}{$search_term}{$n}')"; // @phpstan-ignore-line
1157 + $old_or = "OR ({$wpdb->posts}.post_content LIKE '{$n}{$search_term}{$n}')"; // @phpstan-ignore encapsedStringPart.nonString (The esc_sql() call above returns a string, as a string is passed.)
965 1158 $table_ids = implode( '|', $table_ids );
966 1159 $regexp = '\\\\[' . TablePress::$shortcode . ' id=(["\\\']?)(' . $table_ids . ')([\]"\\\' /])'; // ' needs to be single escaped, [ double escaped (with \\) in mySQL
967 1160 $new_or = $old_or . " OR ({$wpdb->posts}.post_content REGEXP '{$regexp}')";
968 1161 $search_sql = str_replace( $old_or, $new_or, $search_sql );
@@ -990,10 +1183,50 @@
990 1183 $render_attributes = shortcode_parse_atts( $block_attributes['parameters'] );
991 1184 } else {
992 1185 $render_attributes = array();
993 1186 }
994 - $render_attributes['id'] = $block_attributes['id']; // @phpstan-ignore-line
1187 + $render_attributes['id'] = $block_attributes['id'];
995 1188
996 1189 return $this->shortcode_table( $render_attributes );
1190 + }
1191 +
1192 + /**
1193 + * Registers the TablePress Elementor widgets.
1194 + *
1195 + * @since 3.1.0
1196 + *
1197 + * @param \Elementor\Widgets_Manager $widgets_manager Elementor widgets manager.
1198 + */
1199 + public function register_elementor_widgets( \Elementor\Widgets_Manager $widgets_manager ): void {
1200 + TablePress::load_file( 'class-elementor-widget-table.php', 'classes' );
1201 + $widgets_manager->register( new TablePress\Elementor\TablePressTableWidget() ); // @phpstan-ignore method.notFound (Elementor methods are not in the stubs.)
1202 + }
1203 +
1204 + /**
1205 + * Enqueues the TablePress Elementor Editor CSS styles.
1206 + *
1207 + * @since 3.1.0
1208 + */
1209 + public function enqueue_elementor_editor_styles(): void {
1210 + $svg_url = plugins_url( 'admin/img/tablepress-editor-button.svg', TABLEPRESS__FILE__ );
1211 + wp_register_style( 'tablepress-elementor', false ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
1212 + wp_add_inline_style(
1213 + 'tablepress-elementor',
1214 + <<<CSS
1215 + .elementor-panel .elementor-element .icon:has(> .tablepress-elementor-icon) {
1216 + height: 43.5px;
1217 + }
1218 + .tablepress-elementor-icon {
1219 + display: inline-block;
1220 + height: 28px;
1221 + width: 28px;
1222 + background-image: url({$svg_url});
1223 + background-repeat: no-repeat;
1224 + background-position: center;
1225 + background-size: 28px auto;
1226 + }
1227 + CSS
1228 + );
1229 + wp_enqueue_style( 'tablepress-elementor' );
997 1230 }
998 1231
999 1232 } // class TablePress_Frontend_Controller