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 / Helpers / Utils.php

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

1,433 lines 39.3 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 // Per-side border box: keys are direction names (top/right/bottom/left),
550 // each holding its own {width, style, color}.
551 $sides = array( 'top', 'right', 'bottom', 'left' );
552 $is_per_side = count( array_intersect( array_keys( $key ), $sides ) ) > 0;
553
554 if ( $is_per_side ) {
555 $border = array();
556 foreach ( $key as $direction => $value ) {
557 if ( ! is_array( $value ) ) {
558 continue;
559 }
560 if ( isset( $value['width'] ) && '' !== $value['width'] ) {
561 $color = ! empty( $value['color'] ) ? $value['color'] : 'currentColor';
562 $style = ! empty( $value['style'] ) ? $value['style'] : 'solid';
563 $border[ "border-{$direction}" ] = "{$value['width']} {$style} {$color}";
564 }
565 }
566 return $border;
567 }
568
569 // Flat border: a defined width alone is enough to emit a declaration —
570 // this ensures an explicit 0 always produces `border-width: 0` instead
571 // of being silently dropped when style/color weren't also set (which
572 // let a theme's/Elementor's default table border show through).
573 if ( isset( $key['width'] ) && '' !== $key['width'] ) {
574 $color = ! empty( $key['color'] ) ? $key['color'] : 'currentColor';
575 $style = ! empty( $key['style'] ) ? $key['style'] : 'solid';
576 return array( 'border' => "{$key['width']} {$style} {$color}" );
577 }
578
579 if ( ! empty( $key['color'] ) ) {
580 $style = ! empty( $key['style'] ) ? $key['style'] : 'solid';
581 return array( 'border' => "1px {$style} {$key['color']}" );
582 }
583
584 return array( 'border' => null );
585 }
586
587 /**
588 * Builds a CSS box-model value (margin/padding/border-radius style) from a
589 * {top,right,bottom,left} array, collapsing to a shorthand string when all
590 * four sides share values.
591 *
592 * @param array $key Box values keyed by 'top'/'right'/'bottom'/'left'.
593 * @param string $property CSS property prefix (e.g. "margin", "padding"), or
594 * "border-radius" for the corner-radius longhand variant.
595 * @return array CSS declarations keyed by property name (shorthand or per-side longhand).
596 */
597 public static function get_box_value( $key = array(), $property = '' ) {
598 $top = isset( $key['top'] ) ? $key['top'] : null;
599 $right = isset( $key['right'] ) ? $key['right'] : null;
600 $bottom = isset( $key['bottom'] ) ? $key['bottom'] : null;
601 $left = isset( $key['left'] ) ? $key['left'] : null;
602
603 $box_object = array(
604 'top' => $top,
605 'right' => $right,
606 'bottom' => $bottom,
607 'left' => $left,
608 );
609 $count = count(
610 array_filter(
611 $box_object,
612 function ( $value ) {
613 return null !== $value;
614 }
615 )
616 );
617
618 if ( 0 === $count ) {
619 return array( $property => null );
620 }
621
622 if ( 4 === $count ) {
623 $box_values = '';
624
625 if ( $top === $bottom && $top === $right && $top === $left ) {
626 $box_values = $top;
627 } elseif ( $top === $bottom && $left === $right ) {
628 $box_values = "{$top} {$right}";
629 } else {
630 $box_values = "{$top} {$right} {$bottom} {$left}";
631 }
632
633 return array( $property => $box_values );
634 }
635
636 $final_box = array();
637
638 if ( 'border-radius' !== $property ) {
639 foreach ( $box_object as $direction => $value ) {
640 $final_box[ "{$property}-{$direction}" ] = isset( $key[ $direction ] ) ? $key[ $direction ] : null;
641 }
642
643 return $final_box;
644 }
645
646 if ( 'border-radius' === $property ) {
647 $final_box['border-top-left-radius'] = $top;
648 $final_box['border-top-right-radius'] = $right;
649 $final_box['border-bottom-right-radius'] = $bottom;
650 $final_box['border-bottom-left-radius'] = $left;
651
652 return $final_box;
653 }
654 }
655
656 /**
657 * Retrieves the color based on the given type and color.
658 *
659 * @param string $type The type of color to retrieve. Can be either 'gradient' or 'color'.
660 * @param string $color The color value.
661 * @return string The retrieved color.
662 */
663 public static function get_color( $type, $color ) {
664
665 if ( empty( $color ) || 0 === strpos( $color, 'linear-gradient(' ) || 0 === strpos( $color, 'radial-gradient(' ) || 0 === strpos( $color, '#' ) ) {
666 return $color;
667 }
668
669 $color_parts = explode( ',', $color, 2 );
670
671 $color_parts1 = $color_parts[0];
672 $color_parts2 = $color_parts[1];
673
674 $bg_color = '';
675
676 if ( 'gradient' === $type ) {
677 $bg_color = 'var(--wp--preset--gradient--' . $color_parts1 . ',' . $color_parts2 . ')';
678
679 } else {
680 $bg_color = 'var(--wp--preset--color--' . $color_parts1 . ',' . $color_parts2 . ')';
681 }
682 return $bg_color;
683 }
684
685 /**
686 * Builds CSS background-* declarations (color, gradient, or image with
687 * position/size/repeat/attachment) from a block's background attribute object.
688 *
689 * @param array $background Background attribute object (backgroundType plus the
690 * relevant color/gradient/image sub-fields).
691 * @param string $device Responsive device key used for position/size sub-fields; default "Desktop".
692 * @return array CSS declarations keyed by "background-*" property name.
693 */
694 public static function fill_background_generator( $background, $device = 'Desktop' ) {
695
696 $fill_background = array(
697 'background-image' => '',
698 );
699
700 if ( isset( $background['backgroundType'] ) && 'classic' === $background['backgroundType'] ) {
701 $fill_background['background-color'] = isset( $background['backgroundColor'] ) ? self::get_color( 'color', $background['backgroundColor'] ) : '';
702 }
703
704 if ( isset( $background['backgroundType'] ) && 'gradient' === $background['backgroundType'] && ! empty( $background['gradient'] ) ) {
705
706 $fill_background['background-image'] = isset( $background['gradient'] ) ? ( self::get_color( 'gradient', $background['gradient'] ) ?? '' ) : '';
707 }
708
709 if ( isset( $background['backgroundType'] ) && 'image' === $background['backgroundType'] && ! empty( $background['backgroundImage'] ) ) {
710 if ( ! empty( $background['backgroundImage']['imageUrl'] ) ) {
711 $fill_background['background-image'] = "url({$background['backgroundImage']['imageUrl']})";
712 }
713
714 if ( ! empty( $background['backgroundAttachment'] ) ) {
715 $fill_background['background-attachment'] = $background['backgroundAttachment'];
716 }
717
718 if ( ! empty( $background['backgroundPosition'][ $device ] ) && 'custom' !== $background['backgroundPosition'][ $device ] ) {
719 $fill_background['background-position'] = $background['backgroundPosition'][ $device ];
720 }
721
722 if (
723 ! empty( $background['backgroundPosition'][ $device ] ) && 'custom' === $background['backgroundPosition'][ $device ] &&
724 ! empty( $background['customPositionX'][ $device ] ) && ! empty( $background['customPositionY'][ $device ] )
725 ) {
726 $fill_background['background-position'] = self::get_slider_value( $background['customPositionX'][ $device ] ) . ' ' . self::get_slider_value( $background['customPositionY'][ $device ] );
727 }
728
729 if ( ! empty( $background['backgroundSize'] ) && 'custom' !== $background['backgroundSize'] ) {
730 $fill_background['background-size'] = $background['backgroundSize'];
731 }
732
733 if (
734 ! empty( $background['backgroundSize'] ) && 'custom' === $background['backgroundSize'] &&
735 ! empty( $background['customSize'][ $device ] )
736 ) {
737 $fill_background['background-size'] = self::get_slider_value( $background['customSize'][ $device ] ) . ' auto';
738 }
739
740 if ( ! empty( $background['backgroundRepeat'] ) ) {
741 $fill_background['background-repeat'] = $background['backgroundRepeat'];
742 }
743 }
744
745 return $fill_background;
746 }
747
748 /**
749 * Builds CSS font-, text-, line-height, letter-spacing, and word-spacing
750 * declarations from a typography attribute object for the given responsive device.
751 *
752 * @param array $key Typography attribute object (fontFamily/fontSize/fontStyle/
753 * fontWeight/textDecoration/textTransform/lineHeight/letterSpacing/wordSpacing).
754 * @param string $device Responsive device key (e.g. "Desktop"); non-Desktop devices
755 * omit font-family/style/weight/text-decoration/text-transform,
756 * which are treated as desktop-only.
757 * @return array CSS declarations keyed by property name.
758 */
759 public static function get_typography_value( $key, $device ) {
760 if ( 'Desktop' === $device ) {
761 return array(
762 'font-family' => isset( $key['fontFamily']['value'] ) ? $key['fontFamily']['value'] : null,
763 'font-size' => isset( $key['fontSize'][ $device ]['size'] ) && isset( $key['fontSize'][ $device ]['unit'] )
764 ? $key['fontSize'][ $device ]['size'] . $key['fontSize'][ $device ]['unit']
765 : null,
766 'font-style' => isset( $key['fontStyle'] ) ? $key['fontStyle'] : null,
767 'font-weight' => isset( $key['fontWeight']['value'] ) ? $key['fontWeight']['value'] : null,
768 'text-decoration' => isset( $key['textDecoration'] ) ? $key['textDecoration'] : null,
769 'text-transform' => isset( $key['textTransform'] ) ? $key['textTransform'] : null,
770 'line-height' => isset( $key['lineHeight'][ $device ]['size'] ) && isset( $key['lineHeight'][ $device ]['unit'] )
771 ? $key['lineHeight'][ $device ]['size'] . $key['lineHeight'][ $device ]['unit']
772 : null,
773 'letter-spacing' => isset( $key['letterSpacing'][ $device ]['size'] ) && isset( $key['letterSpacing'][ $device ]['unit'] )
774 ? $key['letterSpacing'][ $device ]['size'] . $key['letterSpacing'][ $device ]['unit']
775 : null,
776 'word-spacing' => isset( $key['wordSpacing'][ $device ]['size'] ) && isset( $key['wordSpacing'][ $device ]['unit'] )
777 ? $key['wordSpacing'][ $device ]['size'] . $key['wordSpacing'][ $device ]['unit']
778 : null,
779 );
780 } else {
781 return array(
782 'font-size' => isset( $key['fontSize'][ $device ]['size'] ) && isset( $key['fontSize'][ $device ]['unit'] )
783 ? $key['fontSize'][ $device ]['size'] . $key['fontSize'][ $device ]['unit']
784 : null,
785 'line-height' => isset( $key['lineHeight'][ $device ]['size'] ) && isset( $key['lineHeight'][ $device ]['unit'] )
786 ? $key['lineHeight'][ $device ]['size'] . $key['lineHeight'][ $device ]['unit']
787 : null,
788 'letter-spacing' => isset( $key['letterSpacing'][ $device ]['size'] ) && isset( $key['letterSpacing'][ $device ]['unit'] )
789 ? $key['letterSpacing'][ $device ]['size'] . $key['letterSpacing'][ $device ]['unit']
790 : null,
791 'word-spacing' => isset( $key['wordSpacing'][ $device ]['size'] ) && isset( $key['wordSpacing'][ $device ]['unit'] )
792 ? $key['wordSpacing'][ $device ]['size'] . $key['wordSpacing'][ $device ]['unit']
793 : null,
794 );
795 }
796 }
797
798 /**
799 * Gets slider value.
800 *
801 * Similar to getSliderValue function in JS helper.
802 *
803 * @param array $key Slider value array with "size" and "unit" keys.
804 * @return string|null The combined "{size}{unit}" string, or null if no size is set.
805 */
806 public static function get_slider_value( $key ) {
807 $value = '';
808
809 if ( ! empty( $key['size'] ) && ! empty( $key['unit'] ) ) {
810 $value = $key['size'] . $key['unit'];
811 } elseif ( ! empty( $key['size'] ) && empty( $key['unit'] ) ) {
812 $value = $key['size'];
813 } else {
814 $value = null;
815 }
816
817 return $value;
818 }
819
820 /**
821 * Converts a per-device map of {selector, ...cssValues} style records into
822 * per-device CSS text blocks.
823 *
824 * @param array $raw_css Style records keyed by device slug (desktop/tablet/mobile/etc.),
825 * each an array of {selector, ...property=>value} entries.
826 * @return array<string,string> Compiled CSS text keyed by device slug.
827 */
828 public static function parse_css( $raw_css ) {
829
830 $styles = array();
831 $device_list = array( 'desktop', 'tablet', 'mobile', 'tabletlandscape', 'mobilelandscape', 'laptop', 'widescreen' );
832
833 foreach ( $device_list as $device ) {
834 $device_styles = $raw_css[ $device ] ?? array();
835
836 $styles[ $device ] = array_map(
837 function ( $style ) {
838 if ( ! is_array( $style ) || ! isset( $style['selector'] ) ) {
839 return '';
840 }
841
842 $selector = $style['selector'];
843 $css_values = array_filter(
844 $style,
845 function ( $value, $key ) {
846 return 'selector' !== $key && null !== $value && '' !== $value && ! is_numeric( $value ) && ! in_array( $value, array( 'px', 'em', 'rem', '%', 'vh', 'vw' ), true ) && false === strpos( $value, 'undefined' );
847 },
848 ARRAY_FILTER_USE_BOTH
849 );
850
851 if ( empty( $css_values ) ) {
852 return '';
853 }
854
855 return "{$selector} { " . implode(
856 ' ',
857 array_map(
858 function ( $key, $value ) {
859 return "{$key}: {$value};";
860 },
861 array_keys( $css_values ),
862 $css_values
863 )
864 ) . ' }';
865 },
866 $device_styles
867 );
868 }
869
870 $device_styles = array_map(
871 function ( $style ) {
872 return implode( "\n", $style );
873 },
874 $styles
875 );
876
877 return $device_styles;
878 }
879
880 /**
881 * Checks whether a parsed block is one of this plugin's "tablebuilder/*" blocks,
882 * optionally requiring a specific (possibly nested) attribute to be present.
883 *
884 * @param string $block_content Rendered block HTML content.
885 * @param array $parsed_block Parsed block array (as passed to a `render_block` callback).
886 * @param string $attrs Optional. Top-level attribute key to require.
887 * @param string $attrs2 Optional. Nested key within $attrs to require.
888 * @return bool True if this is a table-builder block matching the given criteria.
889 */
890 public static function is_table_builder_block( $block_content, $parsed_block, $attrs = '', $attrs2 = '' ) {
891 // phpcs:ignore Squiz.PHP.CommentedOutCode.Found -- explanatory prose comments, not commented-out code.
892 // Check if $block_content is not empty.
893 $has_block_content = ! empty( $block_content );
894 // Check if $block['blockName'] is not empty and contains 'roxSlider'.
895 $has_valid_block_name = isset( $parsed_block['blockName'] ) && false !== strpos( $parsed_block['blockName'], 'tablebuilder' );
896
897 // Check if $block['attrs']['blockClass'] is not empty.
898 $has_block_class = ! empty( $attrs ) && ! empty( $attrs2 )
899 ? ! empty( $parsed_block['attrs'][ $attrs ][ $attrs2 ] )
900 : ! empty( $parsed_block['attrs'][ $attrs ] ?? '' );
901
902 if ( empty( $attrs ) && empty( $attrs2 ) ) {
903 $has_block_class = true;
904 }
905
906 // Return true if all conditions are met.
907 return $has_block_content && $has_valid_block_name && $has_block_class;
908 }
909
910 /**
911 * Retrieves the link attributes based on the provided attribute array.
912 *
913 * @param array $attribute The attribute array containing the link data.
914 * @return string The generated link attributes as a string.
915 */
916 public static function get_link_attributes( $attribute ) {
917 if ( empty( $attribute['url'] ) ) {
918 return '';
919 }
920
921 $link_data = array();
922
923 $link_data['href'] = esc_url( $attribute['url'], wp_allowed_protocols() );
924
925 ( isset( $attribute['newTab'] ) && $attribute['newTab'] ) ? $link_data['target'] = '_blank' : '';
926
927 ( isset( $attribute['noFollow'] ) && $attribute['noFollow'] ) ? $link_data['rel'] = 'nofollow' : '';
928
929 if ( isset( $attribute['customAttributes'] ) && is_array( $attribute['customAttributes'] ) ) {
930 foreach ( $attribute['customAttributes'] as $key => $value ) {
931 if ( ! empty( $value ) ) {
932 $attr_key_value = explode( '|', $value );
933
934 $attr_key = mb_strtolower( $attr_key_value[0] );
935
936 // Not allowed characters are removed.
937 preg_match( '/[-_a-z0-9]+/', $attr_key, $attr_key_matches );
938
939 if ( empty( $attr_key_matches[0] ) ) {
940 continue;
941 }
942
943 $attr_key = $attr_key_matches[0];
944
945 // Javascript events and unescaped href are avoided.
946 if ( 'href' === $attr_key || 'on' === substr( $attr_key, 0, 2 ) ) {
947 continue;
948 }
949
950 if ( isset( $attr_key_value[1] ) ) {
951 $attr_value = trim( $attr_key_value[1] );
952 } else {
953 $attr_value = '';
954 }
955
956 $link_data[ $attr_key ] = $attr_value;
957 }
958 }
959 }
960
961 $link_attributes = '';
962 foreach ( $link_data as $key => $value ) {
963 $link_attributes .= sprintf( '%s="%s" ', $key, esc_attr( $value ) );
964 }
965
966 return $link_attributes;
967 }
968
969 /**
970 * Gets the plugin's cached license status ("valid" or "invalid"), based on
971 * whether both the stored license key and license identifier are set.
972 *
973 * @return string "valid" or "invalid".
974 */
975 public static function status() {
976
977 $cached = wp_cache_get( 'table_builder__license_status' );
978
979 if ( false !== $cached ) {
980 return $cached;
981 }
982
983 $oppai = get_option( '__table_builder_oppai__', '' );
984 $key = get_option( '__table_builder_license_key__', '' );
985 $status = 'invalid';
986
987 if ( '' !== $oppai && '' !== $key ) {
988 $status = 'valid';
989 }
990
991 wp_cache_set( 'table_builder__license_status', $status );
992
993 return $status;
994 }
995
996 /**
997 * Checks whether the current request's host's TLD is outside a known list of
998 * public/commercial TLDs — used as a rough heuristic to detect local/dev
999 * environments (e.g. `.test`, `.local`, `.localhost`) for license checks.
1000 *
1001 * @return bool True if the current host's TLD is NOT in the known public-TLD list
1002 * (i.e. it looks like a local/dev environment).
1003 */
1004 public static function is_local() {
1005 $valid_domains = array(
1006 '.academy',
1007 '.accountant',
1008 '.accountants',
1009 '.actor',
1010 '.adult',
1011 '.africa',
1012 '.agency',
1013 '.airforce',
1014 '.apartments',
1015 '.app',
1016 '.army',
1017 '.art',
1018 '.asia',
1019 '.associates',
1020 '.attorney',
1021 '.auction',
1022 '.audio',
1023 '.auto',
1024 '.baby',
1025 '.band',
1026 '.bar',
1027 '.bargains',
1028 '.beer',
1029 '.berlin',
1030 '.best',
1031 '.bid',
1032 '.bike',
1033 '.bingo',
1034 '.bio',
1035 '.biz',
1036 '.black',
1037 '.blackfriday',
1038 '.blog',
1039 '.blue',
1040 '.boston',
1041 '.boutique',
1042 '.build',
1043 '.builders',
1044 '.business',
1045 '.buzz',
1046 '.cab',
1047 '.cafe',
1048 '.cam',
1049 '.camera',
1050 '.camp',
1051 '.capital',
1052 '.car',
1053 '.cards',
1054 '.care',
1055 '.careers',
1056 '.cars',
1057 '.casa',
1058 '.cash',
1059 '.casino',
1060 '.catering',
1061 '.center',
1062 '.ceo',
1063 '.chat',
1064 '.cheap',
1065 '.christmas',
1066 '.church',
1067 '.city',
1068 '.claims',
1069 '.cleaning',
1070 '.click',
1071 '.clinic',
1072 '.clothing',
1073 '.cloud',
1074 '.club',
1075 '.coach',
1076 '.codes',
1077 '.coffee',
1078 '.college',
1079 '.com',
1080 '.community',
1081 '.company',
1082 '.computer',
1083 '.condos',
1084 '.construction',
1085 '.consulting',
1086 '.contact',
1087 '.contractors',
1088 '.cooking',
1089 '.cool',
1090 '.country',
1091 '.coupons',
1092 '.courses',
1093 '.credit',
1094 '.creditcard',
1095 '.cricket',
1096 '.cruises',
1097 '.cymru',
1098 '.cyou',
1099 '.dance',
1100 '.date',
1101 '.dating',
1102 '.day',
1103 '.deals',
1104 '.degree',
1105 '.delivery',
1106 '.democrat',
1107 '.dental',
1108 '.dentist',
1109 '.desi',
1110 '.design',
1111 '.dev',
1112 '.diamonds',
1113 '.diet',
1114 '.digital',
1115 '.direct',
1116 '.directory',
1117 '.discount',
1118 '.doctor',
1119 '.dog',
1120 '.domains',
1121 '.download',
1122 '.earth',
1123 '.eco',
1124 '.education',
1125 '.email',
1126 '.energy',
1127 '.engineer',
1128 '.engineering',
1129 '.enterprises',
1130 '.equipment',
1131 '.estate',
1132 '.events',
1133 '.exchange',
1134 '.expert',
1135 '.exposed',
1136 '.express',
1137 '.fail',
1138 '.faith',
1139 '.family',
1140 '.fans',
1141 '.farm',
1142 '.fashion',
1143 '.feedback',
1144 '.film',
1145 '.finance',
1146 '.financial',
1147 '.fish',
1148 '.fishing',
1149 '.fit',
1150 '.fitness',
1151 '.flights',
1152 '.florist',
1153 '.flowers',
1154 '.football',
1155 '.forsale',
1156 '.foundation',
1157 '.fun',
1158 '.fund',
1159 '.furniture',
1160 '.futbol',
1161 '.fyi',
1162 '.gallery',
1163 '.game',
1164 '.games',
1165 '.garden',
1166 '.gay',
1167 '.gdn',
1168 '.gift',
1169 '.gifts',
1170 '.gives',
1171 '.glass',
1172 '.global',
1173 '.gmbh',
1174 '.gold',
1175 '.golf',
1176 '.graphics',
1177 '.gratis',
1178 '.green',
1179 '.gripe',
1180 '.group',
1181 '.guide',
1182 '.guitars',
1183 '.guru',
1184 '.hamburg',
1185 '.haus',
1186 '.health',
1187 '.healthcare',
1188 '.help',
1189 '.hiphop',
1190 '.hockey',
1191 '.holdings',
1192 '.holiday',
1193 '.horse',
1194 '.host',
1195 '.hosting',
1196 '.house',
1197 '.how',
1198 '.icu',
1199 '.immo',
1200 '.immobilien',
1201 '.inc',
1202 '.industries',
1203 '.info',
1204 '.ink',
1205 '.institute',
1206 '.insure',
1207 '.international',
1208 '.investments',
1209 '.irish',
1210 '.jetzt',
1211 '.jewelry',
1212 '.juegos',
1213 '.kaufen',
1214 '.kim',
1215 '.kitchen',
1216 '.kiwi',
1217 '.krd',
1218 '.kyoto',
1219 '.land',
1220 '.lat',
1221 '.lawyer',
1222 '.lease',
1223 '.legal',
1224 '.lgbt',
1225 '.life',
1226 '.lighting',
1227 '.limited',
1228 '.limo',
1229 '.link',
1230 '.live',
1231 '.llc',
1232 '.loan',
1233 '.loans',
1234 '.lol',
1235 '.london',
1236 '.love',
1237 '.ltd',
1238 '.ltda',
1239 '.luxury',
1240 '.maison',
1241 '.management',
1242 '.market',
1243 '.marketing',
1244 '.mba',
1245 '.media',
1246 '.melbourne',
1247 '.memorial',
1248 '.men',
1249 '.menu',
1250 '.miami',
1251 '.mobi',
1252 '.moda',
1253 '.moe',
1254 '.mom',
1255 '.money',
1256 '.monster',
1257 '.mortgage',
1258 '.movie',
1259 '.nagoya',
1260 '.name',
1261 '.navy',
1262 '.net',
1263 '.network',
1264 '.new',
1265 '.news',
1266 '.ninja',
1267 '.nyc',
1268 '.observer',
1269 '.okinawa',
1270 '.one',
1271 '.onl',
1272 '.online',
1273 '.org',
1274 '.osaka',
1275 '.page',
1276 '.paris',
1277 '.partners',
1278 '.parts',
1279 '.party',
1280 '.photo',
1281 '.photography',
1282 '.photos',
1283 '.pics',
1284 '.pictures',
1285 '.pink',
1286 '.pizza',
1287 '.place',
1288 '.plumbing',
1289 '.plus',
1290 '.poker',
1291 '.porn',
1292 '.press',
1293 '.pro',
1294 '.productions',
1295 '.properties',
1296 '.property',
1297 '.protection',
1298 '.pub',
1299 '.racing',
1300 '.realty',
1301 '.recipes',
1302 '.red',
1303 '.rehab',
1304 '.reise',
1305 '.reisen',
1306 '.rent',
1307 '.rentals',
1308 '.repair',
1309 '.report',
1310 '.republican',
1311 '.rest',
1312 '.restaurant',
1313 '.review',
1314 '.reviews',
1315 '.rip',
1316 '.rocks',
1317 '.rodeo',
1318 '.run',
1319 '.ryukyu',
1320 '.sale',
1321 '.sarl',
1322 '.school',
1323 '.schule',
1324 '.science',
1325 '.security',
1326 '.services',
1327 '.sex',
1328 '.sexy',
1329 '.shiksha',
1330 '.shoes',
1331 '.shop',
1332 '.shopping',
1333 '.show',
1334 '.singles',
1335 '.site',
1336 '.ski',
1337 '.soccer',
1338 '.social',
1339 '.software',
1340 '.solar',
1341 '.solutions',
1342 '.soy',
1343 '.space',
1344 '.storage',
1345 '.store',
1346 '.stream',
1347 '.studio',
1348 '.study',
1349 '.style',
1350 '.sucks',
1351 '.supplies',
1352 '.supply',
1353 '.support',
1354 '.surf',
1355 '.surgery',
1356 '.sydney',
1357 '.systems',
1358 '.tattoo',
1359 '.tax',
1360 '.taxi',
1361 '.team',
1362 '.tech',
1363 '.technology',
1364 '.tel',
1365 '.tennis',
1366 '.theater',
1367 '.theatre',
1368 '.tienda',
1369 '.tips',
1370 '.tires',
1371 '.today',
1372 '.tokyo',
1373 '.tools',
1374 '.top',
1375 '.tours',
1376 '.town',
1377 '.toys',
1378 '.trade',
1379 '.training',
1380 '.travel',
1381 '.tube',
1382 '.university',
1383 '.uno',
1384 '.vacations',
1385 '.vegas',
1386 '.ventures',
1387 '.vet',
1388 '.viajes',
1389 '.video',
1390 '.villas',
1391 '.vin',
1392 '.vip',
1393 '.vision',
1394 '.vodka',
1395 '.vote',
1396 '.voting',
1397 '.voto',
1398 '.voyage',
1399 '.wales',
1400 '.watch',
1401 '.webcam',
1402 '.website',
1403 '.wedding',
1404 '.wiki',
1405 '.win',
1406 '.wine',
1407 '.work',
1408 '.works',
1409 '.world',
1410 '.wtf',
1411 '.xn--3ds443g',
1412 '.xn--6frz82g',
1413 '.xxx',
1414 '.xyz',
1415 '.yoga',
1416 '.yokohama',
1417 '.zone',
1418 );
1419
1420 $host = ! empty( $_SERVER['HTTP_HOST'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_HOST'] ) ) : '';
1421
1422 // get the domain.
1423 $domain = explode( '.', $host );
1424 if ( count( $domain ) >= 2 ) {
1425 $domain = '.' . $domain[ count( $domain ) - 1 ];
1426 } else {
1427 $domain = null;
1428 }
1429
1430 return ! in_array( $domain, $valid_domains, true );
1431 }
1432 }
1433