gocodebox/lifterlms

View on GitHub
includes/abstracts/llms.abstract.notification.view.php

Summary

Maintainability
D
1 day
Test Coverage
F
56%
<?php
/**
 * Notification View Abstract
 *
 * @package LifterLMS/Abstracts/Classes
 *
 * @since 3.8.0
 * @version 6.4.0
 */

defined( 'ABSPATH' ) || exit;

/**
 * LLMS_Abstract_Notification_View class.
 *
 * @since 3.8.0
 * @since 3.30.3 Explicitly define undefined properties.
 * @since 3.31.0 Add filter on `$basic_options` class property.
 * @since 3.37.19 Introduced the method `get_object()`. It'll allow extending classes
 *                 defining the way the object associated to the notification should be retrieved.
 *                 Use `in_array` with strict comparison where possible.
 */
abstract class LLMS_Abstract_Notification_View extends LLMS_Abstract_Options_Data {

    /**
     * Settings for basic notifications
     *
     * @var array
     */
    protected $basic_options = array(
        /**
         * Time in milliseconds to show a notification
         * before automatically dismissing it.
         */
        'auto_dismiss' => 0,
        /**
         * Enables manual dismissal of notifications.
         */
        'dismissible'  => false,
    );

    /**
     * @var string
     * @since 3.8.0
     */
    public $id;

    /**
     * Instance of the LLMS_Post_Model for the triggering post
     *
     * @var LLMS_Post_Model
     */
    protected $post;

    /**
     * Supported fields for notification types
     *
     * @var array
     */
    protected $supported_fields = array();

    /**
     * Notification Trigger ID
     *
     * @var int
     */
    public $trigger_id;

    /**
     * Instance of the current LLMS_Notification
     *
     * @var LLMS_Notification
     */
    protected $notification;

    /**
     * Instance of LLMS_Student for the subscriber
     *
     * @var LLMS_Student
     */
    protected $subscriber;

    /**
     * Instance of an LLMS_Student for the triggering user
     *
     * @var LLMS_Student
     */
    protected $user;

    /**
     * Merge codes.
     *
     * @var string[]
     */
    protected $merge_codes;

    /**
     * Replace merge codes with actual values
     *
     * @since 3.8.0
     *
     * @param string $code The merge code to get merged data for.
     * @return string
     */
    abstract protected function set_merge_data( $code );

    /**
     * Setup body content for output
     *
     * @since 3.8.0
     *
     * @return string
     */
    abstract protected function set_body();

    /**
     * Setup footer content for output
     *
     * @since 3.8.0
     *
     * @return string
     */
    abstract protected function set_footer();

    /**
     * Setup notification icon for output
     *
     * @since 3.8.0
     *
     * @return string
     */
    abstract protected function set_icon();

    /**
     * Setup merge codes that can be used with the notification
     *
     * @since 3.8.0
     *
     * @return array
     */
    abstract protected function set_merge_codes();

    /**
     * Setup notification subject line for output
     *
     * @since 3.8.0
     *
     * @return string
     */
    abstract protected function set_subject();

    /**
     * Setup notification title for output
     *
     * On an email the title acts as the "heading" element.
     *
     * @since 3.8.0
     *
     * @return string
     */
    abstract protected function set_title();

    /**
     * Constructor
     *
     * @since 3.8.0
     * @since 3.31.0 Add filter on `$basic_options` class class property.
     * @since 3.37.19 Moved the retrieval of the associated llms post into a protected method.
     * @since 5.0.0 Force [llms-user] shortocde to the user ID of the user who triggered the notification.
     *
     * @param mixed $notification Notification id, instance of LLMS_Notification
     *                            or an object containing at least an 'id'.
     * @return void
     */
    public function __construct( $notification ) {

        if ( is_numeric( $notification ) ) {
            $this->id           = $notification;
            $this->notification = new LLMS_Notification( $notification );
        } elseif ( is_a( $notification, 'LLMS_Notification' ) ) {
            $this->id           = $notification->get( 'id' );
            $this->notification = $notification;
        } elseif ( is_object( $notification ) && isset( $notification->id ) ) {
            $this->id           = $notification->id;
            $this->notification = new LLMS_Notification( $notification->id );
        }

        $this->subscriber = new LLMS_Student( $this->notification->get( 'subscriber' ) );
        $this->user       = new LLMS_Student( $this->notification->get( 'user_id' ) );
        $this->post       = $this->get_object();

        $this->basic_options = apply_filters( $this->get_filter( 'basic_options' ), $this->basic_options, $this );

        add_filter( 'llms_user_info_shortcode_user_id', array( $this, 'set_shortcode_user' ) );

    }

