gocodebox/lifterlms

View on GitHub
includes/models/model.llms.coupon.php

Summary

Maintainability
A
25 mins
Test Coverage
F
55%
<?php
/**
 * LifterLMS Coupon Model
 *
 * @package LifterLMS/Models/Classes
 *
 * @since 3.0.0
 * @version 3.24.0
 */

defined( 'ABSPATH' ) || exit;

/**
 * LLMS_Coupon model.
 *
 * @property  $coupon_amount  (float)  Amount to subtract from the price when using the coupon. Used with $discount_type to determine the type of discount
 * @property  $coupon_courses  (array)  Array of Course IDs the coupon can be used against
 * @property  $coupon_membership  (array) Array of Membership IDs the coupon can be used against
 * @property  $description  (string)  A string of text. Used as an internal note field  .
 * @property  $discount_type  (string)  Determines the discount type [dollar|percent]
 * @property  $enable_trial_discount  (yes/no)  Enables an optional additional amount field to apply to the Trial Price of access plans with a trial [yes|no]
 * @property  $expiration_date  (string)  Date String describing a date after which the coupon can no longer be used. Format: m/d/Y
 * @property  $plan_type  (string)  Determine the type of plans the coupon can be used with . [any|one-time|recurring]
 * @property  $title  (string)  Coupon Code / Post Title
 * @property  $trial_amount  (float)  Amount to subtract from the trial price when using the coupon. Used with $discount_type to determine the type of discount
 * @property  $usage_limit  (int)  Amount of times the coupon can be used.
 *
 * @since 3.0.0
 * @since 3.24.0 Unknown.
 */
class LLMS_Coupon extends LLMS_Post_Model {

    protected $properties = array(
        'coupon_amount'         => 'float',
        'coupon_courses'        => 'array',
        'coupon_membership'     => 'array',
        'description'           => 'string',
        'discount_type'         => 'string',
        'enable_trial_discount' => 'yesno',
        'expiration_date'       => 'string',
        'plan_type'             => 'string',
        'trial_amount'          => 'float',
        'usage_limit'           => 'absint',
    );

    protected $db_post_type    = 'llms_coupon';
    protected $model_post_type = 'coupon';

    /**
     * Determine if the coupon can be applied to an access plan
     *
     * @since    3.0.0
     * @version  3.0.0
     * @param    int|obj $plan_id  WP Post ID of the LLMS Access Plan or an instance of LLMS_Access_Plan
     * @return  bool
     */
    public function applies_to_plan( $plan_id ) {

        if ( $plan_id instanceof LLMS_Access_Plan ) {
            $plan = $plan_id;
        } else {
            $plan = new LLMS_Access_Plan( $plan_id );
        }

        // Check if it can be applied to the plan's product first.
        if ( ! $this->applies_to_product( $plan->get( 'product_id' ) ) ) {
            return false;
        }

        // If the coupon can only be used with one-time plans and the plan is recurring.
        if ( 'one-time' === $this->get( 'plan_type' ) && $plan->is_recurring() ) {
            return false;
        }

        return true;
    }

    /**
     * Determine if a coupon can be applied to a specific product
     *
     * @since  3.0.0
     * @version  3.0.0
     * @param  int $product_id  WP Post ID of a LLMS Course or Membership
     * @return boolean     true if it can be applied, false otherwise
     */
    public function applies_to_product( $product_id ) {
        $products = $this->get_products();
        // No product restrictions.
        if ( empty( $products ) ) {
            return true;
        } else {
            return in_array( $product_id, $products );
        }
    }

    /**
     * Retrieve the timestamp of a coupon expiration date
     * Transforms the expiration date to a timestamp and adds 23 hours 59 minutes and 59 seconds to the date
     * Coupons expire end of day on the expiration date (EG: 2015-12-01 @ 23:59:59)
     *
     * @return   false|int
     * @since    3.19.0
     * @version  3.19.0
     */
    public function get_expiration_time() {
        $expires = $this->get_date( 'expiration_date', 'U' );
        if ( ! $expires ) {
            return false;
        }
        return ( (int) $expires + DAY_IN_SECONDS - 1 );
    }

    /**
     * Get the discount type for human reading and allow translation
     *
     * @since   3.0.0
     * @version 3.24.0
     * @return  string
     */
    public function get_formatted_discount_type() {
        switch ( $this->get_discount_type() ) {
            case 'percent':
                return __( 'Percentage Discount', 'lifterlms' );
            break;
            case 'dollar':
                return sprintf( _x( '%s Discount', 'flat rate coupon discount', 'lifterlms' ), get_lifterlms_currency_symbol() );
            break;
        }
    }

