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

3,693 lines 121.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * The custom style component
4 *
5 * @package BoldBlocks
6 * @author Phi Phan <mrphipv@gmail.com>
7 * @copyright Copyright (c) 2022, Phi Phan
8 */
9
10 namespace BoldBlocks;
11
12 // Exit if accessed directly.
13 defined( 'ABSPATH' ) || exit;
14
15 if ( ! class_exists( CustomStyle::class ) ) :
16 /**
17 * The controller class for style.
18 */
19 class CustomStyle extends CoreComponent {
20 /**
21 * The style handle for feature styles
22 *
23 * @var array
24 */
25 private $feature_style_handle = 'cbb-dynamic-style';
26
27 /**
28 * Store the context of styles
29 *
30 * @var array
31 */
32 private $style_contexts = [
33 'loops' => [],
34 'loop' => '',
35 'post' => 0,
36 'counter' => 0,
37 'content' => false,
38 'content_counter' => 0,
39 ];
40
41 /**
42 * Store the ID of query loop with carousel or grid layout
43 *
44 * @var array
45 */
46 private $query_ids = [];
47
48 /**
49 * The cached styles
50 *
51 * @var array
52 */
53 private $styles = [];
54
55 /**
56 * The refined breakpoints
57 *
58 * @var array
59 */
60 private $breakpoints = [];
61
62 /**
63 * The list of non-cbb blocks that support dynamic style
64 *
65 * @var array
66 */
67 public $non_cbb_blocks = [ 'core/template-part' ];
68
69 /**
70 * Run main hooks
71 *
72 * @return void
73 */
74 public function run() {
75 // Register setting fields.
76 add_action( 'init', [ $this, 'register_setting_fields' ] );
77
78 // Register style handle.
79 add_action( 'init', [ $this, 'register_feature_style' ] );
80
81 // Render block style.
82 add_filter( 'render_block', [ $this, 'render_block_style' ], 10, 3 );
83
84 // Render link to post first.
85 add_filter( 'render_block', [ $this, 'build_block_link_to_post' ], 20, 3 );
86
87 // Build dynamic block overlay.
88 add_filter( 'render_block', [ $this, 'build_block_overlay' ], 20, 3 );
89
90 // Build dynamic block background.
91 add_filter( 'render_block', [ $this, 'build_block_background' ], 30, 3 );
92
93 // Build animations.
94 add_filter( 'render_block', [ $this, 'build_block_animations' ], 30, 3 );
95
96 // Render grid style for the query loop block.
97 add_filter( 'render_block_core/query', [ $this, 'render_query_loop_grid_style' ], 10, 3 );
98
99 // Render carousel settings for the query loop block.
100 add_filter( 'render_block_core/query', [ $this, 'render_query_loop_carousel_layout' ], 10, 3 );
101
102 // Enqueue block styles.
103 add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_responsive_styles' ] );
104
105 // Enqueue breakpoints.
106 add_action( 'init', [ $this, 'enqueue_reponsive_breakpoints' ] );
107
108 // Update loop id.
109 add_filter( 'render_block_context', [ $this, 'update_loop_id' ], 10, 2 );
110
111 // Reset loop id.
112 add_filter( 'render_block', [ $this, 'reset_loop_id' ], 10, 2 );
113
114 // Render preload style for carousels.
115 add_filter( 'cbb_get_block_style', [ $this, 'get_carousel_preload_style' ], 10, 2 );
116
117 // Support dynamic style for none-cbb blocks.
118 add_filter( 'cbb_support_block_style', [ $this, 'support_block_style' ], 10, 2 );
119
120 // Set a block layout for non-cbb blocks.
121 add_filter( 'cbb_get_block_layout', [ $this, 'get_block_layout' ], 10, 2 );
122
123 // Render sticky data for the core/template-part block.
124 add_filter( 'render_block_core/template-part', [ $this, 'render_template_part_sticky' ], 10, 3 );
125
126 // Build preloader for the carousel.
127 add_filter( 'render_block', [ $this, 'build_carousel_preloader' ], 30, 3 );
128
129 // 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' => array(
146 'name' => 'CBBBreakpoints',
147 'schema' => array(
148 'items' => array(
149 'type' => 'object',
150 'properties' => array(
151 'breakpoint' => array(
152 'type' => 'number',
153 ),
154 'prefix' => array(
155 'type' => 'string',
156 ),
157 ),
158 'additionalProperties' => array(
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 $play_pause_icon = apply_filters( 'cbb_play_pause_icon', '<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" class="control-play-pause"><g class="icon-pause"><rect width="4.5" height="14" x="3.75" y="3" rx="1.5"></rect><rect width="4.5" height="14" x="11.75" y="3" rx="1.5"></rect></g><path class="icon-play" d="M5 15.25V4.77a1.44 1.44 0 0 1 1.44-1.62 1.86 1.86 0 0 1 1.11.31l8.53 5c.76.44 1.17.8 1.17 1.51s-.41 1.07-1.17 1.51l-8.53 5a1.86 1.86 0 0 1-1.11.31A1.42 1.42 0 0 1 5 15.25Z"></path></svg>' );
2063 $block_background .= '<button class="cbb-video-play-pause">' . $play_pause_icon . '</button>';
2064 }
2065
2066 if ( strpos( $block['innerHTML'] ?? '', $block_class ) === false ) {
2067 $block_content = $this->add_class_to_block( $block_content, $block_class );
2068 }
2069
2070 $block_content = $this->add_inner_content_to_block( $block_content, $block_background );
2071
2072 return $block_content;
2073 }
2074
2075 /**
2076 * Render block overlay
2077 *
2078 * @param string $block_content
2079 * @param array $block
2080 * @param WP_Block $block_instance
2081 * @return string
2082 */
2083 public function build_block_overlay( $block_content, $block, $block_instance ) {
2084 // Ignore admin side.
2085 if ( is_admin() ) {
2086 return $block_content;
2087 }
2088
2089 $block_overlay = $block['attrs']['boldblocks']['overlay'] ?? false;
2090
2091 // There is no overlay value.
2092 if ( ! ( $block_overlay['overlayColor'] ?? false ) ) {
2093 return $block_content;
2094 }
2095
2096 // Ignore legacy markup.
2097 if ( strpos( $block['innerHTML'] ?? '', 'bb:block-overlay' ) !== false ) {
2098 return $block_content;
2099 }
2100
2101 $bg_color = '';
2102 $overlay_color = $block_overlay['overlayColor'] ?? [];
2103 if ( ( $overlay_color['gradientSlug'] ?? false ) || ( $overlay_color['gradientValue'] ?? false ) ) {
2104 if ( $overlay_color['gradientSlug'] ?? false ) {
2105 $bg_color = sprintf( 'var(--wp--preset--gradient--%1$s, %2$s)', $overlay_color['gradientSlug'], $overlay_color['gradientValue'] ?? '' );
2106 } else {
2107 $bg_color = $overlay_color['gradientValue'];
2108 }
2109 } elseif ( ( $overlay_color['slug'] ?? false ) || ( $overlay_color['value'] ?? false ) ) {
2110 if ( $overlay_color['slug'] ?? false ) {
2111 $bg_color = sprintf( 'var(--wp--preset--color--%1$s, %2$s)', $overlay_color['slug'], $overlay_color['value'] ?? '' );
2112 } else {
2113 $bg_color = $overlay_color['value'];
2114 }
2115 }
2116
2117 if ( empty( $bg_color ) ) {
2118 return $block_content;
2119 }
2120
2121 $opacity = absint( $block_overlay['opacity'] ?? 100 );
2122
2123 // Build the markup.
2124 $overlay_markup = sprintf( '<div aria-hidden="true" class="bb:block-overlay" style="background:%1$s;opacity:%2$s;"></div>', $bg_color, $opacity / 100 );
2125
2126 $block_class = 'bb:has-overlay';
2127 if ( strpos( $block['innerHTML'] ?? '', $block_class ) === false ) {
2128 $block_content = $this->add_class_to_block( $block_content, $block_class );
2129 }
2130
2131 $block_content = $this->add_inner_content_to_block( $block_content, $overlay_markup );
2132
2133 return $block_content;
2134 }
2135
2136 /**
2137 * Render grid style for the query loop's grid layout
2138 *
2139 * @param string $block_content
2140 * @param array $block
2141 * @param WP_Block $block_instance
2142 * @return string
2143 */
2144 public function render_query_loop_grid_style( $block_content, $block, $block_instance ) {
2145 // Ignore admin side.
2146 if ( is_admin() ) {
2147 return $block_content;
2148 }
2149
2150 // If this is a core/query block that has grid layout.
2151 $grid_data = $this->get_query_loop_grid_data( $block, $block_instance );
2152 if ( ! $grid_data ) {
2153 return $block_content;
2154 }
2155
2156 $display_layout = $grid_data['displayType'] ?? '';
2157 if ( 'responsiveGrid' === $display_layout && ( $grid_data['grid'] ?? false ) ) {
2158 // Buil selector.
2159 $selector = 'cbb-query-' . $this->get_query_loop_id( $block );
2160
2161 // Get responsive settings.
2162 $breakpoints = $this->get_breakpoints();
2163
2164 // Get custom style.
2165 $block_style = $this->get_query_grid_style(
2166 [
2167 'selector' => ".{$selector} > ul",
2168 'block' => $block,
2169 'data' => $grid_data['grid'],
2170 'breakpoints' => $breakpoints,
2171 ]
2172 );
2173
2174 if ( empty( $block_style ) ) {
2175 return $block_content;
2176 }
2177
2178 $handle = $selector;
2179 // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
2180 wp_register_style( $handle, '' );
2181 wp_add_inline_style( $handle, $block_style );
2182 wp_enqueue_style( $handle );
2183
2184 // Add selector to block wrapper element.
2185 $block_content = $this->add_class_to_block( $block_content, $selector );
2186 }
2187
2188 return $block_content;
2189 }
2190
2191 /**
2192 * Build an unique ID for a query loop
2193 *
2194 * @param array $block
2195 * @return string
2196 */
2197 private function get_query_loop_id( $block ) {
2198 $query_id = $block['attrs']['queryId'] ?? 1;
2199 $key = "id_{$query_id}";
2200 if ( ! isset( $this->query_ids[ $key ] ) ) {
2201 $this->query_ids[ $key ] = 1;
2202 } else {
2203 $this->query_ids[ $key ]++;
2204 }
2205
2206 return $query_id . '-' . $this->query_ids[ $key ];
2207 }
2208
2209 /**
2210 * Get grid layout data
2211 *
2212 * @param array $block
2213 * @param WP_Block $block_instance
2214 * @return array
2215 */
2216 private function get_query_loop_grid_data( $block, $block_instance ) {
2217 $data = [];
2218 $old_layout_type = $block['attrs']['displayLayout']['type'] ?? '';
2219 if ( 'grid' === $old_layout_type ) {
2220 // Convert to the new name.
2221 $old_layout_type = 'responsiveGrid';
2222 }
2223 $data['displayType'] = $old_layout_type;
2224 $data['grid'] = $block['attrs']['boldblocks']['grid'] ?? [];
2225
2226 if ( $block_instance->block_type->api_version >= 3 ) {
2227 $post_template = $this->get_nested_post_template( $block_instance );
2228
2229 if ( $post_template ) {
2230 if ( 'responsiveGrid' === ( $post_template->attributes['boldblocks']['layout']['type'] ?? '' ) ) {
2231 $data['displayType'] = 'responsiveGrid';
2232 $data['grid'] = $post_template->attributes['boldblocks']['grid'] ?? [];
2233 }
2234 }
2235 }
2236
2237 return $data;
2238 }
2239
2240 /**
2241 * Build grid style
2242 *
2243 * @param array $args
2244 * @return string
2245 */
2246 private function get_query_grid_style( $args ) {
2247 $selector = $args['selector'];
2248 $data = $args['data'];
2249
2250 // None-responsive style.
2251 $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;}";
2252
2253 // Columns style.
2254 $style .= $this->build_query_loop_responsive_style(
2255 array_merge(
2256 $args,
2257 [
2258 'setting_value' => $data['columns'] ?? null,
2259 'func_build_responsive_style' => function ( $args ) {
2260 $style_array = [];
2261 $value = $args['value'];
2262 if ( $value ) {
2263 $style_array = [
2264 '--cbb--grid--columns' => $value,
2265 ];
2266 }
2267
2268 return $style_array;
2269 },
2270 'func_build_dependent_style' => function ( $args ) {
2271 $responsive_styles = $args['responsive_styles'] ?? [];
2272 $selector = $args['selector'];
2273 $breakpoints = $args['breakpoints'] ?? [];
2274 $style = "{$selector}{grid-template-columns:var(--cbb--grid--columns);}";
2275
2276 foreach ( $responsive_styles as $breakpoint => $attribute_array ) {
2277 $media_query = $breakpoints[ $breakpoint ]['minQuery'] ?? '';
2278 if ( $media_query ) {
2279 $style = \str_replace( '##CONTENT##', $style, $media_query );
2280 }
2281
2282 break;
2283 }
2284
2285 return $style;
2286 },
2287 ]
2288 )
2289 );
2290
2291 // Rows style.
2292 $style .= $this->build_query_loop_responsive_style(
2293 array_merge(
2294 $args,
2295 [
2296 'setting_value' => $data['rows'] ?? null,
2297 'func_build_responsive_style' => function ( $args ) {
2298 $style_array = [];
2299 $value = $args['value'];
2300 if ( $value ) {
2301 $style_array = [
2302 '--cbb--grid--rows' => $value,
2303 ];
2304 }
2305
2306 return $style_array;
2307 },
2308 'func_build_dependent_style' => function ( $args ) {
2309 $responsive_styles = $args['responsive_styles'] ?? [];
2310 $selector = $args['selector'];
2311 $breakpoints = $args['breakpoints'] ?? [];
2312 $style = "{$selector}{grid-template-rows:var(--cbb--grid--rows);}";
2313
2314 foreach ( $responsive_styles as $breakpoint => $attribute_array ) {
2315 $media_query = $breakpoints[ $breakpoint ]['minQuery'] ?? '';
2316 if ( $media_query ) {
2317 $style = \str_replace( '##CONTENT##', $style, $media_query );
2318 }
2319
2320 break;
2321 }
2322
2323 return $style;
2324 },
2325 ]
2326 )
2327 );
2328
2329 // Gap style.
2330 $style .= $this->build_query_loop_responsive_style(
2331 array_merge(
2332 $args,
2333 [
2334 'setting_value' => $data['gap'] ?? null,
2335 'func_build_responsive_style' => function ( $args ) {
2336 $style_array = [];
2337 $value = $args['value'];
2338 if ( $value && is_array( $value ) ) {
2339 $row = $value['row'] ?? null;
2340
2341 if ( $this->is_valid_value( $row ) ) {
2342 $style_array['--cbb--grid--gap--row'] = $row;
2343 }
2344
2345 $column = $value['column'] ?? null;
2346
2347 if ( $this->is_valid_value( $column ) ) {
2348 $style_array['--cbb--grid--gap--column'] = $column;
2349 }
2350 }
2351
2352 return $style_array;
2353 },
2354 'func_build_dependent_style' => function ( $args ) {
2355 $responsive_styles = $args['responsive_styles'] ?? [];
2356 $selector = $args['selector'];
2357 $breakpoints = $args['breakpoints'] ?? [];
2358 $style = '';
2359
2360 foreach ( $responsive_styles as $breakpoint => $style_array ) {
2361 if ( strpos( $style, 'row-gap' ) !== false && strpos( $style, 'column-gap' ) !== false ) {
2362 break;
2363 }
2364
2365 $media_query = $breakpoints[ $breakpoint ]['minQuery'] ?? '';
2366
2367 if ( strpos( $style, 'row-gap' ) === false && isset( $style_array['--cbb--grid--gap--row'] ) ) {
2368 $row_style = "{$selector}{row-gap:var(--cbb--grid--gap--row);}";
2369 if ( $media_query ) {
2370 $style .= \str_replace( '##CONTENT##', $row_style, $media_query );
2371 } else {
2372 $style .= $row_style;
2373 }
2374 }
2375 if ( strpos( $style, 'column-gap' ) === false && isset( $style_array['--cbb--grid--gap--column'] ) ) {
2376 $column_style = "{$selector}{column-gap:var(--cbb--grid--gap--column);}";
2377 if ( $media_query ) {
2378 $style .= \str_replace( '##CONTENT##', $column_style, $media_query );
2379 } else {
2380 $style .= $column_style;
2381 }
2382 }
2383 }
2384
2385 return $style;
2386 },
2387 ]
2388 )
2389 );
2390
2391 $items = $data['items'] ?? [];
2392 if ( $items ) {
2393 foreach ( $items as $item_key => $item_value ) {
2394 $item_index = str_replace( 'item', '', $item_key );
2395
2396 // Column style.
2397 $style .= $this->build_query_loop_responsive_style(
2398 array_merge(
2399 $args,
2400 [
2401 'setting_value' => $item_value['columnSpan'] ?? [],
2402 'setting_name' => 'columnSpan',
2403 'selector' => "{$selector} > li:nth-of-type({$item_index})",
2404 'func_build_responsive_style' => [ $this, 'build_grid_item_responsive_style' ],
2405 'func_build_dependent_style' => [ $this, 'build_grid_item_dependent_style' ],
2406 ]
2407 )
2408 );
2409
2410 // Row style.
2411 $style .= $this->build_query_loop_responsive_style(
2412 array_merge(
2413 $args,
2414 [
2415 'setting_value' => $item_value['rowSpan'] ?? [],
2416 'setting_name' => 'rowSpan',
2417 'selector' => "{$selector} > li:nth-of-type({$item_index})",
2418 'func_build_responsive_style' => [ $this, 'build_grid_item_responsive_style' ],
2419 'func_build_dependent_style' => [ $this, 'build_grid_item_dependent_style' ],
2420 ]
2421 )
2422 );
2423 }
2424 }
2425
2426 return $style;
2427 }
2428
2429 /**
2430 * Build responsive style for grid item
2431 *
2432 * @param array $args
2433 * @return array
2434 */
2435 private function build_grid_item_responsive_style( $args ) {
2436 $setting_name = $args['setting_name'] ?? 'columnSpan';
2437 $variable_name = 'columnSpan' === $setting_name ? '--cbb--grid-item--column' : '--cbb--grid-item--row';
2438 $style_array = [];
2439 $value = $args['value'];
2440 if ( $value && is_array( $value ) ) {
2441 $span = $value['span'] ?? null;
2442 $start = $value['start'] ?? null;
2443
2444 if ( $this->is_valid_value( $start ) || $this->is_valid_value( $span ) ) {
2445 if ( ! $this->is_valid_value( $start ) ) {
2446 $start = 'auto';
2447 }
2448
2449 if ( ! $this->is_valid_value( $span ) ) {
2450 $span = 'auto';
2451 }
2452 if ( $span !== 'auto' && $span > 0 ) {
2453 $span = "span {$span}";
2454 }
2455
2456 $style_array[ $variable_name ] = "{$start} / {$span}";
2457 }
2458
2459 if ( 'columnSpan' === $setting_name ) {
2460 $order = $value['order'] ?? null;
2461 if ( is_numeric( $order ) ) {
2462 $style_array['--cbb--grid-item--order'] = $order;
2463 }
2464 }
2465 }
2466
2467 return $style_array;
2468 }
2469
2470 /**
2471 * Build dependent style for grid item
2472 *
2473 * @param array $args
2474 * @return array
2475 */
2476 private function build_grid_item_dependent_style( $args ) {
2477 $setting_name = $args['setting_name'] ?? 'columnSpan';
2478 $variable_name = 'columnSpan' === $setting_name ? '--cbb--grid-item--column' : '--cbb--grid-item--row';
2479 $css_name = 'columnSpan' === $setting_name ? 'grid-column' : 'grid-row';
2480
2481 $responsive_styles = $args['responsive_styles'] ?? [];
2482 $selector = $args['selector'];
2483 $breakpoints = $args['breakpoints'] ?? [];
2484 $style = '';
2485
2486 foreach ( $responsive_styles as $breakpoint => $style_array ) {
2487 if ( 'columnSpan' === $setting_name ) {
2488 if ( strpos( $style, $css_name ) !== false && strpos( $style, 'order' ) !== false ) {
2489 break;
2490 }
2491 } else {
2492 if ( strpos( $style, $css_name ) !== false ) {
2493 break;
2494 }
2495 }
2496
2497 $media_query = $breakpoints[ $breakpoint ]['minQuery'] ?? '';
2498
2499 if ( strpos( $style, $css_name ) === false && isset( $style_array[ $variable_name ] ) ) {
2500 $column_style = "{$selector}{{$css_name}:var($variable_name);}";
2501 if ( $media_query ) {
2502 $style .= \str_replace( '##CONTENT##', $column_style, $media_query );
2503 } else {
2504 $style .= $column_style;
2505 }
2506 }
2507 if ( 'columnSpan' === $setting_name && strpos( $style, 'order' ) === false && isset( $style_array['--cbb--grid-item--order'] ) ) {
2508 $order_style = "{$selector}{order:var(--cbb--grid-item--order);}";
2509 if ( $media_query ) {
2510 $style .= \str_replace( '##CONTENT##', $order_style, $media_query );
2511 } else {
2512 $style .= $order_style;
2513 }
2514 }
2515 }
2516
2517 return $style;
2518 }
2519
2520 /**
2521 * Render carousel layout for query loop blocks
2522 *
2523 * @param string $block_content
2524 * @param array $block
2525 * @param WP_Block $block_instance
2526 * @return string
2527 */
2528 public function render_query_loop_carousel_layout( $block_content, $block, $block_instance ) {
2529 // Ignore admin side.
2530 if ( is_admin() ) {
2531 return $block_content;
2532 }
2533
2534 $post_template = $this->get_nested_post_template( $block_instance );
2535 if ( $post_template ) {
2536 // If this has carousel layout.
2537 if ( 'carousel' === ( $post_template->attributes['boldblocks']['layout']['type'] ?? '' ) ) {
2538 // Build carousel settings.
2539 $carousel_settings = $post_template->attributes['boldblocks']['carousel'] ?? [];
2540 if ( $carousel_settings ) {
2541 if ( ! $this->block_has_attribute( 'data-carousel-settings', $block_content ) ) {
2542 $carousel_attr = $this->build_query_loop_carousel_settings( $carousel_settings );
2543
2544 if ( $carousel_attr ) {
2545 $block_content = $this->add_data_to_block( $block_content, 'data-carousel-settings', esc_attr( $carousel_attr ) );
2546 }
2547 }
2548
2549 // Buil selector.
2550 $selector = 'cbb-query-' . $this->get_query_loop_id( $block );
2551
2552 // Get responsive settings.
2553 $breakpoints = $this->get_breakpoints();
2554
2555 // Get custom style.
2556 $block_style = $this->get_carousel_preload_style(
2557 '',
2558 [
2559 'selector' => ".{$selector}",
2560 'settings' => [ 'carousel' => $carousel_settings ],
2561 'block_layout' => 'carousel',
2562 'breakpoints' => $breakpoints,
2563 ]
2564 );
2565
2566 $carousel_class = 'js-carousel-layout cbb-carousel-preload';
2567
2568 if ( $block_style ) {
2569 $handle = $selector;
2570 // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
2571 wp_register_style( $handle, '' );
2572 wp_add_inline_style( $handle, $block_style );
2573 wp_enqueue_style( $handle );
2574
2575 $carousel_class .= ' ' . $selector;
2576 }
2577
2578 if ( $carousel_settings['displayLoader'] ?? false ) {
2579 $carousel_class .= ' has-preloader';
2580
2581 if ( 'coverflow' === ( $carousel_settings['effect'] ?? '' ) ) {
2582 $carousel_class .= ' is-fading-loader';
2583 }
2584
2585 // Add preloader.
2586 $block_content = $this->add_inner_content_to_block( $block_content, $this->get_carousel_preloader_markup( $block, $block_instance ) );
2587 }
2588
2589 // Add selector to block wrapper element.
2590 $block_content = $this->add_class_to_block( $block_content, $carousel_class );
2591 }
2592 }
2593 }
2594
2595 return $block_content;
2596 }
2597
2598 /**
2599 * Add preload style for carousels
2600 *
2601 * @param string $style
2602 * @param array $args
2603 */
2604 public function get_carousel_preload_style( $style, $args ) {
2605 if ( 'carousel' === ( $args['block_layout'] ?? '' ) ) {
2606 $preload_style = '';
2607 $value = $args['settings']['carousel'] ?? false;
2608 $selector = $args['selector'];
2609 $breakpoints = $args['breakpoints'];
2610 if ( $value && is_array( $value ) ) {
2611 if ( $value['enableResponsive'] ?? false ) {
2612 $setting_value = $value['breakpoints'] ?? [];
2613 $responsive_styles = [];
2614 foreach ( $setting_value as $breakpoint => $value_by_breakpoint ) {
2615 $value = null;
2616 if ( $this->is_valid_value( $value_by_breakpoint['value'] ?? null ) ) {
2617 $value = $value_by_breakpoint['value'];
2618 } elseif ( isset( $value_by_breakpoint['inherit'] ) && is_string( $value_by_breakpoint['inherit'] ) ) {
2619 $value = $setting_value[ $value_by_breakpoint['inherit'] ]['value'] ?? null;
2620 }
2621
2622 if ( $this->is_valid_value( $value ) ) {
2623 $style_by_breakpoint = $this->build_carousel_preload_style( $value );
2624 if ( $style_by_breakpoint ) {
2625 $responsive_styles[ $breakpoint ] = $style_by_breakpoint;
2626 }
2627 }
2628 }
2629
2630 $this->sort_styles( $responsive_styles );
2631
2632 $last_responsive_style = '';
2633 foreach ( $responsive_styles as $breakpoint => $style_by_breakpoint ) {
2634 if ( ! $style_by_breakpoint || ! is_array( $style_by_breakpoint ) ) {
2635 continue;
2636 }
2637
2638 $responsive_style = '';
2639 foreach ( $style_by_breakpoint as $attr_key => $attr_value ) {
2640 $responsive_style .= "{$attr_key}:{$attr_value};";
2641 }
2642
2643 if ( $responsive_style !== $last_responsive_style ) {
2644 $style_with_selector = "{$selector}{{$responsive_style}}";
2645 $media_query = $breakpoints[ $breakpoint ]['minQuery'] ?? '';
2646 if ( $media_query ) {
2647 $preload_style .= \str_replace( '##CONTENT##', $style_with_selector, $media_query );
2648 } else {
2649 $preload_style .= $style_with_selector;
2650 }
2651 $last_responsive_style = $responsive_style;
2652 }
2653 }
2654 } else {
2655 $style_array = $this->build_carousel_preload_style( $value );
2656 $attr_style = '';
2657 foreach ( $style_array as $attr_key => $attr_value ) {
2658 $attr_style .= "{$attr_key}:{$attr_value};";
2659 }
2660
2661 if ( $attr_style ) {
2662 $preload_style .= "{$selector}{{$attr_style}}";
2663 }
2664 }
2665 }
2666
2667 if ( $preload_style ) {
2668 $style .= $preload_style;
2669 }
2670 }
2671
2672 return $style;
2673 }
2674
2675 /**
2676 * Build preload style for carousels
2677 *
2678 * @param array $value
2679 * @return void
2680 */
2681 private function build_carousel_preload_style( $value ) {
2682 $style_array = [];
2683 $slides_perview = $value['slidesPerView'] ?? 1;
2684 if ( is_array( $slides_perview ) ) {
2685 if ( $slides_perview['auto'] ?? false ) {
2686 $slides_perview = 'auto';
2687 } else {
2688 $slides_perview = $slides_perview['value'] ?? 1;
2689 }
2690 }
2691
2692 if ( $slides_perview !== 'auto' ) {
2693 $gap_count = absint( ceil( $slides_perview ) ) - 1;
2694 if ( $slides_perview <= 0 ) {
2695 $slides_perview = 1;
2696 }
2697 } else {
2698 $gap_count = 0;
2699 }
2700
2701 $space_between = $value['spaceBetween'] ?? 0;
2702 if ( $space_between < 0 ) {
2703 $space_between = 0;
2704 }
2705
2706 $total_gap = $gap_count * $space_between;
2707
2708 $style_array['--cbb-slide-width'] = $slides_perview === 'auto' ? 'auto' : "calc((100% - {$total_gap}px ) / {$slides_perview})";
2709 $style_array['--cbb-carousel-gap'] = "{$space_between}px";
2710
2711 return $style_array;
2712 }
2713
2714 /**
2715 * Build a json string of carousel settings
2716 *
2717 * @param array $carousel_settings
2718 * @return string
2719 */
2720 private function build_query_loop_carousel_settings( $carousel_settings ) {
2721 // phpcs:disable
2722 $dataset = [];
2723
2724 // Speed.
2725 if ( $carousel_settings['speed'] ?? false ) {
2726 $dataset['speed'] = $carousel_settings['speed'];
2727 }
2728
2729 // Loop and rewind.
2730 $dataset['loop'] = false;
2731 $dataset['rewind'] = false;
2732
2733 $loop_type = $carousel_settings['loopType'] ?? '';
2734 if ( 'infinite' === $loop_type ) {
2735 $dataset['loop'] = true;
2736 } elseif ( 'rewind' === $loop_type ) {
2737 $dataset['rewind'] = true;
2738 }
2739
2740 // Autoplay.
2741 $dataset['autoplay'] = false;
2742 $autoplay = $carousel_settings['autoplay'] ?? false;
2743 if ( $autoplay && is_array( $autoplay ) && ( $autoplay['enable'] ?? false ) ) {
2744 unset( $autoplay['enable'] );
2745 $dataset['autoplay'] = $autoplay;
2746 }
2747
2748 // Direction.
2749 if ( $carousel_settings['direction'] ?? false ) {
2750 $dataset['direction'] = $carousel_settings['direction'];
2751 }
2752
2753 // Effect.
2754 $effect = $carousel_settings['effect'] ?? 'slide';
2755 // Handle wrong default data.
2756 if ( is_array( $effect ) ) {
2757 $effect = 'slide';
2758 }
2759
2760 if ( $effect ) {
2761 $dataset['effect'] = $effect;
2762 $effectSettings = $carousel_settings['effectSettings'] ?? [];
2763 $settings = $effectSettings[ $effect . 'Effect' ] ?? false;
2764
2765 // Fade.
2766 if ( 'fade' === $effect ) {
2767 $dataset['fadeEffect'] = [ 'crossFade' => true ];
2768 }
2769
2770 // Coverflow.
2771 if ( 'coverflow' === $effect ) {
2772 if ( is_array( $settings ) ) {
2773 $dataset['coverflowEffect'] = $settings;
2774 } else {
2775 $dataset['coverflowEffect'] = [ 'slideShadows' => false ];
2776 }
2777 }
2778
2779 // Creative.
2780 if ( 'creative' === $effect ) {
2781 if ( is_array( $settings ) ) {
2782 $dataset['creativeEffect'] = $settings;
2783 } else {
2784 $dataset['creativeEffect'] = [
2785 'prev' => [
2786 'translate' => [ '-20%', 0, -1 ],
2787 ],
2788 'next' => [
2789 'translate' => [ '100%', 0, 0 ],
2790 ],
2791 ];
2792 }
2793 }
2794 }
2795
2796 // slidesPerView.
2797 $slidesPerViewDependencies = [
2798 'fade',
2799 'flip',
2800 'cube',
2801 'cards',
2802 'creative',
2803 ];
2804
2805 $slidesPerViewRaw = $carousel_settings['slidesPerView'] ?? 1;
2806 if ( in_array( $effect, $slidesPerViewDependencies, true ) ) {
2807 $dataset['slidesPerView'] = 1;
2808 } else {
2809 $slidesPerView = $slidesPerViewRaw;
2810 if ( is_array( $slidesPerViewRaw ) ) {
2811 $slidesPerView = $slidesPerViewRaw['auto'] ?? false
2812 ? 'auto'
2813 : $slidesPerViewRaw['value'] ?? 1;
2814 }
2815
2816 if ( $slidesPerViewRaw ) {
2817 $dataset['slidesPerView'] = $slidesPerViewRaw;
2818
2819 if ( $slidesPerView > 1 && ( $carousel_settings['slidesPerGroup'] ?? false ) ) {
2820 $dataset['slidesPerGroup'] = $carousel_settings['slidesPerGroup'];
2821 }
2822 }
2823
2824 // spaceBetween.
2825 $spaceBetween = $carousel_settings['spaceBetween'] ?? 0;
2826 if ( $spaceBetween ) {
2827 $dataset['spaceBetween'] = $spaceBetween;
2828 }
2829
2830 // Breakpoints.
2831 $breakpointValues = [
2832 'sm' => 576,
2833 'md' => 768,
2834 'lg' => 1024,
2835 ];
2836 $enableResponsive = $carousel_settings['enableResponsive'] ?? false;
2837 $breakpoints = $carousel_settings['breakpoints'] ?? [];
2838 if ( $enableResponsive && is_array( $breakpoints ) ) {
2839 $breakpointAtts = [];
2840 foreach ( array_keys( $breakpoints ) as $breakpoint ) :
2841 $value = $breakpoints[ $breakpoint ]['value'] ?? '';
2842 $inherit = $breakpoints[ $breakpoint ]['inherit'] ?? '';
2843
2844 $valueByBreakpoint = false;
2845 if ( $value && is_array( $value ) ) {
2846 $valueByBreakpoint = $value;
2847 }
2848
2849 if ( ! $valueByBreakpoint && is_string( $inherit ) ) {
2850 $inheritValue = $breakpoints[ $inherit ] ?? '';
2851 if ( $inheritValue && is_array( $inheritValue ) ) {
2852 $valueByBreakpoint = $inheritValue;
2853 }
2854 }
2855
2856 if ( is_array( $valueByBreakpoint ) ) {
2857 $breakpointAtts[ $breakpointValues[ $breakpoint ] ] = $valueByBreakpoint;
2858 }
2859 endforeach;
2860
2861 $dataset['breakpoints'] = $breakpointAtts;
2862 } else {
2863 if ( $carousel_settings['oneSlidePerViewInMobile'] ?? false ) {
2864 $dataset['breakpoints'] = [
2865 $breakpointValues['sm'] => [
2866 'slidesPerView' => 1,
2867 'spaceBetween' => 0,
2868 ],
2869
2870 $breakpointValues['md'] => [
2871 'slidesPerView' => $slidesPerViewRaw,
2872 'spaceBetween' => $spaceBetween,
2873 ],
2874 ];
2875 }
2876 }
2877 }
2878
2879 // centeredSlides.
2880 $dataset['centeredSlides'] = $carousel_settings['centeredSlides'] ?? false;
2881 if ($dataset['centeredSlides'] && $effect === 'slide' && ($carousel_settings['centeredSlidesSettings']['enable'] ?? '')) {
2882 $dataset['centeredSlidesSettings'] = $carousel_settings['centeredSlidesSettings'];
2883 }
2884
2885 // Pagination.
2886 $pagination = $carousel_settings['pagination'] ?? [];
2887 $dataset['pagination'] = $pagination['enable'] ?? false;
2888 if ( $dataset['pagination'] ) {
2889 $dataset['paginationSettings'] = $pagination;
2890 }
2891
2892 // Navigation.
2893 $navigation = $carousel_settings['navigation'] ?? [];
2894 $dataset['navigation'] = $navigation['enable'] ?? false;
2895 if ( $dataset['navigation'] ) {
2896 $dataset['navigationSettings'] = $navigation;
2897 }
2898
2899 // Scrollbar.
2900 $scrollbar = $carousel_settings['scrollbar'] ?? [];
2901 $dataset['scrollbar'] = $scrollbar['enable'] ?? false;
2902 if ( $dataset['scrollbar'] ) {
2903 $dataset['scrollbarSettings'] = $scrollbar;
2904 }
2905
2906 // Equal height?.
2907 $equalHeight = $carousel_settings['equalHeight'] ?? false;
2908 if ( $equalHeight ) {
2909 // Only add the property when it is true to prevent from invalid content.
2910 $dataset['equalHeight'] = $equalHeight;
2911 }
2912
2913 $thumbs = $carousel_settings['thumbs'] ?? false;
2914 if ( $thumbs && ( $thumbs['enable'] ?? false ) ) {
2915 $dataset['thumbsSettings'] = $thumbs;
2916 }
2917
2918 return wp_json_encode( $dataset );
2919 // phpcs:enable
2920 }
2921
2922 /**
2923 * Get nested post template block for core/query
2924 *
2925 * @param WP_Block $block_instance
2926 * @return boolean|WP_Block
2927 */
2928 private function get_nested_post_template( $block_instance ) {
2929 return $this->find_nested_block( $block_instance, 'core/post-template' );
2930 }
2931
2932 /**
2933 * Find a specific nested block from a parent block
2934 *
2935 * @param WP_Block $block
2936 * @param string $block_name
2937 * @return mixed
2938 */
2939 public function find_nested_block( $block, $block_name ) {
2940 if ( $block->name === $block_name ) {
2941 return $block;
2942 }
2943
2944 foreach ( $block->inner_blocks as $inner_block ) {
2945 $result = $this->find_nested_block( $inner_block, $block_name );
2946
2947 if ( $result ) {
2948 return $result;
2949 }
2950 }
2951
2952 return false;
2953 }
2954
2955 /**
2956 * Build responsive style
2957 *
2958 * @param array $args
2959 * @param array &$style_array
2960 * @param array &$responsive_style_array
2961 * @return string
2962 */
2963 private function build_query_loop_responsive_style( $args ) {
2964 $func_build_responsive_style = $args['func_build_responsive_style'] ?? '';
2965 if ( ! \is_callable( $func_build_responsive_style ) ) {
2966 return '';
2967 }
2968
2969 $setting_value = $args['setting_value'] ?? [];
2970 if ( empty( $setting_value ) || ! \is_array( $setting_value ) ) {
2971 return '';
2972 }
2973
2974 $breakpoints = $args['breakpoints'];
2975 $selector = $args['selector'];
2976
2977 $responsive_styles = [];
2978 foreach ( $setting_value as $breakpoint => $value_by_breakpoint ) {
2979 $value = null;
2980 if ( $this->is_valid_value( $value_by_breakpoint['value'] ?? null ) ) {
2981 $value = $value_by_breakpoint['value'];
2982 } elseif ( isset( $value_by_breakpoint['inherit'] ) && is_string( $value_by_breakpoint['inherit'] ) ) {
2983 $value = $setting_value[ $value_by_breakpoint['inherit'] ]['value'] ?? null;
2984 }
2985
2986 if ( $this->is_valid_value( $value ) ) {
2987 $style_by_breakpoint = $func_build_responsive_style( array_merge( $args, [ 'value' => $value ] ) );
2988 if ( $style_by_breakpoint ) {
2989 $responsive_styles[ $breakpoint ] = $style_by_breakpoint;
2990 }
2991 }
2992 }
2993
2994 $this->sort_styles( $responsive_styles );
2995
2996 $dependent_style = '';
2997 $func_build_dependent_style = $args['func_build_dependent_style'] ?? '';
2998 if ( \is_callable( $func_build_dependent_style ) ) {
2999 $dependent_style = $func_build_dependent_style( array_merge( $args, [ 'responsive_styles' => $responsive_styles ] ) );
3000 }
3001
3002 $style = '';
3003 $last_responsive_style = '';
3004 foreach ( $responsive_styles as $breakpoint => $style_by_breakpoint ) {
3005 if ( ! $style_by_breakpoint || ! is_array( $style_by_breakpoint ) ) {
3006 continue;
3007 }
3008
3009 $responsive_style = '';
3010 foreach ( $style_by_breakpoint as $attr_key => $attr_value ) {
3011 $responsive_style .= "{$attr_key}:{$attr_value};";
3012 }
3013
3014 if ( $responsive_style !== $last_responsive_style ) {
3015 $style_with_selector = "{$selector}{{$responsive_style}}";
3016 $media_query = $breakpoints[ $breakpoint ]['minQuery'] ?? '';
3017 if ( $media_query ) {
3018 $style .= \str_replace( '##CONTENT##', $style_with_selector, $media_query );
3019 } else {
3020 $style .= $style_with_selector;
3021 }
3022 $last_responsive_style = $responsive_style;
3023 }
3024 }
3025
3026 return $style . $dependent_style;
3027 }
3028
3029 /**
3030 * Build the breakpoints
3031 *
3032 * @return array
3033 */
3034 private function get_breakpoints() {
3035 if ( ! $this->breakpoints ) {
3036 $breakpoints = get_option( 'cbb_breakpoints' );
3037 if ( empty( $breakpoints ) ) {
3038 $breakpoints = [
3039 [
3040 'breakpoint' => 576,
3041 'prefix' => 'sm',
3042 ],
3043 [
3044 'breakpoint' => 768,
3045 'prefix' => 'md',
3046 ],
3047 [
3048 'breakpoint' => 1024,
3049 'prefix' => 'lg',
3050 ],
3051 ];
3052 }
3053
3054 $breakpoints = array_filter(
3055 array_map(
3056 function( $item ) {
3057 if ( empty( $item['breakpoint'] ) || empty( $item['prefix'] ) ) {
3058 return false;
3059 }
3060
3061 $breakpoint = absint( $item['breakpoint'] );
3062
3063 if ( $breakpoint < 10 ) {
3064 return false;
3065 }
3066
3067 $max_breakpoint = $breakpoint - 1;
3068 $item['breakpointMax'] = $max_breakpoint;
3069
3070 if ( $item['prefix'] !== 'sm' ) {
3071 $item['minQuery'] = "@media (min-width: {$breakpoint}px){##CONTENT##}";
3072 $item['maxQuery'] = "@media (max-width: {$max_breakpoint}px){##MAX_CONTENT##}";
3073 } else {
3074 $item['minQuery'] = '';
3075 $item['maxQuery'] = "@media (max-width: {$max_breakpoint}px){##MAX_CONTENT##}";
3076 }
3077
3078 return $item;
3079 },
3080 $breakpoints
3081 )
3082 );
3083
3084 $this->breakpoints = array_column( $breakpoints, null, 'prefix' );
3085 }
3086
3087 return $this->breakpoints;
3088 }
3089
3090 /**
3091 * Generate block selector.
3092 *
3093 * @param string $block_name
3094 * @param WP_Block $block_instance
3095 * @return string
3096 */
3097 private function generate_selector( $block_name, $block_instance ) {
3098 $selector_prefix = 'cbb';
3099
3100 if ( '' !== $this->style_contexts['loop'] ) {
3101 $post_id = $block_instance->context['postId'] ?? 0;
3102 if ( ! $this->style_contexts['post'] || $this->style_contexts['post'] !== $post_id ) {
3103 $this->style_contexts['post'] = $post_id;
3104 $this->style_contexts['counter'] = 0;
3105 $this->style_contexts['content_counter'] = 0;
3106 }
3107
3108 if ( $this->style_contexts['content'] ) {
3109 // The content context selector.
3110 $content_counter = ++ $this->style_contexts['content_counter'];
3111 return "{$selector_prefix}-p{$this->style_contexts['post']}-{$content_counter}";
3112 }
3113
3114 $counter = ++ $this->style_contexts['counter'];
3115 // The selector inside the loop.
3116 return "{$selector_prefix}-{$this->style_contexts['loop']}-{$counter}";
3117 } else {
3118 // The global selector.
3119 return wp_unique_id( "{$selector_prefix}-g-" );
3120 }
3121 }
3122
3123 /**
3124 * Update loop id when having a post template block
3125 *
3126 * @param array $context
3127 * @param array $block
3128 * @return array
3129 */
3130 public function update_loop_id( $context, $block ) {
3131 if ( $this->is_loop_temlate_block( $block['blockName'] ?? '' ) ) {
3132 if ( isset( $context['queryId'] ) ) {
3133 $query_id = $context['queryId'];
3134 if ( in_array( $query_id, $this->style_contexts['loops'], true ) ) {
3135 $count = count(
3136 array_filter(
3137 $this->style_contexts['loops'],
3138 function( $value ) use ( $query_id ) {
3139 return $value === $query_id;
3140 }
3141 )
3142 );
3143
3144 $this->style_contexts['loop'] = "{$query_id}i{$count}";
3145 } else {
3146 $this->style_contexts['loop'] = $query_id;
3147 }
3148
3149 $this->style_contexts['loops'][] = $query_id;
3150 }
3151 }
3152
3153 // Post content inside a loop.
3154 if ( $this->style_contexts['loop'] && 'core/post-content' === ( $block['blockName'] ?? '' ) ) {
3155 $this->style_contexts['content'] = true;
3156 }
3157
3158 return $context;
3159 }
3160
3161 /**
3162 * Reset loop id when the post template is rendered
3163 *
3164 * @param string $content
3165 * @param array $block
3166 * @return string
3167 */
3168 public function reset_loop_id( $content, $block ) {
3169 if ( $this->is_loop_temlate_block( $block['blockName'] ?? '' ) ) {
3170 $this->style_contexts['loop'] = '';
3171 $this->style_contexts['post'] = 0;
3172 }
3173
3174 // Post content inside a loop.
3175 if ( 'core/post-content' === ( $block['blockName'] ?? '' ) ) {
3176 $this->style_contexts['content'] = false;
3177 }
3178
3179 return $content;
3180 }
3181
3182 /**
3183 * Render sticky attributes for core template part blocks
3184 *
3185 * @param string $block_content
3186 * @param array $block
3187 * @param WP_Block $block_instance
3188 * @return string
3189 */
3190 public function render_template_part_sticky( $block_content, $block, $block_instance ) {
3191 // Ignore admin side.
3192 if ( is_admin() ) {
3193 return $block_content;
3194 }
3195
3196 // If this block has sticky data.
3197 $sticky_data = $block['attrs']['boldblocks']['sticky'] ?? false;
3198 if ( $sticky_data && ( $sticky_data['enable'] ?? false ) ) {
3199 $sticky_classes = [ 'is-sticky-block' ];
3200 $sticky_classes[] = 'is-stick-to-' . ( $sticky_data['stickTo'] ?? 'top' );
3201
3202 if ( $sticky_data['isStickOnScrollingUp'] ?? false ) {
3203 $sticky_classes[] = 'is-sticky-on-scrollup';
3204 } else {
3205 if ( $sticky_data['isFixed'] ?? false ) {
3206 $sticky_classes[] = 'is-fixed';
3207 if ( $sticky_data['isInFlow'] ?? false ) {
3208 $sticky_classes[] = 'is-in-flow';
3209 }
3210 if ( $sticky_data['isDetectingScroll'] ?? false ) {
3211 $sticky_classes[] = 'is-detecting-scroll';
3212 }
3213 } elseif ( $sticky_data['isDetectingStuck'] ?? false ) {
3214 $sticky_classes[] = 'is-detecting-stuck';
3215 }
3216 }
3217
3218 if ( in_array( $block['attrs']['area'] ?? '', [ 'header', 'footer' ], true ) ) {
3219 $sticky_classes[] = 'is-sticky-' . $block['attrs']['area'];
3220 }
3221
3222 $block_content = $this->add_class_to_block( $block_content, implode( ' ', $sticky_classes ) );
3223 }
3224
3225 return $block_content;
3226 }
3227
3228 /**
3229 * Render preloader for carousel layout blocks
3230 *
3231 * @param string $block_content
3232 * @param array $block
3233 * @param WP_Block $block_instance
3234 * @return string
3235 */
3236 public function build_carousel_preloader( $block_content, $block, $block_instance ) {
3237 // Ignore admin side.
3238 if ( is_admin() ) {
3239 return $block_content;
3240 }
3241
3242 // There is no carousel layout.
3243 if ( 'carousel' !== ( $block_instance->block_type->supports['layoutType'] ?? '' ) ) {
3244 return $block_content;
3245 }
3246
3247 // Preload class.
3248 $preloader_class = 'cbb-carousel-preload';
3249
3250 // Has preloader?
3251 $has_preloader = $block['attrs']['boldblocks']['carousel']['displayLoader'] ?? false;
3252 if ( $has_preloader ) {
3253 $preloader_class .= ' has-preloader';
3254 if ( 'coverflow' === ( $block['attrs']['boldblocks']['carousel']['effect'] ?? '' ) ) {
3255 $preloader_class .= ' is-fading-loader';
3256 }
3257
3258 // Add the loader to the markup.
3259 $block_content = $this->add_inner_content_to_block( $block_content, $this->get_carousel_preloader_markup( $block, $block_instance ) );
3260 }
3261
3262 // Mark it as having preloader.
3263 $block_content = $this->add_class_to_block( $block_content, $preloader_class );
3264
3265 return $block_content;
3266 }
3267
3268 /**
3269 * Get the preloader markup for carousels
3270 *
3271 * @param array $block
3272 * @param WP_Block $block_instance
3273 * @return string
3274 */
3275 private function get_carousel_preloader_markup( $block, $block_instance ) {
3276 return apply_filters( 'cbb_carousel_preloader', '<div class="cbb-loader"></div>', $block, $block_instance );
3277 }
3278
3279 /**
3280 * Migrate cbb class for old CBB blocks.
3281 *
3282 * @param string $block_content
3283 * @param array $block
3284 * @param WP_Block $block_instance
3285 * @return string
3286 */
3287 public function migrate_cbb_class( $block_content, $block, $block_instance ) {
3288 // Ignore admin side or empty content.
3289 if ( is_admin() || ! $block_content ) {
3290 return $block_content;
3291 }
3292
3293 // There is no need to migrate.
3294 $migrate_cbb_class = $block_instance->block_type->supports['migrateClass'] ?? '';
3295 if ( ! $migrate_cbb_class ) {
3296 return $block_content;
3297 }
3298
3299 // Replace the class.
3300 $block_reader = new \WP_HTML_Tag_Processor( $block_content );
3301 if ( $block_reader->next_tag( [ 'class_name' => $migrate_cbb_class ] ) ) {
3302 $new_class = str_ends_with( $migrate_cbb_class, 'custom' ) ? 'cbb-block' : 'cbb-block-parent';
3303 $block_reader->set_attribute( 'class', str_replace( $migrate_cbb_class, $new_class, $block_reader->get_attribute( 'class' ) ) );
3304
3305 // Render the updated HTML.
3306 return $block_reader->get_updated_html();
3307 }
3308
3309 return $block_content;
3310 }
3311
3312 /**
3313 * Support block style for non-cbb blocks
3314 *
3315 * @param string $is_supported
3316 * @param array $block
3317 * @return boolean
3318 */
3319 public function support_block_style( $is_supported, $block ) {
3320 if ( in_array( $block['blockName'] ?? '', $this->non_cbb_blocks, true ) ) {
3321 $is_supported = true;
3322 }
3323
3324 return $is_supported;
3325 }
3326
3327 /**
3328 * Set a valid layout for none-cbb blocks
3329 *
3330 * @param string $block_layout
3331 * @param array $block
3332 * @return string
3333 */
3334 public function get_block_layout( $block_layout, $block ) {
3335 if ( in_array( $block['blockName'] ?? '', $this->non_cbb_blocks, true ) ) {
3336 $block_layout = 'standalone';
3337 }
3338
3339 return $block_layout;
3340 }
3341
3342 /**
3343 * Check whether the block is a loop template block.
3344 *
3345 * @param string $block_name
3346 * @return array
3347 */
3348 private function is_loop_temlate_block( $block_name ) {
3349 $loop_template_blocks = apply_filters( 'cbb_get_loop_temlate_blocks', [ 'core/post-template' ] );
3350
3351 return in_array( $block_name, $loop_template_blocks, true );
3352 }
3353
3354 /**
3355 * Check whether current block has custom style or not.
3356 *
3357 * @param array $block
3358 * @param WP_Block $block_instance
3359 * @return boolean
3360 */
3361 private function has_style( $block, $block_instance ) {
3362 // There is no boldblocks value.
3363 if ( ! isset( $block['attrs']['boldblocks'] ) ) {
3364 return false;
3365 }
3366
3367 return apply_filters( 'cbb_support_block_style', isset( $block_instance->block_type->supports['cbb'] ), $block, $block_instance );
3368 }
3369
3370 /**
3371 * Order style array by breakpoints
3372 *
3373 * @param array $styles
3374 * @return void
3375 */
3376 private function sort_styles( &$styles ) {
3377 uksort(
3378 $styles,
3379 function( $a, $b ) {
3380 if ( 'lg' === $a || 'sm' === $b ) {
3381 return 1;
3382 }
3383
3384 if ( 'sm' === $a || 'lg' === $b ) {
3385 return -1;
3386 }
3387
3388 return 0;
3389 }
3390 );
3391 }
3392
3393 /**
3394 * Build responsive style
3395 *
3396 * @param array $args
3397 * @param array &$responsive_style_array
3398 * @param array &$classes
3399 * @return string
3400 */
3401 private function build_responsive_style( $args, &$responsive_style_array, &$classes ) {
3402 $return_value = false;
3403 $func_build_responsive_style = $args['func_build_responsive_style'] ?? '';
3404 if ( ! \is_callable( $func_build_responsive_style ) ) {
3405 return $return_value;
3406 }
3407
3408 $setting_value = $args['setting_value'] ?? [];
3409 if ( empty( $setting_value ) || ! \is_array( $setting_value ) ) {
3410 return $return_value;
3411 }
3412
3413 $breakpoints = $args['breakpoints'];
3414 $selector = $args['selector'];
3415
3416 $setting_values = [];
3417 $responsive_styles = [];
3418 foreach ( $setting_value as $breakpoint => $value_by_breakpoint ) {
3419 $value = null;
3420 if ( $this->is_valid_value( $value_by_breakpoint['value'] ?? null ) ) {
3421 $value = $value_by_breakpoint['value'];
3422 } elseif ( isset( $value_by_breakpoint['inherit'] ) && is_string( $value_by_breakpoint['inherit'] ) ) {
3423 $value = $setting_value[ $value_by_breakpoint['inherit'] ]['value'] ?? null;
3424 }
3425
3426 if ( $this->is_valid_value( $value ) ) {
3427 $style_by_breakpoint = $func_build_responsive_style( array_merge( $args, [ 'value' => $value ] ) );
3428 if ( $style_by_breakpoint ) {
3429 $responsive_styles[ $breakpoint ] = $style_by_breakpoint;
3430 }
3431 }
3432
3433 $setting_values[ $breakpoint ] = $value;
3434 }
3435
3436 $this->sort_styles( $responsive_styles );
3437
3438 $dependent_style = '';
3439 $func_build_dependent_style = $args['func_build_dependent_style'] ?? '';
3440 if ( \is_callable( $func_build_dependent_style ) ) {
3441 $dependent_style = $func_build_dependent_style(
3442 array_merge(
3443 $args,
3444 [
3445 'setting_values' => $setting_values,
3446 'responsive_styles' => $responsive_styles,
3447 ]
3448 ),
3449 $classes
3450 );
3451 }
3452
3453 $keys = [];
3454 $style = '';
3455 $last_responsive_style = '';
3456 foreach ( $responsive_styles as $breakpoint => $style_by_breakpoint ) {
3457 $responsive_style = '';
3458 foreach ( $style_by_breakpoint as $attr_key => $attr_value ) {
3459 $responsive_style .= "{$attr_key}:{$attr_value};";
3460
3461 if ( ! in_array( $attr_key, $keys, true ) ) {
3462 $keys[] = $attr_key;
3463 $classes[] = $breakpoint . '-' . str_replace( '--cbb--', 'cbb-', $attr_key );
3464 }
3465 }
3466
3467 if ( $responsive_style !== $last_responsive_style ) {
3468 $style_with_selector = "{$selector}{{$responsive_style}}";
3469 $min_query = $breakpoints[ $breakpoint ]['minQuery'] ?? '';
3470 if ( $min_query ) {
3471 $style_with_selector = \str_replace( '##CONTENT##', $style_with_selector, $min_query );
3472 }
3473
3474 $style .= $style_with_selector;
3475 $last_responsive_style = $responsive_style;
3476 }
3477 }
3478
3479 // Add style to the style_array.
3480 if ( $style ) {
3481 $style .= $dependent_style;
3482 $responsive_style_array[] = $style;
3483
3484 return $style;
3485 }
3486
3487 return $return_value;
3488 }
3489
3490 /**
3491 * Check wether the block markup has a attribute or not.
3492 *
3493 * @param string $attribute
3494 * @param string $block_content
3495 * @return boolean
3496 */
3497 private function block_has_attribute( $attribute, $block_content ) {
3498 $tags = new \WP_HTML_Tag_Processor( $block_content );
3499 if ( $tags->next_tag() ) {
3500 return null !== $tags->get_attribute( $attribute );
3501 }
3502
3503 return false;
3504 }
3505
3506 /**
3507 * Add CSS class to block wrapper
3508 *
3509 * @param string $block_content
3510 * @param string $classes
3511 * @return string
3512 */
3513 public function add_class_to_block( $block_content, $classes ) {
3514 $tags = new \WP_HTML_Tag_Processor( $block_content );
3515 if ( $tags->next_tag() ) {
3516 $tags->add_class( $classes );
3517 }
3518 return $tags->get_updated_html();
3519 }
3520
3521 /**
3522 * Add data attribute to block wrapper
3523 *
3524 * @param string $block_content
3525 * @param string $value
3526 * @return string
3527 */
3528 public function add_data_to_block( $block_content, $name, $value ) {
3529 $tags = new \WP_HTML_Tag_Processor( $block_content );
3530 if ( $tags->next_tag() ) {
3531 $tags->set_attribute( $name, $value );
3532 }
3533
3534 return $tags->get_updated_html();
3535 }
3536
3537 /**
3538 * Add inner content to block markup
3539 *
3540 * @param string $block_content
3541 * @param string $inner_content
3542 * @return string
3543 */
3544 public function add_inner_content_to_block( $block_content, $inner_content ) {
3545 if ( $inner_content ) {
3546 $block_content = \preg_replace(
3547 '/' . \preg_quote( '>', '/' ) . '/',
3548 '>' . $inner_content,
3549 $block_content,
3550 1
3551 );
3552 }
3553
3554 return $block_content;
3555 }
3556
3557 /**
3558 * Detect a value is valid
3559 *
3560 * @param mixed $value
3561 * @return boolean
3562 */
3563 private function is_valid_value( $value ) {
3564 return isset( $value ) && $value !== '';
3565 }
3566
3567 /**
3568 * Detect a responsive value is valid
3569 *
3570 * @param mixed $value
3571 * @return boolean
3572 */
3573 private function is_valid_responsive_value( $value ) {
3574 return ! empty( $value['lg']['value'] ) || ! empty( $value['md']['value'] ) || ! empty( $value['sm']['value'] );
3575 }
3576
3577 /**
3578 * Get value for spacing var
3579 *
3580 * @param string $value
3581 * @return string
3582 */
3583 private function get_spacing_value( $value ) {
3584 if ( ! $this->is_valid_value( $value ) ) {
3585 return '';
3586 }
3587
3588 if ( strpos( $value, 'var:preset|spacing|' ) !== false ) {
3589 preg_match( '/var:preset\|spacing\|(.+)/', $value, $slug );
3590
3591 if ( ! $slug ) {
3592 return $value;
3593 }
3594
3595 return "var(--wp--preset--spacing--{$slug[1]})";
3596 }
3597
3598 return $value;
3599 }
3600
3601 /**
3602 * Get CSS value for a color object.
3603 *
3604 * @param array $color_array
3605 * @return string
3606 */
3607 private function get_css_color_value( $color_array ) {
3608 $value = '';
3609
3610 if ( ! empty( $color_array ) ) {
3611 if ( is_string( $color_array ) ) {
3612 $value = $color_array;
3613 } elseif ( is_array( $color_array ) ) {
3614 if ( $color_array['gradientSlug'] ?? false ) {
3615 $value = "var(--wp--preset--gradient--{$color_array['gradientSlug']}, {$color_array['gradientValue']})";
3616 } elseif ( $color_array['gradientValue'] ?? false ) {
3617 $value = $color_array['gradientValue'];
3618 } elseif ( $color_array['slug'] ?? false ) {
3619 $value = "var(--wp--preset--color--{$color_array['slug']}, {$color_array['value']})";
3620 } elseif ( $color_array['value'] ?? false ) {
3621 $value = $color_array['value'];
3622 }
3623 }
3624 }
3625
3626 return $value;
3627 }
3628
3629 /**
3630 * Refine custom JS and CSS value
3631 *
3632 * @param string $code
3633 * @param array $tokens
3634 * @param string $type
3635 * @return string
3636 */
3637 public function refine_custom_value( $code, $tokens = [], $type = 'CSS' ) {
3638 // Remove tags.
3639 // $code = preg_replace( '/(<([\w-]+)>)/mi', '', $code );
3640
3641 // Replace breakpoint tokens.
3642 $breakpoints = $this->get_breakpoints();
3643
3644 $tokens = array_merge(
3645 $tokens,
3646 [
3647 'TABLET_UP' => ( $breakpoints['md']['breakpoint'] ?? 768 ) . 'px',
3648 'TABLET_DOWN' => ( $breakpoints['md']['breakpointMax'] ?? 767 ) . 'px',
3649 'DESKTOP_UP' => ( $breakpoints['lg']['breakpoint'] ?? 1024 ) . 'px',
3650 'DESKTOP_DOWN' => ( $breakpoints['lg']['breakpointMax'] ?? 1023 ) . 'px',
3651 ]
3652 );
3653
3654 $code = str_replace( array_keys( $tokens ), array_values( $tokens ), $code );
3655
3656 // Minify it.
3657 return $this->minify( $code, $type );
3658 }
3659
3660 /**
3661 * Simple minify JS and CSS
3662 *
3663 * @param string $code
3664 * @param string $type
3665 * @return string
3666 */
3667 public function minify( $code, $type = 'CSS' ) {
3668 if ( $this->the_plugin_instance->is_debug_mode() ) {
3669 return $code;
3670 }
3671
3672 if ( 'CSS' === $type ) {
3673 $code = preg_replace( '/\s+/', ' ', $code );
3674 } elseif ( 'JS' === $type ) {
3675 // Remove comments first - https://stackoverflow.com/a/31907095/1038868.
3676 $pattern = '/(?:(?:\/\*(?:[^*]|(?:\*+[^*\/]))*\*+\/)|(?:(?<!\:|\\\|\'|\")\/\/.*))/';
3677 $code = preg_replace( $pattern, '', $code );
3678
3679 // Remove spaces, tabs, and new lines after a line if it ends with a semicolon, a bracket, or a comma, or an open parentheses.
3680 $code = preg_replace( '/([;{,\(])\s+/', '$1', $code );
3681
3682 // Remove before ...
3683 $code = preg_replace( '/\s+([}\)])/', '$1', $code );
3684
3685 // Remove spaces before a line.
3686 $code = preg_replace( '/^\s+/m', '', $code );
3687 }
3688
3689 return trim( $code );
3690 }
3691 }
3692 endif;
3693