PluginProbe
Kit (formerly ConvertKit) – Email Newsletter, Email Marketing, Membership, Subscribers and Landing Pages / 2.7.5
Kit (formerly ConvertKit) – Email Newsletter, Email Marketing, Membership, Subscribers and Landing Pages v2.7.5
3.4.6 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 All 199 releases
← All changes | includes/class-convertkit-output.php +77 -102 3.3.8 → 2.7.5 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
@@ -312,11 +295,11 @@
312 295 $this->forms = new ConvertKit_Resource_Forms( 'output_form' );
313 296 }
314 297
315 298 // Get Form HTML.
316 - $form = $this->forms->get_html( $form_id, $post_id );
299 + $form = $this->forms->get_html( $form_id );
317 300
318 - // If an error occurred, it could be because the specified Form ID for the Post either:
301 + // If an error occured, it could be because the specified Form ID for the Post either:
319 302 // - 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 303 // - the form was deleted from the ConvertKit account.
321 304 // Attempt to fallback to the default form for this Post Type.
322 305 if ( is_wp_error( $form ) ) {
@@ -336,11 +319,11 @@
336 319 return $content;
337 320 }
338 321
339 322 // Get Form HTML.
340 - $form = $this->forms->get_html( $form_id, $post_id );
323 + $form = $this->forms->get_html( $form_id );
341 324
342 - // If an error occurred again, the default form doesn't exist in this ConvertKit account.
325 + // If an error occured again, the default form doesn't exist in this ConvertKit account.
343 326 // Just return the Post Content, unedited.
344 327 if ( is_wp_error( $form ) ) {
345 328 if ( $this->settings->debug_enabled() ) {
346 329 $content .= '<!-- Kit append_form_to_content(): Default Form: ' . $form->get_error_message() . ' -->';
@@ -434,14 +417,28 @@
434 417 if ( empty( $form ) ) {
435 418 return $content;
436 419 }
437 420
438 - // Load the content into the parser.
439 - $parser = new ConvertKit_HTML_Parser( $content, LIBXML_HTML_NODEFDTD );
421 + // Define the meta tag.
422 + $meta_tag = '<meta http-equiv="Content-Type" content="text/html; charset=utf-8">';
440 423
424 + // Wrap content in <html>, <head> and <body> tags now, so we can inject the UTF-8 Content-Type meta tag.
425 + $modified_content = '<html><head></head><body>' . $content . '</body></html>';
426 +
427 + // Forcibly tell DOMDocument that this HTML uses the UTF-8 charset.
428 + // <meta charset="utf-8"> isn't enough, as DOMDocument still interprets the HTML as ISO-8859, which breaks character encoding
429 + // Use of mb_convert_encoding() with HTML-ENTITIES is deprecated in PHP 8.2, so we have to use this method.
430 + // If we don't, special characters render incorrectly.
431 + $modified_content = str_replace( '<head>', '<head>' . "\n" . $meta_tag, $modified_content );
432 +
433 + // Load Page / Post content into DOMDocument.
434 + libxml_use_internal_errors( true );
435 + $html = new DOMDocument();
436 + $html->loadHTML( $modified_content, LIBXML_HTML_NODEFDTD );
437 +
441 438 // Find the element to append the form to.
442 439 // item() is a zero based index.
443 - $element_node = $parser->html->getElementsByTagName( $tag )->item( $index - 1 );
440 + $element_node = $html->getElementsByTagName( $tag )->item( $index - 1 );
444 441
445 442 // If the element could not be found, either the number of elements by tag name is less
446 443 // than the requested position the form be inserted in, or no element exists.
447 444 // Append the form to the original content and return.
@@ -448,27 +445,31 @@
448 445 if ( is_null( $element_node ) ) {
449 446 return $content . $form;
450 447 }
451 448
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 );
449 + // Create new element for the Form.
450 + $form_node = new DOMDocument();
451 + $form_node->loadHTML( $form, LIBXML_HTML_NODEFDTD );
455 452
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 - }
453 + // Append the form to the specific element.
454 + $element_node->parentNode->insertBefore( $html->importNode( $form_node->documentElement, true ), $element_node->nextSibling ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
461 455
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 - }
456 + // Fetch HTML string.
457 + $modified_content = $html->saveHTML();
467 458
468 - // Return modified HTML string.
469 - return $parser->get_body_html();
459 + // Remove some HTML tags that DOMDocument adds, returning the output.
460 + // We do this instead of using LIBXML_HTML_NOIMPLIED in loadHTML(), because Legacy Forms are not always contained in
461 + // a single root / outer element, which is required for LIBXML_HTML_NOIMPLIED to correctly work.
462 + $modified_content = str_replace( '<html>', '', $modified_content );
463 + $modified_content = str_replace( '</html>', '', $modified_content );
464 + $modified_content = str_replace( '<head>', '', $modified_content );
465 + $modified_content = str_replace( '</head>', '', $modified_content );
466 + $modified_content = str_replace( '<body>', '', $modified_content );
467 + $modified_content = str_replace( '</body>', '', $modified_content );
468 + $modified_content = str_replace( $meta_tag, '', $modified_content );
470 469
470 + return $modified_content;
471 +
471 472 }
472 473
473 474 /**
474 475 * Injects the form after the given element and index, using preg_match_all().
@@ -746,29 +747,47 @@
746 747 * @since 1.9.6
747 748 */
748 749 public function enqueue_scripts() {
749 750
750 - // Get ConvertKit Settings and Post's Settings.
751 - $settings = new ConvertKit_Settings();
751 + // Get Post.
752 + $post = get_post();
752 753
753 - // Bail if the no scripts setting is enabled.
754 - if ( $settings->scripts_disabled() ) {
754 + // Bail if no Post could be fetched.
755 + if ( ! $post ) {
755 756 return;
756 757 }
757 758
758 - // Enqueue frontend JS.
759 - convertkit_enqueue_frontend_js();
759 + // Get ConvertKit Settings and Post's Settings.
760 + $settings = new ConvertKit_Settings();
761 + $convertkit_post = new ConvertKit_Post( $post->ID );
760 762
761 - // Define variables.
763 + // Register scripts that we might use.
764 + wp_register_script(
765 + 'convertkit-js',
766 + CONVERTKIT_PLUGIN_URL . 'resources/frontend/js/convertkit.js',
767 + array(),
768 + CONVERTKIT_PLUGIN_VERSION,
769 + true
770 + );
762 771 wp_localize_script(
763 772 'convertkit-js',
764 773 'convertkit',
765 774 array(
775 + 'ajaxurl' => admin_url( 'admin-ajax.php' ),
766 776 'debug' => $settings->debug_enabled(),
777 + 'nonce' => wp_create_nonce( 'convertkit' ),
767 778 'subscriber_id' => $this->subscriber_id,
768 779 )
769 780 );
770 781
782 + // Bail if the no scripts setting is enabled.
783 + if ( $settings->scripts_disabled() ) {
784 + return;
785 + }
786 +
787 + // Enqueue.
788 + wp_enqueue_script( 'convertkit-js' );
789 +
771 790 }
772 791
773 792 /**
774 793 * Gets the subscriber ID from the request (either the cookie or the URL).
@@ -780,9 +799,9 @@
780 799 // Use ConvertKit_Subscriber class to fetch and validate the subscriber ID.
781 800 $subscriber = new ConvertKit_Subscriber();
782 801 $subscriber_id = $subscriber->get_subscriber_id();
783 802
784 - // If an error occurred, the subscriber ID in the request/cookie is not a valid subscriber.
803 + // If an error occured, the subscriber ID in the request/cookie is not a valid subscriber.
785 804 if ( is_wp_error( $subscriber_id ) ) {
786 805 return;
787 806 }
788 807
@@ -813,11 +832,8 @@
813 832 if ( $this->post_settings !== false && $this->post_settings->uses_no_form() && $this->settings->non_inline_form_honor_none_setting() ) {
814 833 return;
815 834 }
816 835
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 836 // Get form.
821 837 $convertkit_forms = new ConvertKit_Resource_Forms();
822 838
823 839 // Iterate through forms.
@@ -832,15 +848,14 @@
832 848
833 849 // Add the form to the scripts array so it is included in the output.
834 850 add_filter(
835 851 'convertkit_output_scripts_footer',
836 - function ( $scripts ) use ( $form, $limit_per_session ) {
852 + function ( $scripts ) use ( $form ) {
837 853
838 854 $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',
855 + 'async' => true,
856 + 'data-uid' => $form['uid'],
857 + 'src' => $form['embed_js'],
843 858 );
844 859
845 860 return $scripts;
846 861
@@ -857,13 +872,8 @@
857 872 * @since 2.1.4
858 873 */
859 874 public function output_scripts_footer() {
860 875
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 876 // Define array of scripts.
867 877 $scripts = array();
868 878
869 879 /**
@@ -893,13 +903,8 @@
893 903 * @param array $script Form script key/value pairs to output as <script> tag.
894 904 */
895 905 $script = apply_filters( 'convertkit_output_script_footer', $script );
896 906
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 907 // Build output.
903 908 $output = '<script';
904 909 foreach ( $script as $attribute => $value ) {
905 910 // If the value is true, just output the attribute.
@@ -936,38 +941,8 @@
936 941 // Output scripts.
937 942 foreach ( $output_scripts as $output_script ) {
938 943 echo $output_script . "\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
939 944 }
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 945
971 946 }
972 947
973 948 }