PluginProbe
TableKit – WordPress Table Builder for Data Tables, WooCommerce Product Tables & Post Tables / 2.2.14
TableKit – WordPress Table Builder for Data Tables, WooCommerce Product Tables & Post Tables v2.2.14
2.2.14 2.2.13 2.2.12 2.2.11 2.2.10 2.2.9 2.2.8 2.2.7 2.2.6 2.2.5 2.2.4 2.2.3 trunk 1.0.0 1.0.1 2.0.0 2.0.1 2.1.0 2.1.1 2.1.2 2.2.0 2.2.1 2.2.2
← All changes | includes/Shortcode/ShortcodeUtils.php +159 -77 2.2.72.2.14 View file →
@@ -1,5 +1,10 @@
1 1 <?php
2 +/**
3 + * Shared helpers used by the [tableKit] shortcode
4 + *
5 + * @package TableKit
6 + */
2 7
3 8 namespace TableBuilder\Shortcode;
4 9
5 10 use TableBuilder\Config\CPT\TableCPT;
@@ -4,64 +9,96 @@
4 9
5 10 use TableBuilder\Config\CPT\TableCPT;
6 11 use TableBuilder\Helpers\Utils;
7 12
8 -defined('ABSPATH') || exit;
13 +defined( 'ABSPATH' ) || exit;
9 14
10 -class ShortcodeUtils
11 -{
12 - public static function get_block_asset_handle(string $block_name, string $asset_type): string
13 - {
14 - if (function_exists('generate_block_asset_handle')) {
15 - return generate_block_asset_handle($block_name, $asset_type, 0);
15 +/**
16 + * Static helper methods for shortcode block lookup, asset handling, and CSS collection.
17 + */
18 +class ShortcodeUtils {
19 +
20 + /**
21 + * Resolves the registered asset handle for a block's style/view script,
22 + * falling back to TableKit's own naming convention on older WP versions
23 + * that lack generate_block_asset_handle().
24 + *
25 + * @param string $block_name Block name, e.g. "tablebuilder/table-builder".
26 + * @param string $asset_type Either "style" or "viewScript".
27 + * @return string The resolved asset handle.
28 + */
29 + public static function get_block_asset_handle( string $block_name, string $asset_type ): string {
30 + if ( function_exists( 'generate_block_asset_handle' ) ) {
31 + return generate_block_asset_handle( $block_name, $asset_type, 0 );
16 32 }
17 33
18 - $base = str_replace('/', '-', $block_name);
34 + $base = str_replace( '/', '-', $block_name );
19 35
20 36 return 'style' === $asset_type ? "{$base}-style" : "{$base}-view-script";
21 37 }
22 38
23 - public static function filter_table_blocks(array $blocks): array
24 - {
25 - $found = [];
39 + /**
40 + * Recursively filters a parsed block tree down to registered table blocks.
41 + *
42 + * @param array $blocks Parsed block tree, as returned by parse_blocks().
43 + * @return array Matching table blocks, including nested ones.
44 + */
45 + public static function filter_table_blocks( array $blocks ): array {
46 + $found = array();
26 47 $block_names = TableCPT::get_table_blocks();
27 48
28 - foreach ($blocks as $block) {
29 - if (in_array($block['blockName'] ?? '', $block_names, true)) {
49 + foreach ( $blocks as $block ) {
50 + if ( in_array( $block['blockName'] ?? '', $block_names, true ) ) {
30 51 $found[] = $block;
31 52 }
32 - if (! empty($block['innerBlocks'])) {
33 - foreach (self::filter_table_blocks($block['innerBlocks']) as $inner) {
53 + if ( ! empty( $block['innerBlocks'] ) ) {
54 + foreach ( self::filter_table_blocks( $block['innerBlocks'] ) as $inner ) {
34 55 $found[] = $inner;
35 56 }
36 57 }
37 58 }
38 59
39 - return array_values($found);
60 + return array_values( $found );
40 61 }
41 62
42 - public static function get_block_label(string $block_name): string
43 - {
44 - return TableCPT::get_block_label($block_name);
63 + /**
64 + * Gets a human-readable label for a table block name.
65 + *
66 + * @param string $block_name Block name, e.g. "tablebuilder/table-builder".
67 + * @return string The block's display label, or the block name if unknown.
68 + */
69 + public static function get_block_label( string $block_name ): string {
70 + return TableCPT::get_block_label( $block_name );
45 71 }
46 72
47 - public static function decode_json(string $value): array
48 - {
49 - $value = trim($value);
50 - if ($value === '') {
51 - return [];
73 + /**
74 + * Safely decode a JSON string (e.g. the shortcode's attrs_json attribute)
75 + * into an associative array.
76 + *
77 + * @param string $value Raw, possibly-slashed JSON string.
78 + * @return array Decoded array, or an empty array on invalid/empty input.
79 + */
80 + public static function decode_json( string $value ): array {
81 + $value = trim( $value );
82 + if ( '' === $value ) {
83 + return array();
52 84 }
53 85
54 - $decoded = json_decode(wp_unslash($value), true);
86 + $decoded = json_decode( wp_unslash( $value ), true );
55 87
56 - return (JSON_ERROR_NONE === json_last_error() && is_array($decoded))
88 + return ( JSON_ERROR_NONE === json_last_error() && is_array( $decoded ) )
57 89 ? $decoded
58 - : [];
90 + : array();
59 91 }
60 92
61 - public static function enqueue_shared_styles(): void
62 - {
63 - if (! defined('TABLE_BUILDER_BLOCK_PLUGIN_URL')) {
93 + /**
94 + * Enqueues the shared global/component stylesheets used by table blocks
95 + * on the frontend (idempotent — safe to call multiple times).
96 + *
97 + * @return void
98 + */
99 + public static function enqueue_shared_styles(): void {
100 + if ( ! defined( 'TABLE_BUILDER_BLOCK_PLUGIN_URL' ) ) {
64 101 return;
65 102 }
66 103
67 104 wp_enqueue_style(
@@ -66,74 +103,97 @@
66 103
67 104 wp_enqueue_style(
68 105 'table-builder-global',
69 106 TABLE_BUILDER_BLOCK_PLUGIN_URL . 'build/tablebuilder/global.css',
70 - [],
107 + array(),
71 108 TABLE_BUILDER_BLOCK_PLUGIN_VERSION
72 109 );
73 110 wp_enqueue_style(
74 111 'table-builder-components',
75 112 TABLE_BUILDER_BLOCK_PLUGIN_URL . 'build/tablebuilder/components.css',
76 - [],
113 + array(),
77 114 TABLE_BUILDER_BLOCK_PLUGIN_VERSION
78 115 );
79 116 }
80 117
81 - public static function enqueue_block_assets(string $block_name): void
82 - {
83 - $style = self::get_block_asset_handle($block_name, 'style');
84 - $script = self::get_block_asset_handle($block_name, 'viewScript');
118 + /**
119 + * Enqueues a specific block's style/view-script assets, if registered
120 + * (used when rendering a block outside its normal render_block() path,
121 + * e.g. via the [tableKit] shortcode).
122 + *
123 + * @param string $block_name Block name, e.g. "tablebuilder/table-builder".
124 + * @return void
125 + */
126 + public static function enqueue_block_assets( string $block_name ): void {
127 + $style = self::get_block_asset_handle( $block_name, 'style' );
128 + $script = self::get_block_asset_handle( $block_name, 'viewScript' );
85 129
86 - if ($style && wp_style_is($style, 'registered')) {
87 - wp_enqueue_style($style);
130 + if ( $style && wp_style_is( $style, 'registered' ) ) {
131 + wp_enqueue_style( $style );
88 132 }
89 - if ($script && wp_script_is($script, 'registered')) {
90 - wp_enqueue_script($script);
133 + if ( $script && wp_script_is( $script, 'registered' ) ) {
134 + wp_enqueue_script( $script );
91 135 }
92 136 }
93 137
94 - public static function collect_blocks_recursive(array $blocks, callable $predicate): array
95 - {
96 - $found = [];
97 - foreach ($blocks as $block) {
98 - if ($predicate($block)) {
138 + /**
139 + * Recursively collects blocks (including nested inner blocks) matching a predicate.
140 + *
141 + * @param array $blocks Parsed block tree, as returned by parse_blocks().
142 + * @param callable $predicate Callback receiving a single block array, returning bool.
143 + * @return array Matching blocks, in document order.
144 + */
145 + public static function collect_blocks_recursive( array $blocks, callable $predicate ): array {
146 + $found = array();
147 + foreach ( $blocks as $block ) {
148 + if ( $predicate( $block ) ) {
99 149 $found[] = $block;
100 150 }
101 - if (! empty($block['innerBlocks'])) {
151 + if ( ! empty( $block['innerBlocks'] ) ) {
102 152 $found = array_merge(
103 153 $found,
104 - self::collect_blocks_recursive($block['innerBlocks'], $predicate)
154 + self::collect_blocks_recursive( $block['innerBlocks'], $predicate )
105 155 );
106 156 }
107 157 }
108 - return array_values($found);
158 + return array_values( $found );
109 159 }
110 160
111 - public static function is_elementor_editor(): bool
112 - {
113 - if (! defined('ELEMENTOR_VERSION') || ! class_exists('\\Elementor\\Plugin')) {
161 + /**
162 + * Detects whether the current request is inside the Elementor edit or preview mode.
163 + *
164 + * @return bool True if Elementor is active and currently editing/previewing.
165 + */
166 + public static function is_elementor_editor(): bool {
167 + if ( ! defined( 'ELEMENTOR_VERSION' ) || ! class_exists( '\\Elementor\\Plugin' ) ) {
114 168 return false;
115 169 }
116 170
117 171 $plugin = \Elementor\Plugin::$instance ?? null;
118 - if (! $plugin) {
172 + if ( ! $plugin ) {
119 173 return false;
120 174 }
121 175
122 - $is_edit = isset($plugin->editor)
123 - && method_exists($plugin->editor, 'is_edit_mode')
176 + $is_edit = isset( $plugin->editor )
177 + && method_exists( $plugin->editor, 'is_edit_mode' )
124 178 && $plugin->editor->is_edit_mode();
125 179
126 - $is_preview = isset($plugin->preview)
127 - && method_exists($plugin->preview, 'is_preview_mode')
180 + $is_preview = isset( $plugin->preview )
181 + && method_exists( $plugin->preview, 'is_preview_mode' )
128 182 && $plugin->preview->is_preview_mode();
129 183
130 184 return $is_edit || $is_preview;
131 185 }
132 186
133 - public static function get_elementor_init_script(): string
134 - {
135 - if (! self::is_elementor_editor()) {
187 + /**
188 + * Gets an inline script element that re-fires Elementor's frontend "element_ready"
189 + * hooks, needed because content rendered via AJAX/shortcode inside the
190 + * Elementor editor doesn't trigger Elementor's own initialization events.
191 + *
192 + * @return string The script element's markup, or an empty string outside the Elementor editor.
193 + */
194 + public static function get_elementor_init_script(): string {
195 + if ( ! self::is_elementor_editor() ) {
136 196 return '';
137 197 }
138 198
139 199 return <<<'JS'
@@ -154,50 +214,63 @@
154 214 </script>
155 215 JS;
156 216 }
157 217
158 - public static function collect_block_css(array $block): string
159 - {
160 - $css = self::build_css_from_attrs($block['attrs'] ?? []);
218 + /**
219 + * Recursively collects the generated per-breakpoint CSS for a block and its children.
220 + *
221 + * @param array $block A single parsed block (with optional 'innerBlocks').
222 + * @return string Concatenated CSS for this block and all descendants.
223 + */
224 + public static function collect_block_css( array $block ): string {
225 + $css = self::build_css_from_attrs( $block['attrs'] ?? array() );
161 226
162 - foreach ($block['innerBlocks'] ?? [] as $inner_block) {
163 - $css .= self::collect_block_css($inner_block);
227 + foreach ( $block['innerBlocks'] ?? array() as $inner_block ) {
228 + $css .= self::collect_block_css( $inner_block );
164 229 }
165 230
166 231 return $css;
167 232 }
168 233
169 - public static function build_css_from_attrs(array $attrs): string
170 - {
234 + /**
235 + * Builds a single block's responsive CSS from its stored "blocksCSS" attribute,
236 + * wrapping per-device rules in the appropriate min/max-width media query.
237 + *
238 + * @param array $attrs Block attributes, expected to contain a "blocksCSS" map keyed by device slug.
239 + * @return string The generated CSS, or an empty string if there's nothing to output.
240 + */
241 + public static function build_css_from_attrs( array $attrs ): string {
171 242 static $device_list = null;
172 - $device_list ??= Utils::get_device_list();
243 + $device_list ??= Utils::get_device_list();
173 244
174 245 $blocks_css = $attrs['blocksCSS'] ?? null;
175 246
176 - if (empty($blocks_css) || ! is_array($blocks_css)) {
247 + if ( empty( $blocks_css ) || ! is_array( $blocks_css ) ) {
177 248 return '';
178 249 }
179 250
180 251 $css_map = array_filter(
181 252 $blocks_css,
182 - static fn($value) => is_string($value) && '' !== trim($value)
253 + static fn( $value ) => is_string( $value ) && '' !== trim( $value )
183 254 );
184 255
185 - if (empty($css_map)) {
256 + if ( empty( $css_map ) ) {
186 257 return '';
187 258 }
188 259
189 260 $output = '';
190 261
191 - foreach ($device_list as $device) {
192 - $slug = strtolower($device['slug'] ?? '');
193 - $css = trim($css_map[$slug] ?? '');
262 + foreach ( $device_list as $device ) {
263 + $slug = strtolower( $device['slug'] ?? '' );
264 + $css = trim( $css_map[ $slug ] ?? '' );
194 265
195 - if ('' === $css) {
266 + if ( '' === $css ) {
196 267 continue;
197 268 }
198 269
199 - if ('base' === ($device['value'] ?? '')) {
270 + $css = self::sanitize_css( $css );
271 +
272 + if ( 'base' === ( $device['value'] ?? '' ) ) {
200 273 $output .= $css;
201 274 } else {
202 275 $output .= "@media ({$device['direction']}-width:{$device['value']}px){{$css}}";
203 276 }
@@ -202,11 +275,20 @@
202 275 $output .= "@media ({$device['direction']}-width:{$device['value']}px){{$css}}";
203 276 }
204 277 }
205 278
206 - if (! empty($css_map['customStyles'])) {
207 - $output .= $css_map['customStyles'];
279 + if ( ! empty( $css_map['customStyles'] ) ) {
280 + $output .= self::sanitize_css( $css_map['customStyles'] );
208 281 }
209 282
210 283 return $output;
284 + }
285 +
286 + /**
287 + * Strips any HTML-tag-like sequences from a stored CSS string before it's
288 + * @param string $css Raw CSS string from a block attribute.
289 + * @return string Sanitized CSS string.
290 + */
291 + private static function sanitize_css( string $css ): string {
292 + return preg_replace( '/<[^>]*>?/', '', $css );
211 293 }
212 294 }