    /**
     * Get the formatted coupon amount with currency symbol and/or percentage symbol
     *
     * @since 3.0.0
     * @version 3.0.0
     * @param   string $amount key for the amount to format
     * @return  string
     */
    public function get_formatted_amount( $amount = 'coupon_amount' ) {
        $amount = $this->get( $amount );
        switch ( $this->get( 'discount_type' ) ) {
            case 'percent':
                $amount .= '%';
                break;
            case 'dollar':
                $amount = llms_price( $amount );
                break;
        }
        return $amount;
    }

    /**
     * Get an array of all products the coupon can be used with
     * Combines $this->coupon_courses & $this->coupon_membership
     *
     * @since 3.0.0
     * @version 3.0.0
     * @return  array
     */
    public function get_products() {
        return array_merge( $this->get_array( 'coupon_courses' ), $this->get_array( 'coupon_membership' ) );
    }

    /**
     * Get the number of remaining uses
     * calculated by subtracting # of uses from the usage limit
     *
     * @since  3.0.0
     * @version 3.0.0
     * @return string|int
     */
    public function get_remaining_uses() {

        $limit = $this->get( 'usage_limit' );

        // If usage is unlimited.
        if ( ! $limit ) {

            return _x( 'Unlimited', 'Remaining coupon uses', 'lifterlms' );

        } else {

            return $limit - $this->get_uses();

        }

    }

    /**
     * Get the number of times the coupon has been used
     *
     * @return   int
     * @since    3.0.0
     * @version  3.19.0
     */
    public function get_uses() {

        $query = new WP_Query(
            array(
                'meta_query'     => array(
                    array(
                        'key'   => $this->meta_prefix . 'coupon_code',
                        'value' => $this->get( 'title' ),
                    ),
                ),
                'post_status'    => 'any',
                'post_type'      => 'llms_order',
                'posts_per_page' => -1,
            )
        );

        return $query->post_count;

    }

    /**
     * Determine if the main (non-trial) price is discounted by this coupon
     *
     * @return   bool
     * @since    3.21.1
     * @version  3.21.1
     */
    public function has_main_discount() {
        return ( $this->get( 'coupon_amount' ) > 0 );
    }

    /**
     * Determine if a coupon has uses remaining
     *
     * @return boolean   true if uses are remaining, false otherwise
     */
    public function has_remaining_uses() {
        $uses = $this->get_remaining_uses();
        if ( is_numeric( $uses ) ) {
            return ( $uses >= 1 ) ? true : false;
        }
        return true;
    }

    /**
     * Determine if trial amount discount is enabled for the coupon
     *
     * @return  boolean
     * @since   3.0.0
     * @version 3.21.1
     */
    public function has_trial_discount() {
        return llms_parse_bool( $this->get( 'enable_trial_discount' ) );
    }

    /**
     * Determine if a coupon is expired
     *
     * @return  boolean   true if expired, false otherwise
     * @since   3.0.0
     * @version 3.19.0
     */
    public function is_expired() {
        $expires = $this->get_expiration_time();
        // No expiration date, can't expire.
        if ( ! $expires ) {
            return false;
        } else {
            return $expires < llms_current_time( 'timestamp' );
        }
    }

    /**
     * Perform all available validations and return a success or error message
     *
     * @param    int $plan_id  WP Post ID of an LLMS Access Plan
     * @return   WP_Error|true            If true, the coupon is valid, if WP_Error, there was an error
     * @since    3.0.0
     * @version  3.19.0
     */
    public function is_valid( $plan_id ) {

        $msg = false;

        $plan = new LLMS_Access_Plan( $plan_id );

        if ( ! $this->has_remaining_uses() ) {

            $msg = __( 'This coupon has reached its usage limit and can no longer be used.', 'lifterlms' );

        } elseif ( $this->is_expired() ) {

            $msg = sprintf( __( 'This coupon expired on %s and can no longer be used.', 'lifterlms' ), $this->get_date( 'expiration_date', 'F d, Y' ) );

        } elseif ( ! $this->applies_to_product( $plan->get( 'product_id' ) ) ) {

            $msg = sprintf( __( 'This coupon cannot be used to purchase "%s".', 'lifterlms' ), get_the_title( $plan->get( 'product_id' ) ) );

        } elseif ( ! $this->applies_to_plan( $plan ) ) {

            $msg = sprintf( __( 'This coupon cannot be used to purchase "%s".', 'lifterlms' ), $plan->get( 'title' ) );

        }

        // Error encountered.
        if ( $msg ) {

            $ret = new WP_Error();
            $ret->add( 'error', apply_filters( 'lifterlms_coupon_validation_error_message', $msg, $this ) );

        } else {

            $ret = true;

        }

        return apply_filters( 'llms_coupon_is_valid', $ret, $plan, $this );

    }

}