    /**
     * Destructor
     *
     * @since 5.0.0
     *
     * @return void
     */
    public function __destruct() {
        remove_filter( 'llms_user_info_shortcode_user_id', array( $this, 'set_shortcode_user' ) );
    }

    /**
     * Set the user ID used by [llms-user] to the user triggering the notification.
     *
     * @since 5.0.0
     *
     * @param int $uid WP_User ID of the current user.
     * @return int
     */
    public function set_shortcode_user( $uid ) {
        return $this->user->get( 'id' );
    }

    /**
     * Get the object associated to the notification
     *
     * @since 3.37.19
     *
     * @return object
     */
    protected function get_object() {
        return llms_get_post( $this->notification->get( 'post_id' ), 'post' );
    }

    /**
     * Get the html for a basic notification
     *
     * @since 3.8.0
     *
     * @return string
     */
    private function get_basic_html() {

        // Setup html classes.
        $classes = array(
            'llms-notification',
        );

        // Setup html attributes.
        $attributes = array(
            'id'      => $this->id,
            'trigger' => $this->trigger_id,
            'type'    => $this->notification->get( 'type' ),
        );

        if ( $this->basic_options['dismissible'] ) {
            $classes[] = 'is-dismissible';
        }
        if ( $this->basic_options['auto_dismiss'] ) {
            $classes[]                  = 'auto-dismiss';
            $attributes['auto-dismiss'] = $this->basic_options['auto_dismiss'];
        }

        $atts = '';
        foreach ( $attributes as $att => $val ) {
            $atts .= sprintf( ' data-%1$s="%2$s"', $att, $val );
        }

        // Get variables.
        $title  = $this->get_title();
        $icon   = ( 'yes' === $this->get_option( 'icon_hide', 'no' ) ) ? '' : $this->get_icon_src();
        $body   = $this->get_body();
        $footer = $this->get_footer();

        ob_start();
        llms_get_template(
            'notifications/basic.php',
            array(
                'atts'        => $atts,
                'body'        => $body,
                'classes'     => implode( ' ', $classes ),
                'date'        => $this->get_date_display( 5 ),
                'dismissible' => $this->basic_options['dismissible'],
                'footer'      => $footer,
                'icon'        => $icon,
                'id'          => $this->id,
                'status'      => $this->notification->get( 'status' ),
                'title'       => $title,
            )
        );
        $html = trim( preg_replace( '/\s+/S', ' ', ob_get_clean() ) );

        return apply_filters( $this->get_filter( 'get_basic_html' ), $html, $this );

    }

    /**
     * Retrieve the body for the notification
     *
     * @since 3.8.0
     *
     * @return string
     */
    public function get_body( $merge = true ) {
        $body = $this->get_option( 'body', apply_filters( $this->get_filter( 'set_body' ), $this->set_body(), $this ) );
        if ( $merge ) {
            $body = $this->get_merged_string( $body );
        }
        return apply_filters( $this->get_filter( 'get_body' ), wpautop( $body ), $this );
    }

    /**
     * Retrieve a formatted date
     *
     * @since 3.8.0
     *
     * @param string $date   Created or updated.
     * @param string $format Valid PHP date format, defaults to WP date format options.
     * @return string
     */
    public function get_date( $date = 'created', $format = null ) {

        if ( ! $format ) {
            $format = get_option( 'date_format' ) . ' ' . get_option( 'time_format' );
        }

        return date_i18n( $format, strtotime( $this->notification->get( $date ) ) );

    }

