<?php
/**
 * Integrations: Contact Form 7
 *
 * @package     AffiliateWP
 * @subpackage  Integrations
 * @copyright   Copyright (c) 2016, Sandhills Development, LLC
 * @license     http://opensource.org/licenses/gpl-2.0.php GNU Public License
 * @since       2.0
 */

/**
 * Contact Form 7 integration class.
 *
 * This integration provides support for Contact Form 7 and one of the PayPal add-ons, https://wordpress.org/plugins/contact-form-7-paypal-add-on/
 *
 * @since 2.0
 */
class Affiliate_WP_Contact_Form_7 extends Affiliate_WP_Base {

	/**
	 * The Help Scout docs url for this integration.
	 *
	 * @since 2.0
	 * @var string Documentation URL.
	 */
	public $doc_url;

	/**
	 * The PayPal transaction success page url
	 * Specific to the `Contact Form 7 - PayPal Add-on` CF7 add-on.
	 *
	 * @since 2.0
	 */
	public $return_url;

	/**
	 * The PayPal transaction cancellation page url.
	 * Specific to the `Contact Form 7 - PayPal Add-on` CF7 add-on.
	 *
	 * @since 2.0
	 */
	public $cancel_url;

	/**
	 * The context for referrals. This refers to the integration that is being used.
	 *
	 * @access  public
	 * @since   1.2
	 */
	public $context = 'contactform7';

	/**
	 * @access  public
	 * @see     Affiliate_WP_Base::init
	 * @since   2.0
	 */
	public function init() {

		$this->doc_url = 'https://affiliatewp.com/docs/contact-form-7/';

		// Set the success and cancel url.
		$this->return_url = get_home_url();
		$this->cancel_url = get_home_url();
		$paypal_options   = get_option( 'cf7pp_options' );
		if ( is_array( $paypal_options ) && isset( $paypal_options['return'] ) ) {
			$this->return_url = $paypal_options['return'];
		}
		if ( is_array( $paypal_options ) && isset( $paypal_options['cancel'] ) ) {
			$this->cancel_url = $paypal_options['cancel'];
		}

		// Misc AffWP CF7 functions.
		$this->include_cf7_functions();

		// Register core settings.
		add_filter( 'affwp_settings_tabs', array( $this, 'register_settings_tab' ) );
		add_filter( 'affwp_settings', array( $this, 'register_settings' ) );

		// Add PayPal meta to the contact form submision object.
		add_action( 'wpcf7_submit', array( $this, 'add_paypal_meta' ), 1, 2 );

		add_action( 'wpcf7_mail_sent', array( $this, 'maybe_unhook_cf7pp' ), -999 );

		// Mark referral complete.
		add_action( 'wp_footer', array( $this, 'mark_referral_complete' ), 9999 );

		// Revoke referral.
		add_action( 'wp_footer', array( $this, 'revoke' ), 9999 );

		// Set reference.
		add_filter( 'affwp_referral_reference_column', array( $this, 'reference_link' ), 10, 2 );
	}

	/**
	 * Include Contact Form 7 functions
	 * @access  public
	 * @since   2.0
	 */
	public function include_cf7_functions() {
		require_once ( AFFILIATEWP_PLUGIN_DIR . 'includes/integrations/extras/contactform7-functions.php' );
	}

	/**
	 * Unhooks the `cf7pp_after_send_mail` function only if a referring affiliate is found.
	 *
	 * If referred, processes the PayPal redirect after generating the initial referral.
	 *
	 * @since  2.0.3
	 *
	 * @return void
	 */
	public function maybe_unhook_cf7pp() {

		if ( $this->was_referred() ) {
			remove_action( 'wpcf7_mail_sent', 'cf7pp_after_send_mail' );
			add_action( 'wpcf7_submit', array( $this, 'add_pending_referral' ), 10, 2 );
			add_action( 'affwp_cf7_submit', 'affwp_cf7_paypal_redirect', 10, 3 );
		}
	}

	/**
	 * Register the Contact Form 7 integration settings tab
	 *
	 * @access public
	 * @since  2.0
	 * @return array The new tab name
	 */
	public function register_settings_tab( $tabs = array() ) {

		$tabs['contactform7'] = __( 'Contact Form 7', 'affiliate-wp' );

		return $tabs;
	}

