<?php
/**
 * Integrations: Paid Member Subscriptions
 *
 * @package     AffiliateWP
 * @subpackage  Integrations
 * @copyright   Copyright (c) 2014, Sandhills Development, LLC
 * @license     http://opensource.org/licenses/gpl-2.0.php GNU Public License
 * @since       1.2
 */

#[\AllowDynamicProperties]

/**
 * Implements an integration for Paid Member Subscriptions.
 *
 * @since 1.2
 *
 * @see Affiliate_WP_Base
 */
class Affiliate_WP_PMS extends Affiliate_WP_Base {

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

    /**
     * Get things started
     *
     * @access public
     * @since  2.0
    */
    public function init() {

        // Subscription plan custom referral rate
        add_action( 'pms_view_meta_box_subscription_details_bottom', array( $this, 'add_subscription_view_rate_meta' ) );
        add_action( 'pms_save_meta_box_pms-subscription', array( $this, 'save_subscription_rate_meta' ) );

        // Reference link
        add_filter( 'affwp_referral_reference_column', array( $this, 'reference_link' ), 10, 2 );

        // Discount codes affiliate tracking
        add_action( 'pms_view_meta_box_discount_codes_bottom', array( $this, 'add_discount_code_view_affiliate_tracking' ) );
        add_action( 'pms_save_meta_box_pms-discount-codes', array( $this, 'save_discount_code_affiliate_tracking_meta' ) );

        add_action( 'pms_register_payment', array( $this, 'add_pending_referral_on_register' ) );
        add_action( 'pms_payment_updated', array( $this, 'complete_pending_referral_on_payment_complete' ), 10, 3 );
        add_action( 'pms_payment_updated', array( $this, 'reject_referral_on_payment_refunded' ), 10, 3 );

	    add_action( 'pms_payment_update', array( $this, 'complete_pending_referral_on_payment_complete_new' ), 10, 3 );
	    add_action( 'pms_payment_update', array( $this, 'reject_referral_on_payment_refunded_new' ), 10, 3 );

        // Add referral for a free membership plan.
        add_action( 'pms_after_checkout_is_processed', array( $this, 'add_referral_for_free_membership' ), 10, 2 );

    }

    /**
     * Adds the individual subscription plan referral rate field
     *
     * @access public
     * @since  2.0
     * @param  int $subscription_id
     */
    public function add_subscription_view_rate_meta( $subscription_id ) {

        if ( empty( $subscription_id ) ) {
            return;
        }

        $subscription_rate = get_post_meta( $subscription_id, '_affwp_pms_subscription_rate', true );
        ?>

        <div class="pms-meta-box-field-wrapper">
            <label for="affwp_subscription_rate" class="pms-meta-box-field-label"><?php echo __( 'Referral Rate', 'affiliate-wp' ); ?></label>
            <input type="text" id="affwp_subscription_rate" name="affwp_pms_subscription_rate" value="<?php echo esc_attr( $subscription_rate ) ?>" />
            <p class="description"><?php echo __( 'This rate will be used to calculate affiliate earnings when members register their account with this subscription. Leave blank to use the site default referral rate.', 'affiliate-wp' ); ?></p>
        </div>

        <?php
    }

    /**
     * Saves the individual subscription plan referral rate
     *
     * @access public
     * @since  2.0
     * @param  int $subscription_id
     */
    public function save_subscription_rate_meta( $subscription_id ) {

        if (  empty( $subscription_id ) ) {
            return;
        }

        $rate = isset( $_POST['affwp_pms_subscription_rate'] ) ? sanitize_text_field( $_POST['affwp_pms_subscription_rate'] ) : '';

        update_post_meta( $subscription_id, '_affwp_pms_subscription_rate', $rate );

    }

