gocodebox/lifterlms

View on GitHub
includes/notifications/controllers/class.llms.notification.controller.upcoming.payment.reminder.php

Summary

Maintainability
C
7 hrs
Test Coverage
F
45%
<?php
/**
 * Notification Controller: Upcoming Payment Reminder
 *
 * @package LifterLMS/Notifications/Controllers/Classes
 *
 * @since 5.2.0
 * @version 5.2.0
 */

defined( 'ABSPATH' ) || exit;

/**
 * Notification Controller: Upcoming Payment Reminder
 *
 * @since 5.2.0
 */
class LLMS_Notification_Controller_Upcoming_Payment_Reminder extends LLMS_Abstract_Notification_Controller {

    /**
     * Trigger Identifier
     *
     * @var string
     */
    public $id = 'upcoming_payment_reminder';

    /**
     * Action hooks used to trigger sending of the notification
     *
     * @var array
     */
    protected $action_hooks = array(
        'llms_send_upcoming_payment_reminder_notification',
    );

    /**
     * Determines if test notifications can be sent
     *
     * @var bool
     */
    protected $testable = array(
        'basic' => false,
        'email' => true,
    );

    /**
     * Number of accepted arguments passed to the callback function
     *
     * @var integer
     */
    protected $action_accepted_args = 2;

    /**
     * Add an action to trigger the notification to send
     *
     * @since 5.2.0
     *
     * @return void
     */
    protected function add_actions() {

        parent::add_actions();

        // Add actions to recurring payment scheduling/unscheduling.
        add_action( 'llms_charge_recurring_payment_scheduled', array( $this, 'schedule_upcoming_payment_reminders' ), 10, 2 );
        add_action( 'llms_charge_recurring_payment_unscheduled', array( $this, 'unschedule_upcoming_payment_reminders' ) );

    }

    /**
     * Callback function called when the upcoming payment reminder notification is fired
     *
     * @since 5.2.0
     *
     * @param int    $order_id WP Post ID of the order.
     * @param string $type     The notification type identifier.
     * @return boolean
     */
    public function action_callback( $order_id = null, $type = null ) {

        // Make sure order_id and type have been provided.
        if ( ! $order_id || ! $type ) {
            return false;
        }

        // These checks are basically the same we do in LLMS_Controller_Orders::recurring_charge().

        // Recurring payments disabled as a site feature when in staging mode.
        if ( ! LLMS_Site::get_feature( 'recurring_payments' ) ) {
            return false;
        }

        $order = llms_get_post( $order_id );

        // Make sure the order still exists.
        if ( ! $order || ! is_a( $order, 'LLMS_Order' ) ) {
            return false;
        }

        $user_id = $order->get( 'user_id' );

        // Check the user still exists.
        if ( ! get_user_by( 'id', $user_id ) ) {
            return false;
        }

        // Ensure Gateway is still available and supports recurring payments.
        $gateway = $order->get_gateway();
        if ( is_wp_error( $gateway ) || ! $gateway->supports( 'recurring_payments' ) ) {
            return false;
        }

        $this->user_id = $user_id;
        $this->post_id = $order->get( 'id' );

        $this->send( false, array( $type ) );

        return true;

    }

    /**
     * Takes a subscriber type (student, author, etc) and retrieves a User ID.
     *
     * @since 5.2.0
     *
     * @param string $subscriber Subscriber type string.
     * @return int|false
     */
    protected function get_subscriber( $subscriber ) {

        switch ( $subscriber ) {

            case 'author':
                $order = llms_get_post( $this->post_id );
                if ( ! is_a( $order, 'LLMS_Order' ) ) {
                    return false;
                }
                $product = $order->get_product();
                if ( is_a( $product, 'WP_Post' ) ) {
                    return false;
                }
                $uid = $product->get( 'author' );
                break;

            case 'student':
                $uid = $this->user_id;
                break;

            default:
                $uid = false;

        }

        return $uid;

    }

    /**
     * Get the translatable title for the notification
     *
     * Used on settings screens.
     *
     * @since 5.2.0
     *
     * @return string
     */
    public function get_title() {
        return __( 'Upcoming Payment Reminder', 'lifterlms' );
    }

    /**
     * Setup the subscriber options for the notification
     *
     * @since 5.2.0
     *
     * @param string $type The notification type identifier.
     * @return array
     */
    protected function set_subscriber_options( $type ) {

        $options = array();

        switch ( $type ) {

            case 'basic':
                $options[] = $this->get_subscriber_option_array( 'student', 'yes' );
                break;

            case 'email':
                $options[] = $this->get_subscriber_option_array( 'author', 'no' );
                $options[] = $this->get_subscriber_option_array( 'student', 'yes' );
                $options[] = $this->get_subscriber_option_array( 'custom', 'no' );
                break;

        }

        return $options;

    }

