with ', $block_content ); // Return the button if no spam protection provider is active. $spam_protection = new ConvertKit_Spam_Protection(); $provider = $spam_protection->get_active_provider(); if ( ! $provider ) { return $block_content; } // Enqueue the spam protection provider's JS. $provider->enqueue_scripts(); // Parse the button's DOM. $parser = new ConvertKit_HTML_Parser( $block_content ); $button = $parser->xpath->query( '//button' )->item( 0 ); // Attach the spam protection provider's attributes/elements to the form/button as necessary. // $button is narrowed from DOMNode to DOMElement by the //button xpath expression above. $provider->attach_to_form_button_dom( $parser, $button, 'convertkit_form_builder' ); // @phpstan-ignore-line // Return button HTML. return $parser->get_body_html(); } /** * Wraps the block's content within a element, and adds hidden fields. * * @since 3.0.0 * * @param string $content Block content. * @param array $atts Block attributes. * @param int $post_id Post ID. * @return string */ private function add_form_to_block_content( $content, $atts, $post_id ) { // Load the content into the parser. $parser = new ConvertKit_HTML_Parser( $content ); // Get block container. $block_container = $parser->xpath->query( '//div[contains(@class, "wp-block-convertkit-form-builder")]' )->item( 0 ); // If no block container was found, return the original content. // This shouldn't happen, as the block editor supplies the container, but it's a safeguard. if ( ! $block_container ) { return $content; } // Create form element. $form = $parser->html->createElement( 'form' ); $form->setAttribute( 'action', esc_url( $this->get_current_url( $post_id ) ) ); $form->setAttribute( 'method', 'post' ); // Move form builder div contents into form. while ( $block_container->hasChildNodes() ) { $form->appendChild( $block_container->firstChild ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase } // Suffix field IDs and labels with the block's index from the second block onwards, // so IDs are unique when multiple blocks are on the same page. if ( $this->render_count > 1 ) { foreach ( $parser->xpath->query( './/*[starts-with(@id, "kit-form-builder-")]', $form ) as $element ) { $id = $element->getAttribute( 'id' ); // @phpstan-ignore-line $new_id = $id . '-' . $this->render_count; $element->setAttribute( 'id', $new_id ); // @phpstan-ignore-line foreach ( $parser->xpath->query( './/label[@for="' . $id . '"]', $form ) as $label ) { $label->setAttribute( 'for', $new_id ); // @phpstan-ignore-line } } } // Add subscribed message if required. if ( $this->subscriber_id ) { $subscribed_message = $parser->html->createElement( 'div' ); $subscribed_message->setAttribute( 'class', 'convertkit-form-builder-subscribed-message' ); $subscribed_message->appendChild( $parser->html->createTextNode( $atts['text_if_subscribed'] ) ); $form->insertBefore( $subscribed_message, $form->firstChild ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase } // Add error notice if the submission failed, and this is the submitted block. // If no block index was submitted (e.g. a cached page from an older version), display it on all blocks. if ( is_wp_error( $this->error ) && ( ! $this->submitted_block_index || $this->submitted_block_index === $this->render_count ) ) { $error_id = 'convertkit-form-builder-error-' . $this->render_count; $error_notice = $parser->html->createElement( 'div' ); $error_notice->setAttribute( 'id', $error_id ); $error_notice->setAttribute( 'class', 'convertkit-form-builder-notice convertkit-form-builder-notice-error' ); $error_notice->setAttribute( 'role', 'alert' ); $error_notice->setAttribute( 'tabindex', '-1' ); $error_notice->appendChild( $parser->html->createTextNode( $this->error->get_error_message() ) ); $form->insertBefore( $error_notice, $form->firstChild ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase // Focus the email field if it caused the error, so screen readers and // browsers move to it. Otherwise focus the notice, as the error isn't // specific to a field. // Query within the form, as it's not yet appended to the document. $email_field = $parser->xpath->query( './/input[@name="convertkit[email]"]', $form )->item( 0 ); if ( $email_field && $this->error->get_error_code() === 'convertkit_block_form_builder_invalid_email' ) { $email_field->setAttribute( 'aria-invalid', 'true' ); // @phpstan-ignore-line $email_field->setAttribute( 'aria-describedby', $error_id ); // @phpstan-ignore-line $email_field->setAttribute( 'autofocus', 'autofocus' ); // @phpstan-ignore-line } else { $error_notice->setAttribute( 'autofocus', 'autofocus' ); } } // Add hidden fields. $fields = array( 'convertkit[post_id]' => absint( $post_id ), 'convertkit[store_entries]' => $atts['store_entries'] ? '1' : '0', 'convertkit[redirect]' => esc_url( $atts['redirect'] ), 'convertkit[form_id]' => absint( $atts['form_id'] ), 'convertkit[tag_id]' => absint( $atts['tag_id'] ), 'convertkit[sequence_id]' => absint( $atts['sequence_id'] ), 'convertkit[block_index]' => absint( $this->render_count ), '_wpnonce' => wp_create_nonce( 'convertkit_block_form_builder' ), ); foreach ( $fields as $name => $value ) { $hidden = $parser->html->createElement( 'input' ); $hidden->setAttribute( 'type', 'hidden' ); $hidden->setAttribute( 'name', $name ); $hidden->setAttribute( 'value', $value ); $form->appendChild( $hidden ); } // Replace div contents with form. $block_container->appendChild( $form ); // Return modified content. return $parser->get_body_html(); } /** * Returns the URL of the page the form is displayed on, so the form submits * back to the same page, falling back to the Post's URL. * * @since 3.4.6 * * @param int $post_id Post ID. * @return string */ private function get_current_url( $post_id ) { // Fallback to the Post's URL if the request URI isn't available. if ( ! isset( $_SERVER['REQUEST_URI'] ) ) { return get_permalink( $post_id ); } // Remove the subscriber ID, which is only used when visiting a link from a Kit email. return remove_query_arg( 'ck_subscriber_id', esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ) ); } }