PluginProbe
Content Blocks Builder – Create blocks, repeater blocks with carousel, grid, popup layouts / 2.8.10
Content Blocks Builder – Create blocks, repeater blocks with carousel, grid, popup layouts v2.8.10
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.10, at includes/custom-style.php

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