	/**
	 * Adds AffiliateWP integration settings.
	 *
	 * Register Contact Form 7 Integration settings.
	 *
	 * @since  2.0
	 * @since  2.12.0 Added affwp_cf7_referral_default_status setting.
	 *
	 * @access public
	 *
	 * @param  array $settings The existing settings.
	 * @return array $settings The updated settings.
	 */
	public function register_settings( array $settings = array() ) : array {

		$settings['contactform7'] = array(
			'affwp_cf7_enable_all_forms'        => array(
				'name' => __(
					'Enable Referrals On All Contact Form 7 Forms',
					'affiliate-wp'
				),
				'desc' => sprintf(
					wp_kses_post(
						/* translators: 1: Contact Form 7 docs URL, 2: External link icon markup, 3: Info icon markup */
						__(
							'Check this box to enable referrals on all Contact Form 7 forms.<ul><li>%3$s Once enabled, referrals will be generated for all valid Contact Form 7 forms.</li><li>%2$s <a href="%1$s" target="_blank">Documentation for this integration</a></li></ul>',
							'affiliate-wp'
						)
					),
					esc_url(
						/**
						 * Doc url filter.
						 *
						 * The Contact Form 7 Help Scout docs url displayed within plugin settings.
						 *
						 * @since  1.0
						 *
						 * @param string $doc_url Help Scout docs url to provide within plugin settings.
						 */
						apply_filters(
							'affwp_cf7_admin_docs_url',
							$this->doc_url
						)
					),
					'<span class="dashicons dashicons-external"></span>',
					'<span class="dashicons dashicons-info"></span>'
				),
				'type' => 'checkbox',
			),
			'affwp_cf7_referral_default_status' => array(
				'name'    => esc_html__(
					'Default Referral Status',
					'affiliate-wp'
				),
				'desc'    => esc_html__(
					'Select the Default Status For All Referrals Generated By Contact Form 7.',
					'affiliate-wp'
				),
				'type'    => 'select',
				'options' => array(
					'unpaid'  => 'Unpaid',
					'pending' => 'Pending',
				),
				'std'     => $this->is_first_use() ? 'unpaid' : 'pending',
			),
			'affwp_cf7_enable_specific_forms'   => array(
				'name'    => sprintf(
					'<strong>%s</strong>',
					esc_html__(
						'Enable Referrals For Specific Contact Form 7 Forms',
						'affiliate-wp'
					)
				),
				'type'    => 'multicheck',
				'options' => $this->all_forms_multicheck_render(),
			),
		);

		// Get all CF7 forms.
		$forms = $this->get_all_forms();

		if ( empty( $forms ) || ! is_array( $forms ) ) {
			return $settings;
		}

		foreach ( $forms as $form_id => $title ) {

			if ( ! is_numeric( $form_id ) ) {
				continue;
			}
			
			if ( ! is_string( $title ) ) {
				continue;
			}
			
			$settings['contactform7'][ "cf7_referral_type_{$form_id}" ] = array(
				'name'    => sprintf(
					/* translators: 1: Form title, 2: Form ID */
					esc_html__(
						'Referral Type For %1$s (Form ID: %2$d)',
						'affiliate-wp'
					),
					$title,
					$form_id
				),
				'type'    => 'select',
				'options' => array_map(
					function( $type ) {
						return $type['label'];
					},
					affwp_get_referral_types()
				),
			);
		}

		return $settings;
	}

	/**
	 * Returns if this is the first time running this integration.
	 *
	 * Check for user settings, if all forms any specific form or any specific
	 * form are enabled, this user has been using the integration already.
	 *
	 * @since 2.12.0
	 *
	 * @return bool Is the first time the user is using the integration?
	 */
	public function is_first_use() : bool {

		if (
			empty( affiliate_wp()->settings->get( 'affwp_cf7_enable_all_forms' ) ) &&
			empty( $this->get_enabled_forms() )
		) {
			return true;
		}

		return false;
	}

	/**
	 * Get forms which have AffiliateWP enabled.
	 * Directly checks the `wpcf7_contact_form` post type.
	 *
	 * @since  2.0
	 *
	 * @return array $enabled_forms All enabled CF7 forms
	 */
	public function get_all_forms() {

		$all_forms = array();

		$args = array(
			'post_type'   => array( 'wpcf7_contact_form' ),
			'post_status' => array( 'publish' )
		);

		$forms = get_posts( $args );

		// The Loop
		if ( $forms ) {
			foreach( $forms as $form ) {

				$all_forms[ $form->ID ] = get_the_title( $form->ID );
			}
		}

		return $all_forms;
	}

