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 +649 -315 1.12 → 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 Relaoded 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,45 +311,97 @@
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.
249 - $js_options['datatables_paginate_entries'] = intval( $js_options['datatables_paginate_entries'] );
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,74 +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 - }
610 + $script_template = <<<'JS'
611 + var DT_TP = {};
612 + jQuery(($)=>{
613 + %1$s%2$s
614 + });
615 + JS;
616 + /**
617 + * Filters the script/jQuery wrapper code for the DataTables commands calls.
618 + *
619 + * @since 1.14.0
620 + *
621 + * @param string $script_template Default script/jQuery wrapper code for the DataTables commands calls.
622 + */
623 + $script_template = apply_filters( 'tablepress_all_datatables_commands_wrapper', $script_template );
399 624
400 - // Echo DataTables strings and JS calls.
401 - echo <<<JS
402 -<script type="text/javascript">
403 -jQuery(document).ready(function($){
404 -{$datatables_strings}{$commands}
405 -});
406 -</script>
407 -JS;
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;
408 630 }
409 631
410 632 /**
411 - * Handle Shortcode [table id=<ID> /] in `the_content()`.
633 + * Handles the Shortcode [table id=<ID> /].
412 634 *
413 635 * @since 1.0.0
414 636 *
415 - * @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.
416 638 * @return string Resulting HTML code for the table with the ID <ID>.
417 639 */
418 - public function shortcode_table( $shortcode_atts ) {
419 - // For empty Shortcodes like [table] or [table /], an empty string is passed, see WP Core #26927.
420 - $shortcode_atts = (array) $shortcode_atts;
640 + public function shortcode_table( array $shortcode_atts ): string {
641 + $this->maybe_enqueue_css();
421 642
422 643 $_render = TablePress::load_class( 'TablePress_Render', 'class-render.php', 'classes' );
423 644
424 645 $default_shortcode_atts = $_render->get_default_render_options();
425 646 /**
426 - * Filter the available/default attributes for the [table] Shortcode.
647 + * Filters the available/default attributes for the [table] Shortcode.
427 648 *
428 649 * @since 1.0.0
429 650 *
430 - * @param array $default_shortcode_atts The [table] Shortcode default attributes.
651 + * @param array<string, mixed> $default_shortcode_atts The [table] Shortcode default attributes.
431 652 */
432 653 $default_shortcode_atts = apply_filters( 'tablepress_shortcode_table_default_shortcode_atts', $default_shortcode_atts );
433 654 // Parse Shortcode attributes, only allow those that are specified.
434 655 $shortcode_atts = shortcode_atts( $default_shortcode_atts, $shortcode_atts ); // Optional third argument left out on purpose. Use filter in the next line instead.
435 656 /**
436 - * Filter the attributes that were passed to the [table] Shortcode.
657 + * Filters the attributes that were passed to the [table] Shortcode.
437 658 *
438 659 * @since 1.0.0
439 660 *
440 - * @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.
441 662 */
442 663 $shortcode_atts = apply_filters( 'tablepress_shortcode_table_shortcode_atts', $shortcode_atts );
443 664
444 665 // Check, if a table with the given ID exists.
445 - $table_id = preg_replace( '/[^a-zA-Z0-9_-]/', '', $shortcode_atts['id'] );
666 + $table_id = (string) preg_replace( '/[^a-zA-Z0-9_-]/', '', $shortcode_atts['id'] );
446 667 if ( ! TablePress::$model_table->table_exists( $table_id ) ) {
447 - $message = "[table &#8220;{$table_id}&#8221; not found /]<br />\n";
668 + $message = "&#91;table “{$table_id}” not found /&#93;<br />\n";
448 669 /**
449 - * Filter the "Table not found" message.
670 + * Filters the "Table not found" message.
450 671 *
451 672 * @since 1.0.0
452 673 *
453 674 * @param string $message The "Table not found" message.
@@ -459,11 +680,11 @@
459 680
460 681 // Load table, with table data, options, and visibility settings.
461 682 $table = TablePress::$model_table->load( $table_id, true, true );
462 683 if ( is_wp_error( $table ) ) {
463 - $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";
464 685 /**
465 - * Filter the "Table could not be loaded" message.
686 + * Filters the "Table could not be loaded" message.
466 687 *
467 688 * @since 1.0.0
468 689 *
469 690 * @param string $message The "Table could not be loaded" message.
@@ -473,11 +694,11 @@
473 694 $message = apply_filters( 'tablepress_table_load_error_message', $message, $table_id, $table );
474 695 return $message;
475 696 }
476 697 if ( isset( $table['is_corrupted'] ) && $table['is_corrupted'] ) {
477 - $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>";
478 699 /**
479 - * Filter the "Table data is corrupted" message.
700 + * Filters the "Table data is corrupted" message.
480 701 *
481 702 * @since 1.0.0
482 703 *
483 704 * @param string $message The "Table data is corrupted" message.
@@ -487,36 +708,54 @@
487 708 $message = apply_filters( 'tablepress_table_corrupted_message', $message, $table_id, $table['json_error'] );
488 709 return $message;
489 710 }
490 711
491 - /**
492 - * Filter whether the "datatables_custom_commands" Shortcode parameter is disabled.
493 - *
494 - * By default, the "datatables_custom_commands" Shortcode parameter is disabled for security reasons.
495 - *
496 - * @since 1.0.0
497 - *
498 - * @param bool $disable Whether to disable the "datatables_custom_commands" Shortcode parameter. Default true.
499 - */
500 - if ( ! is_null( $shortcode_atts['datatables_custom_commands'] ) && apply_filters( 'tablepress_disable_custom_commands_shortcode_parameter', true ) ) {
501 - $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 + }
502 730 }
503 731
504 732 // Determine options to use (if set in Shortcode, use those, otherwise use stored options, from the "Edit" screen).
505 733 $render_options = array();
506 734 foreach ( $shortcode_atts as $key => $value ) {
507 - // We have to check this, because strings 'true' or 'false' are not recognized as boolean!
508 - if ( is_string( $value ) && 'true' === strtolower( $value ) ) {
509 - $render_options[ $key ] = true;
510 - } elseif ( is_string( $value ) && 'false' === strtolower( $value ) ) {
511 - $render_options[ $key ] = false;
512 - } 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.
513 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 + }
514 748 } else {
749 + // Keep all other values.
515 750 $render_options[ $key ] = $value;
516 751 }
517 752 }
518 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 +
519 758 // Generate unique HTML ID, depending on how often this table has already been shown on this page.
520 759 if ( ! isset( $this->shown_tables[ $table_id ] ) ) {
521 760 $this->shown_tables[ $table_id ] = array(
522 761 'count' => 0,
@@ -522,9 +761,9 @@
522 761 'count' => 0,
523 762 'instances' => array(),
524 763 );
525 764 }
526 - $this->shown_tables[ $table_id ]['count']++;
765 + ++$this->shown_tables[ $table_id ]['count'];
527 766 $count = $this->shown_tables[ $table_id ]['count'];
528 767 $render_options['html_id'] = "tablepress-{$table_id}";
529 768 if ( $count > 1 ) {
530 769 $render_options['html_id'] .= "-no-{$count}";
@@ -529,15 +768,15 @@
529 768 if ( $count > 1 ) {
530 769 $render_options['html_id'] .= "-no-{$count}";
531 770 }
532 771 /**
533 - * Filter the ID of the table HTML element.
772 + * Filters the ID of the table HTML element.
534 773 *
535 774 * @since 1.0.0
536 775 *
537 776 * @param string $html_id The ID of the table HTML element.
538 777 * @param string $table_id The current table ID.
539 - * @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.
540 779 */
541 780 $render_options['html_id'] = apply_filters( 'tablepress_html_id', $render_options['html_id'], $table_id, $count );
542 781
543 782 // Generate the "Edit Table" link.
@@ -542,9 +781,9 @@
542 781
543 782 // Generate the "Edit Table" link.
544 783 $render_options['edit_table_url'] = '';
545 784 /**
546 - * Filter whether the "Edit" link below the table shall be shown.
785 + * Filters whether the "Edit" link below the table shall be shown.
547 786 *
548 787 * The "Edit" link is only shown to logged-in users who possess the necessary capability to edit the table.
549 788 *
550 789 * @since 1.0.0
@@ -551,71 +790,38 @@
551 790 *
552 791 * @param bool $show Whether to show the "Edit" link below the table. Default true.
553 792 * @param string $table_id The current table ID.
554 793 */
555 - 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'] ) ) {
556 795 $render_options['edit_table_url'] = TablePress::url( array( 'action' => 'edit', 'table_id' => $table['id'] ) );
557 796 }
558 797
559 798 /**
560 - * Filter the render options for the table.
799 + * Filters the render options for the table.
561 800 *
562 801 * The render options are determined from the settings on a table's "Edit" screen and the Shortcode parameters.
563 802 *
564 803 * @since 1.0.0
565 804 *
566 - * @param array $render_options The render options for the table.
567 - * @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.
568 807 */
569 808 $render_options = apply_filters( 'tablepress_table_render_options', $render_options, $table );
570 809
571 - // 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.
572 - if ( $render_options['use_datatables'] && $render_options['table_head'] && count( $table['data'] ) > 1 ) {
573 - // Get options for the DataTables JavaScript library from the table's render options.
574 - $js_options = array();
575 - foreach ( array(
576 - 'alternating_row_colors',
577 - 'datatables_sort',
578 - 'datatables_paginate',
579 - 'datatables_paginate',
580 - 'datatables_paginate_entries',
581 - 'datatables_lengthchange',
582 - 'datatables_filter',
583 - 'datatables_info',
584 - 'datatables_scrollx',
585 - 'datatables_scrolly',
586 - 'datatables_locale',
587 - 'datatables_custom_commands',
588 - ) as $option ) {
589 - $js_options[ $option ] = $render_options[ $option ];
590 - }
591 - /**
592 - * Filter the JavaScript options for the table.
593 - *
594 - * The JavaScript options are determined from the settings on a table's "Edit" screen and the Shortcode parameters.
595 - * They are part of the render options and can be overwritten with Shortcode parameters.
596 - *
597 - * @since 1.0.0
598 - *
599 - * @param array $js_options The JavaScript options for the table.
600 - * @param string $table_id The current table ID.
601 - * @param array $render_options The render options for the table.
602 - */
603 - $js_options = apply_filters( 'tablepress_table_js_options', $js_options, $table_id, $render_options );
604 - $this->shown_tables[ $table_id ]['instances'][ $render_options['html_id'] ] = $js_options;
605 - $this->_enqueue_datatables();
606 - }
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'] );
607 813
608 814 // Check if table output shall and can be loaded from the transient cache, otherwise generate the output.
609 815 if ( $render_options['cache_table_output'] && ! is_user_logged_in() ) {
610 816 // Hash the Render Options array to get a unique cache identifier.
611 - $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
612 818 $transient_name = 'tablepress_' . $table_hash; // Attention: This string must not be longer than 45 characters!
613 819 $output = get_transient( $transient_name );
614 820 if ( false === $output || '' === $output ) {
615 821 // Render/generate the table HTML, as it was not found in the cache.
616 822 $_render->set_input( $table, $render_options );
617 - $output = $_render->get_output();
823 + $output = $_render->get_output( 'html' );
618 824 // Save render output in a transient, set cache timeout to 24 hours.
619 825 set_transient( $transient_name, $output, DAY_IN_SECONDS );
620 826 // Update output caches list transient (necessary for cache invalidation upon table saving).
621 827 $caches_list_transient_name = 'tablepress_c_' . md5( $table_id );
@@ -630,9 +836,9 @@
630 836 }
631 837 set_transient( $caches_list_transient_name, wp_json_encode( $caches_list, TABLEPRESS_JSON_OPTIONS ), 2 * DAY_IN_SECONDS );
632 838 } else {
633 839 /**
634 - * Filter the cache hit comment message.
840 + * Filters the cache hit comment message.
635 841 *
636 842 * @since 1.0.0
637 843 *
638 844 * @param string $comment The cache hit comment message.
@@ -641,14 +847,65 @@
641 847 }
642 848 } else {
643 849 // Render/generate the table HTML, as no cache is to be used.
644 850 $_render->set_input( $table, $render_options );
645 - $output = $_render->get_output();
851 + $output = $_render->get_output( 'html' );
646 852 }
647 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 +
648 905 // Maybe print a list of used render options.
649 906 if ( $render_options['shortcode_debug'] && is_user_logged_in() ) {
650 - $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
651 908 }
652 909
653 910 return $output;
654 911 }
@@ -653,19 +910,16 @@
653 910 return $output;
654 911 }
655 912
656 913 /**
657 - * Handle Shortcode [table-info id=<ID> field=<name> /] in the_content().
914 + * Handles the Shortcode [table-info id=<ID> field=<name> /].
658 915 *
659 916 * @since 1.0.0
660 917 *
661 - * @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.
662 919 * @return string Text that replaces the Shortcode (error message or asked-for information).
663 920 */
664 - public function shortcode_table_info( $shortcode_atts ) {
665 - // For empty Shortcodes like [table-info] or [table-info /], an empty string is passed, see Core #26927.
666 - $shortcode_atts = (array) $shortcode_atts;
667 -
921 + public function shortcode_table_info( array $shortcode_atts ): string {
668 922 // Parse Shortcode attributes, only allow those that are specified.
669 923 $default_shortcode_atts = array(
670 924 'id' => '',
671 925 'field' => '',
@@ -671,35 +925,35 @@
671 925 'field' => '',
672 926 'format' => '',
673 927 );
674 928 /**
675 - * Filter the available/default attributes for the [table-info] Shortcode.
929 + * Filters the available/default attributes for the [table-info] Shortcode.
676 930 *
677 931 * @since 1.0.0
678 932 *
679 - * @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.
680 934 */
681 935 $default_shortcode_atts = apply_filters( 'tablepress_shortcode_table_info_default_shortcode_atts', $default_shortcode_atts );
682 936 $shortcode_atts = shortcode_atts( $default_shortcode_atts, $shortcode_atts ); // Optional third argument left out on purpose. Use filter in the next line instead.
683 937 /**
684 - * Filter the attributes that were passed to the [table-info] Shortcode.
938 + * Filters the attributes that were passed to the [table-info] Shortcode.
685 939 *
686 940 * @since 1.0.0
687 941 *
688 - * @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.
689 943 */
690 944 $shortcode_atts = apply_filters( 'tablepress_shortcode_table_info_shortcode_atts', $shortcode_atts );
691 945
692 946 /**
693 - * 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.
694 948 *
695 949 * @since 1.0.0
696 950 *
697 - * @param bool|string $overwrite Whether the [table-info] output is overwritten. Return false for the regular content, and a string to overwrite the output.
698 - * @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.
699 953 */
700 954 $overwrite = apply_filters( 'tablepress_shortcode_table_info_overwrite', false, $shortcode_atts );
701 - if ( $overwrite ) {
955 + if ( is_string( $overwrite ) ) {
702 956 return $overwrite;
703 957 }
704 958
705 959 // Check, if a table with the given ID exists.
@@ -704,9 +958,9 @@
704 958
705 959 // Check, if a table with the given ID exists.
706 960 $table_id = preg_replace( '/[^a-zA-Z0-9_-]/', '', $shortcode_atts['id'] );
707 961 if ( ! TablePress::$model_table->table_exists( $table_id ) ) {
708 - $message = "[table &#8220;{$table_id}&#8221; not found /]<br />\n";
962 + $message = "&#91;table “{$table_id}” not found /&#93;<br />\n";
709 963 /** This filter is documented in controllers/controller-frontend.php */
710 964 $message = apply_filters( 'tablepress_table_not_found_message', $message, $table_id );
711 965 return $message;
712 966 }
@@ -713,16 +967,16 @@
713 967
714 968 // Load table, with table data, options, and visibility settings.
715 969 $table = TablePress::$model_table->load( $table_id, true, true );
716 970 if ( is_wp_error( $table ) ) {
717 - $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";
718 972 /** This filter is documented in controllers/controller-frontend.php */
719 973 $message = apply_filters( 'tablepress_table_load_error_message', $message, $table_id, $table );
720 974 return $message;
721 975 }
722 976
723 - $field = preg_replace( '/[^a-z_]/', '', strtolower( $shortcode_atts['field'] ) );
724 - $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'] ) );
725 979
726 980 // Generate output, depending on what information (field) was asked for.
727 981 switch ( $field ) {
728 982 case 'name':
@@ -727,36 +981,46 @@
727 981 switch ( $field ) {
728 982 case 'name':
729 983 case 'description':
730 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 + }
731 992 break;
732 993 case 'last_modified':
733 994 switch ( $format ) {
734 995 case 'raw':
996 + case 'mysql':
735 997 $output = $table['last_modified'];
736 998 break;
737 999 case 'human':
738 - $modified_timestamp = strtotime( $table['last_modified'] );
739 - $current_timestamp = current_time( 'timestamp' );
1000 + $modified_timestamp = date_create( $table['last_modified'], wp_timezone() );
1001 + if ( false === $modified_timestamp ) {
1002 + $modified_timestamp = $table['last_modified'];
1003 + } else {
1004 + $modified_timestamp = $modified_timestamp->getTimestamp();
1005 + }
1006 + $current_timestamp = time();
740 1007 $time_diff = $current_timestamp - $modified_timestamp;
741 - // Time difference is only shown up to one day.
742 - if ( $time_diff >= 0 && $time_diff < DAY_IN_SECONDS ) {
1008 + // Time difference is only shown up to one week.
1009 + if ( $time_diff >= 0 && $time_diff < WEEK_IN_SECONDS ) {
743 1010 $output = sprintf( __( '%s ago', 'default' ), human_time_diff( $modified_timestamp, $current_timestamp ) );
744 1011 } else {
745 - $output = TablePress::format_datetime( $table['last_modified'], 'mysql', '<br />' );
1012 + $output = TablePress::format_datetime( $table['last_modified'], '<br />' );
746 1013 }
747 1014 break;
748 1015 case 'date':
749 - $modified_timestamp = strtotime( $table['last_modified'] );
750 - $output = date_i18n( get_option( 'date_format' ), $modified_timestamp );
1016 + $output = TablePress::format_datetime( $table['last_modified'], get_option( 'date_format' ) );
751 1017 break;
752 1018 case 'time':
753 - $modified_timestamp = strtotime( $table['last_modified'] );
754 - $output = date_i18n( get_option( 'time_format' ), $modified_timestamp );
1019 + $output = TablePress::format_datetime( $table['last_modified'], get_option( 'time_format' ) );
755 1020 break;
756 - case 'mysql':
757 1021 default:
758 - $output = TablePress::format_datetime( $table['last_modified'], 'mysql', ' ' );
1022 + $output = TablePress::format_datetime( $table['last_modified'] );
759 1023 break;
760 1024 }
761 1025 break;
762 1026 case 'last_editor':
@@ -767,14 +1031,10 @@
767 1031 break;
768 1032 case 'number_rows':
769 1033 $output = count( $table['data'] );
770 1034 if ( 'raw' !== $format ) {
771 - if ( $table['options']['table_head'] ) {
772 - $output = $output - 1;
773 - }
774 - if ( $table['options']['table_foot'] ) {
775 - $output = $output - 1;
776 - }
1035 + $output -= $table['options']['table_head'];
1036 + $output -= $table['options']['table_foot'];
777 1037 }
778 1038 break;
779 1039 case 'number_columns':
780 1040 $output = count( $table['data'][0] );
@@ -779,30 +1039,30 @@
779 1039 case 'number_columns':
780 1040 $output = count( $table['data'][0] );
781 1041 break;
782 1042 default:
783 - $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";
784 1044 /**
785 - * Filter the "table info field not found" message.
1045 + * Filters the "table info field not found" message.
786 1046 *
787 1047 * @since 1.0.0
788 1048 *
789 - * @param string $output The "table info field not found" message.
790 - * @param array $table The current table ID.
791 - * @param string $field The field that was not found.
792 - * @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.
793 1053 */
794 1054 $output = apply_filters( 'tablepress_table_info_not_found_message', $output, $table, $field, $format );
795 1055 }
796 1056
797 1057 /**
798 - * Filter the output of the [table-info] Shortcode.
1058 + * Filters the output of the [table-info] Shortcode.
799 1059 *
800 1060 * @since 1.0.0
801 1061 *
802 - * @param string $output The output of the [table-info] Shortcode.
803 - * @param array $table The current table.
804 - * @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.
805 1065 */
806 1066 $output = apply_filters( 'tablepress_shortcode_table_info_output', $output, $table, $shortcode_atts );
807 1067 return $output;
808 1068 }
@@ -807,9 +1067,9 @@
807 1067 return $output;
808 1068 }
809 1069
810 1070 /**
811 - * 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.
812 1072 *
813 1073 * This is done by looping through all search terms and TablePress tables and searching there for the search term,
814 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
815 1075 * Shortcode for one of these tables in their content.
@@ -820,11 +1080,18 @@
820 1080 *
821 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.
822 1082 * @return string Eventually extended SQL "WHERE" clause, to also find posts/pages with Shortcodes in them.
823 1083 */
824 - 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 +
825 1087 global $wpdb;
826 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 +
827 1094 if ( ! is_search() || ! is_main_query() ) {
828 1095 return $search_sql;
829 1096 }
830 1097
@@ -838,20 +1105,27 @@
838 1105 $table_ids = TablePress::$model_table->load_all( true, false );
839 1106 // Array of all search words that were found, and the table IDs where they were found.
840 1107 $query_result = array();
841 1108
1109 + $fn_stripos = function_exists( 'mb_stripos' ) ? 'mb_stripos' : 'stripos';
1110 +
842 1111 foreach ( $table_ids as $table_id ) {
843 1112 // Load table, with table data, options, and visibility settings.
844 1113 $table = TablePress::$model_table->load( $table_id, true, true );
845 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.
846 1121 if ( isset( $table['is_corrupted'] ) && $table['is_corrupted'] ) {
847 - // Do not search in corrupted tables.
848 1122 continue;
849 1123 }
850 1124
851 1125 foreach ( $search_terms as $search_term ) {
852 - if ( ( $table['options']['print_name'] && false !== stripos( $table['name'], $search_term ) )
853 - || ( $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 ) ) ) {
854 1128 // Found the search term in the name or description (and they are shown).
855 1129 $query_result[ $search_term ][] = $table_id; // Add table ID to result list.
856 1130 // No need to continue searching this search term in this table.
857 1131 continue;
@@ -867,11 +1141,11 @@
867 1141 if ( 0 === $table['visibility']['columns'][ $col_idx ] ) {
868 1142 // Column is hidden, so don't search in it.
869 1143 continue;
870 1144 }
871 - // @TODO: Cells are not evaluated here, so math formulas are searched.
872 - if ( false !== stripos( $table_cell, $search_term ) ) {
873 - // Found the search term in the cell content.
1145 + // @todo Cells are not evaluated here, so math formulas are searched.
1146 + if ( false !== $fn_stripos( $table_cell, (string) $search_term ) ) {
1147 + // Found the search term in the cell content.
874 1148 $query_result[ $search_term ][] = $table_id; // Add table ID to result list
875 1149 // No need to continue searching this search term in this table.
876 1150 continue 3;
877 1151 }
@@ -887,9 +1161,9 @@
887 1161 $n = ( empty( $exact ) ) ? '%' : '';
888 1162 $search_sql = $wpdb->remove_placeholder_escape( $search_sql );
889 1163 foreach ( $query_result as $search_term => $table_ids ) {
890 1164 $search_term = esc_sql( $wpdb->esc_like( $search_term ) );
891 - $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.)
892 1166 $table_ids = implode( '|', $table_ids );
893 1167 $regexp = '\\\\[' . TablePress::$shortcode . ' id=(["\\\']?)(' . $table_ids . ')([\]"\\\' /])'; // ' needs to be single escaped, [ double escaped (with \\) in mySQL
894 1168 $new_or = $old_or . " OR ({$wpdb->posts}.post_content REGEXP '{$regexp}')";
895 1169 $search_sql = str_replace( $old_or, $new_or, $search_sql );
@@ -896,7 +1170,67 @@
896 1170 }
897 1171 $search_sql = $wpdb->add_placeholder_escape( $search_sql );
898 1172
899 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' );
900 1234 }
901 1235
902 1236 } // class TablePress_Frontend_Controller