includes/models/model.llms.coupon.php
<?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 );
}
}