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

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