    /**
     * Get relative or absolute date
     *
     * Returns relative if relative date is less than $max_days
     * otherwise returns the absolute date.
     *
     * @since 3.8.0
     *
     * @param int $max_days Max age of notification to display relative date for.
     * @return string
     */
    public function get_date_display( $max_days = 5 ) {

        $now     = current_time( 'timestamp' );
        $created = $this->get_date( 'created', 'U' );

        if ( ( $now - $created ) <= ( $max_days * DAY_IN_SECONDS ) ) {

            return sprintf( _x( 'About %s ago', 'relative date display', 'lifterlms' ), $this->get_date_relative( 'created' ) );

        }

        return $this->get_date( 'created' );

    }

    /**
     * Retrieve a date relative to the current time
     *
     * @since 3.8.0
     *
     * @param string $date Created or updated.
     * @return string
     */
    public function get_date_relative( $date = 'created' ) {
        return llms_get_date_diff( current_time( 'timestamp' ), $this->get_date( $date, 'U' ), 1 );
    }

    /**
     * Get the html for an email notification
     *
     * @since 3.28.2 Unknown.
     *
     * @return string
     * @since 3.8.0
     */
    private function get_email_html() {
        return apply_filters( $this->get_filter( 'get_email_html' ), $this->get_body(), $this );
    }

    /**
     * Get a filter hook string prefixed for the current view
     *
     * @since 3.8.0
     *
     * @param string $hook Hook name.
     * @return string
     */
    protected function get_filter( $hook ) {
        return 'llms_notification_view' . $this->trigger_id . '_' . $hook;
    }

    /**
     * Get an array of field-related options to be add to the notifications view config page on the admin panel
     *
     * @since 3.8.0
     *
     * @param string $type Type of the field.
     * @return array
     */
    public function get_field_options( $type ) {

        $options = array();

        if ( $this->has_field_support( $type, 'subject' ) ) {
            $options[] = array(
                'after_html' => llms_merge_code_button( '#' . $this->get_option_name( 'subject' ), false, $this->get_merge_codes() ),
                'id'         => $this->get_option_name( 'subject' ),
                'title'      => __( 'Subject', 'lifterlms' ),
                'type'       => 'text',
                'value'      => $this->get_subject( false ),
            );
        }

        if ( $this->has_field_support( $type, 'title' ) ) {
            $options[] = array(
                'after_html' => llms_merge_code_button( '#' . $this->get_option_name( 'title' ), false, $this->get_merge_codes() ),
                'id'         => $this->get_option_name( 'title' ),
                'title'      => ( 'email' === $type ) ? __( 'Heading', 'lifterlms' ) : __( 'Title', 'lifterlms' ),
                'type'       => 'text',
                'value'      => $this->get_title( false ),
            );
        }

        if ( $this->has_field_support( $type, 'body' ) ) {
            $options[] = array(
                'editor_settings' => array(
                    'teeny' => true,
                ),
                'id'              => $this->get_option_name( 'body' ),
                'title'           => __( 'Body', 'lifterlms' ),
                'type'            => 'wpeditor',
                'value'           => $this->get_body( false ),
            );
        }

        if ( $this->has_field_support( $type, 'icon' ) ) {
            $options[] = array(
                'id'         => $this->get_option_name( 'icon' ),
                'image_size' => 'llms_notification_icon',
                'title'      => __( 'Icon', 'lifterlms' ),
                'type'       => 'image',
                'value'      => $this->get_icon(),
            );
            $options[] = array(
                'default'     => 'no',
                'description' => __( 'When checked the icon will not be displayed when showing this notification.', 'lifterlms' ),
                'id'          => $this->get_option_name( 'icon_hide' ),
                'title'       => __( 'Disable Icon', 'lifterlms' ),
                'type'        => 'checkbox',
            );
        }

        return apply_filters( $this->get_filter( 'get_field_options' ), $options, $this );
    }

    /**
     * Retrieve the footer for the notification
     *
     * @since 3.8.0
     *
     * @return string
     */
    public function get_footer() {
        return apply_filters( $this->get_filter( 'get_footer' ), $this->set_footer(), $this );
    }

    /**
     * Retrieve the full HTML to be output for the notification type
     *
     * @since 3.8.0
     * @since 4.16.0 Pass `null` to the 3rd-party filter.
     *
     * @return string|WP_Error If the notification type is not supported, returns an error.
     */
    public function get_html() {

        $type = $this->notification->get( 'type' );

        switch ( $type ) {

            case 'email':
                $html = $this->get_email_html();
                break;

            case 'basic':
                $html = $this->get_basic_html();
                break;

            // 3rd party/custom types.
            default:
                $html = apply_filters( $this->get_filter( 'get_' . $type . '_html' ), null, $this );

        }

        return apply_filters( $this->get_filter( 'get_html' ), $html, $this );

    }

