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 +634 -309 1.14 → 3.4 View file →
@@ -7,13 +7,16 @@
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 /**
15 17 * Frontend Controller class, extends Base Controller Class
18 + *
16 19 * @package TablePress
17 20 * @subpackage Controllers
18 21 * @author Tobias Bäthge
19 22 * @since 1.0.0
@@ -20,18 +23,47 @@
20 23 */
21 24 class TablePress_Frontend_Controller extends TablePress_Controller {
22 25
23 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 + /**
24 48 * List of tables that are shown for the current request.
25 49 *
26 50 * @since 1.0.0
27 - * @var array
51 + * @var array<string, array{count: int, instances: array<string, array<string, mixed>>}>
28 52 */
29 - protected $shown_tables = array();
53 + protected array $shown_tables = array();
30 54
31 55 /**
32 - * Initiate Frontend functionality.
56 + * List of registered DataTables datetime formats.
33 57 *
58 + * @since 3.0.0
59 + * @var string[]
60 + */
61 + protected array $datatables_datetime_formats = array();
62 +
63 + /**
64 + * Initiates Frontend functionality.
65 + *
34 66 * @since 1.0.0
35 67 */
36 68 public function __construct() {
37 69 parent::__construct();
@@ -36,26 +68,42 @@
36 68 public function __construct() {
37 69 parent::__construct();
38 70
39 71 /**
40 - * Filter 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.
41 73 *
42 74 * @since 1.0.0
43 75 *
44 - * @param bool $use Whether the Default CSS shall be loaded. Default true.
76 + * @param string $parent_page Current admin menu parent page.
45 77 */
46 - 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 ) {
47 93 add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_css' ) );
48 94 }
49 95
50 - // Add DataTables invocation calls.
51 - 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.)
52 98
53 - // Remove WP-Table Reloaded Shortcodes and CSS, and add TablePress Shortcodes.
54 - add_action( 'init', array( $this, 'init_shortcodes' ), 20 ); // run on priority 20 as WP-Table Reloaded Shortcodes are registered at priority 10
99 + add_action( 'wp_print_footer_scripts', array( $this, 'add_datatables_calls' ), 9 ); // Priority 9 so that this runs before `_wp_footer_scripts()`.
55 100
101 + // Register TablePress Shortcodes. Priority 20 is kept for backwards-compatibility purposes.
102 + add_action( 'init', array( $this, 'init_shortcodes' ), 20 );
103 +
56 104 /**
57 - * Filter whether the WordPress search shall also search TablePress tables.
105 + * Filters whether the WordPress search shall also search TablePress tables.
58 106 *
59 107 * @since 1.0.0
60 108 *
61 109 * @param bool $search Whether the TablePress tables shall be searched. Default true.
@@ -67,57 +115,117 @@
67 115
68 116 /**
69 117 * Load TablePress Template Tag functions.
70 118 */
71 - require_once TABLEPRESS_ABSPATH . 'controllers/template-tag-functions.php';
119 + TablePress::load_file( 'template-tag-functions.php', 'controllers' );
120 +
121 + /**
122 + * Register the tablepress/table block and its dependencies.
123 + */
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',
130 + array(
131 + 'render_callback' => array( $this, 'table_block_render_callback' ),
132 + ),
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 );
72 140 }
73 141
74 142 /**
75 - * Register TablePress Shortcodes, after removing WP-Table Reloaded Shortcodes.
143 + * Registers TablePress Shortcodes.
76 144 *
77 145 * @since 1.0.0
78 146 */
79 - public function init_shortcodes() {
80 - // Remove previously registered [table /] Shortcodes (e.g. from WP-Table Reloaded), as these would otherwise be used instead of TablePress's Shortcodes.
81 - remove_shortcode( TablePress::$shortcode );
82 - remove_shortcode( TablePress::$shortcode_info );
83 - // Dequeue WP-Table Reloaded Default CSS, as it can influence TablePress table styling.
84 - if ( isset( $GLOBALS['WP_Table_Reloaded_Frontend'] ) ) {
85 - remove_action( 'wp_head', array( $GLOBALS['WP_Table_Reloaded_Frontend'], 'add_frontend_css' ) );
147 + public function init_shortcodes(): void {
148 + add_shortcode( TablePress::$shortcode, array( $this, 'shortcode_table' ) );
149 + add_shortcode( TablePress::$shortcode_info, array( $this, 'shortcode_table_info' ) );
150 + }
151 +
152 + /**
153 + * Checks if the CSS files for TablePress default CSS and "Custom CSS" should be loaded.
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;
86 163 }
87 164
88 - add_shortcode( TablePress::$shortcode, array( $this, 'shortcode_table' ) );
89 - add_shortcode( TablePress::$shortcode_info, array( $this, 'shortcode_table_info' ) );
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();
90 182 }
91 183
92 184 /**
93 - * Enqueue CSS files for default CSS and "Custom CSS" (if desired).
185 + * Enqueues CSS files for TablePress default CSS and "Custom CSS" (if desired).
94 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 + *
95 190 * @since 1.0.0
96 191 */
97 - public function enqueue_css() {
98 - /** This filter is documented in controllers/controller-frontend.php */
192 + public function enqueue_css(): void {
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 + */
99 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 +
100 209 $custom_css = TablePress::$model_options->get( 'custom_css' );
101 - $use_custom_css = ( TablePress::$model_options->get( 'use_custom_css' ) && '' !== $custom_css );
102 - $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' );
103 212 /**
104 - * Filter the "Custom CSS" version number that is appended to the enqueued CSS files
213 + * Filters the "Custom CSS" version number that is appended to the enqueued CSS files
105 214 *
106 215 * @since 1.0.0
107 216 *
108 217 * @param int $version The "Custom CSS" version.
109 218 */
110 - $custom_css_version = apply_filters( 'tablepress_custom_css_version', TablePress::$model_options->get( 'custom_css_version' ) );
219 + $custom_css_version = (string) apply_filters( 'tablepress_custom_css_version', TablePress::$model_options->get( 'custom_css_version' ) );
111 220
112 221 $tablepress_css = TablePress::load_class( 'TablePress_CSS', 'class-css.php', 'classes' );
113 222
114 223 // Determine Default CSS URL.
115 224 $rtl = ( is_rtl() ) ? '-rtl' : '';
116 - $suffix = SCRIPT_DEBUG ? '' : '.min';
117 - $unfiltered_default_css_url = plugins_url( "css/default{$rtl}{$suffix}.css", TABLEPRESS__FILE__ );
225 + $unfiltered_default_css_url = plugins_url( "css/build/default{$rtl}.css", TABLEPRESS__FILE__ );
118 226 /**
119 - * Filter the URL from which the TablePress Default CSS file is loaded.
227 + * Filters the URL from which the TablePress Default CSS file is loaded.
120 228 *
121 229 * @since 1.0.0
122 230 *
123 231 * @param string $unfiltered_default_css_url URL of the TablePress Default CSS file.
@@ -129,85 +237,75 @@
129 237 if ( $use_custom_css_combined_file ) {
130 238 $custom_css_combined_url = $tablepress_css->get_custom_css_location( 'combined', 'url' );
131 239 // Need to use 'tablepress-default' instead of 'tablepress-combined' to not break existing TablePress Extensions.
132 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 );
133 249 } else {
134 - $custom_css_dependencies = array();
135 - if ( $use_default_css ) {
136 - wp_enqueue_style( 'tablepress-default', $default_css_url, array(), TablePress::version );
137 - // Add dependency to make sure that Custom CSS is printed after Default CSS.
138 - $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' );
139 260 }
261 + return;
262 + }
140 263
141 - $use_custom_css_minified_file = ( $use_custom_css_file && ! SCRIPT_DEBUG && $tablepress_css->load_custom_css_from_file( 'minified' ) );
142 - if ( $use_custom_css_minified_file ) {
143 - $custom_css_minified_url = $tablepress_css->get_custom_css_location( 'minified', 'url' );
144 - wp_enqueue_style( 'tablepress-custom', $custom_css_minified_url, $custom_css_dependencies, $custom_css_version );
145 - 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' );
146 270 }
271 + return;
272 + }
147 273
148 - $use_custom_css_normal_file = ( $use_custom_css_file && $tablepress_css->load_custom_css_from_file( 'normal' ) );
149 - if ( $use_custom_css_normal_file ) {
150 - $custom_css_normal_url = $tablepress_css->get_custom_css_location( 'normal', 'url' );
151 - wp_enqueue_style( 'tablepress-custom', $custom_css_normal_url, $custom_css_dependencies, $custom_css_version );
152 - 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;
153 279 }
154 -
155 - if ( $use_custom_css ) {
156 - // Get "Custom CSS" from options, try minified Custom CSS first,
157 - $custom_css_minified = TablePress::$model_options->get( 'custom_css_minified' );
158 - if ( ! empty( $custom_css_minified ) ) {
159 - $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' );
160 292 }
161 - /**
162 - * Filter the "Custom CSS" code that is to be loaded as inline CSS.
163 - *
164 - * @since 1.0.0
165 - *
166 - * @param string $custom_css The "Custom CSS" code.
167 - */
168 - $custom_css = apply_filters( 'tablepress_custom_css', $custom_css );
169 - if ( ! empty( $custom_css ) ) {
170 - // wp_add_inline_style() requires a loaded CSS file, so we have to work around that if "Default CSS" is disabled,
171 - if ( $use_default_css ) {
172 - // Handle of the file to which the <style> shall be appended.
173 - wp_add_inline_style( 'tablepress-default', $custom_css );
174 - } else {
175 - add_action( 'wp_head', array( $this, '_print_custom_css' ), 8 ); // priority 8 to hook in right after WP_Styles has been processed
176 - }
177 - }
293 + return;
178 294 }
179 295 }
180 296 }
181 297
182 298 /**
183 - * Print "Custom CSS" to "wp_head" inline.
299 + * Enqueues the DataTables JavaScript library and its dependencies.
184 300 *
185 - * This is necessary if "Default CSS" is off, and saving "Custom CSS" to a file is not possible.
186 - *
187 - * @since 1.0.0
301 + * @since 3.0.0
188 302 */
189 - public function _print_custom_css() {
190 - // Get "Custom CSS" from options, try minified Custom CSS first.
191 - $custom_css = TablePress::$model_options->get( 'custom_css_minified' );
192 - if ( empty( $custom_css ) ) {
193 - $custom_css = TablePress::$model_options->get( 'custom_css' );
194 - }
195 - /** This filter is documented in controllers/controller-frontend.php */
196 - $custom_css = apply_filters( 'tablepress_custom_css', $custom_css );
197 - echo "<style type='text/css'>\n{$custom_css}\n</style>\n";
198 - }
199 -
200 - /**
201 - * Enqueue the DataTables JavaScript library and its dependencies.
202 - *
203 - * @since 1.0.0
204 - */
205 - protected function _enqueue_datatables() {
303 + protected function enqueue_datatables_files(): void {
206 304 $js_file = 'js/jquery.datatables.min.js';
207 305 $js_url = plugins_url( $js_file, TABLEPRESS__FILE__ );
208 306 /**
209 - * Filter the URL from which the DataTables JavaScript library file is loaded.
307 + * Filters the URL from which the DataTables JavaScript library file is loaded.
210 308 *
211 309 * @since 1.0.0
212 310 *
213 311 * @param string $js_url URL of the DataTables JS library file.
@@ -213,36 +311,85 @@
213 311 * @param string $js_url URL of the DataTables JS library file.
214 312 * @param string $js_file Path and file name of the DataTables JS library file.
215 313 */
216 314 $js_url = apply_filters( 'tablepress_datatables_js_url', $js_url, $js_file );
217 - wp_enqueue_script( 'tablepress-datatables', $js_url, array( 'jquery' ), 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 ) );
218 334 }
219 335
220 336 /**
221 - * Add JS code for invocation of DataTables JS library.
337 + * Adds the JavaScript code for the invocation of the DataTables JS library.
222 338 *
223 339 * @since 1.0.0
224 340 */
225 - public function add_datatables_calls() {
341 + public function add_datatables_calls(): void {
342 + // Prevent repeated execution (which would lead to DataTables error messages) via a static variable.
343 + static $datatables_calls_printed = false;
344 + if ( $datatables_calls_printed ) {
345 + return;
346 + }
347 +
348 + // Bail early if there are no TablePress tables on the page.
226 349 if ( empty( $this->shown_tables ) ) {
227 - // There are no tables with activated DataTables on the page that is currently rendered.
228 350 return;
229 351 }
230 352
231 - // Storage for the DataTables languages.
232 - $datatables_languages = array();
353 + /*
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.
356 + */
357 + if ( function_exists( 'get_current_screen' ) ) {
358 + $current_screen = get_current_screen();
359 + if ( ( $current_screen instanceof WP_Screen ) && $current_screen->is_block_editor() ) {
360 + return;
361 + }
362 + }
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 +
379 + // Storage for the DataTables language strings.
380 + $datatables_language = array();
233 381 // Generate the specific JS commands, depending on chosen features on the "Edit" screen and the Shortcode parameters.
234 382 $commands = array();
235 383
236 - foreach ( $this->shown_tables as $table_id => $table_store ) {
237 - if ( empty( $table_store['instances'] ) ) {
238 - continue;
239 - }
384 + foreach ( $shown_tables_with_datatables as $table_id => $table_store ) {
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.
386 +
240 387 foreach ( $table_store['instances'] as $html_id => $js_options ) {
241 388 $parameters = array();
242 389
243 390 // Settle dependencies/conflicts between certain features.
244 - if ( false !== $js_options['datatables_scrolly'] ) { // not necessarily a boolean!
391 + if ( false !== $js_options['datatables_scrolly'] ) { // datatables_scrolly can be a string, so that the explicit `false` check is needed.
245 392 // Vertical scrolling and pagination don't work together.
246 393 $js_options['datatables_paginate'] = false;
247 394 }
248 395 // Sanitize, as it may come from a Shortcode attribute.
@@ -247,11 +394,14 @@
247 394 }
248 395 // Sanitize, as it may come from a Shortcode attribute.
249 396 $js_options['datatables_paginate_entries'] = (int) $js_options['datatables_paginate_entries'];
250 397
251 - // DataTables language/translation handling.
398 + /*
399 + * DataTables language/translation handling.
400 + */
401 +
252 402 /**
253 - * Filter the locale/language for the DataTables JavaScript library.
403 + * Filters the locale/language for the DataTables JavaScript library.
254 404 *
255 405 * @since 1.0.0
256 406 *
257 407 * @param string $locale The DataTables JS library locale.
@@ -257,125 +407,197 @@
257 407 * @param string $locale The DataTables JS library locale.
258 408 * @param string $table_id The current table ID.
259 409 */
260 410 $datatables_locale = apply_filters( 'tablepress_datatables_locale', $js_options['datatables_locale'], $table_id );
261 - // Only do the expensive language file checks if they haven't been done yet.
262 - if ( ! isset( $datatables_languages[ $datatables_locale ] ) ) {
263 - $orig_language_file = TABLEPRESS_ABSPATH . "i18n/datatables/lang-{$datatables_locale}.json";
411 +
412 + // Only load each locale's language file once.
413 + if ( ! isset( $datatables_language[ $datatables_locale ] ) ) {
414 + $orig_language_file = TABLEPRESS_ABSPATH . "i18n/datatables/lang-{$datatables_locale}.php";
415 +
264 416 /**
265 - * Filter the language file for the DataTables JavaScript library.
417 + * Filters the language file path for the DataTables JavaScript library.
266 418 *
419 + * PHP files that return an array and JSON files are supported.
420 + * The JSON file method is deprecated and should no longer be used.
421 + *
267 422 * @since 1.0.0
268 423 *
269 - * @param string $orig_language_file Language file for the DataTables JS library.
424 + * @param string $orig_language_file Language file path for the DataTables JS library.
270 425 * @param string $datatables_locale Current locale/language for the DataTables JS library.
271 - * @param string $path Path of the language file.
426 + * @param string $tablepress_abspath Base path of the TablePress plugin.
272 427 */
273 - $language_file = apply_filters( 'tablepress_datatables_language_file', $orig_language_file, $datatables_locale, TABLEPRESS_ABSPATH ); // Make sure to check file_exists( $new_file ) when using this filter!
274 - // Load translation if it's not "en_US" (included as the default in DataTables) and the language file exists, or if the filter was used to change the language file.
275 - if ( ( 'en_US' !== $datatables_locale && file_exists( $language_file ) )
276 - || ( $orig_language_file !== $language_file ) ) {
277 - $datatables_languages[ $datatables_locale ] = $language_file;
428 + $language_file = apply_filters( 'tablepress_datatables_language_file', $orig_language_file, $datatables_locale, TABLEPRESS_ABSPATH );
429 +
430 + /*
431 + * Load translation file if it's not "en_US" (included as the default in DataTables)
432 + * or if the filter was used to change the language file, and the language file exists.
433 + * Otherwise, use an empty en_US placeholder, so that the strings are filterable later.
434 + */
435 + if ( ( 'en_US' !== $datatables_locale || $orig_language_file !== $language_file ) && file_exists( $language_file ) ) {
436 + if ( str_ends_with( $language_file, '.php' ) ) {
437 + $datatables_strings = require $language_file;
438 + if ( ! is_array( $datatables_strings ) ) {
439 + $datatables_strings = array();
440 + }
441 + } elseif ( str_ends_with( $language_file, '.json' ) ) {
442 + $datatables_strings = file_get_contents( $language_file );
443 + $datatables_strings = json_decode( $datatables_strings, true ); // @phpstan-ignore argument.type
444 + // Check if JSON could be decoded.
445 + if ( is_null( $datatables_strings ) ) {
446 + $datatables_strings = array();
447 + }
448 + $datatables_strings = (array) $datatables_strings;
449 + } else {
450 + // The filtered language file exists, but is not a .php or .json file, so don't use it.
451 + $datatables_strings = array();
452 + }
453 + } else {
454 + // If no translation file for the defined locale exists or is needed, use "en_US", as that's built-in.
455 + $datatables_locale = 'en_US';
456 + $datatables_strings = array();
278 457 }
458 +
459 + /**
460 + * Filters the language strings for the DataTables JavaScript library's features.
461 + *
462 + * @since 2.0.0
463 + *
464 + * @param array<string, mixed> $datatables_strings The language strings for DataTables.
465 + * @param string $datatables_locale Current locale/language for the DataTables JS library.
466 + */
467 + $datatables_language[ $datatables_locale ] = apply_filters( 'tablepress_datatables_language_strings', $datatables_strings, $datatables_locale );
279 468 }
280 - // If translation is registered to have its strings added to the JS, add corresponding parameter to DataTables call.
281 - if ( isset( $datatables_languages[ $datatables_locale ] ) ) {
282 - $parameters['language'] = '"language":DataTables_language["' . $datatables_locale . '"]';
283 - }
469 + $parameters['language'] = "language:DT_language['{$datatables_locale}']";
470 +
284 471 // These parameters need to be added for performance gain or to overwrite unwanted default behavior.
285 472 if ( $js_options['datatables_sort'] ) {
286 473 // No initial sort.
287 - $parameters['order'] = '"order":[]';
474 + $parameters['order'] = 'order:[]';
288 475 // Don't add additional classes, to speed up sorting.
289 - $parameters['orderClasses'] = '"orderClasses":false';
476 + $parameters['orderClasses'] = 'orderClasses:false';
290 477 }
291 - // Alternating row colors is default, so remove them if not wanted with [].
292 - $parameters['stripeClasses'] = '"stripeClasses":' . ( ( $js_options['alternating_row_colors'] ) ? '["even","odd"]' : '[]' );
478 +
293 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.
294 480 if ( ! $js_options['datatables_sort'] ) {
295 - $parameters['ordering'] = '"ordering":false';
481 + $parameters['ordering'] = 'ordering:false';
296 482 }
297 483 if ( $js_options['datatables_paginate'] ) {
298 - $parameters['pagingType'] = '"pagingType":"simple"';
484 + $parameters['pagingType'] = "pagingType:'simple_numbers'";
299 485 if ( $js_options['datatables_lengthchange'] ) {
300 486 $length_menu = array( 10, 25, 50, 100 );
301 487 if ( ! in_array( $js_options['datatables_paginate_entries'], $length_menu, true ) ) {
302 488 $length_menu[] = $js_options['datatables_paginate_entries'];
303 489 sort( $length_menu, SORT_NUMERIC );
304 - $parameters['lengthMenu'] = '"lengthMenu":[' . implode( ',', $length_menu ) . ']';
490 + $parameters['lengthMenu'] = 'lengthMenu:[' . implode( ',', $length_menu ) . ']';
305 491 }
306 492 } else {
307 - $parameters['lengthChange'] = '"lengthChange":false';
493 + $parameters['lengthChange'] = 'lengthChange:false';
308 494 }
309 495 if ( 10 !== $js_options['datatables_paginate_entries'] ) {
310 - $parameters['pageLength'] = '"pageLength":' . $js_options['datatables_paginate_entries'];
496 + $parameters['pageLength'] = "pageLength:{$js_options['datatables_paginate_entries']}";
311 497 }
312 498 } else {
313 - $parameters['paging'] = '"paging":false';
499 + $parameters['paging'] = 'paging:false';
314 500 }
315 501 if ( ! $js_options['datatables_filter'] ) {
316 - $parameters['searching'] = '"searching":false';
502 + $parameters['searching'] = 'searching:false';
317 503 }
318 504 if ( ! $js_options['datatables_info'] ) {
319 - $parameters['info'] = '"info":false';
505 + $parameters['info'] = 'info:false';
320 506 }
321 507 if ( $js_options['datatables_scrollx'] ) {
322 - $parameters['scrollX'] = '"scrollX":true';
508 + $parameters['scrollX'] = 'scrollX:true';
323 509 }
324 510 if ( false !== $js_options['datatables_scrolly'] ) {
325 - $parameters['scrollY'] = '"scrollY":"' . preg_replace( '#[^0-9a-z.%]#', '', $js_options['datatables_scrolly'] ) . '"';
326 - $parameters['scrollCollapse'] = '"scrollCollapse":true';
511 + $parameters['scrollY'] = 'scrollY:"' . preg_replace( '#[^0-9a-z.%]#', '', $js_options['datatables_scrolly'] ) . '"';
512 + $parameters['scrollCollapse'] = 'scrollCollapse:true';
327 513 }
328 - if ( ! empty( $js_options['datatables_custom_commands'] ) ) {
329 - $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.
330 517 }
331 518
332 519 /**
333 - * Filter the parameters that are passed to the DataTables JavaScript library.
520 + * Filters the parameters that are passed to the DataTables JavaScript library.
334 521 *
335 522 * @since 1.0.0
336 523 *
337 - * @param array $parameters The parameters for the DataTables JS library.
338 - * @param string $table_id The current table ID.
339 - * @param string $html_id The ID of the table HTML element.
340 - * @param array $js_options The options for the JS library.
524 + * @param array<string, mixed> $parameters The parameters for the DataTables JS library.
525 + * @param string $table_id The current table ID.
526 + * @param string $html_id The ID of the table HTML element.
527 + * @param array<string, mixed> $js_options The options for the JS library.
341 528 */
342 529 $parameters = apply_filters( 'tablepress_datatables_parameters', $parameters, $table_id, $html_id, $js_options );
343 530
344 - // If an existing parameter (in the from `"parameter":`) is set in the "Custom Commands", remove its default value.
345 - if ( isset( $parameters['custom_commands'] ) ) {
346 - foreach ( array_keys( $parameters ) as $maybe_overwritten_parameter ) {
347 - if ( false !== strpos( $parameters['custom_commands'], "\"{$maybe_overwritten_parameter}\":" ) ) {
348 - unset( $parameters[ $maybe_overwritten_parameter ] );
349 - }
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 ] );
350 536 }
351 537 }
352 538
539 + $name = substr( $html_id, 11 ); // Remove "tablepress-" from the HTML ID.
540 + $name = "DT_TP['" . str_replace( '-', '_', $name ) . "']";
353 541 $parameters = implode( ',', $parameters );
354 542 $parameters = ( ! empty( $parameters ) ) ? '{' . $parameters . '}' : '';
355 543
356 - $command = "$('#{$html_id}').dataTable({$parameters});";
544 + $command = "{$name} = new DataTable('#{$html_id}',{$parameters});";
357 545 /**
358 - * Filter the JavaScript command that invokes the DataTables JavaScript library on one table.
546 + * Filters the JavaScript command that invokes the DataTables JavaScript library on one table.
359 547 *
360 548 * @since 1.0.0
361 549 *
362 - * @param string $command The JS command for the DataTables JS library.
363 - * @param string $html_id The ID of the table HTML element.
364 - * @param array $parameters The parameters for the DataTables JS library.
365 - * @param string $table_id The current table ID.
366 - * @param array $js_options The options for the JS library.
550 + * @param string $command The JS command for the DataTables JS library.
551 + * @param string $html_id The ID of the table HTML element.
552 + * @param string $parameters The parameters for the DataTables JS library.
553 + * @param string $table_id The current table ID.
554 + * @param array<string, mixed> $js_options The options for the JS library.
555 + * @param string $name The name of the DataTable instance.
367 556 */
368 - $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 );
369 558 if ( ! empty( $command ) ) {
370 559 $commands[] = $command;
371 560 }
372 - }
561 + } // foreach table instance
562 + } // foreach table ID
563 +
564 + // DataTables language/translation handling.
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";
581 + } else {
582 + $datatables_datetime_command = '';
373 583 }
374 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 +
375 597 $commands = implode( "\n", $commands );
376 598 /**
377 - * Filter the JavaScript commands that invoke the DataTables JavaScript library on all tables on the page.
599 + * Filters the JavaScript commands that invoke the DataTables JavaScript library on all tables on the page.
378 600 *
379 601 * @since 1.0.0
380 602 *
381 603 * @param string $commands The JS commands for the DataTables JS library.
@@ -380,84 +602,73 @@
380 602 *
381 603 * @param string $commands The JS commands for the DataTables JS library.
382 604 */
383 605 $commands = apply_filters( 'tablepress_all_datatables_commands', $commands );
384 - if ( empty( $commands ) ) {
606 + if ( '' === $commands ) {
385 607 return;
386 608 }
387 609
388 - // DataTables language/translation handling.
389 - $datatables_strings = '';
390 - foreach ( $datatables_languages as $locale => $language_file ) {
391 - $strings = file_get_contents( $language_file );
392 - // Remove unnecessary white space.
393 - $strings = str_replace( array( "\n", "\r", "\t" ), '', $strings );
394 - $datatables_strings .= "DataTables_language[\"{$locale}\"]={$strings};\n";
395 - }
396 - if ( ! empty( $datatables_strings ) ) {
397 - $datatables_strings = "var DataTables_language={};\n" . $datatables_strings;
398 - }
399 -
400 - $script_type_attr = current_theme_supports( 'html5', 'script' ) ? '' : ' type="text/javascript"';
401 -
402 - $js_wrapper = <<<'JS'
403 -<script%3$s>
404 -jQuery(function($){
405 -%1$s%2$s
406 -});
407 -</script>
408 -JS;
610 + $script_template = <<<'JS'
611 + var DT_TP = {};
612 + jQuery(($)=>{
613 + %1$s%2$s
614 + });
615 + JS;
409 616 /**
410 - * Filter the script/jQuery wrapper code for the DataTables commands calls.
617 + * Filters the script/jQuery wrapper code for the DataTables commands calls.
411 618 *
412 619 * @since 1.14.0
413 620 *
414 - * @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.
415 622 */
416 - $js_wrapper = apply_filters( 'tablepress_all_datatables_commands_wrapper', $js_wrapper );
417 - printf( $js_wrapper, $datatables_strings, $commands, $script_type_attr );
623 + $script_template = apply_filters( 'tablepress_all_datatables_commands_wrapper', $script_template );
624 +
625 + $script = sprintf( $script_template, $datatables_pre_commands, $commands );
626 + wp_add_inline_script( 'tablepress-datatables', $script );
627 +
628 + // Prevent repeated execution (which would lead to DataTables error messages) via a static variable.
629 + $datatables_calls_printed = true;
418 630 }
419 631
420 632 /**
421 - * Handle Shortcode [table id=<ID> /] in `the_content()`.
633 + * Handles the Shortcode [table id=<ID> /].
422 634 *
423 635 * @since 1.0.0
424 636 *
425 - * @param array $shortcode_atts List of attributes that where included in the Shortcode.
637 + * @param array<string, mixed> $shortcode_atts List of attributes that where included in the Shortcode.
426 638 * @return string Resulting HTML code for the table with the ID <ID>.
427 639 */
428 - public function shortcode_table( $shortcode_atts ) {
429 - // For empty Shortcodes like [table] or [table /], an empty string is passed, see WP Core #26927.
430 - $shortcode_atts = (array) $shortcode_atts;
640 + public function shortcode_table( array $shortcode_atts ): string {
641 + $this->maybe_enqueue_css();
431 642
432 643 $_render = TablePress::load_class( 'TablePress_Render', 'class-render.php', 'classes' );
433 644
434 645 $default_shortcode_atts = $_render->get_default_render_options();
435 646 /**
436 - * Filter the available/default attributes for the [table] Shortcode.
647 + * Filters the available/default attributes for the [table] Shortcode.
437 648 *
438 649 * @since 1.0.0
439 650 *
440 - * @param array $default_shortcode_atts The [table] Shortcode default attributes.
651 + * @param array<string, mixed> $default_shortcode_atts The [table] Shortcode default attributes.
441 652 */
442 653 $default_shortcode_atts = apply_filters( 'tablepress_shortcode_table_default_shortcode_atts', $default_shortcode_atts );
443 654 // Parse Shortcode attributes, only allow those that are specified.
444 655 $shortcode_atts = shortcode_atts( $default_shortcode_atts, $shortcode_atts ); // Optional third argument left out on purpose. Use filter in the next line instead.
445 656 /**
446 - * Filter the attributes that were passed to the [table] Shortcode.
657 + * Filters the attributes that were passed to the [table] Shortcode.
447 658 *
448 659 * @since 1.0.0
449 660 *
450 - * @param array $shortcode_atts The attributes passed to the [table] Shortcode.
661 + * @param array<string, mixed> $shortcode_atts The attributes passed to the [table] Shortcode.
451 662 */
452 663 $shortcode_atts = apply_filters( 'tablepress_shortcode_table_shortcode_atts', $shortcode_atts );
453 664
454 665 // Check, if a table with the given ID exists.
455 - $table_id = preg_replace( '/[^a-zA-Z0-9_-]/', '', $shortcode_atts['id'] );
666 + $table_id = (string) preg_replace( '/[^a-zA-Z0-9_-]/', '', $shortcode_atts['id'] );
456 667 if ( ! TablePress::$model_table->table_exists( $table_id ) ) {
457 - $message = "[table &#8220;{$table_id}&#8221; not found /]<br />\n";
668 + $message = "&#91;table “{$table_id}” not found /&#93;<br />\n";
458 669 /**
459 - * Filter the "Table not found" message.
670 + * Filters the "Table not found" message.
460 671 *
461 672 * @since 1.0.0
462 673 *
463 674 * @param string $message The "Table not found" message.
@@ -469,11 +680,11 @@
469 680
470 681 // Load table, with table data, options, and visibility settings.
471 682 $table = TablePress::$model_table->load( $table_id, true, true );
472 683 if ( is_wp_error( $table ) ) {
473 - $message = "[table &#8220;{$table_id}&#8221; could not be loaded /]<br />\n";
684 + $message = "&#91;table “{$table_id}” could not be loaded /&#93;<br />\n";
474 685 /**
475 - * Filter the "Table could not be loaded" message.
686 + * Filters the "Table could not be loaded" message.
476 687 *
477 688 * @since 1.0.0
478 689 *
479 690 * @param string $message The "Table could not be loaded" message.
@@ -483,11 +694,11 @@
483 694 $message = apply_filters( 'tablepress_table_load_error_message', $message, $table_id, $table );
484 695 return $message;
485 696 }
486 697 if ( isset( $table['is_corrupted'] ) && $table['is_corrupted'] ) {
487 - $message = "<div>Attention: The internal data of table &#8220;{$table_id}&#8221; is corrupted!</div>";
698 + $message = "<div>Attention: The internal data of table “{$table_id}” is corrupted!</div>";
488 699 /**
489 - * Filter the "Table data is corrupted" message.
700 + * Filters the "Table data is corrupted" message.
490 701 *
491 702 * @since 1.0.0
492 703 *
493 704 * @param string $message The "Table data is corrupted" message.
@@ -497,36 +708,54 @@
497 708 $message = apply_filters( 'tablepress_table_corrupted_message', $message, $table_id, $table['json_error'] );
498 709 return $message;
499 710 }
500 711
501 - /**
502 - * Filter whether the "datatables_custom_commands" Shortcode parameter is disabled.
503 - *
504 - * By default, the "datatables_custom_commands" Shortcode parameter is disabled for security reasons.
505 - *
506 - * @since 1.0.0
507 - *
508 - * @param bool $disable Whether to disable the "datatables_custom_commands" Shortcode parameter. Default true.
509 - */
510 - if ( ! is_null( $shortcode_atts['datatables_custom_commands'] ) && apply_filters( 'tablepress_disable_custom_commands_shortcode_parameter', true ) ) {
511 - $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 + }
512 730 }
513 731
514 732 // Determine options to use (if set in Shortcode, use those, otherwise use stored options, from the "Edit" screen).
515 733 $render_options = array();
516 734 foreach ( $shortcode_atts as $key => $value ) {
517 - // We have to check this, because strings 'true' or 'false' are not recognized as boolean!
518 - if ( is_string( $value ) && 'true' === strtolower( $value ) ) {
519 - $render_options[ $key ] = true;
520 - } elseif ( is_string( $value ) && 'false' === strtolower( $value ) ) {
521 - $render_options[ $key ] = false;
522 - } elseif ( is_null( $value ) && isset( $table['options'][ $key ] ) ) {
735 + if ( is_null( $value ) && isset( $table['options'][ $key ] ) ) {
736 + // Use the table's stored option value, if the Shortcode parameter was not set.
523 737 $render_options[ $key ] = $table['options'][ $key ];
738 + } elseif ( is_string( $value ) ) {
739 + // Convert strings 'true' or 'false' to boolean, keep others.
740 + $value_lowercase = strtolower( $value );
741 + if ( 'true' === $value_lowercase ) {
742 + $render_options[ $key ] = true;
743 + } elseif ( 'false' === $value_lowercase ) {
744 + $render_options[ $key ] = false;
745 + } else {
746 + $render_options[ $key ] = $value;
747 + }
524 748 } else {
749 + // Keep all other values.
525 750 $render_options[ $key ] = $value;
526 751 }
527 752 }
528 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 +
529 758 // Generate unique HTML ID, depending on how often this table has already been shown on this page.
530 759 if ( ! isset( $this->shown_tables[ $table_id ] ) ) {
531 760 $this->shown_tables[ $table_id ] = array(
532 761 'count' => 0,
@@ -532,9 +761,9 @@
532 761 'count' => 0,
533 762 'instances' => array(),
534 763 );
535 764 }
536 - $this->shown_tables[ $table_id ]['count']++;
765 + ++$this->shown_tables[ $table_id ]['count'];
537 766 $count = $this->shown_tables[ $table_id ]['count'];
538 767 $render_options['html_id'] = "tablepress-{$table_id}";
539 768 if ( $count > 1 ) {
540 769 $render_options['html_id'] .= "-no-{$count}";
@@ -539,15 +768,15 @@
539 768 if ( $count > 1 ) {
540 769 $render_options['html_id'] .= "-no-{$count}";
541 770 }
542 771 /**
543 - * Filter the ID of the table HTML element.
772 + * Filters the ID of the table HTML element.
544 773 *
545 774 * @since 1.0.0
546 775 *
547 776 * @param string $html_id The ID of the table HTML element.
548 777 * @param string $table_id The current table ID.
549 - * @param string $count Number of copies of the table with this table ID on the page.
778 + * @param int $count Number of copies of the table with this table ID on the page.
550 779 */
551 780 $render_options['html_id'] = apply_filters( 'tablepress_html_id', $render_options['html_id'], $table_id, $count );
552 781
553 782 // Generate the "Edit Table" link.
@@ -552,9 +781,9 @@
552 781
553 782 // Generate the "Edit Table" link.
554 783 $render_options['edit_table_url'] = '';
555 784 /**
556 - * Filter whether the "Edit" link below the table shall be shown.
785 + * Filters whether the "Edit" link below the table shall be shown.
557 786 *
558 787 * The "Edit" link is only shown to logged-in users who possess the necessary capability to edit the table.
559 788 *
560 789 * @since 1.0.0
@@ -561,71 +790,38 @@
561 790 *
562 791 * @param bool $show Whether to show the "Edit" link below the table. Default true.
563 792 * @param string $table_id The current table ID.
564 793 */
565 - 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'] ) ) {
566 795 $render_options['edit_table_url'] = TablePress::url( array( 'action' => 'edit', 'table_id' => $table['id'] ) );
567 796 }
568 797
569 798 /**
570 - * Filter the render options for the table.
799 + * Filters the render options for the table.
571 800 *
572 801 * The render options are determined from the settings on a table's "Edit" screen and the Shortcode parameters.
573 802 *
574 803 * @since 1.0.0
575 804 *
576 - * @param array $render_options The render options for the table.
577 - * @param array $table The current table.
805 + * @param array<string, mixed> $render_options The render options for the table.
806 + * @param array<string, mixed> $table The current table.
578 807 */
579 808 $render_options = apply_filters( 'tablepress_table_render_options', $render_options, $table );
580 809
581 - // Eventually add this table to list of tables which have a JS library enabled and thus are to be included in the script's call in the footer.
582 - if ( $render_options['use_datatables'] && $render_options['table_head'] && count( $table['data'] ) > 1 ) {
583 - // Get options for the DataTables JavaScript library from the table's render options.
584 - $js_options = array();
585 - foreach ( array(
586 - 'alternating_row_colors',
587 - 'datatables_sort',
588 - 'datatables_paginate',
589 - 'datatables_paginate',
590 - 'datatables_paginate_entries',
591 - 'datatables_lengthchange',
592 - 'datatables_filter',
593 - 'datatables_info',
594 - 'datatables_scrollx',
595 - 'datatables_scrolly',
596 - 'datatables_locale',
597 - 'datatables_custom_commands',
598 - ) as $option ) {
599 - $js_options[ $option ] = $render_options[ $option ];
600 - }
601 - /**
602 - * Filter the JavaScript options for the table.
603 - *
604 - * The JavaScript options are determined from the settings on a table's "Edit" screen and the Shortcode parameters.
605 - * They are part of the render options and can be overwritten with Shortcode parameters.
606 - *
607 - * @since 1.0.0
608 - *
609 - * @param array $js_options The JavaScript options for the table.
610 - * @param string $table_id The current table ID.
611 - * @param array $render_options The render options for the table.
612 - */
613 - $js_options = apply_filters( 'tablepress_table_js_options', $js_options, $table_id, $render_options );
614 - $this->shown_tables[ $table_id ]['instances'][ $render_options['html_id'] ] = $js_options;
615 - $this->_enqueue_datatables();
616 - }
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'] );
617 813
618 814 // Check if table output shall and can be loaded from the transient cache, otherwise generate the output.
619 815 if ( $render_options['cache_table_output'] && ! is_user_logged_in() ) {
620 816 // Hash the Render Options array to get a unique cache identifier.
621 - $table_hash = md5( wp_json_encode( $render_options, TABLEPRESS_JSON_OPTIONS ) );
817 + $table_hash = md5( wp_json_encode( $render_options, TABLEPRESS_JSON_OPTIONS ) ); // @phpstan-ignore argument.type
622 818 $transient_name = 'tablepress_' . $table_hash; // Attention: This string must not be longer than 45 characters!
623 819 $output = get_transient( $transient_name );
624 820 if ( false === $output || '' === $output ) {
625 821 // Render/generate the table HTML, as it was not found in the cache.
626 822 $_render->set_input( $table, $render_options );
627 - $output = $_render->get_output();
823 + $output = $_render->get_output( 'html' );
628 824 // Save render output in a transient, set cache timeout to 24 hours.
629 825 set_transient( $transient_name, $output, DAY_IN_SECONDS );
630 826 // Update output caches list transient (necessary for cache invalidation upon table saving).
631 827 $caches_list_transient_name = 'tablepress_c_' . md5( $table_id );
@@ -640,9 +836,9 @@
640 836 }
641 837 set_transient( $caches_list_transient_name, wp_json_encode( $caches_list, TABLEPRESS_JSON_OPTIONS ), 2 * DAY_IN_SECONDS );
642 838 } else {
643 839 /**
644 - * Filter the cache hit comment message.
840 + * Filters the cache hit comment message.
645 841 *
646 842 * @since 1.0.0
647 843 *
648 844 * @param string $comment The cache hit comment message.
@@ -651,14 +847,65 @@
651 847 }
652 848 } else {
653 849 // Render/generate the table HTML, as no cache is to be used.
654 850 $_render->set_input( $table, $render_options );
655 - $output = $_render->get_output();
851 + $output = $_render->get_output( 'html' );
656 852 }
657 853
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.
855 + if ( $render_options['use_datatables']
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 + ) {
859 + // Get options for the DataTables JavaScript library from the table's render options.
860 + $js_options = array();
861 + foreach ( array(
862 + 'alternating_row_colors',
863 + 'datatables_sort',
864 + 'datatables_paginate',
865 + 'datatables_paginate',
866 + 'datatables_paginate_entries',
867 + 'datatables_lengthchange',
868 + 'datatables_filter',
869 + 'datatables_info',
870 + 'datatables_scrollx',
871 + 'datatables_scrolly',
872 + 'datatables_locale',
873 + 'datatables_custom_commands',
874 + ) as $option ) {
875 + $js_options[ $option ] = $render_options[ $option ];
876 + }
877 + /**
878 + * Filters the JavaScript options for the table.
879 + *
880 + * The JavaScript options are determined from the settings on a table's "Edit" screen and the Shortcode parameters.
881 + * They are part of the render options and can be overwritten with Shortcode parameters.
882 + *
883 + * @since 1.0.0
884 + *
885 + * @param array<string, mixed> $js_options The JavaScript options for the table.
886 + * @param string $table_id The current table ID.
887 + * @param array<string, mixed> $render_options The render options for the table.
888 + */
889 + $js_options = apply_filters( 'tablepress_table_js_options', $js_options, $table_id, $render_options );
890 +
891 + $this->shown_tables[ $table_id ]['instances'][ (string) $render_options['html_id'] ] = $js_options;
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 + }
903 + }
904 +
658 905 // Maybe print a list of used render options.
659 906 if ( $render_options['shortcode_debug'] && is_user_logged_in() ) {
660 - $output .= '<pre>' . var_export( $render_options, true ) . '</pre>';
907 + $output .= '<pre>' . esc_html( wp_json_encode( $render_options, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ) ) . '</pre>'; // @phpstan-ignore argument.type
661 908 }
662 909
663 910 return $output;
664 911 }
@@ -663,19 +910,16 @@
663 910 return $output;
664 911 }
665 912
666 913 /**
667 - * Handle Shortcode [table-info id=<ID> field=<name> /] in the_content().
914 + * Handles the Shortcode [table-info id=<ID> field=<name> /].
668 915 *
669 916 * @since 1.0.0
670 917 *
671 - * @param array $shortcode_atts List of attributes that where included in the Shortcode.
918 + * @param array<string, mixed> $shortcode_atts List of attributes that where included in the Shortcode.
672 919 * @return string Text that replaces the Shortcode (error message or asked-for information).
673 920 */
674 - public function shortcode_table_info( $shortcode_atts ) {
675 - // For empty Shortcodes like [table-info] or [table-info /], an empty string is passed, see Core #26927.
676 - $shortcode_atts = (array) $shortcode_atts;
677 -
921 + public function shortcode_table_info( array $shortcode_atts ): string {
678 922 // Parse Shortcode attributes, only allow those that are specified.
679 923 $default_shortcode_atts = array(
680 924 'id' => '',
681 925 'field' => '',
@@ -681,35 +925,35 @@
681 925 'field' => '',
682 926 'format' => '',
683 927 );
684 928 /**
685 - * Filter the available/default attributes for the [table-info] Shortcode.
929 + * Filters the available/default attributes for the [table-info] Shortcode.
686 930 *
687 931 * @since 1.0.0
688 932 *
689 - * @param array $default_shortcode_atts The [table-info] Shortcode default attributes.
933 + * @param array<string, mixed> $default_shortcode_atts The [table-info] Shortcode default attributes.
690 934 */
691 935 $default_shortcode_atts = apply_filters( 'tablepress_shortcode_table_info_default_shortcode_atts', $default_shortcode_atts );
692 936 $shortcode_atts = shortcode_atts( $default_shortcode_atts, $shortcode_atts ); // Optional third argument left out on purpose. Use filter in the next line instead.
693 937 /**
694 - * Filter the attributes that were passed to the [table-info] Shortcode.
938 + * Filters the attributes that were passed to the [table-info] Shortcode.
695 939 *
696 940 * @since 1.0.0
697 941 *
698 - * @param array $shortcode_atts The attributes passed to the [table-info] Shortcode.
942 + * @param array<string, mixed> $shortcode_atts The attributes passed to the [table-info] Shortcode.
699 943 */
700 944 $shortcode_atts = apply_filters( 'tablepress_shortcode_table_info_shortcode_atts', $shortcode_atts );
701 945
702 946 /**
703 - * Filter whether the output of the [table-info] Shortcode is overwritten/short-circuited.
947 + * Filters whether the output of the [table-info] Shortcode is overwritten/short-circuited.
704 948 *
705 949 * @since 1.0.0
706 950 *
707 - * @param bool|string $overwrite Whether the [table-info] output is overwritten. Return false for the regular content, and a string to overwrite the output.
708 - * @param array $shortcode_atts The attributes passed to the [table-info] Shortcode.
951 + * @param false|string $overwrite Whether the [table-info] output is overwritten. Return false for the regular content, and a string to overwrite the output.
952 + * @param array<string, mixed> $shortcode_atts The attributes passed to the [table-info] Shortcode.
709 953 */
710 954 $overwrite = apply_filters( 'tablepress_shortcode_table_info_overwrite', false, $shortcode_atts );
711 - if ( $overwrite ) {
955 + if ( is_string( $overwrite ) ) {
712 956 return $overwrite;
713 957 }
714 958
715 959 // Check, if a table with the given ID exists.
@@ -714,9 +958,9 @@
714 958
715 959 // Check, if a table with the given ID exists.
716 960 $table_id = preg_replace( '/[^a-zA-Z0-9_-]/', '', $shortcode_atts['id'] );
717 961 if ( ! TablePress::$model_table->table_exists( $table_id ) ) {
718 - $message = "[table &#8220;{$table_id}&#8221; not found /]<br />\n";
962 + $message = "&#91;table “{$table_id}” not found /&#93;<br />\n";
719 963 /** This filter is documented in controllers/controller-frontend.php */
720 964 $message = apply_filters( 'tablepress_table_not_found_message', $message, $table_id );
721 965 return $message;
722 966 }
@@ -723,16 +967,16 @@
723 967
724 968 // Load table, with table data, options, and visibility settings.
725 969 $table = TablePress::$model_table->load( $table_id, true, true );
726 970 if ( is_wp_error( $table ) ) {
727 - $message = "[table &#8220;{$table_id}&#8221; could not be loaded /]<br />\n";
971 + $message = "&#91;table “{$table_id}” could not be loaded /&#93;<br />\n";
728 972 /** This filter is documented in controllers/controller-frontend.php */
729 973 $message = apply_filters( 'tablepress_table_load_error_message', $message, $table_id, $table );
730 974 return $message;
731 975 }
732 976
733 - $field = preg_replace( '/[^a-z_]/', '', strtolower( $shortcode_atts['field'] ) );
734 - $format = preg_replace( '/[^a-z]/', '', strtolower( $shortcode_atts['format'] ) );
977 + $field = (string) preg_replace( '/[^a-z_]/', '', strtolower( $shortcode_atts['field'] ) );
978 + $format = (string) preg_replace( '/[^a-z]/', '', strtolower( $shortcode_atts['format'] ) );
735 979
736 980 // Generate output, depending on what information (field) was asked for.
737 981 switch ( $field ) {
738 982 case 'name':
@@ -737,8 +981,15 @@
737 981 switch ( $field ) {
738 982 case 'name':
739 983 case 'description':
740 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 + }
741 992 break;
742 993 case 'last_modified':
743 994 switch ( $format ) {
744 995 case 'raw':
@@ -746,9 +997,13 @@
746 997 $output = $table['last_modified'];
747 998 break;
748 999 case 'human':
749 1000 $modified_timestamp = date_create( $table['last_modified'], wp_timezone() );
750 - $modified_timestamp = $modified_timestamp->getTimestamp();
1001 + if ( false === $modified_timestamp ) {
1002 + $modified_timestamp = $table['last_modified'];
1003 + } else {
1004 + $modified_timestamp = $modified_timestamp->getTimestamp();
1005 + }
751 1006 $current_timestamp = time();
752 1007 $time_diff = $current_timestamp - $modified_timestamp;
753 1008 // Time difference is only shown up to one week.
754 1009 if ( $time_diff >= 0 && $time_diff < WEEK_IN_SECONDS ) {
@@ -776,14 +1031,10 @@
776 1031 break;
777 1032 case 'number_rows':
778 1033 $output = count( $table['data'] );
779 1034 if ( 'raw' !== $format ) {
780 - if ( $table['options']['table_head'] ) {
781 - $output = $output - 1;
782 - }
783 - if ( $table['options']['table_foot'] ) {
784 - $output = $output - 1;
785 - }
1035 + $output -= $table['options']['table_head'];
1036 + $output -= $table['options']['table_foot'];
786 1037 }
787 1038 break;
788 1039 case 'number_columns':
789 1040 $output = count( $table['data'][0] );
@@ -788,30 +1039,30 @@
788 1039 case 'number_columns':
789 1040 $output = count( $table['data'][0] );
790 1041 break;
791 1042 default:
792 - $output = "[table-info field &#8220;{$field}&#8221; not found in table &#8220;{$table_id}&#8221; /]<br />\n";
1043 + $output = "&#91;table-info field “{$field}” not found in table “{$table_id}” /&#93;<br />\n";
793 1044 /**
794 - * Filter the "table info field not found" message.
1045 + * Filters the "table info field not found" message.
795 1046 *
796 1047 * @since 1.0.0
797 1048 *
798 - * @param string $output The "table info field not found" message.
799 - * @param array $table The current table ID.
800 - * @param string $field The field that was not found.
801 - * @param string $format The return format for the field.
1049 + * @param string $output The "table info field not found" message.
1050 + * @param array<string, mixed> $table The current table.
1051 + * @param string $field The field that was not found.
1052 + * @param string $format The return format for the field.
802 1053 */
803 1054 $output = apply_filters( 'tablepress_table_info_not_found_message', $output, $table, $field, $format );
804 1055 }
805 1056
806 1057 /**
807 - * Filter the output of the [table-info] Shortcode.
1058 + * Filters the output of the [table-info] Shortcode.
808 1059 *
809 1060 * @since 1.0.0
810 1061 *
811 - * @param string $output The output of the [table-info] Shortcode.
812 - * @param array $table The current table.
813 - * @param array $shortcode_atts The attributes passed to the [table-info] Shortcode.
1062 + * @param string $output The output of the [table-info] Shortcode.
1063 + * @param array<string, mixed> $table The current table.
1064 + * @param array<string, mixed> $shortcode_atts The attributes passed to the [table-info] Shortcode.
814 1065 */
815 1066 $output = apply_filters( 'tablepress_shortcode_table_info_output', $output, $table, $shortcode_atts );
816 1067 return $output;
817 1068 }
@@ -816,9 +1067,9 @@
816 1067 return $output;
817 1068 }
818 1069
819 1070 /**
820 - * 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.
821 1072 *
822 1073 * This is done by looping through all search terms and TablePress tables and searching there for the search term,
823 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
824 1075 * Shortcode for one of these tables in their content.
@@ -829,11 +1080,18 @@
829 1080 *
830 1081 * @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.
831 1082 * @return string Eventually extended SQL "WHERE" clause, to also find posts/pages with Shortcodes in them.
832 1083 */
833 - public function posts_search_filter( $search_sql ) {
1084 + public function posts_search_filter( /* string */ $search_sql ): string {
1085 + // 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.
1086 +
834 1087 global $wpdb;
835 1088
1089 + // Protect against cases where `null` is somehow passed to the filter hook callback.
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.)
1091 + return '';
1092 + }
1093 +
836 1094 if ( ! is_search() || ! is_main_query() ) {
837 1095 return $search_sql;
838 1096 }
839 1097
@@ -847,20 +1105,27 @@
847 1105 $table_ids = TablePress::$model_table->load_all( true, false );
848 1106 // Array of all search words that were found, and the table IDs where they were found.
849 1107 $query_result = array();
850 1108
1109 + $fn_stripos = function_exists( 'mb_stripos' ) ? 'mb_stripos' : 'stripos';
1110 +
851 1111 foreach ( $table_ids as $table_id ) {
852 1112 // Load table, with table data, options, and visibility settings.
853 1113 $table = TablePress::$model_table->load( $table_id, true, true );
854 1114
1115 + // Skip tables that could not be loaded.
1116 + if ( is_wp_error( $table ) ) {
1117 + continue;
1118 + }
1119 +
1120 + // Do not search in corrupted tables.
855 1121 if ( isset( $table['is_corrupted'] ) && $table['is_corrupted'] ) {
856 - // Do not search in corrupted tables.
857 1122 continue;
858 1123 }
859 1124
860 1125 foreach ( $search_terms as $search_term ) {
861 - if ( ( $table['options']['print_name'] && false !== stripos( $table['name'], $search_term ) )
862 - || ( $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 ) ) ) {
863 1128 // Found the search term in the name or description (and they are shown).
864 1129 $query_result[ $search_term ][] = $table_id; // Add table ID to result list.
865 1130 // No need to continue searching this search term in this table.
866 1131 continue;
@@ -876,10 +1141,10 @@
876 1141 if ( 0 === $table['visibility']['columns'][ $col_idx ] ) {
877 1142 // Column is hidden, so don't search in it.
878 1143 continue;
879 1144 }
880 - // @TODO: Cells are not evaluated here, so math formulas are searched.
881 - if ( false !== stripos( $table_cell, $search_term ) ) {
1145 + // @todo Cells are not evaluated here, so math formulas are searched.
1146 + if ( false !== $fn_stripos( $table_cell, (string) $search_term ) ) {
882 1147 // Found the search term in the cell content.
883 1148 $query_result[ $search_term ][] = $table_id; // Add table ID to result list
884 1149 // No need to continue searching this search term in this table.
885 1150 continue 3;
@@ -896,9 +1161,9 @@
896 1161 $n = ( empty( $exact ) ) ? '%' : '';
897 1162 $search_sql = $wpdb->remove_placeholder_escape( $search_sql );
898 1163 foreach ( $query_result as $search_term => $table_ids ) {
899 1164 $search_term = esc_sql( $wpdb->esc_like( $search_term ) );
900 - $old_or = "OR ({$wpdb->posts}.post_content LIKE '{$n}{$search_term}{$n}')";
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.)
901 1166 $table_ids = implode( '|', $table_ids );
902 1167 $regexp = '\\\\[' . TablePress::$shortcode . ' id=(["\\\']?)(' . $table_ids . ')([\]"\\\' /])'; // ' needs to be single escaped, [ double escaped (with \\) in mySQL
903 1168 $new_or = $old_or . " OR ({$wpdb->posts}.post_content REGEXP '{$regexp}')";
904 1169 $search_sql = str_replace( $old_or, $new_or, $search_sql );
@@ -905,7 +1170,67 @@
905 1170 }
906 1171 $search_sql = $wpdb->add_placeholder_escape( $search_sql );
907 1172
908 1173 return $search_sql;
1174 + }
1175 +
1176 + /**
1177 + * Callback function for rendering the tablepress/table block.
1178 + *
1179 + * @since 2.0.0
1180 + *
1181 + * @param array<string, string> $block_attributes List of attributes that where included in the block settings.
1182 + * @return string Resulting HTML code for the table.
1183 + */
1184 + public function table_block_render_callback( array $block_attributes ): string {
1185 + // Don't return anything if no table was selected.
1186 + if ( '' === $block_attributes['id'] ) {
1187 + return '';
1188 + }
1189 +
1190 + $render_attributes = shortcode_parse_atts( $block_attributes['parameters'] );
1191 + $render_attributes['id'] = $block_attributes['id'];
1192 +
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' );
909 1234 }
910 1235
911 1236 } // class TablePress_Frontend_Controller