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 +377 -38 2.8.4trunk View file →
@@ -131,10 +131,19 @@
131 131
132 132 // Render settings for carousels.
133 133 add_filter( 'render_block', [ $this, 'render_carousel_settings' ], 30, 3 );
134 134
135 - // Migrate CBB class for old blocks.
136 - 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' ] );
137 146 }
138 147
139 148 /**
140 149 * Register custom setting fields
@@ -1100,8 +1109,9 @@
1100 1109
1101 1110 /**
1102 1111 * Build transform item: translate, rotate, scale, skew
1103 1112 *
1113 + * @param array $transform_item
1104 1114 * @return string
1105 1115 */
1106 1116 private function build_transform_item( $transform_item ) {
1107 1117 $style = '';
@@ -1392,8 +1402,9 @@
1392 1402 /**
1393 1403 * Build style for accordion border
1394 1404 *
1395 1405 * @param array $args
1406 + * @param array $style_array
1396 1407 * @return string
1397 1408 */
1398 1409 private function build_accordion_border_style( $args, &$style_array ) {
1399 1410 $value = $args['value'] ?? null;
@@ -1400,15 +1411,30 @@
1400 1411 if ( $value && is_array( $value ) ) {
1401 1412 if ( ( $value['top'] ?? false ) || ( $value['right'] ?? false ) || ( $value['bottom'] ?? false ) || ( $value['left'] ?? false ) ) {
1402 1413 $border_styles = $this->build_border_style( $args );
1403 1414 } else {
1404 - $border_styles = $this->build_border_style( [ 'value' => ['top' => $value, 'right' => $value, 'bottom' => $value, 'left' => $value] ] );
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 + );
1405 1425 }
1406 1426
1407 1427 if ( $border_styles ) {
1408 - $border_style = implode( ';', array_map( function ($attr_key) use ($border_styles) {
1409 - return "{$attr_key}:{$border_styles[ $attr_key ]}";
1410 - }, array_keys($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 + );
1411 1437
1412 1438 $style_array[] = "{$args['selector']}{{$border_style};}";
1413 1439 }
1414 1440 }
@@ -1419,8 +1445,9 @@
1419 1445 /**
1420 1446 * Build style for accordion radius
1421 1447 *
1422 1448 * @param array $args
1449 + * @param array $style_array
1423 1450 * @return string
1424 1451 */
1425 1452 private function build_accordion_radius_style( $args, &$style_array ) {
1426 1453 $value = $args['value'] ?? null;
@@ -1462,9 +1489,9 @@
1462 1489
1463 1490 /**
1464 1491 * Check whether current block has custom style or not.
1465 1492 *
1466 - * @param [type] $block
1493 + * @param array $block
1467 1494 * @return boolean
1468 1495 */
1469 1496 private function get_custom_css_style( $block ) {
1470 1497 // There is no boldblocks value.
@@ -1558,10 +1585,16 @@
1558 1585 return $this->build_thumbnail_background( $background, $block_content, $block, $block_instance );
1559 1586 } elseif ( $media_url ) {
1560 1587 return $this->build_custom_image_background( $background, $block_content, $block, $block_instance );
1561 1588 }
1562 - } elseif ( 'video' === $media_type && $media_url ) {
1563 - 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 + }
1564 1597 }
1565 1598
1566 1599 return $block_content;
1567 1600 }
@@ -1954,9 +1987,9 @@
1954 1987 if ( ! $name || ! $duration ) {
1955 1988 return $accumulator;
1956 1989 }
1957 1990
1958 - $delay = $animation['delay'] ?? 0;
1991 + $delay = empty( $animation['delay'] ) ? 0 : $animation['delay'];
1959 1992 $repeat = $animation['repeat'] ?? 1;
1960 1993 $infinite = $animation['infinite'] ?? false;
1961 1994 $timing_function = $animation['timingFunction'] ?? '';
1962 1995 $animation_name = isset( $animation_names[ $name ]['name'] ) ? $animation_names[ $name ]['name'] : $name;
@@ -1989,8 +2022,9 @@
1989 2022 if ( $animation_object['name'] && $animation_object['animation'] ) {
1990 2023 $animation_object['multipleTimes'] = $block['attrs']['boldblocks']['animateMultipleTimes'] ?? false;
1991 2024
1992 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' );
1993 2027 }
1994 2028
1995 2029 return $block_content;
1996 2030 }
@@ -2137,9 +2171,10 @@
2137 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 );
2138 2172
2139 2173 if ( $media['isPausable'] ?? false ) {
2140 2174 $block_class .= ' cbb-has-video-controls';
2141 - $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>';
2142 2177 }
2143 2178
2144 2179 if ( strpos( $block['innerHTML'] ?? '', $block_class ) === false ) {
2145 2180 $block_content = $this->add_class_to_block( $block_content, $block_class );
@@ -2150,8 +2185,123 @@
2150 2185 return $block_content;
2151 2186 }
2152 2187
2153 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 + /**
2154 2304 * Render block overlay
2155 2305 *
2156 2306 * @param string $block_content
2157 2307 * @param array $block
@@ -2213,9 +2363,9 @@
2213 2363
2214 2364 /**
2215 2365 * Render accordions block data
2216 2366 *
2217 - * @param array $parsed_block
2367 + * @param array $parsed_block
2218 2368 * @return array
2219 2369 */
2220 2370 public function render_accordion_block_data( $parsed_block ) {
2221 2371 $block_type = \WP_Block_Type_Registry::get_instance()->get_registered( $parsed_block['blockName'] );
@@ -2748,9 +2898,9 @@
2748 2898 // Build carousel settings.
2749 2899 $carousel_settings = $post_template->attributes['boldblocks']['carousel'] ?? [];
2750 2900 if ( $carousel_settings ) {
2751 2901 if ( ! $this->block_has_attribute( 'data-carousel-settings', $block_content ) ) {
2752 - $carousel_attr = $this->build_carousel_settings( $carousel_settings, true );
2902 + $carousel_attr = $this->build_carousel_settings( $carousel_settings, true, $post_template->parsed_block, $post_template );
2753 2903
2754 2904 if ( $carousel_attr ) {
2755 2905 $block_content = $this->add_data_to_block( $block_content, 'data-carousel-settings', esc_attr( $carousel_attr ) );
2756 2906 }
@@ -2923,13 +3073,15 @@
2923 3073
2924 3074 /**
2925 3075 * Build a json string of carousel settings
2926 3076 *
2927 - * @param array $carousel_settings
2928 - * @param boolean $exclude_li_role
3077 + * @param array $carousel_settings
3078 + * @param boolean $exclude_li_role
3079 + * @param array $parsed_block
3080 + * @param WP_Block $block_instance
2929 3081 * @return string
2930 3082 */
2931 - private function build_carousel_settings( $carousel_settings, $exclude_li_role = false ) {
3083 + private function build_carousel_settings( $carousel_settings, $exclude_li_role = false, $parsed_block = null, $block_instance = null ) {
2932 3084 // phpcs:disable
2933 3085 $dataset = [];
2934 3086
2935 3087 // Speed.
@@ -3023,8 +3175,14 @@
3023 3175
3024 3176 if ( in_array($effect, $slidesPerViewDependencies, true ) ) {
3025 3177 $dataset['slidesPerView'] = 1;
3026 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 +
3027 3185 $slidesPerView = $this->carousel_refine_slides_per_view( $carousel_settings['slidesPerView'] ?? 1 );
3028 3186 $slidesPerGroup = absint( $carousel_settings['slidesPerGroup'] ?? 0 );
3029 3187
3030 3188 if ( $slidesPerView ) {
@@ -3083,9 +3241,9 @@
3083 3241 $dataset['slidesPerView'] = $first_breakpoint['slidesPerView'];
3084 3242 if ($dataset['slidesPerView'] > 1 && ($first_breakpoint['slidesPerGroup'] ?? 0)) {
3085 3243 $dataset['slidesPerGroup'] = $first_breakpoint['slidesPerGroup'];
3086 3244 }
3087 - if ( $first_breakpoint['spaceBetween'] ) {
3245 + if ( ! empty( $first_breakpoint['spaceBetween'] ) ) {
3088 3246 $dataset['spaceBetween'] = $first_breakpoint['spaceBetween'];
3089 3247 }
3090 3248
3091 3249 // Set the breakpoints.
@@ -3109,17 +3267,17 @@
3109 3267 'slidesPerGroup' => $slidesPerGroup ? $slidesPerGroup : 1,
3110 3268 ],
3111 3269 ];
3112 3270 }
3271 +
3272 + // Reset centeredSlides
3273 + if ( ! ( $slidesPerView > 1 || 'auto' === $slidesPerView ) ) {
3274 + unset( $dataset['centeredSlides'] );
3275 + unset( $dataset['centeredSlidesSettings'] );
3276 + }
3113 3277 }
3114 3278 }
3115 3279
3116 - // centeredSlides.
3117 - $dataset['centeredSlides'] = $carousel_settings['centeredSlides'] ?? false;
3118 - if ($dataset['centeredSlides'] && $effect === 'slide' && ($carousel_settings['centeredSlidesSettings']['enable'] ?? '')) {
3119 - $dataset['centeredSlidesSettings'] = $carousel_settings['centeredSlidesSettings'];
3120 - }
3121 -
3122 3280 // Pagination.
3123 3281 $pagination = $carousel_settings['pagination'] ?? [];
3124 3282 $dataset['pagination'] = $pagination['enable'] ?? false;
3125 3283 if ( $dataset['pagination'] ) {
@@ -3129,8 +3287,20 @@
3129 3287 // Navigation.
3130 3288 $navigation = $carousel_settings['navigation'] ?? [];
3131 3289 $dataset['navigation'] = $navigation['enable'] ?? false;
3132 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 + }
3133 3303 $dataset['navigationSettings'] = $navigation;
3134 3304 }
3135 3305
3136 3306 // Scrollbar.
@@ -3157,9 +3327,9 @@
3157 3327 if ( $exclude_li_role ) {
3158 3328 $dataset['a11y'] = ['slideRole' => '', 'slideLabelMessage' => ''];
3159 3329 }
3160 3330
3161 - return wp_json_encode( $dataset );
3331 + return wp_json_encode( apply_filters( 'cbb_get_carousel_settings', $dataset, $parsed_block, $block_instance ) );
3162 3332 // phpcs:enable
3163 3333 }
3164 3334
3165 3335 /**
@@ -3165,9 +3335,9 @@
3165 3335 /**
3166 3336 * Refine the slides per view
3167 3337 *
3168 3338 * @param mixed $raw_value
3169 - * @return void
3339 + * @return string|float 'auto' || number
3170 3340 */
3171 3341 private function carousel_refine_slides_per_view( $raw_value ) {
3172 3342 $value = $raw_value;
3173 3343 if ( is_array( $raw_value ) ) {
@@ -3505,9 +3675,9 @@
3505 3675 // Carousel settings.
3506 3676 $carousel_settings = $block['attrs']['boldblocks']['carousel'] ?? [];
3507 3677
3508 3678 // Dataset attribute.
3509 - $carousel_dataset = $this->build_carousel_settings( $carousel_settings );
3679 + $carousel_dataset = $this->build_carousel_settings( $carousel_settings, false, $block, $block_instance );
3510 3680 if ( $carousel_dataset ) {
3511 3681 $block_content = $this->add_data_to_block( $block_content, 'data-carousel-settings', esc_attr( $carousel_dataset ) );
3512 3682 }
3513 3683
@@ -3543,9 +3713,9 @@
3543 3713 return apply_filters( 'cbb_carousel_preloader', '<div class="cbb-loader"></div>', $block, $block_instance );
3544 3714 }
3545 3715
3546 3716 /**
3547 - * Migrate cbb class for old CBB blocks.
3717 + * Manage css class: migrate old class and adding custom css class.
3548 3718 *
3549 3719 * @param string $block_content
3550 3720 * @param array $block
3551 3721 * @param WP_Block $block_instance
@@ -3550,28 +3720,51 @@
3550 3720 * @param array $block
3551 3721 * @param WP_Block $block_instance
3552 3722 * @return string
3553 3723 */
3554 - public function migrate_cbb_class( $block_content, $block, $block_instance ) {
3724 + public function manage_cbb_class( $block_content, $block, $block_instance ) {
3555 3725 // Ignore admin side or empty content.
3556 3726 if ( is_admin() || ! $block_content ) {
3557 3727 return $block_content;
3558 3728 }
3559 3729
3560 - // There is no need to migrate.
3561 - $migrate_cbb_class = $block_instance->block_type->supports['migrateClass'] ?? '';
3562 - if ( ! $migrate_cbb_class ) {
3730 + // Bail if it is not a CBB block.
3731 + if ( empty( $block_instance->block_type->supports['cbb'] ?? '' ) ) {
3563 3732 return $block_content;
3564 3733 }
3565 3734
3566 - // Replace the class.
3567 - $block_reader = new \WP_HTML_Tag_Processor( $block_content );
3568 - if ( $block_reader->next_tag( [ 'class_name' => $migrate_cbb_class ] ) ) {
3569 - $new_class = str_ends_with( $migrate_cbb_class, 'custom' ) ? 'cbb-block' : 'cbb-block-parent';
3570 - $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'] ?? '' );
3571 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 +
3572 3765 // Render the updated HTML.
3573 - return $block_reader->get_updated_html();
3766 + return $p->get_updated_html();
3574 3767 }
3575 3768
3576 3769 return $block_content;
3577 3770 }
@@ -3576,8 +3769,44 @@
3576 3769 return $block_content;
3577 3770 }
3578 3771
3579 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 + /**
3580 3809 * Support block style for non-cbb blocks
3581 3810 *
3582 3811 * @param string $is_supported
3583 3812 * @param array $block
@@ -3788,8 +4017,9 @@
3788 4017 /**
3789 4018 * Add data attribute to block wrapper
3790 4019 *
3791 4020 * @param string $block_content
4021 + * @param string $name
3792 4022 * @param string $value
3793 4023 * @return string
3794 4024 */
3795 4025 public function add_data_to_block( $block_content, $name, $value ) {
@@ -3821,8 +4051,46 @@
3821 4051 return $block_content;
3822 4052 }
3823 4053
3824 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 + /**
3825 4093 * Detect a value is valid
3826 4094 *
3827 4095 * @param mixed $value
3828 4096 * @return boolean
@@ -3893,8 +4161,66 @@
3893 4161 return $value;
3894 4162 }
3895 4163
3896 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 + /**
3897 4223 * Refine custom JS and CSS value
3898 4224 *
3899 4225 * @param string $code
3900 4226 * @param array $tokens
@@ -3902,9 +4228,22 @@
3902 4228 * @return string
3903 4229 */
3904 4230 public function refine_custom_value( $code, $tokens = [], $type = 'CSS' ) {
3905 4231 // Remove tags.
3906 - // $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 + }
3907 4246
3908 4247 // Replace breakpoint tokens.
3909 4248 $breakpoints = $this->get_breakpoints();
3910 4249