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

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