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

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