	/**
	 * Outputs all Contact Form 7 forms presently created in the site.
	 *
	 * @since  2.0
	 *
	 * @return $cf7_forms  Array of Contact Form 7 form titles and IDs.
	 */
	public function all_forms_multicheck_render() {

		$cf7_forms = array();

		$all_forms = $this->get_all_forms();

		foreach ( $all_forms as $id => $title ) {
			$cf7_forms[ $id ] = '<strong>' . $title . '</strong> <em>(' . __( 'Form ID: ', 'affiliate-wp' ) . $id . ' )</em>';
		}

		return $cf7_forms;
	}

	/**
	 * Gets forms which have AffiliateWP enabled.
	 *
	 * @since  2.0
	 *
	 * @return $enabled  The enabled forms
	 */
	public function get_enabled_forms() {
		$enabled = array();
		$enabled = affiliate_wp()->settings->get( 'affwp_cf7_enable_specific_forms' );

		if ( empty( $enabled ) ) {
			$enabled = array();
		}

		/**
		 * The Contact Form 7 forms for which AffiliateWP is enabled.
		 *
		 * @since 2.0
		 *
		 * @param array $enabled An array of integers, each being the ID of a Contact Form 7
		 *                       form for which AffiliateWP is enabled.
		 */
		return apply_filters( 'affwp_cf7_enabled_forms', $enabled );
	}

	/**
	 * Checks if a form has referrals enabled.
	 *
	 * @since  2.0
	 *
	 * @return $enabled bool True if the form has referrals enabled.
	 */
	public function form_enabled( $form_id 	) {
		$enabled = array_key_exists( $form_id, $this->get_enabled_forms() ) ? true : false;

		return (bool) $enabled;
	}

	/**
	 * Adds PayPal add-on meta to the form object.
	 *
	 * @param stdClass  $contactform The contact form data.
	 * @return stdClass $contactform The modified contact form data.
	 * @since 2.0
	 *
	 */
	public function add_paypal_meta( $contactform ) {

		$form_id = $contactform->id();

		update_post_meta( $form_id, 'affwp_cf7_form_id', $form_id );

		$enabled     = get_post_meta( $form_id, '_cf7pp_enable', true );
		$email       = get_post_meta( $form_id, '_cf7pp_email',  true );
		$amount      = get_post_meta( $form_id, '_cf7pp_price',  true );
		$description = get_post_meta( $form_id, '_cf7pp_name',   true );
		$sku         = get_post_meta( $form_id, '_cf7pp_id',     true );

		// Temporarily cast object and add referral data.
		$contactform = (object) array_merge(
			(array) $contactform, array(
				'affwp_paypal_enabled'       => $enabled,
				'affwp_customer_email'       => $email,
				'affwp_base_amount'          => $amount,
				'affwp_referral_description' => $description,
				'affwp_product_sku'          => $sku
			)
		);

		return $contactform;
	}

	/**
	 * Provides CF7 form meta via ajax.
	 *
	 * @since  2.0
	 *
	 * @return void
	 * @see    affwp_cf7_ajax
	 */
	public function ajax_get_paypal_meta(){

		if ( isset( $_REQUEST ) ) {
			$form_id = absint( $_REQUEST['form_id'] );
		}

		$enabled     = get_post_meta( $form_id, '_cf7pp_enable', true );
		$amount      = get_post_meta( $form_id, '_cf7pp_price',  true );
		$description = get_post_meta( $form_id, '_cf7pp_name',   true );
		$sku         = get_post_meta( $form_id, '_cf7pp_id',     true );

		$response = array(
			'success'     => true,
			'form_id'     => $form_id,
			'enabled'     => $enabled,
			'amount'      => $amount,
			'description' => $description,
			'sku'         => $sku
		);

		echo json_encode( $response );

		die();
	}