    /**
     * Adds the affiliate tracking field in the add/edit discount code meta-box
     *
     * @access public
     * @since  2.0
     * @param  int $discount_code_id
     */
    public function add_discount_code_view_affiliate_tracking( $discount_code_id ) {

        if ( empty( $discount_code_id ) ) {
            return;
        }

        add_filter( 'affwp_is_admin_page', '__return_true' );
        affwp_admin_scripts();

        $user_id      = '';
        $user_name    = '';
        $affiliate_id = get_post_meta( $discount_code_id, '_affwp_pms_affiliate_id', true );

        if ( $affiliate_id ) {
            $user_id   = affwp_get_affiliate_user_id( $affiliate_id );
            $user      = get_userdata( $user_id );
            $user_name = $user ? $user->user_login : '';
        }

        ?>

        <div class="pms-meta-box-field-wrapper">
            <label for="user_name" class="pms-meta-box-field-label"><?php echo __( 'Affiliate Discount?', 'affiliate-wp' ); ?></label>
            <input type="text" name="affwp_pms_user_name" id="user_name" value="<?php echo esc_attr( $user_name ); ?>" class="affwp-user-search" data-affwp-status="active" autocomplete="off" style="min-width: 250px;"/>
            <p class="description"><?php echo __( 'If you would like to connect this discount to an affiliate, enter the name of the affiliate it belongs to.', 'affiliate-wp' ); ?></p>
        </div>

        <?php
    }

    /**
     * Saves the affiliate id associated with the discount code
     *
     * @access public
     * @since  2.0
     * @param  int $discount_code_id
     */
    public function save_discount_code_affiliate_tracking_meta( $discount_code_id ) {

        if ( empty( $discount_code_id ) ) {
            return;
        }

        if ( empty( $_POST['affwp_pms_user_name'] ) ) {
            delete_post_meta( $discount_code_id, '_affwp_pms_affiliate_id' );
            return;
        }

        if ( empty( $_POST['affwp_pms_user_id'] ) && empty( $_POST['affwp_pms_user_name'] ) ) {
            return;
        }

        $data = affiliate_wp()->utils->process_request_data( $_POST, 'affwp_pms_user_name' );

        $affiliate_id = affwp_get_affiliate_id( $data['user_id'] );

        update_post_meta( $discount_code_id, '_affwp_pms_affiliate_id', $affiliate_id );

    }

	/**
	 * Adds a pending referral when registering to a subscription plan
	 * This handles only first time subscriptions, on the register form
	 *
	 * @access public
	 * @since  2.0
	 * @param  array $payment_data
	 */
	public function add_pending_referral_on_register( $payment_data ) {
		// Get payment ID.
		if ( isset( $payment_data['payment_id'] ) ) {
			$payment_id = $payment_data['payment_id'];
		} else {
			$payment_id = $payment_data['user_data']['user_id'] . '|' . $payment_data['subscription_data']['subscription_plan_id'];
		}

		// Get description.
		$description = $payment_data['user_data']['subscription']->name;

		/*
		* Handle discount code referral
		*/
		$affiliate_discount = false;
		if ( ! empty( $payment_data['discount_code'] ) ) {
			$this->log( 'PMS: Discount code used, determining if it is an affiliate discount code' );

			$discount = null;

			// Attempt to retrieve the discount object using the available function.
			if ( function_exists( 'pms_in_get_discount_by_code' ) ) {
				// Use the newer function if available.
				$discount = pms_in_get_discount_by_code( $payment_data['discount_code'] );
			} elseif ( function_exists( 'pms_get_discount_by_code' ) ) {
				// Fallback to the older function for backward compatibility.
				$discount = pms_get_discount_by_code( $payment_data['discount_code'] );
			}

			if ( is_object( $discount ) ) {
				// Set base amount as the discounted value
				$base_amount = $payment_data['amount'];
				$affiliate_id = get_post_meta( $discount->id, '_affwp_pms_affiliate_id', true );

				if ( ! empty( $affiliate_id ) ) {
					$this->log( 'PMS: Discount code used that belongs to affiliate ID ' . $affiliate_id );

					if ( ! affiliate_wp()->tracking->is_valid_affiliate( $affiliate_id ) ) {
							$this->log( 'PMS: Referral not created because affiliate is invalid.' );
					} else {
							$this->affiliate_id = $affiliate_id;
							$affiliate_discount = true;

							$this->log( 'PMS: Discount code used that belongs to affiliate ID ' . $affiliate_id . ' will be used to generate referral based on amount ' . $base_amount );
					}
				}
			} else {
				$this->log( 'PMS Error: Unable to retrieve discount object. Required functions pms_get_discount_by_code and pms_in_get_discount_by_code are missing.' );
			}
		}

		// Do nothing if we have no referral and no affiliate on discount
		if ( ! $this->was_referred() && ! $affiliate_discount ) {
			return; // PMS: Referral creation stopped because customer was not referred and no affiliate discount was detected.
		}

		// Create draft referral.
		$referral_id = $this->insert_draft_referral(
			$this->affiliate_id,
			array(
				'reference'          => $payment_id,
				'description'        => $description,
				'is_coupon_referral' => $affiliate_discount,
			)
		);
		if ( ! $referral_id ) {
			$this->log( 'Draft referral creation failed.' );
			return;
		}

		// Allow referrals only on register subscription.
		$allowed_form_locations = array( 'register', 'register_email_confirmation', 'new_subscription', 'renew_subscription', 'upgrade_subscription' );
		if ( ! in_array( $payment_data['form_location'], $allowed_form_locations ) ) {
			$this->log( 'PMS: Referral not created because the used form, ' . $payment_data['form_location'] . ', is not one of the allowed form locations' );
			$this->mark_referral_failed( $referral_id );
			return;
		}


		if ( $this->was_referred() && ! $affiliate_discount ) {

				/**
				 * Check to see if the subscriber is also an affiliate. Return if true.
				 */
				if ( $this->is_affiliate_email( $payment_data['user_data']['user_id'] ) ) {
					$this->log( 'PMS: Referral not created because affiliate\'s own account was used.' );
					$this->mark_referral_failed( $referral_id );
					return;
				}

		}

		// Set base amount for referral
		if ( ! isset( $base_amount ) ) {
				$base_amount = $payment_data['amount'];
		}

		// If the base amount is zero and it's set to ignore zero amounts, exit
		if ( 0 == $base_amount && affiliate_wp()->settings->get( 'ignore_zero_referrals' ) ) {
			$this->log( 'PMS: Referral not created due to 0.00 amount.' );
			$this->mark_referral_failed( $referral_id );
			return;
		}

		// Calculate the referral amount
		$referral_amount = $this->calculate_referral_amount( $base_amount, $payment_id, $payment_data['user_data']['subscription']->id, $this->affiliate_id );

		$this->log( 'PMS: Referral calculated for affiliate ID ' . $this->affiliate_id . ' in the amount of ' . $referral_amount );

		// Hydrates the previously created referral.
		$this->hydrate_referral(
			$referral_id,
			array(
				'status'             => 'pending',
				'amount'             => $referral_amount,
				'is_coupon_referral' => ! empty( $payment_data['discount_code'] ),
				'custom'             => array(
					'is_coupon_referral' => ! empty( $payment_data['discount_code'] ),
				),
			)
		);
		$this->log( sprintf( 'Paid Member Subscriptions referral #%d updated to pending successfully.', $referral_id ) );

		// If the base amount is zero complete the referral.
		if ( 0 == $base_amount ) {
			$this->complete_referral( $payment_id );
		}
	}

	/**
	 * Adds a referral when registering to a free subscription plan.
	 * This handles only first time subscriptions, on the register form.
	 *
	 * @access public
	 * @since  2.2.12
	 * @param  array $subscription
	 * @param  string $form_location
	 */
    public function add_referral_for_free_membership( $subscription, $form_location ) {

	    $subscription_id = $subscription->subscription_plan_id;

	    $subscription_plan = pms_get_subscription_plan( $subscription_id );

	    if ( ( $subscription_plan->price == 0 ) && ( $subscription_plan->sign_up_fee == 0 ) ) {

		    // Do nothing if we have no referral
		    if ( ! $this->was_referred() ) {
			    $this->log( 'PMS: Referral creation stopped because customer was not referred and no affiliate discount was detected' );
			    return;
		    }

				// Get payment ID.
				$payment_id = $subscription->user_id . '|' . $subscription->subscription_plan_id;

				// Get description.
				$description = $subscription_plan->name;

				// Create draft referral.
				$referral_id = $this->insert_draft_referral(
					$this->affiliate_id,
					array(
						'reference'   => $payment_id,
						'description' => $description,
					)
				);
				if ( ! $referral_id ) {
					$this->log( 'Draft referral creation failed.' );
					return;
				}

				/**
				 * Check to see if the subscriber is also an affiliate. Return if true.
				 */
				if ( $this->is_affiliate_email( $subscription->user_id ) ) {
					$this->log( 'PMS: Referral not created because affiliate\'s own account was used.' );
					$this->mark_referral_failed( $referral_id );
					return;
				}

				// Allow referrals only on register subscription
		    $allowed_form_locations = array( 'register', 'register_email_confirmation', 'new_subscription', 'renew_subscription', 'upgrade_subscription' );
		    if ( ! in_array( $form_location, $allowed_form_locations ) ) {
			    $this->log( 'PMS: Referral not created because the used form, ' . $form_location . ', is not one of the allowed form locations' );
					$this->mark_referral_failed( $referral_id );
			    return;
		    }

		    $base_amount = $subscription_plan->price;

		    // If the base amount is zero and it's set to ignore zero amounts, exit
		    if ( 0 == $base_amount && affiliate_wp()->settings->get( 'ignore_zero_referrals' ) ) {
			    $this->log( 'PMS: Referral not created due to 0.00 amount.' );
					$this->mark_referral_failed( $referral_id );
			    return;
		    }

		    // Calculate the referral amount
		    $referral_amount = $this->calculate_referral_amount( $base_amount, $payment_id, $subscription_plan->id, $this->affiliate_id );

		    $this->log( 'PMS: Referral calculated for affiliate ID ' . $this->affiliate_id . ' in the amount of ' . $referral_amount );

				// Hydrates the previously created referral.
				$this->hydrate_referral(
					$referral_id,
					array(
						'status'             => 'pending',
						'amount'             => $referral_amount,
						'is_coupon_referral' => ! empty( $payment_data['discount_code'] ),
						'custom'             => array(
							'is_coupon_referral' => ! empty( $payment_data['discount_code'] ),
						),
					)
				);
				$this->log( sprintf( 'Paid Member Subscriptions referral #%d updated to pending successfully.', $referral_id ) );

		    $this->complete_referral( $payment_id );

	    }

    }

    /**
     * Completes the referral when the payment is updated
     *
     * @access public
     * @since  2.0
     * @param  bool $updated
     * @param  array $data
     * @param  int $payment_id
     */
    public function complete_pending_referral_on_payment_complete( $updated, $data, $payment_id ) {

        if ( ! $updated ) {
            return;
        }

        if ( empty( $updated['status'] ) ) {
            return;
        }

        if ( $updated['status'] == 'completed' ) {
            $this->log( 'PMS: Referral for payment ID ' . $payment_id . ' about to be marked as complete' );
            $this->complete_referral( $payment_id );
        } else {
            $this->log( 'PMS: Referral for payment ID ' . $payment_id . ' not marked as complete because payment status is not completed' );
        }

    }

    /**
     * Rejects a referral when the status of the payment is set to refunded
     *
     * @access public
     * @since  2.0
     * @param  bool $updated
     * @param  array $data
     * @param  int $payment_id
     */
    public function reject_referral_on_payment_refunded( $updated, $data, $payment_id ) {

        if ( ! affiliate_wp()->settings->get( 'revoke_on_refund' ) ) {
            return;
        }

        if ( ! $updated ) {
            return;
        }

        if ( empty( $updated['status'] ) ) {
            return;
        }

        if ( $updated['status'] == 'refunded' ) {
            $this->log( 'PMS: Referral for payment ID ' . $payment_id . ' about to be marked as rejected during refund' );
            $this->reject_referral( $payment_id );
        }

    }

