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

3,735 lines 122.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * The custom style component
4 *
5 * @package BoldBlocks
6 * @author Phi Phan <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 ) ) . '" 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 );
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 * @return string
2716 */
2717 private function build_carousel_settings( $carousel_settings ) {
2718 // phpcs:disable
2719 $dataset = [];
2720
2721 // Speed.
2722 if ( $carousel_settings['speed'] ?? false ) {
2723 $dataset['speed'] = $carousel_settings['speed'];
2724 }
2725
2726 // Loop and rewind.
2727 $dataset['loop'] = false;
2728 $dataset['rewind'] = false;
2729
2730 $loop_type = $carousel_settings['loopType'] ?? '';
2731 if ( 'infinite' === $loop_type ) {
2732 $dataset['loop'] = true;
2733 } elseif ( 'rewind' === $loop_type ) {
2734 $dataset['rewind'] = true;
2735 }
2736
2737 // Autoplay.
2738 $dataset['autoplay'] = false;
2739 $autoplay = $carousel_settings['autoplay'] ?? false;
2740 if ( $autoplay && is_array( $autoplay ) && ( $autoplay['enable'] ?? false ) ) {
2741 unset( $autoplay['enable'] );
2742
2743 if ( !$dataset['loop'] && !$dataset['rewind']) {
2744 $autoplay['stopOnLastSlide'] = true;
2745 }
2746
2747 $dataset['autoplay'] = $autoplay;
2748 }
2749
2750 // Play/pause.
2751 if ( $carousel_settings['playPause']['enable'] ?? false ) {
2752 $dataset['playPause'] = $carousel_settings['playPause'];
2753 }
2754
2755 // Direction.
2756 if ( $carousel_settings['direction'] ?? false ) {
2757 $dataset['direction'] = $carousel_settings['direction'];
2758 }
2759
2760 // Effect.
2761 $effect = $carousel_settings['effect'] ?? 'slide';
2762 // Handle wrong default data.
2763 if ( is_array( $effect ) ) {
2764 $effect = 'slide';
2765 }
2766
2767 if ( $effect ) {
2768 $dataset['effect'] = $effect;
2769 $effectSettings = $carousel_settings['effectSettings'] ?? [];
2770 $settings = $effectSettings[ $effect . 'Effect' ] ?? false;
2771
2772 // Fade.
2773 if ( 'fade' === $effect ) {
2774 $dataset['fadeEffect'] = [ 'crossFade' => true ];
2775 } elseif ( 'coverflow' === $effect ) {
2776 // Coverflow.
2777 if ( is_array( $settings ) ) {
2778 $dataset['coverflowEffect'] = $settings;
2779 } else {
2780 $dataset['coverflowEffect'] = [ 'slideShadows' => false ];
2781 }
2782 } elseif ( 'creative' === $effect ) {
2783 // Creative.
2784 if ( is_array( $settings ) ) {
2785 $dataset['creativeEffect'] = $settings;
2786 } else {
2787 $dataset['creativeEffect'] = [
2788 'prev' => [
2789 'translate' => [ '-20%', 0, -1 ],
2790 ],
2791 'next' => [
2792 'translate' => [ '100%', 0, 0 ],
2793 ],
2794 ];
2795 }
2796
2797 $dataset['direction'] = "horizontal";
2798 }
2799 }
2800
2801 // slidesPerView.
2802 $slidesPerViewDependencies = [
2803 'fade',
2804 'flip',
2805 'cube',
2806 'cards',
2807 'creative',
2808 ];
2809
2810 if ( in_array($effect, $slidesPerViewDependencies, true ) ) {
2811 $dataset['slidesPerView'] = 1;
2812 } else {
2813 $slidesPerView = $this->carousel_refine_slides_per_view( $carousel_settings['slidesPerView'] ?? 1 );
2814
2815 if ( $slidesPerView ) {
2816 $dataset['slidesPerView'] = $slidesPerView;
2817
2818 if ( $slidesPerView > 1 && ( $carousel_settings['slidesPerGroup'] ?? false ) ) {
2819 $dataset['slidesPerGroup'] = absint( $carousel_settings['slidesPerGroup'] );
2820 }
2821 }
2822
2823 // spaceBetween.
2824 $spaceBetween = absint( $carousel_settings['spaceBetween'] ?? 0 );
2825 if ( $spaceBetween ) {
2826 $dataset['spaceBetween'] = $spaceBetween;
2827 }
2828
2829 // Breakpoints.
2830 $breakpoint_settings = $this->get_breakpoints();
2831 $breakpoint_values = [
2832 'sm' => $breakpoint_settings['sm']['breakpoint'] ?? 576,
2833 'md' => $breakpoint_settings['md']['breakpoint'] ?? 768,
2834 'lg' => $breakpoint_settings['lg']['breakpoint'] ?? 1024,
2835 ];
2836
2837 $breakpoints = $carousel_settings['breakpoints'] ?? [];
2838 if ( ($carousel_settings['enableResponsive'] ?? false) && is_array( $breakpoints ) ) {
2839 // Mobile first.
2840 $this->sort_styles( $breakpoints );
2841
2842 $breakpoint_atts = [];
2843 foreach ( array_keys( $breakpoints ) as $breakpoint ) :
2844 $value = $breakpoints[ $breakpoint ]['value'] ?? '';
2845 $inherit = $breakpoints[ $breakpoint ]['inherit'] ?? '';
2846
2847 $value_by_breakpoint = false;
2848 if ( $value && is_array( $value ) ) {
2849 $value_by_breakpoint = $value;
2850 }
2851
2852 if ( ! $value_by_breakpoint && is_string( $inherit ) ) {
2853 $inheritValue = $breakpoints[ $inherit ] ?? '';
2854 if ( $inheritValue && is_array( $inheritValue ) ) {
2855 $value_by_breakpoint = $inheritValue;
2856 }
2857 }
2858
2859 if ( is_array( $value_by_breakpoint ) ) {
2860 $value_by_breakpoint['slidesPerView'] = $this->carousel_refine_slides_per_view( $value_by_breakpoint['slidesPerView'] ?? 1);
2861 $breakpoint_atts[ $breakpoint_values[ $breakpoint ] ] = $value_by_breakpoint;
2862 }
2863 endforeach;
2864
2865 if ( $breakpoint_atts ) {
2866 // Replace global params.
2867 $first_breakpoint = array_values($breakpoint_atts)[0];
2868 $dataset['slidesPerView'] = $first_breakpoint['slidesPerView'];
2869 if ($dataset['slidesPerView'] > 1 && ($first_breakpoint['slidesPerGroup'] ?? 0)) {
2870 $dataset['slidesPerGroup'] = $first_breakpoint['slidesPerGroup'];
2871 }
2872 if ( $first_breakpoint['spaceBetween'] ) {
2873 $dataset['spaceBetween'] = $first_breakpoint['spaceBetween'];
2874 }
2875
2876 // Set the breakpoints.
2877 $dataset['breakpoints'] = $breakpoint_atts;
2878 }
2879 } else {
2880 if ( $carousel_settings['oneSlidePerViewInMobile'] ?? false ) {
2881 $dataset['breakpoints'] = [
2882 $breakpoint_values['sm'] => [
2883 'slidesPerView' => 1,
2884 'spaceBetween' => 0,
2885 ],
2886
2887 $breakpoint_values['md'] => [
2888 'slidesPerView' => $slidesPerView,
2889 'spaceBetween' => $spaceBetween,
2890 ],
2891 ];
2892 }
2893 }
2894 }
2895
2896 // centeredSlides.
2897 $dataset['centeredSlides'] = $carousel_settings['centeredSlides'] ?? false;
2898 if ($dataset['centeredSlides'] && $effect === 'slide' && ($carousel_settings['centeredSlidesSettings']['enable'] ?? '')) {
2899 $dataset['centeredSlidesSettings'] = $carousel_settings['centeredSlidesSettings'];
2900 }
2901
2902 // Pagination.
2903 $pagination = $carousel_settings['pagination'] ?? [];
2904 $dataset['pagination'] = $pagination['enable'] ?? false;
2905 if ( $dataset['pagination'] ) {
2906 $dataset['paginationSettings'] = $pagination;
2907 }
2908
2909 // Navigation.
2910 $navigation = $carousel_settings['navigation'] ?? [];
2911 $dataset['navigation'] = $navigation['enable'] ?? false;
2912 if ( $dataset['navigation'] ) {
2913 $dataset['navigationSettings'] = $navigation;
2914 }
2915
2916 // Scrollbar.
2917 $scrollbar = $carousel_settings['scrollbar'] ?? [];
2918 $dataset['scrollbar'] = $scrollbar['enable'] ?? false;
2919 if ( $dataset['scrollbar'] ) {
2920 $dataset['scrollbarSettings'] = $scrollbar;
2921 }
2922
2923 // Equal height?.
2924 $equalHeight = $carousel_settings['equalHeight'] ?? false;
2925 if ( $equalHeight ) {
2926 // Only add the property when it is true to prevent from invalid content.
2927 $dataset['equalHeight'] = $equalHeight;
2928 }
2929
2930 // Sync carousels.
2931 $thumbs = $carousel_settings['thumbs'] ?? [];
2932 if ( $thumbs['enable'] ?? false ) {
2933 $dataset['thumbsSettings'] = $thumbs;
2934 }
2935
2936 return wp_json_encode( $dataset );
2937 // phpcs:enable
2938 }
2939
2940 /**
2941 * Refine the slides per view
2942 *
2943 * @param mixed $raw_value
2944 * @return void
2945 */
2946 private function carousel_refine_slides_per_view( $raw_value ) {
2947 $value = $raw_value;
2948 if ( is_array( $raw_value ) ) {
2949 $value = $raw_value['auto'] ?? false
2950 ? 'auto'
2951 : floatval( $raw_value['value'] ?? 1 );
2952 }
2953
2954 return $value ? $value : 1;
2955 }
2956
2957 /**
2958 * Get nested post template block for core/query
2959 *
2960 * @param WP_Block $block_instance
2961 * @return boolean|WP_Block
2962 */
2963 private function get_nested_post_template( $block_instance ) {
2964 return $this->find_nested_block( $block_instance, 'core/post-template' );
2965 }
2966
2967 /**
2968 * Find a specific nested block from a parent block
2969 *
2970 * @param WP_Block $block
2971 * @param string $block_name
2972 * @return mixed
2973 */
2974 public function find_nested_block( $block, $block_name ) {
2975 if ( $block->name === $block_name ) {
2976 return $block;
2977 }
2978
2979 foreach ( $block->inner_blocks as $inner_block ) {
2980 $result = $this->find_nested_block( $inner_block, $block_name );
2981
2982 if ( $result ) {
2983 return $result;
2984 }
2985 }
2986
2987 return false;
2988 }
2989
2990 /**
2991 * Build responsive style
2992 *
2993 * @param array $args
2994 * @param array &$style_array
2995 * @param array &$responsive_style_array
2996 * @return string
2997 */
2998 private function build_query_loop_responsive_style( $args ) {
2999 $func_build_responsive_style = $args['func_build_responsive_style'] ?? '';
3000 if ( ! \is_callable( $func_build_responsive_style ) ) {
3001 return '';
3002 }
3003
3004 $setting_value = $args['setting_value'] ?? [];
3005 if ( empty( $setting_value ) || ! \is_array( $setting_value ) ) {
3006 return '';
3007 }
3008
3009 $breakpoints = $args['breakpoints'];
3010 $selector = $args['selector'];
3011
3012 $responsive_styles = [];
3013 foreach ( $setting_value as $breakpoint => $value_by_breakpoint ) {
3014 $value = null;
3015 if ( $this->is_valid_value( $value_by_breakpoint['value'] ?? null ) ) {
3016 $value = $value_by_breakpoint['value'];
3017 } elseif ( isset( $value_by_breakpoint['inherit'] ) && is_string( $value_by_breakpoint['inherit'] ) ) {
3018 $value = $setting_value[ $value_by_breakpoint['inherit'] ]['value'] ?? null;
3019 }
3020
3021 if ( $this->is_valid_value( $value ) ) {
3022 $style_by_breakpoint = $func_build_responsive_style( array_merge( $args, [ 'value' => $value ] ) );
3023 if ( $style_by_breakpoint ) {
3024 $responsive_styles[ $breakpoint ] = $style_by_breakpoint;
3025 }
3026 }
3027 }
3028
3029 $this->sort_styles( $responsive_styles );
3030
3031 $dependent_style = '';
3032 $func_build_dependent_style = $args['func_build_dependent_style'] ?? '';
3033 if ( \is_callable( $func_build_dependent_style ) ) {
3034 $dependent_style = $func_build_dependent_style( array_merge( $args, [ 'responsive_styles' => $responsive_styles ] ) );
3035 }
3036
3037 $style = '';
3038 $last_responsive_style = '';
3039 foreach ( $responsive_styles as $breakpoint => $style_by_breakpoint ) {
3040 if ( ! $style_by_breakpoint || ! is_array( $style_by_breakpoint ) ) {
3041 continue;
3042 }
3043
3044 $responsive_style = '';
3045 foreach ( $style_by_breakpoint as $attr_key => $attr_value ) {
3046 $responsive_style .= "{$attr_key}:{$attr_value};";
3047 }
3048
3049 if ( $responsive_style !== $last_responsive_style ) {
3050 $style_with_selector = "{$selector}{{$responsive_style}}";
3051 $media_query = $breakpoints[ $breakpoint ]['minQuery'] ?? '';
3052 if ( $media_query ) {
3053 $style .= \str_replace( '##CONTENT##', $style_with_selector, $media_query );
3054 } else {
3055 $style .= $style_with_selector;
3056 }
3057 $last_responsive_style = $responsive_style;
3058 }
3059 }
3060
3061 return $style . $dependent_style;
3062 }
3063
3064 /**
3065 * Build the breakpoints
3066 *
3067 * @return array
3068 */
3069 private function get_breakpoints() {
3070 if ( ! $this->breakpoints ) {
3071 $breakpoints = get_option( 'cbb_breakpoints' );
3072 if ( empty( $breakpoints ) ) {
3073 $breakpoints = [
3074 [
3075 'breakpoint' => 576,
3076 'prefix' => 'sm',
3077 ],
3078 [
3079 'breakpoint' => 768,
3080 'prefix' => 'md',
3081 ],
3082 [
3083 'breakpoint' => 1024,
3084 'prefix' => 'lg',
3085 ],
3086 ];
3087 }
3088
3089 $breakpoints = array_filter(
3090 array_map(
3091 function ( $item ) {
3092 if ( empty( $item['breakpoint'] ) || empty( $item['prefix'] ) ) {
3093 return false;
3094 }
3095
3096 $breakpoint = absint( $item['breakpoint'] );
3097
3098 if ( $breakpoint < 10 ) {
3099 return false;
3100 }
3101
3102 $max_breakpoint = $breakpoint - 1;
3103 $item['breakpointMax'] = $max_breakpoint;
3104
3105 if ( $item['prefix'] !== 'sm' ) {
3106 $item['minQuery'] = "@media (min-width: {$breakpoint}px){##CONTENT##}";
3107 $item['maxQuery'] = "@media (max-width: {$max_breakpoint}px){##MAX_CONTENT##}";
3108 } else {
3109 $item['minQuery'] = '';
3110 $item['maxQuery'] = "@media (max-width: {$max_breakpoint}px){##MAX_CONTENT##}";
3111 }
3112
3113 return $item;
3114 },
3115 $breakpoints
3116 )
3117 );
3118
3119 $this->breakpoints = array_column( $breakpoints, null, 'prefix' );
3120 }
3121
3122 return $this->breakpoints;
3123 }
3124
3125 /**
3126 * Generate block selector.
3127 *
3128 * @param string $block_name
3129 * @param WP_Block $block_instance
3130 * @return string
3131 */
3132 private function generate_selector( $block_name, $block_instance ) {
3133 $selector_prefix = 'cbb';
3134
3135 if ( '' !== $this->style_contexts['loop'] ) {
3136 $post_id = $block_instance->context['postId'] ?? 0;
3137 if ( ! $this->style_contexts['post'] || $this->style_contexts['post'] !== $post_id ) {
3138 $this->style_contexts['post'] = $post_id;
3139 $this->style_contexts['counter'] = 0;
3140 $this->style_contexts['content_counter'] = 0;
3141 }
3142
3143 if ( $this->style_contexts['content'] ) {
3144 // The content context selector.
3145 $content_counter = ++$this->style_contexts['content_counter'];
3146 return "{$selector_prefix}-p{$this->style_contexts['post']}-{$content_counter}";
3147 }
3148
3149 $counter = ++$this->style_contexts['counter'];
3150 // The selector inside the loop.
3151 return "{$selector_prefix}-{$this->style_contexts['loop']}-{$counter}";
3152 } else {
3153 // The global selector.
3154 return wp_unique_id( "{$selector_prefix}-g-" );
3155 }
3156 }
3157
3158 /**
3159 * Update loop id when having a post template block
3160 *
3161 * @param array $context
3162 * @param array $block
3163 * @return array
3164 */
3165 public function update_loop_id( $context, $block ) {
3166 if ( $this->is_loop_temlate_block( $block['blockName'] ?? '' ) ) {
3167 if ( isset( $context['queryId'] ) ) {
3168 $query_id = $context['queryId'];
3169 if ( in_array( $query_id, $this->style_contexts['loops'], true ) ) {
3170 $count = count(
3171 array_filter(
3172 $this->style_contexts['loops'],
3173 function ( $value ) use ( $query_id ) {
3174 return $value === $query_id;
3175 }
3176 )
3177 );
3178
3179 $this->style_contexts['loop'] = "{$query_id}i{$count}";
3180 } else {
3181 $this->style_contexts['loop'] = $query_id;
3182 }
3183
3184 $this->style_contexts['loops'][] = $query_id;
3185 }
3186 }
3187
3188 // Post content inside a loop.
3189 if ( $this->style_contexts['loop'] && 'core/post-content' === ( $block['blockName'] ?? '' ) ) {
3190 $this->style_contexts['content'] = true;
3191 }
3192
3193 return $context;
3194 }
3195
3196 /**
3197 * Reset loop id when the post template is rendered
3198 *
3199 * @param string $content
3200 * @param array $block
3201 * @return string
3202 */
3203 public function reset_loop_id( $content, $block ) {
3204 if ( $this->is_loop_temlate_block( $block['blockName'] ?? '' ) ) {
3205 $this->style_contexts['loop'] = '';
3206 $this->style_contexts['post'] = 0;
3207 }
3208
3209 // Post content inside a loop.
3210 if ( 'core/post-content' === ( $block['blockName'] ?? '' ) ) {
3211 $this->style_contexts['content'] = false;
3212 }
3213
3214 return $content;
3215 }
3216
3217 /**
3218 * Render sticky attributes for core template part blocks
3219 *
3220 * @param string $block_content
3221 * @param array $block
3222 * @param WP_Block $block_instance
3223 * @return string
3224 */
3225 public function render_template_part_sticky( $block_content, $block, $block_instance ) {
3226 // Ignore admin side.
3227 if ( is_admin() ) {
3228 return $block_content;
3229 }
3230
3231 // If this block has sticky data.
3232 $sticky_data = $block['attrs']['boldblocks']['sticky'] ?? false;
3233 if ( $sticky_data && ( $sticky_data['enable'] ?? false ) ) {
3234 $sticky_classes = [ 'is-sticky-block' ];
3235 $sticky_classes[] = 'is-stick-to-' . ( $sticky_data['stickTo'] ?? 'top' );
3236
3237 if ( $sticky_data['isStickOnScrollingUp'] ?? false ) {
3238 $sticky_classes[] = 'is-sticky-on-scrollup';
3239 } elseif ( $sticky_data['isFixed'] ?? false ) {
3240 $sticky_classes[] = 'is-fixed';
3241 if ( $sticky_data['isInFlow'] ?? false ) {
3242 $sticky_classes[] = 'is-in-flow';
3243 }
3244 if ( $sticky_data['isDetectingScroll'] ?? false ) {
3245 $sticky_classes[] = 'is-detecting-scroll';
3246 }
3247 } elseif ( $sticky_data['isDetectingStuck'] ?? false ) {
3248 $sticky_classes[] = 'is-detecting-stuck';
3249 }
3250
3251 if ( in_array( $block['attrs']['area'] ?? '', [ 'header', 'footer' ], true ) ) {
3252 $sticky_classes[] = 'is-sticky-' . $block['attrs']['area'];
3253 }
3254
3255 $block_content = $this->add_class_to_block( $block_content, implode( ' ', $sticky_classes ) );
3256 }
3257
3258 return $block_content;
3259 }
3260
3261 /**
3262 * Render settings for carousel layout blocks
3263 *
3264 * @param string $block_content
3265 * @param array $block
3266 * @param WP_Block $block_instance
3267 * @return string
3268 */
3269 public function render_carousel_settings( $block_content, $block, $block_instance ) {
3270 // Ignore admin side.
3271 if ( is_admin() ) {
3272 return $block_content;
3273 }
3274
3275 // There is no carousel layout.
3276 if ( 'carousel' !== ( $block_instance->block_type->supports['layoutType'] ?? '' ) ) {
3277 return $block_content;
3278 }
3279
3280 // Carousel settings.
3281 $carousel_settings = $block['attrs']['boldblocks']['carousel'] ?? [];
3282
3283 // Dataset attribute.
3284 $carousel_dataset = $this->build_carousel_settings( $carousel_settings );
3285 if ( $carousel_dataset ) {
3286 $block_content = $this->add_data_to_block( $block_content, 'data-carousel-settings', esc_attr( $carousel_dataset ) );
3287 }
3288
3289 // Preload class.
3290 $preloader_class = 'cbb-carousel-preload';
3291
3292 // Has preloader?
3293 $has_preloader = $carousel_settings['displayLoader'] ?? false;
3294 if ( $has_preloader ) {
3295 $preloader_class .= ' has-preloader';
3296 if ( 'coverflow' === ( $carousel_settings['effect'] ?? '' ) ) {
3297 $preloader_class .= ' is-fading-loader';
3298 }
3299
3300 // Add the loader to the markup.
3301 $block_content = $this->add_inner_content_to_block( $block_content, $this->get_carousel_preloader_markup( $block, $block_instance ) );
3302 }
3303
3304 // Mark it as having preloader.
3305 $block_content = $this->add_class_to_block( $block_content, $preloader_class );
3306
3307 return $block_content;
3308 }
3309
3310 /**
3311 * Get the preloader markup for carousels
3312 *
3313 * @param array $block
3314 * @param WP_Block $block_instance
3315 * @return string
3316 */
3317 private function get_carousel_preloader_markup( $block, $block_instance ) {
3318 return apply_filters( 'cbb_carousel_preloader', '<div class="cbb-loader"></div>', $block, $block_instance );
3319 }
3320
3321 /**
3322 * Migrate cbb class for old CBB blocks.
3323 *
3324 * @param string $block_content
3325 * @param array $block
3326 * @param WP_Block $block_instance
3327 * @return string
3328 */
3329 public function migrate_cbb_class( $block_content, $block, $block_instance ) {
3330 // Ignore admin side or empty content.
3331 if ( is_admin() || ! $block_content ) {
3332 return $block_content;
3333 }
3334
3335 // There is no need to migrate.
3336 $migrate_cbb_class = $block_instance->block_type->supports['migrateClass'] ?? '';
3337 if ( ! $migrate_cbb_class ) {
3338 return $block_content;
3339 }
3340
3341 // Replace the class.
3342 $block_reader = new \WP_HTML_Tag_Processor( $block_content );
3343 if ( $block_reader->next_tag( [ 'class_name' => $migrate_cbb_class ] ) ) {
3344 $new_class = str_ends_with( $migrate_cbb_class, 'custom' ) ? 'cbb-block' : 'cbb-block-parent';
3345 $block_reader->set_attribute( 'class', str_replace( $migrate_cbb_class, $new_class, $block_reader->get_attribute( 'class' ) ) );
3346
3347 // Render the updated HTML.
3348 return $block_reader->get_updated_html();
3349 }
3350
3351 return $block_content;
3352 }
3353
3354 /**
3355 * Support block style for non-cbb blocks
3356 *
3357 * @param string $is_supported
3358 * @param array $block
3359 * @return boolean
3360 */
3361 public function support_block_style( $is_supported, $block ) {
3362 if ( in_array( $block['blockName'] ?? '', $this->non_cbb_blocks, true ) ) {
3363 $is_supported = true;
3364 }
3365
3366 return $is_supported;
3367 }
3368
3369 /**
3370 * Set a valid layout for none-cbb blocks
3371 *
3372 * @param string $block_layout
3373 * @param array $block
3374 * @return string
3375 */
3376 public function get_block_layout( $block_layout, $block ) {
3377 if ( in_array( $block['blockName'] ?? '', $this->non_cbb_blocks, true ) ) {
3378 $block_layout = 'standalone';
3379 }
3380
3381 return $block_layout;
3382 }
3383
3384 /**
3385 * Check whether the block is a loop template block.
3386 *
3387 * @param string $block_name
3388 * @return array
3389 */
3390 private function is_loop_temlate_block( $block_name ) {
3391 $loop_template_blocks = apply_filters( 'cbb_get_loop_temlate_blocks', [ 'core/post-template' ] );
3392
3393 return in_array( $block_name, $loop_template_blocks, true );
3394 }
3395
3396 /**
3397 * Check whether current block has custom style or not.
3398 *
3399 * @param array $block
3400 * @param WP_Block $block_instance
3401 * @return boolean
3402 */
3403 private function has_style( $block, $block_instance ) {
3404 // There is no boldblocks value.
3405 if ( ! isset( $block['attrs']['boldblocks'] ) ) {
3406 return false;
3407 }
3408
3409 return apply_filters( 'cbb_support_block_style', isset( $block_instance->block_type->supports['cbb'] ), $block, $block_instance );
3410 }
3411
3412 /**
3413 * Order style array by breakpoints
3414 *
3415 * @param array $styles
3416 * @return void
3417 */
3418 private function sort_styles( &$styles ) {
3419 uksort(
3420 $styles,
3421 function ( $a, $b ) {
3422 if ( 'lg' === $a || 'sm' === $b ) {
3423 return 1;
3424 }
3425
3426 if ( 'sm' === $a || 'lg' === $b ) {
3427 return -1;
3428 }
3429
3430 return 0;
3431 }
3432 );
3433 }
3434
3435 /**
3436 * Build responsive style
3437 *
3438 * @param array $args
3439 * @param array &$responsive_style_array
3440 * @param array &$classes
3441 * @return string
3442 */
3443 private function build_responsive_style( $args, &$responsive_style_array, &$classes ) {
3444 $return_value = false;
3445 $func_build_responsive_style = $args['func_build_responsive_style'] ?? '';
3446 if ( ! \is_callable( $func_build_responsive_style ) ) {
3447 return $return_value;
3448 }
3449
3450 $setting_value = $args['setting_value'] ?? [];
3451 if ( empty( $setting_value ) || ! \is_array( $setting_value ) ) {
3452 return $return_value;
3453 }
3454
3455 $breakpoints = $args['breakpoints'];
3456 $selector = $args['selector'];
3457
3458 $setting_values = [];
3459 $responsive_styles = [];
3460 foreach ( $setting_value as $breakpoint => $value_by_breakpoint ) {
3461 $value = null;
3462 if ( $this->is_valid_value( $value_by_breakpoint['value'] ?? null ) ) {
3463 $value = $value_by_breakpoint['value'];
3464 } elseif ( isset( $value_by_breakpoint['inherit'] ) && is_string( $value_by_breakpoint['inherit'] ) ) {
3465 $value = $setting_value[ $value_by_breakpoint['inherit'] ]['value'] ?? null;
3466 }
3467
3468 if ( $this->is_valid_value( $value ) ) {
3469 $style_by_breakpoint = $func_build_responsive_style( array_merge( $args, [ 'value' => $value ] ) );
3470 if ( $style_by_breakpoint ) {
3471 $responsive_styles[ $breakpoint ] = $style_by_breakpoint;
3472 }
3473 }
3474
3475 $setting_values[ $breakpoint ] = $value;
3476 }
3477
3478 $this->sort_styles( $responsive_styles );
3479
3480 $dependent_style = '';
3481 $func_build_dependent_style = $args['func_build_dependent_style'] ?? '';
3482 if ( \is_callable( $func_build_dependent_style ) ) {
3483 $dependent_style = $func_build_dependent_style(
3484 array_merge(
3485 $args,
3486 [
3487 'setting_values' => $setting_values,
3488 'responsive_styles' => $responsive_styles,
3489 ]
3490 ),
3491 $classes
3492 );
3493 }
3494
3495 $keys = [];
3496 $style = '';
3497 $last_responsive_style = '';
3498 foreach ( $responsive_styles as $breakpoint => $style_by_breakpoint ) {
3499 $responsive_style = '';
3500 foreach ( $style_by_breakpoint as $attr_key => $attr_value ) {
3501 $responsive_style .= "{$attr_key}:{$attr_value};";
3502
3503 if ( ! in_array( $attr_key, $keys, true ) ) {
3504 $keys[] = $attr_key;
3505 $classes[] = $breakpoint . '-' . str_replace( '--cbb--', 'cbb-', $attr_key );
3506 }
3507 }
3508
3509 if ( $responsive_style !== $last_responsive_style ) {
3510 $style_with_selector = "{$selector}{{$responsive_style}}";
3511 $min_query = $breakpoints[ $breakpoint ]['minQuery'] ?? '';
3512 if ( $min_query ) {
3513 $style_with_selector = \str_replace( '##CONTENT##', $style_with_selector, $min_query );
3514 }
3515
3516 $style .= $style_with_selector;
3517 $last_responsive_style = $responsive_style;
3518 }
3519 }
3520
3521 // Add style to the style_array.
3522 if ( $style ) {
3523 $style .= $dependent_style;
3524 $responsive_style_array[] = $style;
3525
3526 return $style;
3527 }
3528
3529 return $return_value;
3530 }
3531
3532 /**
3533 * Check wether the block markup has a attribute or not.
3534 *
3535 * @param string $attribute
3536 * @param string $block_content
3537 * @return boolean
3538 */
3539 private function block_has_attribute( $attribute, $block_content ) {
3540 $tags = new \WP_HTML_Tag_Processor( $block_content );
3541 if ( $tags->next_tag() ) {
3542 return null !== $tags->get_attribute( $attribute );
3543 }
3544
3545 return false;
3546 }
3547
3548 /**
3549 * Add CSS class to block wrapper
3550 *
3551 * @param string $block_content
3552 * @param string $classes
3553 * @return string
3554 */
3555 public function add_class_to_block( $block_content, $classes ) {
3556 $tags = new \WP_HTML_Tag_Processor( $block_content );
3557 if ( $tags->next_tag() ) {
3558 $tags->add_class( $classes );
3559 }
3560 return $tags->get_updated_html();
3561 }
3562
3563 /**
3564 * Add data attribute to block wrapper
3565 *
3566 * @param string $block_content
3567 * @param string $value
3568 * @return string
3569 */
3570 public function add_data_to_block( $block_content, $name, $value ) {
3571 $tags = new \WP_HTML_Tag_Processor( $block_content );
3572 if ( $tags->next_tag() ) {
3573 $tags->set_attribute( $name, $value );
3574 }
3575
3576 return $tags->get_updated_html();
3577 }
3578
3579 /**
3580 * Add inner content to block markup
3581 *
3582 * @param string $block_content
3583 * @param string $inner_content
3584 * @return string
3585 */
3586 public function add_inner_content_to_block( $block_content, $inner_content ) {
3587 if ( $inner_content ) {
3588 $block_content = \preg_replace(
3589 '/' . \preg_quote( '>', '/' ) . '/',
3590 '>' . $inner_content,
3591 $block_content,
3592 1
3593 );
3594 }
3595
3596 return $block_content;
3597 }
3598
3599 /**
3600 * Detect a value is valid
3601 *
3602 * @param mixed $value
3603 * @return boolean
3604 */
3605 private function is_valid_value( $value ) {
3606 return isset( $value ) && $value !== '';
3607 }
3608
3609 /**
3610 * Detect a responsive value is valid
3611 *
3612 * @param mixed $value
3613 * @return boolean
3614 */
3615 private function is_valid_responsive_value( $value ) {
3616 return ! empty( $value['lg']['value'] ) || ! empty( $value['md']['value'] ) || ! empty( $value['sm']['value'] );
3617 }
3618
3619 /**
3620 * Get value for spacing var
3621 *
3622 * @param string $value
3623 * @return string
3624 */
3625 private function get_spacing_value( $value ) {
3626 if ( ! $this->is_valid_value( $value ) ) {
3627 return '';
3628 }
3629
3630 if ( strpos( $value, 'var:preset|spacing|' ) !== false ) {
3631 preg_match( '/var:preset\|spacing\|(.+)/', $value, $slug );
3632
3633 if ( ! $slug ) {
3634 return $value;
3635 }
3636
3637 return "var(--wp--preset--spacing--{$slug[1]})";
3638 }
3639
3640 return $value;
3641 }
3642
3643 /**
3644 * Get CSS value for a color object.
3645 *
3646 * @param array $color_array
3647 * @return string
3648 */
3649 private function get_css_color_value( $color_array ) {
3650 $value = '';
3651
3652 if ( ! empty( $color_array ) ) {
3653 if ( is_string( $color_array ) ) {
3654 $value = $color_array;
3655 } elseif ( is_array( $color_array ) ) {
3656 if ( $color_array['gradientSlug'] ?? false ) {
3657 $value = "var(--wp--preset--gradient--{$color_array['gradientSlug']}, {$color_array['gradientValue']})";
3658 } elseif ( $color_array['gradientValue'] ?? false ) {
3659 $value = $color_array['gradientValue'];
3660 } elseif ( $color_array['slug'] ?? false ) {
3661 $value = "var(--wp--preset--color--{$color_array['slug']}, {$color_array['value']})";
3662 } elseif ( $color_array['value'] ?? false ) {
3663 $value = $color_array['value'];
3664 }
3665 }
3666 }
3667
3668 return $value;
3669 }
3670
3671 /**
3672 * Refine custom JS and CSS value
3673 *
3674 * @param string $code
3675 * @param array $tokens
3676 * @param string $type
3677 * @return string
3678 */
3679 public function refine_custom_value( $code, $tokens = [], $type = 'CSS' ) {
3680 // Remove tags.
3681 // $code = preg_replace( '/(<([\w-]+)>)/mi', '', $code );
3682
3683 // Replace breakpoint tokens.
3684 $breakpoints = $this->get_breakpoints();
3685
3686 $tokens = array_merge(
3687 $tokens,
3688 [
3689 'TABLET_UP' => ( $breakpoints['md']['breakpoint'] ?? 768 ) . 'px',
3690 'TABLET_DOWN' => ( $breakpoints['md']['breakpointMax'] ?? 767 ) . 'px',
3691 'DESKTOP_UP' => ( $breakpoints['lg']['breakpoint'] ?? 1024 ) . 'px',
3692 'DESKTOP_DOWN' => ( $breakpoints['lg']['breakpointMax'] ?? 1023 ) . 'px',
3693 ]
3694 );
3695
3696 $code = str_replace( array_keys( $tokens ), array_values( $tokens ), $code );
3697
3698 // Minify it.
3699 return $this->minify( $code, $type );
3700 }
3701
3702 /**
3703 * Simple minify JS and CSS
3704 *
3705 * @param string $code
3706 * @param string $type
3707 * @return string
3708 */
3709 public function minify( $code, $type = 'CSS' ) {
3710 if ( $this->the_plugin_instance->is_debug_mode() ) {
3711 return $code;
3712 }
3713
3714 if ( 'CSS' === $type ) {
3715 $code = preg_replace( '/\s+/', ' ', $code );
3716 } elseif ( 'JS' === $type ) {
3717 // Remove comments first - https://stackoverflow.com/a/31907095/1038868.
3718 $pattern = '/(?:(?:\/\*(?:[^*]|(?:\*+[^*\/]))*\*+\/)|(?:(?<!\:|\\\|\'|\")\/\/.*))/';
3719 $code = preg_replace( $pattern, '', $code );
3720
3721 // Remove spaces, tabs, and new lines after a line if it ends with a semicolon, a bracket, or a comma, or an open parentheses.
3722 $code = preg_replace( '/([;{,\(])\s+/', '$1', $code );
3723
3724 // Remove before ...
3725 $code = preg_replace( '/\s+([}\)])/', '$1', $code );
3726
3727 // Remove spaces before a line.
3728 $code = preg_replace( '/^\s+/m', '', $code );
3729 }
3730
3731 return trim( $code );
3732 }
3733 }
3734 endif;
3735