    /**
     * Cancels scheduled upcoming payment reminder notifications
     *
     * Does nothing if no payments are scheduled.
     *
     * @since 5.2.0
     *
     * @param LLMS_Order $order Instance of the LLMS_Order which we'll schedule the payment reminder for.
     * @return void
     */
    public function unschedule_upcoming_payment_reminders( $order ) {

        $types = array_keys( $this->get_supported_types() );

        foreach ( $types as $type ) {
            $this->unschedule_upcoming_payment_reminder( $order, $type );
        }

    }

    /**
     * Cancels a scheduled upcoming payment reminder notification type
     *
     * Does nothing if no payments are scheduled.
     *
     * @since 5.2.0
     *
     * @param LLMS_Order $order Instance of the LLMS_Order which we'll schedule the payment reminder for.
     * @param string     $type  The notification type identifier.
     * @return void
     */
    public function unschedule_upcoming_payment_reminder( $order, $type ) {

        $action_args = $this->get_recurring_payment_reminder_action_args( $order, $type );

        if ( as_next_scheduled_action( 'llms_send_upcoming_payment_reminder_notification', $action_args ) ) {
            as_unschedule_action( 'llms_send_upcoming_payment_reminder_notification', $action_args );
        }

    }

    /**
     * Schedule upcoming payment reminder notification
     *
     * @since 5.2.0
     *
     * @param LLMS_Order $order        Instance of the LLMS_Order which we'll schedule the payment reminder for.
     * @param int        $payment_date Optional. The upcoming payment due date in Unix time format and UTC. Default is 0.
     *                                 When not provided it'll be calculated from the order.
     * @return array
     */
    public function schedule_upcoming_payment_reminders( $order, $payment_date = 0 ) {

        $types  = array_keys( $this->get_supported_types() );
        $return = array();
        foreach ( $types as $type ) {
            $return[ $type ] = $this->schedule_upcoming_payment_reminder( $order, $type, $payment_date );
        }

        return $return;

    }

    /**
     * Schedule upcoming payment reminder notification
     *
     * @since 5.2.0
     *
     * @param LLMS_Order $order        Instance of the LLMS_Order which we'll schedule the payment reminder for.
     * @param string     $type         The notification type identifier.
     * @param int        $payment_date Optional. The upcoming payment due date in Unix time format and UTC. Default is 0.
     *                                 When not provided it'll be calculated from the order.
     * @return WP_Error|int WP_Error either if there's no reminder date or if it's passed. Otherwise returns the return value of `as_schedule_single_action`: the action's ID.
     */
    public function schedule_upcoming_payment_reminder( $order, $type, $payment_date = 0 ) {

        $action_args = $this->get_recurring_payment_reminder_action_args( $order, $type );

        // Unschedule upcoming payment reminder (does nothing if no action scheduled).
        $this->unschedule_upcoming_payment_reminder( $order, $type );

        // Convert our reminder date to Unix Time and UTC before passing to the scheduler.
        $reminder_date = $this->get_upcoming_payment_reminder_date( $order, $type, $payment_date );

        // If no reminder date.
        if ( is_wp_error( $reminder_date ) ) {
            return $reminder_date;
        }

        // Or reminder date set in the past.
        if ( $reminder_date < llms_current_time( 'U', true ) ) {
            return new WP_Error( 'upcoming-payment-reminder-passed', __( 'Upcoming payment reminder passed', 'lifterlms' ) );
        }

        // Schedule upcoming payment reminder.
        return as_schedule_single_action(
            $reminder_date,
            'llms_send_upcoming_payment_reminder_notification',
            $action_args
        );

    }

    /**
     * Retrieve the date to remind user before actual payment
     *
     * @since 5.2.0
     *
     * @param LLMS_Order $order        Instance of the LLMS_Order which we'll schedule the payment reminder for.
     * @param string     $type         The notification type identifier.
     * @param integer    $payment_date Optional. The upcoming payment due date in Unix time format and UTC. Default is 0.
     *                                 When not provided it'll be calculated from the order.
     * @return WP_Error|integer Returns a WP_Error if there's no payment scheduled, otherwise the reminder date in Unix format and UTC.
     */
    private function get_upcoming_payment_reminder_date( $order, $type, $payment_date = 0 ) {

        $next_payment_date = $payment_date ? $payment_date : $order->get_recurring_payment_due_date_for_scheduler();
        if ( is_wp_error( $next_payment_date ) ) {
            return $next_payment_date;
        }

        /**
         * Filters the number of days before the upcoming payment due date when to notify the customer
         *
         * The dynamic portion of this filter, `$this->id`, refers to the notification trigger identifier.
         *
         * @since 5.2.0
         *
         * @param integer    $days  The number of days before the upcoming payment due date when to notify the customer.
         * @param LLMS_Order $order Order object.
         * @param string     $type  The notification type identifier.
         */
        $days = apply_filters( "llms_notification_{$this->id}_reminder_days", $this->get_reminder_days( $type ), $order, $type );

        // Sanitize: makes sure it's always a negative number.
        $days = -1 * max( 1, absint( $days ) );

        /**
         * Filters the next upcoming payment reminder date
         *
         * The dynamic portion of this filter, `$this->id`, refers to the notification trigger identifier.
         *
         * @since 5.2.0
         *
         * @param integer    $upcoming_payment_reminder_time Unix timestamp for the next payment due date.
         * @param LLMS_Order $order                          Order object.
         * @param string     $type                           The notification type identifier.
         */
        $upcoming_payment_reminder_time = apply_filters( "llms_notification_{$this->id}_reminder_date", strtotime( "{$days} day", $next_payment_date ), $order, $type );

        return $upcoming_payment_reminder_time;

    }


