subscriber_id ) { return; } // Bail if not a singular Post Type supported by ConvertKit. if ( ! is_singular( convertkit_get_supported_post_types() ) ) { return; } // Get Post ID. $post_id = get_the_ID(); // Bail if a Post ID couldn't be identified. if ( ! $post_id ) { return; } // Get Settings, if they have not yet been loaded. if ( ! $this->settings ) { $this->settings = new ConvertKit_Settings(); } // Bail if the API if an API Key and Secret is not defined. if ( ! $this->settings->has_api_key_and_secret() ) { return; } // Get ConvertKit Post's Settings, if they have not yet been loaded. if ( ! $this->post_settings ) { $this->post_settings = new ConvertKit_Post( $post_id ); } // Bail if no "Add a Tag" setting specified for this Page. if ( ! $this->post_settings->has_tag() ) { return; } // Initialize the API. $api = new ConvertKit_API_V4( CONVERTKIT_OAUTH_CLIENT_ID, CONVERTKIT_OAUTH_CLIENT_REDIRECT_URI, $this->settings->get_access_token(), $this->settings->get_refresh_token(), $this->settings->debug_enabled(), 'output' ); // Tag subscriber. $api->tag_subscriber( $this->post_settings->get_tag(), $this->subscriber_id ); } /** * Runs the `convertkit_output_output_form` action for singular Post Types that don't use the_content() * or apply_filters( 'the_content' ) to output a ConvertKit Form. * * @since 1.9.6 */ public function output_form() { /** * Outputs a ConvertKit Form on singular Post Types that don't use the_content() * or apply_filters( 'the_content' ). * * @since 1.9.6 * * @return string Post Content with Form Appended, if applicable */ do_action( 'convertkit_output_output_form' ); } /** * Outputs a ConvertKit Landing Page if configured, replacing all output for the singular Post Type. * * @since 1.9.6 */ public function page_takeover() { $queried_object = get_queried_object(); // Bail if the queried object cannot be inspected. if ( ! isset( $queried_object->post_type ) ) { return; } // Get Post ID. $post_id = $queried_object->ID; // Bail if the queried object isn't a supported Post Type for Landing Pages. if ( $queried_object->post_type !== 'page' ) { return; } // Get ConvertKit Post's Settings, if they have not yet been loaded. if ( ! $this->post_settings ) { $this->post_settings = new ConvertKit_Post( $post_id ); } // Get Landing Page ID. $landing_page_id = $this->post_settings->get_landing_page(); /** * Define the ConvertKit Landing Page ID to display for the given Post ID, * overriding the Post settings. * * Return false to not display any ConvertKit Landing Page. * * @since 1.9.6 * * @param int $landing_page_id Landing Page ID * @param int $post_id Post ID */ $landing_page_id = apply_filters( 'convertkit_output_page_takeover_landing_page_id', $landing_page_id, $post_id ); // Bail if no Landing Page is configured to be output. if ( empty( $landing_page_id ) ) { return; } // Get available ConvertKit Landing Pages, if they have not yet been loaded. if ( ! $this->landing_pages ) { $this->landing_pages = new ConvertKit_Resource_Landing_Pages( 'output_landing_page' ); } // Get Landing Page. $landing_page = $this->landing_pages->get_html( $this->post_settings->get_landing_page() ); // Bail if an error occured. if ( is_wp_error( $landing_page ) ) { return; } // Replace the favicon with the WordPress site's favicon, if specified. $landing_page = $this->landing_pages->replace_favicon( $landing_page ); /** * Perform any actions immediately prior to outputting the Landing Page. * * Caching and minification Plugins may need to hook here to prevent * CSS / JS minification and lazy loading images, which can interfere * with Landing Pages. * * @since 2.4.4 * * @param string $landing_page ConvertKit Landing Page HTML. * @param int $landing_page_id ConvertKit Landing Page ID. * @param int $post_id WordPress Page ID. */ do_action( 'convertkit_output_landing_page_before', $landing_page, $landing_page_id, $post_id ); // Output Landing Page. // Output is supplied from ConvertKit's API, which is already sanitized. echo $landing_page; // phpcs:ignore WordPress.Security.EscapeOutput exit; } /** * Appends a form to the singular Page, Post or Custom Post Type's Content. * * @param string $content Post Content. * @return string Post Content with Form Appended, if applicable */ public function append_form_to_content( $content ) { // Bail if not a singular Post Type supported by ConvertKit. if ( ! is_singular( convertkit_get_supported_post_types() ) ) { return $content; } // Get Post ID and ConvertKit Form ID for the Post. $post_id = get_the_ID(); $form_id = $this->get_post_form_id( $post_id ); /** * Define the ConvertKit Form ID to display for the given Post ID, * overriding the Post, Category or Plugin settings. * * Return false to not display any ConvertKit Form. * * @since 1.9.6 * * @param bool|int $form_id Form ID * @param int $post_id Post ID */ $form_id = apply_filters( 'convertkit_output_append_form_to_content_form_id', $form_id, $post_id ); // Return the Post Content, unedited, if the Form ID is false or zero. if ( ! $form_id ) { return $content; } // Get available ConvertKit Forms, if they have not yet been loaded. if ( ! $this->forms ) { $this->forms = new ConvertKit_Resource_Forms( 'output_form' ); } // Get Form HTML. $form = $this->forms->get_html( $form_id ); // If an error occured, it could be because the specified Form ID for the Post either: // - belongs to another ConvertKit account (i.e. API credentials were changed in the Plugin, but this Post's specified Form was not changed), or // - the form was deleted from the ConvertKit account. // Attempt to fallback to the default form for this Post Type. if ( is_wp_error( $form ) ) { if ( $this->settings->debug_enabled() ) { $content .= ''; } // Get Default Form ID for this Post's Type. $form_id = $this->settings->get_default_form( get_post_type( $post_id ) ); // If no Default Form is specified, just return the Post Content, unedited. if ( ! $form_id ) { if ( $this->settings->debug_enabled() ) { $content .= ''; } return $content; } // Get Form HTML. $form = $this->forms->get_html( $form_id ); // If an error occured again, the default form doesn't exist in this ConvertKit account. // Just return the Post Content, unedited. if ( is_wp_error( $form ) ) { if ( $this->settings->debug_enabled() ) { $content .= ''; } return $content; } } // If here, we have a ConvertKit Form. // Append form to Post's Content. $content = $content .= $form; /** * Filter the Post's Content, which includes a ConvertKit Form, immediately before it is output. * * @since 1.9.6 * * @param string $content Post Content * @param string $form ConvertKit Form HTML * @param int $post_id Post ID * @param int $form_id ConvertKit Form ID */ $content = apply_filters( 'convertkit_frontend_append_form', $content, $form, $post_id, $form_id ); return $content; } /** * Registers the ConvertKit Form block to before or after the Query Loop block, when viewing a Category archive. * * See append_form_block_on_category_archive() configures the block to display the applicable category's Form. * * @since 2.4.9.1 * * @param array $hooked_blocks The list of hooked block types. * @param string $position The relative position of the hooked blocks. * @param string $anchor_block The anchor block type. * @param WP_Block_Template|WP_Post|array $context The block template, template part, wp_navigation post type, or pattern that the anchor block belongs to. * @return array */ public function maybe_register_form_block_on_category_archive( $hooked_blocks, $position, $anchor_block, $context ) { // Don't append if we're not viewing a category archive. if ( ! is_category() ) { return $hooked_blocks; } if ( $context instanceof WP_Block_Template && $context->slug !== 'archive' ) { return $hooked_blocks; } // Don't append if the anchor block isn't the Query Loop block. if ( $anchor_block !== 'core/query' ) { return $hooked_blocks; } // Don't append if the Category's form position setting is not defined. $form_position = $this->get_term_form_position(); if ( ! $form_position ) { // Unhook this function as we don't need to check again in this request, as we'll // never output a form on the Category archive. remove_filter( 'hooked_block_types', array( $this, 'maybe_register_form_block_on_category_archive' ), 10 ); return $hooked_blocks; } // Don't append if the position doesn't match. if ( $form_position !== $position ) { return $hooked_blocks; } // Hook the ConvertKit Form block. $hooked_blocks[] = 'convertkit/form'; // Unhook this function as we don't need to check again in this request, as // we have now appended the form. remove_filter( 'hooked_block_types', array( $this, 'maybe_register_form_block_on_category_archive' ), 10 ); return $hooked_blocks; } /** * Configures the ConvertKit Form block that was hooked below the Query Loop block by maybe_register_form_block_on_category_archive, * defining the Form ID based on the current Category's Form ID. * * @since 2.4.9.1 * * @param array $parsed_hooked_block The parsed block array for the given hooked block type, or null to suppress the block. * @return null|array */ public function append_form_block_to_category_archive( $parsed_hooked_block ) { // Sanity check that we're still viewing a Category archive. if ( ! is_category() ) { // Returning null will unregister the Form block from displaying. return null; } // Get Category archive being viewed. $category = get_category( get_query_var( 'cat' ) ); // Bail if the Category could be found. if ( is_wp_error( $category ) || is_null( $category ) ) { // Returning null will unregister the Form block from displaying. return null; } // Load Term Settings. $term_settings = new ConvertKit_Term( $category->term_id ); // Bail if no Form specified for the Category. if ( ! $term_settings->has_form() ) { // Returning null will unregister the Form block from displaying. return null; } // Define the form block attributes to display the given Form ID. $parsed_hooked_block['attrs'] = array( 'id' => absint( $term_settings->get_form() ), ); // Return the Form block with its attributes. return $parsed_hooked_block; } /** * Returns the Post, Category or Plugin ConvertKit Form ID for the given Post. * * If the Post specifies a form to use, returns that Form ID. * If the Post uses the 'Default' setting, and an assigned Category has a Form ID, uses the Category's Form ID. * Otherwise falls back to the Plugin's Default Form ID (if any). * * @since 1.9.6 * * @param int $post_id Post ID. * @return bool|string|int false|'default'|Form ID */ private function get_post_form_id( $post_id ) { // Get Settings, if they have not yet been loaded. if ( ! $this->settings ) { $this->settings = new ConvertKit_Settings(); } // Get ConvertKit Post's Settings, if they have not yet been loaded. if ( ! $this->post_settings ) { $this->post_settings = new ConvertKit_Post( $post_id ); } // If the Post specifies a Form to use, return its ID now. if ( $this->post_settings->has_form() ) { return $this->post_settings->get_form(); } // If the Post specifies that no Form should be used, return false. if ( $this->post_settings->uses_no_form() ) { return false; } // Sanity check that the Post uses the Default Form setting, which should be the case // because the above conditions were not met. if ( ! $this->post_settings->uses_default_form() ) { return false; } // Get Post's Categories. $categories = wp_get_post_categories( $post_id, array( 'fields' => 'ids', ) ); // If no Categories exist, use the Default Form. if ( ! is_array( $categories ) || ! count( $categories ) ) { // Get Post Type. return $this->settings->get_default_form( get_post_type( $post_id ) ); } /** * Iterate through Categories in reverse order. * This honors the behaviour < 1.9.6, which states that if multiple Categories each have a Form. * assigned, the last Category with a Form in the wp_get_post_categories() call will be used. */ $categories = array_reverse( $categories ); foreach ( $categories as $term_id ) { // Load Term Settings. $term_settings = new ConvertKit_Term( $term_id ); // If a Form ID exists, return it now. if ( $term_settings->has_form() ) { return $term_settings->get_form(); } } // If here, use the Plugin's Default Form. return $this->settings->get_default_form( get_post_type( $post_id ) ); } /** * Returns the Form Position setting for the currently viewed Category. * * @since 2.4.9.1 * * @return bool|string */ private function get_term_form_position() { // Get Category archive being viewed. $category = get_category( get_query_var( 'cat' ) ); // Bail if the Category could be found. if ( is_wp_error( $category ) || is_null( $category ) ) { return false; } // Load Term Settings. $term_settings = new ConvertKit_Term( $category->term_id ); // Return false if no form position is defined i.e. we don't want to display // it on the Category archive. if ( ! $term_settings->has_form_position() ) { return false; } // Return form position. return $term_settings->get_form_position(); } /** * Enqueue scripts. * * @since 1.9.6 */ public function enqueue_scripts() { // Get Post. $post = get_post(); // Bail if no Post could be fetched. if ( ! $post ) { return; } // Get ConvertKit Settings and Post's Settings. $settings = new ConvertKit_Settings(); $convertkit_post = new ConvertKit_Post( $post->ID ); // Register scripts that we might use. wp_register_script( 'convertkit-js', CONVERTKIT_PLUGIN_URL . 'resources/frontend/js/convertkit.js', array(), CONVERTKIT_PLUGIN_VERSION, true ); wp_localize_script( 'convertkit-js', 'convertkit', array( 'ajaxurl' => admin_url( 'admin-ajax.php' ), 'debug' => $settings->debug_enabled(), 'nonce' => wp_create_nonce( 'convertkit' ), 'subscriber_id' => $this->subscriber_id, ) ); // Bail if the no scripts setting is enabled. if ( $settings->scripts_disabled() ) { return; } // Enqueue. wp_enqueue_script( 'convertkit-js' ); } /** * Gets the subscriber ID from the request (either the cookie or the URL). * * @since 1.9.6 */ public function get_subscriber_id_from_request() { // Use ConvertKit_Subscriber class to fetch and validate the subscriber ID. $subscriber = new ConvertKit_Subscriber(); $subscriber_id = $subscriber->get_subscriber_id(); // If an error occured, the subscriber ID in the request/cookie is not a valid subscriber. if ( is_wp_error( $subscriber_id ) ) { return; } $this->subscriber_id = $subscriber_id; } /** * Outputs a non-inline form if defined in the Plugin's settings > * Default Non-Inline Form (Global) setting. * * @since 2.3.3 */ public function output_global_non_inline_form() { // Get Settings, if they have not yet been loaded. if ( ! $this->settings ) { $this->settings = new ConvertKit_Settings(); } // Bail if no non-inline form setting is specified. if ( ! $this->settings->has_non_inline_form() ) { return; } // Get form. $convertkit_forms = new ConvertKit_Resource_Forms(); $form = $convertkit_forms->get_by_id( (int) $this->settings->get_non_inline_form() ); // Bail if the Form doesn't exist (this shouldn't happen, but you never know). if ( ! $form ) { return; } // Add the form to the scripts array so it is included in the output. add_filter( 'convertkit_output_scripts_footer', function ( $scripts ) use ( $form ) { $scripts[] = array( 'async' => true, 'data-uid' => $form['uid'], 'src' => $form['embed_js'], ); return $scripts; } ); } /** * Outputs any JS '; // Add to array. $output_scripts[] = $output; } // Remove duplicate scripts. // This prevents the same non-inline form displaying twice. For example, if a modal form is specified both // in the Page's settings and the Form block, the user would see the same modal form displayed twice // because the script would be output twice. $output_scripts = array_unique( $output_scripts ); // Output scripts. foreach ( $output_scripts as $output_script ) { echo $output_script . "\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } } }