PluginProbe
TablePress – Tables in WordPress made easy / 3.4
TablePress – Tables in WordPress made easy v3.4
3.4 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 All 45 releases
← All changes | controllers/controller-frontend.php +391 -175 2.3.2 → 3.4 View file →
@@ -7,8 +7,10 @@
7 7 * @author Tobias Bäthge
8 8 * @since 1.0.0
9 9 */
10 10
11 +declare(strict_types=1);
12 +
11 13 // Prohibit direct script loading.
12 14 defined( 'ABSPATH' ) || die( 'No direct script access allowed!' );
13 15
14 16 /**
@@ -21,18 +23,47 @@
21 23 */
22 24 class TablePress_Frontend_Controller extends TablePress_Controller {
23 25
24 26 /**
27 + * Whether to use the legacy CSS loading method of enqueuing all CSS files on all pages.
28 + *
29 + * @since 3.0.1
30 + */
31 + public bool $use_legacy_css_loading = false;
32 +
33 + /**
34 + * File name of the admin screens' parent page in the admin menu.
35 + *
36 + * @since 1.0.0
37 + */
38 + public string $parent_page = 'middle';
39 +
40 + /**
41 + * Whether TablePress admin screens are a top-level menu item in the admin menu.
42 + *
43 + * @since 1.0.0
44 + */
45 + public bool $is_top_level_page = false;
46 +
47 + /**
25 48 * List of tables that are shown for the current request.
26 49 *
27 50 * @since 1.0.0
28 51 * @var array<string, array{count: int, instances: array<string, array<string, mixed>>}>
29 52 */
30 - protected $shown_tables = array();
53 + protected array $shown_tables = array();
31 54
32 55 /**
33 - * Initiate Frontend functionality.
56 + * List of registered DataTables datetime formats.
34 57 *
58 + * @since 3.0.0
59 + * @var string[]
60 + */
61 + protected array $datatables_datetime_formats = array();
62 +
63 + /**
64 + * Initiates Frontend functionality.
65 + *
35 66 * @since 1.0.0
36 67 */
37 68 public function __construct() {
38 69 parent::__construct();
@@ -37,21 +68,37 @@
37 68 public function __construct() {
38 69 parent::__construct();
39 70
40 71 /**
41 - * Filters whether the TablePress Default CSS code shall be loaded.
72 + * Filters the admin menu parent page, which is needed for the construction of plugin URLs.
42 73 *
43 74 * @since 1.0.0
44 75 *
45 - * @param bool $use Whether the Default CSS shall be loaded. Default true.
76 + * @param string $parent_page Current admin menu parent page.
46 77 */
47 - if ( apply_filters( 'tablepress_use_default_css', true ) || TablePress::$model_options->get( 'use_custom_css' ) ) {
78 + $this->parent_page = apply_filters( 'tablepress_admin_menu_parent_page', TablePress::$model_options->get( 'admin_menu_parent_page' ) );
79 + $this->is_top_level_page = in_array( $this->parent_page, array( 'top', 'middle', 'bottom' ), true );
80 +
81 + /**
82 + * Filters whether TablePress should load its frontend CSS files on all pages.
83 + * For block themes, the default behavior is to only load the CSS files when a table is encountered on the page.
84 + * If Elementor is active, the CSS is also loaded on the editor page.
85 + *
86 + * @since 3.0.1
87 + *
88 + * @param bool $use_legacy_css_loading Whether TablePress should load its frontend CSS files on all pages.
89 + */
90 + $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' ) ) );
91 +
92 + if ( $this->use_legacy_css_loading ) {
48 93 add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_css' ) );
49 94 }
50 95
51 - // Add DataTables invocation calls.
52 - add_action( 'wp_print_footer_scripts', array( $this, 'add_datatables_calls' ), 11 ); // After inclusion of files.
96 + // Register a placeholder script handle so that plugins can properly declare it as a dependency, even before the actual script is enqueued in `enqueue_datatables_files()`.
97 + wp_register_script( 'tablepress-datatables', '', array(), TablePress::version, array( 'in_footer' => true ) ); // @phpstan-ignore argument.type ($src is empty, as it's a placeholder.)
53 98
99 + add_action( 'wp_print_footer_scripts', array( $this, 'add_datatables_calls' ), 9 ); // Priority 9 so that this runs before `_wp_footer_scripts()`.
100 +
54 101 // Register TablePress Shortcodes. Priority 20 is kept for backwards-compatibility purposes.
55 102 add_action( 'init', array( $this, 'init_shortcodes' ), 20 );
56 103
57 104 /**
@@ -73,18 +120,28 @@
73 120
74 121 /**
75 122 * Register the tablepress/table block and its dependencies.
76 123 */
77 - register_block_type(
78 - TABLEPRESS_ABSPATH . 'blocks/table/',
124 + wp_register_block_metadata_collection(
125 + TABLEPRESS_ABSPATH . 'blocks',
126 + TABLEPRESS_ABSPATH . 'blocks/blocks-manifest.php',
127 + );
128 + register_block_type_from_metadata(
129 + TABLEPRESS_ABSPATH . 'blocks/table/block.json',
79 130 array(
80 131 'render_callback' => array( $this, 'table_block_render_callback' ),
81 - )
132 + ),
82 133 );
134 +
135 + /**
136 + * Register the TablePress Elementor widgets.
137 + */
138 + add_action( 'elementor/widgets/register', array( $this, 'register_elementor_widgets' ) );
139 + add_action( 'elementor/editor/after_enqueue_styles', array( $this, 'enqueue_elementor_editor_styles' ), 10, 0 );
83 140 }
84 141
85 142 /**
86 - * Register TablePress Shortcodes.
143 + * Registers TablePress Shortcodes.
87 144 *
88 145 * @since 1.0.0
89 146 */
90 147 public function init_shortcodes(): void {
@@ -92,18 +149,67 @@
92 149 add_shortcode( TablePress::$shortcode_info, array( $this, 'shortcode_table_info' ) );
93 150 }
94 151
95 152 /**
96 - * Enqueue CSS files for default CSS and "Custom CSS" (if desired).
153 + * Checks if the CSS files for TablePress default CSS and "Custom CSS" should be loaded.
97 154 *
155 + * This function is only called when a [table /] Shortcode or "TablePress Table" block is evaluated, so that CSS files are only loaded when needed.
156 + *
157 + * @since 3.0.0
158 + */
159 + public function maybe_enqueue_css(): void {
160 + // Bail early if the legacy CSS loading mechanism is used, as the files will then have been enqueued already.
161 + if ( $this->use_legacy_css_loading && ! doing_action( 'enqueue_block_assets' ) ) {
162 + return;
163 + }
164 +
165 + /*
166 + * Bail early if the function is called from some action hook outside of the normal rendering process.
167 + * 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.
168 + * In these cases, we don't want to enqueue the CSS, as it would likely not be printed on the page.
169 + */
170 + if ( doing_action( 'wp_head' ) || doing_action( 'wp_footer' ) ) {
171 + return;
172 + }
173 +
174 + // Prevent repeated execution via a static variable.
175 + static $css_enqueued = false;
176 + if ( $css_enqueued && ! doing_action( 'enqueue_block_assets' ) ) {
177 + return;
178 + }
179 + $css_enqueued = true;
180 +
181 + $this->enqueue_css();
182 + }
183 +
184 + /**
185 + * Enqueues CSS files for TablePress default CSS and "Custom CSS" (if desired).
186 + *
187 + * If styles have not been printed to the page (in the `<head>`), the TablePress CSS files will be enqueued.
188 + * If styles have already been printed to the page, the TablePress CSS files will be printed right away (likely in the `<body`>).
189 + *
98 190 * @since 1.0.0
99 191 */
100 192 public function enqueue_css(): void {
101 - /** This filter is documented in controllers/controller-frontend.php */
193 + /**
194 + * Filters whether the TablePress Default CSS code shall be loaded.
195 + *
196 + * @since 1.0.0
197 + *
198 + * @param bool $use Whether the Default CSS shall be loaded. Default true.
199 + */
102 200 $use_default_css = apply_filters( 'tablepress_use_default_css', true );
201 + $use_custom_css = TablePress::$model_options->get( 'use_custom_css' );
202 +
203 + if ( ! $use_default_css && ! $use_custom_css ) {
204 + // Register a placeholder dependency, so that the handle is known for other styles.
205 + wp_register_style( 'tablepress-default', false ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
206 + return;
207 + }
208 +
103 209 $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' ) );
210 + $use_custom_css = $use_custom_css && '' !== $custom_css;
211 + $use_custom_css_file = $use_custom_css && TablePress::$model_options->get( 'use_custom_css_file' );
106 212 /**
107 213 * Filters the "Custom CSS" version number that is appended to the enqueued CSS files
108 214 *
109 215 * @since 1.0.0
@@ -131,81 +237,71 @@
131 237 if ( $use_custom_css_combined_file ) {
132 238 $custom_css_combined_url = $tablepress_css->get_custom_css_location( 'combined', 'url' );
133 239 // Need to use 'tablepress-default' instead of 'tablepress-combined' to not break existing TablePress Extensions.
134 240 wp_enqueue_style( 'tablepress-default', $custom_css_combined_url, array(), $custom_css_version );
241 + if ( did_action( 'wp_print_styles' ) ) {
242 + wp_print_styles( 'tablepress-default' );
243 + }
244 + return;
245 + }
246 +
247 + if ( $use_default_css ) {
248 + wp_enqueue_style( 'tablepress-default', $default_css_url, array(), TablePress::version );
135 249 } 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';
250 + // Register a placeholder dependency, so that the handle is known for other styles.
251 + wp_register_style( 'tablepress-default', false ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
252 + }
253 +
254 + $use_custom_css_minified_file = ( $use_custom_css_file && ! SCRIPT_DEBUG && $tablepress_css->load_custom_css_from_file( 'minified' ) );
255 + if ( $use_custom_css_minified_file ) {
256 + $custom_css_minified_url = $tablepress_css->get_custom_css_location( 'minified', 'url' );
257 + wp_enqueue_style( 'tablepress-custom', $custom_css_minified_url, array( 'tablepress-default' ), $custom_css_version );
258 + if ( did_action( 'wp_print_styles' ) ) {
259 + wp_print_styles( 'tablepress-custom' );
141 260 }
261 + return;
262 + }
142 263
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;
264 + $use_custom_css_normal_file = ( $use_custom_css_file && $tablepress_css->load_custom_css_from_file( 'normal' ) );
265 + if ( $use_custom_css_normal_file ) {
266 + $custom_css_normal_url = $tablepress_css->get_custom_css_location( 'normal', 'url' );
267 + wp_enqueue_style( 'tablepress-custom', $custom_css_normal_url, array( 'tablepress-default' ), $custom_css_version );
268 + if ( did_action( 'wp_print_styles' ) ) {
269 + wp_print_styles( 'tablepress-custom' );
148 270 }
271 + return;
272 + }
149 273
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;
274 + if ( $use_custom_css ) {
275 + // Get "Custom CSS" from options, try minified Custom CSS first.
276 + $custom_css_minified = TablePress::$model_options->get( 'custom_css_minified' );
277 + if ( ! empty( $custom_css_minified ) ) {
278 + $custom_css = $custom_css_minified;
155 279 }
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;
280 + /**
281 + * Filters the "Custom CSS" code that is to be loaded as inline CSS.
282 + *
283 + * @since 1.0.0
284 + *
285 + * @param string $custom_css The "Custom CSS" code.
286 + */
287 + $custom_css = apply_filters( 'tablepress_custom_css', $custom_css );
288 + if ( ! empty( $custom_css ) ) {
289 + wp_add_inline_style( 'tablepress-default', $custom_css );
290 + if ( did_action( 'wp_print_styles' ) ) {
291 + wp_print_styles( 'tablepress-default' );
162 292 }
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 - }
293 + return;
180 294 }
181 295 }
182 296 }
183 297
184 298 /**
185 - * Print "Custom CSS" to "wp_head" inline.
299 + * Enqueues the DataTables JavaScript library and its dependencies.
186 300 *
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
301 + * @since 3.0.0
190 302 */
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 {
303 + protected function enqueue_datatables_files(): void {
208 304 $js_file = 'js/jquery.datatables.min.js';
209 305 $js_url = plugins_url( $js_file, TABLEPRESS__FILE__ );
210 306 /**
211 307 * Filters the URL from which the DataTables JavaScript library file is loaded.
@@ -215,13 +311,31 @@
215 311 * @param string $js_url URL of the DataTables JS library file.
216 312 * @param string $js_file Path and file name of the DataTables JS library file.
217 313 */
218 314 $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 );
315 +
316 + $dependencies = array( 'jquery-core' );
317 + if ( ! empty( $this->datatables_datetime_formats ) ) {
318 + $dependencies[] = 'moment';
319 + }
320 + /**
321 + * Filters the dependencies for the DataTables JavaScript library.
322 + *
323 + * @since 3.0.0
324 + *
325 + * @param string[] $dependencies The dependencies for the DataTables JS library.
326 + *
327 + * @phpstan-param non-empty-string[] $dependencies
328 + */
329 + $dependencies = apply_filters( 'tablepress_datatables_js_dependencies', $dependencies );
330 +
331 + // De-register the placeholder script handle and enqueue the actual script, so that the dependencies are correct.
332 + wp_deregister_script( 'tablepress-datatables' );
333 + wp_enqueue_script( 'tablepress-datatables', $js_url, $dependencies, TablePress::version, array( 'in_footer' => true ) );
220 334 }
221 335
222 336 /**
223 - * Add JS code for invocation of DataTables JS library.
337 + * Adds the JavaScript code for the invocation of the DataTables JS library.
224 338 *
225 339 * @since 1.0.0
226 340 */
227 341 public function add_datatables_calls(): void {
@@ -230,16 +344,16 @@
230 344 if ( $datatables_calls_printed ) {
231 345 return;
232 346 }
233 347
348 + // Bail early if there are no TablePress tables on the page.
234 349 if ( empty( $this->shown_tables ) ) {
235 - // There are no tables with activated DataTables on the page that is currently rendered.
236 350 return;
237 351 }
238 352
239 353 /*
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.
354 + * Don't add the DataTables function calls in the scope of the block editor iframe.
355 + * This is necessary for non-block themes, for others, the repeated execution check above is sufficient.
242 356 */
243 357 if ( function_exists( 'get_current_screen' ) ) {
244 358 $current_screen = get_current_screen();
245 359 if ( ( $current_screen instanceof WP_Screen ) && $current_screen->is_block_editor() ) {
@@ -246,20 +360,31 @@
246 360 return;
247 361 }
248 362 }
249 363
364 + // Filter out all tables that use DataTables.
365 + $shown_tables_with_datatables = array();
366 + foreach ( $this->shown_tables as $table_id => $table_store ) {
367 + if ( ! empty( $table_store['instances'] ) ) {
368 + $shown_tables_with_datatables[ (string) $table_id ] = $table_store;
369 + }
370 + }
371 +
372 + // Bail early if there are no tables with activated DataTables on the page.
373 + if ( empty( $shown_tables_with_datatables ) ) {
374 + return;
375 + }
376 +
377 + $this->enqueue_datatables_files();
378 +
250 379 // Storage for the DataTables language strings.
251 380 $datatables_language = array();
252 381 // Generate the specific JS commands, depending on chosen features on the "Edit" screen and the Shortcode parameters.
253 382 $commands = array();
254 383
255 - foreach ( $this->shown_tables as $table_id => $table_store ) {
384 + foreach ( $shown_tables_with_datatables as $table_id => $table_store ) {
256 385 $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.
257 386
258 - if ( empty( $table_store['instances'] ) ) {
259 - continue;
260 - }
261 -
262 387 foreach ( $table_store['instances'] as $html_id => $js_options ) {
263 388 $parameters = array();
264 389
265 390 // Settle dependencies/conflicts between certain features.
@@ -314,9 +439,9 @@
314 439 $datatables_strings = array();
315 440 }
316 441 } elseif ( str_ends_with( $language_file, '.json' ) ) {
317 442 $datatables_strings = file_get_contents( $language_file );
318 - $datatables_strings = json_decode( $datatables_strings, true ); // @phpstan-ignore-line
443 + $datatables_strings = json_decode( $datatables_strings, true ); // @phpstan-ignore argument.type
319 444 // Check if JSON could be decoded.
320 445 if ( is_null( $datatables_strings ) ) {
321 446 $datatables_strings = array();
322 447 }
@@ -340,58 +465,56 @@
340 465 * @param string $datatables_locale Current locale/language for the DataTables JS library.
341 466 */
342 467 $datatables_language[ $datatables_locale ] = apply_filters( 'tablepress_datatables_language_strings', $datatables_strings, $datatables_locale );
343 468 }
344 - $parameters['language'] = '"language":DT_language["' . $datatables_locale . '"]';
469 + $parameters['language'] = "language:DT_language['{$datatables_locale}']";
345 470
346 471 // These parameters need to be added for performance gain or to overwrite unwanted default behavior.
347 472 if ( $js_options['datatables_sort'] ) {
348 473 // No initial sort.
349 - $parameters['order'] = '"order":[]';
474 + $parameters['order'] = 'order:[]';
350 475 // Don't add additional classes, to speed up sorting.
351 - $parameters['orderClasses'] = '"orderClasses":false';
476 + $parameters['orderClasses'] = 'orderClasses:false';
352 477 }
353 478
354 - // Alternating row colors is default, so remove them if not wanted with [].
355 - $parameters['stripeClasses'] = '"stripeClasses":' . ( ( $js_options['alternating_row_colors'] ) ? '["even","odd"]' : '[]' );
356 -
357 479 // 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.
358 480 if ( ! $js_options['datatables_sort'] ) {
359 - $parameters['ordering'] = '"ordering":false';
481 + $parameters['ordering'] = 'ordering:false';
360 482 }
361 483 if ( $js_options['datatables_paginate'] ) {
362 - $parameters['pagingType'] = '"pagingType":"simple"';
484 + $parameters['pagingType'] = "pagingType:'simple_numbers'";
363 485 if ( $js_options['datatables_lengthchange'] ) {
364 486 $length_menu = array( 10, 25, 50, 100 );
365 487 if ( ! in_array( $js_options['datatables_paginate_entries'], $length_menu, true ) ) {
366 488 $length_menu[] = $js_options['datatables_paginate_entries'];
367 489 sort( $length_menu, SORT_NUMERIC );
368 - $parameters['lengthMenu'] = '"lengthMenu":[' . implode( ',', $length_menu ) . ']';
490 + $parameters['lengthMenu'] = 'lengthMenu:[' . implode( ',', $length_menu ) . ']';
369 491 }
370 492 } else {
371 - $parameters['lengthChange'] = '"lengthChange":false';
493 + $parameters['lengthChange'] = 'lengthChange:false';
372 494 }
373 495 if ( 10 !== $js_options['datatables_paginate_entries'] ) {
374 - $parameters['pageLength'] = '"pageLength":' . $js_options['datatables_paginate_entries'];
496 + $parameters['pageLength'] = "pageLength:{$js_options['datatables_paginate_entries']}";
375 497 }
376 498 } else {
377 - $parameters['paging'] = '"paging":false';
499 + $parameters['paging'] = 'paging:false';
378 500 }
379 501 if ( ! $js_options['datatables_filter'] ) {
380 - $parameters['searching'] = '"searching":false';
502 + $parameters['searching'] = 'searching:false';
381 503 }
382 504 if ( ! $js_options['datatables_info'] ) {
383 - $parameters['info'] = '"info":false';
505 + $parameters['info'] = 'info:false';
384 506 }
385 507 if ( $js_options['datatables_scrollx'] ) {
386 - $parameters['scrollX'] = '"scrollX":true';
508 + $parameters['scrollX'] = 'scrollX:true';
387 509 }
388 510 if ( false !== $js_options['datatables_scrolly'] ) {
389 - $parameters['scrollY'] = '"scrollY":"' . preg_replace( '#[^0-9a-z.%]#', '', $js_options['datatables_scrolly'] ) . '"';
390 - $parameters['scrollCollapse'] = '"scrollCollapse":true';
511 + $parameters['scrollY'] = 'scrollY:"' . preg_replace( '#[^0-9a-z.%]#', '', $js_options['datatables_scrolly'] ) . '"';
512 + $parameters['scrollCollapse'] = 'scrollCollapse:true';
391 513 }
392 - if ( ! empty( $js_options['datatables_custom_commands'] ) ) {
393 - $parameters['custom_commands'] = $js_options['datatables_custom_commands'];
514 + if ( '' !== $js_options['datatables_custom_commands'] ) {
515 + $parameters['custom_commands'] = trim( $js_options['datatables_custom_commands'] ); // Remove leading and trailing whitespace.
516 + $parameters['custom_commands'] = trim( $parameters['custom_commands'], ',' ); // Remove potentially leading and trailing commas to prevent JS script errors.
394 517 }
395 518
396 519 /**
397 520 * Filters the parameters that are passed to the DataTables JavaScript library.
@@ -404,21 +527,22 @@
404 527 * @param array<string, mixed> $js_options The options for the JS library.
405 528 */
406 529 $parameters = apply_filters( 'tablepress_datatables_parameters', $parameters, $table_id, $html_id, $js_options );
407 530
408 - // If an existing parameter (in the from `"parameter":`) is set in the "Custom Commands", remove its default value.
409 - if ( isset( $parameters['custom_commands'] ) ) {
410 - foreach ( array_keys( $parameters ) as $maybe_overwritten_parameter ) {
411 - if ( str_contains( $parameters['custom_commands'], "\"{$maybe_overwritten_parameter}\":" ) ) {
412 - unset( $parameters[ $maybe_overwritten_parameter ] );
413 - }
531 + // If an existing parameter is set as an object key in the "Custom Commands", remove its separate value, to allow for full overrides.
532 + if ( isset( $parameters['custom_commands'] ) && '' !== $parameters['custom_commands'] ) {
533 + $parameters_in_custom_commands = TablePress::extract_keys_from_js_object_string( '{' . $parameters['custom_commands'] . '}' );
534 + foreach ( $parameters_in_custom_commands as $parameter_in_custom_commands ) {
535 + unset( $parameters[ $parameter_in_custom_commands ] );
414 536 }
415 537 }
416 538
539 + $name = substr( $html_id, 11 ); // Remove "tablepress-" from the HTML ID.
540 + $name = "DT_TP['" . str_replace( '-', '_', $name ) . "']";
417 541 $parameters = implode( ',', $parameters );
418 542 $parameters = ( ! empty( $parameters ) ) ? '{' . $parameters . '}' : '';
419 543
420 - $command = "$('#{$html_id}').DataTable({$parameters});";
544 + $command = "{$name} = new DataTable('#{$html_id}',{$parameters});";
421 545 /**
422 546 * Filters the JavaScript command that invokes the DataTables JavaScript library on one table.
423 547 *
424 548 * @since 1.0.0
@@ -427,10 +551,11 @@
427 551 * @param string $html_id The ID of the table HTML element.
428 552 * @param string $parameters The parameters for the DataTables JS library.
429 553 * @param string $table_id The current table ID.
430 554 * @param array<string, mixed> $js_options The options for the JS library.
555 + * @param string $name The name of the DataTable instance.
431 556 */
432 - $command = apply_filters( 'tablepress_datatables_command', $command, $html_id, $parameters, $table_id, $js_options );
557 + $command = apply_filters( 'tablepress_datatables_command', $command, $html_id, $parameters, $table_id, $js_options, $name );
433 558 if ( ! empty( $command ) ) {
434 559 $commands[] = $command;
435 560 }
436 561 } // foreach table instance
@@ -436,15 +561,40 @@
436 561 } // foreach table instance
437 562 } // foreach table ID
438 563
439 564 // DataTables language/translation handling.
440 - if ( ! empty( $datatables_language ) ) {
441 - $datatables_language = wp_json_encode( $datatables_language, JSON_UNESCAPED_UNICODE | JSON_FORCE_OBJECT );
442 - $datatables_language = "var DT_language={$datatables_language};\n";
565 + $datatables_language_command = wp_json_encode( $datatables_language, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_FORCE_OBJECT );
566 + $datatables_language_command = "var DT_language={$datatables_language_command};\n";
567 +
568 + // DataTables datetime format string handling.
569 + if ( ! empty( $this->datatables_datetime_formats ) ) {
570 + // Create a command like `DataTable.datetime("MM/DD/YYYY");DataTable.datetime("DD.MM.YYYY");`.
571 + $datatables_datetime_command = implode(
572 + '',
573 + array_map(
574 + static function ( string $datetime_format ): string {
575 + $datetime_format = wp_json_encode( $datetime_format, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES );
576 + return "DataTable.datetime({$datetime_format});";
577 + },
578 + $this->datatables_datetime_formats,
579 + )
580 + ) . "\n";
443 581 } else {
444 - $datatables_language = '';
582 + $datatables_datetime_command = '';
445 583 }
446 584
585 + /**
586 + * Filters the JavaScript code for the DataTables JavaScript library that initializes the automatically detected date/time formats via moment.js.
587 + *
588 + * @since 3.0.0
589 + *
590 + * @param string $datatables_datetime_command The JS code for the DataTables JS library that initializes the date/time formats.
591 + * @param string[] $datatables_datetime_formats The date/time formats for moment.js.
592 + */
593 + $datatables_datetime_command = apply_filters( 'tablepress_datatables_datetime_command', $datatables_datetime_command, $this->datatables_datetime_formats );
594 +
595 + $datatables_pre_commands = $datatables_language_command . $datatables_datetime_command;
596 +
447 597 $commands = implode( "\n", $commands );
448 598 /**
449 599 * Filters the JavaScript commands that invoke the DataTables JavaScript library on all tables on the page.
450 600 *
@@ -456,41 +606,40 @@
456 606 if ( '' === $commands ) {
457 607 return;
458 608 }
459 609
460 - $script_type_attr = current_theme_supports( 'html5', 'script' ) ? '' : ' type="text/javascript"';
461 -
462 - $js_wrapper = <<<'JS'
463 -<script%3$s>
464 -jQuery(function($){
465 -%1$s%2$s
466 -});
467 -</script>
468 -JS;
610 + $script_template = <<<'JS'
611 + var DT_TP = {};
612 + jQuery(($)=>{
613 + %1$s%2$s
614 + });
615 + JS;
469 616 /**
470 617 * Filters the script/jQuery wrapper code for the DataTables commands calls.
471 618 *
472 619 * @since 1.14.0
473 620 *
474 - * @param string $js_wrapper Default script/jQuery wrapper code for the DataTables commands calls.
621 + * @param string $script_template Default script/jQuery wrapper code for the DataTables commands calls.
475 622 */
476 - $js_wrapper = apply_filters( 'tablepress_all_datatables_commands_wrapper', $js_wrapper );
477 - printf( $js_wrapper, $datatables_language, $commands, $script_type_attr );
623 + $script_template = apply_filters( 'tablepress_all_datatables_commands_wrapper', $script_template );
478 624
625 + $script = sprintf( $script_template, $datatables_pre_commands, $commands );
626 + wp_add_inline_script( 'tablepress-datatables', $script );
627 +
479 628 // Prevent repeated execution (which would lead to DataTables error messages) via a static variable.
480 629 $datatables_calls_printed = true;
481 630 }
482 631
483 632 /**
484 - * Handle Shortcode [table id=<ID> /].
633 + * Handles the Shortcode [table id=<ID> /].
485 634 *
486 635 * @since 1.0.0
487 636 *
488 - * @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 /].
637 + * @param array<string, mixed> $shortcode_atts List of attributes that where included in the Shortcode.
489 638 * @return string Resulting HTML code for the table with the ID <ID>.
490 639 */
491 - public function shortcode_table( /* array|string */ $shortcode_atts ): string {
492 - $shortcode_atts = (array) $shortcode_atts;
640 + public function shortcode_table( array $shortcode_atts ): string {
641 + $this->maybe_enqueue_css();
493 642
494 643 $_render = TablePress::load_class( 'TablePress_Render', 'class-render.php', 'classes' );
495 644
496 645 $default_shortcode_atts = $_render->get_default_render_options();
@@ -559,19 +708,26 @@
559 708 $message = apply_filters( 'tablepress_table_corrupted_message', $message, $table_id, $table['json_error'] );
560 709 return $message;
561 710 }
562 711
563 - /**
564 - * Filters whether the "datatables_custom_commands" Shortcode parameter is disabled.
565 - *
566 - * By default, the "datatables_custom_commands" Shortcode parameter is disabled for security reasons.
567 - *
568 - * @since 1.0.0
569 - *
570 - * @param bool $disable Whether to disable the "datatables_custom_commands" Shortcode parameter. Default true.
571 - */
572 - if ( ! is_null( $shortcode_atts['datatables_custom_commands'] ) && apply_filters( 'tablepress_disable_custom_commands_shortcode_parameter', true ) ) {
573 - $shortcode_atts['datatables_custom_commands'] = null;
712 + if ( ! is_null( $shortcode_atts['datatables_custom_commands'] ) ) {
713 + /**
714 + * Filters whether the "datatables_custom_commands" Shortcode parameter is disabled.
715 + *
716 + * By default, the "datatables_custom_commands" Shortcode parameter is disabled for security reasons.
717 + *
718 + * @since 1.0.0
719 + *
720 + * @param bool $disable Whether to disable the "datatables_custom_commands" Shortcode parameter. Default true.
721 + */
722 + if ( apply_filters( 'tablepress_disable_custom_commands_shortcode_parameter', true ) ) {
723 + $shortcode_atts['datatables_custom_commands'] = null;
724 + } else {
725 + // Convert the HTML entity `&amp;` back to `&` manually, as entities in Shortcodes in normal text paragraphs are sometimes double-encoded.
726 + $shortcode_atts['datatables_custom_commands'] = str_replace( '&amp;', '&', $shortcode_atts['datatables_custom_commands'] );
727 + // Convert HTML entities like `&lt;`, `&lsqb;`, `&#91;`, and `&amp;` back to their respective characters.
728 + $shortcode_atts['datatables_custom_commands'] = html_entity_decode( $shortcode_atts['datatables_custom_commands'], ENT_QUOTES | ENT_HTML5, get_option( 'blog_charset' ) );
729 + }
574 730 }
575 731
576 732 // Determine options to use (if set in Shortcode, use those, otherwise use stored options, from the "Edit" screen).
577 733 $render_options = array();
@@ -594,8 +750,12 @@
594 750 $render_options[ $key ] = $value;
595 751 }
596 752 }
597 753
754 + // Backward compatibility: Convert boolean or numeric string "table_head" and "table_foot" options to integer.
755 + $render_options['table_head'] = absint( $render_options['table_head'] );
756 + $render_options['table_foot'] = absint( $render_options['table_foot'] );
757 +
598 758 // Generate unique HTML ID, depending on how often this table has already been shown on this page.
599 759 if ( ! isset( $this->shown_tables[ $table_id ] ) ) {
600 760 $this->shown_tables[ $table_id ] = array(
601 761 'count' => 0,
@@ -630,9 +790,9 @@
630 790 *
631 791 * @param bool $show Whether to show the "Edit" link below the table. Default true.
632 792 * @param string $table_id The current table ID.
633 793 */
634 - if ( is_user_logged_in() && apply_filters( 'tablepress_edit_link_below_table', true, $table['id'] ) && current_user_can( 'tablepress_edit_table', $table['id'] ) ) {
794 + 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'] ) ) {
635 795 $render_options['edit_table_url'] = TablePress::url( array( 'action' => 'edit', 'table_id' => $table['id'] ) );
636 796 }
637 797
638 798 /**
@@ -646,12 +806,16 @@
646 806 * @param array<string, mixed> $table The current table.
647 807 */
648 808 $render_options = apply_filters( 'tablepress_table_render_options', $render_options, $table );
649 809
810 + // Backward compatibility: Convert boolean "table_head" and "table_foot" options to integer, in case they were overwritten via the filter hook.
811 + $render_options['table_head'] = absint( $render_options['table_head'] );
812 + $render_options['table_foot'] = absint( $render_options['table_foot'] );
813 +
650 814 // Check if table output shall and can be loaded from the transient cache, otherwise generate the output.
651 815 if ( $render_options['cache_table_output'] && ! is_user_logged_in() ) {
652 816 // Hash the Render Options array to get a unique cache identifier.
653 - $table_hash = md5( wp_json_encode( $render_options, TABLEPRESS_JSON_OPTIONS ) ); // @phpstan-ignore-line
817 + $table_hash = md5( wp_json_encode( $render_options, TABLEPRESS_JSON_OPTIONS ) ); // @phpstan-ignore argument.type
654 818 $transient_name = 'tablepress_' . $table_hash; // Attention: This string must not be longer than 45 characters!
655 819 $output = get_transient( $transient_name );
656 820 if ( false === $output || '' === $output ) {
657 821 // Render/generate the table HTML, as it was not found in the cache.
@@ -688,13 +852,11 @@
688 852 }
689 853
690 854 // 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.
691 855 if ( $render_options['use_datatables']
692 - && $render_options['table_head']
693 - && str_contains( $output, '<thead' ) // A `<thead>` tag is required.
694 - && ! str_contains( $output, ' colspan="' ) // `colspan` attributes are forbidden.
695 - && ! str_contains( $output, ' rowspan="' ) // `rowspan` attributes are forbidden.
696 - ) {
856 + && 0 < $render_options['table_head']
857 + && ! 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>`.
858 + ) {
697 859 // Get options for the DataTables JavaScript library from the table's render options.
698 860 $js_options = array();
699 861 foreach ( array(
700 862 'alternating_row_colors',
@@ -724,15 +886,26 @@
724 886 * @param string $table_id The current table ID.
725 887 * @param array<string, mixed> $render_options The render options for the table.
726 888 */
727 889 $js_options = apply_filters( 'tablepress_table_js_options', $js_options, $table_id, $render_options );
890 +
728 891 $this->shown_tables[ $table_id ]['instances'][ (string) $render_options['html_id'] ] = $js_options;
729 - $this->_enqueue_datatables();
892 +
893 + // DataTables datetime format string handling.
894 + if ( '' !== $render_options['datatables_datetime'] ) {
895 + $render_options['datatables_datetime'] = explode( '|', $render_options['datatables_datetime'] );
896 + foreach ( $render_options['datatables_datetime'] as $datetime_format ) {
897 + $datetime_format = trim( $datetime_format );
898 + if ( '' !== $datetime_format && ! in_array( $datetime_format, $this->datatables_datetime_formats, true ) ) {
899 + $this->datatables_datetime_formats[] = $datetime_format;
900 + }
901 + }
902 + }
730 903 }
731 904
732 905 // Maybe print a list of used render options.
733 906 if ( $render_options['shortcode_debug'] && is_user_logged_in() ) {
734 - $output .= '<pre>' . var_export( $render_options, true ) . '</pre>'; // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export
907 + $output .= '<pre>' . esc_html( wp_json_encode( $render_options, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ) ) . '</pre>'; // @phpstan-ignore argument.type
735 908 }
736 909
737 910 return $output;
738 911 }
@@ -737,18 +910,16 @@
737 910 return $output;
738 911 }
739 912
740 913 /**
741 - * Handle Shortcode [table-info id=<ID> field=<name> /].
914 + * Handles the Shortcode [table-info id=<ID> field=<name> /].
742 915 *
743 916 * @since 1.0.0
744 917 *
745 - * @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 /].
918 + * @param array<string, mixed> $shortcode_atts List of attributes that where included in the Shortcode.
746 919 * @return string Text that replaces the Shortcode (error message or asked-for information).
747 920 */
748 - public function shortcode_table_info( /* array|string */ $shortcode_atts ): string {
749 - $shortcode_atts = (array) $shortcode_atts;
750 -
921 + public function shortcode_table_info( array $shortcode_atts ): string {
751 922 // Parse Shortcode attributes, only allow those that are specified.
752 923 $default_shortcode_atts = array(
753 924 'id' => '',
754 925 'field' => '',
@@ -810,8 +981,15 @@
810 981 switch ( $field ) {
811 982 case 'name':
812 983 case 'description':
813 984 $output = $table[ $field ];
985 + // Replace any & with &amp; that is not already an encoded entity (from function htmlentities2 in WP 2.8).
986 + // A complete htmlentities2() or htmlspecialchars() would encode <HTML> tags, which we don't want.
987 + $output = (string) preg_replace( '/&(?![A-Za-z]{0,4}\w{2,3};|#[0-9]{2,4};)/', '&amp;', $output );
988 + /** This filter is documented in classes/class-render.php */
989 + if ( apply_filters( 'tablepress_apply_nl2br', true, $table_id ) ) {
990 + $output = nl2br( $output );
991 + }
814 992 break;
815 993 case 'last_modified':
816 994 switch ( $format ) {
817 995 case 'raw':
@@ -819,9 +997,13 @@
819 997 $output = $table['last_modified'];
820 998 break;
821 999 case 'human':
822 1000 $modified_timestamp = date_create( $table['last_modified'], wp_timezone() );
823 - $modified_timestamp = $modified_timestamp->getTimestamp(); // @phpstan-ignore-line
1001 + if ( false === $modified_timestamp ) {
1002 + $modified_timestamp = $table['last_modified'];
1003 + } else {
1004 + $modified_timestamp = $modified_timestamp->getTimestamp();
1005 + }
824 1006 $current_timestamp = time();
825 1007 $time_diff = $current_timestamp - $modified_timestamp;
826 1008 // Time difference is only shown up to one week.
827 1009 if ( $time_diff >= 0 && $time_diff < WEEK_IN_SECONDS ) {
@@ -849,14 +1031,10 @@
849 1031 break;
850 1032 case 'number_rows':
851 1033 $output = count( $table['data'] );
852 1034 if ( 'raw' !== $format ) {
853 - if ( $table['options']['table_head'] ) {
854 - --$output;
855 - }
856 - if ( $table['options']['table_foot'] ) {
857 - --$output;
858 - }
1035 + $output -= $table['options']['table_head'];
1036 + $output -= $table['options']['table_foot'];
859 1037 }
860 1038 break;
861 1039 case 'number_columns':
862 1040 $output = count( $table['data'][0] );
@@ -889,9 +1067,9 @@
889 1067 return $output;
890 1068 }
891 1069
892 1070 /**
893 - * Expand WP Search to also find posts and pages that have a search term in a table that is shown in them.
1071 + * Expands the WP Search to also find posts and pages that have a search term in a table that is shown in them.
894 1072 *
895 1073 * This is done by looping through all search terms and TablePress tables and searching there for the search term,
896 1074 * 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
897 1075 * Shortcode for one of these tables in their content.
@@ -908,9 +1086,9 @@
908 1086
909 1087 global $wpdb;
910 1088
911 1089 // Protect against cases where `null` is somehow passed to the filter hook callback.
912 - if ( ! is_string( $search_sql ) ) {
1090 + if ( ! is_string( $search_sql ) ) { // @phpstan-ignore function.alreadyNarrowedType (The `is_string()` check is needed as the input is coming from a filter hook.)
913 1091 return '';
914 1092 }
915 1093
916 1094 if ( ! is_search() || ! is_main_query() ) {
@@ -927,8 +1105,10 @@
927 1105 $table_ids = TablePress::$model_table->load_all( true, false );
928 1106 // Array of all search words that were found, and the table IDs where they were found.
929 1107 $query_result = array();
930 1108
1109 + $fn_stripos = function_exists( 'mb_stripos' ) ? 'mb_stripos' : 'stripos';
1110 +
931 1111 foreach ( $table_ids as $table_id ) {
932 1112 // Load table, with table data, options, and visibility settings.
933 1113 $table = TablePress::$model_table->load( $table_id, true, true );
934 1114
@@ -942,10 +1122,10 @@
942 1122 continue;
943 1123 }
944 1124
945 1125 foreach ( $search_terms as $search_term ) {
946 - if ( ( $table['options']['print_name'] && false !== stripos( $table['name'], $search_term ) )
947 - || ( $table['options']['print_description'] && false !== stripos( $table['description'], $search_term ) ) ) {
1126 + if ( ( $table['options']['print_name'] && false !== $fn_stripos( $table['name'], (string) $search_term ) )
1127 + || ( $table['options']['print_description'] && false !== $fn_stripos( $table['description'], (string) $search_term ) ) ) {
948 1128 // Found the search term in the name or description (and they are shown).
949 1129 $query_result[ $search_term ][] = $table_id; // Add table ID to result list.
950 1130 // No need to continue searching this search term in this table.
951 1131 continue;
@@ -962,9 +1142,9 @@
962 1142 // Column is hidden, so don't search in it.
963 1143 continue;
964 1144 }
965 1145 // @todo Cells are not evaluated here, so math formulas are searched.
966 - if ( false !== stripos( $table_cell, $search_term ) ) {
1146 + if ( false !== $fn_stripos( $table_cell, (string) $search_term ) ) {
967 1147 // Found the search term in the cell content.
968 1148 $query_result[ $search_term ][] = $table_id; // Add table ID to result list
969 1149 // No need to continue searching this search term in this table.
970 1150 continue 3;
@@ -981,9 +1161,9 @@
981 1161 $n = ( empty( $exact ) ) ? '%' : '';
982 1162 $search_sql = $wpdb->remove_placeholder_escape( $search_sql );
983 1163 foreach ( $query_result as $search_term => $table_ids ) {
984 1164 $search_term = esc_sql( $wpdb->esc_like( $search_term ) );
985 - $old_or = "OR ({$wpdb->posts}.post_content LIKE '{$n}{$search_term}{$n}')"; // @phpstan-ignore-line (The esc_sql() call above returns a string, as a string is passed.)
1165 + $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.)
986 1166 $table_ids = implode( '|', $table_ids );
987 1167 $regexp = '\\\\[' . TablePress::$shortcode . ' id=(["\\\']?)(' . $table_ids . ')([\]"\\\' /])'; // ' needs to be single escaped, [ double escaped (with \\) in mySQL
988 1168 $new_or = $old_or . " OR ({$wpdb->posts}.post_content REGEXP '{$regexp}')";
989 1169 $search_sql = str_replace( $old_or, $new_or, $search_sql );
@@ -1006,15 +1186,51 @@
1006 1186 if ( '' === $block_attributes['id'] ) {
1007 1187 return '';
1008 1188 }
1009 1189
1010 - if ( '' !== trim( $block_attributes['parameters'] ) ) {
1011 - $render_attributes = shortcode_parse_atts( $block_attributes['parameters'] );
1012 - } else {
1013 - $render_attributes = array();
1014 - }
1190 + $render_attributes = shortcode_parse_atts( $block_attributes['parameters'] );
1015 1191 $render_attributes['id'] = $block_attributes['id'];
1016 1192
1017 1193 return $this->shortcode_table( $render_attributes );
1194 + }
1195 +
1196 + /**
1197 + * Registers the TablePress Elementor widgets.
1198 + *
1199 + * @since 3.1.0
1200 + *
1201 + * @param \Elementor\Widgets_Manager $widgets_manager Elementor widgets manager.
1202 + */
1203 + public function register_elementor_widgets( \Elementor\Widgets_Manager $widgets_manager ): void {
1204 + TablePress::load_file( 'class-elementor-widget-table.php', 'classes' );
1205 + $widgets_manager->register( new TablePress\Elementor\TablePressTableWidget() ); // @phpstan-ignore method.notFound (Elementor methods are not in the stubs.)
1206 + }
1207 +
1208 + /**
1209 + * Enqueues the TablePress Elementor Editor CSS styles.
1210 + *
1211 + * @since 3.1.0
1212 + */
1213 + public function enqueue_elementor_editor_styles(): void {
1214 + $svg_url = plugins_url( 'admin/img/tablepress-editor-button.svg', TABLEPRESS__FILE__ );
1215 + wp_register_style( 'tablepress-elementor', false ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
1216 + wp_add_inline_style(
1217 + 'tablepress-elementor',
1218 + <<<CSS
1219 + .elementor-panel .elementor-element .icon:has(> .tablepress-elementor-icon) {
1220 + height: 43.5px;
1221 + }
1222 + .tablepress-elementor-icon {
1223 + display: inline-block;
1224 + height: 28px;
1225 + width: 28px;
1226 + background-image: url({$svg_url});
1227 + background-repeat: no-repeat;
1228 + background-position: center;
1229 + background-size: 28px auto;
1230 + }
1231 + CSS
1232 + );
1233 + wp_enqueue_style( 'tablepress-elementor' );
1018 1234 }
1019 1235
1020 1236 } // class TablePress_Frontend_Controller