    /**
     * Retrieve arguments passed to order-related events processed by the action scheduler
     *
     * @since 5.2.0
     *
     * @param LLMS_Order $order Instance of the LLMS_Order which we'll schedule the payment reminder for.
     */
    private function get_recurring_payment_reminder_action_args( $order, $type ) {
        return array(
            'order_id' => $order->get( 'id' ),
            'type'     => $type,
        );
    }

    /**
     * Set array of additional options to be added to the notification view in the admin panel
     *
     * @since 5.2.0
     *
     * @param string $type Type of the notification.
     * @return array
     */
    protected function set_additional_options( $type ) {

        return array(
            array(
                'id'                => $this->get_option_name( $type . '_reminder_days' ),
                'title'             => __( 'Reminder days', 'lifterlms' ),
                'desc'              => '<br>' . __( 'The number of days before the upcoming payment due date when to notify the customer.', 'lifterlms' ),
                'type'              => 'number',
                'value'             => $this->get_reminder_days( $type ),
                'custom_attributes' => array(
                    'min' => 1,
                ),
            ),
        );

    }

    /**
     * Get an array of LifterLMS Admin Page settings to send test notifications
     *
     * Retrieves 25 recurring orders with an existing next payment date.
     *
     * @since 5.2.0
     *
     * @param string $type Notification type [basic|email].
     * @return array
     */
    public function get_test_settings( $type ) {

        $query = new WP_Query(
            array(
                'post_type'      => 'llms_order',
                'posts_per_page' => 25,
                'post_status'    => array( 'llms-active', 'llms-failed', 'llms-on-hold', 'llms-pending', 'llms-pending-cancel' ),
                'meta_query'     => array(
                    'relation' => 'and',
                    array(
                        'key'     => '_llms_order_type',
                        'value'   => 'recurring',
                        'compare' => '=',
                    ),
                    array(
                        'key'     => '_llms_date_next_payment',
                        'compare' => 'EXISTS',
                    ),
                ),
                'no_found_rows'  => true,
                'order_by'       => 'ID',
            )
        );

        $options = array(
            '' => '',
        );
        foreach ( $query->posts as $post ) {
            $order   = llms_get_post( $post );
            $student = llms_get_student( $order->get( 'user_id' ) );
            if ( $order && $student ) {
                $options[ $order->get( 'id' ) ] = esc_attr(
                    sprintf(
                        // Translators: %1$d = The Order ID; %2$s The customer's full name; %3$s The product title.
                        __( 'Order #%1$d from %2$s for "%3$s"', 'lifterlms' ),
                        $order->get( 'id' ),
                        $student->get_name(),
                        $order->get( 'product_title' )
                    )
                );
            }
        }

        return array(
            array(
                'class'             => 'llms-select2',
                'custom_attributes' => array(
                    'data-allow-clear' => true,
                    'data-placeholder' => __( 'Select a recurring order', 'lifterlms' ),
                ),
                'default'           => '',
                'id'                => 'order_id',
                'desc'              => '<br/>' . __( 'Send yourself a test notification using information from the selected recurring order.', 'lifterlms' ),
                'options'           => $options,
                'title'             => __( 'Send a Test', 'lifterlms' ),
                'type'              => 'select',
            ),
        );

    }

    /**
     * Send a test notification to the currently logged in users
     *
     * @since 5.2.0
     *
     * @param string $type Notification type [basic|email].
     * @param array  $data Array of test notification data as specified by $this->get_test_data().
     *
     * @return int|false
     */
    public function send_test( $type, $data = array() ) {

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

        $order         = llms_get_post( $data['order_id'] );
        $this->user_id = $order->get( 'user_id' );
        $this->post_id = $order->get( 'id' );

        return parent::send_test( $type );

    }

    /**
     * Undocumented function
     *
     * @since 5.2.0
     *
     * @param string $type    The notification type identifier.
     * @param int    $default Opional. The default value. Default is `1`.
     * @return int
     */
    private function get_reminder_days( $type, $default = 1 ) {
        return $this->get_option( $type . '_reminder_days', $default );
    }
}

return LLMS_Notification_Controller_Upcoming_Payment_Reminder::instance();