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

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