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 +633 -69 2.8.2 → 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.
@@ -125,10 +131,19 @@
125 131
126 132 // Render settings for carousels.
127 133 add_filter( 'render_block', [ $this, 'render_carousel_settings' ], 30, 3 );
128 134
129 - // Migrate CBB class for old blocks.
130 - add_filter( 'render_block', [ $this, 'migrate_cbb_class' ], 30, 3 );
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' ] );
131 146 }
132 147
133 148 /**
134 149 * Register custom setting fields
@@ -315,12 +330,12 @@
315 330 $visibility_style .= "@media(min-width:{$md_breakpoint}px) and (max-width:{$lg_breakpoint_max}px){.cbb-hidden-md{display:none !important;}}";
316 331 $visibility_style .= "@media(min-width:{$lg_breakpoint}px){.cbb-hidden-lg{display:none !important;}}";
317 332 $style .= $visibility_style;
318 333
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}";
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}";
323 338 $style .= $grid_style;
324 339
325 340 // Grid item: columnSpan & rowSpan.
326 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);}';
@@ -328,11 +343,11 @@
328 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}";
329 344 $style .= $grid_item_style;
330 345
331 346 // 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}";
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}";
335 350 $style .= $accordion_gap_style;
336 351
337 352 // Accordion padding.
338 353 // We don't need padding style, because it has a default value.
@@ -492,8 +507,13 @@
492 507 'func_build_responsive_style' => [ $this, 'build_rows_style' ],
493 508 'group' => 'grid',
494 509 'layout_type' => 'grid',
495 510 ],
511 + 'autoRows' => [
512 + 'func_build_responsive_style' => [ $this, 'build_auto_rows_style' ],
513 + 'group' => 'grid',
514 + 'layout_type' => 'grid',
515 + ],
496 516 'gap' => [
497 517 'func_build_responsive_style' => [ $this, 'build_gap_style' ],
498 518 'group' => 'grid',
499 519 'layout_type' => 'grid',
@@ -520,8 +540,20 @@
520 540 'group' => 'accordion',
521 541 'setting_name' => 'padding',
522 542 'layout_type' => 'accordion',
523 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 + ],
524 556 'stickyOffset' => [
525 557 'func_build_responsive_style' => [ $this, 'build_sticky_offset_style' ],
526 558 'group' => 'sticky',
527 559 'setting_name' => 'offset',
@@ -537,9 +569,9 @@
537 569 if ( ! in_array( $feature, [ 'border', 'hidden' ], true ) && 'accordionItem' === $block_layout ) {
538 570 continue;
539 571 }
540 572
541 - 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 ) {
542 574 continue;
543 575 }
544 576
545 577 if ( 'stickyOffset' === $feature ) {
@@ -587,8 +619,14 @@
587 619 $this->build_responsive_style( $args, $responsive_style_array, $classes );
588 620 }
589 621 }
590 622
623 + if ( $style_array ) {
624 + foreach ( $style_array as $item_style ) {
625 + $style .= $item_style;
626 + }
627 + }
628 +
591 629 if ( $responsive_style_array ) {
592 630 foreach ( $responsive_style_array as $responsive_style ) {
593 631 $style .= $responsive_style;
594 632 }
@@ -645,26 +683,9 @@
645 683 return $return_value;
646 684 }
647 685
648 686 // 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;
687 + return $func_build_style( $args, $style_array, $classes );
667 688 }
668 689
669 690 /**
670 691 * Build style for padding
@@ -1088,8 +1109,9 @@
1088 1109
1089 1110 /**
1090 1111 * Build transform item: translate, rotate, scale, skew
1091 1112 *
1113 + * @param array $transform_item
1092 1114 * @return string
1093 1115 */
1094 1116 private function build_transform_item( $transform_item ) {
1095 1117 $style = '';
@@ -1166,10 +1188,9 @@
1166 1188 * @param array &$classes
1167 1189 * @return array
1168 1190 */
1169 1191 private function build_hidden_style( $args, &$style_array, &$classes ) {
1170 - $style_array = [];
1171 - $value = $args['value'] ?? null;
1192 + $value = $args['value'] ?? null;
1172 1193 if ( ! empty( $value ) && is_array( $value ) ) {
1173 1194 foreach ( $value as $breakpoint ) {
1174 1195 $class = "cbb-hidden-{$breakpoint}";
1175 1196 if ( ! in_array( $class, $classes, true ) ) {
@@ -1176,10 +1197,8 @@
1176 1197 $classes[] = $class;
1177 1198 }
1178 1199 }
1179 1200 }
1180 -
1181 - return $style_array;
1182 1201 }
1183 1202
1184 1203 /**
1185 1204 * Build style for grid columns
@@ -1213,8 +1232,24 @@
1213 1232 return $style_array;
1214 1233 }
1215 1234
1216 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 + /**
1217 1252 * Build style for grid gap
1218 1253 *
1219 1254 * @param array $args
1220 1255 * @return string
@@ -1306,9 +1341,9 @@
1306 1341 private function build_accordion_gap_style( $args ) {
1307 1342 $style_array = [];
1308 1343 $value = $args['value'];
1309 1344 if ( $this->is_valid_value( $value ) ) {
1310 - $style_array['--cbb--accordion-gap'] = $value;
1345 + $style_array['--cbb--accordion-gap'] = $this->get_spacing_value( $value );
1311 1346 }
1312 1347
1313 1348 return $style_array;
1314 1349 }
@@ -1324,9 +1359,9 @@
1324 1359 $style = '';
1325 1360 $setting_values = $args['setting_values'];
1326 1361 if ( $this->is_valid_value( $setting_values ) && is_array( $setting_values ) ) {
1327 1362 foreach ( $setting_values as $breakpoint => $value ) {
1328 - if ( absint( $value ) > 0 ) {
1363 + if ( strpos( $value, 'var:preset|spacing|' ) !== false || absint( $value ) > 0 ) {
1329 1364 $classes[] = "{$breakpoint}-cbb-a-has-border";
1330 1365 } else {
1331 1366 $classes[] = "{$breakpoint}-cbb-a-no-border";
1332 1367 }
@@ -1349,13 +1384,15 @@
1349 1384 $x = $value['x'] ?? null;
1350 1385 if ( ! $this->is_valid_value( $x ) ) {
1351 1386 $x = '1.25rem';
1352 1387 }
1388 + $x = $this->get_spacing_value( $x );
1353 1389
1354 1390 $y = $value['y'] ?? null;
1355 1391 if ( ! $this->is_valid_value( $y ) ) {
1356 1392 $y = '1rem';
1357 1393 }
1394 + $y = $this->get_spacing_value( $y );
1358 1395
1359 1396 $style_array['--cbb--accordion-padding'] = "{$y} {$x}";
1360 1397 }
1361 1398
@@ -1362,8 +1399,77 @@
1362 1399 return $style_array;
1363 1400 }
1364 1401
1365 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 + /**
1366 1472 * Build style for sticky offset
1367 1473 *
1368 1474 * @param array $args
1369 1475 * @return array
@@ -1383,9 +1489,9 @@
1383 1489
1384 1490 /**
1385 1491 * Check whether current block has custom style or not.
1386 1492 *
1387 - * @param [type] $block
1493 + * @param array $block
1388 1494 * @return boolean
1389 1495 */
1390 1496 private function get_custom_css_style( $block ) {
1391 1497 // There is no boldblocks value.
@@ -1429,9 +1535,9 @@
1429 1535 $background = $block['attrs']['boldblocks']['background'];
1430 1536 $media_type = $background['mediaType'] ?? 'image';
1431 1537
1432 1538 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>' );
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>' );
1434 1540 }
1435 1541
1436 1542 return $block_content;
1437 1543 }
@@ -1479,10 +1585,16 @@
1479 1585 return $this->build_thumbnail_background( $background, $block_content, $block, $block_instance );
1480 1586 } elseif ( $media_url ) {
1481 1587 return $this->build_custom_image_background( $background, $block_content, $block, $block_instance );
1482 1588 }
1483 - } elseif ( 'video' === $media_type && $media_url ) {
1484 - 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 + }
1485 1597 }
1486 1598
1487 1599 return $block_content;
1488 1600 }
@@ -1875,9 +1987,9 @@
1875 1987 if ( ! $name || ! $duration ) {
1876 1988 return $accumulator;
1877 1989 }
1878 1990
1879 - $delay = $animation['delay'] ?? 0;
1991 + $delay = empty( $animation['delay'] ) ? 0 : $animation['delay'];
1880 1992 $repeat = $animation['repeat'] ?? 1;
1881 1993 $infinite = $animation['infinite'] ?? false;
1882 1994 $timing_function = $animation['timingFunction'] ?? '';
1883 1995 $animation_name = isset( $animation_names[ $name ]['name'] ) ? $animation_names[ $name ]['name'] : $name;
@@ -1910,8 +2022,9 @@
1910 2022 if ( $animation_object['name'] && $animation_object['animation'] ) {
1911 2023 $animation_object['multipleTimes'] = $block['attrs']['boldblocks']['animateMultipleTimes'] ?? false;
1912 2024
1913 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' );
1914 2027 }
1915 2028
1916 2029 return $block_content;
1917 2030 }
@@ -2058,9 +2171,10 @@
2058 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 );
2059 2172
2060 2173 if ( $media['isPausable'] ?? false ) {
2061 2174 $block_class .= ' cbb-has-video-controls';
2062 - $block_background .= '<button class="cbb-play-pause cbb-video-play-pause is-playing"></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>';
2063 2177 }
2064 2178
2065 2179 if ( strpos( $block['innerHTML'] ?? '', $block_class ) === false ) {
2066 2180 $block_content = $this->add_class_to_block( $block_content, $block_class );
@@ -2071,8 +2185,123 @@
2071 2185 return $block_content;
2072 2186 }
2073 2187
2074 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 + /**
2075 2304 * Render block overlay
2076 2305 *
2077 2306 * @param string $block_content
2078 2307 * @param array $block
@@ -2132,8 +2361,104 @@
2132 2361 return $block_content;
2133 2362 }
2134 2363
2135 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 + /**
2136 2461 * Render grid style for the query loop's grid layout
2137 2462 *
2138 2463 * @param string $block_content
2139 2464 * @param array $block
@@ -2324,8 +2649,46 @@
2324 2649 ]
2325 2650 )
2326 2651 );
2327 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 +
2328 2691 // Gap style.
2329 2692 $style .= $this->build_query_loop_responsive_style(
2330 2693 array_merge(
2331 2694 $args,
@@ -2535,9 +2898,9 @@
2535 2898 // Build carousel settings.
2536 2899 $carousel_settings = $post_template->attributes['boldblocks']['carousel'] ?? [];
2537 2900 if ( $carousel_settings ) {
2538 2901 if ( ! $this->block_has_attribute( 'data-carousel-settings', $block_content ) ) {
2539 - $carousel_attr = $this->build_carousel_settings( $carousel_settings );
2902 + $carousel_attr = $this->build_carousel_settings( $carousel_settings, true, $post_template->parsed_block, $post_template );
2540 2903
2541 2904 if ( $carousel_attr ) {
2542 2905 $block_content = $this->add_data_to_block( $block_content, 'data-carousel-settings', esc_attr( $carousel_attr ) );
2543 2906 }
@@ -2710,12 +3073,15 @@
2710 3073
2711 3074 /**
2712 3075 * Build a json string of carousel settings
2713 3076 *
2714 - * @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
2715 3081 * @return string
2716 3082 */
2717 - private function build_carousel_settings( $carousel_settings ) {
3083 + private function build_carousel_settings( $carousel_settings, $exclude_li_role = false, $parsed_block = null, $block_instance = null ) {
2718 3084 // phpcs:disable
2719 3085 $dataset = [];
2720 3086
2721 3087 // Speed.
@@ -2809,15 +3175,22 @@
2809 3175
2810 3176 if ( in_array($effect, $slidesPerViewDependencies, true ) ) {
2811 3177 $dataset['slidesPerView'] = 1;
2812 3178 } else {
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'];
3183 + }
3184 +
2813 3185 $slidesPerView = $this->carousel_refine_slides_per_view( $carousel_settings['slidesPerView'] ?? 1 );
3186 + $slidesPerGroup = absint( $carousel_settings['slidesPerGroup'] ?? 0 );
2814 3187
2815 3188 if ( $slidesPerView ) {
2816 3189 $dataset['slidesPerView'] = $slidesPerView;
2817 3190
2818 - if ( $slidesPerView > 1 && ( $carousel_settings['slidesPerGroup'] ?? false ) ) {
2819 - $dataset['slidesPerGroup'] = absint( $carousel_settings['slidesPerGroup'] );
3191 + if ( $slidesPerView >= 2 && $slidesPerGroup ) {
3192 + $dataset['slidesPerGroup'] = $slidesPerGroup;
2820 3193 }
2821 3194 }
2822 3195
2823 3196 // spaceBetween.
@@ -2868,9 +3241,9 @@
2868 3241 $dataset['slidesPerView'] = $first_breakpoint['slidesPerView'];
2869 3242 if ($dataset['slidesPerView'] > 1 && ($first_breakpoint['slidesPerGroup'] ?? 0)) {
2870 3243 $dataset['slidesPerGroup'] = $first_breakpoint['slidesPerGroup'];
2871 3244 }
2872 - if ( $first_breakpoint['spaceBetween'] ) {
3245 + if ( ! empty( $first_breakpoint['spaceBetween'] ) ) {
2873 3246 $dataset['spaceBetween'] = $first_breakpoint['spaceBetween'];
2874 3247 }
2875 3248
2876 3249 // Set the breakpoints.
@@ -2877,29 +3250,34 @@
2877 3250 $dataset['breakpoints'] = $breakpoint_atts;
2878 3251 }
2879 3252 } else {
2880 3253 if ( $carousel_settings['oneSlidePerViewInMobile'] ?? false ) {
3254 + $dataset['slidesPerView'] = 1;
3255 + if ( $slidesPerGroup ) {
3256 + $dataset['slidesPerGroup'] = 1;
3257 + }
3258 +
2881 3259 $dataset['breakpoints'] = [
2882 3260 $breakpoint_values['sm'] => [
2883 3261 'slidesPerView' => 1,
2884 - 'spaceBetween' => 0,
3262 + 'slidesPerGroup' => 1,
2885 3263 ],
2886 3264
2887 3265 $breakpoint_values['md'] => [
2888 3266 'slidesPerView' => $slidesPerView,
2889 - 'spaceBetween' => $spaceBetween,
3267 + 'slidesPerGroup' => $slidesPerGroup ? $slidesPerGroup : 1,
2890 3268 ],
2891 3269 ];
2892 3270 }
3271 +
3272 + // Reset centeredSlides
3273 + if ( ! ( $slidesPerView > 1 || 'auto' === $slidesPerView ) ) {
3274 + unset( $dataset['centeredSlides'] );
3275 + unset( $dataset['centeredSlidesSettings'] );
3276 + }
2893 3277 }
2894 3278 }
2895 3279
2896 - // centeredSlides.
2897 - $dataset['centeredSlides'] = $carousel_settings['centeredSlides'] ?? false;
2898 - if ($dataset['centeredSlides'] && $effect === 'slide' && ($carousel_settings['centeredSlidesSettings']['enable'] ?? '')) {
2899 - $dataset['centeredSlidesSettings'] = $carousel_settings['centeredSlidesSettings'];
2900 - }
2901 -
2902 3280 // Pagination.
2903 3281 $pagination = $carousel_settings['pagination'] ?? [];
2904 3282 $dataset['pagination'] = $pagination['enable'] ?? false;
2905 3283 if ( $dataset['pagination'] ) {
@@ -2909,8 +3287,20 @@
2909 3287 // Navigation.
2910 3288 $navigation = $carousel_settings['navigation'] ?? [];
2911 3289 $dataset['navigation'] = $navigation['enable'] ?? false;
2912 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 + }
2913 3303 $dataset['navigationSettings'] = $navigation;
2914 3304 }
2915 3305
2916 3306 // Scrollbar.
@@ -2932,9 +3322,14 @@
2932 3322 if ( $thumbs['enable'] ?? false ) {
2933 3323 $dataset['thumbsSettings'] = $thumbs;
2934 3324 }
2935 3325
2936 - 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 ) );
2937 3332 // phpcs:enable
2938 3333 }
2939 3334
2940 3335 /**
@@ -2940,9 +3335,9 @@
2940 3335 /**
2941 3336 * Refine the slides per view
2942 3337 *
2943 3338 * @param mixed $raw_value
2944 - * @return void
3339 + * @return string|float 'auto' || number
2945 3340 */
2946 3341 private function carousel_refine_slides_per_view( $raw_value ) {
2947 3342 $value = $raw_value;
2948 3343 if ( is_array( $raw_value ) ) {
@@ -3280,9 +3675,9 @@
3280 3675 // Carousel settings.
3281 3676 $carousel_settings = $block['attrs']['boldblocks']['carousel'] ?? [];
3282 3677
3283 3678 // Dataset attribute.
3284 - $carousel_dataset = $this->build_carousel_settings( $carousel_settings );
3679 + $carousel_dataset = $this->build_carousel_settings( $carousel_settings, false, $block, $block_instance );
3285 3680 if ( $carousel_dataset ) {
3286 3681 $block_content = $this->add_data_to_block( $block_content, 'data-carousel-settings', esc_attr( $carousel_dataset ) );
3287 3682 }
3288 3683
@@ -3318,9 +3713,9 @@
3318 3713 return apply_filters( 'cbb_carousel_preloader', '<div class="cbb-loader"></div>', $block, $block_instance );
3319 3714 }
3320 3715
3321 3716 /**
3322 - * Migrate cbb class for old CBB blocks.
3717 + * Manage css class: migrate old class and adding custom css class.
3323 3718 *
3324 3719 * @param string $block_content
3325 3720 * @param array $block
3326 3721 * @param WP_Block $block_instance
@@ -3325,28 +3720,51 @@
3325 3720 * @param array $block
3326 3721 * @param WP_Block $block_instance
3327 3722 * @return string
3328 3723 */
3329 - public function migrate_cbb_class( $block_content, $block, $block_instance ) {
3724 + public function manage_cbb_class( $block_content, $block, $block_instance ) {
3330 3725 // Ignore admin side or empty content.
3331 3726 if ( is_admin() || ! $block_content ) {
3332 3727 return $block_content;
3333 3728 }
3334 3729
3335 - // There is no need to migrate.
3336 - $migrate_cbb_class = $block_instance->block_type->supports['migrateClass'] ?? '';
3337 - if ( ! $migrate_cbb_class ) {
3730 + // Bail if it is not a CBB block.
3731 + if ( empty( $block_instance->block_type->supports['cbb'] ?? '' ) ) {
3338 3732 return $block_content;
3339 3733 }
3340 3734
3341 - // Replace the class.
3342 - $block_reader = new \WP_HTML_Tag_Processor( $block_content );
3343 - if ( $block_reader->next_tag( [ 'class_name' => $migrate_cbb_class ] ) ) {
3344 - $new_class = str_ends_with( $migrate_cbb_class, 'custom' ) ? 'cbb-block' : 'cbb-block-parent';
3345 - $block_reader->set_attribute( 'class', str_replace( $migrate_cbb_class, $new_class, $block_reader->get_attribute( 'class' ) ) );
3735 + // Get custom classes.
3736 + $css_classes = trim( $block_instance->block_type->supports['CSSClasses'] ?? '' );
3346 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 +
3347 3765 // Render the updated HTML.
3348 - return $block_reader->get_updated_html();
3766 + return $p->get_updated_html();
3349 3767 }
3350 3768
3351 3769 return $block_content;
3352 3770 }
@@ -3351,8 +3769,44 @@
3351 3769 return $block_content;
3352 3770 }
3353 3771
3354 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 + /**
3355 3809 * Support block style for non-cbb blocks
3356 3810 *
3357 3811 * @param string $is_supported
3358 3812 * @param array $block
@@ -3563,8 +4017,9 @@
3563 4017 /**
3564 4018 * Add data attribute to block wrapper
3565 4019 *
3566 4020 * @param string $block_content
4021 + * @param string $name
3567 4022 * @param string $value
3568 4023 * @return string
3569 4024 */
3570 4025 public function add_data_to_block( $block_content, $name, $value ) {
@@ -3596,8 +4051,46 @@
3596 4051 return $block_content;
3597 4052 }
3598 4053
3599 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 + /**
3600 4093 * Detect a value is valid
3601 4094 *
3602 4095 * @param mixed $value
3603 4096 * @return boolean
@@ -3668,8 +4161,66 @@
3668 4161 return $value;
3669 4162 }
3670 4163
3671 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 + /**
3672 4223 * Refine custom JS and CSS value
3673 4224 *
3674 4225 * @param string $code
3675 4226 * @param array $tokens
@@ -3677,9 +4228,22 @@
3677 4228 * @return string
3678 4229 */
3679 4230 public function refine_custom_value( $code, $tokens = [], $type = 'CSS' ) {
3680 4231 // Remove tags.
3681 - // $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 + }
3682 4246
3683 4247 // Replace breakpoint tokens.
3684 4248 $breakpoints = $this->get_breakpoints();
3685 4249