	/**
	 * Completes the referral when the payment is updated
	 *
	 * Compatible with Paid Member Subscriptions 1.6.2+
	 *
	 * @since  2.9.3
	 * @access public
	 *
	 * @param  int $payment_id
	 * @param  array $data
	 * @param  array $old_data
	 */
	public function complete_pending_referral_on_payment_complete_new( $payment_id, $data, $old_data ) {

		if ( ! $data ) {
			return;
		}

		if ( empty( $data['status'] ) ) {
			return;
		}

		if ( 'completed' === strtolower( $data['status'] ) ) {
			$this->log( 'PMS: Referral for payment ID ' . $payment_id . ' about to be marked as complete' );
			$this->complete_referral( $payment_id );
		} else {
			$this->log( 'PMS: Referral for payment ID ' . $payment_id . ' not marked as complete because payment status is not completed' );
		}
	}

	/**
	 * Rejects a referral when the status of the payment is set to refunded
	 *
	 * Compatible with Paid Member Subscriptions 1.6.2+
	 *
	 * @since  2.9.3
	 * @access public
	 *
	 * @param  int $payment_id
	 * @param  array $data
	 * @param  array $old_data
	 */
	public function reject_referral_on_payment_refunded_new( $payment_id, $data, $old_data ) {

		if ( ! affiliate_wp()->settings->get( 'revoke_on_refund' ) ) {
			return;
		}

		if ( ! $data ) {
			return;
		}

		if ( empty( $data['status'] ) ) {
			return;
		}

		if ( 'refunded' === strtolower( $data['status'] ) ) {
			$this->log( 'PMS: Referral for payment ID ' . $payment_id . ' about to be marked as rejected during refund' );
			$this->reject_referral( $payment_id );
		}
	}

    /**
     * Creates a link to the payment referenced in the referral
     * @since  2.0
     * @access public
     */
    public function reference_link( $reference, $referral ) {

        if ( empty( $referral->context ) || 'pms' != $referral->context ) {
            return $reference;
        }

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

		    $reference_array = explode( '|', $reference );
		    $user_id         = $reference_array[0];

		    $url = admin_url( 'admin.php?page=pms-members-page&subpage=edit_member&member_id=' . $user_id );

	    } else {

		    $url = admin_url( 'admin.php?page=pms-payments-page&pms-action=edit_payment&payment_id=' . $reference );

	    }

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

    }

    /**
     * Retrieves the rate and type for a specific subscription plan
     *
     * @access public
     * @since  2.0
     * @param  int $subscription_id
     * @param  array $args
     * @return float
     */
    public function get_product_rate( $subscription_id = 0, $args = array() ) {

        $rate = get_post_meta( $subscription_id, '_affwp_pms_subscription_rate', true );

        if ( empty( $rate ) || ! is_numeric( $rate ) ) {
            $rate = null;
        }

	    /** This filter is documented in includes/integrations/class-base.php */
        $rate = apply_filters( 'affwp_get_product_rate', $rate, $subscription_id, $args, $this->affiliate_id, $this->context );

	    $rate = affwp_sanitize_referral_rate( $rate );

	    return $rate;

    }

	/**
	 * Retrieves the customer details for a payment.
	 *
	 * @since 2.2.7
	 *
	 * @param int $payment_id The ID of the payment to retrieve customer details for.
	 * @return array An array of the customer details
	 */
	public function get_customer( $payment_id = 0 ) {

		$customer = array();

		if ( false !== strpos( $payment_id, '|' ) ) {

			$payment_id = explode( '|', $payment_id );

			$user_id = $payment_id[0];

			$user = get_userdata( $user_id );

			if ( $user ) {

				$customer['user_id']    = $user->ID;
				$customer['email']      = $user->user_email;
				$customer['first_name'] = $user->first_name;
				$customer['last_name']  = $user->last_name;
				$customer['ip']         = affiliate_wp()->tracking->get_ip();

			}

		} else {

			if ( function_exists( 'pms_get_payment' ) ) {

				$payment = pms_get_payment( $payment_id );

				if ( $payment->user_id ) {

					$user = get_userdata( $payment->user_id );

					if ( $user ) {

						$customer['user_id']    = $payment->user_id;
						$customer['email']      = $user->user_email;
						$customer['first_name'] = $user->first_name;
						$customer['last_name']  = $user->last_name;
						$customer['ip']         = $payment->ip_address;

					}

				}

			}

		}

		return $customer;
	}

	/**
	 * 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( 'Paid_Member_Subscriptions' );
	}
}

new Affiliate_WP_PMS();
