$attributes Block attributes.
* @param string $content Block content.
* @return string
* @since 0.0.1
*/
public function render( $attributes, $content = '' ) {
unset( $content ); // Unused parameter.
if ( empty( $attributes ) ) {
return '';
}
// A custom form is required.
$form_id = isset( $attributes['formId'] ) ? absint( Helper::get_string_value( $attributes['formId'] ) ) : 0;
if ( ! $form_id ) {
// Show message only to users who can edit posts.
if ( current_user_can( 'edit_posts' ) ) {
return '
' . esc_html__( 'Please select a donation form in the block settings.', 'suredonation' ) . '
';
}
return '';
}
return $this->render_custom_form( $form_id, $attributes );
}
/**
* Render a custom donation form.
*
* @param int $form_id Form post ID.
* @param array $attributes Block attributes.
* @return string
* @since 0.0.1
*/
private function render_custom_form( $form_id, $attributes ) {
// Get the form post.
$form = get_post( $form_id );
if ( ! $form instanceof \WP_Post || Donation_Form::POST_TYPE !== $form->post_type ) {
return '' . esc_html__( 'Invalid form selected.', 'suredonation' ) . '
';
}
// Check if form is published.
if ( 'publish' !== $form->post_status ) {
return '' . esc_html__( 'This form is not available.', 'suredonation' ) . '
';
}
// Get the campaign ID from block attributes or form meta.
$campaign_id = isset( $attributes['campaignId'] ) ? absint( Helper::get_string_value( $attributes['campaignId'] ) ) : 0;
if ( ! $campaign_id ) {
$campaign_id = Donation_Form::get_form_campaign_id( $form_id );
}
// Validate campaign if set.
if ( $campaign_id ) {
$campaign = get_post( $campaign_id );
if ( ! $campaign instanceof \WP_Post || SUREDONATION_POST_TYPE !== $campaign->post_type ) {
return '' . esc_html__( 'Invalid campaign selected.', 'suredonation' ) . '
';
}
// Check campaign status.
$campaign_status = Helper::get_campaign_meta_value( $campaign_id, 'campaign_status', 'active' );
if ( 'paused' === $campaign_status || 'completed' === $campaign_status ) {
return '' . esc_html__( 'This campaign is not currently accepting donations.', 'suredonation' ) . '
';
}
}
// Enqueue frontend styles for custom form blocks.
// Note: Frontend JS is handled by the payment block (form-frontend.js).
wp_enqueue_style( 'suredonation-donation-form' );
// Enqueue form frontend script.
wp_enqueue_script( 'suredonation-form-frontend' );
/**
* Fires when a donation form is rendered on the frontend.
*
* Allows payment gateway extensions to enqueue their scripts (e.g., PayPal SDK).
*
* @param int $form_id The donation form post ID.
* @param string $form_content The form post content (blocks).
* @since 1.0.0
*/
do_action( 'suredonation_enqueue_form_frontend_scripts', $form_id, $form->post_content );
// Pass form settings to frontend (wp_localize_script handles script-context escaping).
wp_localize_script(
'suredonation-form-frontend',
'suredonationPayment',
Helper::get_form_payment_settings( $form_id )
);
// Expose the resolved validation messages so client-side validation
// mirrors the server's configured messages.
wp_localize_script(
'suredonation-form-frontend',
'suredonationValidationMessages',
\SureDonation\Inc\Field_Validation::get_resolved_validation_messages()
);
// Shared markup (also used by the [suredonation_form] shortcode).
$form_html = Form_Renderer::render( $form, $campaign_id );
return $this->maybe_anchor_form( $form_html );
}
/**
* Wrap the first donation form rendered on the page in the campaign form
* anchor.
*
* The Campaign Donate Button links to `#suredonation-donation-form`. Owning
* that anchor here (rather than relying on a wrapping group set up by the
* page seeder) keeps the scroll target alive wherever the form block is
* placed — including after a user removes and manually re-adds it. Only the
* first instance per request is anchored so the id stays unique on the page.
*
* "First" means first in PHP render order for the request, not first in the
* visible page. On the campaign page that is the only/intended form. The
* known trade-offs of that scope: a form rendered earlier in the request
* (e.g. a sidebar/widget form) would claim the anchor instead, and
* ServerSideRender emits the anchored wrapper into the block's editor
* preview too. Both are acceptable for the campaign-page use case.
*
* @param string $form_html Rendered form markup.
* @return string
* @since 1.1.0
*/
private function maybe_anchor_form( $form_html ) {
static $anchored = false;
if ( $anchored || '' === $form_html ) {
return $form_html;
}
$anchored = true;
return '' . $form_html . '
';
}
}