includes/abstracts/llms.abstract.notification.view.php
<?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,
),
);
}
}