PluginProbe
TableKit – WordPress Table Builder for Data Tables, WooCommerce Product Tables & Post Tables / 2.2.13
TableKit – WordPress Table Builder for Data Tables, WooCommerce Product Tables & Post Tables v2.2.13
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 / Helpers / Utils.php

Utils.php in TableKit – WordPress Table Builder for Data Tables, WooCommerce Product Tables & Post Tables 2.2.13, at includes/Helpers/Utils.php

1,428 lines 38.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * General-purpose helper methods shared across the plugin
4 *
5 * @package TableKit
6 */
7
8 namespace TableBuilder\Helpers;
9
10 defined( 'ABSPATH' ) || exit;
11
12 /**
13 * Global helper class.
14 *
15 * @since 1.0.0
16 */
17 class Utils {
18
19 /**
20 * Returns an array of allowed HTML tags and attributes for SVG elements.
21 *
22 * @return array The array of allowed HTML tags and attributes.
23 */
24 public static function svg_allowed_html() {
25 $allowed_svg_tags = array(
26 'svg' => array(
27 'xmlns' => true,
28 'width' => true,
29 'height' => true,
30 'viewBox' => true,
31 'viewbox' => true,
32 'fill' => true,
33 'class' => true,
34 'aria-hidden' => true,
35 'aria-labelledby' => true,
36 'role' => true,
37 'preserveaspectratio' => true,
38 'version' => true,
39 ),
40 'title' => array( 'title' => true ),
41 'g' => array(
42 'transform' => true,
43 'style' => true,
44 'id' => true,
45 ),
46 'path' => array(
47 'd' => true,
48 'fill' => true,
49 'fill-rule' => true,
50 'transform' => true,
51 'style' => true,
52 'opacity' => true,
53 'stroke' => true,
54 'stroke-width' => true,
55 'stroke-miterlimit' => true,
56 'stroke-linecap' => true,
57 'stroke-linejoin' => true,
58 'fill-opacity' => true,
59 ),
60 'circle' => array(
61 'cx' => true,
62 'cy' => true,
63 'r' => true,
64 'fill' => true,
65 'stroke' => true,
66 'stroke-width' => true,
67 ),
68 'ellipse' => array(
69 'cx' => true,
70 'cy' => true,
71 'rx' => true,
72 'ry' => true,
73 'fill' => true,
74 'stroke' => true,
75 'stroke-width' => true,
76 ),
77 'line' => array(
78 'x1' => true,
79 'y1' => true,
80 'x2' => true,
81 'y2' => true,
82 'stroke' => true,
83 'stroke-width' => true,
84 ),
85 'polygon' => array(
86 'points' => true,
87 'fill' => true,
88 'stroke' => true,
89 'stroke-width' => true,
90 ),
91 'polyline' => array(
92 'points' => true,
93 'fill' => true,
94 'stroke' => true,
95 'stroke-width' => true,
96 ),
97 'rect' => array(
98 'x' => true,
99 'y' => true,
100 'width' => true,
101 'height' => true,
102 'fill' => true,
103 'stroke' => true,
104 'stroke-width' => true,
105 ),
106 'text' => array(
107 'x' => true,
108 'y' => true,
109 'dx' => true,
110 'dy' => true,
111 'text-anchor' => true,
112 'style' => true,
113 ),
114 'tspan' => array(
115 'x' => true,
116 'y' => true,
117 'dx' => true,
118 'dy' => true,
119 'text-anchor' => true,
120 'style' => true,
121 ),
122 'defs' => array(),
123 'lineargradient' => array(
124 'id' => true,
125 'x1' => true,
126 'y1' => true,
127 'x2' => true,
128 'y2' => true,
129 'gradientunits' => true,
130 ),
131 'stop' => array(
132 'offset' => true,
133 'style' => true,
134 'stop-color' => true,
135 'stop-opacity' => true,
136 ),
137 'radialgradient' => array(
138 'id' => true,
139 'cx' => true,
140 'cy' => true,
141 'r' => true,
142 'gradientunits' => true,
143 'gradienttransform' => true,
144 ),
145 );
146
147 /**
148 * Filters the allowed SVG tags and attributes used when sanitizing block output.
149 *
150 * @since Unknown
151 *
152 * @param array $allowed_svg_tags Allowed SVG elements and their attributes, keyed by tag name.
153 */
154 return apply_filters( 'table_builder_allowed_svg_attrs_tags', $allowed_svg_tags );
155 }
156
157 /**
158 * Returns an array of allowed JSON attribute tags.
159 *
160 * This function defines an array of allowed JSON attribute tags and their corresponding properties.
161 * The array includes tags such as 'object', 'array', 'string', 'number', 'integer', 'boolean', 'null',
162 * 'enum', 'const', 'oneOf', 'allOf', 'anyOf', 'not', 'if', 'then', 'else', and 'format'.
163 *
164 * @return array The array of allowed JSON attribute tags.
165 */
166 public static function allowed_json_attrs_tags() {
167 $allowed_json_tags = array(
168 'object' => array(
169 'type' => true,
170 'properties' => true,
171 'required' => true,
172 'additionalProperties' => true,
173 'propertyNames' => true,
174 'dependencies' => true,
175 'minProperties' => true,
176 'maxProperties' => true,
177 ),
178 'array' => array(
179 'type' => true,
180 'items' => true,
181 'minItems' => true,
182 'maxItems' => true,
183 'uniqueItems' => true,
184 ),
185 'string' => array(
186 'type' => true,
187 'minLength' => true,
188 'maxLength' => true,
189 'pattern' => true,
190 'format' => true,
191 'contentEncoding' => true,
192 'contentMediaType' => true,
193 ),
194 'number' => array(
195 'type' => true,
196 'minimum' => true,
197 'maximum' => true,
198 'exclusiveMinimum' => true,
199 'exclusiveMaximum' => true,
200 'multipleOf' => true,
201 ),
202 'integer' => array(
203 'type' => true,
204 'minimum' => true,
205 'maximum' => true,
206 'exclusiveMinimum' => true,
207 'exclusiveMaximum' => true,
208 ),
209 'boolean' => array(
210 'type' => true,
211 ),
212 'null' => array(
213 'type' => true,
214 ),
215 'enum' => array(
216 'type' => true,
217 'enum' => true,
218 ),
219 'const' => array(
220 'type' => true,
221 'const' => true,
222 ),
223 'oneOf' => array(
224 'type' => true,
225 'oneOf' => true,
226 ),
227 'allOf' => array(
228 'type' => true,
229 'allOf' => true,
230 ),
231 'anyOf' => array(
232 'type' => true,
233 'anyOf' => true,
234 ),
235 'not' => array(
236 'type' => true,
237 'not' => true,
238 ),
239 'if' => array(
240 'type' => true,
241 'if' => true,
242 ),
243 'then' => array(
244 'type' => true,
245 'then' => true,
246 ),
247 'else' => array(
248 'type' => true,
249 'else' => true,
250 ),
251 'format' => array(
252 'type' => true,
253 'format' => true,
254 ),
255 'title' => true,
256 'description' => true,
257 'default' => true,
258 'examples' => true,
259 '$ref' => true,
260 '$id' => true,
261 '$schema' => true,
262 // Adding Lottie-specific keys.
263 'v' => true,
264 'meta' => true,
265 'fr' => true,
266 'ip' => true,
267 'op' => true,
268 'w' => true,
269 'h' => true,
270 'nm' => true,
271 'ddd' => true,
272 'assets' => true,
273 'layers' => true,
274 'markers' => true,
275 );
276
277 /**
278 * Filters the allowed JSON Schema/Lottie attribute keys used when sanitizing block output.
279 *
280 * @since Unknown
281 *
282 * @param array $allowed_json_tags Allowed JSON keys, keyed by key name.
283 */
284 return apply_filters( 'table_builder_allowed_json_attrs_tags', $allowed_json_tags );
285 }
286
287 /**
288 * Returns the allowed HTML tags and attributes for the iframe element.
289 *
290 * @return array The allowed HTML tags and attributes.
291 */
292 public static function iframe_allowed_html() {
293 return array(
294 'iframe' => array(
295 'src' => true,
296 'name' => true,
297 'sandbox' => true,
298 'width' => true,
299 'height' => true,
300 'marginheight' => true,
301 'marginwidth' => true,
302 'scrolling' => true,
303 'allowfullscreen' => true,
304 'frameborder' => true,
305 'title' => true,
306 'id' => true,
307 'class' => true,
308 'style' => true,
309 'tabindex' => true,
310 'allow' => true,
311 ),
312 );
313 }
314
315 /**
316 * Returns the allowed HTML tags and attributes for the "gdc" (GenerateBlocks
317 * dynamic content) shortcode-style tag used in migrated content.
318 *
319 * @return array The allowed HTML tags and attributes.
320 */
321 public static function gdc_allowed_html() {
322 return array(
323 'gdc' => array(
324 'selectedpath' => true,
325 'class' => true,
326 'id' => true,
327 'fallback' => true,
328 'postcustomfield' => true,
329 'postcustomfieldkey' => true,
330 'postdatetype' => true,
331 'dateformat' => true,
332 'customdateformat' => true,
333 'excerptlength' => true,
334 'tagindex' => true,
335 'timetype' => true,
336 'timeformat' => true,
337 'customtimeformat' => true,
338 'categoryindex' => true,
339 'nocomment' => true,
340 'singlecomment' => true,
341 'multicomments' => true,
342 'currentdateformat' => true,
343 'customcurrentdateformat' => true,
344 'currenttimeformat' => true,
345 'customcurrenttimeformat' => true,
346 'authorinfo' => true,
347 'currentuserinfo' => true,
348 'acfgroup' => true,
349 'acffield' => true,
350 ),
351 );
352 }
353
354 /**
355 * Returns the allowed HTML tags and attributes for the img element.
356 *
357 * @return array The allowed HTML tags and attributes.
358 */
359 public static function img_allowed_html() {
360 return array(
361 'img' => array(
362 'alt' => true,
363 'src' => true,
364 'srcset' => true,
365 'class' => true,
366 'height' => true,
367 'width' => true,
368 ),
369 );
370 }
371
372 /**
373 * Returns the allowed HTML tags and attributes for the style element.
374 *
375 * @return array The allowed HTML tags and attributes.
376 */
377 public static function style_allowed_html() {
378 return array(
379 'style' => array(
380 'class' => true,
381 'id' => true,
382 ),
383 );
384 }
385
386 /**
387 * Returns the default responsive breakpoint list (Desktop/Tablet/Mobile)
388 * used across the block editor's responsive controls.
389 *
390 * @return array[] List of device descriptors (label/slug/value/direction/isActive/isRequired).
391 */
392 public static function get_device_list() {
393 $default_device_list = array(
394 array(
395 'label' => 'Desktop',
396 'slug' => 'Desktop',
397 'value' => 'base',
398 'direction' => 'max',
399 'isActive' => true,
400 'isRequired' => true,
401 ),
402 array(
403 'label' => 'Tablet',
404 'slug' => 'Tablet',
405 'value' => '1024',
406 'direction' => 'max',
407 'isActive' => true,
408 'isRequired' => true,
409 ),
410 array(
411 'label' => 'Mobile',
412 'slug' => 'Mobile',
413 'value' => '767',
414 'direction' => 'max',
415 'isActive' => true,
416 'isRequired' => true,
417 ),
418 );
419
420 return $default_device_list;
421 }
422
423 /**
424 * Injects a `class="table-builder-icon"` attribute into a raw `<svg ...>` string.
425 *
426 * @since 1.0.0
427 *
428 * @param string $svg Raw SVG markup.
429 * @return string The SVG markup with the class attribute injected, or unchanged if no `<svg` tag is found.
430 */
431 public static function add_class_to_svg( $svg ) {
432 $original_string = $svg;
433 $substring_to_add = "class='table-builder-icon' ";
434
435 $position = strpos( $original_string, '<svg' );
436
437 if ( false !== $position ) {
438 $svg = substr_replace( $original_string, $substring_to_add, $position + 5, 0 );
439 return $svg;
440 }
441
442 return $svg;
443 }
444
445 /**
446 * Retrieves the dynamic block wrapper attributes.
447 *
448 * This function retrieves the wrapper attributes for a dynamic block.
449 * It checks if the function "get_block_wrapper_attributes" exists and if the block is not empty.
450 * If both conditions are met, it retrieves the block attributes, including the block ID.
451 * It then merges the required attributes with any extra attributes provided.
452 * Finally, it applies the "tablebuilder/dynamic_block_wrapper_attributes" filter and returns the wrapper attributes.
453 *
454 * @param object $block The dynamic block object.
455 * @param array $extra_attrs Additional attributes to be merged with the required attributes.
456 * @return string The wrapper attributes for the dynamic block.
457 */
458 public static function get_dynamic_block_wrapper_attributes( $block, $extra_attrs = array() ) {
459 if ( function_exists( 'get_block_wrapper_attributes' ) && ! empty( $block ) ) {
460 $block_attrs = $block->attributes;
461 $block_id = $block_attrs['blockID'];
462 $required_attrs = array(
463 'id' => 'block-' . $block_id,
464 );
465 /**
466 * Filters a dynamic block's wrapper attributes before they're rendered.
467 *
468 * @since Unknown
469 *
470 * @param array $wrapper_attrs Merged required + extra wrapper attributes (e.g. "id").
471 * @param object $block The dynamic block object.
472 */
473 // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores -- slash-namespaced hook name is this plugin's established public API (used by the Pro add-on); renaming would be a breaking change.
474 $wrapper_attrs = apply_filters( 'tablebuilder/dynamic_block_wrapper_attributes', array_merge( $required_attrs, $extra_attrs ), $block );
475 return get_block_wrapper_attributes( $wrapper_attrs );
476 }
477 }
478
479 /**
480 * Extends the allowed HTML tags for post content.
481 *
482 * @return array The extended array of allowed HTML tags.
483 */
484 public static function post_kses_extend_allowed_html() {
485 $default_post_allowed_html = wp_kses_allowed_html( 'post' );
486
487 $post_allowed_html = array_merge( $default_post_allowed_html, self::svg_allowed_html(), self::iframe_allowed_html(), self::gdc_allowed_html(), self::style_allowed_html() );
488
489 return $post_allowed_html;
490 }
491
492 /**
493 * Retrieves the plugin's settings, or a specific key/field/inner-field within them,
494 * from the "table_builder_settings_list" option.
495 *
496 * @param string $key Optional. Top-level settings key to look up.
497 * @param string $field Optional. Field within that key to look up; defaults to "status"
498 * (returned as a bool, true when its value is "active").
499 * @param string $inner_field Optional. Nested field within $field to look up.
500 * @return mixed The retrieved settings value, or false if not found.
501 */
502 public static function get_settings( $key = '', $field = 'status', $inner_field = '' ) {
503 $settings = get_option( 'table_builder_settings_list' );
504
505 // check if $key & $field both empty.
506 if ( empty( $key ) && empty( $field ) ) {
507 return $settings;
508 }
509
510 // check for primary key.
511 if ( ! empty( $key ) && ! empty( $settings[ $key ] ) ) {
512 $settings = $settings[ $key ];
513 }
514
515 // check for primary field.
516 if ( ! empty( $field ) ) {
517 if ( 'status' === $field && ! empty( $settings[ $field ] ) ) {
518 return ( 'active' === $settings[ $field ] ) ? true : false;
519 } else {
520 $settings = ! empty( $settings[ $field ] ) ? $settings[ $field ] : false;
521 }
522 }
523
524 // check for inner field.
525 if ( ! empty( $inner_field ) ) {
526 if ( isset( $settings[ $inner_field ]['value'] ) ) {
527 $settings = $settings[ $inner_field ]['value'];
528 } else {
529 $settings = ! empty( $settings[ $inner_field ] ) ? $settings[ $inner_field ] : false;
530 }
531 }
532
533 return $settings;
534 }
535
536 /**
537 * Returns an array representing the border value based on the given key.
538 *
539 * @param mixed $key The key used to determine the border value.
540 * @return array An array representing the border value. The array contains the following keys:
541 * - 'border': The border value, or null if the key is null.
542 */
543 public static function get_border_value( $key ) {
544
545 if ( ! is_array( $key ) ) {
546 return array( 'border' => null );
547 }
548
549 $key_length = count( $key );
550
551 if ( $key_length < 3 ) {
552 $properties = array( 'style', 'color', 'width' );
553 $border_parts = array();
554
555 foreach ( $properties as $property ) {
556 if ( isset( $key[ $property ] ) ) {
557 $border_parts[] = $key[ $property ];
558 }
559 }
560
561 return array( 'border' => implode( ' ', $border_parts ) );
562
563 }
564
565 if ( 3 === $key_length ) {
566 if ( isset( $key['style'] ) ) {
567 return array( 'border' => "{$key['width']} {$key['style']} {$key['color']}" );
568 }
569 }
570
571 if ( 4 === $key_length || 3 === $key_length ) {
572 $border = array();
573 foreach ( $key as $direction => $value ) {
574 if ( isset( $value['style'] ) ) {
575 $border[ "border-{$direction}" ] = "{$value['width']} {$value['style']} {$value['color']}";
576 }
577 }
578 return $border;
579 }
580 }
581
582 /**
583 * Builds a CSS box-model value (margin/padding/border-radius style) from a
584 * {top,right,bottom,left} array, collapsing to a shorthand string when all
585 * four sides share values.
586 *
587 * @param array $key Box values keyed by 'top'/'right'/'bottom'/'left'.
588 * @param string $property CSS property prefix (e.g. "margin", "padding"), or
589 * "border-radius" for the corner-radius longhand variant.
590 * @return array CSS declarations keyed by property name (shorthand or per-side longhand).
591 */
592 public static function get_box_value( $key = array(), $property = '' ) {
593 $top = isset( $key['top'] ) ? $key['top'] : null;
594 $right = isset( $key['right'] ) ? $key['right'] : null;
595 $bottom = isset( $key['bottom'] ) ? $key['bottom'] : null;
596 $left = isset( $key['left'] ) ? $key['left'] : null;
597
598 $box_object = array(
599 'top' => $top,
600 'right' => $right,
601 'bottom' => $bottom,
602 'left' => $left,
603 );
604 $count = count(
605 array_filter(
606 $box_object,
607 function ( $value ) {
608 return null !== $value;
609 }
610 )
611 );
612
613 if ( 0 === $count ) {
614 return array( $property => null );
615 }
616
617 if ( 4 === $count ) {
618 $box_values = '';
619
620 if ( $top === $bottom && $top === $right && $top === $left ) {
621 $box_values = $top;
622 } elseif ( $top === $bottom && $left === $right ) {
623 $box_values = "{$top} {$right}";
624 } else {
625 $box_values = "{$top} {$right} {$bottom} {$left}";
626 }
627
628 return array( $property => $box_values );
629 }
630
631 $final_box = array();
632
633 if ( 'border-radius' !== $property ) {
634 foreach ( $box_object as $direction => $value ) {
635 $final_box[ "{$property}-{$direction}" ] = isset( $key[ $direction ] ) ? $key[ $direction ] : null;
636 }
637
638 return $final_box;
639 }
640
641 if ( 'border-radius' === $property ) {
642 $final_box['border-top-left-radius'] = $top;
643 $final_box['border-top-right-radius'] = $right;
644 $final_box['border-bottom-right-radius'] = $bottom;
645 $final_box['border-bottom-left-radius'] = $left;
646
647 return $final_box;
648 }
649 }
650
651 /**
652 * Retrieves the color based on the given type and color.
653 *
654 * @param string $type The type of color to retrieve. Can be either 'gradient' or 'color'.
655 * @param string $color The color value.
656 * @return string The retrieved color.
657 */
658 public static function get_color( $type, $color ) {
659
660 if ( empty( $color ) || 0 === strpos( $color, 'linear-gradient(' ) || 0 === strpos( $color, 'radial-gradient(' ) || 0 === strpos( $color, '#' ) ) {
661 return $color;
662 }
663
664 $color_parts = explode( ',', $color, 2 );
665
666 $color_parts1 = $color_parts[0];
667 $color_parts2 = $color_parts[1];
668
669 $bg_color = '';
670
671 if ( 'gradient' === $type ) {
672 $bg_color = 'var(--wp--preset--gradient--' . $color_parts1 . ',' . $color_parts2 . ')';
673
674 } else {
675 $bg_color = 'var(--wp--preset--color--' . $color_parts1 . ',' . $color_parts2 . ')';
676 }
677 return $bg_color;
678 }
679
680 /**
681 * Builds CSS background-* declarations (color, gradient, or image with
682 * position/size/repeat/attachment) from a block's background attribute object.
683 *
684 * @param array $background Background attribute object (backgroundType plus the
685 * relevant color/gradient/image sub-fields).
686 * @param string $device Responsive device key used for position/size sub-fields; default "Desktop".
687 * @return array CSS declarations keyed by "background-*" property name.
688 */
689 public static function fill_background_generator( $background, $device = 'Desktop' ) {
690
691 $fill_background = array(
692 'background-image' => '',
693 );
694
695 if ( isset( $background['backgroundType'] ) && 'classic' === $background['backgroundType'] ) {
696 $fill_background['background-color'] = isset( $background['backgroundColor'] ) ? self::get_color( 'color', $background['backgroundColor'] ) : '';
697 }
698
699 if ( isset( $background['backgroundType'] ) && 'gradient' === $background['backgroundType'] && ! empty( $background['gradient'] ) ) {
700
701 $fill_background['background-image'] = isset( $background['gradient'] ) ? ( self::get_color( 'gradient', $background['gradient'] ) ?? '' ) : '';
702 }
703
704 if ( isset( $background['backgroundType'] ) && 'image' === $background['backgroundType'] && ! empty( $background['backgroundImage'] ) ) {
705 if ( ! empty( $background['backgroundImage']['imageUrl'] ) ) {
706 $fill_background['background-image'] = "url({$background['backgroundImage']['imageUrl']})";
707 }
708
709 if ( ! empty( $background['backgroundAttachment'] ) ) {
710 $fill_background['background-attachment'] = $background['backgroundAttachment'];
711 }
712
713 if ( ! empty( $background['backgroundPosition'][ $device ] ) && 'custom' !== $background['backgroundPosition'][ $device ] ) {
714 $fill_background['background-position'] = $background['backgroundPosition'][ $device ];
715 }
716
717 if (
718 ! empty( $background['backgroundPosition'][ $device ] ) && 'custom' === $background['backgroundPosition'][ $device ] &&
719 ! empty( $background['customPositionX'][ $device ] ) && ! empty( $background['customPositionY'][ $device ] )
720 ) {
721 $fill_background['background-position'] = self::get_slider_value( $background['customPositionX'][ $device ] ) . ' ' . self::get_slider_value( $background['customPositionY'][ $device ] );
722 }
723
724 if ( ! empty( $background['backgroundSize'] ) && 'custom' !== $background['backgroundSize'] ) {
725 $fill_background['background-size'] = $background['backgroundSize'];
726 }
727
728 if (
729 ! empty( $background['backgroundSize'] ) && 'custom' === $background['backgroundSize'] &&
730 ! empty( $background['customSize'][ $device ] )
731 ) {
732 $fill_background['background-size'] = self::get_slider_value( $background['customSize'][ $device ] ) . ' auto';
733 }
734
735 if ( ! empty( $background['backgroundRepeat'] ) ) {
736 $fill_background['background-repeat'] = $background['backgroundRepeat'];
737 }
738 }
739
740 return $fill_background;
741 }
742
743 /**
744 * Builds CSS font-, text-, line-height, letter-spacing, and word-spacing
745 * declarations from a typography attribute object for the given responsive device.
746 *
747 * @param array $key Typography attribute object (fontFamily/fontSize/fontStyle/
748 * fontWeight/textDecoration/textTransform/lineHeight/letterSpacing/wordSpacing).
749 * @param string $device Responsive device key (e.g. "Desktop"); non-Desktop devices
750 * omit font-family/style/weight/text-decoration/text-transform,
751 * which are treated as desktop-only.
752 * @return array CSS declarations keyed by property name.
753 */
754 public static function get_typography_value( $key, $device ) {
755 if ( 'Desktop' === $device ) {
756 return array(
757 'font-family' => isset( $key['fontFamily']['value'] ) ? $key['fontFamily']['value'] : null,
758 'font-size' => isset( $key['fontSize'][ $device ]['size'] ) && isset( $key['fontSize'][ $device ]['unit'] )
759 ? $key['fontSize'][ $device ]['size'] . $key['fontSize'][ $device ]['unit']
760 : null,
761 'font-style' => isset( $key['fontStyle'] ) ? $key['fontStyle'] : null,
762 'font-weight' => isset( $key['fontWeight']['value'] ) ? $key['fontWeight']['value'] : null,
763 'text-decoration' => isset( $key['textDecoration'] ) ? $key['textDecoration'] : null,
764 'text-transform' => isset( $key['textTransform'] ) ? $key['textTransform'] : null,
765 'line-height' => isset( $key['lineHeight'][ $device ]['size'] ) && isset( $key['lineHeight'][ $device ]['unit'] )
766 ? $key['lineHeight'][ $device ]['size'] . $key['lineHeight'][ $device ]['unit']
767 : null,
768 'letter-spacing' => isset( $key['letterSpacing'][ $device ]['size'] ) && isset( $key['letterSpacing'][ $device ]['unit'] )
769 ? $key['letterSpacing'][ $device ]['size'] . $key['letterSpacing'][ $device ]['unit']
770 : null,
771 'word-spacing' => isset( $key['wordSpacing'][ $device ]['size'] ) && isset( $key['wordSpacing'][ $device ]['unit'] )
772 ? $key['wordSpacing'][ $device ]['size'] . $key['wordSpacing'][ $device ]['unit']
773 : null,
774 );
775 } else {
776 return array(
777 'font-size' => isset( $key['fontSize'][ $device ]['size'] ) && isset( $key['fontSize'][ $device ]['unit'] )
778 ? $key['fontSize'][ $device ]['size'] . $key['fontSize'][ $device ]['unit']
779 : null,
780 'line-height' => isset( $key['lineHeight'][ $device ]['size'] ) && isset( $key['lineHeight'][ $device ]['unit'] )
781 ? $key['lineHeight'][ $device ]['size'] . $key['lineHeight'][ $device ]['unit']
782 : null,
783 'letter-spacing' => isset( $key['letterSpacing'][ $device ]['size'] ) && isset( $key['letterSpacing'][ $device ]['unit'] )
784 ? $key['letterSpacing'][ $device ]['size'] . $key['letterSpacing'][ $device ]['unit']
785 : null,
786 'word-spacing' => isset( $key['wordSpacing'][ $device ]['size'] ) && isset( $key['wordSpacing'][ $device ]['unit'] )
787 ? $key['wordSpacing'][ $device ]['size'] . $key['wordSpacing'][ $device ]['unit']
788 : null,
789 );
790 }
791 }
792
793 /**
794 * Gets slider value.
795 *
796 * Similar to getSliderValue function in JS helper.
797 *
798 * @param array $key Slider value array with "size" and "unit" keys.
799 * @return string|null The combined "{size}{unit}" string, or null if no size is set.
800 */
801 public static function get_slider_value( $key ) {
802 $value = '';
803
804 if ( ! empty( $key['size'] ) && ! empty( $key['unit'] ) ) {
805 $value = $key['size'] . $key['unit'];
806 } elseif ( ! empty( $key['size'] ) && empty( $key['unit'] ) ) {
807 $value = $key['size'];
808 } else {
809 $value = null;
810 }
811
812 return $value;
813 }
814
815 /**
816 * Converts a per-device map of {selector, ...cssValues} style records into
817 * per-device CSS text blocks.
818 *
819 * @param array $raw_css Style records keyed by device slug (desktop/tablet/mobile/etc.),
820 * each an array of {selector, ...property=>value} entries.
821 * @return array<string,string> Compiled CSS text keyed by device slug.
822 */
823 public static function parse_css( $raw_css ) {
824
825 $styles = array();
826 $device_list = array( 'desktop', 'tablet', 'mobile', 'tabletlandscape', 'mobilelandscape', 'laptop', 'widescreen' );
827
828 foreach ( $device_list as $device ) {
829 $device_styles = $raw_css[ $device ] ?? array();
830
831 $styles[ $device ] = array_map(
832 function ( $style ) {
833 if ( ! is_array( $style ) || ! isset( $style['selector'] ) ) {
834 return '';
835 }
836
837 $selector = $style['selector'];
838 $css_values = array_filter(
839 $style,
840 function ( $value, $key ) {
841 return 'selector' !== $key && null !== $value && '' !== $value && ! is_numeric( $value ) && ! in_array( $value, array( 'px', 'em', 'rem', '%', 'vh', 'vw' ), true ) && false === strpos( $value, 'undefined' );
842 },
843 ARRAY_FILTER_USE_BOTH
844 );
845
846 if ( empty( $css_values ) ) {
847 return '';
848 }
849
850 return "{$selector} { " . implode(
851 ' ',
852 array_map(
853 function ( $key, $value ) {
854 return "{$key}: {$value};";
855 },
856 array_keys( $css_values ),
857 $css_values
858 )
859 ) . ' }';
860 },
861 $device_styles
862 );
863 }
864
865 $device_styles = array_map(
866 function ( $style ) {
867 return implode( "\n", $style );
868 },
869 $styles
870 );
871
872 return $device_styles;
873 }
874
875 /**
876 * Checks whether a parsed block is one of this plugin's "tablebuilder/*" blocks,
877 * optionally requiring a specific (possibly nested) attribute to be present.
878 *
879 * @param string $block_content Rendered block HTML content.
880 * @param array $parsed_block Parsed block array (as passed to a `render_block` callback).
881 * @param string $attrs Optional. Top-level attribute key to require.
882 * @param string $attrs2 Optional. Nested key within $attrs to require.
883 * @return bool True if this is a table-builder block matching the given criteria.
884 */
885 public static function is_table_builder_block( $block_content, $parsed_block, $attrs = '', $attrs2 = '' ) {
886 // phpcs:ignore Squiz.PHP.CommentedOutCode.Found -- explanatory prose comments, not commented-out code.
887 // Check if $block_content is not empty.
888 $has_block_content = ! empty( $block_content );
889 // Check if $block['blockName'] is not empty and contains 'roxSlider'.
890 $has_valid_block_name = isset( $parsed_block['blockName'] ) && false !== strpos( $parsed_block['blockName'], 'tablebuilder' );
891
892 // Check if $block['attrs']['blockClass'] is not empty.
893 $has_block_class = ! empty( $attrs ) && ! empty( $attrs2 )
894 ? ! empty( $parsed_block['attrs'][ $attrs ][ $attrs2 ] )
895 : ! empty( $parsed_block['attrs'][ $attrs ] ?? '' );
896
897 if ( empty( $attrs ) && empty( $attrs2 ) ) {
898 $has_block_class = true;
899 }
900
901 // Return true if all conditions are met.
902 return $has_block_content && $has_valid_block_name && $has_block_class;
903 }
904
905 /**
906 * Retrieves the link attributes based on the provided attribute array.
907 *
908 * @param array $attribute The attribute array containing the link data.
909 * @return string The generated link attributes as a string.
910 */
911 public static function get_link_attributes( $attribute ) {
912 if ( empty( $attribute['url'] ) ) {
913 return '';
914 }
915
916 $link_data = array();
917
918 $link_data['href'] = esc_url( $attribute['url'], wp_allowed_protocols() );
919
920 ( isset( $attribute['newTab'] ) && $attribute['newTab'] ) ? $link_data['target'] = '_blank' : '';
921
922 ( isset( $attribute['noFollow'] ) && $attribute['noFollow'] ) ? $link_data['rel'] = 'nofollow' : '';
923
924 if ( isset( $attribute['customAttributes'] ) && is_array( $attribute['customAttributes'] ) ) {
925 foreach ( $attribute['customAttributes'] as $key => $value ) {
926 if ( ! empty( $value ) ) {
927 $attr_key_value = explode( '|', $value );
928
929 $attr_key = mb_strtolower( $attr_key_value[0] );
930
931 // Not allowed characters are removed.
932 preg_match( '/[-_a-z0-9]+/', $attr_key, $attr_key_matches );
933
934 if ( empty( $attr_key_matches[0] ) ) {
935 continue;
936 }
937
938 $attr_key = $attr_key_matches[0];
939
940 // Javascript events and unescaped href are avoided.
941 if ( 'href' === $attr_key || 'on' === substr( $attr_key, 0, 2 ) ) {
942 continue;
943 }
944
945 if ( isset( $attr_key_value[1] ) ) {
946 $attr_value = trim( $attr_key_value[1] );
947 } else {
948 $attr_value = '';
949 }
950
951 $link_data[ $attr_key ] = $attr_value;
952 }
953 }
954 }
955
956 $link_attributes = '';
957 foreach ( $link_data as $key => $value ) {
958 $link_attributes .= sprintf( '%s="%s" ', $key, esc_attr( $value ) );
959 }
960
961 return $link_attributes;
962 }
963
964 /**
965 * Gets the plugin's cached license status ("valid" or "invalid"), based on
966 * whether both the stored license key and license identifier are set.
967 *
968 * @return string "valid" or "invalid".
969 */
970 public static function status() {
971
972 $cached = wp_cache_get( 'table_builder__license_status' );
973
974 if ( false !== $cached ) {
975 return $cached;
976 }
977
978 $oppai = get_option( '__table_builder_oppai__', '' );
979 $key = get_option( '__table_builder_license_key__', '' );
980 $status = 'invalid';
981
982 if ( '' !== $oppai && '' !== $key ) {
983 $status = 'valid';
984 }
985
986 wp_cache_set( 'table_builder__license_status', $status );
987
988 return $status;
989 }
990
991 /**
992 * Checks whether the current request's host's TLD is outside a known list of
993 * public/commercial TLDs — used as a rough heuristic to detect local/dev
994 * environments (e.g. `.test`, `.local`, `.localhost`) for license checks.
995 *
996 * @return bool True if the current host's TLD is NOT in the known public-TLD list
997 * (i.e. it looks like a local/dev environment).
998 */
999 public static function is_local() {
1000 $valid_domains = array(
1001 '.academy',
1002 '.accountant',
1003 '.accountants',
1004 '.actor',
1005 '.adult',
1006 '.africa',
1007 '.agency',
1008 '.airforce',
1009 '.apartments',
1010 '.app',
1011 '.army',
1012 '.art',
1013 '.asia',
1014 '.associates',
1015 '.attorney',
1016 '.auction',
1017 '.audio',
1018 '.auto',
1019 '.baby',
1020 '.band',
1021 '.bar',
1022 '.bargains',
1023 '.beer',
1024 '.berlin',
1025 '.best',
1026 '.bid',
1027 '.bike',
1028 '.bingo',
1029 '.bio',
1030 '.biz',
1031 '.black',
1032 '.blackfriday',
1033 '.blog',
1034 '.blue',
1035 '.boston',
1036 '.boutique',
1037 '.build',
1038 '.builders',
1039 '.business',
1040 '.buzz',
1041 '.cab',
1042 '.cafe',
1043 '.cam',
1044 '.camera',
1045 '.camp',
1046 '.capital',
1047 '.car',
1048 '.cards',
1049 '.care',
1050 '.careers',
1051 '.cars',
1052 '.casa',
1053 '.cash',
1054 '.casino',
1055 '.catering',
1056 '.center',
1057 '.ceo',
1058 '.chat',
1059 '.cheap',
1060 '.christmas',
1061 '.church',
1062 '.city',
1063 '.claims',
1064 '.cleaning',
1065 '.click',
1066 '.clinic',
1067 '.clothing',
1068 '.cloud',
1069 '.club',
1070 '.coach',
1071 '.codes',
1072 '.coffee',
1073 '.college',
1074 '.com',
1075 '.community',
1076 '.company',
1077 '.computer',
1078 '.condos',
1079 '.construction',
1080 '.consulting',
1081 '.contact',
1082 '.contractors',
1083 '.cooking',
1084 '.cool',
1085 '.country',
1086 '.coupons',
1087 '.courses',
1088 '.credit',
1089 '.creditcard',
1090 '.cricket',
1091 '.cruises',
1092 '.cymru',
1093 '.cyou',
1094 '.dance',
1095 '.date',
1096 '.dating',
1097 '.day',
1098 '.deals',
1099 '.degree',
1100 '.delivery',
1101 '.democrat',
1102 '.dental',
1103 '.dentist',
1104 '.desi',
1105 '.design',
1106 '.dev',
1107 '.diamonds',
1108 '.diet',
1109 '.digital',
1110 '.direct',
1111 '.directory',
1112 '.discount',
1113 '.doctor',
1114 '.dog',
1115 '.domains',
1116 '.download',
1117 '.earth',
1118 '.eco',
1119 '.education',
1120 '.email',
1121 '.energy',
1122 '.engineer',
1123 '.engineering',
1124 '.enterprises',
1125 '.equipment',
1126 '.estate',
1127 '.events',
1128 '.exchange',
1129 '.expert',
1130 '.exposed',
1131 '.express',
1132 '.fail',
1133 '.faith',
1134 '.family',
1135 '.fans',
1136 '.farm',
1137 '.fashion',
1138 '.feedback',
1139 '.film',
1140 '.finance',
1141 '.financial',
1142 '.fish',
1143 '.fishing',
1144 '.fit',
1145 '.fitness',
1146 '.flights',
1147 '.florist',
1148 '.flowers',
1149 '.football',
1150 '.forsale',
1151 '.foundation',
1152 '.fun',
1153 '.fund',
1154 '.furniture',
1155 '.futbol',
1156 '.fyi',
1157 '.gallery',
1158 '.game',
1159 '.games',
1160 '.garden',
1161 '.gay',
1162 '.gdn',
1163 '.gift',
1164 '.gifts',
1165 '.gives',
1166 '.glass',
1167 '.global',
1168 '.gmbh',
1169 '.gold',
1170 '.golf',
1171 '.graphics',
1172 '.gratis',
1173 '.green',
1174 '.gripe',
1175 '.group',
1176 '.guide',
1177 '.guitars',
1178 '.guru',
1179 '.hamburg',
1180 '.haus',
1181 '.health',
1182 '.healthcare',
1183 '.help',
1184 '.hiphop',
1185 '.hockey',
1186 '.holdings',
1187 '.holiday',
1188 '.horse',
1189 '.host',
1190 '.hosting',
1191 '.house',
1192 '.how',
1193 '.icu',
1194 '.immo',
1195 '.immobilien',
1196 '.inc',
1197 '.industries',
1198 '.info',
1199 '.ink',
1200 '.institute',
1201 '.insure',
1202 '.international',
1203 '.investments',
1204 '.irish',
1205 '.jetzt',
1206 '.jewelry',
1207 '.juegos',
1208 '.kaufen',
1209 '.kim',
1210 '.kitchen',
1211 '.kiwi',
1212 '.krd',
1213 '.kyoto',
1214 '.land',
1215 '.lat',
1216 '.lawyer',
1217 '.lease',
1218 '.legal',
1219 '.lgbt',
1220 '.life',
1221 '.lighting',
1222 '.limited',
1223 '.limo',
1224 '.link',
1225 '.live',
1226 '.llc',
1227 '.loan',
1228 '.loans',
1229 '.lol',
1230 '.london',
1231 '.love',
1232 '.ltd',
1233 '.ltda',
1234 '.luxury',
1235 '.maison',
1236 '.management',
1237 '.market',
1238 '.marketing',
1239 '.mba',
1240 '.media',
1241 '.melbourne',
1242 '.memorial',
1243 '.men',
1244 '.menu',
1245 '.miami',
1246 '.mobi',
1247 '.moda',
1248 '.moe',
1249 '.mom',
1250 '.money',
1251 '.monster',
1252 '.mortgage',
1253 '.movie',
1254 '.nagoya',
1255 '.name',
1256 '.navy',
1257 '.net',
1258 '.network',
1259 '.new',
1260 '.news',
1261 '.ninja',
1262 '.nyc',
1263 '.observer',
1264 '.okinawa',
1265 '.one',
1266 '.onl',
1267 '.online',
1268 '.org',
1269 '.osaka',
1270 '.page',
1271 '.paris',
1272 '.partners',
1273 '.parts',
1274 '.party',
1275 '.photo',
1276 '.photography',
1277 '.photos',
1278 '.pics',
1279 '.pictures',
1280 '.pink',
1281 '.pizza',
1282 '.place',
1283 '.plumbing',
1284 '.plus',
1285 '.poker',
1286 '.porn',
1287 '.press',
1288 '.pro',
1289 '.productions',
1290 '.properties',
1291 '.property',
1292 '.protection',
1293 '.pub',
1294 '.racing',
1295 '.realty',
1296 '.recipes',
1297 '.red',
1298 '.rehab',
1299 '.reise',
1300 '.reisen',
1301 '.rent',
1302 '.rentals',
1303 '.repair',
1304 '.report',
1305 '.republican',
1306 '.rest',
1307 '.restaurant',
1308 '.review',
1309 '.reviews',
1310 '.rip',
1311 '.rocks',
1312 '.rodeo',
1313 '.run',
1314 '.ryukyu',
1315 '.sale',
1316 '.sarl',
1317 '.school',
1318 '.schule',
1319 '.science',
1320 '.security',
1321 '.services',
1322 '.sex',
1323 '.sexy',
1324 '.shiksha',
1325 '.shoes',
1326 '.shop',
1327 '.shopping',
1328 '.show',
1329 '.singles',
1330 '.site',
1331 '.ski',
1332 '.soccer',
1333 '.social',
1334 '.software',
1335 '.solar',
1336 '.solutions',
1337 '.soy',
1338 '.space',
1339 '.storage',
1340 '.store',
1341 '.stream',
1342 '.studio',
1343 '.study',
1344 '.style',
1345 '.sucks',
1346 '.supplies',
1347 '.supply',
1348 '.support',
1349 '.surf',
1350 '.surgery',
1351 '.sydney',
1352 '.systems',
1353 '.tattoo',
1354 '.tax',
1355 '.taxi',
1356 '.team',
1357 '.tech',
1358 '.technology',
1359 '.tel',
1360 '.tennis',
1361 '.theater',
1362 '.theatre',
1363 '.tienda',
1364 '.tips',
1365 '.tires',
1366 '.today',
1367 '.tokyo',
1368 '.tools',
1369 '.top',
1370 '.tours',
1371 '.town',
1372 '.toys',
1373 '.trade',
1374 '.training',
1375 '.travel',
1376 '.tube',
1377 '.university',
1378 '.uno',
1379 '.vacations',
1380 '.vegas',
1381 '.ventures',
1382 '.vet',
1383 '.viajes',
1384 '.video',
1385 '.villas',
1386 '.vin',
1387 '.vip',
1388 '.vision',
1389 '.vodka',
1390 '.vote',
1391 '.voting',
1392 '.voto',
1393 '.voyage',
1394 '.wales',
1395 '.watch',
1396 '.webcam',
1397 '.website',
1398 '.wedding',
1399 '.wiki',
1400 '.win',
1401 '.wine',
1402 '.work',
1403 '.works',
1404 '.world',
1405 '.wtf',
1406 '.xn--3ds443g',
1407 '.xn--6frz82g',
1408 '.xxx',
1409 '.xyz',
1410 '.yoga',
1411 '.yokohama',
1412 '.zone',
1413 );
1414
1415 $host = ! empty( $_SERVER['HTTP_HOST'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_HOST'] ) ) : '';
1416
1417 // get the domain.
1418 $domain = explode( '.', $host );
1419 if ( count( $domain ) >= 2 ) {
1420 $domain = '.' . $domain[ count( $domain ) - 1 ];
1421 } else {
1422 $domain = null;
1423 }
1424
1425 return ! in_array( $domain, $valid_domains, true );
1426 }
1427 }
1428