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

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