    /**
     * Retrieve the icon id for the notification
     *
     * Returns an attachment id for the image.
     *
     * @since 3.8.0
     *
     * @return mixed
     */
    public function get_icon() {
        $icon = $this->get_option( 'icon', apply_filters( $this->get_filter( 'set_icon' ), $this->set_icon(), $this ) );
        return apply_filters( $this->get_filter( 'get_icon' ), $icon, $this );
    }

    /**
     * Retrieve a default icon for the notification based on the notification type
     *
     * @since 3.8.0
     * @since 3.10.0 Unknown.
     * @since 3.37.19 Use `in_array` with strict comparison.
     *
     * @param string $type Type of icon [positive|negative].
     * @return string
     */
    public function get_icon_default( $type ) {
        if ( ! in_array( $type, array( 'negative', 'positive', 'warning' ), true ) ) {
            $ret = '';
        } else {
            $ret = llms()->plugin_url() . '/assets/images/notifications/icon-' . $type . '.png';
        }
        return apply_filters( 'llms_notification_get_icon_default', $ret, $type, $this );
    }

    /**
     * Retrieve the icon src for the notification
     *
     * @since 3.8.0
     *
     * @return string
     */
    public function get_icon_src() {
        $src = '';
        $val = $this->get_icon();
        if ( is_numeric( $val ) ) {
            $src = wp_get_attachment_image_src( $val, 'llms_notification_icon' );
            if ( is_array( $src ) ) {
                $src = $src[0];
            }
        } else {
            $src = $val;
        }
        return apply_filters( $this->get_filter( 'get_icon_src' ), $src, $this );
    }

    /**
     * Get available merge codes for the current notification.
     *
     * @since 3.8.0
     * @since 3.11.0 Unknown.
     * @since 6.4.0 Cache merge codes.
     *
     * @return array
     */
    public function get_merge_codes() {

        if ( ! isset( $this->merge_codes ) ) {
            $codes = array_merge( $this->get_merge_code_defaults(), $this->set_merge_codes() );
            asort( $codes );
            $this->merge_codes = $codes;
        }

        return apply_filters( $this->get_filter( 'get_merge_codes' ), $this->merge_codes, $this );

    }

    /**
     * Get default merge codes available to all notifications of a given type
     *
     * @since 3.11.0
     *
     * @return array
     */
    protected function get_merge_code_defaults() {

        switch ( $this->notification->get( 'type' ) ) {

            case 'email':
                $codes = array(
                    '{{DIVIDER}}' => __( 'Divider Line', 'lifterlms' ),
                );
                break;

            default:
                $codes = array();
        }

        return $codes;

    }

    /**
     * Merge a string.
     *
     * @since 3.8.0
     * @since 3.37.19 Use `in_array` with strict comparison.
     * @since 6.4.0 Only populate effectively used merged data.
     *
     * @param string $string An unmerged string.
     * @return string
     */
    private function get_merged_string( $string ) {

        // Only merge if there are codes in the string.
        if ( false !== strpos( $string, '{{' ) ) {

            $merge_code_defaults = $this->get_merge_code_defaults();

            foreach ( $this->get_used_merge_codes( $string ) as $code ) {

                // Set defaults.
                if ( array_key_exists( $code, $merge_code_defaults ) ) {

                    $func = 'set_merge_data_default';

                    // Set customs with extended class func.
                } else {

                    $func = 'set_merge_data';

                }

                $string = str_replace( $code, $this->$func( $code ), $string );

            }
        }

        return apply_filters( $this->get_filter( 'get_merged_string' ), $this->sentence_case( $string ), $this );

    }

    /**
     * Retrieve merge codes used in a given string.
     *
     * @since 6.4.0
     *
     * @param string $string Text string whereto look for merge codes.
     * @return array Returns a list of merge codes actually used in the passed string.
     */
    private function get_used_merge_codes( $string ) {

        return array_keys(
            array_filter(
                $this->get_merge_codes(),
                function ( $code ) use ( $string ) {
                    return false !== strpos( $string, $code );
                },
                ARRAY_FILTER_USE_KEY
            )
        );

    }

