PluginProbe
aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder / 2.13.1
aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder v2.13.1
2.13.0 2.13.1 2.12.0 2.11.1 2.11.0 2.10.0 2.9.0 2.7.4 2.7.5 2.7.6 2.7.7 2.8.0 2.8.1 2.9.1 trunk 1.0 1.0-beta1 1.0-beta2 1.0-beta3 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 1.1.2 All 80 releases
← All changes | includes/helper.php +1035 -0 2.7.42.13.1 View file →
@@ -1,0 +1,1035 @@
1 +<?php
2 +namespace ABlocks;
3 +
4 +if ( ! defined( 'ABSPATH' ) ) {
5 + exit; // Exit if accessed directly.
6 +}
7 +
8 +use ABlocks\traits\Importer;
9 +
10 +class Helper {
11 +
12 + use Importer;
13 +
14 + /** Memoized responsive device list (see get_responsive_devices). */
15 + private static $responsive_devices_cache = null;
16 +
17 + public static function get_time() {
18 + return time() + ( get_option( 'gmt_offset' ) * HOUR_IN_SECONDS );
19 + }
20 +
21 + public static function get_settings( $key, $default = null ) {
22 + global $ablocks_settings;
23 +
24 + if ( isset( $ablocks_settings->{$key} ) ) {
25 + return $ablocks_settings->{$key};
26 + }
27 +
28 + return $default;
29 + }
30 +
31 + /**
32 + * Responsive breakpoint widths (px) for the whole plugin. User-configurable
33 + * via Settings; defaults preserve the historical 800/480 values. Every CSS
34 + * generator and the block editor reads these so breakpoints stay in sync.
35 + */
36 + /**
37 + * Make one CSS property or declaration value safe to write into a stylesheet.
38 + *
39 + * Block attributes reach the compiled CSS verbatim and that CSS is echoed
40 + * inside a `<style>` element, so a value carrying `</style>` closes the
41 + * element and everything after it is parsed as HTML — a stored XSS
42 + * available to anyone who can set a block attribute, which includes a
43 + * Contributor editing their own draft. `{` and `}` are the same problem one
44 + * level down: they close the rule and let the value choose its own
45 + * selector.
46 + *
47 + * Only those four characters are removed. A declaration value never needs
48 + * them, and everything a real one does need survives: data URIs (which
49 + * carry `;`), gradients, `calc()` and `var()` (parentheses and commas),
50 + * font stacks (quotes), `content` escapes (backslashes), and shorthand
51 + * slashes such as `font: 12px/1.5`.
52 + *
53 + * `;` is deliberately kept. With the braces gone, an injected `;` can only
54 + * add declarations to the same rule — which targets the block's own
55 + * element, exactly what its style controls already allow — so removing it
56 + * would break data URIs to buy nothing.
57 + *
58 + * @param mixed $value A CSS property name or declaration value.
59 + * @return string The value with the escape characters removed.
60 + */
61 + public static function esc_css_value( $value ) {
62 + if ( ! is_scalar( $value ) ) {
63 + return '';
64 + }
65 + return str_replace( [ '<', '>', '{', '}' ], '', (string) $value );
66 + }
67 +
68 + public static function get_breakpoints() {
69 + $tablet = (int) self::get_settings( 'breakpoint_tablet', 800 );
70 + $mobile = (int) self::get_settings( 'breakpoint_mobile', 480 );
71 +
72 + // Guard against nonsensical config (mobile must be below tablet).
73 + if ( $tablet < 1 ) {
74 + $tablet = 800;
75 + }
76 + if ( $mobile < 1 || $mobile >= $tablet ) {
77 + $mobile = min( 480, $tablet - 1 );
78 + }
79 +
80 + return array(
81 + 'tablet' => $tablet,
82 + 'mobile' => $mobile,
83 + );
84 + }
85 +
86 + /**
87 + * The full ordered list of responsive devices the atomic style system emits
88 + * for: the base (Desktop, width 0 = no media query), the two built-in
89 + * breakpoints, then any user-registered custom breakpoints. Each entry:
90 + * id - stable identifier (also the WP device name for the built-ins)
91 + * label - shown in the editor device switcher
92 + * suffix - appended to responsive attribute keys (e.g. fontSize + suffix)
93 + * width - max-width px for the @media rule (0 = base, no media query)
94 + *
95 + * Ordering is load-bearing, not cosmetic: the base comes first and every
96 + * other device follows widest-first, so narrower breakpoints emit later and
97 + * win in `cascade` mode. Precedence therefore follows the breakpoint's own
98 + * bounds rather than the order a custom breakpoint happened to be
99 + * registered in.
100 + */
101 + public static function get_responsive_devices() {
102 + if ( null !== self::$responsive_devices_cache ) {
103 + return self::$responsive_devices_cache;
104 + }
105 +
106 + $bp = self::get_breakpoints();
107 + // Built-ins are max-width only (min 0). `width` is the sort key
108 + // (max-width, or a large value for min-only so it sorts widest).
109 + $devices = array(
110 + array( 'id' => 'Desktop', 'label' => 'Desktop', 'suffix' => '', 'width' => 0, 'min' => 0, 'max' => 0 ),
111 + array( 'id' => 'Tablet', 'label' => 'Tablet', 'suffix' => 'Tablet', 'width' => $bp['tablet'], 'min' => 0, 'max' => $bp['tablet'] ),
112 + array( 'id' => 'Mobile', 'label' => 'Mobile', 'suffix' => 'Mobile', 'width' => $bp['mobile'], 'min' => 0, 'max' => $bp['mobile'] ),
113 + );
114 +
115 + $custom = self::get_settings( 'breakpoint_custom', array() );
116 + if ( is_array( $custom ) ) {
117 + foreach ( $custom as $c ) {
118 + $c = (array) $c;
119 + // Advanced breakpoints support a min and/or max width. `width` is
120 + // kept as a legacy alias for max-width.
121 + $max = isset( $c['maxWidth'] ) ? (int) $c['maxWidth'] : ( isset( $c['width'] ) ? (int) $c['width'] : 0 );
122 + $min = isset( $c['minWidth'] ) ? (int) $c['minWidth'] : 0;
123 + if ( $max < 1 && $min < 1 ) {
124 + continue; // needs at least one bound
125 + }
126 + // Stable, alphanumeric suffix so stored values survive label edits.
127 + $key = ! empty( $c['key'] ) ? preg_replace( '/[^a-zA-Z0-9]/', '', $c['key'] ) : (string) ( $max ? $max : $min );
128 + $suffix = 'Bp' . ucfirst( $key );
129 + $label = ! empty( $c['label'] ) ? $c['label'] : self::breakpoint_auto_label( $min, $max );
130 + $devices[] = array(
131 + 'id' => $suffix,
132 + 'label' => $label,
133 + 'suffix' => $suffix,
134 + 'width' => $max > 0 ? $max : 999999, // sort key (min-only = widest)
135 + 'min' => $min,
136 + 'max' => $max,
137 + );
138 + }
139 + }
140 +
141 + self::$responsive_devices_cache = self::sort_responsive_devices( $devices );
142 + return self::$responsive_devices_cache;
143 + }
144 +
145 + /**
146 + * Base first, then widest-first. Ties break on id so the order is stable
147 + * regardless of the PHP version's sort stability.
148 + */
149 + private static function sort_responsive_devices( $devices ) {
150 + $base = array();
151 + $rest = array();
152 + foreach ( $devices as $d ) {
153 + if ( empty( $d['min'] ) && empty( $d['max'] ) ) {
154 + $base[] = $d;
155 + } else {
156 + $rest[] = $d;
157 + }
158 + }
159 +
160 + usort(
161 + $rest,
162 + function ( $a, $b ) {
163 + $cmp = (int) $b['width'] - (int) $a['width'];
164 + return 0 !== $cmp ? $cmp : strcmp( (string) $a['id'], (string) $b['id'] );
165 + }
166 + );
167 +
168 + return array_merge( $base, $rest );
169 + }
170 +
171 + /** Drop the memoized device list (settings changed mid-request). */
172 + public static function flush_responsive_devices_cache() {
173 + self::$responsive_devices_cache = null;
174 + }
175 +
176 + /** A readable fallback label for a min/max breakpoint. */
177 + public static function breakpoint_auto_label( $min, $max ) {
178 + if ( $min > 0 && $max > 0 ) {
179 + return $min . '–' . $max . 'px';
180 + }
181 + if ( $max > 0 ) {
182 + return '≤ ' . $max . 'px';
183 + }
184 + return '≥ ' . $min . 'px';
185 + }
186 +
187 + /** Compose a CSS media condition (no `@media` keyword) from min/max px. */
188 + public static function breakpoint_media_condition( $min, $max ) {
189 + $parts = array();
190 + if ( $min > 0 ) {
191 + $parts[] = '(min-width:' . (int) $min . 'px)';
192 + }
193 + if ( $max > 0 ) {
194 + $parts[] = '(max-width:' . (int) $max . 'px)';
195 + }
196 + return implode( ' and ', $parts );
197 + }
198 +
199 + /**
200 + * How breakpoint queries relate to each other, site-wide.
201 + *
202 + * cascade (default) - max-width envelopes. A Tablet value still applies at
203 + * Mobile widths unless Mobile overrides it. This is how
204 + * aBlocks v1/v2 blocks behave, so a page mixing block
205 + * versions stays consistent.
206 + * strict - exclusive bands. A Tablet value applies only between
207 + * the Mobile bound and the Tablet bound, matching
208 + * WordPress core and block themes.
209 + */
210 + public static function get_breakpoint_mode() {
211 + return 'strict' === self::get_settings( 'breakpoint_mode', 'cascade' ) ? 'strict' : 'cascade';
212 + }
213 +
214 + /**
215 + * The single place an atomic media query is built. Returns the complete
216 + * `@media …` prelude for a device entry, or '' for the base device (which
217 + * needs no query at all).
218 + *
219 + * Both bounds are honoured, so a custom breakpoint declared with only a
220 + * `minWidth` produces a real min-width query instead of being skipped —
221 + * animations already behaved this way, style rules did not.
222 + */
223 + public static function breakpoint_media_query( $device ) {
224 + $device = (array) $device;
225 + $min = isset( $device['min'] ) ? (int) $device['min'] : 0;
226 + $max = isset( $device['max'] ) ? (int) $device['max'] : 0;
227 +
228 + if ( $min < 1 && $max < 1 ) {
229 + return '';
230 + }
231 +
232 + /*
233 + * Strict mode bounds a max-width breakpoint from below with the next
234 + * narrower breakpoint, turning overlapping envelopes into exclusive
235 + * bands. A breakpoint that already declares its own min is left alone —
236 + * the author has stated the band explicitly.
237 + */
238 + if ( 'strict' === self::get_breakpoint_mode() && $max > 0 && $min < 1 ) {
239 + $narrower = self::next_narrower_max( $max );
240 + if ( $narrower > 0 ) {
241 + $min = $narrower + 1;
242 + }
243 + }
244 +
245 + $condition = self::breakpoint_media_condition( $min, $max );
246 + return '' === $condition ? '' : '@media screen and ' . $condition;
247 + }
248 +
249 + /** The largest max-width bound narrower than $max, or 0 if none. */
250 + private static function next_narrower_max( $max ) {
251 + $best = 0;
252 + foreach ( self::get_responsive_devices() as $d ) {
253 + $dmax = isset( $d['max'] ) ? (int) $d['max'] : 0;
254 + if ( $dmax > 0 && $dmax < $max && $dmax > $best ) {
255 + $best = $dmax;
256 + }
257 + }
258 + return $best;
259 + }
260 +
261 + public static function get_page_permalink( $page, $fallback = null ) {
262 + $page_id = self::get_settings( $page );
263 + $permalink = 0 < $page_id ? get_permalink( $page_id ) : '';
264 + if ( ! $permalink ) {
265 + $permalink = is_null( $fallback ) ? get_home_url() : $fallback;
266 + }
267 +
268 + return apply_filters( 'ablocks/get_' . $page . '_permalink', $permalink );
269 + }
270 +
271 + public static function get_logout_url( $redirect = '' ) {
272 + $redirect = $redirect ? $redirect : apply_filters( 'ablocks/logout_default_redirect_url', self::get_page_permalink( 'dashboard_page' ) );
273 +
274 + return wp_logout_url( $redirect );
275 + }
276 + public static function is_enabled_block( $block_name, $parent_block_name = '' ) {
277 + global $ablocks_blocks;
278 + $block_name = ! empty( $parent_block_name ) ? $parent_block_name : $block_name;
279 + if ( isset( $ablocks_blocks->{$block_name} ) ) {
280 + return (bool) $ablocks_blocks->{$block_name};
281 + }
282 + return false;
283 + }
284 +
285 +
286 + public static function is_plugin_installed( $path ) {
287 + $installed_plugins = get_plugins();
288 + return isset( $installed_plugins[ $path ] );
289 + }
290 +
291 + public static function is_active_academy() {
292 + $academy = 'academy/academy.php';
293 + return self::is_plugin_active( $academy );
294 + }
295 +
296 +
297 + public static function is_active_storeengine() {
298 + $storeengine = 'storeengine/storeengine.php';
299 + return self::is_plugin_active( $storeengine );
300 + }
301 +
302 + public static function is_active_ablocks_pro() {
303 + $ablocks = 'ablocks-pro/ablocks-pro.php';
304 + return self::is_plugin_active( $ablocks );
305 + }
306 + public static function is_active_wp_map_block() {
307 + $wp_map_block = 'wp-map-block/wp-map-block.php';
308 + return self::is_plugin_active( $wp_map_block );
309 + }
310 + public static function is_active_quizpress() {
311 + return class_exists( 'QuizPress' );
312 + }
313 + public static function is_active_zencommunity() {
314 + $zencommunity = 'zencommunity/zencommunity.php';
315 + return self::is_plugin_active( $zencommunity );
316 + }
317 + public static function is_active_gemboards() {
318 + $gemboards = 'gemboards/gemboards.php';
319 + return self::is_plugin_active( $gemboards );
320 + }
321 + public static function is_active_easy_content_manager() {
322 + return class_exists( 'EasyContentManager' );
323 + }
324 +
325 + public static function is_enabled_assets_generation() {
326 + // Default OFF — combining/generating per-page asset files churns while a
327 + // site is still being built, so it's recommended (via the Performance tab
328 + // notice) once the site is complete rather than forced on. When enabled it
329 + // merges every block's CSS/JS into one per-page file, inlined when small
330 + // (see Assets::enqueue_frontend_assets), removing the per-block
331 + // render-blocking stylesheets.
332 + $flag = (bool) self::get_settings( 'enabled_assets_file_generation', false );
333 + return apply_filters( 'ablocks/is_enabled_assets_generation', $flag );
334 + }
335 +
336 + public static function is_plugin_active( $basename ) {
337 + if ( ! function_exists( 'get_plugins' ) ) {
338 + include_once ABSPATH . '/wp-admin/includes/plugin.php';
339 + }
340 + return is_plugin_active( $basename );
341 + }
342 +
343 + public static function is_dev_mode_enable() {
344 + $environment = wp_get_environment_type();
345 + if ( 'local' === $environment || 'development' === $environment ) {
346 + return true;
347 + }
348 + }
349 +
350 + /**
351 + * The aBlocks submenu.
352 + *
353 + * Each item declares the aBlocks capability that owns it rather than
354 + * manage_options, so a site can hand somebody the Theme Builder without
355 + * handing them the whole of WordPress. Administrators hold every one of
356 + * these, so nothing changes for them. See Permissions.
357 + */
358 + public static function get_admin_menu_list() {
359 + $menu = [];
360 + $menu[ ABLOCKS_PLUGIN_SLUG ] = [
361 + 'parent_slug' => ABLOCKS_PLUGIN_SLUG,
362 + 'title' => __( 'Dashboard', 'ablocks' ),
363 + 'capability' => Permissions::ACCESS,
364 + ];
365 + if ( self::is_enabled_block( 'form-builder' ) ) {
366 + $menu[ ABLOCKS_PLUGIN_SLUG . '-submissions' ] = [
367 + 'parent_slug' => ABLOCKS_PLUGIN_SLUG,
368 + 'title' => __( 'Submissions', 'ablocks' ),
369 + 'capability' => 'ablocks_view_submissions',
370 + ];
371 + }
372 + if ( self::get_addon_active_status( 'theme-builder' ) ) {
373 + $menu[ ABLOCKS_PLUGIN_SLUG . '-theme-builder' ] = [
374 + 'parent_slug' => ABLOCKS_PLUGIN_SLUG,
375 + 'title' => __( 'Theme Builder', 'ablocks' ),
376 + 'capability' => 'ablocks_manage_theme_builder',
377 + ];
378 + }
379 + $menu[ ABLOCKS_PLUGIN_SLUG . '-addons' ] = [
380 + 'parent_slug' => ABLOCKS_PLUGIN_SLUG,
381 + 'title' => __( 'Add-ons', 'ablocks' ),
382 + 'capability' => 'ablocks_manage_addons',
383 + ];
384 + $menu[ ABLOCKS_PLUGIN_SLUG . '-scanner' ] = [
385 + 'parent_slug' => ABLOCKS_PLUGIN_SLUG,
386 + 'title' => __( 'Site Scanner', 'ablocks' ),
387 + 'capability' => 'ablocks_run_scanner',
388 + ];
389 + $menu[ ABLOCKS_PLUGIN_SLUG . '-settings' ] = [
390 + 'parent_slug' => ABLOCKS_PLUGIN_SLUG,
391 + 'title' => __( 'Settings', 'ablocks' ),
392 + 'capability' => Permissions::SAVE_SETTINGS,
393 + ];
394 + if ( ! defined( 'ABLOCKS_PRO_VERSION' ) ) {
395 + $menu[ ABLOCKS_PLUGIN_SLUG . '-get-pro' ] = [
396 + 'parent_slug' => ABLOCKS_PLUGIN_SLUG,
397 + 'title' => '<span class="dashicons dashicons-awards academy-blue-color"></span> ' . __( 'Get Pro', 'ablocks' ),
398 + 'capability' => Permissions::ACCESS,
399 + ];
400 + }
401 + return apply_filters( 'ablocks/admin_menu_list', $menu );
402 + }
403 +
404 + public static function get_preloader_html() {
405 + ob_start();
406 + ?>
407 + <div class="ablocks-initial-preloader"><?php esc_html_e( 'Loading...', 'ablocks' ); ?></div>
408 + <?php
409 + return ob_get_clean();
410 + }
411 + public static function has_value( $value ) {
412 + return isset( $value ) && ! empty( $value );
413 + }
414 +
415 + public static function get_array_value( $array, $key, $default ) {
416 + return ( isset( $array[ $key ] ) && ! empty( $array[ $key ] ) ) ? $array[ $key ] : $default;
417 + }
418 +
419 + public static function get_responsive_value( $attribute, $attribute_object_key, $device, $attribute_default_value = [] ) {
420 + // Closure to "clean" a value (similar to your JS clean() helper)
421 + $clean = function( $value ) {
422 + return self::has_value( $value ) ? $value : null;
423 + };
424 +
425 + // Desktop value (fallback to default, or false if nothing found)
426 + $desktop_value =
427 + $clean( $attribute[ $attribute_object_key ] ?? null ) ??
428 + $clean( $attribute_default_value[ $attribute_object_key ] ?? null ) ??
429 + false;
430 +
431 + // Tablet value (fallback to desktop if nothing found)
432 + $tablet_key = $attribute_object_key . 'Tablet';
433 + $tablet_value =
434 + $clean( $attribute[ $tablet_key ] ?? null ) ??
435 + $clean( $attribute_default_value[ $tablet_key ] ?? null ) ??
436 + $desktop_value;
437 +
438 + // Mobile value (fallback to tablet if nothing found)
439 + $mobile_key = $attribute_object_key . 'Mobile';
440 + $mobile_value =
441 + $clean( $attribute[ $mobile_key ] ?? null ) ??
442 + $clean( $attribute_default_value[ $mobile_key ] ?? null ) ??
443 + $tablet_value;
444 +
445 + // Return based on device
446 + switch ( $device ) {
447 + case 'Mobile':
448 + return $mobile_value;
449 + case 'Tablet':
450 + return $tablet_value;
451 + default:
452 + return $desktop_value;
453 + }
454 + }
455 +
456 + public static function is_gutenberg_editor() {
457 + global $pagenow;
458 + if ( $pagenow === 'post.php' || $pagenow === 'post-new.php' ) {
459 + return true;
460 + }
461 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended
462 + return ( isset( $_GET['context'] ) && 'edit' === $_GET['context'] ) || ( isset( $_GET['action'] ) && 'edit' === $_GET['action'] );
463 + }
464 + public static function attr_shortcode( $attr_array ) {
465 + $html_attr = '';
466 + foreach ( $attr_array as $attr_name => $attr_val ) {
467 + if ( empty( $attr_val ) ) {
468 + continue;
469 + }
470 + if ( is_array( $attr_val ) ) {
471 + $html_attr .= $attr_name . '="' . implode( ',', $attr_val ) . '" ';
472 + } else {
473 + $html_attr .= $attr_name . '="' . $attr_val . '" ';
474 + }
475 + }
476 + return $html_attr;
477 + }
478 +
479 + public static function get_attribute_value( $attributes, $attribute_name ) {
480 + return isset( $attributes[ $attribute_name ] ) ? $attributes[ $attribute_name ] : '';
481 + }
482 +
483 + public static function get_terms_list( $taxonomy = 'category' ) {
484 + $options = [];
485 + $terms = get_terms( [
486 + 'taxonomy' => $taxonomy,
487 + 'hide_empty' => true,
488 + ] );
489 +
490 + if ( ! empty( $terms ) && ! is_wp_error( $terms ) ) {
491 + foreach ( $terms as $term ) {
492 + $options[] = [
493 + 'label' => $term->name,
494 + 'value' => $term->term_id,
495 + ];
496 + }
497 + }
498 +
499 + return $options;
500 + }
501 +
502 + public static function get_author_data( $post_id, $author_id = false ) {
503 + $author_id = $author_id ?: get_post_field( 'post_author', $post_id );
504 + $user = get_userdata( $author_id );
505 +
506 + if ( ! $user ) {
507 + wp_send_json_error( 'Author not found.', 404 );
508 + }
509 +
510 + $data = [
511 + 'author_id' => $author_id,
512 + 'author_name' => sanitize_text_field( $user->display_name ),
513 + 'author_posts_count' => count_user_posts( $author_id ),
514 + 'author_posts_url' => esc_url( get_author_posts_url( $author_id ) ),
515 + 'author_profile_picture_url' => esc_url( get_avatar_url( $author_id ) ),
516 + 'author_bio' => sanitize_text_field( get_user_meta( $author_id, 'description', true ) ),
517 + 'author_email' => sanitize_email( $user->user_email ),
518 + 'author_website' => esc_url( $user->user_url ),
519 + 'author_first_name' => sanitize_text_field( get_user_meta( $author_id, 'first_name', true ) ),
520 + 'author_last_name' => sanitize_text_field( get_user_meta( $author_id, 'last_name', true ) )
521 + ];
522 +
523 + return $data;
524 + }
525 +
526 + public static function get_icon_picker_attribute( $attributePrefix = 'icon', $defaultValue = [] ) {
527 + $svgPathKey = $attributePrefix . 'SvgPath';
528 + $svgViewBoxKey = $attributePrefix . 'SvgViewBox';
529 + $svgClassKey = $attributePrefix . 'Class';
530 +
531 + $attribute = [
532 + $svgPathKey => [
533 + 'type' => 'string',
534 + 'source' => 'attribute',
535 + 'selector' => 'svg.ablocks-svg-icon path',
536 + 'attribute' => 'd',
537 + ],
538 + $svgViewBoxKey => [
539 + 'type' => 'string',
540 + 'source' => 'attribute',
541 + 'selector' => 'svg.ablocks-svg-icon',
542 + 'attribute' => 'viewBox',
543 + ],
544 + $svgClassKey => [
545 + 'type' => 'string',
546 + ],
547 + ];
548 +
549 + if ( isset( $defaultValue['path'] ) && isset( $defaultValue['viewBox'] ) ) {
550 + $attribute[ $svgPathKey ]['default'] = $defaultValue['path'];
551 + $attribute[ $svgViewBoxKey ]['default'] = $defaultValue['viewBox'];
552 + }
553 + if ( isset( $defaultValue['className'] ) ) {
554 + $attribute[ $svgClassKey ]['default'] = $defaultValue['className'];
555 + }
556 + return $attribute;
557 + }
558 +
559 + public static function get_terms_for_post( $taxonomy, $post_id ) {
560 + $terms = wp_get_object_terms( $post_id, $taxonomy, [ 'fields' => 'all' ] );
561 +
562 + if ( is_wp_error( $terms ) ) {
563 + return [];
564 + }
565 +
566 + return array_map( function( $term ) {
567 + return [
568 + 'id' => $term->term_id,
569 + 'name' => $term->name,
570 + 'slug' => $term->slug,
571 + ];
572 + }, $terms );
573 + }
574 +
575 + public static function get_taxonomies_data_for_post_type( $post_type ) {
576 + $all_taxonomies = get_object_taxonomies( $post_type, 'objects' );
577 + return array_values(array_map( function( $taxonomy ) {
578 + return [
579 + 'value' => $taxonomy->name,
580 + 'label' => $taxonomy->label,
581 + ];
582 + }, $all_taxonomies ));
583 + }
584 +
585 + public static function get_post_excerpt( $post_id, $length = false ) {
586 + $excerpt = get_the_excerpt( $post_id );
587 + if ( $length ) {
588 + return wp_trim_words( $excerpt, $length, '...' );
589 + }
590 + return $excerpt;
591 + }
592 +
593 + public static function get_post_terms_as_string( $post_id, $taxonomy, $separator = ', ' ) {
594 + $terms = wp_get_post_terms( $post_id, $taxonomy );
595 +
596 + if ( ! is_wp_error( $terms ) && ! empty( $terms ) ) {
597 + $term_names = wp_list_pluck( $terms, 'name' );
598 + return implode( $separator, $term_names );
599 + }
600 +
601 + return '';
602 + }
603 +
604 + public static function get_post_time_date( $post_id, $dynamicContentAttribute, $is_time = false ) {
605 + $dateTimeType = $dynamicContentAttribute['dateTimeType'];
606 + $dateTimeFormat = $dynamicContentAttribute['dateTimeFormat'];
607 + $customDateTimeFormat = $dynamicContentAttribute['customDateTimeFormat'];
608 +
609 + $format = $dateTimeFormat;
610 + $defaultFormat = $is_time ? 'g:i a' : 'j M, Y';
611 + if ( ! $dateTimeFormat ) {
612 + $format = $defaultFormat;
613 + } elseif ( 'custom' === $dateTimeFormat ) {
614 + $format = $customDateTimeFormat || $defaultFormat;
615 + }
616 +
617 + $date_time = '';
618 + if ( ! $dateTimeType || 'post_published' === $dateTimeType ) {
619 + $date_time = get_the_date( $format, $post_id );
620 + } elseif ( 'post_modified' === $dateTimeType ) {
621 + $date_time = get_the_modified_date( $format, $post_id );
622 + }
623 +
624 + return $date_time;
625 + }
626 +
627 + public static function is_fse_theme() {
628 + return function_exists( 'wp_is_block_theme' ) && wp_is_block_theme();
629 + }
630 +
631 + public static function check_post_type_from_admin( $post_type ) {
632 + global $post;
633 + if ( is_admin() ) {
634 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended
635 + if ( $post && get_post_type( $post ) === $post_type ) {
636 + return true;
637 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended
638 + } elseif ( isset( $_GET['post_type'] ) && $_GET['post_type'] === $post_type ) {
639 + return true;
640 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended
641 + } elseif ( isset( $_GET['post'] ) ) {
642 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended
643 + $queried_post_type = get_post_type( sanitize_text_field( wp_unslash( $_GET['post'] ) ) );
644 + if ( $queried_post_type === $post_type ) {
645 + return true;
646 + }
647 + }
648 + }
649 + return false;
650 + }
651 +
652 + public static function get_content_by_object_id( string $id_or_fse_slug ) : ?string {
653 + if ( is_numeric( $id_or_fse_slug ) ) {
654 + if (
655 + ! current_user_can( 'edit_post', $id_or_fse_slug ) &&
656 + get_post_status( $id_or_fse_slug ) !== 'publish'
657 + ) {
658 + return null;
659 + }
660 + return get_post_field( 'post_content', intval( $id_or_fse_slug ) );
661 + } elseif (
662 + ! empty( $template = get_block_template( $id_or_fse_slug, 'wp_template_part' ) ) ||
663 + ! empty( $template = get_block_template( $id_or_fse_slug ) )
664 + ) {
665 + return $template->content;
666 + }
667 + return null;
668 + }
669 +
670 + public static function get_block_attributes( string $post_id, string $block_id, string $block_name ) : array {
671 + // Cache parsed blocks per object id for the request — this is called once
672 + // per loop/REST lookup and would otherwise re-fetch + re-parse the whole
673 + // post content every time.
674 + static $parsed_cache = [];
675 + if ( ! array_key_exists( $post_id, $parsed_cache ) ) {
676 + $post_content = self::get_content_by_object_id( $post_id );
677 + $parsed_cache[ $post_id ] = ( ! is_null( $post_content ) && is_array( $blocks = parse_blocks( $post_content ) ) )
678 + ? $blocks
679 + : null;
680 + }
681 + if ( is_array( $parsed_cache[ $post_id ] ) ) {
682 + return self::get_block_attributes_recursive( $block_id, $block_name, $parsed_cache[ $post_id ] );
683 + }
684 + return [];
685 + }
686 +
687 + public static function get_block_attributes_recursive( string $block_id, string $block_name, array $blocks ) : array {
688 + foreach ( $blocks as $block ) {
689 +
690 + if (
691 + ( $block['attrs']['block_id'] ?? '' ) === $block_id &&
692 + ( $block['blockName'] ?? '' ) === $block_name
693 + ) {
694 + return [
695 + 'parentAttributes' => $block['attrs'] ?? [],
696 + 'innerBlocks' => self::extract_inner_blocks( $block['innerBlocks'] ?? [] ),
697 + ];
698 + }
699 +
700 + if (
701 + array_key_exists( 'innerBlocks', $block ) &&
702 + is_array( $block['innerBlocks'] ) &&
703 + count( $block['innerBlocks'] ) > 0
704 + ) {
705 + $data = self::get_block_attributes_recursive(
706 + $block_id,
707 + $block_name,
708 + $block['innerBlocks']
709 + );
710 + if ( ! empty( $data ) ) {
711 + return $data;
712 + }
713 + }
714 +
715 + if (
716 + isset( $block['blockName'] ) &&
717 + $block['blockName'] === 'core/template-part' &&
718 + ! empty( $block['attrs']['slug'] )
719 + ) {
720 + $part_slug = $block['attrs']['theme'] . '//' . $block['attrs']['slug'];
721 + $data = self::get_block_attributes( $part_slug, $block_id, $block_name );
722 + if ( ! empty( $data ) ) {
723 + return $data;
724 + }
725 + }
726 + }//end foreach
727 + return [];
728 + }
729 +
730 + public static function extract_inner_blocks( $innerBlocks ) {
731 + return array_map(function ( $inner_block ) {
732 + $block_data = [
733 + 'blockName' => $inner_block['blockName'],
734 + 'attributes' => $inner_block['attrs'],
735 + ];
736 + if ( ! empty( $inner_block['innerBlocks'] ) ) {
737 + $block_data['innerBlocks'] = self::extract_inner_blocks( $inner_block['innerBlocks'] );
738 + }
739 + return $block_data;
740 + }, $innerBlocks);
741 + }
742 +
743 + public static function generate_schema_using_form_data( $customFields ) {
744 + $schema = [];
745 +
746 + foreach ( $customFields as $inputField ) {
747 + // Check if 'name' exists in the input field
748 + if ( isset( $inputField['name'] ) ) {
749 + $field_name = $inputField['name'];
750 + $input_type = $inputField['inputType'] ?? 'text'; // Default to 'text' if not specified
751 +
752 + // Map the input types to schema types
753 + switch ( $input_type ) {
754 + case 'Text':
755 + $schema[ $field_name ] = 'string';
756 + break;
757 + case 'Email':
758 + $schema[ $field_name ] = 'email';
759 + break;
760 + case 'Password':
761 + $schema[ $field_name ] = 'string';
762 + break;
763 + case 'Number':
764 + $schema[ $field_name ] = 'number';
765 + break;
766 + case 'Url':
767 + $schema[ $field_name ] = 'url';
768 + break;
769 + case 'Boolean':
770 + $schema[ $field_name ] = 'boolean';
771 + break;
772 + case 'Textarea':
773 + $schema[ $field_name ] = 'textarea';
774 + break;
775 + default:
776 + $schema[ $field_name ] = 'string'; // Default to string for unknown types
777 + break;
778 + }//end switch
779 + }//end if
780 + }//end foreach
781 +
782 + return $schema;
783 + }
784 +
785 + public static function sorted_input_fields_by_input_type( $blockData ) {
786 + $sorted_custom_fields = [];
787 + foreach ( $blockData as $block ) {
788 + $name = $block['attributes']['name'] ?? null;
789 + $inputType = $block['attributes']['inputType'] ?? null;
790 +
791 + if ( $name && isset( $custom_fields[ $name ] ) ) {
792 + $sorted_custom_fields[] = [
793 + 'name' => $name,
794 + 'inputType' => $inputType,
795 + 'value' => $custom_fields[ $name ]
796 + ];
797 + }
798 + }
799 +
800 + return $sorted_custom_fields;
801 + }
802 +
803 +
804 + public static function render_svg_icon_using_attr( $attributes = array() ) {
805 + $default_attributes = array(
806 + 'path' => '',
807 + 'viewBox' => '0 0 24 24',
808 + 'className' => 'icon-class',
809 + 'width' => '24',
810 + 'height' => '24',
811 + );
812 +
813 + // Merge passed attributes with default values
814 + $attributes = array_merge( $default_attributes, $attributes );
815 +
816 + // Sanitize attributes for safety
817 + $path = esc_attr( $attributes['path'] );
818 + $viewBox = esc_attr( $attributes['viewBox'] );
819 + $className = esc_attr( $attributes['className'] );
820 + $width = esc_attr( $attributes['width'] );
821 + $height = esc_attr( $attributes['height'] );
822 +
823 + // Output the SVG
824 + return '
825 + <svg
826 + xmlns="http://www.w3.org/2000/svg"
827 + viewBox="' . $viewBox . '"
828 + class="' . $className . '"
829 + width="' . $width . '"
830 + height="' . $height . '">
831 + <path d="' . $path . '"></path>
832 + </svg>';
833 + }
834 +
835 + public static function get_template( $template_name, $args = array(), $template_path = '', $default_path = '' ) {
836 + $template = false;
837 +
838 + if ( ! $template ) {
839 + $template = self::locate_template( $template_name, $template_path, $default_path );
840 + }
841 +
842 + // Allow 3rd party plugin filter template file from their plugin.
843 + $filter_template = apply_filters( 'ablocks/get_template', $template, $template_name, $args, $template_path, $default_path );
844 +
845 + if ( $filter_template !== $template ) {
846 + if ( ! file_exists( $filter_template ) ) {
847 + /* translators: %s template */
848 + wc_doing_it_wrong( __FUNCTION__, sprintf( __( '%s does not exist.', 'ablocks' ), '<code>' . $filter_template . '</code>' ), '1.0.0' );
849 +
850 + return;
851 + }
852 + $template = $filter_template;
853 + }
854 +
855 + $action_args = array(
856 + 'template_name' => $template_name,
857 + 'template_path' => $template_path,
858 + 'located' => $template,
859 + 'args' => $args,
860 + );
861 +
862 + if ( ! empty( $args ) && is_array( $args ) ) {
863 + if ( isset( $args['action_args'] ) ) {
864 + wc_doing_it_wrong(
865 + __FUNCTION__,
866 + __( 'action_args should not be overwritten when calling ablocks/get_template.', 'ablocks' ),
867 + '1.0.0'
868 + );
869 + unset( $args['action_args'] );
870 + }
871 + extract( $args ); // @codingStandardsIgnoreLine
872 + }
873 +
874 + do_action( 'ablocks/before_template_part', $action_args['template_name'], $action_args['template_path'], $action_args['located'], $action_args['args'] );
875 + include $action_args['located'];
876 +
877 + do_action( 'ablocks/after_template_part', $action_args['template_name'], $action_args['template_path'], $action_args['located'], $action_args['args'] );
878 + }
879 +
880 + public static function locate_template( $template_name, $template_path = '', $default_path = '' ) {
881 + if ( ! $template_path ) {
882 + $template_path = self::template_path();
883 + }
884 +
885 + if ( ! $default_path ) {
886 + $default_path = self::plugin_path() . 'templates/';
887 + }
888 +
889 + if ( empty( $template ) ) {
890 + $template = locate_template(
891 + array(
892 + trailingslashit( $template_path ) . $template_name,
893 + $template_name,
894 + )
895 + );
896 + }
897 + if ( ! $template ) {
898 + $template = $default_path . $template_name;
899 + }
900 +
901 + // Return what we found.
902 + return apply_filters( 'ablocks/locate_template', $template, $template_name, $template_path );
903 + }
904 +
905 + public static function template_path() {
906 + return apply_filters( 'ablocks/template_path', 'ablocks/' );
907 + }
908 + public static function plugin_path() {
909 + return apply_filters( 'ablocks/plugin_path', ABLOCKS_ROOT_DIR_PATH );
910 + }
911 +
912 + public static function get_addon_active_status( $addon_name, $is_pro = false ) {
913 + global $ablocks_addons;
914 + if ( $is_pro && ! self::is_active_ablocks_pro() ) {
915 + return false;
916 + }
917 + if ( isset( $ablocks_addons->{$addon_name} ) ) {
918 + return (bool) $ablocks_addons->{$addon_name};
919 + }
920 +
921 + return false;
922 + }
923 +
924 + public static function sanitize_checkbox_field( $boolean ) {
925 + return filter_var( sanitize_text_field( $boolean ), FILTER_VALIDATE_BOOLEAN );
926 + }
927 +
928 + public static function is_valid_site_url( string $url ): bool {
929 + return str_starts_with( $url, get_option( 'siteurl' ) );
930 + }
931 +
932 + public static function get_script_loading_strategy() {
933 + return 'defer';
934 + }
935 +
936 + public static function is_static_front_page( $post_id ) {
937 + $show_on_front = get_option( 'show_on_front' );
938 + $page_on_front = (int) get_option( 'page_on_front' );
939 + return ( $show_on_front === 'page' && $page_on_front === (int) $post_id );
940 + }
941 +
942 + public static function get_public_post_type_options() {
943 + $args = [
944 + 'public' => true,
945 + '_builtin' => true,
946 + ];
947 + $post_types = get_post_types( $args, 'objects' );
948 + unset( $post_types['attachment'] ); // Remove 'attachment' if present
949 +
950 + // Get custom post types
951 + $args['_builtin'] = false;
952 + $custom_post_types = get_post_types( $args, 'objects' );
953 +
954 + // Allow filters to modify the combined post types
955 + $all_post_types = apply_filters(
956 + 'ablocks_theme_builder/location_rule_post_types',
957 + array_merge( $post_types, $custom_post_types )
958 + );
959 +
960 + // Format result
961 + $result = [];
962 + foreach ( $all_post_types as $post_type => $post_type_obj ) {
963 + $result[] = [
964 + 'label' => $post_type_obj->label,
965 + 'value' => $post_type,
966 + ];
967 + }
968 +
969 + return $result;
970 + }
971 +
972 + public static function clear_third_party_plugin_cache() {
973 +
974 + // Breeze
975 + try {
976 + if ( class_exists( 'Breeze_Purge' ) ) {
977 + \Breeze_Purge::breeze_cache_flush();
978 + }
979 + } catch ( \Throwable $e ) {
980 + // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
981 + error_log( 'Breeze Cache Clear Failed: ' . $e->getMessage() );
982 + }
983 +
984 + // W3 Total Cache
985 + try {
986 + if ( function_exists( 'w3tc_flush_all' ) ) {
987 + \w3tc_flush_all();
988 + }
989 + } catch ( \Throwable $e ) {
990 + // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
991 + error_log( 'W3 Total Cache Clear Failed: ' . $e->getMessage() );
992 + }
993 +
994 + // WP Super Cache
995 + try {
996 + if ( function_exists( 'wp_cache_clear_cache' ) ) {
997 + \wp_cache_clear_cache();
998 + }
999 + } catch ( \Throwable $e ) {
1000 + // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
1001 + error_log( 'WP Super Cache Clear Failed: ' . $e->getMessage() );
1002 + }
1003 +
1004 + // LiteSpeed Cache
1005 + try {
1006 + if ( class_exists( 'LiteSpeed_Cache_API' ) ) {
1007 + \LiteSpeed_Cache_API::purge_all();
1008 + }
1009 + } catch ( \Throwable $e ) {
1010 + // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
1011 + error_log( 'LiteSpeed Cache Clear Failed: ' . $e->getMessage() );
1012 + }
1013 +
1014 + // WP Fastest Cache
1015 + try {
1016 + if ( class_exists( 'WpFastestCache' ) ) {
1017 + $wpfc = new \WpFastestCache();
1018 + $wpfc->deleteCache();
1019 + }
1020 + } catch ( \Throwable $e ) {
1021 + // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
1022 + error_log( 'WP Fastest Cache Clear Failed: ' . $e->getMessage() );
1023 + }
1024 +
1025 + // Autoptimize
1026 + try {
1027 + if ( function_exists( 'autoptimize_clearall' ) ) {
1028 + \autoptimize_clearall();
1029 + }
1030 + } catch ( \Throwable $e ) {
1031 + // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
1032 + error_log( 'Autoptimize Clear Failed: ' . $e->getMessage() );
1033 + }
1034 + }
1035 +}