	/**
	 * Returns PayPal form submission meta as arguments, allowing for transactions to be trackable by AffiliateWP.
	 *
	 * @since  2.0
	 *
	 * @param  object             $contactform  CF7 form object.
	 * @param  object             $result       Modified CF7 form object.
	 *
	 * @return mixed|bool|string  $url_args     A string containing query parameters, used in paypal redirects. Returns false if parameters could not be determined.
	 */
	public function get_url_args( $cf7 ) {

		$form_id = absint( $cf7->id() );

		if ( ! $form_id ) {
			return false;
		}

		if( ! $this->form_enabled( $form_id ) && ! affiliate_wp()->settings->get( 'affwp_cf7_enable_all_forms', false ) ) {
			return false;
		}

		if ( ! get_post_meta( $form_id, '_cf7pp_enable', true ) ) {
			return false;
		}

		$amount      = get_post_meta( $form_id, '_cf7pp_price',  true );
		$description = get_post_meta( $form_id, '_cf7pp_name',   true );
		$sku         = get_post_meta( $form_id, '_cf7pp_id',     true );

		// Add meta to the return and cancel urls.
		$args = '?form_id=' . $form_id . '&sub_time=' . date_i18n( 'U' ) . '&amount=' . $amount . '&description=' . $description . '&sku=' . $sku;

		$url_args = esc_url( $args );

		return $url_args;

	}

	/**
	 * Utility function which returns the current page ID.
	 *
	 * @since  2.0
	 *
	 * @return mixed int|bool  $current_page_id The current page ID, or booean false if unable to locate the current page ID.
	 */
	public function get_current_page_id() {

		global $post;

		if ( ! $post ) {
			return false;
		}

		$current_page_id = $post->ID;

		return $current_page_id ? $current_page_id : false;
	}

	/**
	 * Adds a referral when a form is submitted.
	 *
	 * @since 2.0
	 *
	 * @param object $contactform   CF7 form submission object.
	 * @param object $result        Submitted CF7 form submission data.
	 */
	public function add_pending_referral( $contactform, $result ) {

		$current_page_id = $this->get_current_page_id();

		$form_id = absint( $contactform->id() );

		if ( ! $form_id ) {
			return false;
		}

		if( ! $this->form_enabled( $form_id ) && ! affiliate_wp()->settings->get( 'affwp_cf7_enable_all_forms', false ) ) {

			// Let the PayPal plugin take over
			if( function_exists( 'cf7pp_after_send_mail' ) ) {

				cf7pp_after_send_mail( $contactform );

			}

			return false;
		}

		if ( $this->was_referred() ) {

			$paypal = get_post_meta( $form_id, '_cf7pp_enable', true );

			if( $paypal ) {

				$product_id  = get_post_meta( $form_id, '_cf7pp_id',     true );
				$description = get_post_meta( $form_id, '_cf7pp_name',   true );
				$base_amount = floatval( get_post_meta( $form_id, '_cf7pp_price',  true ) );

			} else {

				$product_id  = 0;
				$description = get_the_title( $form_id );
				$base_amount = 0;

			}

			$this->referral_type = affiliate_wp()->settings->get( 'cf7_referral_type_' . $form_id );

			/**
			 * Filters the referral description for the AffiliateWP Contact Form 7 integration.
			 *
			 * @since 2.1.12
			 *
			 * @param string $description Item description or CF7 form title.
			 * @param string $form_id     CF7 form ID.
			 * @param object $contactform CF7 form submission object.
			 * @param object $result      Submitted CF7 form submission data.
			 *
			 */
			$description = apply_filters( 'affwp_cf7_referral_description', $description, $form_id, $contactform, $result );

			$reference       = $form_id . '-' . date_i18n( 'U' );
			$affiliate_id    = $this->get_affiliate_id( $reference );
			$referral_total  = $this->calculate_referral_amount( $base_amount, $reference, $product_id, $affiliate_id );
			$referral_id     = $this->insert_pending_referral( $referral_total, $reference, $description, $product_id );
			$referral_status = affiliate_wp()->settings->get( 'affwp_cf7_referral_default_status' );

			if ( ! empty( $referral_status ) && 'unpaid' === $referral_status ) {
				$this->complete_referral( $reference );
			}

			// Bail if PayPal add-on is not enabled.
			if ( ! $paypal ) {
				return false;
			}

			/**
			 * Provides the referral ID to PayPal transaction processes.
			 * This action is specific to the AffiliateWP Contact Form 7 integration.
			 *
			 * @param object $contactform The form object.
			 * @param object $result      The modified form object.
			 * @param int    $referral_id The referral ID.
			 *
			 * @since 2.0
			 */
			do_action( 'affwp_cf7_submit', $contactform, $result, $referral_id );
		}


	}

