PluginProbe
Content Blocks Builder – Create blocks, repeater blocks with carousel, grid, popup layouts / trunk
Content Blocks Builder – Create blocks, repeater blocks with carousel, grid, popup layouts vtrunk
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
← All changes | includes/custom-style.php +841 -150 2.7.8 → trunk View file →
@@ -92,8 +92,14 @@
92 92
93 93 // Build animations.
94 94 add_filter( 'render_block', [ $this, 'build_block_animations' ], 30, 3 );
95 95
96 + // Render accordions.
97 + add_filter( 'render_block_data', [ $this, 'render_accordion_block_data' ] );
98 +
99 + // Render accordion item blocks.
100 + add_filter( 'render_block', [ $this, 'render_accordion_item_block' ], 10, 3 );
101 +
96 102 // Render grid style for the query loop block.
97 103 add_filter( 'render_block_core/query', [ $this, 'render_query_loop_grid_style' ], 10, 3 );
98 104
99 105 // Render carousel settings for the query loop block.
@@ -122,10 +128,22 @@
122 128
123 129 // Render sticky data for the core/template-part block.
124 130 add_filter( 'render_block_core/template-part', [ $this, 'render_template_part_sticky' ], 10, 3 );
125 131
126 - // Build preloader for the carousel.
127 - add_filter( 'render_block', [ $this, 'build_carousel_preloader' ], 30, 3 );
132 + // Render settings for carousels.
133 + add_filter( 'render_block', [ $this, 'render_carousel_settings' ], 30, 3 );
134 +
135 + // Handle migration css class and custom css.
136 + add_filter( 'render_block', [ $this, 'manage_cbb_class' ], 30, 3 );
137 +
138 + // Render steps block data.
139 + add_filter( 'render_block_data', [ $this, 'render_steps_block_data' ], 10, 3 );
140 +
141 + // Render steps blocks.
142 + add_filter( 'render_block', [ $this, 'render_steps_attributes' ], 10, 2 );
143 +
144 + // Sanitize post data before saving to database.
145 + add_filter( 'wp_insert_post_data', [ $this, 'sanitize_post_data' ] );
128 146 }
129 147
130 148 /**
131 149 * Register custom setting fields
@@ -132,32 +150,33 @@
132 150 *
133 151 * @return void
134 152 */
135 153 public function register_setting_fields() {
154 + // phpcs:ignore PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic
136 155 register_setting(
137 - 'boldblocks',
156 + 'cbb',
138 157 'cbb_breakpoints',
139 158 [
140 159 'type' => 'array',
141 - 'show_in_rest' => array(
160 + 'show_in_rest' => [
142 161 'name' => 'CBBBreakpoints',
143 - 'schema' => array(
144 - 'items' => array(
162 + 'schema' => [
163 + 'items' => [
145 164 'type' => 'object',
146 - 'properties' => array(
147 - 'breakpoint' => array(
165 + 'properties' => [
166 + 'breakpoint' => [
148 167 'type' => 'number',
149 - ),
150 - 'prefix' => array(
168 + ],
169 + 'prefix' => [
151 170 'type' => 'string',
152 - ),
153 - ),
154 - 'additionalProperties' => array(
171 + ],
172 + ],
173 + 'additionalProperties' => [
155 174 'type' => 'string',
156 - ),
157 - ),
158 - ),
159 - ),
175 + ],
176 + ],
177 + ],
178 + ],
160 179 'default' => [
161 180 [
162 181 'breakpoint' => 576,
163 182 'prefix' => 'sm',
@@ -311,12 +330,12 @@
311 330 $visibility_style .= "@media(min-width:{$md_breakpoint}px) and (max-width:{$lg_breakpoint_max}px){.cbb-hidden-md{display:none !important;}}";
312 331 $visibility_style .= "@media(min-width:{$lg_breakpoint}px){.cbb-hidden-lg{display:none !important;}}";
313 332 $style .= $visibility_style;
314 333
315 - // Grid: Columns & gap.
316 - $grid_style = '.sm-cbb-grid-columns > *,.sm-cbb-grid-columns > * + *{margin:0}.sm-cbb-grid-columns{grid-template-columns:var(--cbb--grid-columns);}.sm-cbb-grid-rows{grid-template-rows:var(--cbb--grid-rows);}.sm-cbb-grid-gap-column{column-gap:var(--cbb--grid-gap-column);}.sm-cbb-grid-gap-row{row-gap:var(--cbb--grid-gap-row);}';
317 - $grid_style .= "{$md_start}.md-cbb-grid-columns > * {margin:0}.md-cbb-grid-columns{grid-template-columns:var(--cbb--grid-columns);}.md-cbb-grid-rows{grid-template-rows:var(--cbb--grid-rows);}.md-cbb-grid-gap-column{column-gap:var(--cbb--grid-gap-column);}.md-cbb-grid-gap-row{row-gap:var(--cbb--grid-gap-row);}{$end}";
318 - $grid_style .= "{$lg_start}.lg-cbb-grid-columns > * {margin:0}.lg-cbb-grid-columns{grid-template-columns:var(--cbb--grid-columns);}.lg-cbb-grid-rows{grid-template-rows:var(--cbb--grid-rows);}.lg-cbb-grid-gap-column{column-gap:var(--cbb--grid-gap-column);}.lg-cbb-grid-gap-row{row-gap:var(--cbb--grid-gap-row);}{$end}";
334 + // Grid: Columns, rows, auto-rows, & gap.
335 + $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-auto-rows{grid-auto-rows:var(--cbb--grid-auto-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);}';
336 + $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-auto-rows{grid-auto-rows:var(--cbb--grid-auto-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}";
337 + $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-auto-rows{grid-auto-rows:var(--cbb--grid-auto-rows);}.lg-cbb-grid-gap-column{column-gap:var(--cbb--grid-gap-column);}.lg-cbb-grid-gap-row{row-gap:var(--cbb--grid-gap-row);}{$end}";
319 338 $style .= $grid_style;
320 339
321 340 // Grid item: columnSpan & rowSpan.
322 341 $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);}';
@@ -324,11 +343,11 @@
324 343 $grid_item_style .= "{$lg_start}.lg-cbb-grid-item-column{grid-column:var(--cbb--grid-item-column);}.lg-cbb-grid-item-row{grid-row:var(--cbb--grid-item-row);}.lg-cbb-grid-item-order{order:var(--cbb--grid-item-order);}{$end}";
325 344 $style .= $grid_item_style;
326 345
327 346 // Accordion gap.
328 - $accordion_gap_style = '.sm-cbb-accordion-gap{display:flex;flex-direction:column;gap:var(--cbb--accordion-gap);}.sm-cbb-a-has-border > .is-accordion-item{border-top:var(--cbb--item-border-top);}.sm-cbb-a-no-border > .is-accordion-item{border-top:0;}';
329 - $accordion_gap_style .= "{$md_start}.md-cbb-accordion-gap{display:flex;flex-direction:column;gap:var(--cbb--accordion-gap);}.md-cbb-a-has-border > .is-accordion-item{border-top:var(--cbb--item-border-top);}.md-cbb-a-no-border > .is-accordion-item{border-top:0;}{$end}";
330 - $accordion_gap_style .= "{$lg_start}.lg-cbb-accordion-gap{display:flex;flex-direction:column;gap:var(--cbb--accordion-gap);}.lg-cbb-a-has-border > .is-accordion-item{border-top:var(--cbb--item-border-top);}.lg-cbb-a-no-border > .is-accordion-item{border-top:0;}{$end}";
347 + $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--ab-top);}.sm-cbb-a-no-border > .is-accordion-item{border-top:0;}';
348 + $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--ab-top);}.md-cbb-a-no-border > .is-accordion-item{border-top:0;}{$end}";
349 + $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--ab-top);}.lg-cbb-a-no-border > .is-accordion-item{border-top:0;}{$end}";
331 350 $style .= $accordion_gap_style;
332 351
333 352 // Accordion padding.
334 353 // We don't need padding style, because it has a default value.
@@ -397,8 +416,13 @@
397 416 $this->styles[ $selector ] = $block_styles;
398 417 }
399 418
400 419 if ( empty( $block_styles['style'] ?? '' ) ) {
420 + if ( isset( $block_styles['classes'] ) && count( $block_styles['classes'] ) > 1 ) {
421 + // There is no style, but we have class-features like visibility.
422 + $block_content = $this->add_class_to_block( $block_content, implode( ' ', $block_styles['classes'] ) );
423 + }
424 +
401 425 return $block_content;
402 426 }
403 427
404 428 if ( ! wp_is_block_theme() ) {
@@ -483,8 +507,13 @@
483 507 'func_build_responsive_style' => [ $this, 'build_rows_style' ],
484 508 'group' => 'grid',
485 509 'layout_type' => 'grid',
486 510 ],
511 + 'autoRows' => [
512 + 'func_build_responsive_style' => [ $this, 'build_auto_rows_style' ],
513 + 'group' => 'grid',
514 + 'layout_type' => 'grid',
515 + ],
487 516 'gap' => [
488 517 'func_build_responsive_style' => [ $this, 'build_gap_style' ],
489 518 'group' => 'grid',
490 519 'layout_type' => 'grid',
@@ -511,8 +540,20 @@
511 540 'group' => 'accordion',
512 541 'setting_name' => 'padding',
513 542 'layout_type' => 'accordion',
514 543 ],
544 + 'accordionBorder' => [
545 + 'func_build_style' => [ $this, 'build_accordion_border_style' ],
546 + 'group' => 'accordion',
547 + 'setting_name' => 'border',
548 + 'layout_type' => 'accordion',
549 + ],
550 + 'accordionRadius' => [
551 + 'func_build_style' => [ $this, 'build_accordion_radius_style' ],
552 + 'group' => 'accordion',
553 + 'setting_name' => 'radius',
554 + 'layout_type' => 'accordion',
555 + ],
515 556 'stickyOffset' => [
516 557 'func_build_responsive_style' => [ $this, 'build_sticky_offset_style' ],
517 558 'group' => 'sticky',
518 559 'setting_name' => 'offset',
@@ -528,9 +569,9 @@
528 569 if ( ! in_array( $feature, [ 'border', 'hidden' ], true ) && 'accordionItem' === $block_layout ) {
529 570 continue;
530 571 }
531 572
532 - if ( ! in_array( $feature, [ 'border', 'hidden', 'accordionGap', 'accordionPadding' ], true ) && 'accordion' === $block_layout ) {
573 + if ( ! in_array( $feature, [ 'border', 'hidden', 'accordionGap', 'accordionPadding', 'accordionBorder', 'accordionRadius' ], true ) && 'accordion' === $block_layout ) {
533 574 continue;
534 575 }
535 576
536 577 if ( 'stickyOffset' === $feature ) {
@@ -570,16 +611,22 @@
570 611 $feature_args
571 612 );
572 613
573 614 if ( \is_callable( $args['func_build_style'] ?? '' ) ) {
574 - $feature_style = $this->build_style( array_merge( $args, [ 'value' => $setting_value ] ), $style_array, $classes );
615 + $this->build_style( array_merge( $args, [ 'value' => $setting_value ] ), $style_array, $classes );
575 616 }
576 617
577 - if ( \is_callable( $args['func_build_responsive_style'] ?? '' ) ) {
578 - $feature_responsive_style = $this->build_responsive_style( $args, $responsive_style_array, $classes );
618 + if ( \is_callable( $args['func_build_responsive_style'] ?? '' ) && $this->is_valid_responsive_value( $args['setting_value'] ) ) {
619 + $this->build_responsive_style( $args, $responsive_style_array, $classes );
579 620 }
580 621 }
581 622
623 + if ( $style_array ) {
624 + foreach ( $style_array as $item_style ) {
625 + $style .= $item_style;
626 + }
627 + }
628 +
582 629 if ( $responsive_style_array ) {
583 630 foreach ( $responsive_style_array as $responsive_style ) {
584 631 $style .= $responsive_style;
585 632 }
@@ -636,26 +683,9 @@
636 683 return $return_value;
637 684 }
638 685
639 686 // Build style.
640 - $feature_styles = $func_build_style( $args, $style_array, $classes );
641 -
642 - if ( ! $feature_styles ) {
643 - return $return_value;
644 - }
645 -
646 - $keys = [];
647 - $style = '';
648 - foreach ( $feature_styles as $attr_key => $attr_value ) {
649 - $style .= "{$attr_key}:{$attr_value};";
650 -
651 - if ( ! in_array( $attr_key, $keys, true ) ) {
652 - $keys[] = $attr_key;
653 - $classes[] = str_replace( '--cbb--', 'cbb-', $attr_key );
654 - }
655 - }
656 -
657 - return $style ? $style : $return_value;
687 + return $func_build_style( $args, $style_array, $classes );
658 688 }
659 689
660 690 /**
661 691 * Build style for padding
@@ -1079,8 +1109,9 @@
1079 1109
1080 1110 /**
1081 1111 * Build transform item: translate, rotate, scale, skew
1082 1112 *
1113 + * @param array $transform_item
1083 1114 * @return string
1084 1115 */
1085 1116 private function build_transform_item( $transform_item ) {
1086 1117 $style = '';
@@ -1157,10 +1188,9 @@
1157 1188 * @param array &$classes
1158 1189 * @return array
1159 1190 */
1160 1191 private function build_hidden_style( $args, &$style_array, &$classes ) {
1161 - $style_array = [];
1162 - $value = $args['value'] ?? null;
1192 + $value = $args['value'] ?? null;
1163 1193 if ( ! empty( $value ) && is_array( $value ) ) {
1164 1194 foreach ( $value as $breakpoint ) {
1165 1195 $class = "cbb-hidden-{$breakpoint}";
1166 1196 if ( ! in_array( $class, $classes, true ) ) {
@@ -1167,10 +1197,8 @@
1167 1197 $classes[] = $class;
1168 1198 }
1169 1199 }
1170 1200 }
1171 -
1172 - return $style_array;
1173 1201 }
1174 1202
1175 1203 /**
1176 1204 * Build style for grid columns
@@ -1204,8 +1232,24 @@
1204 1232 return $style_array;
1205 1233 }
1206 1234
1207 1235 /**
1236 + * Build style for grid auto rows
1237 + *
1238 + * @param array $args
1239 + * @return string
1240 + */
1241 + private function build_auto_rows_style( $args ) {
1242 + $style_array = [];
1243 + $value = $args['value'] ?? null;
1244 + if ( $this->is_valid_value( $value ) ) {
1245 + $style_array['--cbb--grid-auto-rows'] = $value;
1246 + }
1247 +
1248 + return $style_array;
1249 + }
1250 +
1251 + /**
1208 1252 * Build style for grid gap
1209 1253 *
1210 1254 * @param array $args
1211 1255 * @return string
@@ -1297,9 +1341,9 @@
1297 1341 private function build_accordion_gap_style( $args ) {
1298 1342 $style_array = [];
1299 1343 $value = $args['value'];
1300 1344 if ( $this->is_valid_value( $value ) ) {
1301 - $style_array['--cbb--accordion-gap'] = $value;
1345 + $style_array['--cbb--accordion-gap'] = $this->get_spacing_value( $value );
1302 1346 }
1303 1347
1304 1348 return $style_array;
1305 1349 }
@@ -1315,9 +1359,9 @@
1315 1359 $style = '';
1316 1360 $setting_values = $args['setting_values'];
1317 1361 if ( $this->is_valid_value( $setting_values ) && is_array( $setting_values ) ) {
1318 1362 foreach ( $setting_values as $breakpoint => $value ) {
1319 - if ( absint( $value ) > 0 ) {
1363 + if ( strpos( $value, 'var:preset|spacing|' ) !== false || absint( $value ) > 0 ) {
1320 1364 $classes[] = "{$breakpoint}-cbb-a-has-border";
1321 1365 } else {
1322 1366 $classes[] = "{$breakpoint}-cbb-a-no-border";
1323 1367 }
@@ -1340,13 +1384,15 @@
1340 1384 $x = $value['x'] ?? null;
1341 1385 if ( ! $this->is_valid_value( $x ) ) {
1342 1386 $x = '1.25rem';
1343 1387 }
1388 + $x = $this->get_spacing_value( $x );
1344 1389
1345 1390 $y = $value['y'] ?? null;
1346 1391 if ( ! $this->is_valid_value( $y ) ) {
1347 1392 $y = '1rem';
1348 1393 }
1394 + $y = $this->get_spacing_value( $y );
1349 1395
1350 1396 $style_array['--cbb--accordion-padding'] = "{$y} {$x}";
1351 1397 }
1352 1398
@@ -1353,8 +1399,77 @@
1353 1399 return $style_array;
1354 1400 }
1355 1401
1356 1402 /**
1403 + * Build style for accordion border
1404 + *
1405 + * @param array $args
1406 + * @param array $style_array
1407 + * @return string
1408 + */
1409 + private function build_accordion_border_style( $args, &$style_array ) {
1410 + $value = $args['value'] ?? null;
1411 + if ( $value && is_array( $value ) ) {
1412 + if ( ( $value['top'] ?? false ) || ( $value['right'] ?? false ) || ( $value['bottom'] ?? false ) || ( $value['left'] ?? false ) ) {
1413 + $border_styles = $this->build_border_style( $args );
1414 + } else {
1415 + $border_styles = $this->build_border_style(
1416 + [
1417 + 'value' => [
1418 + 'top' => $value,
1419 + 'right' => $value,
1420 + 'bottom' => $value,
1421 + 'left' => $value,
1422 + ],
1423 + ]
1424 + );
1425 + }
1426 +
1427 + if ( $border_styles ) {
1428 + $border_style = implode(
1429 + ';',
1430 + array_map(
1431 + function ( $attr_key ) use ( $border_styles ) {
1432 + return "{$attr_key}:{$border_styles[ $attr_key ]}";
1433 + },
1434 + array_keys( $border_styles )
1435 + )
1436 + );
1437 +
1438 + $style_array[] = "{$args['selector']}{{$border_style};}";
1439 + }
1440 + }
1441 +
1442 + return $style_array;
1443 + }
1444 +
1445 + /**
1446 + * Build style for accordion radius
1447 + *
1448 + * @param array $args
1449 + * @param array $style_array
1450 + * @return string
1451 + */
1452 + private function build_accordion_radius_style( $args, &$style_array ) {
1453 + $value = $args['value'] ?? null;
1454 + if ( $value ) {
1455 + if ( is_array( $value ) ) {
1456 + $top_left = $value['topLeft'] ?? 0;
1457 + $top_right = $value['topRight'] ?? 0;
1458 + $bottom_right = $value['bottomRight'] ?? 0;
1459 + $bottom_left = $value['bottomLeft'] ?? 0;
1460 +
1461 + $css_value = "{$top_left} {$top_right} {$bottom_right} {$bottom_left}";
1462 + } elseif ( is_string( $value ) ) {
1463 + $css_value = $value;
1464 + }
1465 + $style_array[] = "{$args['selector']}{--cbb--border-radius:{$css_value};}";
1466 + }
1467 +
1468 + return $style_array;
1469 + }
1470 +
1471 + /**
1357 1472 * Build style for sticky offset
1358 1473 *
1359 1474 * @param array $args
1360 1475 * @return array
@@ -1374,9 +1489,9 @@
1374 1489
1375 1490 /**
1376 1491 * Check whether current block has custom style or not.
1377 1492 *
1378 - * @param [type] $block
1493 + * @param array $block
1379 1494 * @return boolean
1380 1495 */
1381 1496 private function get_custom_css_style( $block ) {
1382 1497 // There is no boldblocks value.
@@ -1420,9 +1535,9 @@
1420 1535 $background = $block['attrs']['boldblocks']['background'];
1421 1536 $media_type = $background['mediaType'] ?? 'image';
1422 1537
1423 1538 if ( 'image' === $media_type && ( $background['image']['useFeaturedImage'] ?? false ) && ( $background['image']['linkToPost'] ?? false ) ) {
1424 - $block_content = $this->add_inner_content_to_block( $block_content, '<a class="bb:link-to-post" href="' . esc_url( get_permalink( $post_id ) ) . '" rel="permalink"></a>' );
1539 + $block_content = $this->add_inner_content_to_block( $block_content, '<a class="bb:link-to-post" href="' . esc_url( get_permalink( $post_id ) ) . '" aria-label="' . esc_attr( get_the_title( $post_id ) ) . '" rel="permalink"></a>' );
1425 1540 }
1426 1541
1427 1542 return $block_content;
1428 1543 }
@@ -1470,10 +1585,16 @@
1470 1585 return $this->build_thumbnail_background( $background, $block_content, $block, $block_instance );
1471 1586 } elseif ( $media_url ) {
1472 1587 return $this->build_custom_image_background( $background, $block_content, $block, $block_instance );
1473 1588 }
1474 - } elseif ( 'video' === $media_type && $media_url ) {
1475 - return $this->build_video_background( $background, $block_content, $block, $block_instance );
1589 + } elseif ( 'video' === $media_type ) {
1590 + if ( $media_url ) {
1591 + return $this->build_video_background( $background, $block_content, $block, $block_instance );
1592 + }
1593 + } elseif ( 'embed' === $media_type ) {
1594 + if ( $media_url ) {
1595 + return $this->build_embed_background( $background, $block_content, $block, $block_instance );
1596 + }
1476 1597 }
1477 1598
1478 1599 return $block_content;
1479 1600 }
@@ -1565,19 +1686,17 @@
1565 1686 } else {
1566 1687 $image_url = $media['url'] ?? '';
1567 1688 }
1568 1689
1569 - $alt_text = $media['customAlt'] ?? wp_get_attachment_caption( $image_id );
1570 - $object_fit = $settings['objectFit'] ?? 'cover';
1690 + $custom_alt_text = $media['customAlt'] ?? '';
1691 + $object_fit = $settings['objectFit'] ?? 'cover';
1571 1692 if ( empty( $object_fit ) ) {
1572 1693 $object_fit = 'cover';
1573 1694 }
1574 1695 $img_style = 'display:block;width:100%;height:100%;object-fit:' . $object_fit . ';object-position:' . $background_position . ';';
1575 1696 $background_styles = [];
1576 - $animation_type = false;
1577 1697 if ( $settings['isImgElement'] ?? false ) {
1578 1698 $attrs = [
1579 - 'alt' => esc_attr( $alt_text ),
1580 1699 'style' => esc_attr( $img_style ),
1581 1700 'class' => 'bb:block-background--img',
1582 1701 ];
1583 1702
@@ -1590,8 +1709,12 @@
1590 1709 }
1591 1710 }
1592 1711
1593 1712 if ( $is_internal_image ) {
1713 + $alt = trim( wp_strip_all_tags( get_post_meta( $image_id, '_wp_attachment_image_alt', true ) ) );
1714 + if ( ! $alt ) {
1715 + $attrs['alt'] = $custom_alt_text;
1716 + }
1594 1717 $image_element = wp_get_attachment_image(
1595 1718 $image_id,
1596 1719 $image_size,
1597 1720 false,
@@ -1597,9 +1720,9 @@
1597 1720 false,
1598 1721 $attrs
1599 1722 );
1600 1723 } else {
1601 - $image_element = $this->generate_image_element( $image_url, $attrs );
1724 + $image_element = $this->generate_image_element( $image_url, array_merge( $attrs, [ 'alt' => $custom_alt_text ] ) );
1602 1725 }
1603 1726
1604 1727 if ( $mobile_background ) {
1605 1728 $background_classes[] = 'cbb-mobile-image';
@@ -1606,8 +1729,11 @@
1606 1729 $attrs['class'] .= ' is-mobile-image';
1607 1730
1608 1731 $mobile_image = '';
1609 1732 if ( $mobile_background['id'] ?? false ) {
1733 + $alt = trim( wp_strip_all_tags( get_post_meta( $mobile_background['id'], '_wp_attachment_image_alt', true ) ) );
1734 + $attrs['alt'] = $alt ? $alt : $custom_alt_text;
1735 +
1610 1736 $mobile_image = wp_get_attachment_image(
1611 1737 $mobile_background['id'],
1612 1738 $image_size,
1613 1739 false,
@@ -1615,9 +1741,9 @@
1615 1741 );
1616 1742 }
1617 1743
1618 1744 if ( ! $mobile_image ) {
1619 - $mobile_image = $this->generate_image_element( $mobile_background['url'], $attrs );
1745 + $mobile_image = $this->generate_image_element( $mobile_background['url'], array_merge( $attrs, [ 'alt' => $custom_alt_text ] ) );
1620 1746 }
1621 1747
1622 1748 $image_element .= $mobile_image;
1623 1749 }
@@ -1640,9 +1766,9 @@
1640 1766 $background_classes[] = 'cbb-mobile-background';
1641 1767 }
1642 1768 }
1643 1769
1644 - $animation_type = $settings['animation']['type'] ?? false;
1770 + $animation_type = $settings['animation']['type'] ?? '';
1645 1771 $animation_settings = $settings['animation']['settings'] ?? [];
1646 1772 if ( $animation_type && 'none' !== $animation_type ) {
1647 1773 $background_classes[] = $animation_type;
1648 1774
@@ -1712,8 +1838,34 @@
1712 1838 if ( ! empty( $animation_settings['zoom']['withOpacity'] ) ) {
1713 1839 $background_styles['--cbb--zoom-initial-opacity'] = $animation_settings['zoom']['initialOpacity'] ?? 0;
1714 1840 $background_styles['--cbb--zoom-opacity'] = $animation_settings['zoom']['opacity'] ?? 1;
1715 1841 }
1842 + } elseif ( 'bg-zoomIn' === $animation_type ) {
1843 + if ( isset( $animation_settings['zoomIn']['initialScale'] ) ) {
1844 + $background_styles['--cbb--zoom-initial-scale'] = $animation_settings['zoomIn']['initialScale'];
1845 + }
1846 + if ( isset( $animation_settings['zoomIn']['duration'] ) ) {
1847 + $background_styles['--cbb--zoom-duration'] = $animation_settings['zoomIn']['duration'] . 's';
1848 + }
1849 + if ( isset( $animation_settings['zoomIn']['timingFunction'] ) ) {
1850 + $background_styles['--cbb--zoom-timing-function'] = $animation_settings['zoomIn']['timingFunction'];
1851 + }
1852 +
1853 + if ( ! empty( $animation_settings['zoomIn']['withOpacity'] ) ) {
1854 + $background_styles['--cbb--zoom-initial-opacity'] = $animation_settings['zoomIn']['initialOpacity'] ?? 0;
1855 + $background_styles['--cbb--zoom-opacity'] = $animation_settings['zoomIn']['opacity'] ?? 1;
1856 + }
1857 +
1858 + $dataset['data-reveal-animation'] = esc_attr(
1859 + wp_json_encode(
1860 + [
1861 + 'name' => 'bgZoomIn',
1862 + 'animation' => 'bgZoomIn var(--cbb--zoom-duration) var(--cbb--zoom-timing-function)',
1863 + 'multipleTimes' => $animation_settings['zoomIn']['multipleTimes'] ?? false,
1864 + 'forwards' => true,
1865 + ]
1866 + )
1867 + );
1716 1868 }
1717 1869 }
1718 1870
1719 1871 $background_style = $background_styles ? array_reduce(
@@ -1835,9 +1987,9 @@
1835 1987 if ( ! $name || ! $duration ) {
1836 1988 return $accumulator;
1837 1989 }
1838 1990
1839 - $delay = $animation['delay'] ?? 0;
1991 + $delay = empty( $animation['delay'] ) ? 0 : $animation['delay'];
1840 1992 $repeat = $animation['repeat'] ?? 1;
1841 1993 $infinite = $animation['infinite'] ?? false;
1842 1994 $timing_function = $animation['timingFunction'] ?? '';
1843 1995 $animation_name = isset( $animation_names[ $name ]['name'] ) ? $animation_names[ $name ]['name'] : $name;
@@ -1870,8 +2022,9 @@
1870 2022 if ( $animation_object['name'] && $animation_object['animation'] ) {
1871 2023 $animation_object['multipleTimes'] = $block['attrs']['boldblocks']['animateMultipleTimes'] ?? false;
1872 2024
1873 2025 $block_content = $this->add_data_to_block( $block_content, 'data-reveal-animation', esc_attr( wp_json_encode( $animation_object ) ) );
2026 + $block_content = $this->add_class_to_block( $block_content, 'animate__waiting' );
1874 2027 }
1875 2028
1876 2029 return $block_content;
1877 2030 }
@@ -2018,10 +2171,10 @@
2018 2171 $block_background = sprintf( '<div class="bb:block-background bb:block-background--video"><video src="%1$s" autoplay="autoplay" muted loop playsinline preload="auto" style="%2$s"/></div>', $media['url'], $video_style );
2019 2172
2020 2173 if ( $media['isPausable'] ?? false ) {
2021 2174 $block_class .= ' cbb-has-video-controls';
2022 - $play_pause_icon = apply_filters( 'cbb_play_pause_icon', '<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" class="control-play-pause"><g class="icon-pause"><rect width="4.5" height="14" x="3.75" y="3" rx="1.5"></rect><rect width="4.5" height="14" x="11.75" y="3" rx="1.5"></rect></g><path class="icon-play" d="M5 15.25V4.77a1.44 1.44 0 0 1 1.44-1.62 1.86 1.86 0 0 1 1.11.31l8.53 5c.76.44 1.17.8 1.17 1.51s-.41 1.07-1.17 1.51l-8.53 5a1.86 1.86 0 0 1-1.11.31A1.42 1.42 0 0 1 5 15.25Z"></path></svg>' );
2023 - $block_background .= '<button class="cbb-video-play-pause">' . $play_pause_icon . '</button>';
2175 + $position_class = ( $media['playPausePosition'] ?? '' ) ? ' is-' . esc_attr( $media['playPausePosition'] ) : '';
2176 + $block_background .= '<button class="cbb-play-pause cbb-video-play-pause is-playing' . $position_class . '"></button>';
2024 2177 }
2025 2178
2026 2179 if ( strpos( $block['innerHTML'] ?? '', $block_class ) === false ) {
2027 2180 $block_content = $this->add_class_to_block( $block_content, $block_class );
@@ -2032,8 +2185,123 @@
2032 2185 return $block_content;
2033 2186 }
2034 2187
2035 2188 /**
2189 + * Build the embed background
2190 + *
2191 + * @param array $background
2192 + * @param string $block_content
2193 + * @param array $block
2194 + * @param WP_Block $block_instance
2195 + * @return string
2196 + */
2197 + private function build_embed_background( $background, $block_content, $block, $block_instance ) {
2198 + $media = $background['embed'] ?? [];
2199 +
2200 + // Invalid data.
2201 + if ( ! is_string( $media['url'] ) ) {
2202 + return $block_content;
2203 + }
2204 +
2205 + $block_background = '';
2206 +
2207 + // Use WordPress's native oEmbed processing (includes caching).
2208 + $oembed_html = wp_oembed_get( $media['url'] );
2209 +
2210 + if ( $oembed_html ) {
2211 + // Extract iframe src from the oEmbed HTML.
2212 + preg_match( '/src=["\']([^"\']+)["\']/', $oembed_html, $src_matches );
2213 + if ( ! empty( $src_matches[1] ) ) {
2214 + $iframe_src = $src_matches[1];
2215 +
2216 + // Detect provider from iframe src URL.
2217 + $lower_src = strtolower( $iframe_src );
2218 + $provider = null;
2219 +
2220 + if ( str_contains( $lower_src, 'youtube.com' ) || str_contains( $lower_src, 'youtu.be' ) ) {
2221 + $provider = 'youtube';
2222 + } elseif ( str_contains( $lower_src, 'vimeo.com' ) ) {
2223 + $provider = 'vimeo';
2224 + } elseif ( str_contains( $lower_src, 'videopress.com' ) ) {
2225 + $provider = 'videopress';
2226 + } elseif ( str_contains( $lower_src, 'wordpress.tv' ) ) {
2227 + $provider = 'wordpress-tv';
2228 + }
2229 +
2230 + // Modify iframe src to add background video parameters based on provider.
2231 + $parsed_url = wp_parse_url( $iframe_src );
2232 + if ( $parsed_url && isset( $parsed_url['host'] ) ) {
2233 + // Parse existing query parameters.
2234 + $query_params = array();
2235 + if ( isset( $parsed_url['query'] ) ) {
2236 + parse_str( $parsed_url['query'], $query_params );
2237 + }
2238 +
2239 + // Add background video parameters based on provider.
2240 + if ( 'youtube' === $provider ) {
2241 + $query_params['autoplay'] = '1';
2242 + $query_params['mute'] = '1';
2243 + $query_params['loop'] = '1';
2244 + $query_params['controls'] = '0';
2245 + $query_params['playsinline'] = '1';
2246 + $query_params['rel'] = '1';
2247 +
2248 + // For loop to work, we need the playlist parameter.
2249 + $path = $parsed_url['path'] ?? '';
2250 + $path_segments = explode( '/', $path );
2251 + $video_id = end( $path_segments );
2252 + if ( $video_id ) {
2253 + $query_params['playlist'] = $video_id;
2254 + }
2255 + } elseif ( 'vimeo' === $provider ) {
2256 + $query_params['autoplay'] = '1';
2257 + $query_params['muted'] = '1';
2258 + $query_params['loop'] = '1';
2259 + $query_params['background'] = '1';
2260 + $query_params['controls'] = '0';
2261 + $query_params['transparent'] = '0';
2262 + } else {
2263 + $query_params['autoplay'] = '1';
2264 + $query_params['loop'] = '1';
2265 + $query_params['muted'] = '1';
2266 + }
2267 +
2268 + // Rebuild the URL with new parameters.
2269 + $iframe_src = $parsed_url['scheme'] . '://' . $parsed_url['host'];
2270 + if ( isset( $parsed_url['path'] ) ) {
2271 + $iframe_src .= $parsed_url['path'];
2272 + }
2273 + if ( ! empty( $query_params ) ) {
2274 + $iframe_src .= '?' . http_build_query( $query_params );
2275 + }
2276 + }
2277 +
2278 + // Build the iframe HTML that will replace the figure.
2279 + $iframe_html = sprintf(
2280 + '<iframe src="%s" title="Background video" frameborder="0" allow="autoplay; fullscreen"></iframe>',
2281 + esc_url( $iframe_src )
2282 + );
2283 +
2284 + $block_background = sprintf( '<div class="bb:block-background bb:block-background--embed">%s</div>', $iframe_html );
2285 + }
2286 + }
2287 +
2288 + if ( ! $block_background ) {
2289 + return $block_content;
2290 + }
2291 +
2292 + $block_class = 'bb:has-background bb:has-background--embed';
2293 + if ( strpos( $block['innerHTML'] ?? '', $block_class ) === false ) {
2294 + $block_content = $this->add_class_to_block( $block_content, $block_class );
2295 + }
2296 +
2297 + $block_content = $this->add_inner_content_to_block( $block_content, $block_background );
2298 +
2299 + return $block_content;
2300 + }
2301 +
2302 +
2303 + /**
2036 2304 * Render block overlay
2037 2305 *
2038 2306 * @param string $block_content
2039 2307 * @param array $block
@@ -2093,8 +2361,104 @@
2093 2361 return $block_content;
2094 2362 }
2095 2363
2096 2364 /**
2365 + * Render accordions block data
2366 + *
2367 + * @param array $parsed_block
2368 + * @return array
2369 + */
2370 + public function render_accordion_block_data( $parsed_block ) {
2371 + $block_type = \WP_Block_Type_Registry::get_instance()->get_registered( $parsed_block['blockName'] );
2372 +
2373 + if ( ! $block_type ) {
2374 + return $parsed_block;
2375 + }
2376 +
2377 + $layout_type = $block_type->supports['layoutType'] ?? '';
2378 + if ( ! in_array( $layout_type, [ 'accordion', 'accordionItem' ], true ) ) {
2379 + return $parsed_block;
2380 + }
2381 +
2382 + static $accordion_counter = 0;
2383 + static $accordion_id = '';
2384 + static $item_index = [];
2385 +
2386 + if ( 'accordion' === $layout_type ) {
2387 + ++$accordion_counter;
2388 + $processor = new \WP_HTML_Tag_Processor( $parsed_block['innerHTML'] ?? '' );
2389 +
2390 + $accordion_id = '';
2391 + if ( $processor->next_tag() ) {
2392 + $accordion_id = $processor->get_attribute( 'id' );
2393 + }
2394 +
2395 + if ( ! $accordion_id ) {
2396 + $accordion_id = 'accordion-' . $accordion_counter;
2397 + }
2398 + } elseif ( 'accordionItem' === $layout_type ) {
2399 + if ( ! isset( $item_index[ $accordion_id ] ) ) {
2400 + $item_index[ $accordion_id ] = 1;
2401 + } else {
2402 + ++$item_index[ $accordion_id ];
2403 + }
2404 +
2405 + $parsed_block['attrs']['boldblocks']['accordionItem']['block_id'] = $accordion_id . '-item-' . $item_index[ $accordion_id ];
2406 + }
2407 +
2408 + return $parsed_block;
2409 + }
2410 +
2411 + /**
2412 + * Render accordion item blocks
2413 + *
2414 + * @param string $block_content
2415 + * @param array $block
2416 + * @param WP_Block $block_instance
2417 + * @return string
2418 + */
2419 + public function render_accordion_item_block( $block_content, $block, $block_instance ) {
2420 + // Ignore admin side.
2421 + if ( is_admin() ) {
2422 + return $block_content;
2423 + }
2424 +
2425 + // Bail if it is not an accordion item block.
2426 + if ( 'accordionItem' !== ( $block_instance->block_type->supports['layoutType'] ?? '' ) ) {
2427 + return $block_content;
2428 + }
2429 +
2430 + $block_id = $block['attrs']['boldblocks']['accordionItem']['block_id'] ?? '';
2431 + $block_id_selector = '#' . $block_id;
2432 +
2433 + if ( $block_id ) {
2434 + $processor = new \WP_HTML_Tag_Processor( $block_content );
2435 +
2436 + while ( $processor->next_tag() ) {
2437 + $tag_class = $processor->get_attribute( 'class' );
2438 + if ( ! $tag_class ) {
2439 + continue;
2440 + }
2441 +
2442 + if ( strpos( $tag_class, 'accordion-header' ) !== false ) {
2443 + $processor->set_attribute( 'data-toggle-target', $block_id_selector );
2444 + $processor->set_attribute( 'aria-controls', $block_id );
2445 + } elseif ( strpos( $tag_class, 'accordion-link' ) !== false ) {
2446 + $processor->set_attribute( 'href', $block_id_selector );
2447 + } elseif ( strpos( $tag_class, 'accordion-collapse' ) !== false ) {
2448 + $processor->set_attribute( 'id', $block_id );
2449 + $processor->set_attribute( 'role', 'region' );
2450 + break;
2451 + }
2452 + }
2453 +
2454 + $block_content = $processor->get_updated_html();
2455 + }
2456 +
2457 + return $block_content;
2458 + }
2459 +
2460 + /**
2097 2461 * Render grid style for the query loop's grid layout
2098 2462 *
2099 2463 * @param string $block_content
2100 2464 * @param array $block
@@ -2159,9 +2523,9 @@
2159 2523 $key = "id_{$query_id}";
2160 2524 if ( ! isset( $this->query_ids[ $key ] ) ) {
2161 2525 $this->query_ids[ $key ] = 1;
2162 2526 } else {
2163 - $this->query_ids[ $key ]++;
2527 + ++$this->query_ids[ $key ];
2164 2528 }
2165 2529
2166 2530 return $query_id . '-' . $this->query_ids[ $key ];
2167 2531 }
@@ -2285,8 +2649,46 @@
2285 2649 ]
2286 2650 )
2287 2651 );
2288 2652
2653 + // Auto rows style.
2654 + $style .= $this->build_query_loop_responsive_style(
2655 + array_merge(
2656 + $args,
2657 + [
2658 + 'setting_value' => $data['autoRows'] ?? null,
2659 + 'func_build_responsive_style' => function ( $args ) {
2660 + $style_array = [];
2661 + $value = $args['value'];
2662 + if ( $value ) {
2663 + $style_array = [
2664 + '--cbb--grid--auto-rows' => $value,
2665 + ];
2666 + }
2667 +
2668 + return $style_array;
2669 + },
2670 + 'func_build_dependent_style' => function ( $args ) {
2671 + $responsive_styles = $args['responsive_styles'] ?? [];
2672 + $selector = $args['selector'];
2673 + $breakpoints = $args['breakpoints'] ?? [];
2674 + $style = "{$selector}{grid-auto-rows:var(--cbb--grid--auto-rows);}";
2675 +
2676 + foreach ( $responsive_styles as $breakpoint => $attribute_array ) {
2677 + $media_query = $breakpoints[ $breakpoint ]['minQuery'] ?? '';
2678 + if ( $media_query ) {
2679 + $style = \str_replace( '##CONTENT##', $style, $media_query );
2680 + }
2681 +
2682 + break;
2683 + }
2684 +
2685 + return $style;
2686 + },
2687 + ]
2688 + )
2689 + );
2690 +
2289 2691 // Gap style.
2290 2692 $style .= $this->build_query_loop_responsive_style(
2291 2693 array_merge(
2292 2694 $args,
@@ -2447,12 +2849,10 @@
2447 2849 if ( 'columnSpan' === $setting_name ) {
2448 2850 if ( strpos( $style, $css_name ) !== false && strpos( $style, 'order' ) !== false ) {
2449 2851 break;
2450 2852 }
2451 - } else {
2452 - if ( strpos( $style, $css_name ) !== false ) {
2453 - break;
2454 - }
2853 + } elseif ( strpos( $style, $css_name ) !== false ) {
2854 + break;
2455 2855 }
2456 2856
2457 2857 $media_query = $breakpoints[ $breakpoint ]['minQuery'] ?? '';
2458 2858
@@ -2498,9 +2898,9 @@
2498 2898 // Build carousel settings.
2499 2899 $carousel_settings = $post_template->attributes['boldblocks']['carousel'] ?? [];
2500 2900 if ( $carousel_settings ) {
2501 2901 if ( ! $this->block_has_attribute( 'data-carousel-settings', $block_content ) ) {
2502 - $carousel_attr = $this->build_query_loop_carousel_settings( $carousel_settings );
2902 + $carousel_attr = $this->build_carousel_settings( $carousel_settings, true, $post_template->parsed_block, $post_template );
2503 2903
2504 2904 if ( $carousel_attr ) {
2505 2905 $block_content = $this->add_data_to_block( $block_content, 'data-carousel-settings', esc_attr( $carousel_attr ) );
2506 2906 }
@@ -2522,9 +2922,9 @@
2522 2922 'breakpoints' => $breakpoints,
2523 2923 ]
2524 2924 );
2525 2925
2526 - $carousel_class = 'js-carousel-layout';
2926 + $carousel_class = 'js-carousel-layout cbb-carousel-preload';
2527 2927
2528 2928 if ( $block_style ) {
2529 2929 $handle = $selector;
2530 2930 // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
@@ -2673,12 +3073,15 @@
2673 3073
2674 3074 /**
2675 3075 * Build a json string of carousel settings
2676 3076 *
2677 - * @param array $carousel_settings
3077 + * @param array $carousel_settings
3078 + * @param boolean $exclude_li_role
3079 + * @param array $parsed_block
3080 + * @param WP_Block $block_instance
2678 3081 * @return string
2679 3082 */
2680 - private function build_query_loop_carousel_settings( $carousel_settings ) {
3083 + private function build_carousel_settings( $carousel_settings, $exclude_li_role = false, $parsed_block = null, $block_instance = null ) {
2681 3084 // phpcs:disable
2682 3085 $dataset = [];
2683 3086
2684 3087 // Speed.
@@ -2699,14 +3102,23 @@
2699 3102
2700 3103 // Autoplay.
2701 3104 $dataset['autoplay'] = false;
2702 3105 $autoplay = $carousel_settings['autoplay'] ?? false;
2703 - $enable_autoplay = false;
2704 3106 if ( $autoplay && is_array( $autoplay ) && ( $autoplay['enable'] ?? false ) ) {
2705 3107 unset( $autoplay['enable'] );
3108 +
3109 + if ( !$dataset['loop'] && !$dataset['rewind']) {
3110 + $autoplay['stopOnLastSlide'] = true;
3111 + }
3112 +
2706 3113 $dataset['autoplay'] = $autoplay;
2707 3114 }
2708 3115
3116 + // Play/pause.
3117 + if ( $carousel_settings['playPause']['enable'] ?? false ) {
3118 + $dataset['playPause'] = $carousel_settings['playPause'];
3119 + }
3120 +
2709 3121 // Direction.
2710 3122 if ( $carousel_settings['direction'] ?? false ) {
2711 3123 $dataset['direction'] = $carousel_settings['direction'];
2712 3124 }
@@ -2725,21 +3137,17 @@
2725 3137
2726 3138 // Fade.
2727 3139 if ( 'fade' === $effect ) {
2728 3140 $dataset['fadeEffect'] = [ 'crossFade' => true ];
2729 - }
2730 -
2731 - // Coverflow.
2732 - if ( 'coverflow' === $effect ) {
3141 + } elseif ( 'coverflow' === $effect ) {
3142 + // Coverflow.
2733 3143 if ( is_array( $settings ) ) {
2734 3144 $dataset['coverflowEffect'] = $settings;
2735 3145 } else {
2736 3146 $dataset['coverflowEffect'] = [ 'slideShadows' => false ];
2737 3147 }
2738 - }
2739 -
2740 - // Creative.
2741 - if ( 'creative' === $effect ) {
3148 + } elseif ( 'creative' === $effect ) {
3149 + // Creative.
2742 3150 if ( is_array( $settings ) ) {
2743 3151 $dataset['creativeEffect'] = $settings;
2744 3152 } else {
2745 3153 $dataset['creativeEffect'] = [
@@ -2750,8 +3158,10 @@
2750 3158 'translate' => [ '100%', 0, 0 ],
2751 3159 ],
2752 3160 ];
2753 3161 }
3162 +
3163 + $dataset['direction'] = "horizontal";
2754 3164 }
2755 3165 }
2756 3166
2757 3167 // slidesPerView.
@@ -2762,88 +3172,112 @@
2762 3172 'cards',
2763 3173 'creative',
2764 3174 ];
2765 3175
2766 - $slidesPerViewRaw = $carousel_settings['slidesPerView'] ?? 1;
2767 - if ( in_array( $effect, $slidesPerViewDependencies, true ) ) {
3176 + if ( in_array($effect, $slidesPerViewDependencies, true ) ) {
2768 3177 $dataset['slidesPerView'] = 1;
2769 3178 } else {
2770 - $slidesPerView = $slidesPerViewRaw;
2771 - if ( is_array( $slidesPerViewRaw ) ) {
2772 - $slidesPerView = $slidesPerViewRaw['auto'] ?? false
2773 - ? 'auto'
2774 - : $slidesPerViewRaw['value'] ?? 1;
3179 + // centeredSlides.
3180 + $dataset['centeredSlides'] = $carousel_settings['centeredSlides'] ?? false;
3181 + if ($dataset['centeredSlides'] && $effect === 'slide' && ($carousel_settings['centeredSlidesSettings']['enable'] ?? '')) {
3182 + $dataset['centeredSlidesSettings'] = $carousel_settings['centeredSlidesSettings'];
2775 3183 }
2776 3184
2777 - if ( $slidesPerViewRaw ) {
2778 - $dataset['slidesPerView'] = $slidesPerViewRaw;
3185 + $slidesPerView = $this->carousel_refine_slides_per_view( $carousel_settings['slidesPerView'] ?? 1 );
3186 + $slidesPerGroup = absint( $carousel_settings['slidesPerGroup'] ?? 0 );
2779 3187
2780 - if ( $slidesPerView > 1 && ( $carousel_settings['slidesPerGroup'] ?? false ) ) {
2781 - $dataset['slidesPerGroup'] = $carousel_settings['slidesPerGroup'];
3188 + if ( $slidesPerView ) {
3189 + $dataset['slidesPerView'] = $slidesPerView;
3190 +
3191 + if ( $slidesPerView >= 2 && $slidesPerGroup ) {
3192 + $dataset['slidesPerGroup'] = $slidesPerGroup;
2782 3193 }
2783 3194 }
2784 3195
2785 3196 // spaceBetween.
2786 - $spaceBetween = $carousel_settings['spaceBetween'] ?? 0;
3197 + $spaceBetween = absint( $carousel_settings['spaceBetween'] ?? 0 );
2787 3198 if ( $spaceBetween ) {
2788 3199 $dataset['spaceBetween'] = $spaceBetween;
2789 3200 }
2790 3201
2791 3202 // Breakpoints.
2792 - $breakpointValues = [
2793 - 'sm' => 576,
2794 - 'md' => 768,
2795 - 'lg' => 1024,
3203 + $breakpoint_settings = $this->get_breakpoints();
3204 + $breakpoint_values = [
3205 + 'sm' => $breakpoint_settings['sm']['breakpoint'] ?? 576,
3206 + 'md' => $breakpoint_settings['md']['breakpoint'] ?? 768,
3207 + 'lg' => $breakpoint_settings['lg']['breakpoint'] ?? 1024,
2796 3208 ];
2797 - $enableResponsive = $carousel_settings['enableResponsive'] ?? false;
3209 +
2798 3210 $breakpoints = $carousel_settings['breakpoints'] ?? [];
2799 - if ( $enableResponsive && is_array( $breakpoints ) ) {
2800 - $breakpointAtts = [];
3211 + if ( ($carousel_settings['enableResponsive'] ?? false) && is_array( $breakpoints ) ) {
3212 + // Mobile first.
3213 + $this->sort_styles( $breakpoints );
3214 +
3215 + $breakpoint_atts = [];
2801 3216 foreach ( array_keys( $breakpoints ) as $breakpoint ) :
2802 3217 $value = $breakpoints[ $breakpoint ]['value'] ?? '';
2803 3218 $inherit = $breakpoints[ $breakpoint ]['inherit'] ?? '';
2804 3219
2805 - $valueByBreakpoint = false;
3220 + $value_by_breakpoint = false;
2806 3221 if ( $value && is_array( $value ) ) {
2807 - $valueByBreakpoint = $value;
3222 + $value_by_breakpoint = $value;
2808 3223 }
2809 3224
2810 - if ( ! $valueByBreakpoint && is_string( $inherit ) ) {
3225 + if ( ! $value_by_breakpoint && is_string( $inherit ) ) {
2811 3226 $inheritValue = $breakpoints[ $inherit ] ?? '';
2812 3227 if ( $inheritValue && is_array( $inheritValue ) ) {
2813 - $valueByBreakpoint = $inheritValue;
3228 + $value_by_breakpoint = $inheritValue;
2814 3229 }
2815 3230 }
2816 3231
2817 - if ( is_array( $valueByBreakpoint ) ) {
2818 - $breakpointAtts[ $breakpointValues[ $breakpoint ] ] = $valueByBreakpoint;
3232 + if ( is_array( $value_by_breakpoint ) ) {
3233 + $value_by_breakpoint['slidesPerView'] = $this->carousel_refine_slides_per_view( $value_by_breakpoint['slidesPerView'] ?? 1);
3234 + $breakpoint_atts[ $breakpoint_values[ $breakpoint ] ] = $value_by_breakpoint;
2819 3235 }
2820 3236 endforeach;
2821 3237
2822 - $dataset['breakpoints'] = $breakpointAtts;
3238 + if ( $breakpoint_atts ) {
3239 + // Replace global params.
3240 + $first_breakpoint = array_values($breakpoint_atts)[0];
3241 + $dataset['slidesPerView'] = $first_breakpoint['slidesPerView'];
3242 + if ($dataset['slidesPerView'] > 1 && ($first_breakpoint['slidesPerGroup'] ?? 0)) {
3243 + $dataset['slidesPerGroup'] = $first_breakpoint['slidesPerGroup'];
3244 + }
3245 + if ( ! empty( $first_breakpoint['spaceBetween'] ) ) {
3246 + $dataset['spaceBetween'] = $first_breakpoint['spaceBetween'];
3247 + }
3248 +
3249 + // Set the breakpoints.
3250 + $dataset['breakpoints'] = $breakpoint_atts;
3251 + }
2823 3252 } else {
2824 3253 if ( $carousel_settings['oneSlidePerViewInMobile'] ?? false ) {
3254 + $dataset['slidesPerView'] = 1;
3255 + if ( $slidesPerGroup ) {
3256 + $dataset['slidesPerGroup'] = 1;
3257 + }
3258 +
2825 3259 $dataset['breakpoints'] = [
2826 - $breakpointValues['sm'] => [
3260 + $breakpoint_values['sm'] => [
2827 3261 'slidesPerView' => 1,
2828 - 'spaceBetween' => 0,
3262 + 'slidesPerGroup' => 1,
2829 3263 ],
2830 3264
2831 - $breakpointValues['md'] => [
2832 - 'slidesPerView' => $slidesPerViewRaw,
2833 - 'spaceBetween' => $spaceBetween,
3265 + $breakpoint_values['md'] => [
3266 + 'slidesPerView' => $slidesPerView,
3267 + 'slidesPerGroup' => $slidesPerGroup ? $slidesPerGroup : 1,
2834 3268 ],
2835 3269 ];
2836 3270 }
3271 +
3272 + // Reset centeredSlides
3273 + if ( ! ( $slidesPerView > 1 || 'auto' === $slidesPerView ) ) {
3274 + unset( $dataset['centeredSlides'] );
3275 + unset( $dataset['centeredSlidesSettings'] );
3276 + }
2837 3277 }
2838 3278 }
2839 3279
2840 - // centeredSlides.
2841 - $dataset['centeredSlides'] = $carousel_settings['centeredSlides'] ?? false;
2842 - if ($dataset['centeredSlides'] && $effect === 'slide' && ($carousel_settings['centeredSlidesSettings']['enable'] ?? '')) {
2843 - $dataset['centeredSlidesSettings'] = $carousel_settings['centeredSlidesSettings'];
2844 - }
2845 -
2846 3280 // Pagination.
2847 3281 $pagination = $carousel_settings['pagination'] ?? [];
2848 3282 $dataset['pagination'] = $pagination['enable'] ?? false;
2849 3283 if ( $dataset['pagination'] ) {
@@ -2853,8 +3287,20 @@
2853 3287 // Navigation.
2854 3288 $navigation = $carousel_settings['navigation'] ?? [];
2855 3289 $dataset['navigation'] = $navigation['enable'] ?? false;
2856 3290 if ( $dataset['navigation'] ) {
3291 + $next_icon = trim($navigation['nextIcon']['value'] ?? '');
3292 + $prev_icon = trim($navigation['prevIcon']['value'] ?? '');
3293 + if ( $next_icon || $prev_icon ) {
3294 + $svg_sanitizer = $this->the_plugin_instance->get_component( IconLibrary::class );
3295 + if ($svg_sanitizer) {
3296 + $next_icon = $next_icon ? $svg_sanitizer->sanitize_svg( $next_icon ) : '';
3297 + $prev_icon = $prev_icon ? $svg_sanitizer->sanitize_svg( $prev_icon ) : '';
3298 +
3299 + $navigation['nextIcon']['value'] = $next_icon;
3300 + $navigation['prevIcon']['value'] = $prev_icon;
3301 + }
3302 + }
2857 3303 $dataset['navigationSettings'] = $navigation;
2858 3304 }
2859 3305
2860 3306 // Scrollbar.
@@ -2870,18 +3316,41 @@
2870 3316 // Only add the property when it is true to prevent from invalid content.
2871 3317 $dataset['equalHeight'] = $equalHeight;
2872 3318 }
2873 3319
2874 - $thumbs = $carousel_settings['thumbs'] ?? false;
2875 - if ( $thumbs && ( $thumbs['enable'] ?? false ) ) {
3320 + // Sync carousels.
3321 + $thumbs = $carousel_settings['thumbs'] ?? [];
3322 + if ( $thumbs['enable'] ?? false ) {
2876 3323 $dataset['thumbsSettings'] = $thumbs;
2877 3324 }
2878 3325
2879 - return wp_json_encode( $dataset );
3326 + // Exclude li role.
3327 + if ( $exclude_li_role ) {
3328 + $dataset['a11y'] = ['slideRole' => '', 'slideLabelMessage' => ''];
3329 + }
3330 +
3331 + return wp_json_encode( apply_filters( 'cbb_get_carousel_settings', $dataset, $parsed_block, $block_instance ) );
2880 3332 // phpcs:enable
2881 3333 }
2882 3334
2883 3335 /**
3336 + * Refine the slides per view
3337 + *
3338 + * @param mixed $raw_value
3339 + * @return string|float 'auto' || number
3340 + */
3341 + private function carousel_refine_slides_per_view( $raw_value ) {
3342 + $value = $raw_value;
3343 + if ( is_array( $raw_value ) ) {
3344 + $value = $raw_value['auto'] ?? false
3345 + ? 'auto'
3346 + : floatval( $raw_value['value'] ?? 1 );
3347 + }
3348 +
3349 + return $value ? $value : 1;
3350 + }
3351 +
3352 + /**
2884 3353 * Get nested post template block for core/query
2885 3354 *
2886 3355 * @param WP_Block $block_instance
2887 3356 * @return boolean|WP_Block
@@ -3013,9 +3482,9 @@
3013 3482 }
3014 3483
3015 3484 $breakpoints = array_filter(
3016 3485 array_map(
3017 - function( $item ) {
3486 + function ( $item ) {
3018 3487 if ( empty( $item['breakpoint'] ) || empty( $item['prefix'] ) ) {
3019 3488 return false;
3020 3489 }
3021 3490
@@ -3067,13 +3536,13 @@
3067 3536 }
3068 3537
3069 3538 if ( $this->style_contexts['content'] ) {
3070 3539 // The content context selector.
3071 - $content_counter = ++ $this->style_contexts['content_counter'];
3540 + $content_counter = ++$this->style_contexts['content_counter'];
3072 3541 return "{$selector_prefix}-p{$this->style_contexts['post']}-{$content_counter}";
3073 3542 }
3074 3543
3075 - $counter = ++ $this->style_contexts['counter'];
3544 + $counter = ++$this->style_contexts['counter'];
3076 3545 // The selector inside the loop.
3077 3546 return "{$selector_prefix}-{$this->style_contexts['loop']}-{$counter}";
3078 3547 } else {
3079 3548 // The global selector.
@@ -3095,9 +3564,9 @@
3095 3564 if ( in_array( $query_id, $this->style_contexts['loops'], true ) ) {
3096 3565 $count = count(
3097 3566 array_filter(
3098 3567 $this->style_contexts['loops'],
3099 - function( $value ) use ( $query_id ) {
3568 + function ( $value ) use ( $query_id ) {
3100 3569 return $value === $query_id;
3101 3570 }
3102 3571 )
3103 3572 );
@@ -3161,20 +3630,18 @@
3161 3630 $sticky_classes[] = 'is-stick-to-' . ( $sticky_data['stickTo'] ?? 'top' );
3162 3631
3163 3632 if ( $sticky_data['isStickOnScrollingUp'] ?? false ) {
3164 3633 $sticky_classes[] = 'is-sticky-on-scrollup';
3165 - } else {
3166 - if ( $sticky_data['isFixed'] ?? false ) {
3167 - $sticky_classes[] = 'is-fixed';
3168 - if ( $sticky_data['isInFlow'] ?? false ) {
3169 - $sticky_classes[] = 'is-in-flow';
3170 - }
3171 - if ( $sticky_data['isDetectingScroll'] ?? false ) {
3172 - $sticky_classes[] = 'is-detecting-scroll';
3173 - }
3174 - } elseif ( $sticky_data['isDetectingStuck'] ?? false ) {
3175 - $sticky_classes[] = 'is-detecting-stuck';
3634 + } elseif ( $sticky_data['isFixed'] ?? false ) {
3635 + $sticky_classes[] = 'is-fixed';
3636 + if ( $sticky_data['isInFlow'] ?? false ) {
3637 + $sticky_classes[] = 'is-in-flow';
3176 3638 }
3639 + if ( $sticky_data['isDetectingScroll'] ?? false ) {
3640 + $sticky_classes[] = 'is-detecting-scroll';
3641 + }
3642 + } elseif ( $sticky_data['isDetectingStuck'] ?? false ) {
3643 + $sticky_classes[] = 'is-detecting-stuck';
3177 3644 }
3178 3645
3179 3646 if ( in_array( $block['attrs']['area'] ?? '', [ 'header', 'footer' ], true ) ) {
3180 3647 $sticky_classes[] = 'is-sticky-' . $block['attrs']['area'];
@@ -3186,9 +3653,9 @@
3186 3653 return $block_content;
3187 3654 }
3188 3655
3189 3656 /**
3190 - * Render preloader for carousel layout blocks
3657 + * Render settings for carousel layout blocks
3191 3658 *
3192 3659 * @param string $block_content
3193 3660 * @param array $block
3194 3661 * @param WP_Block $block_instance
@@ -3193,9 +3660,9 @@
3193 3660 * @param array $block
3194 3661 * @param WP_Block $block_instance
3195 3662 * @return string
3196 3663 */
3197 - public function build_carousel_preloader( $block_content, $block, $block_instance ) {
3664 + public function render_carousel_settings( $block_content, $block, $block_instance ) {
3198 3665 // Ignore admin side.
3199 3666 if ( is_admin() ) {
3200 3667 return $block_content;
3201 3668 }
@@ -3200,27 +3667,39 @@
3200 3667 return $block_content;
3201 3668 }
3202 3669
3203 3670 // There is no carousel layout.
3204 - if ( ! $this->has_style( $block, $block_instance ) || ( 'carousel' !== ( $block_instance->block_type->supports['layoutType'] ?? '' ) ) ) {
3671 + if ( 'carousel' !== ( $block_instance->block_type->supports['layoutType'] ?? '' ) ) {
3205 3672 return $block_content;
3206 3673 }
3207 3674
3675 + // Carousel settings.
3676 + $carousel_settings = $block['attrs']['boldblocks']['carousel'] ?? [];
3677 +
3678 + // Dataset attribute.
3679 + $carousel_dataset = $this->build_carousel_settings( $carousel_settings, false, $block, $block_instance );
3680 + if ( $carousel_dataset ) {
3681 + $block_content = $this->add_data_to_block( $block_content, 'data-carousel-settings', esc_attr( $carousel_dataset ) );
3682 + }
3683 +
3684 + // Preload class.
3685 + $preloader_class = 'cbb-carousel-preload';
3686 +
3208 3687 // Has preloader?
3209 - $has_preloader = $block['attrs']['boldblocks']['carousel']['displayLoader'] ?? false;
3688 + $has_preloader = $carousel_settings['displayLoader'] ?? false;
3210 3689 if ( $has_preloader ) {
3211 - $preloader_class = 'has-preloader';
3212 - if ( 'coverflow' === ( $block['attrs']['boldblocks']['carousel']['effect'] ?? '' ) ) {
3690 + $preloader_class .= ' has-preloader';
3691 + if ( 'coverflow' === ( $carousel_settings['effect'] ?? '' ) ) {
3213 3692 $preloader_class .= ' is-fading-loader';
3214 3693 }
3215 3694
3216 - // Mark it as having preloader.
3217 - $block_content = $this->add_class_to_block( $block_content, $preloader_class );
3218 -
3219 3695 // Add the loader to the markup.
3220 3696 $block_content = $this->add_inner_content_to_block( $block_content, $this->get_carousel_preloader_markup( $block, $block_instance ) );
3221 3697 }
3222 3698
3699 + // Mark it as having preloader.
3700 + $block_content = $this->add_class_to_block( $block_content, $preloader_class );
3701 +
3223 3702 return $block_content;
3224 3703 }
3225 3704
3226 3705 /**
@@ -3234,8 +3713,100 @@
3234 3713 return apply_filters( 'cbb_carousel_preloader', '<div class="cbb-loader"></div>', $block, $block_instance );
3235 3714 }
3236 3715
3237 3716 /**
3717 + * Manage css class: migrate old class and adding custom css class.
3718 + *
3719 + * @param string $block_content
3720 + * @param array $block
3721 + * @param WP_Block $block_instance
3722 + * @return string
3723 + */
3724 + public function manage_cbb_class( $block_content, $block, $block_instance ) {
3725 + // Ignore admin side or empty content.
3726 + if ( is_admin() || ! $block_content ) {
3727 + return $block_content;
3728 + }
3729 +
3730 + // Bail if it is not a CBB block.
3731 + if ( empty( $block_instance->block_type->supports['cbb'] ?? '' ) ) {
3732 + return $block_content;
3733 + }
3734 +
3735 + // Get custom classes.
3736 + $css_classes = trim( $block_instance->block_type->supports['CSSClasses'] ?? '' );
3737 +
3738 + // Get migrate class.
3739 + $migrate_class = trim( $block_instance->block_type->supports['migrateClass'] ?? '' );
3740 +
3741 + // Bail if we don't need to process.
3742 + if ( ! $css_classes && ! $migrate_class ) {
3743 + return $block_content;
3744 + }
3745 +
3746 + // Initialize the HTML Tag Processor.
3747 + $p = new \WP_HTML_Tag_Processor( $block_content );
3748 +
3749 + // Target only the very first outer wrapping element.
3750 + if ( $p->next_tag() ) {
3751 + if ( $migrate_class ) {
3752 + $p->remove_class( $migrate_class );
3753 + $p->add_class( str_ends_with( $migrate_class, 'custom' ) ? 'cbb-block' : 'cbb-block-parent' );
3754 + }
3755 +
3756 + if ( $css_classes ) {
3757 + // Explode by space in case multiple custom classes are supplied.
3758 + foreach ( explode( ' ', $css_classes ) as $single_class ) {
3759 + if ( ! empty( $single_class ) ) {
3760 + $p->add_class( $single_class );
3761 + }
3762 + }
3763 + }
3764 +
3765 + // Render the updated HTML.
3766 + return $p->get_updated_html();
3767 + }
3768 +
3769 + return $block_content;
3770 + }
3771 +
3772 + /**
3773 + * Render steps block data
3774 + *
3775 + * @param array $parsed_block
3776 + * @param array $block
3777 + * @param WP_Block $parent_block
3778 + * @return array
3779 + */
3780 + public function render_steps_block_data( $parsed_block, $block, $parent_block ) {
3781 + if ( ! $parent_block || empty( $parent_block->attributes['boldblocks']['steps']['enableSteps'] ) ) {
3782 + return $parsed_block;
3783 + }
3784 +
3785 + // Mark it as a child of steps.
3786 + $parsed_block['attrs']['boldblocks']['childStep'] = true;
3787 +
3788 + return $parsed_block;
3789 + }
3790 +
3791 + /**
3792 + * Render aria attributes for steps blocks
3793 + *
3794 + * @param string $block_content
3795 + * @param array $block
3796 + * @return string
3797 + */
3798 + public function render_steps_attributes( $block_content, $block ) {
3799 + if ( ! empty( $block['attrs']['boldblocks']['steps']['enableSteps'] ) ) {
3800 + $block_content = $this->add_data_to_block( $block_content, 'role', 'list' );
3801 + } elseif ( ! empty( $block['attrs']['boldblocks']['childStep'] ) ) {
3802 + $block_content = $this->add_data_to_block( $block_content, 'role', 'listitem' );
3803 + }
3804 +
3805 + return $block_content;
3806 + }
3807 +
3808 + /**
3238 3809 * Support block style for non-cbb blocks
3239 3810 *
3240 3811 * @param string $is_supported
3241 3812 * @param array $block
@@ -3300,9 +3871,9 @@
3300 3871 */
3301 3872 private function sort_styles( &$styles ) {
3302 3873 uksort(
3303 3874 $styles,
3304 - function( $a, $b ) {
3875 + function ( $a, $b ) {
3305 3876 if ( 'lg' === $a || 'sm' === $b ) {
3306 3877 return 1;
3307 3878 }
3308 3879
@@ -3446,8 +4017,9 @@
3446 4017 /**
3447 4018 * Add data attribute to block wrapper
3448 4019 *
3449 4020 * @param string $block_content
4021 + * @param string $name
3450 4022 * @param string $value
3451 4023 * @return string
3452 4024 */
3453 4025 public function add_data_to_block( $block_content, $name, $value ) {
@@ -3479,8 +4051,46 @@
3479 4051 return $block_content;
3480 4052 }
3481 4053
3482 4054 /**
4055 + * Get inner html of a tag
4056 + *
4057 + * @param string $content
4058 + * @param string $tag_name
4059 + * @return string
4060 + */
4061 + public function get_element_inner_html( $content, $tag_name ) {
4062 + $processor = new class( $content ) extends \WP_HTML_Tag_Processor {
4063 + public function get_inner_html( $tag_name ) {
4064 + $this->set_bookmark( 'open' );
4065 + $opener = $this->bookmarks['open'];
4066 +
4067 + while ( $this->next_token() ) {
4068 + if ( $this->get_tag() === $tag_name && $this->is_tag_closer() ) {
4069 +
4070 + $this->set_bookmark( 'close' );
4071 + $closer = $this->bookmarks['close'];
4072 +
4073 + return substr(
4074 + $this->html,
4075 + $opener->start + $opener->length,
4076 + $closer->start - $opener->start - $opener->length
4077 + );
4078 + }
4079 + }
4080 +
4081 + return '';
4082 + }
4083 + };
4084 +
4085 + if ( ! $processor->next_tag( $tag_name ) ) {
4086 + return '';
4087 + }
4088 +
4089 + return $processor->get_inner_html( $tag_name );
4090 + }
4091 +
4092 + /**
3483 4093 * Detect a value is valid
3484 4094 *
3485 4095 * @param mixed $value
3486 4096 * @return boolean
@@ -3489,8 +4099,18 @@
3489 4099 return isset( $value ) && $value !== '';
3490 4100 }
3491 4101
3492 4102 /**
4103 + * Detect a responsive value is valid
4104 + *
4105 + * @param mixed $value
4106 + * @return boolean
4107 + */
4108 + private function is_valid_responsive_value( $value ) {
4109 + return ! empty( $value['lg']['value'] ) || ! empty( $value['md']['value'] ) || ! empty( $value['sm']['value'] );
4110 + }
4111 +
4112 + /**
3493 4113 * Get value for spacing var
3494 4114 *
3495 4115 * @param string $value
3496 4116 * @return string
@@ -3541,8 +4161,66 @@
3541 4161 return $value;
3542 4162 }
3543 4163
3544 4164 /**
4165 + * Sanitize post data
4166 + *
4167 + * @param array $data
4168 + * @return array
4169 + */
4170 + public function sanitize_post_data( $data ) {
4171 + if ( empty( $data['post_content'] ) ) {
4172 + return $data;
4173 + }
4174 +
4175 + if ( ! $this->user_can_edit_css() ) {
4176 + $blocks = parse_blocks( wp_unslash( $data['post_content'] ) );
4177 +
4178 + // Strip custom css from blocks.
4179 + $modified = false;
4180 + $blocks = $this->remove_custom_css_from_blocks( $blocks, $modified );
4181 +
4182 + if ( $modified ) {
4183 + $data['post_content'] = wp_slash( serialize_blocks( $blocks ) );
4184 + }
4185 + }
4186 +
4187 + return $data;
4188 + }
4189 +
4190 + /**
4191 + * Remove custom css if any
4192 + *
4193 + * @param array $blocks
4194 + * @param boolean $modified
4195 + * @return array
4196 + */
4197 + private function remove_custom_css_from_blocks( $blocks, &$modified = false ) {
4198 + foreach ( $blocks as &$block ) {
4199 + if ( ! empty( $block['attrs']['boldblocks']['customCSS']['value'] ) ) {
4200 + $block['attrs']['boldblocks']['customCSS']['value'] = '';
4201 + $modified = true;
4202 + }
4203 +
4204 + // Recursive for inner blocks.
4205 + if ( ! empty( $block['innerBlocks'] ) ) {
4206 + $block['innerBlocks'] = $this->remove_custom_css_from_blocks( $block['innerBlocks'], $modified );
4207 + }
4208 + }
4209 +
4210 + return $blocks;
4211 + }
4212 +
4213 + /**
4214 + * If the current user can edit css
4215 + *
4216 + * @return boolean
4217 + */
4218 + public function user_can_edit_css() {
4219 + return apply_filters( 'cbb_user_can_edit_css', current_user_can( 'manage_options' ) );
4220 + }
4221 +
4222 + /**
3545 4223 * Refine custom JS and CSS value
3546 4224 *
3547 4225 * @param string $code
3548 4226 * @param array $tokens
@@ -3550,9 +4228,22 @@
3550 4228 * @return string
3551 4229 */
3552 4230 public function refine_custom_value( $code, $tokens = [], $type = 'CSS' ) {
3553 4231 // Remove tags.
3554 - // $code = preg_replace( '/(<([\w-]+)>)/mi', '', $code );
4232 + if ( 'CSS' === $type ) {
4233 + $unsafe_tags = [ 'script', 'style', 'iframe', 'object', 'embed', 'img', 'video', 'audio', 'math', 'form' ];
4234 +
4235 + // Build a regex pattern that matches:
4236 + // 1. <tag ...> ... </tag> (full tag with content)
4237 + // 2. <tag ... /> or <tag ...> (self-closing or empty).
4238 + $pattern = sprintf(
4239 + '/<(%s)\b[^>]*>(?:.*?<\/\1\s*>)?/is',
4240 + implode( '|', $unsafe_tags )
4241 + );
4242 +
4243 + // Remove all unsafe tags in one go.
4244 + $code = preg_replace( $pattern, '', $code );
4245 + }
3555 4246
3556 4247 // Replace breakpoint tokens.
3557 4248 $breakpoints = $this->get_breakpoints();
3558 4249