← All changes
|
includes/blocks/class-convertkit-block-form-builder.php
+146
-20
3.4.3
→
3.4.6
View file →
| @@ -24,8 +24,38 @@ | ||
| 24 | 24 | */ |
| 25 | 25 | public $subscriber_id = false; |
| 26 | 26 | |
| 27 | 27 | /** |
| 28 | + * Holds the WP_Error object if the form submission failed, | |
| 29 | + * to display on screen as a notice. | |
| 30 | + * | |
| 31 | + * @since 3.4.4 | |
| 32 | + * | |
| 33 | + * @var bool|WP_Error | |
| 34 | + */ | |
| 35 | + public $error = false; | |
| 36 | + | |
| 37 | + /** | |
| 38 | + * Holds the number of times this block has been rendered on the Post, | |
| 39 | + * used to identify each block on the page and ensure error notice IDs are unique. | |
| 40 | + * | |
| 41 | + * @since 3.4.4 | |
| 42 | + * | |
| 43 | + * @var int | |
| 44 | + */ | |
| 45 | + public $render_count = 0; | |
| 46 | + | |
| 47 | + /** | |
| 48 | + * Holds the index of the block that was submitted, so the error notice | |
| 49 | + * is only displayed on that block. | |
| 50 | + * | |
| 51 | + * @since 3.4.6 | |
| 52 | + * | |
| 53 | + * @var int | |
| 54 | + */ | |
| 55 | + public $submitted_block_index = 0; | |
| 56 | + | |
| 57 | + /** | |
| 28 | 58 | * Constructor |
| 29 | 59 | * |
| 30 | 60 | * @since 3.0.0 |
| 31 | 61 | */ |
| @@ -76,13 +106,20 @@ | ||
| 76 | 106 | if ( ! array_key_exists( 'post_id', $_REQUEST['convertkit'] ) ) { |
| 77 | 107 | return; |
| 78 | 108 | } |
| 79 | 109 | |
| 110 | + // Store the submitted block's index, so any error is only displayed on that block. | |
| 111 | + if ( array_key_exists( 'block_index', $_REQUEST['convertkit'] ) ) { | |
| 112 | + $this->submitted_block_index = absint( $_REQUEST['convertkit']['block_index'] ); | |
| 113 | + } | |
| 114 | + | |
| 80 | 115 | // Check spam protection. |
| 81 | 116 | $spam_protection = new ConvertKit_Spam_Protection(); |
| 82 | 117 | |
| 83 | 118 | // Bail if spam protection failed. |
| 84 | - if ( is_wp_error( $spam_protection->verify( 'convertkit_form_builder' ) ) ) { | |
| 119 | + $spam_protection_result = $spam_protection->verify( 'convertkit_form_builder' ); | |
| 120 | + if ( is_wp_error( $spam_protection_result ) ) { | |
| 121 | + $this->error = $spam_protection_result; | |
| 85 | 122 | return; |
| 86 | 123 | } |
| 87 | 124 | |
| 88 | 125 | // Sanitize form data. |
| @@ -87,8 +124,18 @@ | ||
| 87 | 124 | |
| 88 | 125 | // Sanitize form data. |
| 89 | 126 | $form_data = map_deep( wp_unslash( $_REQUEST['convertkit'] ), 'sanitize_text_field' ); |
| 90 | 127 | |
| 128 | + // Bail if the email address is invalid. The entry isn't stored, as an invalid | |
| 129 | + // email address is of no use to the creator. | |
| 130 | + if ( ! is_email( $form_data['email'] ) ) { | |
| 131 | + $this->error = new WP_Error( | |
| 132 | + 'convertkit_block_form_builder_invalid_email', | |
| 133 | + __( 'Please enter a valid email address.', 'convertkit' ) | |
| 134 | + ); | |
| 135 | + return; | |
| 136 | + } | |
| 137 | + | |
| 91 | 138 | // Build custom fields, if any were specified. |
| 92 | 139 | $custom_fields = array(); |
| 93 | 140 | if ( array_key_exists( 'custom_fields', $form_data ) ) { |
| 94 | 141 | $custom_fields = $form_data['custom_fields']; |
| @@ -93,12 +140,15 @@ | ||
| 93 | 140 | if ( array_key_exists( 'custom_fields', $form_data ) ) { |
| 94 | 141 | $custom_fields = $form_data['custom_fields']; |
| 95 | 142 | } |
| 96 | 143 | |
| 144 | + // Get First Name, if the Name field was included in the form. | |
| 145 | + $first_name = array_key_exists( 'first_name', $form_data ) ? $form_data['first_name'] : ''; | |
| 146 | + | |
| 97 | 147 | // Get Form, Tag and Sequence IDs, if any were specified. |
| 98 | - $form_id = array_key_exists( 'form_id', $form_data ) ? $form_data['form_id'] : false; | |
| 99 | - $tag_id = array_key_exists( 'tag_id', $form_data ) ? $form_data['tag_id'] : false; | |
| 100 | - $sequence_id = array_key_exists( 'sequence_id', $form_data ) ? $form_data['sequence_id'] : false; | |
| 148 | + $form_id = array_key_exists( 'form_id', $form_data ) ? absint( $form_data['form_id'] ) : 0; | |
| 149 | + $tag_id = array_key_exists( 'tag_id', $form_data ) ? absint( $form_data['tag_id'] ) : 0; | |
| 150 | + $sequence_id = array_key_exists( 'sequence_id', $form_data ) ? absint( $form_data['sequence_id'] ) : 0; | |
| 101 | 151 | |
| 102 | 152 | // Initialize classes that will be used. |
| 103 | 153 | $settings = new ConvertKit_Settings(); |
| 104 | 154 | $entries = new ConvertKit_Form_Entries(); |
| @@ -110,9 +160,9 @@ | ||
| 110 | 160 | $entries->upsert( |
| 111 | 161 | array( |
| 112 | 162 | 'post_id' => $form_data['post_id'], |
| 113 | 163 | 'email' => $form_data['email'], |
| 114 | - 'first_name' => $form_data['first_name'], | |
| 164 | + 'first_name' => $first_name, | |
| 115 | 165 | 'custom_fields' => $custom_fields, |
| 116 | 166 | 'form_id' => $form_id, |
| 117 | 167 | 'tag_id' => $tag_id, |
| 118 | 168 | 'sequence_id' => $sequence_id, |
| @@ -120,8 +170,13 @@ | ||
| 120 | 170 | 'api_error' => __( 'Plugin Access Token not configured', 'convertkit' ), |
| 121 | 171 | ) |
| 122 | 172 | ); |
| 123 | 173 | } |
| 174 | + | |
| 175 | + $this->error = new WP_Error( | |
| 176 | + 'convertkit_block_form_builder_no_access_token', | |
| 177 | + __( 'Sorry, we were unable to subscribe you. Please try again later.', 'convertkit' ) | |
| 178 | + ); | |
| 124 | 179 | return; |
| 125 | 180 | } |
| 126 | 181 | |
| 127 | 182 | // Initialize the API. |
| @@ -136,14 +191,14 @@ | ||
| 136 | 191 | |
| 137 | 192 | // Determine the subscriber state. |
| 138 | 193 | // If a Form is specified, mark the subscriber as inactive, so the form's double optin is honored. |
| 139 | 194 | // If a Tag or Sequence is specified, mark the subscriber as active, as there's no double optin for tags or sequences. |
| 140 | - $subscriber_state = $form_id !== false ? 'inactive' : 'active'; | |
| 195 | + $subscriber_state = $form_id ? 'inactive' : 'active'; | |
| 141 | 196 | |
| 142 | 197 | // Create subscriber. |
| 143 | 198 | $result = $api->create_subscriber( |
| 144 | 199 | sanitize_email( $form_data['email'] ), |
| 145 | - array_key_exists( 'first_name', $form_data ) ? $form_data['first_name'] : '', | |
| 200 | + $first_name, | |
| 146 | 201 | $subscriber_state, |
| 147 | 202 | $custom_fields |
| 148 | 203 | ); |
| 149 | 204 | |
| @@ -154,9 +209,9 @@ | ||
| 154 | 209 | $entries->upsert( |
| 155 | 210 | array( |
| 156 | 211 | 'post_id' => $form_data['post_id'], |
| 157 | 212 | 'email' => $form_data['email'], |
| 158 | - 'first_name' => $form_data['first_name'], | |
| 213 | + 'first_name' => $first_name, | |
| 159 | 214 | 'custom_fields' => $custom_fields, |
| 160 | 215 | 'form_id' => $form_id, |
| 161 | 216 | 'tag_id' => $tag_id, |
| 162 | 217 | 'sequence_id' => $sequence_id, |
| @@ -164,8 +219,10 @@ | ||
| 164 | 219 | 'api_error' => $result->get_error_message(), |
| 165 | 220 | ) |
| 166 | 221 | ); |
| 167 | 222 | } |
| 223 | + | |
| 224 | + $this->error = $result; | |
| 168 | 225 | return; |
| 169 | 226 | } |
| 170 | 227 | |
| 171 | 228 | // Store entry. |
| @@ -173,9 +230,9 @@ | ||
| 173 | 230 | $entries->upsert( |
| 174 | 231 | array( |
| 175 | 232 | 'post_id' => $form_data['post_id'], |
| 176 | 233 | 'email' => $form_data['email'], |
| 177 | - 'first_name' => $form_data['first_name'], | |
| 234 | + 'first_name' => $first_name, | |
| 178 | 235 | 'custom_fields' => $custom_fields, |
| 179 | 236 | 'form_id' => $form_id, |
| 180 | 237 | 'tag_id' => $tag_id, |
| 181 | 238 | 'sequence_id' => $sequence_id, |
| @@ -183,11 +240,14 @@ | ||
| 183 | 240 | ) |
| 184 | 241 | ); |
| 185 | 242 | } |
| 186 | 243 | |
| 244 | + // Get the subscriber ID, as $result is overwritten by the form, tag and sequence requests below. | |
| 245 | + $subscriber_id = $result['subscriber']['id']; | |
| 246 | + | |
| 187 | 247 | // Store the subscriber ID in a cookie. |
| 188 | 248 | $subscriber = new ConvertKit_Subscriber(); |
| 189 | - $subscriber->set( $result['subscriber']['id'] ); | |
| 249 | + $subscriber->set( $subscriber_id ); | |
| 190 | 250 | |
| 191 | 251 | // If a form was specified, add the subscriber to the form. |
| 192 | 252 | if ( $form_id ) { |
| 193 | 253 | // For Legacy Forms, a different endpoint is used. |
| @@ -194,14 +254,14 @@ | ||
| 194 | 254 | $forms = new ConvertKit_Resource_Forms(); |
| 195 | 255 | if ( $forms->is_legacy( $form_id ) ) { |
| 196 | 256 | $result = $api->add_subscriber_to_legacy_form( |
| 197 | 257 | $form_id, |
| 198 | - $result['subscriber']['id'] | |
| 258 | + $subscriber_id | |
| 199 | 259 | ); |
| 200 | 260 | } else { |
| 201 | 261 | $result = $api->add_subscriber_to_form( |
| 202 | 262 | $form_id, |
| 203 | - $result['subscriber']['id'], | |
| 263 | + $subscriber_id, | |
| 204 | 264 | get_permalink( absint( $form_data['post_id'] ) ) |
| 205 | 265 | ); |
| 206 | 266 | } |
| 207 | 267 | |
| @@ -209,9 +269,9 @@ | ||
| 209 | 269 | $entries->upsert( |
| 210 | 270 | array( |
| 211 | 271 | 'post_id' => $form_data['post_id'], |
| 212 | 272 | 'email' => $form_data['email'], |
| 213 | - 'first_name' => $form_data['first_name'], | |
| 273 | + 'first_name' => $first_name, | |
| 214 | 274 | 'custom_fields' => $custom_fields, |
| 215 | 275 | 'form_id' => $form_id, |
| 216 | 276 | 'tag_id' => $tag_id, |
| 217 | 277 | 'sequence_id' => $sequence_id, |
| @@ -223,9 +283,9 @@ | ||
| 223 | 283 | } |
| 224 | 284 | |
| 225 | 285 | // If a tag was specified, add the subscriber to the tag. |
| 226 | 286 | if ( $tag_id ) { |
| 227 | - $result = $api->tag_subscriber( $tag_id, $result['subscriber']['id'] ); | |
| 287 | + $result = $api->tag_subscriber( $tag_id, $subscriber_id ); | |
| 228 | 288 | |
| 229 | 289 | if ( $form_data['store_entries'] ) { |
| 230 | 290 | $entries->upsert( |
| 231 | 291 | array( |
| @@ -230,9 +290,9 @@ | ||
| 230 | 290 | $entries->upsert( |
| 231 | 291 | array( |
| 232 | 292 | 'post_id' => $form_data['post_id'], |
| 233 | 293 | 'email' => $form_data['email'], |
| 234 | - 'first_name' => $form_data['first_name'], | |
| 294 | + 'first_name' => $first_name, | |
| 235 | 295 | 'custom_fields' => $custom_fields, |
| 236 | 296 | 'form_id' => $form_id, |
| 237 | 297 | 'tag_id' => $tag_id, |
| 238 | 298 | 'sequence_id' => $sequence_id, |
| @@ -244,9 +304,9 @@ | ||
| 244 | 304 | } |
| 245 | 305 | |
| 246 | 306 | // If a sequence was specified, add the subscriber to the sequence. |
| 247 | 307 | if ( $sequence_id ) { |
| 248 | - $result = $api->add_subscriber_to_sequence( $sequence_id, $result['subscriber']['id'] ); | |
| 308 | + $result = $api->add_subscriber_to_sequence( $sequence_id, $subscriber_id ); | |
| 249 | 309 | |
| 250 | 310 | if ( $form_data['store_entries'] ) { |
| 251 | 311 | $entries->upsert( |
| 252 | 312 | array( |
| @@ -251,9 +311,9 @@ | ||
| 251 | 311 | $entries->upsert( |
| 252 | 312 | array( |
| 253 | 313 | 'post_id' => $form_data['post_id'], |
| 254 | 314 | 'email' => $form_data['email'], |
| 255 | - 'first_name' => $form_data['first_name'], | |
| 315 | + 'first_name' => $first_name, | |
| 256 | 316 | 'custom_fields' => $custom_fields, |
| 257 | 317 | 'form_id' => $form_id, |
| 258 | 318 | 'tag_id' => $tag_id, |
| 259 | 319 | 'sequence_id' => $sequence_id, |
| @@ -269,10 +329,10 @@ | ||
| 269 | 329 | if ( array_key_exists( 'redirect', $form_data ) && wp_http_validate_url( sanitize_url( $form_data['redirect'] ) ) ) { |
| 270 | 330 | // Redirect to the URL specified in the form. |
| 271 | 331 | $redirect = sanitize_url( $form_data['redirect'] ); |
| 272 | 332 | } else { |
| 273 | - // Redirect to the Post the form was displayed on, to show a success message. | |
| 274 | - $redirect = get_permalink( absint( $form_data['post_id'] ) ); | |
| 333 | + // Redirect to the page the form was displayed on, to show a success message. | |
| 334 | + $redirect = $this->get_current_url( absint( $form_data['post_id'] ) ); | |
| 275 | 335 | } |
| 276 | 336 | |
| 277 | 337 | // Redirect. |
| 278 | 338 | wp_redirect( $redirect ); // phpcs:ignore WordPress.Security.SafeRedirect.wp_redirect_wp_redirect |
| @@ -675,8 +735,11 @@ | ||
| 675 | 735 | |
| 676 | 736 | // Get Post ID. |
| 677 | 737 | $post_id = is_a( $post, 'WP_Post' ) ? $post->ID : 0; |
| 678 | 738 | |
| 739 | + // Increment the render count, used to identify this block on the page. | |
| 740 | + ++$this->render_count; | |
| 741 | + | |
| 679 | 742 | // Parse attributes, defining fallback defaults if required |
| 680 | 743 | // and moving some attributes (such as Gutenberg's styles), if defined. |
| 681 | 744 | $atts = $this->sanitize_and_declare_atts( $atts ); |
| 682 | 745 | |
| @@ -786,9 +849,9 @@ | ||
| 786 | 849 | } |
| 787 | 850 | |
| 788 | 851 | // Create form element. |
| 789 | 852 | $form = $parser->html->createElement( 'form' ); |
| 790 | - $form->setAttribute( 'action', esc_url( get_permalink( $post_id ) ) ); | |
| 853 | + $form->setAttribute( 'action', esc_url( $this->get_current_url( $post_id ) ) ); | |
| 791 | 854 | $form->setAttribute( 'method', 'post' ); |
| 792 | 855 | |
| 793 | 856 | // Move form builder div contents into form. |
| 794 | 857 | while ( $block_container->hasChildNodes() ) { |
| @@ -794,8 +857,22 @@ | ||
| 794 | 857 | while ( $block_container->hasChildNodes() ) { |
| 795 | 858 | $form->appendChild( $block_container->firstChild ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase |
| 796 | 859 | } |
| 797 | 860 | |
| 861 | + // Suffix field IDs and labels with the block's index from the second block onwards, | |
| 862 | + // so IDs are unique when multiple blocks are on the same page. | |
| 863 | + if ( $this->render_count > 1 ) { | |
| 864 | + foreach ( $parser->xpath->query( './/*[starts-with(@id, "kit-form-builder-")]', $form ) as $element ) { | |
| 865 | + $id = $element->getAttribute( 'id' ); // @phpstan-ignore-line | |
| 866 | + $new_id = $id . '-' . $this->render_count; | |
| 867 | + $element->setAttribute( 'id', $new_id ); // @phpstan-ignore-line | |
| 868 | + | |
| 869 | + foreach ( $parser->xpath->query( './/label[@for="' . $id . '"]', $form ) as $label ) { | |
| 870 | + $label->setAttribute( 'for', $new_id ); // @phpstan-ignore-line | |
| 871 | + } | |
| 872 | + } | |
| 873 | + } | |
| 874 | + | |
| 798 | 875 | // Add subscribed message if required. |
| 799 | 876 | if ( $this->subscriber_id ) { |
| 800 | 877 | $subscribed_message = $parser->html->createElement( 'div' ); |
| 801 | 878 | $subscribed_message->setAttribute( 'class', 'convertkit-form-builder-subscribed-message' ); |
| @@ -802,8 +879,35 @@ | ||
| 802 | 879 | $subscribed_message->appendChild( $parser->html->createTextNode( $atts['text_if_subscribed'] ) ); |
| 803 | 880 | $form->insertBefore( $subscribed_message, $form->firstChild ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase |
| 804 | 881 | } |
| 805 | 882 | |
| 883 | + // Add error notice if the submission failed, and this is the submitted block. | |
| 884 | + // If no block index was submitted (e.g. a cached page from an older version), display it on all blocks. | |
| 885 | + if ( is_wp_error( $this->error ) && ( ! $this->submitted_block_index || $this->submitted_block_index === $this->render_count ) ) { | |
| 886 | + $error_id = 'convertkit-form-builder-error-' . $this->render_count; | |
| 887 | + | |
| 888 | + $error_notice = $parser->html->createElement( 'div' ); | |
| 889 | + $error_notice->setAttribute( 'id', $error_id ); | |
| 890 | + $error_notice->setAttribute( 'class', 'convertkit-form-builder-notice convertkit-form-builder-notice-error' ); | |
| 891 | + $error_notice->setAttribute( 'role', 'alert' ); | |
| 892 | + $error_notice->setAttribute( 'tabindex', '-1' ); | |
| 893 | + $error_notice->appendChild( $parser->html->createTextNode( $this->error->get_error_message() ) ); | |
| 894 | + $form->insertBefore( $error_notice, $form->firstChild ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase | |
| 895 | + | |
| 896 | + // Focus the email field if it caused the error, so screen readers and | |
| 897 | + // browsers move to it. Otherwise focus the notice, as the error isn't | |
| 898 | + // specific to a field. | |
| 899 | + // Query within the form, as it's not yet appended to the document. | |
| 900 | + $email_field = $parser->xpath->query( './/input[@name="convertkit[email]"]', $form )->item( 0 ); | |
| 901 | + if ( $email_field && $this->error->get_error_code() === 'convertkit_block_form_builder_invalid_email' ) { | |
| 902 | + $email_field->setAttribute( 'aria-invalid', 'true' ); // @phpstan-ignore-line | |
| 903 | + $email_field->setAttribute( 'aria-describedby', $error_id ); // @phpstan-ignore-line | |
| 904 | + $email_field->setAttribute( 'autofocus', 'autofocus' ); // @phpstan-ignore-line | |
| 905 | + } else { | |
| 906 | + $error_notice->setAttribute( 'autofocus', 'autofocus' ); | |
| 907 | + } | |
| 908 | + } | |
| 909 | + | |
| 806 | 910 | // Add hidden fields. |
| 807 | 911 | $fields = array( |
| 808 | 912 | 'convertkit[post_id]' => absint( $post_id ), |
| 809 | 913 | 'convertkit[store_entries]' => $atts['store_entries'] ? '1' : '0', |
| @@ -810,8 +914,9 @@ | ||
| 810 | 914 | 'convertkit[redirect]' => esc_url( $atts['redirect'] ), |
| 811 | 915 | 'convertkit[form_id]' => absint( $atts['form_id'] ), |
| 812 | 916 | 'convertkit[tag_id]' => absint( $atts['tag_id'] ), |
| 813 | 917 | 'convertkit[sequence_id]' => absint( $atts['sequence_id'] ), |
| 918 | + 'convertkit[block_index]' => absint( $this->render_count ), | |
| 814 | 919 | '_wpnonce' => wp_create_nonce( 'convertkit_block_form_builder' ), |
| 815 | 920 | ); |
| 816 | 921 | foreach ( $fields as $name => $value ) { |
| 817 | 922 | $hidden = $parser->html->createElement( 'input' ); |
| @@ -825,8 +930,29 @@ | ||
| 825 | 930 | $block_container->appendChild( $form ); |
| 826 | 931 | |
| 827 | 932 | // Return modified content. |
| 828 | 933 | return $parser->get_body_html(); |
| 934 | + | |
| 935 | + } | |
| 936 | + | |
| 937 | + /** | |
| 938 | + * Returns the URL of the page the form is displayed on, so the form submits | |
| 939 | + * back to the same page, falling back to the Post's URL. | |
| 940 | + * | |
| 941 | + * @since 3.4.6 | |
| 942 | + * | |
| 943 | + * @param int $post_id Post ID. | |
| 944 | + * @return string | |
| 945 | + */ | |
| 946 | + private function get_current_url( $post_id ) { | |
| 947 | + | |
| 948 | + // Fallback to the Post's URL if the request URI isn't available. | |
| 949 | + if ( ! isset( $_SERVER['REQUEST_URI'] ) ) { | |
| 950 | + return get_permalink( $post_id ); | |
| 951 | + } | |
| 952 | + | |
| 953 | + // Remove the subscriber ID, which is only used when visiting a link from a Kit email. | |
| 954 | + return remove_query_arg( 'ck_subscriber_id', esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ) ); | |
| 829 | 955 | |
| 830 | 956 | } |
| 831 | 957 | |
| 832 | 958 | } |