	/**
	 * Updates the referral status when a PayPal refund or transaction completion occurs,
	 * via the success or cancel pages provided in the PayPal add-ons.
	 *
	 * @param int    $current_page_id  The current page ID.
	 * @param mixed  $reference        The referral reference.
	 * @since 2.0
	 */
	public function mark_referral_complete( $current_page_id = 0, $reference = '' ) {

		$current_page_id = $this->get_current_page_id();
		$form_id         = ! empty( $_GET['form_id'] )     ? absint( $_GET['form_id'] )         : false;
		$referral_id     = ! empty( $_GET['referral_id'] ) ? absint( $_GET['referral_id'] )     : false;
		$txn_id          = ! empty( $_GET['tx'] )          ? sanitize_text_field( $_GET['tx'] ) : false;

		$paypal          = get_post_meta( $form_id, '_cf7pp_enable', true );

		// Bail if PayPal add-on is not enabled.
		if ( ! $paypal ) {
			return false;
		}

		if ( ! $form_id || ! $referral_id ) {
			$this->log( 'CF7 integration: The form ID or referral ID could not be determined.' );

			return false;
		}

		$return_url     = $this->return_url;
		$return_page_id = url_to_postid( $return_url );

		// Bail if not on the return page.
		if ( (int) $return_page_id !== (int) $current_page_id ) {
			$this->log( 'CF7 integration: The specified success page ID does not match the current page ID.' );

			return false;
		}

		$referral = affwp_get_referral( $referral_id );

		if( $referral ) {

			if( ! empty( $txn_id ) ) {
				$referral->set( 'reference', $txn_id, true );
			}

			$this->complete_referral( $referral );


		} else {

			$this->log( sprintf( 'CF7 integration: Referral could not be retrieved during mark_referral_complete(). ID given: %d.' ), $referral_id );

		}

	}

	/**
	 * Updates the status of the referral. Fires when the cancel page url is loaded from a PayPal transaction.
	 *
	 * @param  string $reference        The reference.
	 * @param  int    $current_page_id  The current page ID.
	 * @return mixed void|bool          Returns nothing if successful, returns boolean false if the
	 * @since 2.0                       current page ID does not match the PayPal cancel page ID.
	 *
	 */
	public function revoke( $current_page_id = 0, $reference = '' ) {

		$current_page_id = $this->get_current_page_id();
		$form_id         = ! empty( $_GET['form_id'] )     ? absint( $_GET['form_id'] )     : false;
		$referral_id     = ! empty( $_GET['referral_id'] ) ? absint( $_GET['referral_id'] ) : false;

		$paypal          = get_post_meta( $form_id, '_cf7pp_enable', true );

		// Bail if PayPal add-on is not enabled.
		if ( ! $paypal ) {
			return false;
		}

		if ( ! $form_id || ! $referral_id ) {
			$this->log( 'CF7 integration: The form ID or referral ID could not be determined.' );

			return false;
		}

		$cancel_url     = $this->cancel_url;
		$cancel_page_id = url_to_postid( $cancel_url );

		// Bail if not on the cancel page
		if ( (int) $cancel_page_id !== (int) $current_page_id ) {
			$this->log( 'CF7 integration: The specified cancel page ID does not match the current page ID.' );

			return false;

		}

		$referral = affwp_get_referral( $referral_id );

		if( $referral ) {

			$this->reject_referral( $referral );

		} else {

			$this->log( sprintf( 'CF7 integration: Referral could not be retrieved during revoke(). ID given: %d.' ), $referral_id );

		}

	}

	/**
	 * Generates a link to the associated Contact Form 7 form in the referral reference column.
	 *
	 * @param  int    $reference
	 * @param  object $referral
	 * @return string
	 * @since  2.0
	 *
	 */
	public function reference_link( $reference, $referral ) {

		if ( ! $referral ) {

			$this->log( 'CF7 integration: No referral data found when attempting to add a referral reference.' );

			return false;
		}

		// To provide a working link to the CF7 form,
		// the Unix date (stored as `sub_time` in submission args) is stripped from the reference string.

		if( false !== strpos( $reference, '-' ) ) {

			$form_id = strstr( $reference, '-', true );

		} else {

			return $reference; // Return transaction ID without a link

		}

		$url = admin_url( 'admin.php?page=wpcf7&action=edit&post=' . $form_id );

		return '<a href="' . esc_url( $url ) . '">' . $reference . '</a>';

	}

	/**
	 * Runs the check necessary to confirm this plugin is active.
	 *
	 * @since 2.5
	 *
	 * @return bool True if the plugin is active, false otherwise.
	 */
	function plugin_is_active() {
		return class_exists( 'WPCF7_ContactForm' );
	}
}

    new Affiliate_WP_Contact_Form_7();
