PluginProbe
Kit (formerly ConvertKit) – Email Newsletter, Email Marketing, Membership, Subscribers and Landing Pages / 3.4.5
Kit (formerly ConvertKit) – Email Newsletter, Email Marketing, Membership, Subscribers and Landing Pages v3.4.5
3.4.5 3.4.4 3.4.3 3.4.2 3.4.1 3.4.0 3.3.9 3.3.8 3.3.7 3.3.6 3.3.5 3.3.4 3.3.3 3.3.2 3.3.1 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.2.6 2.2.7 2.2.8 2.2.9 All 198 releases
← All changes | includes/blocks/class-convertkit-block-form-builder.php +133 -36 3.3.1 → 3.4.5 View file →
@@ -24,8 +24,28 @@
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 + * to ensure error notice IDs are unique.
40 + *
41 + * @since 3.4.4
42 + *
43 + * @var int
44 + */
45 + public $render_count = 0;
46 +
47 + /**
28 48 * Constructor
29 49 *
30 50 * @since 3.0.0
31 51 */
@@ -76,17 +96,15 @@
76 96 if ( ! array_key_exists( 'post_id', $_REQUEST['convertkit'] ) ) {
77 97 return;
78 98 }
79 99
80 - // Check reCAPTCHA.
81 - $recaptcha = new ConvertKit_Recaptcha();
82 - $recaptcha_response = $recaptcha->verify_recaptcha(
83 - ( isset( $_POST['g-recaptcha-response'] ) ? sanitize_text_field( wp_unslash( $_POST['g-recaptcha-response'] ) ) : '' ),
84 - 'convertkit_form_builder'
85 - );
100 + // Check spam protection.
101 + $spam_protection = new ConvertKit_Spam_Protection();
86 102
87 - // Bail if reCAPTCHA failed.
88 - if ( is_wp_error( $recaptcha_response ) ) {
103 + // Bail if spam protection failed.
104 + $spam_protection_result = $spam_protection->verify( 'convertkit_form_builder' );
105 + if ( is_wp_error( $spam_protection_result ) ) {
106 + $this->error = $spam_protection_result;
89 107 return;
90 108 }
91 109
92 110 // Sanitize form data.
@@ -91,8 +109,18 @@
91 109
92 110 // Sanitize form data.
93 111 $form_data = map_deep( wp_unslash( $_REQUEST['convertkit'] ), 'sanitize_text_field' );
94 112
113 + // Bail if the email address is invalid. The entry isn't stored, as an invalid
114 + // email address is of no use to the creator.
115 + if ( ! is_email( $form_data['email'] ) ) {
116 + $this->error = new WP_Error(
117 + 'convertkit_block_form_builder_invalid_email',
118 + __( 'Please enter a valid email address.', 'convertkit' )
119 + );
120 + return;
121 + }
122 +
95 123 // Build custom fields, if any were specified.
96 124 $custom_fields = array();
97 125 if ( array_key_exists( 'custom_fields', $form_data ) ) {
98 126 $custom_fields = $form_data['custom_fields'];
@@ -124,8 +152,13 @@
124 152 'api_error' => __( 'Plugin Access Token not configured', 'convertkit' ),
125 153 )
126 154 );
127 155 }
156 +
157 + $this->error = new WP_Error(
158 + 'convertkit_block_form_builder_no_access_token',
159 + __( 'Sorry, we were unable to subscribe you. Please try again later.', 'convertkit' )
160 + );
128 161 return;
129 162 }
130 163
131 164 // Initialize the API.
@@ -137,17 +170,22 @@
137 170 $settings->debug_enabled(),
138 171 'block_form_builder'
139 172 );
140 173
174 + // Determine the subscriber state.
175 + // If a Form is specified, mark the subscriber as inactive, so the form's double optin is honored.
176 + // If a Tag or Sequence is specified, mark the subscriber as active, as there's no double optin for tags or sequences.
177 + $subscriber_state = $form_id !== false ? 'inactive' : 'active';
178 +
141 179 // Create subscriber.
142 180 $result = $api->create_subscriber(
143 181 sanitize_email( $form_data['email'] ),
144 182 array_key_exists( 'first_name', $form_data ) ? $form_data['first_name'] : '',
145 - 'active',
183 + $subscriber_state,
146 184 $custom_fields
147 185 );
148 186
149 - // Bail if an error occured.
187 + // Bail if an error occurred.
150 188 if ( is_wp_error( $result ) ) {
151 189 // Store entry and return.
152 190 if ( $form_data['store_entries'] ) {
153 191 $entries->upsert(
@@ -163,8 +201,10 @@
163 201 'api_error' => $result->get_error_message(),
164 202 )
165 203 );
166 204 }
205 +
206 + $this->error = $result;
167 207 return;
168 208 }
169 209
170 210 // Store entry.
@@ -188,13 +228,22 @@
188 228 $subscriber->set( $result['subscriber']['id'] );
189 229
190 230 // If a form was specified, add the subscriber to the form.
191 231 if ( $form_id ) {
192 - $result = $api->add_subscriber_to_form(
193 - $form_id,
194 - $result['subscriber']['id'],
195 - get_permalink( absint( $form_data['post_id'] ) )
196 - );
232 + // For Legacy Forms, a different endpoint is used.
233 + $forms = new ConvertKit_Resource_Forms();
234 + if ( $forms->is_legacy( $form_id ) ) {
235 + $result = $api->add_subscriber_to_legacy_form(
236 + $form_id,
237 + $result['subscriber']['id']
238 + );
239 + } else {
240 + $result = $api->add_subscriber_to_form(
241 + $form_id,
242 + $result['subscriber']['id'],
243 + get_permalink( absint( $form_data['post_id'] ) )
244 + );
245 + }
197 246
198 247 if ( $form_data['store_entries'] ) {
199 248 $entries->upsert(
200 249 array(
@@ -386,10 +435,20 @@
386 435 ),
387 436 ),
388 437 ),
389 438
439 + // Help descriptions, displayed when no Access Token / resources exist and this block/shortcode is added.
440 + 'no_access_token' => array(
441 + 'notice' => __( 'Not connected to Kit.', 'convertkit' ),
442 + 'link' => convertkit_get_setup_wizard_plugin_link(),
443 + 'link_text' => __( 'Click here to connect your Kit account.', 'convertkit' ),
444 + 'instruction_text' => __( 'Connect your Kit account at Settings > Kit, and then refresh this page to configure this block.', 'convertkit' ),
445 + ),
446 +
390 447 'has_access_token' => $settings->has_access_and_refresh_token(),
391 - 'has_resources' => $convertkit_forms->exist(),
448 +
449 + // This block works without resources, so we don't need to check if resources exist.
450 + 'has_resources' => true,
392 451 );
393 452
394 453 }
395 454
@@ -496,19 +555,29 @@
496 555 * @return bool|array
497 556 */
498 557 public function get_fields() {
499 558
500 - // Get Kit Forms.
501 - $forms = new ConvertKit_Resource_Forms( 'block_form_builder' );
502 - $forms_options = array();
559 + // Get Kit Forms. Non-legacy forms populate the sidebar dropdown;
560 + // legacy forms are exposed separately as a fallback so the sidebar can
561 + // keep displaying a previously-saved legacy form as the current
562 + // selection without offering other legacy forms as new choices.
563 + $forms = new ConvertKit_Resource_Forms( 'block_form_builder' );
564 + $forms_options = array();
565 + $forms_legacy_options = array();
503 566 if ( $forms->exist() ) {
504 567 foreach ( $forms->get() as $form ) {
505 - // Legacy forms don't include a `format` key, so define them as inline.
506 - $forms_options[ $form['id'] ] = sprintf(
568 + $label = sprintf(
507 569 '%s [%s]',
508 570 sanitize_text_field( $form['name'] ),
571 + // Legacy forms don't include a `format` key, so define them as inline.
509 572 ( ! empty( $form['format'] ) ? sanitize_text_field( $form['format'] ) : 'inline' )
510 573 );
574 +
575 + if ( ! empty( $form['format'] ) ) {
576 + $forms_options[ $form['id'] ] = $label;
577 + } else {
578 + $forms_legacy_options[ $form['id'] ] = $label;
579 + }
511 580 }
512 581 }
513 582
514 583 // Get Kit Tags.
@@ -554,12 +623,13 @@
554 623 'value' => 0,
555 624 ),
556 625 ),
557 626 'form_id' => array(
558 - 'label' => __( 'Form', 'convertkit' ),
559 - 'type' => 'select',
560 - 'description' => __( 'The Kit form to add the subscriber to. Useful if you want to send an incentive email.', 'convertkit' ),
561 - 'values' => $forms_options,
627 + 'label' => __( 'Form', 'convertkit' ),
628 + 'type' => 'select',
629 + 'description' => __( 'The Kit form to add the subscriber to. Useful if you want to send an incentive email.', 'convertkit' ),
630 + 'values' => $forms_options,
631 + 'legacy_values' => $forms_legacy_options,
562 632 ),
563 633 'tag_id' => array(
564 634 'label' => __( 'Tag', 'convertkit' ),
565 635 'type' => 'select',
@@ -706,26 +776,26 @@
706 776 '<button type="submit"$1>$2</button>',
707 777 $block_content
708 778 );
709 779
710 - // Return the button if reCAPTCHA does not need to be used.
711 - $settings = new ConvertKit_Settings();
712 - if ( ! $settings->has_recaptcha_site_and_secret_keys() ) {
780 + // Return the button if no spam protection provider is active.
781 + $spam_protection = new ConvertKit_Spam_Protection();
782 + $provider = $spam_protection->get_active_provider();
783 + if ( ! $provider ) {
713 784 return $block_content;
714 785 }
715 786
716 - // Enqueue reCAPTCHA JS.
717 - $recaptcha = new ConvertKit_Recaptcha();
718 - $recaptcha->enqueue_scripts();
787 + // Enqueue the spam protection provider's JS.
788 + $provider->enqueue_scripts();
719 789
720 - // Add reCAPTCHA attributes to button.
790 + // Parse the button's DOM.
721 791 $parser = new ConvertKit_HTML_Parser( $block_content );
722 792 $button = $parser->xpath->query( '//button' )->item( 0 );
723 - $button->setAttribute( 'data-sitekey', esc_attr( $settings->recaptcha_site_key() ) ); // @phpstan-ignore-line
724 - $button->setAttribute( 'data-callback', 'convertKitRecaptchaFormSubmit' ); // @phpstan-ignore-line
725 - $button->setAttribute( 'data-action', 'convertkit_form_builder' ); // @phpstan-ignore-line
726 - $button->setAttribute( 'class', trim( $button->getAttribute( 'class' ) . ' g-recaptcha' ) ); // @phpstan-ignore-line
727 793
794 + // Attach the spam protection provider's attributes/elements to the form/button as necessary.
795 + // $button is narrowed from DOMNode to DOMElement by the //button xpath expression above.
796 + $provider->attach_to_form_button_dom( $parser, $button, 'convertkit_form_builder' ); // @phpstan-ignore-line
797 +
728 798 // Return button HTML.
729 799 return $parser->get_body_html();
730 800
731 801 }
@@ -769,8 +839,35 @@
769 839 $subscribed_message = $parser->html->createElement( 'div' );
770 840 $subscribed_message->setAttribute( 'class', 'convertkit-form-builder-subscribed-message' );
771 841 $subscribed_message->appendChild( $parser->html->createTextNode( $atts['text_if_subscribed'] ) );
772 842 $form->insertBefore( $subscribed_message, $form->firstChild ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
843 + }
844 +
845 + // Add error notice if the submission failed.
846 + if ( is_wp_error( $this->error ) ) {
847 + ++$this->render_count;
848 + $error_id = 'convertkit-form-builder-error-' . $this->render_count;
849 +
850 + $error_notice = $parser->html->createElement( 'div' );
851 + $error_notice->setAttribute( 'id', $error_id );
852 + $error_notice->setAttribute( 'class', 'convertkit-form-builder-notice convertkit-form-builder-notice-error' );
853 + $error_notice->setAttribute( 'role', 'alert' );
854 + $error_notice->setAttribute( 'tabindex', '-1' );
855 + $error_notice->appendChild( $parser->html->createTextNode( $this->error->get_error_message() ) );
856 + $form->insertBefore( $error_notice, $form->firstChild ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
857 +
858 + // Focus the email field if it caused the error, so screen readers and
859 + // browsers move to it. Otherwise focus the notice, as the error isn't
860 + // specific to a field.
861 + // Query within the form, as it's not yet appended to the document.
862 + $email_field = $parser->xpath->query( './/input[@name="convertkit[email]"]', $form )->item( 0 );
863 + if ( $email_field && $this->error->get_error_code() === 'convertkit_block_form_builder_invalid_email' ) {
864 + $email_field->setAttribute( 'aria-invalid', 'true' ); // @phpstan-ignore-line
865 + $email_field->setAttribute( 'aria-describedby', $error_id ); // @phpstan-ignore-line
866 + $email_field->setAttribute( 'autofocus', 'autofocus' ); // @phpstan-ignore-line
867 + } else {
868 + $error_notice->setAttribute( 'autofocus', 'autofocus' );
869 + }
773 870 }
774 871
775 872 // Add hidden fields.
776 873 $fields = array(