我正在尝试将我为 WordPress 创建的静态古腾堡块转换为动态块。我已经寻找其他解决方案,但没有成功。这个问题 (将使用 @wordpress/create-block 创建的静态 gutenberg 块转换为使用 PHP 注册的动态块的正确方法是什么? https://stackoverflow.com/questions/70944838/what-is-the-correct-way-to-convert-a-static-gutenberg-block-created-using-wordp/71317657)与我正在寻找的非常相似,但我的静态块不是使用 @wordpress/create-block 制作的,在完成所有建议的步骤后,我看到了似乎是弃用问题的内容。
class-hello-tools-custom-blocks.php(注册块)
静态版本如下所示:
register_block_type( 'hello-tools/custom-cta', array(
'style' => $this->plugin_slug . '-public',
'editor_style' => 'hello-gutenberg-admin',
'editor_script' => $this->plugin_slug . '-custom-blocks',
) );
动态变化如下:
register_block_type( __DIR__ . '/src/js/blocks/custom-cta/custom-cta-block.json', array(
'style' => $this->plugin_slug . '-public',
'editor_style' => 'hello-gutenberg-admin',
'editor_script' => $this->plugin_slug . '-custom-blocks',
'render_callback' => array( $this, 'hello_tools_custom_cta_callback' )
) );
同一插件文件中的回调函数如下所示:
public function hello_tools_custom_cta_callback( $attributes, $content ) {
ob_start();
require plugin_dir_path( __DIR__ ) . 'includes/partials/block-callbacks/hello-custom-cta-callback.php';
$output = ob_get_contents();
ob_end_clean();
return $output;
}
custom-cta-block.json(定义块属性)
正如参考文献中所提到的register_block_type()
上面,这是 block.json 文件。
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 2,
"name": "hello-tools/custom-cta",
"title": "Custom CTA",
"category": "hello-blocks",
"icon": "align-center",
"description": "Create call to action element with photo image, text, and button link",
"keywords": [ "call", "action" ],
"version": "1.0.1",
"textdomain": "hello-tools",
"editorScript": "hello-tools-custom-blocks",
"editorStyle": "hello-gutenberg-admin",
"style": "hello-tools-public",
"attributes": {
"switchOrientation": {
"type": "boolean",
"default": false
},
"imageID": {
"type": "integer"
},
"imageURL": {
"type": "string"
},
"imageALT": {
"type": "string",
"default": ""
},
"imageBgColor": {
"type": "string",
"default": "orange"
},
"hideCTAImageOnMobile": {
"type": "boolean",
"default": false
},
"renderWithoutImageBg": {
"type": "boolean",
"default": false
},
"imageRatio": {
"type": "string",
"default": "golden"
},
"textBgColor": {
"type": "string",
"default": "faint-gray"
},
"hasIcon": {
"type": "boolean",
"default": false
},
"iconID": {
"type": "integer"
},
"iconURL": {
"type": "string"
},
"iconALT": {
"type": "string",
"default": ""
},
"hasSuperHeading": {
"type": "boolean",
"default": false
},
"superHeading": {
"type": "string"
},
"headline": {
"type": "string"
},
"blurb": {
"type": "string"
},
"ctaButtonText": {
"type": "string"
},
"ctaLinkURL": {
"type": "string"
},
"textClasses": {
"type": "string",
"default": "hello-custom-cta__description flex-fill hello-flex hello-flex--fixed-size align-self-stretch align-items-center p-3 p-sm-4 p-lg-5"
},
"blockClasses": {
"type": "string",
"default": "hello-block--fullwidth px-sm-3 mb-5 mb-md-6 mb-lg-7 mb-7"
}
},
"supports": {
"multiple": true
},
"example": {
"attributes": {
"headline": "This is a custom CTA!",
"blurb": "<p>Add a catchy headline and body text to capture people's attention</p>",
"ctaButtonText": "Click here"
}
}
}
定制-cta.js
原本的save()
函数已被删除,以告诉 Wordpres 它应该动态呈现,但以前的弃用版本已保持不变。
import { registerBlockType } from '@wordpress/blocks';
import { __ } from '@wordpress/i18n';
import { InspectorControls, MediaUpload, MediaUploadCheck, RichText, URLInput } from '@wordpress/blockEditor';
import { Button, PanelBody, TextControl, TextareaControl, ToggleControl, SelectControl, IconButton } from '@wordpress/components';
import { omit } from 'lodash';
import settings from './custom-cta-block.json';
const blockAttributes = settings.attributes;
registerBlockType( settings, {
edit: ( props ) => {
const { attributes: { switchOrientation, imageID, imageURL, imageALT, imageBgColor, imageRatio, hideCTAImageOnMobile, textBgColor, hasIcon, iconID, iconURL, iconALT, hasSuperHeading, superHeading, headline, blurb, ctaButtonText, ctaLinkURL, blockClasses, textClasses, renderWithoutImageBg }, setAttributes, className, isSelected } = props;
const ctaButtonPlaceholderText = __( "Button text", "hello-tools" );
const flexClasses = `hello-flex hello-wrap--content align-items-center ${switchOrientation && 'switch-orientation'}`;
const onImageSelect = ( imageObj ) => {
// use the medium_large image size of it exists, otherwise use the full-size image
const imageObjURL = ( imageObj.sizes.medium_large ) ? imageObj.sizes.medium_large.url : imageObj.sizes.full.url;
// set the image attributes
setAttributes( { imageID: imageObj.id } );
setAttributes( { imageURL: imageObjURL } );
setAttributes( { imageALT: imageObj.alt } );
}
const onImageBgChange = ( value ) => {
setAttributes( { imageBgColor: value } );
setAttributes( { renderWithoutImageBg: ( value == 'none') ? true : false } );
}
const onIconSelectorChange = ( value ) => {
setAttributes( { hasIcon: value } );
// clear icon image attributes if show icon toggle is changed to false
if ( !value ) {
setAttributes( { iconID: '' } );
setAttributes( { iconURL: '' } );
setAttributes( { iconALT: '' } );
}
}
const onIconSelect = ( imageObj ) => {
setAttributes( { iconID: imageObj.id } );
if ( imageObj.sizes.thumbnail ) {
setAttributes( { iconURL: imageObj.sizes.thumbnail.url } );
} else {
setAttributes( { iconURL: imageObj.sizes.full.url } );
}
setAttributes( { iconALT: imageObj.alt } );
}
return (
<div className="hello-custom-cta-editor p-0 mb-0">
<InspectorControls>
<PanelBody
title={ __( 'Image settings', 'hello-tools' ) }
initialOpen={ true }
>
{ imageID && __( 'You can change the CTA image here', 'hello-tools' ) }
{ imageID && (
<div style={{ margin: '0 0 15px 0', }}>
<MediaUploadCheck>
<MediaUpload
onSelect={ onImageSelect }
allowedTypes="image"
value={ imageID }
render={ ( { open } ) => (
<Button className="hello-components-button" onClick={ open }>
{ __( 'Choose new image', 'hello-tools' ) }
</Button>
) }
/>
</MediaUploadCheck>
</div>
) }
<SelectControl
label={ __( 'Image background color', 'hello-tools' ) }
value={ imageBgColor }
onChange={ onImageBgChange }
options={ [
{ value: 'orange', label: __( 'Orange', 'hello-tools' ) },
{ value: 'blue', label: __( 'Blue', 'hello-tools' ) },
{ value: 'powder-blue', label: __( 'Light blue', 'hello-tools' ) },
{ value: 'faint-gray', label: __( 'Gray', 'hello-tools' ) },
{ value: 'green', label: __( 'Green', 'hello-tools' ) },
{ value: 'none', label: __( 'No background color', 'hello-tools' ) },
] }
/>
<SelectControl
label={ __( 'Image ratio', 'hello-tools' ) }
value={ imageRatio }
onChange={ value => setAttributes( { imageRatio: value } ) }
options={ [
{ value: 'golden', label: '16:9' },
{ value: 'half', label: '1:1' },
] }
/>
<ToggleControl
label={ __( 'Hide image on mobile phones?', 'hello-tools' ) }
checked={ hideCTAImageOnMobile }
onChange={ value => setAttributes( { hideCTAImageOnMobile: value } ) }
/>
<ToggleControl
label={ __( 'Move photo to the right of the text?', 'hello-tools' ) }
help='The default is to have the photo to the left of the text, but you can switch it here.'
checked={ switchOrientation }
onChange={ value => setAttributes( { switchOrientation: value } ) }
/>
</PanelBody>
<PanelBody
title={ __( 'Text settings', 'hello-tools' ) }
initialOpen={ true }
>
<SelectControl
label={ __( 'Background color', 'hello-tools' ) }
value={ textBgColor }
onChange={ value => setAttributes( { textBgColor: value } ) }
options={ [
{ value: 'faint-gray', label: __( 'Gray', 'hello-tools' ) },
{ value: 'white', label: __( 'White', 'hello-tools' ) },
] }
/>
<ToggleControl
label={ __( 'Show icon above the text?', 'hello-tools' ) }
checked={ hasIcon }
onChange={ onIconSelectorChange }
/>
{ hasIcon && (
<div style={{ margin: '-15px 0 15px 0', }}>
<MediaUploadCheck>
<MediaUpload
onSelect={ onIconSelect }
allowedTypes="image"
value={ iconID }
render={ ( { open } ) => (
<Button className="hello-components-button" onClick={ open }>
{ !iconID && __( 'Choose icon image', 'hello-tools' ) }
{ iconID && __( 'Replace icon image', 'hello-tools' ) }
</Button>
) }
/>
</MediaUploadCheck>
</div>
) }
<ToggleControl
label={ __( 'Show super heading?', 'hello-tools' ) }
checked={ hasSuperHeading }
onChange={ value => setAttributes( { hasSuperHeading: value } ) }
/>
</PanelBody>
</InspectorControls>
<div className={ flexClasses }>
{ renderWithoutImageBg && imageURL && (
<div className={ `hello-custom-cta__image-wrap ${imageRatio} no-padding hello-flex align-self-stretch align-items-center` }>
<div className="objectfit__container w-100 h-100">
<img className="hello-custom-cta__image objectfit__image w-100 h-100" src={ imageURL } alt={ imageALT } />
</div>
</div>
) }
{ !renderWithoutImageBg && imageURL && (
<div className={ `hello-custom-cta__image-wrap ${imageRatio} hello-bg--${imageBgColor} hello-flex align-self-stretch align-items-center p-sm-4 p-lg-5` }>
<img className="hello-custom-cta__image" src={ imageURL } alt={ imageALT } />
</div>
) }
{ !renderWithoutImageBg && !imageID && (
<MediaUploadCheck>
<MediaUpload
onSelect={ onImageSelect }
allowedTypes="image"
value={ imageID }
render={ ( { open } ) => (
<Button onClick={ open }>
{ __( 'Choose Image', 'hello-tools' ) }
</Button>
) }
/>
</MediaUploadCheck>
) }
<div className={ `${textClasses} hello-bg--${textBgColor}` }>
<div>
{ hasIcon && iconID && (
<div className="flex-item--fullwidth">
<img className="hello-custom-cta__icon circle d-block mx-auto" src={ iconURL } alt={ iconALT } />
</div>
) }
{ hasSuperHeading && (
<TextControl
className="hello-custom-cta__superheading hello-interactive-input"
value={ superHeading }
onChange={ value => setAttributes( { superHeading: value } ) }
placeholder={ __( 'Add super heading here (optional)', 'hello-tools' ) }
allowedFormats={ [] }
/>
) }
<TextareaControl
className="hello-custom-cta__headline w-100 hello-interactive-input font-weight-bold"
value={ headline }
onChange={ value => setAttributes( { headline: value } ) }
placeholder={ __( 'Add CTA headline here', 'hello-tools' ) }
allowedFormats={ [] }
rows="2"
/>
{ ( isSelected || ( blurb && blurb != '<p></p>' ) ) && (
<div className="w-100">
<RichText
tagName="div"
multiline="p"
className="hello-custom-cta__blurb font-weight-light mt-0 w-100"
translate-name="blurb"
onChange={ value => setAttributes( { blurb: value } ) }
placeholder={ __( 'Subtext goes here (optional)', 'hello-tools' ) }
value={ blurb }
allowedFormats={ [ 'core/bold', 'core/italic' ] }
focusOnInsert={ false }
/>
</div>
) }
<div className="hello-tools-editable-btn-text-wrapper">
<div className="hello-inline-flex">
<RichText
tagName="div"
placeholder={ ctaButtonPlaceholderText }
onFocus={ (e) => e.target.placeholder = "" }
value={ ctaButtonText }
onChange={ value => setAttributes( { ctaButtonText: value } ) }
className="hello-tools-editable-btn-text mt-4 hello-btn hello-btn--blue"
withoutInteractiveFormatting
allowedFormats={ [] }
/>
</div>
</div>
</div>
</div>
</div>
{ isSelected && (
<div className="hello-block-post-selector">
<form
key={ "form-link" }
onSubmit={ ( event ) => event.preventDefault() }
className="hello-flex hello-bg--white">
<URLInput
value={ ctaLinkURL }
onChange={ value => setAttributes( { ctaLinkURL: value } ) }
autoFocus={ false }
/>
<IconButton
className="hello-bg--white"
icon={ "editor-break" }
label={ __( "Apply", "hello-tools" ) }
type={ "submit" }
/>
</form>
</div>
) }
</div>
);
},
save() {
return null; // Data is rendered via PHP callback
},
deprecated: [
{
attributes: { ...blockAttributes },
save( { attributes } ) {
const { switchOrientation, imageID, imageURL, imageALT, imageBgColor, imageRatio, hideCTAImageOnMobile, textBgColor, hasIcon, iconID, iconURL, iconALT, hasSuperHeading, superHeading, headline, blurb, ctaButtonText, ctaLinkURL, blockClasses, textClasses, renderWithoutImageBg } = attributes;
const flexClasses = `hello-flex hello-wrap--content align-items-center ${switchOrientation && 'switch-orientation'}`;
const superHeadingExtraClasses = ( hasIcon && iconID ) ? 'mt-2 ' : '';
const hideImageOnPhones = ( hideCTAImageOnMobile ) ? ' d-upto-md-none' : '';
return (
<div className={ blockClasses }>
<div className="hello-wrap--narrow">
<div className={ flexClasses }
>
{ renderWithoutImageBg && imageURL && (
<div className={ `hello-custom-cta__image-wrap ${imageRatio} hello-flex align-self-stretch align-items-center${hideImageOnPhones}` }>
<div className="objectfit__container w-100 h-100">
<img className="hello-custom-cta__image objectfit__image w-100 h-100" src={ imageURL } alt={ imageALT } />
</div>
</div>
) }
{ !renderWithoutImageBg && imageURL && (
<div className={ `hello-custom-cta__image-wrap ${imageRatio} hello-bg--${imageBgColor} hello-flex align-self-stretch align-items-center p-sm-4 p-lg-5${hideImageOnPhones}` }>
<img className="hello-custom-cta__image" src={ imageURL } alt={ imageALT } />
</div>
) }
<div className={ `${textClasses} hello-bg--${textBgColor}` }>
<div>
{ iconID && (
<div className="flex-item--fullwidth">
<img className="hello-custom-cta__icon circle d-block mx-auto" src={ iconURL } alt={ iconALT } />
</div>
) }
{ hasSuperHeading && superHeading && (
<div translate-name="custom-cta__superheading" className={`${superHeadingExtraClasses}hello-custom-cta__superheading hello-1rem-text font-weight-bold text-uppercase hello-text--orange`}>
<span>{ superHeading }</span>
</div>
) }
<div translate-name="custom-cta__headline" className="hello-custom-cta__headline h2 font-weight-bold my-2 my-md-3">
{ headline }
</div>
{ ( blurb && blurb != '<p></p>' ) && (
<RichText.Content
className="hello-custom-cta__blurb"
tagName="div"
value={ blurb }
/>
) }
{ ctaButtonText && ctaLinkURL && (
<a href={ ctaLinkURL } translate-name="custom-cta__btn-link" className="hello-custom-cta__btn-link mb-3 mb-md-0 btn hello-btn hello-btn--blue">
{ ctaButtonText }
</a>
) }
</div>
</div>
</div>
</div>
</div>
)
}
},
{
attributes: {
...blockAttributes,
blockClasses: {
type: 'string',
default: 'hello-block--fullwidth px-sm-3 mb-7',
},
},
save( { attributes } ) {
const { switchOrientation, imageID, imageURL, imageALT, imageBgColor, imageRatio, hideCTAImageOnMobile, textBgColor, hasIcon, iconID, iconURL, iconALT, hasSuperHeading, superHeading, headline, blurb, ctaButtonText, ctaLinkURL, blockClasses, textClasses, renderWithoutImageBg } = attributes;
const flexClasses = `hello-flex hello-wrap--content align-items-center ${switchOrientation && 'switch-orientation'}`;
const superHeadingExtraClasses = ( hasIcon && iconID ) ? 'mt-2 ' : '';
const hideImageOnPhones = ( hideCTAImageOnMobile ) ? ' d-upto-md-none' : '';
return (
<div className={ blockClasses }>
<div className="hello-wrap--narrow">
<div className={ flexClasses }
>
{ renderWithoutImageBg && imageURL && (
<div className={ `hello-custom-cta__image-wrap ${imageRatio} hello-flex align-self-stretch align-items-center${hideImageOnPhones}` }>
<div className="objectfit__container w-100 h-100">
<img className="hello-custom-cta__image objectfit__image w-100 h-100" src={ imageURL } alt={ imageALT } />
</div>
</div>
) }
{ !renderWithoutImageBg && imageURL && (
<div className={ `hello-custom-cta__image-wrap ${imageRatio} hello-bg--${imageBgColor} hello-flex align-self-stretch align-items-center p-sm-4 p-lg-5${hideImageOnPhones}` }>
<img className="hello-custom-cta__image" src={ imageURL } alt={ imageALT } />
</div>
) }
<div className={ `${textClasses} hello-bg--${textBgColor}` }>
<div>
{ iconID && (
<div className="flex-item--fullwidth">
<img className="hello-custom-cta__icon circle d-block mx-auto" src={ iconURL } alt={ iconALT } />
</div>
) }
{ hasSuperHeading && superHeading && (
<div translate-name="custom-cta__superheading" className={`${superHeadingExtraClasses}hello-custom-cta__superheading hello-1rem-text font-weight-bold text-uppercase hello-text--orange`}>
<span>{ superHeading }</span>
</div>
) }
<div translate-name="custom-cta__headline" className="hello-custom-cta__headline h2 font-weight-bold my-2 my-md-3">
{ headline }
</div>
{ ( blurb && blurb != '<p></p>' ) && (
<RichText.Content
className="hello-custom-cta__blurb"
tagName="div"
value={ blurb }
/>
) }
{ ctaButtonText && ctaLinkURL && (
<a href={ ctaLinkURL } translate-name="custom-cta__btn-link" className="hello-custom-cta__btn-link mb-3 mb-md-0 btn hello-btn hello-btn--blue no-external-icon same-window">
{ ctaButtonText }
</a>
) }
</div>
</div>
</div>
</div>
</div>
)
}
}
],
} );
hello-custom-cta-callback.php(处理实际的 PHP 渲染,在回调函数中引用)
<?php
global $post;
$plugin_slug = hello_get_plugin_slug();
$is_admin = ( is_admin() ) ? true : false;
$switchOrientation = ( array_key_exists( 'switchOrientation', $attributes ) ) ? $attributes['switchOrientation'] : false;
$imageID = ( array_key_exists( 'imageID', $attributes ) ) ? $attributes['imageID'] : '';
$imageURL = ( array_key_exists( 'imageURL', $attributes ) ) ? $attributes['imageURL'] : '';
$imageALT = ( array_key_exists( 'imageALT', $attributes ) ) ? $attributes['imageALT'] : '';
$imageBgColor = ( array_key_exists( 'imageBgColor', $attributes ) ) ? $attributes['imageBgColor'] : 'orange';
$hideCTAImageOnMobile = ( array_key_exists( 'hideCTAImageOnMobile', $attributes ) ) ? $attributes['hideCTAImageOnMobile'] : false;
$renderWithoutImageBg = ( array_key_exists( 'renderWithoutImageBg', $attributes ) ) ? $attributes['renderWithoutImageBg'] : false;
$imageRatio = ( array_key_exists( 'imageRatio', $attributes ) ) ? $attributes['imageRatio'] : 'golden';
$textBgColor = ( array_key_exists( 'textBgColor', $attributes ) ) ? $attributes['textBgColor'] : 'faint-gray';
$hasIcon = ( array_key_exists( 'hasIcon', $attributes ) ) ? $attributes['hasIcon'] : false;
$iconID = ( array_key_exists( 'iconID', $attributes ) ) ? $attributes['iconID'] : '';
$iconURL = ( array_key_exists( 'iconURL', $attributes ) ) ? $attributes['iconURL'] : '';
$iconALT = ( array_key_exists( 'iconALT', $attributes ) ) ? $attributes['iconALT'] : '';
$hasSuperHeading = ( array_key_exists( 'hasSuperHeading', $attributes ) ) ? $attributes['hasSuperHeading'] : false;
$superHeading = ( array_key_exists( 'superHeading', $attributes ) ) ? $attributes['superHeading'] : '';
$headline = ( array_key_exists( 'headline', $attributes ) ) ? $attributes['headline'] : '';
$blurb = ( array_key_exists( 'blurb', $attributes ) ) ? $attributes['blurb'] : '';
$ctaButtonText = ( array_key_exists( 'ctaButtonText', $attributes ) ) ? $attributes['ctaButtonText'] : '';
$ctaLinkURL = ( array_key_exists( 'ctaLinkURL', $attributes ) ) ? $attributes['ctaLinkURL'] : '';
$textClasses = ( array_key_exists( 'textClasses', $attributes ) ) ? $attributes['textClasses'] : 'hello-custom-cta__description flex-fill hello-flex hello-flex--fixed-size align-self-stretch align-items-center p-3 p-sm-4 p-lg-5';
$blockClasses = ( array_key_exists( 'blockClasses', $attributes ) ) ? $attributes['blockClasses'] : 'hello-block--fullwidth px-sm-3 mb-5 mb-md-6 mb-lg-7 mb-7';
$flexClasses = 'hello-flex hello-wrap--content align-items-center';
$flexClasses = ($switchOrientation) ? $flexClasses . ' switch-orientation' : $flexClasses;
$superHeadingExtraClasses = ( $hasIcon && $iconID ) ? 'mt-2 ' : '';
$hideImageOnPhones = ( $hideCTAImageOnMobile ) ? ' d-upto-md-none' : '';
$is_rtl = ( apply_filters( 'wpml_is_rtl', NULL) ) ? true : false;
if ( is_object( $post ) && get_post_meta( $post->ID, '_icl_lang_duplicate_of', true ) ) {
$is_rtl = false; // if this is on a duplicate page, keep the content LTR
}
echo '<pre>'.print_r($attributes,true).'</pre>';
?>
<div class="<?= $blockClasses; ?>">
<div class="hello-wrap--narrow">
<div class="<?= $flexClasses; ?>">
<?php if ($renderWithoutImageBg && $imageURL) : ?>
<div class="hello-custom-cta__image-wrap <?= $imageRatio; ?> hello-flex align-self-stretch align-items-center<?= $hideImageOnPhones; ?>">
<div class="objectfit__container w-100 h-100">
<img class="hello-custom-cta__image objectfit__image w-100 h-100" src="<?= $imageURL; ?>" alt="<?= $imageALT; ?>" />
</div>
</div>
<?php elseif ( !$renderWithoutImageBg && $imageURL ) : ?>
<div class="hello-custom-cta__image-wrap <?= $imageRatio; ?> hello-bg--<?= $imageBgColor; ?> hello-flex align-self-stretch align-items-center p-sm-4 p-lg-5<?= $hideImageOnPhones; ?>">
<img class="hello-custom-cta__image" src="<?= $imageURL; ?>" alt="<?= $imageALT; ?>" />
</div>
<?php endif; ?>
<div class="<?= $textClasses; ?> hello-bg--<?= $textBgColor; ?>">
<div>
<?php if ($iconID) : ?>
<div class="flex-item--fullwidth">
<img class="hello-custom-cta__icon circle d-block mx-auto" src="<?= $iconURL; ?>" alt="<?= $iconALT; ?>" />
</div>
<?php endif; ?>
<?php if ($hasSuperHeading && $superHeading) : ?>
<div translate-name="custom-cta__superheading" class="<?= $superHeadingExtraClasses; ?>hello-custom-cta__superheading hello-1rem-text font-weight-bold text-uppercase hello-text--orange">
<span><?= $superHeading; ?></span>
</div>
<?php endif; ?>
<div translate-name="custom-cta__headline" class="hello-custom-cta__headline h2 font-weight-bold my-2 my-md-3">
<?= $headline; ?>
</div>
<?php if ($blurb && $blurb !== '<p></p>') : ?>
<div class="hello-custom-cta__blurb"><?= $blurb; ?></div>
<?php endif; ?>
<?php if ($ctaButtonText && $ctaLinkURL) : ?>
<a href="<?= $ctaLinkURL; ?>" translate-name="custom-cta__btn-link" class="hello-custom-cta__btn-link mb-3 mb-md-0 btn hello-btn hello-btn--blue"><?= $ctaButtonText; ?></a>
<?php endif; ?>
</div>
</div>
</div>
</div>
</div>