PluginProbe
Content Blocks Builder – Create blocks, repeater blocks with carousel, grid, popup layouts / 2.8.3
Content Blocks Builder – Create blocks, repeater blocks with carousel, grid, popup layouts v2.8.3
2.8.14 2.8.13 2.8.12 2.7.5 2.7.6 2.7.7 2.7.8 2.7.9 2.8.0 2.8.1 2.8.10 2.8.11 2.8.2 2.8.3 2.8.4 2.8.5 2.8.6 2.8.7 2.8.8 2.8.9 trunk 1.0.1 1.0.2 1.1.0 1.1.1 All 147 releases
content-blocks-builder / includes / custom-style.php

custom-style.php in Content Blocks Builder – Create blocks, repeater blocks with carousel, grid, popup layouts 2.8.3, at includes/custom-style.php

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