    /**
     * Access the protected notification object
     *
     * @since 3.18.2
     *
     * @return LLMS_Notification
     */
    public function get_notification() {
        return $this->notification;
    }

    /**
     * Retrieve a prefix for options related to the notification
     *
     * This overrides the LLMS_Abstract_Options_Data method.
     *
     * @since 3.8.0
     *
     * @return string
     */
    protected function get_option_prefix() {
        return sprintf( '%1$snotification_%2$s_%3$s_', $this->option_prefix, $this->trigger_id, $this->notification->get( 'type' ) );
    }

    /**
     * Retrieve the subject for the notification (if supported)
     *
     * @since 3.8.0
     *
     * @return string
     */
    public function get_subject( $merge = true ) {
        $subject = $this->get_option( 'subject', apply_filters( $this->get_filter( 'set_subject' ), $this->set_subject(), $this ) );
        if ( $merge ) {
            $subject = $this->get_merged_string( $subject );
        }
        return apply_filters( $this->get_filter( 'get_subject' ), $subject, $this );
    }

    /**
     * Get supported fields and allow filtering for 3rd parties
     *
     * @since 3.8.0
     *
     * @return array
     */
    public function get_supported_fields() {
        return apply_filters( $this->get_filter( 'get_supported_fields' ), $this->set_supported_fields(), $this );
    }

    /**
     * Retrieve the title for the notification
     *
     * @since 3.8.0
     *
     * @return string
     */
    public function get_title( $merge = true ) {
        $title = $this->get_option( 'title', apply_filters( $this->get_filter( 'set_title' ), $this->set_title(), $this ) );
        if ( $merge ) {
            $title = $this->get_merged_string( $title );
        }
        return apply_filters( $this->get_filter( 'get_title' ), $title, $this );
    }

    /**
     * Determine if the current view supports a field by ID
     *
     * @since 3.8.0
     *
     * @param string $type  Notification type [email|basic].
     * @param string $field Field id.
     * @return bool
     */
    protected function has_field_support( $type, $field ) {
        $fields = $this->get_supported_fields();
        if ( ! isset( $fields[ $type ] ) ) {
            return false;
        }
        $type_fields = $fields[ $type ];
        if ( ! isset( $type_fields[ $field ] ) ) {
            return false;
        }
        return $type_fields[ $field ];
    }

    /**
     * Determine if the notification subscriber is the user who triggered the notification
     *
     * @since 3.8.0
     *
     * @return bool
     */
    protected function is_for_self() {
        return ( $this->subscriber->get_id() === $this->user->get_id() );
    }

    /**
     * Convert a string to sentence case
     *
     * Useful for handling lowercased merged data like "you" which may appear at the beginning or middle of a sentence.
     *
     * @since 3.8.0
     * @since 3.24.0 Unknown.
     *
     * @param string $string A string.
     * @return string
     */
    private function sentence_case( $string ) {

        $sentences  = preg_split( '/(\.|\?|\!)(\s|$)+/', $string, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE );
        $new_string = '';
        foreach ( $sentences as $sentence ) {
            $new_string .= strlen( $sentence ) === 1 ? $sentence . ' ' : ucfirst( trim( $sentence ) );
        }

        return trim( $new_string );

    }

    /**
     * Replace default merge codes with actual values
     *
     * @since 3.11.0
     *
     * @param string $code The merge code to get merged data for.
     * @return string
     */
    protected function set_merge_data_default( $code ) {

        $mailer = llms()->mailer();

        switch ( $code ) {

            case '{{DIVIDER}}':
                $code = $mailer->get_divider_html();
                break;

        }

        return $code;

    }

    /**
     * Define field support for the view
     *
     * Extending classes can override this
     * 3rd parties should filter $this->get_supported_fields().
     *
     * @since 3.8.0
     *
     * @return array
     */
    protected function set_supported_fields() {
        return array(
            'basic' => array(
                'body'  => true,
                'title' => true,
                'icon'  => true,
            ),
            'email' => array(
                'body'    => true,
                'subject' => true,
                'title'   => true,
            ),
        );
    }

}