PluginProbe
TableKit – WordPress Table Builder for Data Tables, WooCommerce Product Tables & Post Tables / trunk
TableKit – WordPress Table Builder for Data Tables, WooCommerce Product Tables & Post Tables vtrunk
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
table-builder-block / includes / Shortcode / ShortcodeUtils.php

ShortcodeUtils.php in TableKit – WordPress Table Builder for Data Tables, WooCommerce Product Tables & Post Tables trunk, at includes/Shortcode/ShortcodeUtils.php

295 lines 8.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Shared helpers used by the [tableKit] shortcode
4 *
5 * @package TableKit
6 */
7
8 namespace TableBuilder\Shortcode;
9
10 use TableBuilder\Config\CPT\TableCPT;
11 use TableBuilder\Helpers\Utils;
12
13 defined( 'ABSPATH' ) || exit;
14
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 );
32 }
33
34 $base = str_replace( '/', '-', $block_name );
35
36 return 'style' === $asset_type ? "{$base}-style" : "{$base}-view-script";
37 }
38
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();
47 $block_names = TableCPT::get_table_blocks();
48
49 foreach ( $blocks as $block ) {
50 if ( in_array( $block['blockName'] ?? '', $block_names, true ) ) {
51 $found[] = $block;
52 }
53 if ( ! empty( $block['innerBlocks'] ) ) {
54 foreach ( self::filter_table_blocks( $block['innerBlocks'] ) as $inner ) {
55 $found[] = $inner;
56 }
57 }
58 }
59
60 return array_values( $found );
61 }
62
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 );
71 }
72
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();
84 }
85
86 $decoded = json_decode( wp_unslash( $value ), true );
87
88 return ( JSON_ERROR_NONE === json_last_error() && is_array( $decoded ) )
89 ? $decoded
90 : array();
91 }
92
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' ) ) {
101 return;
102 }
103
104 wp_enqueue_style(
105 'table-builder-global',
106 TABLE_BUILDER_BLOCK_PLUGIN_URL . 'build/tablebuilder/global.css',
107 array(),
108 TABLE_BUILDER_BLOCK_PLUGIN_VERSION
109 );
110 wp_enqueue_style(
111 'table-builder-components',
112 TABLE_BUILDER_BLOCK_PLUGIN_URL . 'build/tablebuilder/components.css',
113 array(),
114 TABLE_BUILDER_BLOCK_PLUGIN_VERSION
115 );
116 }
117
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' );
129
130 if ( $style && wp_style_is( $style, 'registered' ) ) {
131 wp_enqueue_style( $style );
132 }
133 if ( $script && wp_script_is( $script, 'registered' ) ) {
134 wp_enqueue_script( $script );
135 }
136 }
137
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 ) ) {
149 $found[] = $block;
150 }
151 if ( ! empty( $block['innerBlocks'] ) ) {
152 $found = array_merge(
153 $found,
154 self::collect_blocks_recursive( $block['innerBlocks'], $predicate )
155 );
156 }
157 }
158 return array_values( $found );
159 }
160
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' ) ) {
168 return false;
169 }
170
171 $plugin = \Elementor\Plugin::$instance ?? null;
172 if ( ! $plugin ) {
173 return false;
174 }
175
176 $is_edit = isset( $plugin->editor )
177 && method_exists( $plugin->editor, 'is_edit_mode' )
178 && $plugin->editor->is_edit_mode();
179
180 $is_preview = isset( $plugin->preview )
181 && method_exists( $plugin->preview, 'is_preview_mode' )
182 && $plugin->preview->is_preview_mode();
183
184 return $is_edit || $is_preview;
185 }
186
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() ) {
196 return '';
197 }
198
199 return <<<'JS'
200 <script>
201 (function () {
202 var run = function () {
203 try { document.dispatchEvent(new Event('DOMContentLoaded')); } catch (e) {}
204
205 try {
206 if (window.elementorFrontend && window.elementorFrontend.hooks) {
207 window.elementorFrontend.hooks.doAction('frontend/element_ready/global');
208 }
209 } catch (e) {}
210 };
211
212 setTimeout(run, 0);
213 }());
214 </script>
215 JS;
216 }
217
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() );
226
227 foreach ( $block['innerBlocks'] ?? array() as $inner_block ) {
228 $css .= self::collect_block_css( $inner_block );
229 }
230
231 return $css;
232 }
233
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 {
242 static $device_list = null;
243 $device_list ??= Utils::get_device_list();
244
245 $blocks_css = $attrs['blocksCSS'] ?? null;
246
247 if ( empty( $blocks_css ) || ! is_array( $blocks_css ) ) {
248 return '';
249 }
250
251 $css_map = array_filter(
252 $blocks_css,
253 static fn( $value ) => is_string( $value ) && '' !== trim( $value )
254 );
255
256 if ( empty( $css_map ) ) {
257 return '';
258 }
259
260 $output = '';
261
262 foreach ( $device_list as $device ) {
263 $slug = strtolower( $device['slug'] ?? '' );
264 $css = trim( $css_map[ $slug ] ?? '' );
265
266 if ( '' === $css ) {
267 continue;
268 }
269
270 $css = self::sanitize_css( $css );
271
272 if ( 'base' === ( $device['value'] ?? '' ) ) {
273 $output .= $css;
274 } else {
275 $output .= "@media ({$device['direction']}-width:{$device['value']}px){{$css}}";
276 }
277 }
278
279 if ( ! empty( $css_map['customStyles'] ) ) {
280 $output .= self::sanitize_css( $css_map['customStyles'] );
281 }
282
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 );
293 }
294 }
295