PluginProbe
TableKit – WordPress Table Builder for Data Tables, WooCommerce Product Tables & Post Tables / 2.3.0
TableKit – WordPress Table Builder for Data Tables, WooCommerce Product Tables & Post Tables v2.3.0
2.3.0 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
table-builder-block / includes / Elementor / TablekitWidget.php

TablekitWidget.php in TableKit – WordPress Table Builder for Data Tables, WooCommerce Product Tables & Post Tables 2.3.0, at includes/Elementor/TablekitWidget.php

437 lines 13.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Elementor widget for TableKit tables
4 *
5 * @package TableKit
6 */
7
8 use TableBuilder\Shortcode\ShortcodeUtils;
9
10 if ( ! defined( 'ABSPATH' ) ) {
11 exit;
12 }
13
14 /**
15 * Renders a TableKit table as a native Elementor widget.
16 */
17 class TableKit_Elementor_Widget extends \Elementor\Widget_Base {
18
19 private const ALL_BLOCKS = '';
20
21 /**
22 * Whether the shared global styles have been printed yet this request.
23 *
24 * @var bool
25 */
26 private static bool $global_styles_printed = false;
27
28 /**
29 * Block IDs whose per-instance styles have already been printed.
30 *
31 * @var array
32 */
33 private static array $printed_block_styles = array();
34
35 /**
36 * Block IDs whose per-instance scripts have already been printed.
37 *
38 * @var array
39 */
40 private static array $printed_block_scripts = array();
41
42 /**
43 * Cached table options list, or null if not yet built.
44 *
45 * @var array|null
46 */
47 private static ?array $table_options_cache = null;
48
49 // -------------------------------------------------------------------------
50 // Identity
51 // -------------------------------------------------------------------------
52
53 /**
54 * Elementor widget's internal name/slug.
55 *
56 * @return string
57 */
58 public function get_name(): string {
59 return 'tablekit_table';
60 }
61
62 /**
63 * Elementor widget's display title (shown in the widget panel).
64 *
65 * @return string
66 */
67 public function get_title(): string {
68 return __( 'TableKit', 'table-builder-block' );
69 }
70
71 /**
72 * Elementor widget's icon class.
73 *
74 * @return string
75 */
76 public function get_icon(): string {
77 return 'eicon-table';
78 }
79
80 /**
81 * Elementor category slugs this widget is listed under.
82 *
83 * @return string[]
84 */
85 public function get_categories(): array {
86 return array( 'general' );
87 }
88
89 /**
90 * Search keywords Elementor's widget panel matches against.
91 *
92 * @return string[]
93 */
94 public function get_keywords(): array {
95 return array( 'table', 'tablekit', 'data', 'grid' );
96 }
97
98 /**
99 * Whether Elementor should force a full preview reload when this widget's
100 * settings change (true, since table selection/rendering isn't done via
101 * live-refreshable controls).
102 *
103 * @return bool
104 */
105 public function is_reload_preview_required(): bool {
106 return true;
107 }
108
109 // -------------------------------------------------------------------------
110 // Controls
111 // -------------------------------------------------------------------------
112
113 /**
114 * Registers the widget's Elementor editor controls (table picker, block picker).
115 *
116 * @return void
117 */
118 protected function register_controls(): void {
119 $this->start_controls_section(
120 'tablekit_table_section',
121 array(
122 'label' => __( 'Table', 'table-builder-block' ),
123 'tab' => \Elementor\Controls_Manager::TAB_CONTENT,
124 )
125 );
126
127 $this->add_control(
128 'table_id',
129 array(
130 'label' => __( 'Select Table', 'table-builder-block' ),
131 'type' => \Elementor\Controls_Manager::SELECT2,
132 'options' => $this->get_table_options(),
133 'default' => '',
134 'render_type' => 'template',
135 'label_block' => true,
136 'description' => __( 'Create and manage tables under TableKit → Tables in the WP admin.', 'table-builder-block' ),
137 )
138 );
139
140 $this->add_control(
141 'block_index',
142 array(
143 'label' => __( 'Select Block', 'table-builder-block' ),
144 'type' => \Elementor\Controls_Manager::SELECT,
145 'options' => array( '' => __( '— All Blocks —', 'table-builder-block' ) ),
146 'default' => self::ALL_BLOCKS,
147 'render_type' => 'template',
148 'label_block' => true,
149 'description' => __( 'Choose a specific block to display. Or select — All Blocks — to show all blocks.', 'table-builder-block' ),
150 )
151 );
152
153 $this->end_controls_section();
154 }
155
156 // -------------------------------------------------------------------------
157 // Rendering
158 // -------------------------------------------------------------------------
159
160 /**
161 * Renders the widget on the frontend/editor: resolves the selected table post,
162 * optionally narrow to one block, print shared/per-table CSS, render the
163 * table block(s), and print their view scripts.
164 *
165 * @return void
166 */
167 protected function render(): void {
168 $settings = $this->get_settings_for_display();
169 $raw_id = $settings['table_id'] ?? 0;
170
171 if ( is_array( $raw_id ) ) {
172 $raw_id = reset( $raw_id );
173 }
174
175 $table_id = absint( $raw_id );
176
177 if ( empty( $table_id ) ) {
178 $this->render_placeholder( __( 'Select a table to display.', 'table-builder-block' ) );
179 return;
180 }
181
182 $post = get_post( $table_id );
183
184 if ( ! $post ) {
185 $this->render_placeholder( __( 'Table not found.', 'table-builder-block' ) );
186 return;
187 }
188
189 if ( class_exists( '\\TableBuilder\\Config\\Blocks' ) ) {
190 \TableBuilder\Config\Blocks::instance()->enqueue_block_assets();
191 }
192
193 $all_blocks = parse_blocks( $post->post_content );
194 $table_blocks = $this->filter_table_blocks( $all_blocks );
195 $block_index = $settings['block_index'] ?? self::ALL_BLOCKS;
196 $blocks_to_render = $all_blocks;
197
198 if ( self::ALL_BLOCKS !== $block_index && '' !== $block_index ) {
199 $index = (int) $block_index;
200 if ( isset( $table_blocks[ $index ] ) ) {
201 $blocks_to_render = array( $table_blocks[ $index ] );
202 }
203 }
204
205 $block_names = $this->collect_block_names( $blocks_to_render );
206
207 $this->print_shared_styles( $block_names );
208 $this->print_table_styles( $table_id, $post->post_content );
209
210 // This widget prints its own table CSS above, so the generic late-CSS
211 // printer (AssetGenerator) must not print it a second time.
212 $asset_generator = class_exists( '\\TableBuilder\\Hooks\\AssetGenerator' ) ? \TableBuilder\Hooks\AssetGenerator::instance() : null;
213 if ( $asset_generator ) {
214 $asset_generator->suspend_late_css();
215 }
216
217 echo '<div class="tablekit-elementor-wrap">';
218 try {
219 foreach ( $blocks_to_render as $block ) {
220 echo ( new WP_Block( $block ) )->render(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- standard core block-rendering output, same as render_block().
221 }
222 } finally {
223 if ( $asset_generator ) {
224 $asset_generator->resume_late_css();
225 }
226 }
227 echo '</div>';
228
229 $this->print_block_scripts( $block_names );
230
231 if ( class_exists( '\\TableBuilder\\Shortcode\\ShortcodeUtils' ) ) {
232 echo \TableBuilder\Shortcode\ShortcodeUtils::get_elementor_init_script(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- static hardcoded <script> string, no user input.
233 }
234 }
235
236 /**
237 * No-op: this widget renders server-side only (render() above), Elementor's
238 * JS live-preview template is intentionally unused.
239 *
240 * @return void
241 */
242 protected function content_template(): void {}
243
244 // -------------------------------------------------------------------------
245 // Private helpers
246 // -------------------------------------------------------------------------
247
248 /**
249 * Renders a simple placeholder message in place of the table (used when no
250 * table is selected, or the selected table post can't be found).
251 *
252 * @param string $message Message to display.
253 * @return void
254 */
255 private function render_placeholder( string $message ): void {
256 echo '<div class="tablekit-el-placeholder" style="padding:1.5em;border:1px dashed #ccc;text-align:center;color:#999;">'
257 . '<span class="eicon-table" style="font-size:2em;display:block;margin-bottom:.5em;opacity:.35;"></span>'
258 . '<p style="margin:0;">' . esc_html( $message ) . '</p>'
259 . '</div>';
260 }
261
262 /**
263 * Narrow a parsed block tree down to just the plugin's table blocks.
264 *
265 * @param array $blocks Parsed block tree (as from parse_blocks()).
266 * @return array Table blocks only.
267 */
268 private function filter_table_blocks( array $blocks ): array {
269 return ShortcodeUtils::filter_table_blocks( $blocks );
270 }
271
272 /**
273 * Prints shared CSS once per page load.
274 *
275 * @param string[] $block_names Block names appearing in this render, used to
276 * additionally print each block's registered style once.
277 * @return void
278 */
279 private function print_shared_styles( array $block_names ): void {
280 $css = '';
281
282 if ( ! self::$global_styles_printed ) {
283 foreach ( array( 'global.css', 'components.css' ) as $filename ) {
284 $path = TABLE_BUILDER_BLOCK_PLUGIN_DIR . 'build/tablebuilder/' . $filename;
285 if ( file_exists( $path ) ) {
286 $css .= file_get_contents($path) . "\n"; // phpcs:ignore
287 }
288 }
289
290 $css .= $this->get_registered_style_css( 'tablebuilder/table-builder' );
291 self::$global_styles_printed = true;
292 self::$printed_block_styles['tablebuilder/table-builder'] = true;
293 }
294
295 foreach ( $block_names as $name ) {
296 if ( isset( self::$printed_block_styles[ $name ] ) ) {
297 continue;
298 }
299 $css .= $this->get_registered_style_css( $name );
300 self::$printed_block_styles[ $name ] = true;
301 }
302
303 if ( '' !== trim( $css ) ) {
304 echo '<style id="tablekit-shared-styles">' . $css . '</style>'; // phpcs:ignore
305 }
306 }
307
308 /**
309 * Prints per-table dynamic CSS. Uses a unique ID per table so multiple widgets coexist.
310 *
311 * @param int $table_id Table post ID, used to build a unique <style> element ID.
312 * @param string $post_content Raw post content to collect block-generated CSS from.
313 * @return void
314 */
315 private function print_table_styles( int $table_id, string $post_content ): void {
316 if ( ! class_exists( '\\TableBuilder\\Shortcode\\Shortcode' ) ) {
317 return;
318 }
319
320 $shortcode = \TableBuilder\Shortcode\Shortcode::instance();
321
322 if ( ! $shortcode || ! method_exists( $shortcode, 'collect_blocks_css_from_content' ) ) {
323 return;
324 }
325
326 $css = $shortcode->collect_blocks_css_from_content( $post_content );
327
328 if ( '' !== trim( $css ) ) {
329 echo '<style id="tablekit-el-css-' . esc_attr((string) $table_id) . '">' . $css . '</style>'; // phpcs:ignore
330 }
331 }
332
333 /**
334 * Enqueues and immediately prints block view-scripts inline.
335 * Required for the editor iframe where wp_footer never fires.
336 *
337 * @param string[] $block_names Block names to print view scripts for, in addition
338 * to the base "tablebuilder/table-builder" script.
339 * @return void
340 */
341 private function print_block_scripts( array $block_names ): void {
342 // FIX 8: only print once per full or partial render cycle.
343 static $printed = array();
344
345 foreach ( array_unique( array_merge( array( 'tablebuilder/table-builder' ), $block_names ) ) as $name ) {
346 $handle = \TableBuilder\Shortcode\ShortcodeUtils::get_block_asset_handle( $name, 'viewScript' );
347 if ( isset( $printed[ $handle ] ) ) {
348 continue;
349 }
350 wp_enqueue_script( $handle );
351 wp_print_scripts( $handle );
352 $printed[ $handle ] = true;
353 }
354 }
355
356 /**
357 * Reads the CSS file of a registered WP stylesheet handle.
358 *
359 * @param string $block_name Block name to resolve the registered "style" asset handle for.
360 * @return string The stylesheet's contents, or an empty string if not found/registered.
361 */
362 private function get_registered_style_css( string $block_name ): string {
363 $handle = \TableBuilder\Shortcode\ShortcodeUtils::get_block_asset_handle( $block_name, 'style' );
364 $style = wp_styles()->query( $handle, 'registered' );
365
366 if ( ! $style || empty( $style->src ) ) {
367 return '';
368 }
369
370 $path = str_replace( content_url(), WP_CONTENT_DIR, (string) $style->src );
371 $path = (string) strtok( $path, '?' );
372
373 if ( ! file_exists( $path ) ) {
374 return '';
375 }
376
377 return file_get_contents($path) . "\n"; // phpcs:ignore
378 }
379
380 /**
381 * Recursively collects unique block names from a block tree.
382 *
383 * @param array $blocks Parsed block tree.
384 * @return string[] Unique block names found in the tree.
385 */
386 private function collect_block_names( array $blocks ): array {
387 $all = ShortcodeUtils::collect_blocks_recursive(
388 $blocks,
389 static fn( array $block ): bool => is_string( $block['blockName'] ?? null ) && '' !== $block['blockName']
390 );
391
392 return array_values( array_unique( array_column( $all, 'blockName' ) ) );
393 }
394
395 /**
396 * Builds the SELECT2 options array for the table picker control (published
397 * tablekit_table posts plus any inline-block source posts), memoized per request.
398 *
399 * @return array<string,string> Options keyed by post ID (as a string), valued by label.
400 */
401 private function get_table_options(): array {
402 if ( is_array( self::$table_options_cache ) ) {
403 return self::$table_options_cache;
404 }
405
406 $options = array( '' => __( '-- Select a Table --', 'table-builder-block' ) );
407
408 $tables = get_posts(
409 array(
410 'post_type' => 'tablekit_table',
411 'post_status' => 'publish',
412 'posts_per_page' => -1,
413 'orderby' => 'title',
414 'order' => 'ASC',
415 )
416 );
417
418 foreach ( $tables as $table ) {
419 $options[ (string) $table->ID ] = esc_html( $table->post_title );
420 }
421
422 if ( class_exists( '\\TableBuilder\\Config\\CPT\\TableCPT' ) ) {
423 $inline = \TableBuilder\Config\CPT\TableCPT::instance()->get_inline_table_source_options();
424 foreach ( $inline as $id => $label ) {
425 $key = (string) $id;
426 if ( ! isset( $options[ $key ] ) ) {
427 $options[ $key ] = esc_html( $label );
428 }
429 }
430 }
431
432 self::$table_options_cache = $options;
433
434 return $options;
435 }
436 }
437