PluginProbe
Content Blocks Builder – Create blocks, repeater blocks with carousel, grid, popup layouts / 2.8.8
Content Blocks Builder – Create blocks, repeater blocks with carousel, grid, popup layouts v2.8.8
2.8.14 2.8.13 2.8.12 2.7.5 2.7.6 2.7.7 2.7.8 2.7.9 2.8.0 2.8.1 2.8.10 2.8.11 2.8.2 2.8.3 2.8.4 2.8.5 2.8.6 2.8.7 2.8.8 2.8.9 trunk 1.0.1 1.0.2 1.1.0 1.1.1 All 147 releases
content-blocks-builder / includes / custom-style.php

custom-style.php in Content Blocks Builder – Create blocks, repeater blocks with carousel, grid, popup layouts 2.8.8, at includes/custom-style.php

3,989 lines 130.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * The custom style component
4 *
5 * @package BoldBlocks
6 * @author Phi Phan <mrphipv@gmail.com>
7 * @copyright Copyright (c) 2022, Phi Phan
8 */
9
10 namespace BoldBlocks;
11
12 // Exit if accessed directly.
13 defined( 'ABSPATH' ) || exit;
14
15 if ( ! class_exists( CustomStyle::class ) ) :
16 /**
17 * The controller class for style.
18 */
19 class CustomStyle extends CoreComponent {
20 /**
21 * The style handle for feature styles
22 *
23 * @var array
24 */
25 private $feature_style_handle = 'cbb-dynamic-style';
26
27 /**
28 * Store the context of styles
29 *
30 * @var array
31 */
32 private $style_contexts = [
33 'loops' => [],
34 'loop' => '',
35 'post' => 0,
36 'counter' => 0,
37 'content' => false,
38 'content_counter' => 0,
39 ];
40
41 /**
42 * Store the ID of query loop with carousel or grid layout
43 *
44 * @var array
45 */
46 private $query_ids = [];
47
48 /**
49 * The cached styles
50 *
51 * @var array
52 */
53 private $styles = [];
54
55 /**
56 * The refined breakpoints
57 *
58 * @var array
59 */
60 private $breakpoints = [];
61
62 /**
63 * The list of non-cbb blocks that support dynamic style
64 *
65 * @var array
66 */
67 public $non_cbb_blocks = [ 'core/template-part' ];
68
69 /**
70 * Run main hooks
71 *
72 * @return void
73 */
74 public function run() {
75 // Register setting fields.
76 add_action( 'init', [ $this, 'register_setting_fields' ] );
77
78 // Register style handle.
79 add_action( 'init', [ $this, 'register_feature_style' ] );
80
81 // Render block style.
82 add_filter( 'render_block', [ $this, 'render_block_style' ], 10, 3 );
83
84 // Render link to post first.
85 add_filter( 'render_block', [ $this, 'build_block_link_to_post' ], 20, 3 );
86
87 // Build dynamic block overlay.
88 add_filter( 'render_block', [ $this, 'build_block_overlay' ], 20, 3 );
89
90 // Build dynamic block background.
91 add_filter( 'render_block', [ $this, 'build_block_background' ], 30, 3 );
92
93 // Build animations.
94 add_filter( 'render_block', [ $this, 'build_block_animations' ], 30, 3 );
95
96 // Render accordions.
97 add_filter( 'render_block_data', [ $this, 'render_accordion_block_data' ] );
98
99 // Render accordion item blocks.
100 add_filter( 'render_block', [ $this, 'render_accordion_item_block' ], 10, 3 );
101
102 // Render grid style for the query loop block.
103 add_filter( 'render_block_core/query', [ $this, 'render_query_loop_grid_style' ], 10, 3 );
104
105 // Render carousel settings for the query loop block.
106 add_filter( 'render_block_core/query', [ $this, 'render_query_loop_carousel_layout' ], 10, 3 );
107
108 // Enqueue block styles.
109 add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_responsive_styles' ] );
110
111 // Enqueue breakpoints.
112 add_action( 'init', [ $this, 'enqueue_reponsive_breakpoints' ] );
113
114 // Update loop id.
115 add_filter( 'render_block_context', [ $this, 'update_loop_id' ], 10, 2 );
116
117 // Reset loop id.
118 add_filter( 'render_block', [ $this, 'reset_loop_id' ], 10, 2 );
119
120 // Render preload style for carousels.
121 add_filter( 'cbb_get_block_style', [ $this, 'get_carousel_preload_style' ], 10, 2 );
122
123 // Support dynamic style for none-cbb blocks.
124 add_filter( 'cbb_support_block_style', [ $this, 'support_block_style' ], 10, 2 );
125
126 // Set a block layout for non-cbb blocks.
127 add_filter( 'cbb_get_block_layout', [ $this, 'get_block_layout' ], 10, 2 );
128
129 // Render sticky data for the core/template-part block.
130 add_filter( 'render_block_core/template-part', [ $this, 'render_template_part_sticky' ], 10, 3 );
131
132 // Render settings for carousels.
133 add_filter( 'render_block', [ $this, 'render_carousel_settings' ], 30, 3 );
134
135 // Migrate CBB class for old blocks.
136 add_filter( 'render_block', [ $this, 'migrate_cbb_class' ], 30, 3 );
137 }
138
139 /**
140 * Register custom setting fields
141 *
142 * @return void
143 */
144 public function register_setting_fields() {
145 // phpcs:ignore PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic
146 register_setting(
147 'cbb',
148 'cbb_breakpoints',
149 [
150 'type' => 'array',
151 'show_in_rest' => [
152 'name' => 'CBBBreakpoints',
153 'schema' => [
154 'items' => [
155 'type' => 'object',
156 'properties' => [
157 'breakpoint' => [
158 'type' => 'number',
159 ],
160 'prefix' => [
161 'type' => 'string',
162 ],
163 ],
164 'additionalProperties' => [
165 'type' => 'string',
166 ],
167 ],
168 ],
169 ],
170 'default' => [
171 [
172 'breakpoint' => 576,
173 'prefix' => 'sm',
174 ],
175 [
176 'breakpoint' => 768,
177 'prefix' => 'md',
178 ],
179 [
180 'breakpoint' => 1024,
181 'prefix' => 'lg',
182 ],
183 ],
184 ]
185 );
186 }
187
188 /**
189 * Enqueue responsive breakpoints
190 *
191 * @return void
192 */
193 public function enqueue_reponsive_breakpoints() {
194 // Custom blocks handle.
195 $custom_blocks_handler = $this->the_plugin_instance->get_component( CustomBlocks::class );
196
197 // Get breakpoint setting.
198 $breakpoints = $this->get_breakpoints();
199
200 // Get breakpoint scripts.
201 $breakpoint_script = 'var CBBBreakpoints=' . wp_json_encode( $breakpoints );
202
203 // Load JS breakpoints.
204 wp_add_inline_script( $custom_blocks_handler->custom_blocks_handle, $breakpoint_script, 'before' );
205 wp_add_inline_script( $custom_blocks_handler->carousel_blocks_frontend_handle, $breakpoint_script, 'before' );
206 }
207
208 /**
209 * Register a handle for enqueue block styles.
210 *
211 * @return void
212 */
213 public function register_feature_style() {
214 // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
215 wp_register_style( $this->feature_style_handle, '' );
216 }
217
218 /**
219 * Enqueue all feature styles
220 *
221 * @return void
222 */
223 public function enqueue_responsive_styles() {
224 if ( wp_is_block_theme() ) {
225 wp_enqueue_style( $this->feature_style_handle );
226 }
227 wp_add_inline_style( $this->feature_style_handle, $this->get_responsive_styles() );
228 }
229
230 /**
231 * Build responsive styles
232 *
233 * @return string
234 */
235 private function get_responsive_styles() {
236 $style = '';
237
238 $breakpoints = $this->get_breakpoints();
239 $md_breakpoint = absint( $breakpoints['md']['breakpoint'] ?? 768 );
240 $md_breakpoint_max = $md_breakpoint - 1;
241 $lg_breakpoint = absint( $breakpoints['lg']['breakpoint'] ?? 1024 );
242 $lg_breakpoint_max = $lg_breakpoint - 1;
243 $md_start = "@media(min-width:{$md_breakpoint}px){";
244 $lg_start = "@media(min-width:{$lg_breakpoint}px){";
245 $end = '}';
246
247 // Padding.
248 $padding_style = '.sm-cbb-padding-top{padding-top:var(--cbb--padding-top) !important;}.sm-cbb-padding-right{padding-right:var(--cbb--padding-right) !important;}.sm-cbb-padding-bottom{padding-bottom:var(--cbb--padding-bottom) !important;}.sm-cbb-padding-left{padding-left:var(--cbb--padding-left) !important;}';
249 $padding_style .= "{$md_start}.md-cbb-padding-top{padding-top:var(--cbb--padding-top) !important;}.md-cbb-padding-right{padding-right:var(--cbb--padding-right) !important;}.md-cbb-padding-bottom{padding-bottom:var(--cbb--padding-bottom) !important;}.md-cbb-padding-left{padding-left:var(--cbb--padding-left) !important;}{$end}";
250 $padding_style .= "{$lg_start}.lg-cbb-padding-top{padding-top:var(--cbb--padding-top) !important;}.lg-cbb-padding-right{padding-right:var(--cbb--padding-right) !important;}.lg-cbb-padding-bottom{padding-bottom:var(--cbb--padding-bottom) !important;}.lg-cbb-padding-left{padding-left:var(--cbb--padding-left) !important;}{$end}";
251 $style .= $padding_style;
252
253 // Margin.
254 $margin_style = '.sm-cbb-margin-top{margin-top:var(--cbb--margin-top) !important;}.sm-cbb-margin-right{margin-right:var(--cbb--margin-right) !important;}.sm-cbb-margin-bottom{margin-bottom:var(--cbb--margin-bottom) !important;}.sm-cbb-margin-left{margin-left:var(--cbb--margin-left) !important;}';
255 $margin_style .= "{$md_start}.md-cbb-margin-top{margin-top:var(--cbb--margin-top) !important;}.md-cbb-margin-right{margin-right:var(--cbb--margin-right) !important;}.md-cbb-margin-bottom{margin-bottom:var(--cbb--margin-bottom) !important;}.md-cbb-margin-left{margin-left:var(--cbb--margin-left) !important;}{$end}";
256 $margin_style .= "{$lg_start}.lg-cbb-margin-top{margin-top:var(--cbb--margin-top) !important;}.lg-cbb-margin-right{margin-right:var(--cbb--margin-right) !important;}.lg-cbb-margin-bottom{margin-bottom:var(--cbb--margin-bottom) !important;}.lg-cbb-margin-left{margin-left:var(--cbb--margin-left) !important;}{$end}";
257 $style .= $margin_style;
258
259 // Block gap.
260 $block_gap_style = '.sm-cbb-block-gap > *{margin-block-start:0;margin-block-end:0;}.sm-cbb-block-gap > * + *{margin-block-start:var(--cbb--block-gap) !important;margin-block-end:0;}';
261 $block_gap_style .= "{$md_start}.md-cbb-block-gap > *{margin-block-start:0;margin-block-end:0;}.md-cbb-block-gap > * + *{margin-block-start:var(--cbb--block-gap) !important;margin-block-end:0;}{$end}";
262 $block_gap_style .= "{$lg_start}.lg-cbb-block-gap > *{margin-block-start:0;margin-block-end:0;}.lg-cbb-block-gap > * + *{margin-block-start:var(--cbb--block-gap) !important;margin-block-end:0;}{$end}";
263 $style .= $block_gap_style;
264
265 // Border.
266 $border_style = '.sm-cbb-border-top{border-top:var(--cbb--border-top) !important;}.sm-cbb-border-right{border-right:var(--cbb--border-right) !important;}.sm-cbb-border-bottom{border-bottom:var(--cbb--border-bottom) !important;}.sm-cbb-border-left{border-left:var(--cbb--border-left) !important;}';
267 $border_style .= "{$md_start}.md-cbb-border-top{border-top:var(--cbb--border-top) !important;}.md-cbb-border-right{border-right:var(--cbb--border-right) !important;}.md-cbb-border-bottom{border-bottom:var(--cbb--border-bottom) !important;}.md-cbb-border-left{border-left:var(--cbb--border-left) !important;}{$end}";
268 $border_style .= "{$lg_start}.lg-cbb-border-top{border-top:var(--cbb--border-top) !important;}.lg-cbb-border-right{border-right:var(--cbb--border-right) !important;}.lg-cbb-border-bottom{border-bottom:var(--cbb--border-bottom) !important;}.lg-cbb-border-left{border-left:var(--cbb--border-left) !important;}{$end}";
269 $style .= $border_style;
270
271 // Border radius.
272 $radius_style = '.sm-cbb-border-radius{overflow:hidden;border-radius: var(--cbb--border-radius) !important;}';
273 $radius_style .= "{$md_start}.md-cbb-border-radius{overflow:hidden;border-radius: var(--cbb--border-radius) !important;}{$end}";
274 $radius_style .= "{$lg_start}.lg-cbb-border-radius{overflow:hidden;border-radius: var(--cbb--border-radius) !important;}{$end}";
275 $style .= $radius_style;
276
277 // Width.
278 $width_style = '.sm-cbb-width{width:var(--cbb--width) !important;}';
279 $width_style .= "{$md_start}.md-cbb-width{width:var(--cbb--width) !important;}{$end}";
280 $width_style .= "{$lg_start}.lg-cbb-width{width:var(--cbb--width) !important;}{$end}";
281 $style .= $width_style;
282
283 // Height.
284 $height_style = '.sm-cbb-height{height:var(--cbb--height) !important;}';
285 $height_style .= "{$md_start}.md-cbb-height{height:var(--cbb--height) !important;}{$end}";
286 $height_style .= "{$lg_start}.lg-cbb-height{height:var(--cbb--height) !important;}{$end}";
287 $style .= $height_style;
288
289 // Aspect ratio.
290 $aspect_ratio_style = '.sm-cbb-aspect-ratio{aspect-ratio:var(--cbb--aspect-ratio);}';
291 $aspect_ratio_style .= "{$md_start}.md-cbb-aspect-ratio{aspect-ratio:var(--cbb--aspect-ratio);}{$end}";
292 $aspect_ratio_style .= "{$lg_start}.lg-cbb-aspect-ratio{aspect-ratio:var(--cbb--aspect-ratio);}{$end}";
293 $style .= $aspect_ratio_style;
294
295 // Text alignment.
296 $text_align_style = '.sm-cbb-text-align{text-align:var(--cbb--text-align);}';
297 $text_align_style .= "{$md_start}.md-cbb-text-align{text-align:var(--cbb--text-align);}{$end}";
298 $text_align_style .= "{$lg_start}.lg-cbb-text-align{text-align:var(--cbb--text-align);}{$end}";
299 $style .= $text_align_style;
300
301 // Vertical alignment.
302 $v_align_style = '.sm-cbb-v-align.cbb-layout-grid{display:grid;align-content:var(--cbb--v-align);}.sm-cbb-v-align.cbb-layout-grid>*{width:100%;}.sm-cbb-v-align.cbb-align-items{align-items:var(--cbb--v-align);}.sm-cbb-v-align.cbb-align-self{align-self:var(--cbb--v-align);}';
303 $v_align_style .= "{$md_start}.md-cbb-v-align.cbb-layout-grid{display:grid;align-content:var(--cbb--v-align);}.md-cbb-v-align.cbb-layout-grid>*{width:100%;}.md-cbb-v-align.cbb-align-items{align-items:var(--cbb--v-align);}.md-cbb-v-align.cbb-align-self{align-self:var(--cbb--v-align);}{$end}";
304 $v_align_style .= "{$lg_start}.lg-cbb-v-align.cbb-layout-grid{display:grid;align-content:var(--cbb--v-align);}.lg-cbb-v-align.cbb-layout-grid>*{width:100%;}.lg-cbb-v-align.cbb-align-items{align-items:var(--cbb--v-align);}.lg-cbb-v-align.cbb-align-self{align-self:var(--cbb--v-align);}{$end}";
305 $style .= $v_align_style;
306
307 // Justify alignment.
308 $h_align_style = '.sm-cbb-h-align.cbb-layout-grid{display:grid;justify-content:var(--cbb--h-align);}.sm-cbb-h-align.cbb-layout-grid>*{width:auto;}.sm-cbb-h-align.cbb-justify-items{justify-items:var(--cbb--h-align);}.sm-cbb-h-align.cbb-justify-self{justify-self:var(--cbb--h-align);}';
309 $h_align_style .= "{$md_start}.md-cbb-h-align.cbb-layout-grid{display:grid;justify-content:var(--cbb--h-align);}.md-cbb-h-align.cbb-layout-grid>*{width:auto;}.md-cbb-h-align.cbb-justify-items{justify-items:var(--cbb--h-align);}.md-cbb-h-align.cbb-justify-self{justify-self:var(--cbb--h-align);}{$end}";
310 $h_align_style .= "{$lg_start}.lg-cbb-h-align.cbb-layout-grid{display:grid;justify-content:var(--cbb--h-align);}.lg-cbb-h-align.cbb-layout-grid>*{width:auto;}.lg-cbb-h-align.cbb-justify-items{justify-items:var(--cbb--h-align);}.lg-cbb-h-align.cbb-justify-self{justify-self:var(--cbb--h-align);}{$end}";
311 $style .= $h_align_style;
312
313 // Transform.
314 $transform_style = '.sm-cbb-transform{transform:var(--cbb--transform);transform-origin:var(--cbb--transform-origin)}';
315 $transform_style .= "{$md_start}.md-cbb-transform{transform:var(--cbb--transform);transform-origin:var(--cbb--transform-origin)}{$end}";
316 $transform_style .= "{$lg_start}.lg-cbb-transform{transform:var(--cbb--transform);transform-origin:var(--cbb--transform-origin)}{$end}";
317 $style .= $transform_style;
318
319 // Visibility.
320 $visibility_style = "@media(max-width:{$md_breakpoint_max}px){.cbb-hidden-sm{display:none !important;}}";
321 $visibility_style .= "@media(min-width:{$md_breakpoint}px) and (max-width:{$lg_breakpoint_max}px){.cbb-hidden-md{display:none !important;}}";
322 $visibility_style .= "@media(min-width:{$lg_breakpoint}px){.cbb-hidden-lg{display:none !important;}}";
323 $style .= $visibility_style;
324
325 // Grid: Columns, rows, auto-rows, & gap.
326 $grid_style = '.sm-cbb-grid-columns > *,.sm-cbb-grid-columns > * + *{margin:0}.sm-cbb-grid-columns{grid-template-columns:var(--cbb--grid-columns);}.sm-cbb-grid-rows{grid-template-rows:var(--cbb--grid-rows);}.sm-cbb-grid-auto-rows{grid-auto-rows:var(--cbb--grid-auto-rows);}.sm-cbb-grid-gap-column{column-gap:var(--cbb--grid-gap-column);}.sm-cbb-grid-gap-row{row-gap:var(--cbb--grid-gap-row);}';
327 $grid_style .= "{$md_start}.md-cbb-grid-columns > * {margin:0}.md-cbb-grid-columns{grid-template-columns:var(--cbb--grid-columns);}.md-cbb-grid-rows{grid-template-rows:var(--cbb--grid-rows);}.md-cbb-grid-auto-rows{grid-auto-rows:var(--cbb--grid-auto-rows);}.md-cbb-grid-gap-column{column-gap:var(--cbb--grid-gap-column);}.md-cbb-grid-gap-row{row-gap:var(--cbb--grid-gap-row);}{$end}";
328 $grid_style .= "{$lg_start}.lg-cbb-grid-columns > * {margin:0}.lg-cbb-grid-columns{grid-template-columns:var(--cbb--grid-columns);}.lg-cbb-grid-rows{grid-template-rows:var(--cbb--grid-rows);}.lg-cbb-grid-auto-rows{grid-auto-rows:var(--cbb--grid-auto-rows);}.lg-cbb-grid-gap-column{column-gap:var(--cbb--grid-gap-column);}.lg-cbb-grid-gap-row{row-gap:var(--cbb--grid-gap-row);}{$end}";
329 $style .= $grid_style;
330
331 // Grid item: columnSpan & rowSpan.
332 $grid_item_style = '.sm-cbb-grid-item-column{grid-column:var(--cbb--grid-item-column);}.sm-cbb-grid-item-row{grid-row:var(--cbb--grid-item-row);}.sm-cbb-grid-item-order{order:var(--cbb--grid-item-order);}';
333 $grid_item_style .= "{$md_start}.md-cbb-grid-item-column{grid-column:var(--cbb--grid-item-column);}.md-cbb-grid-item-row{grid-row:var(--cbb--grid-item-row);}.md-cbb-grid-item-order{order:var(--cbb--grid-item-order);}{$end}";
334 $grid_item_style .= "{$lg_start}.lg-cbb-grid-item-column{grid-column:var(--cbb--grid-item-column);}.lg-cbb-grid-item-row{grid-row:var(--cbb--grid-item-row);}.lg-cbb-grid-item-order{order:var(--cbb--grid-item-order);}{$end}";
335 $style .= $grid_item_style;
336
337 // Accordion gap.
338 $accordion_gap_style = '.sm-cbb-accordion-gap{display:flex;flex-direction:column;gap:var(--cbb--accordion-gap);}.sm-cbb-a-has-border > .is-accordion-item{border-top:var(--cbb--ab-top);}.sm-cbb-a-no-border > .is-accordion-item{border-top:0;}';
339 $accordion_gap_style .= "{$md_start}.md-cbb-accordion-gap{display:flex;flex-direction:column;gap:var(--cbb--accordion-gap);}.md-cbb-a-has-border > .is-accordion-item{border-top:var(--cbb--ab-top);}.md-cbb-a-no-border > .is-accordion-item{border-top:0;}{$end}";
340 $accordion_gap_style .= "{$lg_start}.lg-cbb-accordion-gap{display:flex;flex-direction:column;gap:var(--cbb--accordion-gap);}.lg-cbb-a-has-border > .is-accordion-item{border-top:var(--cbb--ab-top);}.lg-cbb-a-no-border > .is-accordion-item{border-top:0;}{$end}";
341 $style .= $accordion_gap_style;
342
343 // Accordion padding.
344 // We don't need padding style, because it has a default value.
345
346 // Sticky offset.
347 $sticky_offset_style = '.is-stick-to-top.sm-cbb-sticky-offset{top: var(--cbb--sticky-offset);}.is-stick-to-bottom.sm-cbb-sticky-offset{bottom: var(--cbb--sticky-offset);}';
348 $sticky_offset_style .= "{$md_start}.is-stick-to-top.md-cbb-sticky-offset{top: var(--cbb--sticky-offset);}.is-stick-to-bottom.md-cbb-sticky-offset{bottom: var(--cbb--sticky-offset);}{$end}";
349 $sticky_offset_style .= "{$lg_start}.is-stick-to-top.lg-cbb-sticky-offset{top: var(--cbb--sticky-offset);}.is-stick-to-bottom.lg-cbb-sticky-offset{bottom: var(--cbb--sticky-offset);}{$end}";
350 $style .= $sticky_offset_style;
351
352 // Offcanvas.
353 $offcanvas_style = "{$md_start}.is-offcanvas.placement-start,.is-offcanvas.placement-end{width:var(--bb--modal-width--md,25rem);}.is-offcanvas.placement-top,.is-offcanvas.placement-bottom{height:var(--bb--modal-height--md,30vh);}{$end}";
354 $offcanvas_style .= "{$lg_start}.is-offcanvas.placement-start,.is-offcanvas.placement-end{width:var(--bb--modal-width--lg,25rem);}.is-offcanvas.placement-top,.is-offcanvas.placement-bottom{height:var(--bb--modal-height--lg,30vh);}{$end}";
355 $style .= $offcanvas_style;
356
357 // Popover.
358 $popover_style = "{$md_start}.is-popover{width:var(--bb--modal-width--md,auto);height:var(--bb--modal-height--md,auto);}{$end}";
359 $popover_style .= "{$lg_start}.is-popover{width:var(--bb--modal-width--lg,auto);height:var(--bb--modal-height--lg,auto);}{$end}";
360 $style .= $popover_style;
361
362 // Modal.
363 $modal_style = "{$md_start}.is-modal > .bb-modal-dialog{width:var(--bb--modal-width--md,32rem);}.is-modal > .bb-modal-dialog[style*=\"--bb--modal-height--md:\"]{height:var(--bb--modal-height--md);}.modal--custom-position{align-items:var(--bb--modal-v-align--md);justify-content:var(--bb--modal-h-align--md);}{$end}";
364 $modal_style .= "{$lg_start}.is-modal > .bb-modal-dialog{width:var(--bb--modal-width--md,50rem);}.is-modal > .bb-modal-dialog[style*=\"--bb--modal-height--lg:\"]{height:var(--bb--modal-height--lg);}.modal--custom-position{align-items:var(--bb--modal-v-align--lg);justify-content:var(--bb--modal-h-align--lg);}{$end}";
365 $style .= $modal_style;
366
367 // Mobile background.
368 $background_style = '.cbb-mobile-image > .bb\:block-background--img{display:none !important;}.cbb-mobile-image > .is-mobile-image {display:block !important;}';
369 $background_style .= "@media(min-width:{$md_breakpoint}px){.cbb-mobile-image > .bb\:block-background--img{display:block !important;}.cbb-mobile-image > .is-mobile-image {display:none !important;}}";
370 $background_style .= "@media(max-width:{$md_breakpoint_max}px){.cbb-mobile-background{background-image: var(--cbb-mobile-background) !important;}}";
371 $style .= $background_style;
372
373 return $style;
374 }
375
376 /**
377 * Render style for all supported blocks
378 *
379 * @param string $block_content
380 * @param array $block
381 * @param WP_Block $block_instance
382 * @return string
383 */
384 public function render_block_style( $block_content, $block, $block_instance ) {
385 // Ignore admin side.
386 if ( is_admin() ) {
387 return $block_content;
388 }
389
390 // Bail if the block has no style.
391 if ( ! $this->has_style( $block, $block_instance ) ) {
392 return $block_content;
393 }
394
395 // Buil selector.
396 $selector = $this->generate_selector( $block['blockName'], $block_instance );
397
398 // Get custom style.
399 $is_in_cache = isset( $this->styles[ $selector ] );
400
401 if ( $is_in_cache ) {
402 $block_styles = $this->styles[ $selector ];
403 } else {
404 $block_styles = $this->get_block_style( $block, $selector, $block_instance );
405
406 // Add to the cache.
407 $this->styles[ $selector ] = $block_styles;
408 }
409
410 if ( empty( $block_styles['style'] ?? '' ) ) {
411 if ( isset( $block_styles['classes'] ) && count( $block_styles['classes'] ) > 1 ) {
412 // There is no style, but we have class-features like visibility.
413 $block_content = $this->add_class_to_block( $block_content, implode( ' ', $block_styles['classes'] ) );
414 }
415
416 return $block_content;
417 }
418
419 if ( ! wp_is_block_theme() ) {
420 // Enqueue style.
421 wp_enqueue_style( $this->feature_style_handle );
422 }
423
424 if ( ! $is_in_cache ) {
425 wp_add_inline_style( $this->feature_style_handle, $block_styles['style'] );
426 }
427
428 // Add selector to block wrapper element.
429 $block_content = $this->add_class_to_block( $block_content, implode( ' ', $block_styles['classes'] ) );
430
431 return $block_content;
432 }
433
434 /**
435 * Get block custom style
436 *
437 * @param array $block
438 * @param string $selector
439 * @param WP_Block $block_instance
440 * @return string
441 */
442 private function get_block_style( $block, $selector, $block_instance ) {
443 $style = '';
444 $class_selector = '.' . $selector;
445
446 $classes = [ $selector ];
447 $style_array = [];
448 $responsive_style_array = [];
449
450 // Get responsive settings.
451 $breakpoints = $this->get_breakpoints();
452
453 // Get the settings.
454 $settings = $block['attrs']['boldblocks'];
455
456 // Features.
457 $features = [
458 'padding' => [
459 'func_build_responsive_style' => [ $this, 'build_padding_style' ],
460 'group' => 'spacing',
461 ],
462 'margin' => [
463 'func_build_responsive_style' => [ $this, 'build_margin_style' ],
464 'group' => 'spacing',
465 ],
466 'blockGap' => [
467 'func_build_responsive_style' => [ $this, 'build_block_gap_style' ],
468 'group' => 'spacing',
469 ],
470 'border' => [ 'func_build_responsive_style' => [ $this, 'build_border_style' ] ],
471 'borderRadius' => [ 'func_build_responsive_style' => [ $this, 'build_border_radius_style' ] ],
472 'width' => [ 'func_build_responsive_style' => [ $this, 'build_width_style' ] ],
473 'height' => [ 'func_build_responsive_style' => [ $this, 'build_height_style' ] ],
474 'aspectRatio' => [ 'func_build_responsive_style' => [ $this, 'build_aspect_ratio_style' ] ],
475 'textAlignment' => [ 'func_build_responsive_style' => [ $this, 'build_text_align_style' ] ],
476 'verticalAlignment' => [
477 'func_build_responsive_style' => [ $this, 'build_vertical_align_style' ],
478 'func_build_dependent_style' => [ $this, 'build_vertical_align_dependent_style' ],
479 ],
480 'justifyAlignment' => [
481 'func_build_responsive_style' => [ $this, 'build_justify_align_style' ],
482 'func_build_dependent_style' => [ $this, 'build_justify_align_dependent_style' ],
483 ],
484 'transform' => [
485 'func_build_responsive_style' => [ $this, 'build_transform_style' ],
486 'func_build_dependent_style' => [ $this, 'build_transform_dependent_style' ],
487 ],
488 'hidden' => [
489 'func_build_style' => [ $this, 'build_hidden_style' ],
490 'group' => 'visibility',
491 ],
492 'columns' => [
493 'func_build_responsive_style' => [ $this, 'build_columns_style' ],
494 'group' => 'grid',
495 'layout_type' => 'grid',
496 ],
497 'rows' => [
498 'func_build_responsive_style' => [ $this, 'build_rows_style' ],
499 'group' => 'grid',
500 'layout_type' => 'grid',
501 ],
502 'autoRows' => [
503 'func_build_responsive_style' => [ $this, 'build_auto_rows_style' ],
504 'group' => 'grid',
505 'layout_type' => 'grid',
506 ],
507 'gap' => [
508 'func_build_responsive_style' => [ $this, 'build_gap_style' ],
509 'group' => 'grid',
510 'layout_type' => 'grid',
511 ],
512 'columnSpan' => [
513 'func_build_responsive_style' => [ $this, 'build_column_span_style' ],
514 'group' => 'gridItem',
515 'layout_type' => 'gridItem',
516 ],
517 'rowSpan' => [
518 'func_build_responsive_style' => [ $this, 'build_row_span_style' ],
519 'group' => 'gridItem',
520 'layout_type' => 'gridItem',
521 ],
522 'accordionGap' => [
523 'func_build_responsive_style' => [ $this, 'build_accordion_gap_style' ],
524 'func_build_dependent_style' => [ $this, 'build_accordion_gap_dependent_style' ],
525 'group' => 'accordion',
526 'setting_name' => 'gap',
527 'layout_type' => 'accordion',
528 ],
529 'accordionPadding' => [
530 'func_build_responsive_style' => [ $this, 'build_accordion_padding_style' ],
531 'group' => 'accordion',
532 'setting_name' => 'padding',
533 'layout_type' => 'accordion',
534 ],
535 'accordionBorder' => [
536 'func_build_style' => [ $this, 'build_accordion_border_style' ],
537 'group' => 'accordion',
538 'setting_name' => 'border',
539 'layout_type' => 'accordion',
540 ],
541 'accordionRadius' => [
542 'func_build_style' => [ $this, 'build_accordion_radius_style' ],
543 'group' => 'accordion',
544 'setting_name' => 'radius',
545 'layout_type' => 'accordion',
546 ],
547 'stickyOffset' => [
548 'func_build_responsive_style' => [ $this, 'build_sticky_offset_style' ],
549 'group' => 'sticky',
550 'setting_name' => 'offset',
551 ],
552 ];
553
554 $block_layout = apply_filters( 'cbb_get_block_layout', $block_instance->block_type->supports['layoutType'] ?? '', $block );
555 foreach ( $features as $feature => $feature_args ) {
556 if ( isset( $feature_args['layout_type'] ) && $block_layout !== $feature_args['layout_type'] ) {
557 continue;
558 }
559
560 if ( ! in_array( $feature, [ 'border', 'hidden' ], true ) && 'accordionItem' === $block_layout ) {
561 continue;
562 }
563
564 if ( ! in_array( $feature, [ 'border', 'hidden', 'accordionGap', 'accordionPadding', 'accordionBorder', 'accordionRadius' ], true ) && 'accordion' === $block_layout ) {
565 continue;
566 }
567
568 if ( 'stickyOffset' === $feature ) {
569 // Not supported block layouts.
570 if ( ! in_array( $block_layout, [ 'standalone', 'gridItem' ], true ) ) {
571 continue;
572 }
573
574 // Not enable sticky or enabling scrolling up.
575 if ( ! ( $settings['sticky']['enable'] ?? false ) || ( $settings['sticky']['isStickOnScrollingUp'] ?? false ) ) {
576 continue;
577 }
578 }
579
580 $group_name = $feature_args['group'] ?? null;
581 $setting_name = $feature_args['setting_name'] ?? $feature;
582 if ( $this->is_valid_value( $group_name ) ) {
583 $setting_value = $settings[ $group_name ][ $setting_name ] ?? null;
584 } else {
585 $setting_value = $settings[ $setting_name ] ?? null;
586 }
587
588 if ( empty( $setting_value ) ) {
589 continue;
590 }
591
592 $args = array_merge(
593 [
594 'settings' => $settings,
595 'setting_value' => $setting_value,
596 'feature' => $feature,
597 'selector' => $class_selector,
598 'breakpoints' => $breakpoints,
599 'block' => $block,
600 'block_layout' => $block_layout,
601 ],
602 $feature_args
603 );
604
605 if ( \is_callable( $args['func_build_style'] ?? '' ) ) {
606 $this->build_style( array_merge( $args, [ 'value' => $setting_value ] ), $style_array, $classes );
607 }
608
609 if ( \is_callable( $args['func_build_responsive_style'] ?? '' ) && $this->is_valid_responsive_value( $args['setting_value'] ) ) {
610 $this->build_responsive_style( $args, $responsive_style_array, $classes );
611 }
612 }
613
614 if ( $style_array ) {
615 foreach ( $style_array as $item_style ) {
616 $style .= $item_style;
617 }
618 }
619
620 if ( $responsive_style_array ) {
621 foreach ( $responsive_style_array as $responsive_style ) {
622 $style .= $responsive_style;
623 }
624 }
625
626 $custom_css = $this->get_custom_css_style( $block );
627
628 if ( $custom_css ) {
629 // Get & refine custom style.
630 $custom_style = $this->refine_custom_value( $custom_css['value'] ?? '', [ 'selector' => $class_selector ], 'CSS' );
631
632 if ( $custom_style ) {
633 $style .= $custom_style;
634 }
635 }
636
637 // Allow altering the style.
638 $style = apply_filters(
639 'cbb_get_block_style',
640 $style,
641 [
642 'settings' => $settings,
643 'selector' => $class_selector,
644 'breakpoints' => $breakpoints,
645 'block' => $block,
646 'block_layout' => $block_layout,
647 ]
648 );
649
650 return [
651 'style' => $style,
652 'classes' => $classes,
653 ];
654 }
655
656 /**
657 * Build custom style
658 *
659 * @param array $args
660 * @param array &$style_array
661 * @param array &$classes
662 * @return string
663 */
664 private function build_style( $args, &$style_array, &$classes ) {
665 $return_value = false;
666 $func_build_style = $args['func_build_style'] ?? '';
667
668 if ( ! \is_callable( $func_build_style ) ) {
669 return $return_value;
670 }
671
672 $setting_value = $args['setting_value'] ?? [];
673 if ( ! $this->is_valid_value( $setting_value ) ) {
674 return $return_value;
675 }
676
677 // Build style.
678 return $func_build_style( $args, $style_array, $classes );
679 }
680
681 /**
682 * Build style for padding
683 *
684 * @param array $args
685 * @return string
686 */
687 private function build_padding_style( $args ) {
688 $style_array = [];
689 $value = $args['value'];
690 if ( is_array( $value ) ) {
691 if ( isset( $value['top'] ) && is_string( $value['top'] ) ) {
692 $top_style = $this->get_spacing_value( $value['top'] );
693 if ( $this->is_valid_value( $top_style ) ) {
694 $style_array['--cbb--padding-top'] = $top_style;
695 }
696 }
697
698 if ( isset( $value['right'] ) && is_string( $value['right'] ) ) {
699 $right_style = $this->get_spacing_value( $value['right'] );
700 if ( $this->is_valid_value( $right_style ) ) {
701 $style_array['--cbb--padding-right'] = $right_style;
702 }
703 }
704
705 if ( isset( $value['bottom'] ) && is_string( $value['bottom'] ) ) {
706 $bottom_style = $this->get_spacing_value( $value['bottom'] );
707 if ( $this->is_valid_value( $bottom_style ) ) {
708 $style_array['--cbb--padding-bottom'] = $bottom_style;
709 }
710 }
711
712 if ( isset( $value['left'] ) && is_string( $value['left'] ) ) {
713 $left_style = $this->get_spacing_value( $value['left'] );
714 if ( $this->is_valid_value( $left_style ) ) {
715 $style_array['--cbb--padding-left'] = $left_style;
716 }
717 }
718 }
719
720 return $style_array;
721 }
722
723 /**
724 * Build style for margin
725 *
726 * @param array $args
727 * @return string
728 */
729 private function build_margin_style( $args ) {
730 $style_array = [];
731 $value = $args['value'];
732 if ( is_array( $value ) ) {
733 if ( isset( $value['top'] ) && is_string( $value['top'] ) ) {
734 $top_style = $this->get_spacing_value( $value['top'] );
735 if ( $this->is_valid_value( $top_style ) ) {
736 $style_array['--cbb--margin-top'] = $top_style;
737 }
738 }
739
740 if ( isset( $value['right'] ) && is_string( $value['right'] ) ) {
741 $right_style = $this->get_spacing_value( $value['right'] );
742 if ( $this->is_valid_value( $right_style ) ) {
743 $style_array['--cbb--margin-right'] = $right_style;
744 }
745 }
746
747 if ( isset( $value['bottom'] ) && is_string( $value['bottom'] ) ) {
748 $bottom_style = $this->get_spacing_value( $value['bottom'] );
749 if ( $this->is_valid_value( $bottom_style ) ) {
750 $style_array['--cbb--margin-bottom'] = $bottom_style;
751 }
752 }
753
754 if ( isset( $value['left'] ) && is_string( $value['left'] ) ) {
755 $left_style = $this->get_spacing_value( $value['left'] );
756 if ( $this->is_valid_value( $left_style ) ) {
757 $style_array['--cbb--margin-left'] = $left_style;
758 }
759 }
760 }
761
762 return $style_array;
763 }
764
765 /**
766 * Build style for gap
767 *
768 * @param array $args
769 * @return string
770 */
771 private function build_block_gap_style( $args ) {
772 $style_array = [];
773 $value = $args['value'];
774 if ( is_array( $value ) && ( $value['top'] ?? false ) ) {
775 $gap_style = $this->get_spacing_value( $value['top'] );
776 if ( $this->is_valid_value( $gap_style ) ) {
777 $style_array['--cbb--block-gap'] = $gap_style;
778 }
779 }
780
781 return $style_array;
782 }
783
784 /**
785 * Build style for border
786 *
787 * @param array $args
788 * @return string
789 */
790 private function build_border_style( $args ) {
791 $style_array = [];
792 $value = $args['value'];
793 if ( is_array( $value ) ) {
794 if ( ( $value['top'] ?? false ) || ( $value['right'] ?? false ) || ( $value['bottom'] ?? false ) || ( $value['left'] ?? false ) ) {
795 $top = $value['top'] ?? null;
796 $right = $value['right'] ?? null;
797 $bottom = $value['bottom'] ?? null;
798 $left = $value['left'] ?? null;
799 } else {
800 $top = $value;
801 $right = $value;
802 $bottom = $value;
803 $left = $value;
804 }
805
806 if ( $top ) {
807 $top_style = $this->build_border_side_css_value( $top, 'top' );
808 if ( $top_style ) {
809 $style_array['--cbb--border-top'] = $top_style;
810 }
811 }
812
813 if ( $right ) {
814 $right_style = $this->build_border_side_css_value( $right, 'right' );
815 if ( $right_style ) {
816 $style_array['--cbb--border-right'] = $right_style;
817 }
818 }
819
820 if ( $bottom ) {
821 $bottom_style = $this->build_border_side_css_value( $bottom, 'bottom' );
822 if ( $bottom_style ) {
823 $style_array['--cbb--border-bottom'] = $bottom_style;
824 }
825 }
826
827 if ( $left ) {
828 $left_style = $this->build_border_side_css_value( $left, 'left' );
829 if ( $left_style ) {
830 $style_array['--cbb--border-left'] = $left_style;
831 }
832 }
833 }
834
835 return $style_array;
836 }
837
838 /**
839 * Build the css variable for border by side
840 *
841 * @param array $value
842 * @param string $side
843 * @return string
844 */
845 private function build_border_side_css_value( $value, $side ) {
846 $style_array = [];
847 if ( is_array( $value ) ) {
848 if ( $this->is_valid_value( $value['width'] ?? null ) && is_string( $value['width'] ) ) {
849 $style_array[] = $value['width'];
850 }
851
852 if ( $this->is_valid_value( $value['style'] ?? null ) && is_string( $value['style'] ) ) {
853 $style_array[] = $value['style'];
854 }
855
856 if ( $this->is_valid_value( $value['color'] ?? null ) ) {
857 $color = $this->get_css_color_value( $value['color'] );
858 if ( $color ) {
859 $style_array[] = $color;
860 }
861 }
862 }
863
864 return implode( ' ', $style_array );
865 }
866
867 /**
868 * Build style for radius
869 *
870 * @param array $args
871 * @return string
872 */
873 private function build_border_radius_style( $args ) {
874 $style_array = [];
875 $value = $args['value'];
876 if ( is_array( $value ) ) {
877 $css_value = '';
878 $top_left = $value['top-left'] ?? 0;
879 $top_right = $value['top-right'] ?? 0;
880 $bottom_right = $value['bottom-right'] ?? 0;
881 $bottom_left = $value['bottom-left'] ?? 0;
882
883 if ( ! empty( $top_left ) || ! empty( $top_right ) || ! empty( $bottom_right ) || ! empty( $bottom_left ) ) {
884 $css_value = "{$top_left} {$top_right} {$bottom_right} {$bottom_left}";
885 }
886
887 if ( $args['settings']['enableEllipticalRadius'] ?? false ) {
888 $top_left = $value['top-left-vertical'] ?? 0;
889 $top_right = $value['top-right-vertical'] ?? 0;
890 $bottom_right = $value['bottom-right-vertical'] ?? 0;
891 $bottom_left = $value['bottom-left-vertical'] ?? 0;
892
893 if ( ! empty( $top_left ) || ! empty( $top_right ) || ! empty( $bottom_right ) || ! empty( $bottom_left ) ) {
894 $css_value = "{$css_value} / {$top_left} {$top_right} {$bottom_right} {$bottom_left}";
895 }
896 }
897
898 if ( $css_value ) {
899 $style_array['--cbb--border-radius'] = $css_value;
900 }
901 }
902
903 return $style_array;
904 }
905
906 /**
907 * Build style for width
908 *
909 * @param array $args
910 * @return string
911 */
912 private function build_width_style( $args ) {
913 $style_array = [];
914 $value = $args['value'];
915 if ( is_array( $value ) && ( $value['value'] ?? false ) ) {
916 $width_style = $value['value'];
917 if ( $this->is_valid_value( $width_style ) ) {
918 $style_array['--cbb--width'] = $width_style;
919 }
920 }
921
922 return $style_array;
923 }
924
925 /**
926 * Build style for height
927 *
928 * @param array $args
929 * @return string
930 */
931 private function build_height_style( $args ) {
932 $style_array = [];
933 $value = $args['value'];
934 if ( is_array( $value ) && ( $value['value'] ?? false ) ) {
935 $style = $value['value'];
936 if ( $this->is_valid_value( $style ) ) {
937 $style_array['--cbb--height'] = $style;
938 }
939 }
940
941 return $style_array;
942 }
943
944 /**
945 * Build style for aspect ratio
946 *
947 * @param array $args
948 * @return string
949 */
950 private function build_aspect_ratio_style( $args ) {
951 $style_array = [];
952 $value = $args['value'];
953 if ( is_array( $value ) && ( $value['value'] ?? false ) ) {
954 $style = $value['value'];
955 if ( $this->is_valid_value( $style ) ) {
956 $style_array['--cbb--aspect-ratio'] = $style;
957 }
958 }
959
960 return $style_array;
961 }
962
963 /**
964 * Build style for text alignment
965 *
966 * @param array $args
967 * @return string
968 */
969 private function build_text_align_style( $args ) {
970 $style_array = [];
971 $value = $args['value'];
972 if ( $this->is_valid_value( $value ) ) {
973 $style_array['--cbb--text-align'] = $value;
974 }
975
976 return $style_array;
977 }
978
979 /**
980 * Build style for vertical alignment
981 *
982 * @param array $args
983 * @return string
984 */
985 private function build_vertical_align_style( $args ) {
986 $style_array = [];
987 $value = $args['value'] ?? null;
988 if ( $this->is_valid_value( $value ) ) {
989 if ( 'top' === $value ) {
990 $value = 'start';
991 } elseif ( 'bottom' === $value ) {
992 $value = 'end';
993 }
994
995 $style_array['--cbb--v-align'] = $value;
996 }
997
998 return $style_array;
999 }
1000
1001 /**
1002 * Build dependent style for vertical alignment
1003 *
1004 * @param array $args
1005 * @param array &$classes
1006 * @return string
1007 */
1008 private function build_vertical_align_dependent_style( $args, &$classes ) {
1009 $style = '';
1010 $value = $args['setting_value'] ?? null;
1011 if ( $this->is_valid_value( $value ) ) {
1012 $block_layout = $args['block_layout'] ?? '';
1013 if ( 'grid' === $block_layout ) {
1014 $class = 'cbb-align-items';
1015 } elseif ( 'gridItem' === $block_layout ) {
1016 $class = 'cbb-align-self';
1017 } else {
1018 $class = 'cbb-layout-grid';
1019 }
1020
1021 if ( ! in_array( $class, $classes, true ) ) {
1022 $classes[] = $class;
1023 }
1024 }
1025
1026 return $style;
1027 }
1028
1029 /**
1030 * Build style for justify alignment
1031 *
1032 * @param array $args
1033 * @return string
1034 */
1035 private function build_justify_align_style( $args ) {
1036 $style_array = [];
1037 $value = $args['value'] ?? null;
1038 if ( $this->is_valid_value( $value ) ) {
1039 $block_layout = $args['block_layout'] ?? '';
1040 if ( in_array( $block_layout, [ 'standalone', 'carouselItem', 'vstackItem' ], true ) ) {
1041 if ( 'left' === $value ) {
1042 $value = 'start';
1043 } elseif ( 'right' === $value ) {
1044 $value = 'end';
1045 }
1046 }
1047 $style_array['--cbb--h-align'] = $value;
1048 }
1049
1050 return $style_array;
1051 }
1052
1053 /**
1054 * Build dependent style for justify alignment
1055 *
1056 * @param array $args
1057 * @param array &$classes
1058 * @return string
1059 */
1060 private function build_justify_align_dependent_style( $args, &$classes ) {
1061 $style = '';
1062 $value = $args['setting_value'] ?? null;
1063 if ( $this->is_valid_value( $value ) ) {
1064 $block_layout = $args['block_layout'] ?? '';
1065 if ( 'grid' === $block_layout ) {
1066 $class = 'cbb-justify-items';
1067 } elseif ( 'gridItem' === $block_layout ) {
1068 $class = 'cbb-justify-self';
1069 } else {
1070 // if ( in_array( $block_layout, [ 'standalone', 'carouselItem', 'vstackItem' ], true ) ) {.
1071 $class = 'cbb-layout-grid';
1072 }
1073
1074 if ( ! in_array( $class, $classes, true ) ) {
1075 $classes[] = $class;
1076 }
1077 }
1078
1079 return $style;
1080 }
1081
1082 /**
1083 * Build style for transform
1084 *
1085 * @param array $args
1086 * @return string
1087 */
1088 private function build_transform_style( $args ) {
1089 $style_array = [];
1090 $value = $args['value'];
1091 if ( ! empty( $value ) && is_array( $value ) ) {
1092 $style = implode( ' ', array_filter( array_map( [ $this, 'build_transform_item' ], $value ) ) );
1093 if ( $this->is_valid_value( $value ) ) {
1094 $style_array['--cbb--transform'] = $style;
1095 }
1096 }
1097
1098 return $style_array;
1099 }
1100
1101 /**
1102 * Build transform item: translate, rotate, scale, skew
1103 *
1104 * @return string
1105 */
1106 private function build_transform_item( $transform_item ) {
1107 $style = '';
1108 if ( is_array( $transform_item ) && count( $transform_item ) > 0 ) {
1109 // Get tranform type.
1110 $transform_type = array_keys( $transform_item )[0];
1111 $transform_value = $transform_item[ $transform_type ] ?? '';
1112
1113 if ( $transform_type === 'rotate' ) {
1114 if ( is_numeric( $transform_value ) ) {
1115 $style = "rotate({$transform_value}deg)";
1116 }
1117 } elseif ( is_array( $transform_value ) ) {
1118 $suffix = 'skew' === $transform_type ? 'deg' : '';
1119
1120 $x = $transform_value['x'] ?? '';
1121 $y = $transform_value['y'] ?? '';
1122 $x_value = preg_replace( '/[^0-9.]/', '', $x );
1123 $y_value = preg_replace( '/[^0-9.]/', '', $y );
1124
1125 if ( \is_numeric( $x_value ) || \is_numeric( $y_value ) ) {
1126 if ( ! \is_numeric( $x_value ) ) {
1127 if ( 'scale' === $transform_type ) {
1128 $x = 1;
1129 } else {
1130 $x = 0;
1131 }
1132 }
1133
1134 if ( ! \is_numeric( $y_value ) ) {
1135 if ( 'scale' === $transform_type ) {
1136 $y = 1;
1137 } else {
1138 $y = 0;
1139 }
1140 }
1141
1142 $style = "{$transform_type}({$x}{$suffix}, {$y}{$suffix})";
1143 }
1144 }
1145 }
1146
1147 return $style;
1148 }
1149
1150 /**
1151 * Build dependent style for transform
1152 *
1153 * @param array $args
1154 * @param array &$classes
1155 * @return string
1156 */
1157 private function build_transform_dependent_style( $args, &$classes ) {
1158 $transform_origin = $args['settings']['transformOrigin'] ?? [];
1159 $x = $transform_origin['x'] ?? .5;
1160 if ( ! \is_numeric( $x ) ) {
1161 $x = .5;
1162 }
1163 $x = round( $x, 2 ) * 100;
1164 $y = $transform_origin['y'] ?? .5;
1165 if ( ! \is_numeric( $y ) ) {
1166 $y = .5;
1167 }
1168 $y = round( $y, 2 ) * 100;
1169
1170 return "{$args['selector']}{--cbb--transform-origin: {$x}% {$y}%;}";
1171 }
1172
1173 /**
1174 * Build style for hidden
1175 *
1176 * @param array $args
1177 * @param array &$style_array
1178 * @param array &$classes
1179 * @return array
1180 */
1181 private function build_hidden_style( $args, &$style_array, &$classes ) {
1182 $value = $args['value'] ?? null;
1183 if ( ! empty( $value ) && is_array( $value ) ) {
1184 foreach ( $value as $breakpoint ) {
1185 $class = "cbb-hidden-{$breakpoint}";
1186 if ( ! in_array( $class, $classes, true ) ) {
1187 $classes[] = $class;
1188 }
1189 }
1190 }
1191 }
1192
1193 /**
1194 * Build style for grid columns
1195 *
1196 * @param array $args
1197 * @return string
1198 */
1199 private function build_columns_style( $args ) {
1200 $style_array = [];
1201 $value = $args['value'] ?? null;
1202 if ( $this->is_valid_value( $value ) ) {
1203 $style_array['--cbb--grid-columns'] = $value;
1204 }
1205
1206 return $style_array;
1207 }
1208
1209 /**
1210 * Build style for grid rows
1211 *
1212 * @param array $args
1213 * @return string
1214 */
1215 private function build_rows_style( $args ) {
1216 $style_array = [];
1217 $value = $args['value'] ?? null;
1218 if ( $this->is_valid_value( $value ) ) {
1219 $style_array['--cbb--grid-rows'] = $value;
1220 }
1221
1222 return $style_array;
1223 }
1224
1225 /**
1226 * Build style for grid auto rows
1227 *
1228 * @param array $args
1229 * @return string
1230 */
1231 private function build_auto_rows_style( $args ) {
1232 $style_array = [];
1233 $value = $args['value'] ?? null;
1234 if ( $this->is_valid_value( $value ) ) {
1235 $style_array['--cbb--grid-auto-rows'] = $value;
1236 }
1237
1238 return $style_array;
1239 }
1240
1241 /**
1242 * Build style for grid gap
1243 *
1244 * @param array $args
1245 * @return string
1246 */
1247 private function build_gap_style( $args ) {
1248 $style_array = [];
1249 $value = $args['value'] ?? null;
1250 if ( $value && is_array( $value ) ) {
1251 $column_gap = $value['column'] ?? null;
1252 if ( $this->is_valid_value( $column_gap ) ) {
1253 $style_array['--cbb--grid-gap-column'] = $column_gap;
1254 }
1255 $row_gap = $value['row'] ?? null;
1256 if ( $this->is_valid_value( $row_gap ) ) {
1257 $style_array['--cbb--grid-gap-row'] = $row_gap;
1258 }
1259 }
1260
1261 return $style_array;
1262 }
1263
1264 /**
1265 * Build style for grid item column span
1266 *
1267 * @param array $args
1268 * @return string
1269 */
1270 private function build_column_span_style( $args ) {
1271 $style_array = [];
1272 $value = $args['value'] ?? null;
1273 if ( $value && is_array( $value ) ) {
1274 $start = $value['start'] ?? null;
1275 $span = $value['span'] ?? null;
1276 $order = $value['order'] ?? null;
1277
1278 if ( $this->is_valid_value( $start ) || $this->is_valid_value( $span ) ) {
1279 $start = $this->is_valid_value( $start ) ? $start : 'auto';
1280 $span = $this->is_valid_value( $span ) ? $span : 'auto';
1281
1282 if ( 'auto' !== $span && absint( $span ) > 0 ) {
1283 $span = "span {$span}";
1284 }
1285
1286 $style_array['--cbb--grid-item-column'] = "{$start} / {$span}";
1287 }
1288
1289 if ( $this->is_valid_value( $order ) && $order !== 'auto' ) {
1290 $style_array['--cbb--grid-item-order'] = $order;
1291 }
1292 }
1293
1294 return $style_array;
1295 }
1296
1297 /**
1298 * Build style for grid item row span
1299 *
1300 * @param array $args
1301 * @return string
1302 */
1303 private function build_row_span_style( $args ) {
1304 $style_array = [];
1305 $value = $args['value'] ?? null;
1306 if ( $value && is_array( $value ) ) {
1307 $start = $value['start'] ?? null;
1308 $span = $value['span'] ?? null;
1309
1310 if ( $this->is_valid_value( $start ) || $this->is_valid_value( $span ) ) {
1311 $start = $this->is_valid_value( $start ) ? $start : 'auto';
1312 $span = $this->is_valid_value( $span ) ? $span : 'auto';
1313
1314 if ( 'auto' !== $span && absint( $span ) > 0 ) {
1315 $span = "span {$span}";
1316 }
1317
1318 $style_array['--cbb--grid-item-row'] = "{$start} / {$span}";
1319 }
1320 }
1321
1322 return $style_array;
1323 }
1324
1325 /**
1326 * Build style for accordion gap
1327 *
1328 * @param array $args
1329 * @return string
1330 */
1331 private function build_accordion_gap_style( $args ) {
1332 $style_array = [];
1333 $value = $args['value'];
1334 if ( $this->is_valid_value( $value ) ) {
1335 $style_array['--cbb--accordion-gap'] = $this->get_spacing_value( $value );
1336 }
1337
1338 return $style_array;
1339 }
1340
1341 /**
1342 * Build dependent style for accordion gap
1343 *
1344 * @param array $args
1345 * @param array &$classes
1346 * @return string
1347 */
1348 private function build_accordion_gap_dependent_style( $args, &$classes ) {
1349 $style = '';
1350 $setting_values = $args['setting_values'];
1351 if ( $this->is_valid_value( $setting_values ) && is_array( $setting_values ) ) {
1352 foreach ( $setting_values as $breakpoint => $value ) {
1353 if ( strpos( $value, 'var:preset|spacing|' ) !== false || absint( $value ) > 0 ) {
1354 $classes[] = "{$breakpoint}-cbb-a-has-border";
1355 } else {
1356 $classes[] = "{$breakpoint}-cbb-a-no-border";
1357 }
1358 }
1359 }
1360
1361 return $style;
1362 }
1363
1364 /**
1365 * Build style for accordion gap
1366 *
1367 * @param array $args
1368 * @return string
1369 */
1370 private function build_accordion_padding_style( $args ) {
1371 $style_array = [];
1372 $value = $args['value'];
1373 if ( $value && is_array( $value ) ) {
1374 $x = $value['x'] ?? null;
1375 if ( ! $this->is_valid_value( $x ) ) {
1376 $x = '1.25rem';
1377 }
1378 $x = $this->get_spacing_value( $x );
1379
1380 $y = $value['y'] ?? null;
1381 if ( ! $this->is_valid_value( $y ) ) {
1382 $y = '1rem';
1383 }
1384 $y = $this->get_spacing_value( $y );
1385
1386 $style_array['--cbb--accordion-padding'] = "{$y} {$x}";
1387 }
1388
1389 return $style_array;
1390 }
1391
1392 /**
1393 * Build style for accordion border
1394 *
1395 * @param array $args
1396 * @return string
1397 */
1398 private function build_accordion_border_style( $args, &$style_array ) {
1399 $value = $args['value'] ?? null;
1400 if ( $value && is_array( $value ) ) {
1401 if ( ( $value['top'] ?? false ) || ( $value['right'] ?? false ) || ( $value['bottom'] ?? false ) || ( $value['left'] ?? false ) ) {
1402 $border_styles = $this->build_border_style( $args );
1403 } else {
1404 $border_styles = $this->build_border_style(
1405 [
1406 'value' => [
1407 'top' => $value,
1408 'right' => $value,
1409 'bottom' => $value,
1410 'left' => $value,
1411 ],
1412 ]
1413 );
1414 }
1415
1416 if ( $border_styles ) {
1417 $border_style = implode(
1418 ';',
1419 array_map(
1420 function ( $attr_key ) use ( $border_styles ) {
1421 return "{$attr_key}:{$border_styles[ $attr_key ]}";
1422 },
1423 array_keys( $border_styles )
1424 )
1425 );
1426
1427 $style_array[] = "{$args['selector']}{{$border_style};}";
1428 }
1429 }
1430
1431 return $style_array;
1432 }
1433
1434 /**
1435 * Build style for accordion radius
1436 *
1437 * @param array $args
1438 * @return string
1439 */
1440 private function build_accordion_radius_style( $args, &$style_array ) {
1441 $value = $args['value'] ?? null;
1442 if ( $value ) {
1443 if ( is_array( $value ) ) {
1444 $top_left = $value['topLeft'] ?? 0;
1445 $top_right = $value['topRight'] ?? 0;
1446 $bottom_right = $value['bottomRight'] ?? 0;
1447 $bottom_left = $value['bottomLeft'] ?? 0;
1448
1449 $css_value = "{$top_left} {$top_right} {$bottom_right} {$bottom_left}";
1450 } elseif ( is_string( $value ) ) {
1451 $css_value = $value;
1452 }
1453 $style_array[] = "{$args['selector']}{--cbb--border-radius:{$css_value};}";
1454 }
1455
1456 return $style_array;
1457 }
1458
1459 /**
1460 * Build style for sticky offset
1461 *
1462 * @param array $args
1463 * @return array
1464 */
1465 private function build_sticky_offset_style( $args ) {
1466 $style_array = [];
1467 $value = $args['value'];
1468 if ( $value && is_array( $value ) ) {
1469 $offset = $value['value'] ?? null;
1470 if ( $this->is_valid_value( $offset ) ) {
1471 $style_array['--cbb--sticky-offset'] = $offset;
1472 }
1473 }
1474
1475 return $style_array;
1476 }
1477
1478 /**
1479 * Check whether current block has custom style or not.
1480 *
1481 * @param [type] $block
1482 * @return boolean
1483 */
1484 private function get_custom_css_style( $block ) {
1485 // There is no boldblocks value.
1486 if ( ! isset( $block['attrs']['boldblocks'] ) || ! isset( $block['attrs']['boldblocks']['customCSS'] ) ) {
1487 return false;
1488 }
1489
1490 $custom_css = $block['attrs']['boldblocks']['customCSS'];
1491 if ( ! is_array( $custom_css ) || empty( $custom_css['value'] ) ) {
1492 return false;
1493 }
1494
1495 return $custom_css;
1496 }
1497
1498 /**
1499 * Render dynamic link to post element
1500 *
1501 * @param string $block_content
1502 * @param array $block
1503 * @param WP_Block $block_instance
1504 * @return string
1505 */
1506 public function build_block_link_to_post( $block_content, $block, $block_instance ) {
1507 // Ignore admin side.
1508 if ( is_admin() ) {
1509 return $block_content;
1510 }
1511
1512 // There is no post context.
1513 $post_id = $block_instance->context['postId'] ?? false;
1514 if ( ! $post_id ) {
1515 return $block_content;
1516 }
1517
1518 // There is no background value.
1519 if ( ! ( $block['attrs']['boldblocks']['background'] ?? false ) ) {
1520 return $block_content;
1521 }
1522
1523 $background = $block['attrs']['boldblocks']['background'];
1524 $media_type = $background['mediaType'] ?? 'image';
1525
1526 if ( 'image' === $media_type && ( $background['image']['useFeaturedImage'] ?? false ) && ( $background['image']['linkToPost'] ?? false ) ) {
1527 $block_content = $this->add_inner_content_to_block( $block_content, '<a class="bb:link-to-post" href="' . esc_url( get_permalink( $post_id ) ) . '" aria-label="' . esc_attr( get_the_title( $post_id ) ) . '" rel="permalink"></a>' );
1528 }
1529
1530 return $block_content;
1531 }
1532
1533 /**
1534 * Render dynamic background image
1535 *
1536 * @param string $block_content
1537 * @param array $block
1538 * @param WP_Block $block_instance
1539 * @return string
1540 */
1541 public function build_block_background( $block_content, $block, $block_instance ) {
1542 // Ignore admin side.
1543 if ( is_admin() ) {
1544 return $block_content;
1545 }
1546
1547 // Ignore legacy markup.
1548 if ( strpos( $block['innerHTML'] ?? '', 'bb:block-background' ) !== false ) {
1549 return $block_content;
1550 }
1551
1552 $context_background = [];
1553 $meta_name = $block['attrs']['metadata']['name'] ?? '';
1554 if ( $meta_name && 'cbb/block-overrides' === ( $block['attrs']['metadata']['bindings']['background']['source'] ?? '' ) ) {
1555 $context_background = $block_instance->context['cbb/block-overrides'][ $meta_name ]['background'] ?? [];
1556 }
1557
1558 $background = $block['attrs']['boldblocks']['background'] ?? [];
1559 if ( $context_background ) {
1560 $background = array_replace_recursive( $background, $context_background );
1561 }
1562
1563 // There is no background value.
1564 if ( ! $background ) {
1565 return $block_content;
1566 }
1567
1568 $media_type = $background['mediaType'] ?? 'image';
1569 $media_url = $background[ $media_type ]['url'] ?? '';
1570
1571 if ( 'image' === $media_type ) {
1572 if ( $background['image']['useFeaturedImage'] ?? false ) {
1573 return $this->build_thumbnail_background( $background, $block_content, $block, $block_instance );
1574 } elseif ( $media_url ) {
1575 return $this->build_custom_image_background( $background, $block_content, $block, $block_instance );
1576 }
1577 } elseif ( 'video' === $media_type && $media_url ) {
1578 return $this->build_video_background( $background, $block_content, $block, $block_instance );
1579 }
1580
1581 return $block_content;
1582 }
1583
1584 /**
1585 * Build the background using custom image
1586 *
1587 * @param array $background
1588 * @param string $block_content
1589 * @param array $block
1590 * @param WP_Block $block_instance
1591 * @return string
1592 */
1593 private function build_custom_image_background( $background, $block_content, $block, $block_instance ) {
1594 $block_class = 'bb:has-background bb:has-background--image';
1595 $block_background = $this->build_image_background( $background['image']['id'] ?? false, $background, $block_class );
1596
1597 if ( strpos( $block['innerHTML'] ?? '', $block_background['block_class'] ) === false ) {
1598 $block_content = $this->add_class_to_block( $block_content, $block_background['block_class'] );
1599 }
1600
1601 $block_content = $this->add_inner_content_to_block( $block_content, $block_background['background_markup'] );
1602
1603 return $block_content;
1604 }
1605
1606 /**
1607 * Build the background using post thumbnail
1608 *
1609 * @param array $background
1610 * @param string $block_content
1611 * @param array $block
1612 * @param WP_Block $block_instance
1613 * @return string
1614 */
1615 private function build_thumbnail_background( $background, $block_content, $block, $block_instance ) {
1616 $post_id = $block_instance->context['postId'] ?? false;
1617 if ( ! $post_id ) {
1618 return $block_content;
1619 }
1620
1621 $image_id = get_post_thumbnail_id( $post_id );
1622
1623 $block_class = 'bb:has-background bb:has-background--image';
1624 if ( $image_id ) {
1625 $block_background = $this->build_image_background( $image_id, $background, $block_class );
1626
1627 if ( strpos( $block['innerHTML'] ?? '', $block_background['block_class'] ) === false ) {
1628 $block_content = $this->add_class_to_block( $block_content, $block_background['block_class'] );
1629 }
1630
1631 $block_content = $this->add_inner_content_to_block( $block_content, $block_background['background_markup'] );
1632 } else {
1633 // Add custom background as a fallback.
1634 if ( $background['image']['url'] ?? '' ) {
1635 $block_content = $this->add_class_to_block( $block_content, 'has-no-thumbnail' );
1636 $block_content = $this->build_custom_image_background( $background, $block_content, $block, $block_instance );
1637 } else {
1638 $block_content = $this->add_class_to_block( $block_content, $block_class . ' has-no-thumbnail' );
1639 }
1640 }
1641
1642 return $block_content;
1643 }
1644
1645 /**
1646 * Build image background
1647 *
1648 * @param int $image_id
1649 * @param array $background
1650 * @param string $block_class
1651 * @return array
1652 */
1653 private function build_image_background( $image_id, $background, $block_class ) {
1654 $settings = $background['settings'] ?? [];
1655 $media = $background['image'] ?? [];
1656 $mobile_background = ! ( $media['useFeaturedImage'] ?? false ) && ( $media['hasMobileBackground'] ?? false ) && ( $media['mobileBackground']['url'] ?? '' ) ? $media['mobileBackground'] : false;
1657 $background_classes = [ 'bb:block-background bb:block-background--image cbb-bg-image' ];
1658 $background_position = round( ( $settings['focalPoint']['x'] ?? .5 ) * 100 ) . '% ' . round( ( $settings['focalPoint']['y'] ?? .5 ) * 100 ) . '%';
1659
1660 $image_element = '';
1661 $dataset = [];
1662 $image_size = $media['size'] ?? 'full';
1663
1664 $is_internal_image = false;
1665 $image_url = $image_id ? wp_get_attachment_image_url( $image_id, $image_size ) : false;
1666 if ( $image_url ) {
1667 $is_internal_image = true;
1668 } else {
1669 $image_url = $media['url'] ?? '';
1670 }
1671
1672 $custom_alt_text = $media['customAlt'] ?? '';
1673 $object_fit = $settings['objectFit'] ?? 'cover';
1674 if ( empty( $object_fit ) ) {
1675 $object_fit = 'cover';
1676 }
1677 $img_style = 'display:block;width:100%;height:100%;object-fit:' . $object_fit . ';object-position:' . $background_position . ';';
1678 $background_styles = [];
1679 if ( $settings['isImgElement'] ?? false ) {
1680 $attrs = [
1681 'style' => esc_attr( $img_style ),
1682 'class' => 'bb:block-background--img',
1683 ];
1684
1685 $loading_priority = $settings['loadingPriority'] ?? '';
1686 if ( $loading_priority ) {
1687 if ( $loading_priority === 'lazy' ) {
1688 $attrs['loading'] = 'lazy';
1689 } elseif ( in_array( $loading_priority, [ 'high', 'low' ], true ) ) {
1690 $attrs['fetchpriority'] = $loading_priority;
1691 }
1692 }
1693
1694 if ( $is_internal_image ) {
1695 $alt = trim( wp_strip_all_tags( get_post_meta( $image_id, '_wp_attachment_image_alt', true ) ) );
1696 if ( ! $alt ) {
1697 $attrs['alt'] = $custom_alt_text;
1698 }
1699 $image_element = wp_get_attachment_image(
1700 $image_id,
1701 $image_size,
1702 false,
1703 $attrs
1704 );
1705 } else {
1706 $image_element = $this->generate_image_element( $image_url, array_merge( $attrs, [ 'alt' => $custom_alt_text ] ) );
1707 }
1708
1709 if ( $mobile_background ) {
1710 $background_classes[] = 'cbb-mobile-image';
1711 $attrs['class'] .= ' is-mobile-image';
1712
1713 $mobile_image = '';
1714 if ( $mobile_background['id'] ?? false ) {
1715 $alt = trim( wp_strip_all_tags( get_post_meta( $mobile_background['id'], '_wp_attachment_image_alt', true ) ) );
1716 $attrs['alt'] = $alt ? $alt : $custom_alt_text;
1717
1718 $mobile_image = wp_get_attachment_image(
1719 $mobile_background['id'],
1720 $image_size,
1721 false,
1722 $attrs
1723 );
1724 }
1725
1726 if ( ! $mobile_image ) {
1727 $mobile_image = $this->generate_image_element( $mobile_background['url'], array_merge( $attrs, [ 'alt' => $custom_alt_text ] ) );
1728 }
1729
1730 $image_element .= $mobile_image;
1731 }
1732 } else {
1733 $image_as_background = $this->get_background_image_url( $image_url );
1734 $background_styles = [
1735 'background-image' => 'url(' . $image_as_background . ')',
1736 'background-position' => $background_position,
1737 ];
1738
1739 if ( ! empty( $settings['isFixed'] ) ) {
1740 $background_styles['background-attachment'] = 'fixed';
1741 }
1742
1743 $background_styles['background-repeat'] = $settings['repeat'] ?? 'no-repeat';
1744 $background_styles['background-size'] = $settings['size'] ?? 'cover';
1745
1746 if ( $mobile_background ) {
1747 $background_styles['--cbb-mobile-background'] = "url('{$this->get_background_image_url( $mobile_background['url'] )}')";
1748 $background_classes[] = 'cbb-mobile-background';
1749 }
1750 }
1751
1752 $animation_type = $settings['animation']['type'] ?? '';
1753 $animation_settings = $settings['animation']['settings'] ?? [];
1754 if ( $animation_type && 'none' !== $animation_type ) {
1755 $background_classes[] = $animation_type;
1756
1757 if ( in_array( $animation_type, [ 'bg-scroll-horizontal', 'bg-scroll-vertical' ], true ) ) {
1758 if ( ! ( $settings['isImgElement'] ?? false ) ) {
1759 if ( $this->has_animation_setting_value( 'scroll', 'duration', $animation_settings ) ) {
1760 $background_styles['animation-duration'] = $this->get_animation_setting_value( 'scroll', 'duration', $animation_settings ) . 's';
1761 }
1762
1763 if ( $background_styles['background-repeat'] !== 'repeat' ) {
1764 $background_styles['background-repeat'] = 'bg-scroll-horizontal' === $animation_type ? 'repeat-x' : 'repeat-y';
1765 }
1766
1767 if ( $this->has_animation_setting_value( 'scroll', 'reverseDirection', $animation_settings ) ) {
1768 $background_classes[] = 'is-reverse';
1769 }
1770 }
1771 } elseif ( 'bg-parallax' === $animation_type ) {
1772 unset( $background_styles['background-attachment'] );
1773
1774 $dataset['data-speed'] = $this->get_animation_setting_value( 'parallax', 'speed', $animation_settings, 0.5 );
1775
1776 if ( $this->get_animation_setting_value( 'parallax', 'opacity', $animation_settings, '' ) ) {
1777 $dataset['data-opacity'] = 'true';
1778 }
1779
1780 if ( $this->get_animation_setting_value( 'parallax', 'disableParallax', $animation_settings, '' ) ) {
1781 $dataset['data-disable-parallax'] = 'true';
1782
1783 $breakpoint = $this->get_animation_setting_value( 'parallax', 'disableParallaxBreakpoint', $animation_settings );
1784 if ( ! $breakpoint ) {
1785 $breakpoint = 767;
1786 }
1787
1788 $dataset['data-disable-parallax-breakpoint'] = $breakpoint;
1789 }
1790 } elseif ( 'bg-zoom' === $animation_type ) {
1791 if ( isset( $animation_settings['zoom']['initialScale'] ) ) {
1792 $background_styles['--cbb--zoom-initial-scale'] = $animation_settings['zoom']['initialScale'];
1793 }
1794 if ( isset( $animation_settings['zoom']['scale'] ) ) {
1795 $background_styles['--cbb--zoom-scale'] = $animation_settings['zoom']['scale'];
1796 }
1797 if ( isset( $animation_settings['zoom']['duration'] ) ) {
1798 $background_styles['--cbb--zoom-duration'] = $animation_settings['zoom']['duration'] . 's';
1799 }
1800 if ( isset( $animation_settings['zoom']['timingFunction'] ) ) {
1801 $background_styles['--cbb--zoom-timing-function'] = $animation_settings['zoom']['timingFunction'];
1802 }
1803
1804 $zoom_event = $animation_settings['zoom']['event'] ?? 'hover';
1805 $background_classes[] = "bg-zoom-{$zoom_event}";
1806
1807 if ( 'reveal' === $zoom_event ) {
1808 $dataset['data-reveal-animation'] = esc_attr(
1809 wp_json_encode(
1810 [
1811 'name' => 'bgZoom',
1812 'animation' => 'bgZoom var(--cbb--zoom-duration) var(--cbb--zoom-timing-function)',
1813 'multipleTimes' => $animation_settings['zoom']['multipleTimes'] ?? false,
1814 'forwards' => true,
1815 ]
1816 )
1817 );
1818 }
1819
1820 if ( ! empty( $animation_settings['zoom']['withOpacity'] ) ) {
1821 $background_styles['--cbb--zoom-initial-opacity'] = $animation_settings['zoom']['initialOpacity'] ?? 0;
1822 $background_styles['--cbb--zoom-opacity'] = $animation_settings['zoom']['opacity'] ?? 1;
1823 }
1824 } elseif ( 'bg-zoomIn' === $animation_type ) {
1825 if ( isset( $animation_settings['zoomIn']['initialScale'] ) ) {
1826 $background_styles['--cbb--zoom-initial-scale'] = $animation_settings['zoomIn']['initialScale'];
1827 }
1828 if ( isset( $animation_settings['zoomIn']['duration'] ) ) {
1829 $background_styles['--cbb--zoom-duration'] = $animation_settings['zoomIn']['duration'] . 's';
1830 }
1831 if ( isset( $animation_settings['zoomIn']['timingFunction'] ) ) {
1832 $background_styles['--cbb--zoom-timing-function'] = $animation_settings['zoomIn']['timingFunction'];
1833 }
1834
1835 if ( ! empty( $animation_settings['zoomIn']['withOpacity'] ) ) {
1836 $background_styles['--cbb--zoom-initial-opacity'] = $animation_settings['zoomIn']['initialOpacity'] ?? 0;
1837 $background_styles['--cbb--zoom-opacity'] = $animation_settings['zoomIn']['opacity'] ?? 1;
1838 }
1839
1840 $dataset['data-reveal-animation'] = esc_attr(
1841 wp_json_encode(
1842 [
1843 'name' => 'bgZoomIn',
1844 'animation' => 'bgZoomIn var(--cbb--zoom-duration) var(--cbb--zoom-timing-function)',
1845 'multipleTimes' => $animation_settings['zoomIn']['multipleTimes'] ?? false,
1846 'forwards' => true,
1847 ]
1848 )
1849 );
1850 }
1851 }
1852
1853 $background_style = $background_styles ? array_reduce(
1854 array_keys( $background_styles ),
1855 function ( $carry, $key ) use ( $background_styles ) {
1856 return $carry . $key . ':' . $background_styles[ $key ] . ';';
1857 },
1858 ''
1859 ) : '';
1860
1861 if ( $background_style ) {
1862 $background_style = ' style="' . $background_style . '"';
1863 }
1864
1865 // Get image full size.
1866 $image_full_size = $is_internal_image ? $this->generate_image_full_attributes( $image_id ) : false;
1867 $dataset['data-src'] = $image_full_size ? $image_full_size['src'] : $image_url;
1868 if ( $image_full_size ) {
1869 $dataset['data-width'] = $image_full_size['width'];
1870 $dataset['data-height'] = $image_full_size['height'];
1871 }
1872
1873 $data_attribute = $dataset ? array_reduce(
1874 array_keys( $dataset ),
1875 function ( $carry, $key ) use ( $dataset ) {
1876 return $carry . ' ' . $key . '="' . esc_attr( $dataset[ $key ] ) . '"';
1877 },
1878 ' '
1879 ) : '';
1880
1881 $background_image = sprintf( '<div class="%1$s"%2$s %3$s>%4$s</div>', \implode( ' ', $background_classes ), $background_style, $data_attribute, $image_element );
1882
1883 if ( $animation_type && 'none' !== $animation_type ) {
1884 $block_class .= ' js-animation-' . $animation_type;
1885 }
1886
1887 return [
1888 'background_markup' => $background_image,
1889 'block_class' => $block_class,
1890 ];
1891 }
1892
1893 /**
1894 * Generate data for full size of an image
1895 *
1896 * @param int $image_id
1897 * @return array
1898 */
1899 private function generate_image_full_attributes( $image_id ) {
1900 $image = wp_get_attachment_image_src( $image_id, 'full' );
1901 if ( $image ) {
1902 list( $src, $width, $height ) = $image;
1903
1904 return [
1905 'src' => $src,
1906 'width' => $width,
1907 'height' => $height,
1908 ];
1909 }
1910
1911 return [];
1912 }
1913
1914 /**
1915 * Generate img tag from an image url
1916 *
1917 * @param string $image_url
1918 * @param array $attrs
1919 * @return string
1920 */
1921 private function generate_image_element( $image_url, $attrs ) {
1922 $attr_html = '';
1923 foreach ( $attrs as $name => $value ) {
1924 $attr_html .= " $name=" . '"' . esc_attr( $value ) . '"';
1925 }
1926
1927 // phpcs:ignore PluginCheck.CodeAnalysis.ImageFunctions.NonEnqueuedImage
1928 return sprintf( '<img src="%1$s"%2$s/>', esc_attr( $image_url ), $attr_html );
1929 }
1930
1931 /**
1932 * Get image as background url
1933 *
1934 * @param string $image_url
1935 * @return string
1936 */
1937 private function get_background_image_url( $image_url ) {
1938 return strpos( $image_url, 'data:image/svg+xml' ) === 0 ? '&quot;' . esc_attr( $image_url ) . '&quot;' : esc_attr( $image_url );
1939 }
1940
1941 /**
1942 * Render animations
1943 *
1944 * @param string $block_content
1945 * @param array $block
1946 * @param WP_Block $block_instance
1947 * @return string
1948 */
1949 public function build_block_animations( $block_content, $block, $block_instance ) {
1950 // There is no animations value.
1951 if ( ! isset( $block['attrs']['boldblocks'] ) || ! isset( $block['attrs']['boldblocks']['animations'] ) ) {
1952 return $block_content;
1953 }
1954
1955 // Get animations.
1956 $animations = $block['attrs']['boldblocks']['animations'];
1957 if ( ! is_array( $animations ) ) {
1958 return $block_content;
1959 }
1960
1961 $animation_names = $this->get_animation_names();
1962
1963 $animation_object = array_reduce(
1964 $animations,
1965 function ( $accumulator, $animation ) use ( $animation_names ) {
1966 $name = $animation['name'] ?? '';
1967 $duration = $animation['duration'] ?? '';
1968
1969 if ( ! $name || ! $duration ) {
1970 return $accumulator;
1971 }
1972
1973 $delay = $animation['delay'] ?? 0;
1974 $repeat = $animation['repeat'] ?? 1;
1975 $infinite = $animation['infinite'] ?? false;
1976 $timing_function = $animation['timingFunction'] ?? '';
1977 $animation_name = isset( $animation_names[ $name ]['name'] ) ? $animation_names[ $name ]['name'] : $name;
1978 if ( ! $timing_function && isset( $animation_names[ $name ]['timingFunction'] ) ) {
1979 $timing_function = $animation_names[ $name ]['timingFunction'];
1980 }
1981 $timing_function = $timing_function ? " $timing_function" : '';
1982 $count = $infinite ? 'infinite' : $repeat;
1983
1984 $animation_string = "$animation_name {$duration}s{$timing_function} {$delay}s $count";
1985
1986 // Save animation.
1987 $accumulator['animation'] = $accumulator['animation']
1988 ? "{$accumulator['animation']}, $animation_string"
1989 : $animation_string;
1990
1991 // Save the name.
1992 $accumulator['name'] = $accumulator['name']
1993 ? "{$accumulator['name']},$name"
1994 : $name;
1995
1996 return $accumulator;
1997 },
1998 [
1999 'animation' => '',
2000 'name' => '',
2001 ]
2002 );
2003
2004 if ( $animation_object['name'] && $animation_object['animation'] ) {
2005 $animation_object['multipleTimes'] = $block['attrs']['boldblocks']['animateMultipleTimes'] ?? false;
2006
2007 $block_content = $this->add_data_to_block( $block_content, 'data-reveal-animation', esc_attr( wp_json_encode( $animation_object ) ) );
2008 $block_content = $this->add_class_to_block( $block_content, 'animate__waiting' );
2009 }
2010
2011 return $block_content;
2012 }
2013
2014 /**
2015 * Get all predefined animations
2016 *
2017 * @return array
2018 */
2019 private function get_animation_names() {
2020 $animation_names = [
2021 'bounce' => false,
2022 'flash' => false,
2023 'pulse' => [ 'timingFunction' => 'ease-in-out' ],
2024 'rubberBand' => false,
2025 'shakeX' => false,
2026 'shakeY' => false,
2027 'headShake' => [ 'timingFunction' => 'ease-in-out' ],
2028 'swing' => false,
2029 'tada' => false,
2030 'wobble' => false,
2031 'jello' => false,
2032 'heartBeat' => [ 'timingFunction' => 'ease-in-out' ],
2033
2034 'backInDown' => false,
2035 'backInLeft' => false,
2036 'backInRight' => false,
2037 'backInUp' => false,
2038
2039 'bounceIn' => false,
2040 'bounceInDown' => false,
2041 'bounceInLeft' => false,
2042 'bounceInRight' => false,
2043 'bounceInUp' => false,
2044
2045 'fadeIn' => false,
2046 'fadeInDown' => [ 'name' => 'fadeInXY' ],
2047 'fadeInDownBig' => [ 'name' => 'fadeInXY' ],
2048 'fadeInLeft' => [ 'name' => 'fadeInXY' ],
2049 'fadeInLeftBig' => [ 'name' => 'fadeInXY' ],
2050 'fadeInRight' => [ 'name' => 'fadeInXY' ],
2051 'fadeInRightBig' => [ 'name' => 'fadeInXY' ],
2052 'fadeInUp' => [ 'name' => 'fadeInXY' ],
2053 'fadeInUpBig' => [ 'name' => 'fadeInXY' ],
2054 'fadeInTopLeft' => [ 'name' => 'fadeInXY' ],
2055 'fadeInTopRight' => [ 'name' => 'fadeInXY' ],
2056 'fadeInBottomLeft' => [ 'name' => 'fadeInXY' ],
2057 'fadeInBottomRight' => [ 'name' => 'fadeInXY' ],
2058
2059 'fadeInUpSmall' => [ 'name' => 'fadeInXY' ],
2060 'fadeInRightSmall' => [ 'name' => 'fadeInXY' ],
2061 'fadeInDownSmall' => [ 'name' => 'fadeInXY' ],
2062 'fadeInLeftSmall' => [ 'name' => 'fadeInXY' ],
2063
2064 'flip' => false,
2065 'flipInX' => false,
2066 'flipInY' => false,
2067
2068 'lightSpeedInRight' => [ 'timingFunction' => 'ease-out' ],
2069 'lightSpeedInLeft' => [ 'timingFunction' => 'ease-out' ],
2070
2071 'rotateIn' => [ 'name' => 'rotateInZ' ],
2072 'rotateInDownLeft' => [ 'name' => 'rotateInZ' ],
2073 'rotateInDownRight' => [ 'name' => 'rotateInZ' ],
2074 'rotateInUpLeft' => [ 'name' => 'rotateInZ' ],
2075 'rotateInUpRight' => [ 'name' => 'rotateInZ' ],
2076
2077 'jackInTheBox' => false,
2078 'rollIn' => false,
2079
2080 'zoomIn' => false,
2081 'zoomInDown' => [ 'name' => 'zoomInXY' ],
2082 'zoomInLeft' => [ 'name' => 'zoomInXY' ],
2083 'zoomInRight' => [ 'name' => 'zoomInXY' ],
2084 'zoomInUp' => [ 'name' => 'zoomInXY' ],
2085
2086 'slideInDown' => [ 'name' => 'slideInXY' ],
2087 'slideInLeft' => [ 'name' => 'slideInXY' ],
2088 'slideInRight' => [ 'name' => 'slideInXY' ],
2089 'slideInUp' => [ 'name' => 'slideInXY' ],
2090
2091 'slideInDownSmall' => [ 'name' => 'slideInXY' ],
2092 'slideInLeftSmall' => [ 'name' => 'slideInXY' ],
2093 'slideInRightSmall' => [ 'name' => 'slideInXY' ],
2094 'slideInUpSmall' => [ 'name' => 'slideInXY' ],
2095
2096 'spin' => false,
2097 'zoomOutIn' => false,
2098 ];
2099
2100 return $animation_names;
2101 }
2102
2103 /**
2104 * Has a animation setting value or not
2105 *
2106 * @param string $type
2107 * @param string $name
2108 * @param array $settings
2109 * @return mixed
2110 */
2111 private function has_animation_setting_value( $type, $name, $settings ) {
2112 return isset( $settings[ $type ][ $name ] ) || isset( $settings[ $name ] );
2113 }
2114
2115 /**
2116 * Get background animation setting value
2117 *
2118 * @param string $type
2119 * @param string $name
2120 * @param array $settings
2121 * @param mixed $default_val
2122 * @return mixed
2123 */
2124 private function get_animation_setting_value( $type, $name, $settings, $default_val = '' ) {
2125 if ( isset( $settings[ $type ][ $name ] ) ) {
2126 return $settings[ $type ][ $name ];
2127 } else {
2128 return $settings[ $name ] ?? $default_val;
2129 }
2130 }
2131
2132 /**
2133 * Build the video background
2134 *
2135 * @param array $background
2136 * @param string $block_content
2137 * @param array $block
2138 * @param WP_Block $block_instance
2139 * @return string
2140 */
2141 private function build_video_background( $background, $block_content, $block, $block_instance ) {
2142 $settings = $background['settings'] ?? [];
2143 $media = $background['video'] ?? [];
2144 $block_class = 'bb:has-background bb:has-background--video';
2145
2146 $background_position = round( ( $settings['focalPoint']['x'] ?? .5 ) * 100 ) . '% ' . round( ( $settings['focalPoint']['y'] ?? .5 ) * 100 ) . '%';
2147 $object_fit = $settings['objectFit'] ?? 'cover';
2148 if ( empty( $object_fit ) ) {
2149 $object_fit = 'cover';
2150 }
2151
2152 $video_style = 'object-fit:' . $object_fit . ';object-position:' . $background_position . ';';
2153 $block_background = sprintf( '<div class="bb:block-background bb:block-background--video"><video src="%1$s" autoplay="autoplay" muted loop playsinline preload="auto" style="%2$s"/></div>', $media['url'], $video_style );
2154
2155 if ( $media['isPausable'] ?? false ) {
2156 $block_class .= ' cbb-has-video-controls';
2157 $block_background .= '<button class="cbb-play-pause cbb-video-play-pause is-playing"></button>';
2158 }
2159
2160 if ( strpos( $block['innerHTML'] ?? '', $block_class ) === false ) {
2161 $block_content = $this->add_class_to_block( $block_content, $block_class );
2162 }
2163
2164 $block_content = $this->add_inner_content_to_block( $block_content, $block_background );
2165
2166 return $block_content;
2167 }
2168
2169 /**
2170 * Render block overlay
2171 *
2172 * @param string $block_content
2173 * @param array $block
2174 * @param WP_Block $block_instance
2175 * @return string
2176 */
2177 public function build_block_overlay( $block_content, $block, $block_instance ) {
2178 // Ignore admin side.
2179 if ( is_admin() ) {
2180 return $block_content;
2181 }
2182
2183 $block_overlay = $block['attrs']['boldblocks']['overlay'] ?? false;
2184
2185 // There is no overlay value.
2186 if ( ! ( $block_overlay['overlayColor'] ?? false ) ) {
2187 return $block_content;
2188 }
2189
2190 // Ignore legacy markup.
2191 if ( strpos( $block['innerHTML'] ?? '', 'bb:block-overlay' ) !== false ) {
2192 return $block_content;
2193 }
2194
2195 $bg_color = '';
2196 $overlay_color = $block_overlay['overlayColor'] ?? [];
2197 if ( ( $overlay_color['gradientSlug'] ?? false ) || ( $overlay_color['gradientValue'] ?? false ) ) {
2198 if ( $overlay_color['gradientSlug'] ?? false ) {
2199 $bg_color = sprintf( 'var(--wp--preset--gradient--%1$s, %2$s)', $overlay_color['gradientSlug'], $overlay_color['gradientValue'] ?? '' );
2200 } else {
2201 $bg_color = $overlay_color['gradientValue'];
2202 }
2203 } elseif ( ( $overlay_color['slug'] ?? false ) || ( $overlay_color['value'] ?? false ) ) {
2204 if ( $overlay_color['slug'] ?? false ) {
2205 $bg_color = sprintf( 'var(--wp--preset--color--%1$s, %2$s)', $overlay_color['slug'], $overlay_color['value'] ?? '' );
2206 } else {
2207 $bg_color = $overlay_color['value'];
2208 }
2209 }
2210
2211 if ( empty( $bg_color ) ) {
2212 return $block_content;
2213 }
2214
2215 $opacity = absint( $block_overlay['opacity'] ?? 100 );
2216
2217 // Build the markup.
2218 $overlay_markup = sprintf( '<div aria-hidden="true" class="bb:block-overlay" style="background:%1$s;opacity:%2$s;"></div>', $bg_color, $opacity / 100 );
2219
2220 $block_class = 'bb:has-overlay';
2221 if ( strpos( $block['innerHTML'] ?? '', $block_class ) === false ) {
2222 $block_content = $this->add_class_to_block( $block_content, $block_class );
2223 }
2224
2225 $block_content = $this->add_inner_content_to_block( $block_content, $overlay_markup );
2226
2227 return $block_content;
2228 }
2229
2230 /**
2231 * Render accordions block data
2232 *
2233 * @param array $parsed_block
2234 * @return array
2235 */
2236 public function render_accordion_block_data( $parsed_block ) {
2237 $block_type = \WP_Block_Type_Registry::get_instance()->get_registered( $parsed_block['blockName'] );
2238
2239 if ( ! $block_type ) {
2240 return $parsed_block;
2241 }
2242
2243 $layout_type = $block_type->supports['layoutType'] ?? '';
2244 if ( ! in_array( $layout_type, [ 'accordion', 'accordionItem' ], true ) ) {
2245 return $parsed_block;
2246 }
2247
2248 static $accordion_counter = 0;
2249 static $accordion_id = '';
2250 static $item_index = [];
2251
2252 if ( 'accordion' === $layout_type ) {
2253 ++$accordion_counter;
2254 $processor = new \WP_HTML_Tag_Processor( $parsed_block['innerHTML'] ?? '' );
2255
2256 $accordion_id = '';
2257 if ( $processor->next_tag() ) {
2258 $accordion_id = $processor->get_attribute( 'id' );
2259 }
2260
2261 if ( ! $accordion_id ) {
2262 $accordion_id = 'accordion-' . $accordion_counter;
2263 }
2264 } elseif ( 'accordionItem' === $layout_type ) {
2265 if ( ! isset( $item_index[ $accordion_id ] ) ) {
2266 $item_index[ $accordion_id ] = 1;
2267 } else {
2268 ++$item_index[ $accordion_id ];
2269 }
2270
2271 $parsed_block['attrs']['boldblocks']['accordionItem']['block_id'] = $accordion_id . '-item-' . $item_index[ $accordion_id ];
2272 }
2273
2274 return $parsed_block;
2275 }
2276
2277 /**
2278 * Render accordion item blocks
2279 *
2280 * @param string $block_content
2281 * @param array $block
2282 * @param WP_Block $block_instance
2283 * @return string
2284 */
2285 public function render_accordion_item_block( $block_content, $block, $block_instance ) {
2286 // Ignore admin side.
2287 if ( is_admin() ) {
2288 return $block_content;
2289 }
2290
2291 // Bail if it is not an accordion item block.
2292 if ( 'accordionItem' !== ( $block_instance->block_type->supports['layoutType'] ?? '' ) ) {
2293 return $block_content;
2294 }
2295
2296 $block_id = $block['attrs']['boldblocks']['accordionItem']['block_id'] ?? '';
2297 $block_id_selector = '#' . $block_id;
2298
2299 if ( $block_id ) {
2300 $processor = new \WP_HTML_Tag_Processor( $block_content );
2301
2302 while ( $processor->next_tag() ) {
2303 $tag_class = $processor->get_attribute( 'class' );
2304 if ( ! $tag_class ) {
2305 continue;
2306 }
2307
2308 if ( strpos( $tag_class, 'accordion-header' ) !== false ) {
2309 $processor->set_attribute( 'data-toggle-target', $block_id_selector );
2310 $processor->set_attribute( 'aria-controls', $block_id );
2311 } elseif ( strpos( $tag_class, 'accordion-link' ) !== false ) {
2312 $processor->set_attribute( 'href', $block_id_selector );
2313 } elseif ( strpos( $tag_class, 'accordion-collapse' ) !== false ) {
2314 $processor->set_attribute( 'id', $block_id );
2315 $processor->set_attribute( 'role', 'region' );
2316 break;
2317 }
2318 }
2319
2320 $block_content = $processor->get_updated_html();
2321 }
2322
2323 return $block_content;
2324 }
2325
2326 /**
2327 * Render grid style for the query loop's grid layout
2328 *
2329 * @param string $block_content
2330 * @param array $block
2331 * @param WP_Block $block_instance
2332 * @return string
2333 */
2334 public function render_query_loop_grid_style( $block_content, $block, $block_instance ) {
2335 // Ignore admin side.
2336 if ( is_admin() ) {
2337 return $block_content;
2338 }
2339
2340 // If this is a core/query block that has grid layout.
2341 $grid_data = $this->get_query_loop_grid_data( $block, $block_instance );
2342 if ( ! $grid_data ) {
2343 return $block_content;
2344 }
2345
2346 $display_layout = $grid_data['displayType'] ?? '';
2347 if ( 'responsiveGrid' === $display_layout && ( $grid_data['grid'] ?? false ) ) {
2348 // Buil selector.
2349 $selector = 'cbb-query-' . $this->get_query_loop_id( $block );
2350
2351 // Get responsive settings.
2352 $breakpoints = $this->get_breakpoints();
2353
2354 // Get custom style.
2355 $block_style = $this->get_query_grid_style(
2356 [
2357 'selector' => ".{$selector} > ul",
2358 'block' => $block,
2359 'data' => $grid_data['grid'],
2360 'breakpoints' => $breakpoints,
2361 ]
2362 );
2363
2364 if ( empty( $block_style ) ) {
2365 return $block_content;
2366 }
2367
2368 $handle = $selector;
2369 // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
2370 wp_register_style( $handle, '' );
2371 wp_add_inline_style( $handle, $block_style );
2372 wp_enqueue_style( $handle );
2373
2374 // Add selector to block wrapper element.
2375 $block_content = $this->add_class_to_block( $block_content, $selector );
2376 }
2377
2378 return $block_content;
2379 }
2380
2381 /**
2382 * Build an unique ID for a query loop
2383 *
2384 * @param array $block
2385 * @return string
2386 */
2387 private function get_query_loop_id( $block ) {
2388 $query_id = $block['attrs']['queryId'] ?? 1;
2389 $key = "id_{$query_id}";
2390 if ( ! isset( $this->query_ids[ $key ] ) ) {
2391 $this->query_ids[ $key ] = 1;
2392 } else {
2393 ++$this->query_ids[ $key ];
2394 }
2395
2396 return $query_id . '-' . $this->query_ids[ $key ];
2397 }
2398
2399 /**
2400 * Get grid layout data
2401 *
2402 * @param array $block
2403 * @param WP_Block $block_instance
2404 * @return array
2405 */
2406 private function get_query_loop_grid_data( $block, $block_instance ) {
2407 $data = [];
2408 $old_layout_type = $block['attrs']['displayLayout']['type'] ?? '';
2409 if ( 'grid' === $old_layout_type ) {
2410 // Convert to the new name.
2411 $old_layout_type = 'responsiveGrid';
2412 }
2413 $data['displayType'] = $old_layout_type;
2414 $data['grid'] = $block['attrs']['boldblocks']['grid'] ?? [];
2415
2416 if ( $block_instance->block_type->api_version >= 3 ) {
2417 $post_template = $this->get_nested_post_template( $block_instance );
2418
2419 if ( $post_template ) {
2420 if ( 'responsiveGrid' === ( $post_template->attributes['boldblocks']['layout']['type'] ?? '' ) ) {
2421 $data['displayType'] = 'responsiveGrid';
2422 $data['grid'] = $post_template->attributes['boldblocks']['grid'] ?? [];
2423 }
2424 }
2425 }
2426
2427 return $data;
2428 }
2429
2430 /**
2431 * Build grid style
2432 *
2433 * @param array $args
2434 * @return string
2435 */
2436 private function get_query_grid_style( $args ) {
2437 $selector = $args['selector'];
2438 $data = $args['data'];
2439
2440 // None-responsive style.
2441 $style = "{$selector}{display:grid;padding:0;margin:0;}{$selector} > li{padding:0;margin:0;}{$selector} > li + li{margin-block-start:0;margin-block-end:0;}";
2442
2443 // Columns style.
2444 $style .= $this->build_query_loop_responsive_style(
2445 array_merge(
2446 $args,
2447 [
2448 'setting_value' => $data['columns'] ?? null,
2449 'func_build_responsive_style' => function ( $args ) {
2450 $style_array = [];
2451 $value = $args['value'];
2452 if ( $value ) {
2453 $style_array = [
2454 '--cbb--grid--columns' => $value,
2455 ];
2456 }
2457
2458 return $style_array;
2459 },
2460 'func_build_dependent_style' => function ( $args ) {
2461 $responsive_styles = $args['responsive_styles'] ?? [];
2462 $selector = $args['selector'];
2463 $breakpoints = $args['breakpoints'] ?? [];
2464 $style = "{$selector}{grid-template-columns:var(--cbb--grid--columns);}";
2465
2466 foreach ( $responsive_styles as $breakpoint => $attribute_array ) {
2467 $media_query = $breakpoints[ $breakpoint ]['minQuery'] ?? '';
2468 if ( $media_query ) {
2469 $style = \str_replace( '##CONTENT##', $style, $media_query );
2470 }
2471
2472 break;
2473 }
2474
2475 return $style;
2476 },
2477 ]
2478 )
2479 );
2480
2481 // Rows style.
2482 $style .= $this->build_query_loop_responsive_style(
2483 array_merge(
2484 $args,
2485 [
2486 'setting_value' => $data['rows'] ?? null,
2487 'func_build_responsive_style' => function ( $args ) {
2488 $style_array = [];
2489 $value = $args['value'];
2490 if ( $value ) {
2491 $style_array = [
2492 '--cbb--grid--rows' => $value,
2493 ];
2494 }
2495
2496 return $style_array;
2497 },
2498 'func_build_dependent_style' => function ( $args ) {
2499 $responsive_styles = $args['responsive_styles'] ?? [];
2500 $selector = $args['selector'];
2501 $breakpoints = $args['breakpoints'] ?? [];
2502 $style = "{$selector}{grid-template-rows:var(--cbb--grid--rows);}";
2503
2504 foreach ( $responsive_styles as $breakpoint => $attribute_array ) {
2505 $media_query = $breakpoints[ $breakpoint ]['minQuery'] ?? '';
2506 if ( $media_query ) {
2507 $style = \str_replace( '##CONTENT##', $style, $media_query );
2508 }
2509
2510 break;
2511 }
2512
2513 return $style;
2514 },
2515 ]
2516 )
2517 );
2518
2519 // Auto rows style.
2520 $style .= $this->build_query_loop_responsive_style(
2521 array_merge(
2522 $args,
2523 [
2524 'setting_value' => $data['autoRows'] ?? null,
2525 'func_build_responsive_style' => function ( $args ) {
2526 $style_array = [];
2527 $value = $args['value'];
2528 if ( $value ) {
2529 $style_array = [
2530 '--cbb--grid--auto-rows' => $value,
2531 ];
2532 }
2533
2534 return $style_array;
2535 },
2536 'func_build_dependent_style' => function ( $args ) {
2537 $responsive_styles = $args['responsive_styles'] ?? [];
2538 $selector = $args['selector'];
2539 $breakpoints = $args['breakpoints'] ?? [];
2540 $style = "{$selector}{grid-auto-rows:var(--cbb--grid--auto-rows);}";
2541
2542 foreach ( $responsive_styles as $breakpoint => $attribute_array ) {
2543 $media_query = $breakpoints[ $breakpoint ]['minQuery'] ?? '';
2544 if ( $media_query ) {
2545 $style = \str_replace( '##CONTENT##', $style, $media_query );
2546 }
2547
2548 break;
2549 }
2550
2551 return $style;
2552 },
2553 ]
2554 )
2555 );
2556
2557 // Gap style.
2558 $style .= $this->build_query_loop_responsive_style(
2559 array_merge(
2560 $args,
2561 [
2562 'setting_value' => $data['gap'] ?? null,
2563 'func_build_responsive_style' => function ( $args ) {
2564 $style_array = [];
2565 $value = $args['value'];
2566 if ( $value && is_array( $value ) ) {
2567 $row = $value['row'] ?? null;
2568
2569 if ( $this->is_valid_value( $row ) ) {
2570 $style_array['--cbb--grid--gap--row'] = $row;
2571 }
2572
2573 $column = $value['column'] ?? null;
2574
2575 if ( $this->is_valid_value( $column ) ) {
2576 $style_array['--cbb--grid--gap--column'] = $column;
2577 }
2578 }
2579
2580 return $style_array;
2581 },
2582 'func_build_dependent_style' => function ( $args ) {
2583 $responsive_styles = $args['responsive_styles'] ?? [];
2584 $selector = $args['selector'];
2585 $breakpoints = $args['breakpoints'] ?? [];
2586 $style = '';
2587
2588 foreach ( $responsive_styles as $breakpoint => $style_array ) {
2589 if ( strpos( $style, 'row-gap' ) !== false && strpos( $style, 'column-gap' ) !== false ) {
2590 break;
2591 }
2592
2593 $media_query = $breakpoints[ $breakpoint ]['minQuery'] ?? '';
2594
2595 if ( strpos( $style, 'row-gap' ) === false && isset( $style_array['--cbb--grid--gap--row'] ) ) {
2596 $row_style = "{$selector}{row-gap:var(--cbb--grid--gap--row);}";
2597 if ( $media_query ) {
2598 $style .= \str_replace( '##CONTENT##', $row_style, $media_query );
2599 } else {
2600 $style .= $row_style;
2601 }
2602 }
2603 if ( strpos( $style, 'column-gap' ) === false && isset( $style_array['--cbb--grid--gap--column'] ) ) {
2604 $column_style = "{$selector}{column-gap:var(--cbb--grid--gap--column);}";
2605 if ( $media_query ) {
2606 $style .= \str_replace( '##CONTENT##', $column_style, $media_query );
2607 } else {
2608 $style .= $column_style;
2609 }
2610 }
2611 }
2612
2613 return $style;
2614 },
2615 ]
2616 )
2617 );
2618
2619 $items = $data['items'] ?? [];
2620 if ( $items ) {
2621 foreach ( $items as $item_key => $item_value ) {
2622 $item_index = str_replace( 'item', '', $item_key );
2623
2624 // Column style.
2625 $style .= $this->build_query_loop_responsive_style(
2626 array_merge(
2627 $args,
2628 [
2629 'setting_value' => $item_value['columnSpan'] ?? [],
2630 'setting_name' => 'columnSpan',
2631 'selector' => "{$selector} > li:nth-of-type({$item_index})",
2632 'func_build_responsive_style' => [ $this, 'build_grid_item_responsive_style' ],
2633 'func_build_dependent_style' => [ $this, 'build_grid_item_dependent_style' ],
2634 ]
2635 )
2636 );
2637
2638 // Row style.
2639 $style .= $this->build_query_loop_responsive_style(
2640 array_merge(
2641 $args,
2642 [
2643 'setting_value' => $item_value['rowSpan'] ?? [],
2644 'setting_name' => 'rowSpan',
2645 'selector' => "{$selector} > li:nth-of-type({$item_index})",
2646 'func_build_responsive_style' => [ $this, 'build_grid_item_responsive_style' ],
2647 'func_build_dependent_style' => [ $this, 'build_grid_item_dependent_style' ],
2648 ]
2649 )
2650 );
2651 }
2652 }
2653
2654 return $style;
2655 }
2656
2657 /**
2658 * Build responsive style for grid item
2659 *
2660 * @param array $args
2661 * @return array
2662 */
2663 private function build_grid_item_responsive_style( $args ) {
2664 $setting_name = $args['setting_name'] ?? 'columnSpan';
2665 $variable_name = 'columnSpan' === $setting_name ? '--cbb--grid-item--column' : '--cbb--grid-item--row';
2666 $style_array = [];
2667 $value = $args['value'];
2668 if ( $value && is_array( $value ) ) {
2669 $span = $value['span'] ?? null;
2670 $start = $value['start'] ?? null;
2671
2672 if ( $this->is_valid_value( $start ) || $this->is_valid_value( $span ) ) {
2673 if ( ! $this->is_valid_value( $start ) ) {
2674 $start = 'auto';
2675 }
2676
2677 if ( ! $this->is_valid_value( $span ) ) {
2678 $span = 'auto';
2679 }
2680 if ( $span !== 'auto' && $span > 0 ) {
2681 $span = "span {$span}";
2682 }
2683
2684 $style_array[ $variable_name ] = "{$start} / {$span}";
2685 }
2686
2687 if ( 'columnSpan' === $setting_name ) {
2688 $order = $value['order'] ?? null;
2689 if ( is_numeric( $order ) ) {
2690 $style_array['--cbb--grid-item--order'] = $order;
2691 }
2692 }
2693 }
2694
2695 return $style_array;
2696 }
2697
2698 /**
2699 * Build dependent style for grid item
2700 *
2701 * @param array $args
2702 * @return array
2703 */
2704 private function build_grid_item_dependent_style( $args ) {
2705 $setting_name = $args['setting_name'] ?? 'columnSpan';
2706 $variable_name = 'columnSpan' === $setting_name ? '--cbb--grid-item--column' : '--cbb--grid-item--row';
2707 $css_name = 'columnSpan' === $setting_name ? 'grid-column' : 'grid-row';
2708
2709 $responsive_styles = $args['responsive_styles'] ?? [];
2710 $selector = $args['selector'];
2711 $breakpoints = $args['breakpoints'] ?? [];
2712 $style = '';
2713
2714 foreach ( $responsive_styles as $breakpoint => $style_array ) {
2715 if ( 'columnSpan' === $setting_name ) {
2716 if ( strpos( $style, $css_name ) !== false && strpos( $style, 'order' ) !== false ) {
2717 break;
2718 }
2719 } elseif ( strpos( $style, $css_name ) !== false ) {
2720 break;
2721 }
2722
2723 $media_query = $breakpoints[ $breakpoint ]['minQuery'] ?? '';
2724
2725 if ( strpos( $style, $css_name ) === false && isset( $style_array[ $variable_name ] ) ) {
2726 $column_style = "{$selector}{{$css_name}:var($variable_name);}";
2727 if ( $media_query ) {
2728 $style .= \str_replace( '##CONTENT##', $column_style, $media_query );
2729 } else {
2730 $style .= $column_style;
2731 }
2732 }
2733 if ( 'columnSpan' === $setting_name && strpos( $style, 'order' ) === false && isset( $style_array['--cbb--grid-item--order'] ) ) {
2734 $order_style = "{$selector}{order:var(--cbb--grid-item--order);}";
2735 if ( $media_query ) {
2736 $style .= \str_replace( '##CONTENT##', $order_style, $media_query );
2737 } else {
2738 $style .= $order_style;
2739 }
2740 }
2741 }
2742
2743 return $style;
2744 }
2745
2746 /**
2747 * Render carousel layout for query loop blocks
2748 *
2749 * @param string $block_content
2750 * @param array $block
2751 * @param WP_Block $block_instance
2752 * @return string
2753 */
2754 public function render_query_loop_carousel_layout( $block_content, $block, $block_instance ) {
2755 // Ignore admin side.
2756 if ( is_admin() ) {
2757 return $block_content;
2758 }
2759
2760 $post_template = $this->get_nested_post_template( $block_instance );
2761 if ( $post_template ) {
2762 // If this has carousel layout.
2763 if ( 'carousel' === ( $post_template->attributes['boldblocks']['layout']['type'] ?? '' ) ) {
2764 // Build carousel settings.
2765 $carousel_settings = $post_template->attributes['boldblocks']['carousel'] ?? [];
2766 if ( $carousel_settings ) {
2767 if ( ! $this->block_has_attribute( 'data-carousel-settings', $block_content ) ) {
2768 $carousel_attr = $this->build_carousel_settings( $carousel_settings, true );
2769
2770 if ( $carousel_attr ) {
2771 $block_content = $this->add_data_to_block( $block_content, 'data-carousel-settings', esc_attr( $carousel_attr ) );
2772 }
2773 }
2774
2775 // Buil selector.
2776 $selector = 'cbb-query-' . $this->get_query_loop_id( $block );
2777
2778 // Get responsive settings.
2779 $breakpoints = $this->get_breakpoints();
2780
2781 // Get custom style.
2782 $block_style = $this->get_carousel_preload_style(
2783 '',
2784 [
2785 'selector' => ".{$selector}",
2786 'settings' => [ 'carousel' => $carousel_settings ],
2787 'block_layout' => 'carousel',
2788 'breakpoints' => $breakpoints,
2789 ]
2790 );
2791
2792 $carousel_class = 'js-carousel-layout cbb-carousel-preload';
2793
2794 if ( $block_style ) {
2795 $handle = $selector;
2796 // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
2797 wp_register_style( $handle, '' );
2798 wp_add_inline_style( $handle, $block_style );
2799 wp_enqueue_style( $handle );
2800
2801 $carousel_class .= ' ' . $selector;
2802 }
2803
2804 if ( $carousel_settings['displayLoader'] ?? false ) {
2805 $carousel_class .= ' has-preloader';
2806
2807 if ( 'coverflow' === ( $carousel_settings['effect'] ?? '' ) ) {
2808 $carousel_class .= ' is-fading-loader';
2809 }
2810
2811 // Add preloader.
2812 $block_content = $this->add_inner_content_to_block( $block_content, $this->get_carousel_preloader_markup( $block, $block_instance ) );
2813 }
2814
2815 // Add selector to block wrapper element.
2816 $block_content = $this->add_class_to_block( $block_content, $carousel_class );
2817 }
2818 }
2819 }
2820
2821 return $block_content;
2822 }
2823
2824 /**
2825 * Add preload style for carousels
2826 *
2827 * @param string $style
2828 * @param array $args
2829 */
2830 public function get_carousel_preload_style( $style, $args ) {
2831 if ( 'carousel' === ( $args['block_layout'] ?? '' ) ) {
2832 $preload_style = '';
2833 $value = $args['settings']['carousel'] ?? false;
2834 $selector = $args['selector'];
2835 $breakpoints = $args['breakpoints'];
2836 if ( $value && is_array( $value ) ) {
2837 if ( $value['enableResponsive'] ?? false ) {
2838 $setting_value = $value['breakpoints'] ?? [];
2839 $responsive_styles = [];
2840 foreach ( $setting_value as $breakpoint => $value_by_breakpoint ) {
2841 $value = null;
2842 if ( $this->is_valid_value( $value_by_breakpoint['value'] ?? null ) ) {
2843 $value = $value_by_breakpoint['value'];
2844 } elseif ( isset( $value_by_breakpoint['inherit'] ) && is_string( $value_by_breakpoint['inherit'] ) ) {
2845 $value = $setting_value[ $value_by_breakpoint['inherit'] ]['value'] ?? null;
2846 }
2847
2848 if ( $this->is_valid_value( $value ) ) {
2849 $style_by_breakpoint = $this->build_carousel_preload_style( $value );
2850 if ( $style_by_breakpoint ) {
2851 $responsive_styles[ $breakpoint ] = $style_by_breakpoint;
2852 }
2853 }
2854 }
2855
2856 $this->sort_styles( $responsive_styles );
2857
2858 $last_responsive_style = '';
2859 foreach ( $responsive_styles as $breakpoint => $style_by_breakpoint ) {
2860 if ( ! $style_by_breakpoint || ! is_array( $style_by_breakpoint ) ) {
2861 continue;
2862 }
2863
2864 $responsive_style = '';
2865 foreach ( $style_by_breakpoint as $attr_key => $attr_value ) {
2866 $responsive_style .= "{$attr_key}:{$attr_value};";
2867 }
2868
2869 if ( $responsive_style !== $last_responsive_style ) {
2870 $style_with_selector = "{$selector}{{$responsive_style}}";
2871 $media_query = $breakpoints[ $breakpoint ]['minQuery'] ?? '';
2872 if ( $media_query ) {
2873 $preload_style .= \str_replace( '##CONTENT##', $style_with_selector, $media_query );
2874 } else {
2875 $preload_style .= $style_with_selector;
2876 }
2877 $last_responsive_style = $responsive_style;
2878 }
2879 }
2880 } else {
2881 $style_array = $this->build_carousel_preload_style( $value );
2882 $attr_style = '';
2883 foreach ( $style_array as $attr_key => $attr_value ) {
2884 $attr_style .= "{$attr_key}:{$attr_value};";
2885 }
2886
2887 if ( $attr_style ) {
2888 $preload_style .= "{$selector}{{$attr_style}}";
2889 }
2890 }
2891 }
2892
2893 if ( $preload_style ) {
2894 $style .= $preload_style;
2895 }
2896 }
2897
2898 return $style;
2899 }
2900
2901 /**
2902 * Build preload style for carousels
2903 *
2904 * @param array $value
2905 * @return void
2906 */
2907 private function build_carousel_preload_style( $value ) {
2908 $style_array = [];
2909 $slides_perview = $value['slidesPerView'] ?? 1;
2910 if ( is_array( $slides_perview ) ) {
2911 if ( $slides_perview['auto'] ?? false ) {
2912 $slides_perview = 'auto';
2913 } else {
2914 $slides_perview = $slides_perview['value'] ?? 1;
2915 }
2916 }
2917
2918 if ( $slides_perview !== 'auto' ) {
2919 $gap_count = absint( ceil( $slides_perview ) ) - 1;
2920 if ( $slides_perview <= 0 ) {
2921 $slides_perview = 1;
2922 }
2923 } else {
2924 $gap_count = 0;
2925 }
2926
2927 $space_between = $value['spaceBetween'] ?? 0;
2928 if ( $space_between < 0 ) {
2929 $space_between = 0;
2930 }
2931
2932 $total_gap = $gap_count * $space_between;
2933
2934 $style_array['--cbb-slide-width'] = $slides_perview === 'auto' ? 'auto' : "calc((100% - {$total_gap}px ) / {$slides_perview})";
2935 $style_array['--cbb-carousel-gap'] = "{$space_between}px";
2936
2937 return $style_array;
2938 }
2939
2940 /**
2941 * Build a json string of carousel settings
2942 *
2943 * @param array $carousel_settings
2944 * @param boolean $exclude_li_role
2945 * @return string
2946 */
2947 private function build_carousel_settings( $carousel_settings, $exclude_li_role = false ) {
2948 // phpcs:disable
2949 $dataset = [];
2950
2951 // Speed.
2952 if ( $carousel_settings['speed'] ?? false ) {
2953 $dataset['speed'] = $carousel_settings['speed'];
2954 }
2955
2956 // Loop and rewind.
2957 $dataset['loop'] = false;
2958 $dataset['rewind'] = false;
2959
2960 $loop_type = $carousel_settings['loopType'] ?? '';
2961 if ( 'infinite' === $loop_type ) {
2962 $dataset['loop'] = true;
2963 } elseif ( 'rewind' === $loop_type ) {
2964 $dataset['rewind'] = true;
2965 }
2966
2967 // Autoplay.
2968 $dataset['autoplay'] = false;
2969 $autoplay = $carousel_settings['autoplay'] ?? false;
2970 if ( $autoplay && is_array( $autoplay ) && ( $autoplay['enable'] ?? false ) ) {
2971 unset( $autoplay['enable'] );
2972
2973 if ( !$dataset['loop'] && !$dataset['rewind']) {
2974 $autoplay['stopOnLastSlide'] = true;
2975 }
2976
2977 $dataset['autoplay'] = $autoplay;
2978 }
2979
2980 // Play/pause.
2981 if ( $carousel_settings['playPause']['enable'] ?? false ) {
2982 $dataset['playPause'] = $carousel_settings['playPause'];
2983 }
2984
2985 // Direction.
2986 if ( $carousel_settings['direction'] ?? false ) {
2987 $dataset['direction'] = $carousel_settings['direction'];
2988 }
2989
2990 // Effect.
2991 $effect = $carousel_settings['effect'] ?? 'slide';
2992 // Handle wrong default data.
2993 if ( is_array( $effect ) ) {
2994 $effect = 'slide';
2995 }
2996
2997 if ( $effect ) {
2998 $dataset['effect'] = $effect;
2999 $effectSettings = $carousel_settings['effectSettings'] ?? [];
3000 $settings = $effectSettings[ $effect . 'Effect' ] ?? false;
3001
3002 // Fade.
3003 if ( 'fade' === $effect ) {
3004 $dataset['fadeEffect'] = [ 'crossFade' => true ];
3005 } elseif ( 'coverflow' === $effect ) {
3006 // Coverflow.
3007 if ( is_array( $settings ) ) {
3008 $dataset['coverflowEffect'] = $settings;
3009 } else {
3010 $dataset['coverflowEffect'] = [ 'slideShadows' => false ];
3011 }
3012 } elseif ( 'creative' === $effect ) {
3013 // Creative.
3014 if ( is_array( $settings ) ) {
3015 $dataset['creativeEffect'] = $settings;
3016 } else {
3017 $dataset['creativeEffect'] = [
3018 'prev' => [
3019 'translate' => [ '-20%', 0, -1 ],
3020 ],
3021 'next' => [
3022 'translate' => [ '100%', 0, 0 ],
3023 ],
3024 ];
3025 }
3026
3027 $dataset['direction'] = "horizontal";
3028 }
3029 }
3030
3031 // slidesPerView.
3032 $slidesPerViewDependencies = [
3033 'fade',
3034 'flip',
3035 'cube',
3036 'cards',
3037 'creative',
3038 ];
3039
3040 if ( in_array($effect, $slidesPerViewDependencies, true ) ) {
3041 $dataset['slidesPerView'] = 1;
3042 } else {
3043 $slidesPerView = $this->carousel_refine_slides_per_view( $carousel_settings['slidesPerView'] ?? 1 );
3044 $slidesPerGroup = absint( $carousel_settings['slidesPerGroup'] ?? 0 );
3045
3046 if ( $slidesPerView ) {
3047 $dataset['slidesPerView'] = $slidesPerView;
3048
3049 if ( $slidesPerView >= 2 && $slidesPerGroup ) {
3050 $dataset['slidesPerGroup'] = $slidesPerGroup;
3051 }
3052 }
3053
3054 // spaceBetween.
3055 $spaceBetween = absint( $carousel_settings['spaceBetween'] ?? 0 );
3056 if ( $spaceBetween ) {
3057 $dataset['spaceBetween'] = $spaceBetween;
3058 }
3059
3060 // Breakpoints.
3061 $breakpoint_settings = $this->get_breakpoints();
3062 $breakpoint_values = [
3063 'sm' => $breakpoint_settings['sm']['breakpoint'] ?? 576,
3064 'md' => $breakpoint_settings['md']['breakpoint'] ?? 768,
3065 'lg' => $breakpoint_settings['lg']['breakpoint'] ?? 1024,
3066 ];
3067
3068 $breakpoints = $carousel_settings['breakpoints'] ?? [];
3069 if ( ($carousel_settings['enableResponsive'] ?? false) && is_array( $breakpoints ) ) {
3070 // Mobile first.
3071 $this->sort_styles( $breakpoints );
3072
3073 $breakpoint_atts = [];
3074 foreach ( array_keys( $breakpoints ) as $breakpoint ) :
3075 $value = $breakpoints[ $breakpoint ]['value'] ?? '';
3076 $inherit = $breakpoints[ $breakpoint ]['inherit'] ?? '';
3077
3078 $value_by_breakpoint = false;
3079 if ( $value && is_array( $value ) ) {
3080 $value_by_breakpoint = $value;
3081 }
3082
3083 if ( ! $value_by_breakpoint && is_string( $inherit ) ) {
3084 $inheritValue = $breakpoints[ $inherit ] ?? '';
3085 if ( $inheritValue && is_array( $inheritValue ) ) {
3086 $value_by_breakpoint = $inheritValue;
3087 }
3088 }
3089
3090 if ( is_array( $value_by_breakpoint ) ) {
3091 $value_by_breakpoint['slidesPerView'] = $this->carousel_refine_slides_per_view( $value_by_breakpoint['slidesPerView'] ?? 1);
3092 $breakpoint_atts[ $breakpoint_values[ $breakpoint ] ] = $value_by_breakpoint;
3093 }
3094 endforeach;
3095
3096 if ( $breakpoint_atts ) {
3097 // Replace global params.
3098 $first_breakpoint = array_values($breakpoint_atts)[0];
3099 $dataset['slidesPerView'] = $first_breakpoint['slidesPerView'];
3100 if ($dataset['slidesPerView'] > 1 && ($first_breakpoint['slidesPerGroup'] ?? 0)) {
3101 $dataset['slidesPerGroup'] = $first_breakpoint['slidesPerGroup'];
3102 }
3103 if ( ! empty( $first_breakpoint['spaceBetween'] ) ) {
3104 $dataset['spaceBetween'] = $first_breakpoint['spaceBetween'];
3105 }
3106
3107 // Set the breakpoints.
3108 $dataset['breakpoints'] = $breakpoint_atts;
3109 }
3110 } else {
3111 if ( $carousel_settings['oneSlidePerViewInMobile'] ?? false ) {
3112 $dataset['slidesPerView'] = 1;
3113 if ( $slidesPerGroup ) {
3114 $dataset['slidesPerGroup'] = 1;
3115 }
3116
3117 $dataset['breakpoints'] = [
3118 $breakpoint_values['sm'] => [
3119 'slidesPerView' => 1,
3120 'slidesPerGroup' => 1,
3121 ],
3122
3123 $breakpoint_values['md'] => [
3124 'slidesPerView' => $slidesPerView,
3125 'slidesPerGroup' => $slidesPerGroup ? $slidesPerGroup : 1,
3126 ],
3127 ];
3128 }
3129 }
3130 }
3131
3132 // centeredSlides.
3133 $dataset['centeredSlides'] = $carousel_settings['centeredSlides'] ?? false;
3134 if ($dataset['centeredSlides'] && $effect === 'slide' && ($carousel_settings['centeredSlidesSettings']['enable'] ?? '')) {
3135 $dataset['centeredSlidesSettings'] = $carousel_settings['centeredSlidesSettings'];
3136 }
3137
3138 // Pagination.
3139 $pagination = $carousel_settings['pagination'] ?? [];
3140 $dataset['pagination'] = $pagination['enable'] ?? false;
3141 if ( $dataset['pagination'] ) {
3142 $dataset['paginationSettings'] = $pagination;
3143 }
3144
3145 // Navigation.
3146 $navigation = $carousel_settings['navigation'] ?? [];
3147 $dataset['navigation'] = $navigation['enable'] ?? false;
3148 if ( $dataset['navigation'] ) {
3149 $dataset['navigationSettings'] = $navigation;
3150 }
3151
3152 // Scrollbar.
3153 $scrollbar = $carousel_settings['scrollbar'] ?? [];
3154 $dataset['scrollbar'] = $scrollbar['enable'] ?? false;
3155 if ( $dataset['scrollbar'] ) {
3156 $dataset['scrollbarSettings'] = $scrollbar;
3157 }
3158
3159 // Equal height?.
3160 $equalHeight = $carousel_settings['equalHeight'] ?? false;
3161 if ( $equalHeight ) {
3162 // Only add the property when it is true to prevent from invalid content.
3163 $dataset['equalHeight'] = $equalHeight;
3164 }
3165
3166 // Sync carousels.
3167 $thumbs = $carousel_settings['thumbs'] ?? [];
3168 if ( $thumbs['enable'] ?? false ) {
3169 $dataset['thumbsSettings'] = $thumbs;
3170 }
3171
3172 // Exclude li role.
3173 if ( $exclude_li_role ) {
3174 $dataset['a11y'] = ['slideRole' => '', 'slideLabelMessage' => ''];
3175 }
3176
3177 return wp_json_encode( $dataset );
3178 // phpcs:enable
3179 }
3180
3181 /**
3182 * Refine the slides per view
3183 *
3184 * @param mixed $raw_value
3185 * @return void
3186 */
3187 private function carousel_refine_slides_per_view( $raw_value ) {
3188 $value = $raw_value;
3189 if ( is_array( $raw_value ) ) {
3190 $value = $raw_value['auto'] ?? false
3191 ? 'auto'
3192 : floatval( $raw_value['value'] ?? 1 );
3193 }
3194
3195 return $value ? $value : 1;
3196 }
3197
3198 /**
3199 * Get nested post template block for core/query
3200 *
3201 * @param WP_Block $block_instance
3202 * @return boolean|WP_Block
3203 */
3204 private function get_nested_post_template( $block_instance ) {
3205 return $this->find_nested_block( $block_instance, 'core/post-template' );
3206 }
3207
3208 /**
3209 * Find a specific nested block from a parent block
3210 *
3211 * @param WP_Block $block
3212 * @param string $block_name
3213 * @return mixed
3214 */
3215 public function find_nested_block( $block, $block_name ) {
3216 if ( $block->name === $block_name ) {
3217 return $block;
3218 }
3219
3220 foreach ( $block->inner_blocks as $inner_block ) {
3221 $result = $this->find_nested_block( $inner_block, $block_name );
3222
3223 if ( $result ) {
3224 return $result;
3225 }
3226 }
3227
3228 return false;
3229 }
3230
3231 /**
3232 * Build responsive style
3233 *
3234 * @param array $args
3235 * @param array &$style_array
3236 * @param array &$responsive_style_array
3237 * @return string
3238 */
3239 private function build_query_loop_responsive_style( $args ) {
3240 $func_build_responsive_style = $args['func_build_responsive_style'] ?? '';
3241 if ( ! \is_callable( $func_build_responsive_style ) ) {
3242 return '';
3243 }
3244
3245 $setting_value = $args['setting_value'] ?? [];
3246 if ( empty( $setting_value ) || ! \is_array( $setting_value ) ) {
3247 return '';
3248 }
3249
3250 $breakpoints = $args['breakpoints'];
3251 $selector = $args['selector'];
3252
3253 $responsive_styles = [];
3254 foreach ( $setting_value as $breakpoint => $value_by_breakpoint ) {
3255 $value = null;
3256 if ( $this->is_valid_value( $value_by_breakpoint['value'] ?? null ) ) {
3257 $value = $value_by_breakpoint['value'];
3258 } elseif ( isset( $value_by_breakpoint['inherit'] ) && is_string( $value_by_breakpoint['inherit'] ) ) {
3259 $value = $setting_value[ $value_by_breakpoint['inherit'] ]['value'] ?? null;
3260 }
3261
3262 if ( $this->is_valid_value( $value ) ) {
3263 $style_by_breakpoint = $func_build_responsive_style( array_merge( $args, [ 'value' => $value ] ) );
3264 if ( $style_by_breakpoint ) {
3265 $responsive_styles[ $breakpoint ] = $style_by_breakpoint;
3266 }
3267 }
3268 }
3269
3270 $this->sort_styles( $responsive_styles );
3271
3272 $dependent_style = '';
3273 $func_build_dependent_style = $args['func_build_dependent_style'] ?? '';
3274 if ( \is_callable( $func_build_dependent_style ) ) {
3275 $dependent_style = $func_build_dependent_style( array_merge( $args, [ 'responsive_styles' => $responsive_styles ] ) );
3276 }
3277
3278 $style = '';
3279 $last_responsive_style = '';
3280 foreach ( $responsive_styles as $breakpoint => $style_by_breakpoint ) {
3281 if ( ! $style_by_breakpoint || ! is_array( $style_by_breakpoint ) ) {
3282 continue;
3283 }
3284
3285 $responsive_style = '';
3286 foreach ( $style_by_breakpoint as $attr_key => $attr_value ) {
3287 $responsive_style .= "{$attr_key}:{$attr_value};";
3288 }
3289
3290 if ( $responsive_style !== $last_responsive_style ) {
3291 $style_with_selector = "{$selector}{{$responsive_style}}";
3292 $media_query = $breakpoints[ $breakpoint ]['minQuery'] ?? '';
3293 if ( $media_query ) {
3294 $style .= \str_replace( '##CONTENT##', $style_with_selector, $media_query );
3295 } else {
3296 $style .= $style_with_selector;
3297 }
3298 $last_responsive_style = $responsive_style;
3299 }
3300 }
3301
3302 return $style . $dependent_style;
3303 }
3304
3305 /**
3306 * Build the breakpoints
3307 *
3308 * @return array
3309 */
3310 private function get_breakpoints() {
3311 if ( ! $this->breakpoints ) {
3312 $breakpoints = get_option( 'cbb_breakpoints' );
3313 if ( empty( $breakpoints ) ) {
3314 $breakpoints = [
3315 [
3316 'breakpoint' => 576,
3317 'prefix' => 'sm',
3318 ],
3319 [
3320 'breakpoint' => 768,
3321 'prefix' => 'md',
3322 ],
3323 [
3324 'breakpoint' => 1024,
3325 'prefix' => 'lg',
3326 ],
3327 ];
3328 }
3329
3330 $breakpoints = array_filter(
3331 array_map(
3332 function ( $item ) {
3333 if ( empty( $item['breakpoint'] ) || empty( $item['prefix'] ) ) {
3334 return false;
3335 }
3336
3337 $breakpoint = absint( $item['breakpoint'] );
3338
3339 if ( $breakpoint < 10 ) {
3340 return false;
3341 }
3342
3343 $max_breakpoint = $breakpoint - 1;
3344 $item['breakpointMax'] = $max_breakpoint;
3345
3346 if ( $item['prefix'] !== 'sm' ) {
3347 $item['minQuery'] = "@media (min-width: {$breakpoint}px){##CONTENT##}";
3348 $item['maxQuery'] = "@media (max-width: {$max_breakpoint}px){##MAX_CONTENT##}";
3349 } else {
3350 $item['minQuery'] = '';
3351 $item['maxQuery'] = "@media (max-width: {$max_breakpoint}px){##MAX_CONTENT##}";
3352 }
3353
3354 return $item;
3355 },
3356 $breakpoints
3357 )
3358 );
3359
3360 $this->breakpoints = array_column( $breakpoints, null, 'prefix' );
3361 }
3362
3363 return $this->breakpoints;
3364 }
3365
3366 /**
3367 * Generate block selector.
3368 *
3369 * @param string $block_name
3370 * @param WP_Block $block_instance
3371 * @return string
3372 */
3373 private function generate_selector( $block_name, $block_instance ) {
3374 $selector_prefix = 'cbb';
3375
3376 if ( '' !== $this->style_contexts['loop'] ) {
3377 $post_id = $block_instance->context['postId'] ?? 0;
3378 if ( ! $this->style_contexts['post'] || $this->style_contexts['post'] !== $post_id ) {
3379 $this->style_contexts['post'] = $post_id;
3380 $this->style_contexts['counter'] = 0;
3381 $this->style_contexts['content_counter'] = 0;
3382 }
3383
3384 if ( $this->style_contexts['content'] ) {
3385 // The content context selector.
3386 $content_counter = ++$this->style_contexts['content_counter'];
3387 return "{$selector_prefix}-p{$this->style_contexts['post']}-{$content_counter}";
3388 }
3389
3390 $counter = ++$this->style_contexts['counter'];
3391 // The selector inside the loop.
3392 return "{$selector_prefix}-{$this->style_contexts['loop']}-{$counter}";
3393 } else {
3394 // The global selector.
3395 return wp_unique_id( "{$selector_prefix}-g-" );
3396 }
3397 }
3398
3399 /**
3400 * Update loop id when having a post template block
3401 *
3402 * @param array $context
3403 * @param array $block
3404 * @return array
3405 */
3406 public function update_loop_id( $context, $block ) {
3407 if ( $this->is_loop_temlate_block( $block['blockName'] ?? '' ) ) {
3408 if ( isset( $context['queryId'] ) ) {
3409 $query_id = $context['queryId'];
3410 if ( in_array( $query_id, $this->style_contexts['loops'], true ) ) {
3411 $count = count(
3412 array_filter(
3413 $this->style_contexts['loops'],
3414 function ( $value ) use ( $query_id ) {
3415 return $value === $query_id;
3416 }
3417 )
3418 );
3419
3420 $this->style_contexts['loop'] = "{$query_id}i{$count}";
3421 } else {
3422 $this->style_contexts['loop'] = $query_id;
3423 }
3424
3425 $this->style_contexts['loops'][] = $query_id;
3426 }
3427 }
3428
3429 // Post content inside a loop.
3430 if ( $this->style_contexts['loop'] && 'core/post-content' === ( $block['blockName'] ?? '' ) ) {
3431 $this->style_contexts['content'] = true;
3432 }
3433
3434 return $context;
3435 }
3436
3437 /**
3438 * Reset loop id when the post template is rendered
3439 *
3440 * @param string $content
3441 * @param array $block
3442 * @return string
3443 */
3444 public function reset_loop_id( $content, $block ) {
3445 if ( $this->is_loop_temlate_block( $block['blockName'] ?? '' ) ) {
3446 $this->style_contexts['loop'] = '';
3447 $this->style_contexts['post'] = 0;
3448 }
3449
3450 // Post content inside a loop.
3451 if ( 'core/post-content' === ( $block['blockName'] ?? '' ) ) {
3452 $this->style_contexts['content'] = false;
3453 }
3454
3455 return $content;
3456 }
3457
3458 /**
3459 * Render sticky attributes for core template part blocks
3460 *
3461 * @param string $block_content
3462 * @param array $block
3463 * @param WP_Block $block_instance
3464 * @return string
3465 */
3466 public function render_template_part_sticky( $block_content, $block, $block_instance ) {
3467 // Ignore admin side.
3468 if ( is_admin() ) {
3469 return $block_content;
3470 }
3471
3472 // If this block has sticky data.
3473 $sticky_data = $block['attrs']['boldblocks']['sticky'] ?? false;
3474 if ( $sticky_data && ( $sticky_data['enable'] ?? false ) ) {
3475 $sticky_classes = [ 'is-sticky-block' ];
3476 $sticky_classes[] = 'is-stick-to-' . ( $sticky_data['stickTo'] ?? 'top' );
3477
3478 if ( $sticky_data['isStickOnScrollingUp'] ?? false ) {
3479 $sticky_classes[] = 'is-sticky-on-scrollup';
3480 } elseif ( $sticky_data['isFixed'] ?? false ) {
3481 $sticky_classes[] = 'is-fixed';
3482 if ( $sticky_data['isInFlow'] ?? false ) {
3483 $sticky_classes[] = 'is-in-flow';
3484 }
3485 if ( $sticky_data['isDetectingScroll'] ?? false ) {
3486 $sticky_classes[] = 'is-detecting-scroll';
3487 }
3488 } elseif ( $sticky_data['isDetectingStuck'] ?? false ) {
3489 $sticky_classes[] = 'is-detecting-stuck';
3490 }
3491
3492 if ( in_array( $block['attrs']['area'] ?? '', [ 'header', 'footer' ], true ) ) {
3493 $sticky_classes[] = 'is-sticky-' . $block['attrs']['area'];
3494 }
3495
3496 $block_content = $this->add_class_to_block( $block_content, implode( ' ', $sticky_classes ) );
3497 }
3498
3499 return $block_content;
3500 }
3501
3502 /**
3503 * Render settings for carousel layout blocks
3504 *
3505 * @param string $block_content
3506 * @param array $block
3507 * @param WP_Block $block_instance
3508 * @return string
3509 */
3510 public function render_carousel_settings( $block_content, $block, $block_instance ) {
3511 // Ignore admin side.
3512 if ( is_admin() ) {
3513 return $block_content;
3514 }
3515
3516 // There is no carousel layout.
3517 if ( 'carousel' !== ( $block_instance->block_type->supports['layoutType'] ?? '' ) ) {
3518 return $block_content;
3519 }
3520
3521 // Carousel settings.
3522 $carousel_settings = $block['attrs']['boldblocks']['carousel'] ?? [];
3523
3524 // Dataset attribute.
3525 $carousel_dataset = $this->build_carousel_settings( $carousel_settings );
3526 if ( $carousel_dataset ) {
3527 $block_content = $this->add_data_to_block( $block_content, 'data-carousel-settings', esc_attr( $carousel_dataset ) );
3528 }
3529
3530 // Preload class.
3531 $preloader_class = 'cbb-carousel-preload';
3532
3533 // Has preloader?
3534 $has_preloader = $carousel_settings['displayLoader'] ?? false;
3535 if ( $has_preloader ) {
3536 $preloader_class .= ' has-preloader';
3537 if ( 'coverflow' === ( $carousel_settings['effect'] ?? '' ) ) {
3538 $preloader_class .= ' is-fading-loader';
3539 }
3540
3541 // Add the loader to the markup.
3542 $block_content = $this->add_inner_content_to_block( $block_content, $this->get_carousel_preloader_markup( $block, $block_instance ) );
3543 }
3544
3545 // Mark it as having preloader.
3546 $block_content = $this->add_class_to_block( $block_content, $preloader_class );
3547
3548 return $block_content;
3549 }
3550
3551 /**
3552 * Get the preloader markup for carousels
3553 *
3554 * @param array $block
3555 * @param WP_Block $block_instance
3556 * @return string
3557 */
3558 private function get_carousel_preloader_markup( $block, $block_instance ) {
3559 return apply_filters( 'cbb_carousel_preloader', '<div class="cbb-loader"></div>', $block, $block_instance );
3560 }
3561
3562 /**
3563 * Migrate cbb class for old CBB blocks.
3564 *
3565 * @param string $block_content
3566 * @param array $block
3567 * @param WP_Block $block_instance
3568 * @return string
3569 */
3570 public function migrate_cbb_class( $block_content, $block, $block_instance ) {
3571 // Ignore admin side or empty content.
3572 if ( is_admin() || ! $block_content ) {
3573 return $block_content;
3574 }
3575
3576 // There is no need to migrate.
3577 $migrate_cbb_class = $block_instance->block_type->supports['migrateClass'] ?? '';
3578 if ( ! $migrate_cbb_class ) {
3579 return $block_content;
3580 }
3581
3582 // Replace the class.
3583 $block_reader = new \WP_HTML_Tag_Processor( $block_content );
3584 if ( $block_reader->next_tag( [ 'class_name' => $migrate_cbb_class ] ) ) {
3585 $new_class = str_ends_with( $migrate_cbb_class, 'custom' ) ? 'cbb-block' : 'cbb-block-parent';
3586 $block_reader->set_attribute( 'class', str_replace( $migrate_cbb_class, $new_class, $block_reader->get_attribute( 'class' ) ) );
3587
3588 // Render the updated HTML.
3589 return $block_reader->get_updated_html();
3590 }
3591
3592 return $block_content;
3593 }
3594
3595 /**
3596 * Support block style for non-cbb blocks
3597 *
3598 * @param string $is_supported
3599 * @param array $block
3600 * @return boolean
3601 */
3602 public function support_block_style( $is_supported, $block ) {
3603 if ( in_array( $block['blockName'] ?? '', $this->non_cbb_blocks, true ) ) {
3604 $is_supported = true;
3605 }
3606
3607 return $is_supported;
3608 }
3609
3610 /**
3611 * Set a valid layout for none-cbb blocks
3612 *
3613 * @param string $block_layout
3614 * @param array $block
3615 * @return string
3616 */
3617 public function get_block_layout( $block_layout, $block ) {
3618 if ( in_array( $block['blockName'] ?? '', $this->non_cbb_blocks, true ) ) {
3619 $block_layout = 'standalone';
3620 }
3621
3622 return $block_layout;
3623 }
3624
3625 /**
3626 * Check whether the block is a loop template block.
3627 *
3628 * @param string $block_name
3629 * @return array
3630 */
3631 private function is_loop_temlate_block( $block_name ) {
3632 $loop_template_blocks = apply_filters( 'cbb_get_loop_temlate_blocks', [ 'core/post-template' ] );
3633
3634 return in_array( $block_name, $loop_template_blocks, true );
3635 }
3636
3637 /**
3638 * Check whether current block has custom style or not.
3639 *
3640 * @param array $block
3641 * @param WP_Block $block_instance
3642 * @return boolean
3643 */
3644 private function has_style( $block, $block_instance ) {
3645 // There is no boldblocks value.
3646 if ( ! isset( $block['attrs']['boldblocks'] ) ) {
3647 return false;
3648 }
3649
3650 return apply_filters( 'cbb_support_block_style', isset( $block_instance->block_type->supports['cbb'] ), $block, $block_instance );
3651 }
3652
3653 /**
3654 * Order style array by breakpoints
3655 *
3656 * @param array $styles
3657 * @return void
3658 */
3659 private function sort_styles( &$styles ) {
3660 uksort(
3661 $styles,
3662 function ( $a, $b ) {
3663 if ( 'lg' === $a || 'sm' === $b ) {
3664 return 1;
3665 }
3666
3667 if ( 'sm' === $a || 'lg' === $b ) {
3668 return -1;
3669 }
3670
3671 return 0;
3672 }
3673 );
3674 }
3675
3676 /**
3677 * Build responsive style
3678 *
3679 * @param array $args
3680 * @param array &$responsive_style_array
3681 * @param array &$classes
3682 * @return string
3683 */
3684 private function build_responsive_style( $args, &$responsive_style_array, &$classes ) {
3685 $return_value = false;
3686 $func_build_responsive_style = $args['func_build_responsive_style'] ?? '';
3687 if ( ! \is_callable( $func_build_responsive_style ) ) {
3688 return $return_value;
3689 }
3690
3691 $setting_value = $args['setting_value'] ?? [];
3692 if ( empty( $setting_value ) || ! \is_array( $setting_value ) ) {
3693 return $return_value;
3694 }
3695
3696 $breakpoints = $args['breakpoints'];
3697 $selector = $args['selector'];
3698
3699 $setting_values = [];
3700 $responsive_styles = [];
3701 foreach ( $setting_value as $breakpoint => $value_by_breakpoint ) {
3702 $value = null;
3703 if ( $this->is_valid_value( $value_by_breakpoint['value'] ?? null ) ) {
3704 $value = $value_by_breakpoint['value'];
3705 } elseif ( isset( $value_by_breakpoint['inherit'] ) && is_string( $value_by_breakpoint['inherit'] ) ) {
3706 $value = $setting_value[ $value_by_breakpoint['inherit'] ]['value'] ?? null;
3707 }
3708
3709 if ( $this->is_valid_value( $value ) ) {
3710 $style_by_breakpoint = $func_build_responsive_style( array_merge( $args, [ 'value' => $value ] ) );
3711 if ( $style_by_breakpoint ) {
3712 $responsive_styles[ $breakpoint ] = $style_by_breakpoint;
3713 }
3714 }
3715
3716 $setting_values[ $breakpoint ] = $value;
3717 }
3718
3719 $this->sort_styles( $responsive_styles );
3720
3721 $dependent_style = '';
3722 $func_build_dependent_style = $args['func_build_dependent_style'] ?? '';
3723 if ( \is_callable( $func_build_dependent_style ) ) {
3724 $dependent_style = $func_build_dependent_style(
3725 array_merge(
3726 $args,
3727 [
3728 'setting_values' => $setting_values,
3729 'responsive_styles' => $responsive_styles,
3730 ]
3731 ),
3732 $classes
3733 );
3734 }
3735
3736 $keys = [];
3737 $style = '';
3738 $last_responsive_style = '';
3739 foreach ( $responsive_styles as $breakpoint => $style_by_breakpoint ) {
3740 $responsive_style = '';
3741 foreach ( $style_by_breakpoint as $attr_key => $attr_value ) {
3742 $responsive_style .= "{$attr_key}:{$attr_value};";
3743
3744 if ( ! in_array( $attr_key, $keys, true ) ) {
3745 $keys[] = $attr_key;
3746 $classes[] = $breakpoint . '-' . str_replace( '--cbb--', 'cbb-', $attr_key );
3747 }
3748 }
3749
3750 if ( $responsive_style !== $last_responsive_style ) {
3751 $style_with_selector = "{$selector}{{$responsive_style}}";
3752 $min_query = $breakpoints[ $breakpoint ]['minQuery'] ?? '';
3753 if ( $min_query ) {
3754 $style_with_selector = \str_replace( '##CONTENT##', $style_with_selector, $min_query );
3755 }
3756
3757 $style .= $style_with_selector;
3758 $last_responsive_style = $responsive_style;
3759 }
3760 }
3761
3762 // Add style to the style_array.
3763 if ( $style ) {
3764 $style .= $dependent_style;
3765 $responsive_style_array[] = $style;
3766
3767 return $style;
3768 }
3769
3770 return $return_value;
3771 }
3772
3773 /**
3774 * Check wether the block markup has a attribute or not.
3775 *
3776 * @param string $attribute
3777 * @param string $block_content
3778 * @return boolean
3779 */
3780 private function block_has_attribute( $attribute, $block_content ) {
3781 $tags = new \WP_HTML_Tag_Processor( $block_content );
3782 if ( $tags->next_tag() ) {
3783 return null !== $tags->get_attribute( $attribute );
3784 }
3785
3786 return false;
3787 }
3788
3789 /**
3790 * Add CSS class to block wrapper
3791 *
3792 * @param string $block_content
3793 * @param string $classes
3794 * @return string
3795 */
3796 public function add_class_to_block( $block_content, $classes ) {
3797 $tags = new \WP_HTML_Tag_Processor( $block_content );
3798 if ( $tags->next_tag() ) {
3799 $tags->add_class( $classes );
3800 }
3801 return $tags->get_updated_html();
3802 }
3803
3804 /**
3805 * Add data attribute to block wrapper
3806 *
3807 * @param string $block_content
3808 * @param string $value
3809 * @return string
3810 */
3811 public function add_data_to_block( $block_content, $name, $value ) {
3812 $tags = new \WP_HTML_Tag_Processor( $block_content );
3813 if ( $tags->next_tag() ) {
3814 $tags->set_attribute( $name, $value );
3815 }
3816
3817 return $tags->get_updated_html();
3818 }
3819
3820 /**
3821 * Add inner content to block markup
3822 *
3823 * @param string $block_content
3824 * @param string $inner_content
3825 * @return string
3826 */
3827 public function add_inner_content_to_block( $block_content, $inner_content ) {
3828 if ( $inner_content ) {
3829 $block_content = \preg_replace(
3830 '/' . \preg_quote( '>', '/' ) . '/',
3831 '>' . $inner_content,
3832 $block_content,
3833 1
3834 );
3835 }
3836
3837 return $block_content;
3838 }
3839
3840 /**
3841 * Detect a value is valid
3842 *
3843 * @param mixed $value
3844 * @return boolean
3845 */
3846 private function is_valid_value( $value ) {
3847 return isset( $value ) && $value !== '';
3848 }
3849
3850 /**
3851 * Detect a responsive value is valid
3852 *
3853 * @param mixed $value
3854 * @return boolean
3855 */
3856 private function is_valid_responsive_value( $value ) {
3857 return ! empty( $value['lg']['value'] ) || ! empty( $value['md']['value'] ) || ! empty( $value['sm']['value'] );
3858 }
3859
3860 /**
3861 * Get value for spacing var
3862 *
3863 * @param string $value
3864 * @return string
3865 */
3866 private function get_spacing_value( $value ) {
3867 if ( ! $this->is_valid_value( $value ) ) {
3868 return '';
3869 }
3870
3871 if ( strpos( $value, 'var:preset|spacing|' ) !== false ) {
3872 preg_match( '/var:preset\|spacing\|(.+)/', $value, $slug );
3873
3874 if ( ! $slug ) {
3875 return $value;
3876 }
3877
3878 return "var(--wp--preset--spacing--{$slug[1]})";
3879 }
3880
3881 return $value;
3882 }
3883
3884 /**
3885 * Get CSS value for a color object.
3886 *
3887 * @param array $color_array
3888 * @return string
3889 */
3890 private function get_css_color_value( $color_array ) {
3891 $value = '';
3892
3893 if ( ! empty( $color_array ) ) {
3894 if ( is_string( $color_array ) ) {
3895 $value = $color_array;
3896 } elseif ( is_array( $color_array ) ) {
3897 if ( $color_array['gradientSlug'] ?? false ) {
3898 $value = "var(--wp--preset--gradient--{$color_array['gradientSlug']}, {$color_array['gradientValue']})";
3899 } elseif ( $color_array['gradientValue'] ?? false ) {
3900 $value = $color_array['gradientValue'];
3901 } elseif ( $color_array['slug'] ?? false ) {
3902 $value = "var(--wp--preset--color--{$color_array['slug']}, {$color_array['value']})";
3903 } elseif ( $color_array['value'] ?? false ) {
3904 $value = $color_array['value'];
3905 }
3906 }
3907 }
3908
3909 return $value;
3910 }
3911
3912 /**
3913 * Refine custom JS and CSS value
3914 *
3915 * @param string $code
3916 * @param array $tokens
3917 * @param string $type
3918 * @return string
3919 */
3920 public function refine_custom_value( $code, $tokens = [], $type = 'CSS' ) {
3921 // Remove tags.
3922 if ( 'CSS' === $type ) {
3923 $unsafe_tags = [ 'script', 'style', 'iframe', 'object', 'embed', 'img', 'video', 'audio', 'math', 'form' ];
3924
3925 // Build a regex pattern that matches:
3926 // 1. <tag ...> ... </tag> (full tag with content)
3927 // 2. <tag ... /> or <tag ...> (self-closing or empty).
3928 $pattern = sprintf(
3929 '/<(%s)\b[^>]*>(?:.*?<\/\1\s*>)?/is',
3930 implode( '|', $unsafe_tags )
3931 );
3932
3933 // Remove all unsafe tags in one go.
3934 $code = preg_replace( $pattern, '', $code );
3935 }
3936
3937 // Replace breakpoint tokens.
3938 $breakpoints = $this->get_breakpoints();
3939
3940 $tokens = array_merge(
3941 $tokens,
3942 [
3943 'TABLET_UP' => ( $breakpoints['md']['breakpoint'] ?? 768 ) . 'px',
3944 'TABLET_DOWN' => ( $breakpoints['md']['breakpointMax'] ?? 767 ) . 'px',
3945 'DESKTOP_UP' => ( $breakpoints['lg']['breakpoint'] ?? 1024 ) . 'px',
3946 'DESKTOP_DOWN' => ( $breakpoints['lg']['breakpointMax'] ?? 1023 ) . 'px',
3947 ]
3948 );
3949
3950 $code = str_replace( array_keys( $tokens ), array_values( $tokens ), $code );
3951
3952 // Minify it.
3953 return $this->minify( $code, $type );
3954 }
3955
3956 /**
3957 * Simple minify JS and CSS
3958 *
3959 * @param string $code
3960 * @param string $type
3961 * @return string
3962 */
3963 public function minify( $code, $type = 'CSS' ) {
3964 if ( $this->the_plugin_instance->is_debug_mode() ) {
3965 return $code;
3966 }
3967
3968 if ( 'CSS' === $type ) {
3969 $code = preg_replace( '/\s+/', ' ', $code );
3970 } elseif ( 'JS' === $type ) {
3971 // Remove comments first - https://stackoverflow.com/a/31907095/1038868.
3972 $pattern = '/(?:(?:\/\*(?:[^*]|(?:\*+[^*\/]))*\*+\/)|(?:(?<!\:|\\\|\'|\")\/\/.*))/';
3973 $code = preg_replace( $pattern, '', $code );
3974
3975 // Remove spaces, tabs, and new lines after a line if it ends with a semicolon, a bracket, or a comma, or an open parentheses.
3976 $code = preg_replace( '/([;{,\(])\s+/', '$1', $code );
3977
3978 // Remove before ...
3979 $code = preg_replace( '/\s+([}\)])/', '$1', $code );
3980
3981 // Remove spaces before a line.
3982 $code = preg_replace( '/^\s+/m', '', $code );
3983 }
3984
3985 return trim( $code );
3986 }
3987 }
3988 endif;
3989