LLMS_Notifications
LifterLMS Notifications Management and Interface
Description Description
Loads and allows interactions with notification views, controllers, and processors.
Source Source
File: includes/notifications/class.llms.notifications.php
class LLMS_Notifications { use LLMS_Trait_Singleton; /** * Controller instances * * @var LLMS_Abstract_Notification_Controller[] */ private $controllers = array(); /** * Notifications being displayed on this page load. * * @var array */ private $displayed = array(); /** * Background processor instances * * @var LLMS_Abstract_Notification_Processor[] */ private $processors = array(); /** * Array of processors needing to be dispatched on shutdown * * @var string[] */ private $processors_to_dispatch = array(); /** * [string $view_classname => string $trigger ] * * @var string[] */ private $views = array(); /** * Constructor * * @since 3.8.0 * @since 3.22.0 Unknown. * @since 3.36.1 Record basic notifications as read during `wp_print_footer_scripts`. * @since 3.38.0 Schedule processors using an async scheduled action. * @since 6.0.0 Do not load / enqueue basic notifications on the admin panel. * Removed the deprecated `llms_processors_async_dispatching` filter hook. * * @return void */ private function __construct() { $this->load(); if ( ! is_admin() ) { add_action( 'wp', array( $this, 'enqueue_basic' ) ); add_action( 'wp_print_footer_scripts', array( $this, 'mark_displayed_basics_as_read' ) ); } add_action( 'shutdown', array( $this, 'schedule_processors_dispatch' ) ); add_action( 'llms_dispatch_notification_processor_async', array( $this, 'dispatch_processor_async' ) ); } /** * Async callback to dispatch processors * * Locates the processor by ID and dispatches it for processing. * * The trigger hook `llms_dispatch_notification_processor_async` is called by the action scheduler library. * * @since 3.38.0 * * @see llms_dispatch_notification_processor_async * * @param string $id Processor ID. * @return array|WP_Error */ public function dispatch_processor_async( $id ) { $processor = $this->get_processor( $id ); if ( $processor ) { return $processor->dispatch(); } // Translators: %s = Processor ID. return new WP_Error( 'invalid-processor', sprintf( __( 'The processor "%s" does not exist.', 'lifterlms' ), $id ) ); } /** * Enqueue basic notifications for onscreen display. * * @since 3.22.0 * @since 3.36.1 Don't automatically mark notifications as read. * @since 3.38.0 Use `wp_json_decode()` in favor of `json_decode()`. * @since 4.4.0 Use `LLMS_Assets::enqueue_inline()` in favor of deprecated `LLMS_Frontend_Assets::enqueue_inline_script()`. * @since 7.1.0 Improve notifications query performance by not calculating unneeded found rows. * * @return void */ public function enqueue_basic() { $user_id = get_current_user_id(); if ( ! $user_id ) { return; } // Get 5 most recent new notifications for the current user. $query = new LLMS_Notifications_Query( array( 'per_page' => 5, 'statuses' => 'new', 'types' => 'basic', 'subscriber' => $user_id, 'no_found_rows' => true, ) ); $this->displayed = $query->get_notifications(); // Push to JS. llms()->assets->enqueue_inline( 'llms-queued-notifications', 'window.llms.queued_notifications = ' . wp_json_encode( $this->displayed ) . ';', 'footer' ); } /** * Record notifications as read. * * Ensures that notifications are not missed due to redirects that happen after `wp`. * * @since 3.36.1 * * @return void */ public function mark_displayed_basics_as_read() { if ( $this->displayed ) { foreach ( $this->displayed as $notification ) { $notification->set( 'status', 'read' ); } } } /** * Get the directory path for core notification classes * * @since 3.8.0 * * @return string */ private function get_directory() { return LLMS_PLUGIN_DIR . 'includes/notifications/'; } /** * Get a single controller instance * * @since 3.8.0 * * @param string $controller Trigger id (eg: lesson_complete). * @return LLMS_Abstract_Notification_Controller|false */ public function get_controller( $controller ) { if ( isset( $this->controllers[ $controller ] ) ) { return $this->controllers[ $controller ]; } return false; } /** * Get loaded controllers * * @since 3.8.0 * * @return LLMS_Abstract_Notification_Controller[] */ public function get_controllers() { return $this->controllers; } /** * Retrieve a single processor instance * * @since 3.8.0 * * @param string $processor Name of the processor (eg: email). * @return LLMS_Abstract_Notification_Processor|false */ public function get_processor( $processor ) { if ( isset( $this->processors[ $processor ] ) ) { return $this->processors[ $processor ]; } return false; } /** * Get loaded processors * * @since 3.8.0 * * @return LLMS_Abstract_Notification_Processor[] */ public function get_processors() { return $this->processors; } /** * Retrieve a view instance of a notification * * @since 3.8.0 * @since 3.24.0 Unknown. * @since 3.38.0 Use strict comparison. * * @param LLMS_Notification $notification Notification instance. * @return LLMS_Abstract_Notification_View|false */ public function get_view( $notification ) { $trigger = $notification->get( 'trigger_id' ); if ( in_array( $trigger, $this->views, true ) ) { $views = array_flip( $this->views ); $class = $views[ $trigger ]; $view = new $class( $notification ); return $view; } return false; } /** * Get the classname for the view of a given notification based off it's trigger * * @since 3.8.0 * @since 3.24.0 Unknown. * * @param string $trigger Trigger id (eg: lesson_complete). * @param string $prefix Default = 'LLMS'. * @return string */ private function get_view_classname( $trigger, $prefix = null ) { $prefix = $prefix ? $prefix : 'LLMS'; $name = str_replace( ' ', '_', ucwords( str_replace( '_', ' ', $trigger ) ) ); return sprintf( '%1$s_Notification_View_%2$s', $prefix, $name ); } /** * Load all notifications * * @since 3.8.0 * @since 3.24.0 Unknown. * @since 5.2.0 Added 'upcoming_payment_reminder'. * * @return void */ private function load() { $triggers = array( 'achievement_earned', 'certificate_earned', 'course_complete', 'course_track_complete', 'enrollment', 'lesson_complete', 'manual_payment_due', 'payment_retry', 'purchase_receipt', 'quiz_failed', 'quiz_graded', 'quiz_passed', 'section_complete', 'student_welcome', 'subscription_cancelled', 'upcoming_payment_reminder', ); foreach ( $triggers as $name ) { $this->load_controller( $name ); $this->load_view( $name ); } $processors = array( 'email', ); foreach ( $processors as $name ) { $this->load_processor( $name ); } /** * Run an action after all core notification classes are loaded. * * Third party notifications can hook into this action. * Use `load_view()`, `load_controller()`, and `load_processor()` methods * to load notifications into the class and be auto-called by the core APIs. * * @since Unknown * * @param LLMS_Notifications $this Instance of the notifications singleton. */ do_action( 'llms_notifications_loaded', $this ); } /** * Load and initialize a single controller * * @since 3.8.0 * * @param string $trigger Trigger id (eg: lesson_complete). * @param string $path Full path to the controller file, allows third parties to load external controllers. * @return boolean `true` if the controller is added and loaded, `false` otherwise. */ public function load_controller( $trigger, $path = null ) { // Default path for core views. if ( ! $path ) { $path = $this->get_directory() . 'controllers/class.llms.notification.controller.' . $this->name_to_file( $trigger ) . '.php'; } if ( file_exists( $path ) ) { $this->controllers[ $trigger ] = require_once $path; return true; } return false; } /** * Load a single processor * * @since 3.8.0 * * @param string $type Processor type id. * @param string $path Optional path (for allowing 3rd party processor loading). * @return boolean */ public function load_processor( $type, $path = null ) { // Default path for core processors. if ( ! $path ) { $path = $this->get_directory() . 'processors/class.llms.notification.processor.' . $type . '.php'; } if ( file_exists( $path ) ) { $this->processors[ $type ] = require_once $path; return true; } return false; } /** * Validate trigger and load its view. * * @since 3.8.0 * @since 3.24.0 Unknown. * @since 6.0.0 Removed loading of class files that don't instantiate their class in favor of autoloading. * * @param string $trigger Trigger id (eg: lesson_complete). * @param string $path Full path to the view file, allows third parties to load external views. * @param string $prefix Classname prefix. Defaults to "LLMS". Can be used by 3rd parties to adjust * the prefix in accordance with the projects standards. * @return boolean `true` if the view is added and loaded, `false` otherwise. */ public function load_view( $trigger, $path = null, $prefix = null ) { // Default path for core views. if ( ! $path ) { $path = $this->get_directory() . 'views/class.llms.notification.view.' . $this->name_to_file( $trigger ) . '.php'; } if ( file_exists( $path ) ) { if ( ! is_null( $prefix ) ) { require_once $path; } $this->views[ $this->get_view_classname( $trigger, $prefix ) ] = $trigger; return true; } return false; } /** * Convert a trigger name to a filename string * * EG: "lesson_complete" to "lesson.complete". * * @since 3.8.0 * * @param string $name Trigger name. * @return string */ private function name_to_file( $name ) { return str_replace( '_', '.', $name ); } /** * Schedule a processor to dispatch its queue on shutdown * * @since 3.8.0 * @since 3.38.0 Use strict comparisons. * * @param string $id Processor ID (eg: email). * @return void */ public function schedule_processing( $id ) { if ( ! in_array( $id, $this->processors_to_dispatch, true ) ) { $this->processors_to_dispatch[] = $id; } } /** * Check for processors that have items in the queue * * For any found processors, saves their queue and schedules them to be processes via a scheduled event. * * @since 3.38.0 * * @return array Array containing information about the scheduled processors. * The array keys will be the processor ID and the values will be the timestamp of the event or a WP_Error object. */ public function schedule_processors_dispatch() { $scheduled = array(); if ( $this->processors_to_dispatch ) { foreach ( $this->processors_to_dispatch as $key => $id ) { // Retrieve the processor. $processor = $this->get_processor( $id ); // Remove it from the list of processors to dispatch. unset( $this->processors_to_dispatch[ $key ] ); $scheduled[ $id ] = $processor ? $this->schedule_single_processor( $processor, $id ) : new WP_Error( 'invalid-processor', // Translators: %s = Processor ID. sprintf( __( 'The processor "%s" does not exist.', 'lifterlms' ), $id ) ); } } return $scheduled; } /** * Save pending batches and schedule the async dispatching of a processor. * * @since 3.38.0 * * @param LLMS_Abstract_Notification_Processor $processor Notification processor object. * @param string $id Processor ID. * @return int|WP_Error Timestamp of the scheduled event or an error object. */ protected function schedule_single_processor( $processor, $id ) { $hook = 'llms_dispatch_notification_processor_async'; $args = array( $id ); // Save items in the queue. $processor->save(); // Check if there's already a scheduled event. $timestamp = as_next_scheduled_action( $hook, $args ); // If there's no event scheduled already, schedule one. if ( ! $timestamp ) { $timestamp = llms_current_time( 'timestamp', 1 ); // Error encountered scheduling the event. if ( ! as_schedule_single_action( $timestamp, $hook, $args ) ) { $timestamp = new WP_Error( 'schedule-error', // Translators: %s = Processor ID. sprintf( __( 'There was an error dispatching the "%s" processor.', 'lifterlms' ), $id ) ); } } return $timestamp; }
Expand full source code Collapse full source code View on GitHub
Methods Methods
- __construct — Constructor
- dispatch_processor_async — Async callback to dispatch processors
- dispatch_processors — On shutdown, check for processors that have items in the queue that need to be saved — deprecated
- enqueue_basic — Enqueue basic notifications for onscreen display
- get_controller — Get a single controller instance
- get_controllers — Get loaded controllers
- get_directory — Get the directory path for core notification classes
- get_processor — Retrieve a single processor instance
- get_processors — Get loaded processors
- get_view — Retrieve a view instance of a notification
- get_view_classname — Get the classname for the view of a given notification based off it's trigger
- instance — Main Instance
- load — Load all notifications
- load_controller — Load and initialize a single controller
- load_processor — Load a single processor
- load_view — Validate trigger and load its view.
- mark_displayed_basics_as_read — Record notifications as read.
- name_to_file — Convert a trigger name to a filename string
- schedule_processing — Schedule a processor to dispatch its queue on shutdown
- schedule_processors_dispatch — Check for processors that have items in the queue
- schedule_single_processor — Save pending batches and schedule the async dispatching of a processor.
Changelog Changelog
Version | Description |
---|---|
6.0.0 | Removed deprecated items.
|
5.3.0 | Replace singleton code with LLMS_Trait_Singleton . |
3.8.0 | |
3.38.0 | Updated processor scheduling for increased performance and reliability. |
3.36.1 | Record notifications as read during the wp_print_footer_scripts hook. |
3.24.0 | Introduced. |