PluginProbe
Kit (formerly ConvertKit) – Email Newsletter, Email Marketing, Membership, Subscribers and Landing Pages / 2.7.4
Kit (formerly ConvertKit) – Email Newsletter, Email Marketing, Membership, Subscribers and Landing Pages v2.7.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 2.3.0 2.3.1 All 196 releases
← All changes | includes/class-convertkit-output.php +78 -110 3.4.22.7.4 View file →
@@ -66,8 +66,9 @@
66 66 * @since 1.9.6
67 67 */
68 68 public function __construct() {
69 69
70 + add_action( 'init', array( $this, 'get_subscriber_id_from_request' ) );
70 71 add_action( 'wp', array( $this, 'maybe_tag_subscriber' ) );
71 72 add_action( 'template_redirect', array( $this, 'output_form' ) );
72 73 add_action( 'template_redirect', array( $this, 'page_takeover' ) );
73 74 add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
@@ -87,8 +88,13 @@
87 88 * @since 2.4.9.1
88 89 */
89 90 public function maybe_tag_subscriber() {
90 91
92 + // Bail if no subscriber ID detected.
93 + if ( ! $this->subscriber_id ) {
94 + return;
95 + }
96 +
91 97 // Bail if not a singular Post Type supported by ConvertKit.
92 98 if ( ! is_singular( convertkit_get_supported_post_types() ) ) {
93 99 return;
94 100 }
@@ -120,16 +126,8 @@
120 126 if ( ! $this->post_settings->has_tag() ) {
121 127 return;
122 128 }
123 129
124 - // Get subscriber ID from URL or cookie.
125 - $this->get_subscriber_id_from_request();
126 -
127 - // Bail if no subscriber ID detected.
128 - if ( ! $this->subscriber_id ) {
129 - return;
130 - }
131 -
132 130 // Initialize the API.
133 131 $api = new ConvertKit_API_V4(
134 132 CONVERTKIT_OAUTH_CLIENT_ID,
135 133 CONVERTKIT_OAUTH_CLIENT_REDIRECT_URI,
@@ -138,23 +136,8 @@
138 136 $this->settings->debug_enabled(),
139 137 'output'
140 138 );
141 139
142 - // If the subscriber ID is not numeric, it's a cryptographically-signed subscriber ID, set
143 - // when using the Member Content functionality by Kit's API.
144 - // Fetch the underlying subscriber ID for the tag_subscriber() method.
145 - if ( ! is_numeric( $this->subscriber_id ) ) {
146 - $result = $api->profile( $this->subscriber_id );
147 -
148 - // If an error occurred, the subscriber ID is invalid.
149 - if ( is_wp_error( $result ) ) {
150 - return;
151 - }
152 -
153 - // Set the subscriber ID.
154 - $this->subscriber_id = $result['id'];
155 - }
156 -
157 140 // Tag subscriber.
158 141 $api->tag_subscriber( $this->post_settings->get_tag(), $this->subscriber_id );
159 142
160 143 }
@@ -234,9 +217,9 @@
234 217
235 218 // Get Landing Page.
236 219 $landing_page = $this->landing_pages->get_html( $this->post_settings->get_landing_page() );
237 220
238 - // Bail if an error occurred.
221 + // Bail if an error occured.
239 222 if ( is_wp_error( $landing_page ) ) {
240 223 return;
241 224 }
242 225
@@ -277,17 +260,10 @@
277 260 if ( ! is_singular( convertkit_get_supported_post_types() ) ) {
278 261 return $content;
279 262 }
280 263
281 - // Get Post ID.
264 + // Get Post ID and ConvertKit Form ID for the Post.
282 265 $post_id = get_the_ID();
283 -
284 - // Bail if Post Type is not supported by Kit.
285 - if ( ! in_array( get_post_type( $post_id ), convertkit_get_supported_post_types(), true ) ) {
286 - return $content;
287 - }
288 -
289 - // Get Form ID for the Post.
290 266 $form_id = $this->get_post_form_id( $post_id );
291 267
292 268 /**
293 269 * Define the ConvertKit Form ID to display for the given Post ID,
@@ -312,11 +288,11 @@
312 288 $this->forms = new ConvertKit_Resource_Forms( 'output_form' );
313 289 }
314 290
315 291 // Get Form HTML.
316 - $form = $this->forms->get_html( $form_id, $post_id );
292 + $form = $this->forms->get_html( $form_id );
317 293
318 - // If an error occurred, it could be because the specified Form ID for the Post either:
294 + // If an error occured, it could be because the specified Form ID for the Post either:
319 295 // - belongs to another ConvertKit account (i.e. API credentials were changed in the Plugin, but this Post's specified Form was not changed), or
320 296 // - the form was deleted from the ConvertKit account.
321 297 // Attempt to fallback to the default form for this Post Type.
322 298 if ( is_wp_error( $form ) ) {
@@ -336,11 +312,11 @@
336 312 return $content;
337 313 }
338 314
339 315 // Get Form HTML.
340 - $form = $this->forms->get_html( $form_id, $post_id );
316 + $form = $this->forms->get_html( $form_id );
341 317
342 - // If an error occurred again, the default form doesn't exist in this ConvertKit account.
318 + // If an error occured again, the default form doesn't exist in this ConvertKit account.
343 319 // Just return the Post Content, unedited.
344 320 if ( is_wp_error( $form ) ) {
345 321 if ( $this->settings->debug_enabled() ) {
346 322 $content .= '<!-- Kit append_form_to_content(): Default Form: ' . $form->get_error_message() . ' -->';
@@ -434,14 +410,28 @@
434 410 if ( empty( $form ) ) {
435 411 return $content;
436 412 }
437 413
438 - // Load the content into the parser.
439 - $parser = new ConvertKit_HTML_Parser( $content, LIBXML_HTML_NODEFDTD );
414 + // Define the meta tag.
415 + $meta_tag = '<meta http-equiv="Content-Type" content="text/html; charset=utf-8">';
440 416
417 + // Wrap content in <html>, <head> and <body> tags now, so we can inject the UTF-8 Content-Type meta tag.
418 + $modified_content = '<html><head></head><body>' . $content . '</body></html>';
419 +
420 + // Forcibly tell DOMDocument that this HTML uses the UTF-8 charset.
421 + // <meta charset="utf-8"> isn't enough, as DOMDocument still interprets the HTML as ISO-8859, which breaks character encoding
422 + // Use of mb_convert_encoding() with HTML-ENTITIES is deprecated in PHP 8.2, so we have to use this method.
423 + // If we don't, special characters render incorrectly.
424 + $modified_content = str_replace( '<head>', '<head>' . "\n" . $meta_tag, $modified_content );
425 +
426 + // Load Page / Post content into DOMDocument.
427 + libxml_use_internal_errors( true );
428 + $html = new DOMDocument();
429 + $html->loadHTML( $modified_content, LIBXML_HTML_NODEFDTD );
430 +
441 431 // Find the element to append the form to.
442 432 // item() is a zero based index.
443 - $element_node = $parser->html->getElementsByTagName( $tag )->item( $index - 1 );
433 + $element_node = $html->getElementsByTagName( $tag )->item( $index - 1 );
444 434
445 435 // If the element could not be found, either the number of elements by tag name is less
446 436 // than the requested position the form be inserted in, or no element exists.
447 437 // Append the form to the original content and return.
@@ -448,27 +438,31 @@
448 438 if ( is_null( $element_node ) ) {
449 439 return $content . $form;
450 440 }
451 441
452 - // Load the form into the parser.
453 - $form_parser = new ConvertKit_HTML_Parser( $form, LIBXML_HTML_NODEFDTD );
454 - $form_body = $form_parser->html->getElementsByTagName( 'body' )->item( 0 );
442 + // Create new element for the Form.
443 + $form_node = new DOMDocument();
444 + $form_node->loadHTML( $form, LIBXML_HTML_NODEFDTD );
455 445
456 - // Collect nodes first to avoid live NodeList mutation issues.
457 - $nodes_to_insert = array();
458 - foreach ( $form_body->childNodes as $child ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
459 - $nodes_to_insert[] = $parser->html->importNode( $child, true );
460 - }
446 + // Append the form to the specific element.
447 + $element_node->parentNode->insertBefore( $html->importNode( $form_node->documentElement, true ), $element_node->nextSibling ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
461 448
462 - // Inject the form node(s) after the element node e.g. after the paragraph, heading etc.
463 - $next_sibling = $element_node->nextSibling; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
464 - foreach ( $nodes_to_insert as $node ) {
465 - $element_node->parentNode->insertBefore( $node, $element_node->nextSibling ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
466 - }
449 + // Fetch HTML string.
450 + $modified_content = $html->saveHTML();
467 451
468 - // Return modified HTML string.
469 - return $parser->get_body_html();
452 + // Remove some HTML tags that DOMDocument adds, returning the output.
453 + // We do this instead of using LIBXML_HTML_NOIMPLIED in loadHTML(), because Legacy Forms are not always contained in
454 + // a single root / outer element, which is required for LIBXML_HTML_NOIMPLIED to correctly work.
455 + $modified_content = str_replace( '<html>', '', $modified_content );
456 + $modified_content = str_replace( '</html>', '', $modified_content );
457 + $modified_content = str_replace( '<head>', '', $modified_content );
458 + $modified_content = str_replace( '</head>', '', $modified_content );
459 + $modified_content = str_replace( '<body>', '', $modified_content );
460 + $modified_content = str_replace( '</body>', '', $modified_content );
461 + $modified_content = str_replace( $meta_tag, '', $modified_content );
470 462
463 + return $modified_content;
464 +
471 465 }
472 466
473 467 /**
474 468 * Injects the form after the given element and index, using preg_match_all().
@@ -746,29 +740,47 @@
746 740 * @since 1.9.6
747 741 */
748 742 public function enqueue_scripts() {
749 743
750 - // Get ConvertKit Settings and Post's Settings.
751 - $settings = new ConvertKit_Settings();
744 + // Get Post.
745 + $post = get_post();
752 746
753 - // Bail if the no scripts setting is enabled.
754 - if ( $settings->scripts_disabled() ) {
747 + // Bail if no Post could be fetched.
748 + if ( ! $post ) {
755 749 return;
756 750 }
757 751
758 - // Enqueue frontend JS.
759 - convertkit_enqueue_frontend_js();
752 + // Get ConvertKit Settings and Post's Settings.
753 + $settings = new ConvertKit_Settings();
754 + $convertkit_post = new ConvertKit_Post( $post->ID );
760 755
761 - // Define variables.
756 + // Register scripts that we might use.
757 + wp_register_script(
758 + 'convertkit-js',
759 + CONVERTKIT_PLUGIN_URL . 'resources/frontend/js/convertkit.js',
760 + array(),
761 + CONVERTKIT_PLUGIN_VERSION,
762 + true
763 + );
762 764 wp_localize_script(
763 765 'convertkit-js',
764 766 'convertkit',
765 767 array(
768 + 'ajaxurl' => admin_url( 'admin-ajax.php' ),
766 769 'debug' => $settings->debug_enabled(),
770 + 'nonce' => wp_create_nonce( 'convertkit' ),
767 771 'subscriber_id' => $this->subscriber_id,
768 772 )
769 773 );
770 774
775 + // Bail if the no scripts setting is enabled.
776 + if ( $settings->scripts_disabled() ) {
777 + return;
778 + }
779 +
780 + // Enqueue.
781 + wp_enqueue_script( 'convertkit-js' );
782 +
771 783 }
772 784
773 785 /**
774 786 * Gets the subscriber ID from the request (either the cookie or the URL).
@@ -780,9 +792,9 @@
780 792 // Use ConvertKit_Subscriber class to fetch and validate the subscriber ID.
781 793 $subscriber = new ConvertKit_Subscriber();
782 794 $subscriber_id = $subscriber->get_subscriber_id();
783 795
784 - // If an error occurred, the subscriber ID in the request/cookie is not a valid subscriber.
796 + // If an error occured, the subscriber ID in the request/cookie is not a valid subscriber.
785 797 if ( is_wp_error( $subscriber_id ) ) {
786 798 return;
787 799 }
788 800
@@ -813,11 +825,8 @@
813 825 if ( $this->post_settings !== false && $this->post_settings->uses_no_form() && $this->settings->non_inline_form_honor_none_setting() ) {
814 826 return;
815 827 }
816 828
817 - // Determine if the Non-inline Form Limit per Session setting is enabled.
818 - $limit_per_session = $this->settings->non_inline_form_limit_per_session();
819 -
820 829 // Get form.
821 830 $convertkit_forms = new ConvertKit_Resource_Forms();
822 831
823 832 // Iterate through forms.
@@ -832,15 +841,14 @@
832 841
833 842 // Add the form to the scripts array so it is included in the output.
834 843 add_filter(
835 844 'convertkit_output_scripts_footer',
836 - function ( $scripts ) use ( $form, $limit_per_session ) {
845 + function ( $scripts ) use ( $form ) {
837 846
838 847 $scripts[] = array(
839 - 'async' => true,
840 - 'data-uid' => $form['uid'],
841 - 'src' => $form['embed_js'],
842 - 'data-kit-limit-per-session' => $limit_per_session ? '1' : '0',
848 + 'async' => true,
849 + 'data-uid' => $form['uid'],
850 + 'src' => $form['embed_js'],
843 851 );
844 852
845 853 return $scripts;
846 854
@@ -857,13 +865,8 @@
857 865 * @since 2.1.4
858 866 */
859 867 public function output_scripts_footer() {
860 868
861 - // Don't output scripts if the request is for a search page or 404.
862 - if ( is_search() || is_404() ) {
863 - return;
864 - }
865 -
866 869 // Define array of scripts.
867 870 $scripts = array();
868 871
869 872 /**
@@ -893,13 +896,8 @@
893 896 * @param array $script Form script key/value pairs to output as <script> tag.
894 897 */
895 898 $script = apply_filters( 'convertkit_output_script_footer', $script );
896 899
897 - // Skip script if it is limited by the Non-inline Form Limit per Session setting.
898 - if ( $this->is_script_output_limited_by_session( $script ) ) {
899 - continue;
900 - }
901 -
902 900 // Build output.
903 901 $output = '<script';
904 902 foreach ( $script as $attribute => $value ) {
905 903 // If the value is true, just output the attribute.
@@ -936,38 +934,8 @@
936 934 // Output scripts.
937 935 foreach ( $output_scripts as $output_script ) {
938 936 echo $output_script . "\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
939 937 }
940 -
941 - }
942 -
943 - /**
944 - * Checks if a script is limited by the Non-inline Form Limit per Session setting.
945 - *
946 - * @since 3.0.0
947 - *
948 - * @param array $script Script.
949 - * @return bool
950 - */
951 - private function is_script_output_limited_by_session( $script ) {
952 -
953 - // Get Settings, if they have not yet been loaded.
954 - if ( ! $this->settings ) {
955 - $this->settings = new ConvertKit_Settings();
956 - }
957 -
958 - // Display script if the "Display Limit" setting isn't enabled.
959 - if ( ! $this->settings->non_inline_form_limit_per_session() ) {
960 - return false;
961 - }
962 -
963 - // Display script if the "Display Limit" setting should not be applied to this script.
964 - if ( ! isset( $script['data-kit-limit-per-session'] ) ) {
965 - return false;
966 - }
967 -
968 - // Display script if this is the first time the visitor has seen any non-inline form.
969 - return isset( $_COOKIE['ck_non_inline_form_displayed'] );
970 938
971 939 }
972 940
973 941 }