From ffab7a8ef1d54a4e6cc8ed968c6269b2f0e835f1 Mon Sep 17 00:00:00 2001 From: SEO Themes Date: Fri, 7 Jul 2023 23:17:34 +0800 Subject: [PATCH 01/10] Begin work on admin notifications functionality --- assets/scss/admin/_main.scss | 31 ++- .../admin/class-llms-admin-notification.php | 113 +++++++++ .../admin/class-llms-admin-notifications.php | 220 ++++++++++++++++++ includes/admin/class.llms.admin.notices.php | 25 +- includes/class-llms-loader.php | 4 +- 5 files changed, 388 insertions(+), 5 deletions(-) create mode 100644 includes/admin/class-llms-admin-notification.php create mode 100644 includes/admin/class-llms-admin-notifications.php diff --git a/assets/scss/admin/_main.scss b/assets/scss/admin/_main.scss index c2d1b5a91c..6aa2a8675e 100644 --- a/assets/scss/admin/_main.scss +++ b/assets/scss/admin/_main.scss @@ -167,6 +167,7 @@ div[id^="lifterlms-"] .inside { border-left: 4px solid #F8954F; .llms-admin-notice-icon { + color: #F8954F; background-color: #FEF4ED; } } @@ -179,6 +180,7 @@ div[id^="lifterlms-"] .inside { } .llms-admin-notice-icon { + color: #466DD8; background-color: #EDF0FB; } @@ -192,6 +194,7 @@ div[id^="lifterlms-"] .inside { } .llms-admin-notice-icon { + color: #18A957; background-color: #E8F6EE; } @@ -205,23 +208,43 @@ div[id^="lifterlms-"] .inside { } .llms-admin-notice-icon { + color: #9C0F2E; background-color: #FCE8EC; } } .llms-admin-notice-icon { + min-width: 70px; + display: flex; + align-items: center; + justify-content: center; + + .dashicons { + width: 20px; + height: 20px; + font-size: 20px; + + &:before { + font-size: inherit; + } + } + } + + .llms-admin-notice-lifterlms-icon { background-image: url(../../assets/images/lifterlms-icon-color.png); background-position: center center; background-repeat: no-repeat; background-size: 30px; - min-width: 70px; - } .llms-admin-notice-content { color: #111; padding: 20px; + + > * { + margin-top: 0; + } } h3 { @@ -247,6 +270,10 @@ div[id^="lifterlms-"] .inside { p:last-of-type { margin-bottom: 0; } + + p + .wp-block-buttons { + margin-top: 15px; + } } .llms-button-action, diff --git a/includes/admin/class-llms-admin-notification.php b/includes/admin/class-llms-admin-notification.php new file mode 100644 index 0000000000..0fca61e9c4 --- /dev/null +++ b/includes/admin/class-llms-admin-notification.php @@ -0,0 +1,113 @@ +id = $object->id ?? 0; + $this->title = $object->title ?? esc_html__( 'Untitled Notification', 'lifterlms' ); + $this->content = $object->content ?? ''; + $this->start_date = $object->start_date ?? date( 'd/m/Y' ); + $this->end_date = $object->end_date ?? date( 'd/m/Y', strtotime( '+1 week' ) ); + $this->type = $object->type ?? 'info'; + $this->icon = $object->icon ?? 'info-outline'; + $this->priority = $object->priority ?? 10; + $this->dismissible = $object->dismissible ?? true; + $this->conditions = $object->conditions ?? []; + + } + + +} diff --git a/includes/admin/class-llms-admin-notifications.php b/includes/admin/class-llms-admin-notifications.php new file mode 100644 index 0000000000..19c927ca76 --- /dev/null +++ b/includes/admin/class-llms-admin-notifications.php @@ -0,0 +1,220 @@ +url = $this->get_notifications_url(); + + add_action( 'current_screen', array( $this, 'add_notifications' ) ); + } + + /** + * Conditionally displays notifications. + * + * @since [version] + * + * @param WP_Screen $current_screen WP_Screen instance. + * @return void + */ + public function add_notifications( WP_Screen $current_screen ): void { + + if ( ! str_contains( $current_screen->base, 'llms' ) && ! str_contains( $current_screen->id, 'llms' ) ) { + return; + } + + $notifications = $this->get_notifications( true ); + + if ( ! $notifications ) { + return; + } + + foreach ( $notifications as $notification ) { + + if ( ! $this->check_conditions( $notification ) ) { + continue; + } + + LLMS_Admin_Notices::add_notice( + $notification->id, + $notification->content, + array( + 'dismissible' => $notification->dismissible, + 'dismiss_for_days' => 7, + 'flash' => false, + 'html' => '', + 'remind_in_days' => 7, + 'remindable' => false, + 'type' => $notification->type, + 'icon' => $notification->icon, + 'template' => false, + 'template_path' => '', + 'default_path' => '', + 'priority' => $notification->priority, + ) + ); + } + } + + /** + * Returns the filtered URL to the notifications JSON file. + * + * @since [version] + * + * @return string + */ + private function get_notifications_url(): string { + + /** + * Filter the notifications URL. + * + * @since [version] + * + * @param string $url The URL. + */ + $url = apply_filters( + 'llms_notifications_url', + 'https://lifter-telemetry.local/wp-content/cache/llms/notifications/notifications.json' + ); + + return esc_url( $url ); + } + + /** + * Returns array of all notifications. + * + * @since [version] + * + * @param bool $fetch Force a refresh of the notifications. + * @return LLMS_Admin_Notification[] Array of applicable notification objects. + */ + private function get_notifications( bool $fetch = false ): array { + + $notifications = get_transient( $this->name ); + + if ( ! $notifications || $fetch ) { + $notifications = $this->fetch_notifications(); + + set_transient( $this->name, $notifications, DAY_IN_SECONDS ); + } + + /** + * Allows filtering of notifications. + * + * @since [version] + * + * @param array $notifications Array of notification objects. + */ + $filtered = apply_filters( $this->name, $notifications ); + + $notifications = array(); + + foreach ( $filtered as $notification ) { + if ( ! is_array( $notification->conditions ?? null ) ) { + $notification->conditions = array(); + } + + $notifications[] = new LLMS_Admin_Notification( $notification ); + } + + usort( + $notifications, + static function ( + LLMS_Admin_Notification $a, + LLMS_Admin_Notification $b + ): bool { + return $a->priority - $b->priority; + } + ); + + return $notifications; + + } + + /** + * Fetches notifications from the server. + * + * @since [version] + * + * @return array Array of notification objects. + */ + private function fetch_notifications(): array { + + $response = wp_remote_get( + $this->get_notifications_url(), + array( + 'timeout' => 5, + 'sslverify' => false, + ) + ); + + if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) { + return []; + } + + $body = wp_remote_retrieve_body( $response ); + + return json_decode( $body, false ) ?? []; + + } + + /** + * Checks if a notification should be displayed. + * + * @since [version] + * + * @param LLMS_Admin_Notification $notification Notification object. + * @return bool + */ + private function check_conditions( LLMS_Admin_Notification $notification ): bool { + if ( ! $notification->conditions ) { + return true; + } + + return false; + } + +} + +LLMS_Admin_Notifications::instance(); diff --git a/includes/admin/class.llms.admin.notices.php b/includes/admin/class.llms.admin.notices.php index 7ce71c33b0..82cf9778e5 100644 --- a/includes/admin/class.llms.admin.notices.php +++ b/includes/admin/class.llms.admin.notices.php @@ -78,6 +78,7 @@ public static function add_output_actions() { * * @since 3.0.0 * @since 3.3.0 Added "flash" option. + * @since [version] Added "icon" option. * * @param string $notice_id Unique id of the notice. * @param string $html_or_options Html content of the notice for short notices that don't need a template @@ -115,7 +116,8 @@ public static function add_notice( $notice_id, $html_or_options = '', $options = 'type' => 'info', // Info, warning, success, error. 'template' => false, // Template name, eg "admin/notices/notice.php". 'template_path' => '', // Allow override of default llms()->template_path(). - 'default_path' => '', // Allow override of default path llms()->plugin_path() . '/templates/'. An addon may add a notice and pass it's own path in here. + 'default_path' => '', // Allow override of default path llms()->plugin_path() . '/templates/'. An addon may add a notice and pass its own path in here. + 'icon' => 'lifterlms', // Accepts any Dashicon class name. ) ); @@ -317,9 +319,28 @@ public static function output_notice( $notice_id ) { return; } + + $icon = $notice['icon'] ?? 'lifterlms'; + $icon_name = ucwords( str_replace('-', ' ', $icon ) ); + ?>
-
+
+ +
+ + + +
+ +
+ + + +
+ +
+
diff --git a/includes/class-llms-loader.php b/includes/class-llms-loader.php index 3e5303803e..207a3f6c68 100644 --- a/includes/class-llms-loader.php +++ b/includes/class-llms-loader.php @@ -5,7 +5,7 @@ * @package LifterLMS/Classes * * @since 4.0.0 - * @version 7.2.0 + * @version [version] */ defined( 'ABSPATH' ) || exit; @@ -321,6 +321,7 @@ public function includes() { * @since 5.9.0 Drop usage of deprecated `FILTER_SANITIZE_STRING`. * @since 6.0.0 Removed loading of class files that don't instantiate their class in favor of autoloading. * @since 7.2.0 Include `LLMS_Admin_Dashboard_Wigdet` class. + * @since [version] Include `LLMS_Admin_Notifications` class. * * @return void */ @@ -337,6 +338,7 @@ public function includes_admin() { require_once LLMS_PLUGIN_DIR . 'includes/admin/class-llms-mailhawk.php'; require_once LLMS_PLUGIN_DIR . 'includes/admin/class-llms-sendwp.php'; require_once LLMS_PLUGIN_DIR . 'includes/forms/class-llms-forms-unsupported-versions.php'; + require_once LLMS_PLUGIN_DIR . 'includes/admin/class-llms-admin-notifications.php'; // Admin classes (files to be renamed). require_once LLMS_PLUGIN_DIR . 'includes/admin/class.llms.admin.dashboard.php'; From 6ebe2ac7b3de509af4f4f5cb7327e6ee602f1469 Mon Sep 17 00:00:00 2001 From: SEO Themes Date: Mon, 10 Jul 2023 22:09:05 +0800 Subject: [PATCH 02/10] Add basic notification conditional logic callbacks --- .../admin/class-llms-admin-notification.php | 8 +-- .../admin/class-llms-admin-notifications.php | 63 +++++++++++++++++-- 2 files changed, 62 insertions(+), 9 deletions(-) diff --git a/includes/admin/class-llms-admin-notification.php b/includes/admin/class-llms-admin-notification.php index 0fca61e9c4..7cf37ef636 100644 --- a/includes/admin/class-llms-admin-notification.php +++ b/includes/admin/class-llms-admin-notification.php @@ -83,7 +83,7 @@ class LLMS_Admin_Notification { /** * Notification conditions. * - * @var array + * @var array[] */ public array $conditions; @@ -92,9 +92,9 @@ class LLMS_Admin_Notification { * * @since [version] * - * @param object $object Notification stdClass. + * @param object $object Notification object. */ - public function __construct( $object ) { + public function __construct( object $object ) { $this->id = $object->id ?? 0; $this->title = $object->title ?? esc_html__( 'Untitled Notification', 'lifterlms' ); @@ -105,7 +105,7 @@ public function __construct( $object ) { $this->icon = $object->icon ?? 'info-outline'; $this->priority = $object->priority ?? 10; $this->dismissible = $object->dismissible ?? true; - $this->conditions = $object->conditions ?? []; + $this->conditions = (array) ( $object->conditions ?? [] ); } diff --git a/includes/admin/class-llms-admin-notifications.php b/includes/admin/class-llms-admin-notifications.php index 19c927ca76..cf0c03d378 100644 --- a/includes/admin/class-llms-admin-notifications.php +++ b/includes/admin/class-llms-admin-notifications.php @@ -76,6 +76,7 @@ public function add_notifications( WP_Screen $current_screen ): void { continue; } + LLMS_Admin_Notices::add_notice( $notification->id, $notification->content, @@ -151,10 +152,6 @@ private function get_notifications( bool $fetch = false ): array { $notifications = array(); foreach ( $filtered as $notification ) { - if ( ! is_array( $notification->conditions ?? null ) ) { - $notification->conditions = array(); - } - $notifications[] = new LLMS_Admin_Notification( $notification ); } @@ -212,7 +209,63 @@ private function check_conditions( LLMS_Admin_Notification $notification ): bool return true; } - return false; + foreach ( $notification->conditions as $condition ) { + if ( ! $this->check_condition( (object) $condition ) ) { + return false; + } + + if ( ! $this->check_date_range( (object) $condition ) ) { + return false; + } + } + + return true; + } + + /** + * Checks if a single condition is met. + * + * @since [version] + * + * @param object $condition Condition object. + * @return bool + */ + private function check_condition( object $condition ): bool { + $value = explode( ',', $condition->value ); + + $callbacks = [ + 'plugin_active' => static fn( $value ) => is_plugin_active( $value ), + 'plugin_inactive' => static fn( $value ) => ! is_plugin_active( $value ), + 'lifterlms_version' => static fn( $value, $operator ) => version_compare( + llms()->version, + $value, + $operator + ), + 'lifterlms_license' => static fn( $value, $operator ) => version_compare( + llms()->version, + $value, + $operator + ), + ]; + + if ( ! isset( $callbacks[ $condition->type ] ) ) { + return false; + } + + return $callbacks[ $condition->type ]( $value, $condition->operator ); + + } + + /** + * Checks if a notification is within date range. + * + * @since [version] + * + * @param object $condition Condition object. + * @return bool + */ + private function check_date_range( object $condition ): bool { + return true; } } From 49698ad299e5af37f9d61757a95edde9e0e1e8b4 Mon Sep 17 00:00:00 2001 From: SEO Themes Date: Tue, 11 Jul 2023 18:30:12 +0800 Subject: [PATCH 03/10] Fix date range conditions --- .../admin/class-llms-admin-notifications.php | 36 +++++++++++++++---- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/includes/admin/class-llms-admin-notifications.php b/includes/admin/class-llms-admin-notifications.php index cf0c03d378..7af5bf21f6 100644 --- a/includes/admin/class-llms-admin-notifications.php +++ b/includes/admin/class-llms-admin-notifications.php @@ -76,7 +76,6 @@ public function add_notifications( WP_Screen $current_screen ): void { continue; } - LLMS_Admin_Notices::add_notice( $notification->id, $notification->content, @@ -205,6 +204,11 @@ private function fetch_notifications(): array { * @return bool */ private function check_conditions( LLMS_Admin_Notification $notification ): bool { + + if ( ! $this->check_date_range( $notification ) ) { + return false; + } + if ( ! $notification->conditions ) { return true; } @@ -213,10 +217,6 @@ private function check_conditions( LLMS_Admin_Notification $notification ): bool if ( ! $this->check_condition( (object) $condition ) ) { return false; } - - if ( ! $this->check_date_range( (object) $condition ) ) { - return false; - } } return true; @@ -261,10 +261,32 @@ private function check_condition( object $condition ): bool { * * @since [version] * - * @param object $condition Condition object. + * @param LLMS_Admin_Notification $notification Notification object. * @return bool */ - private function check_date_range( object $condition ): bool { + private function check_date_range( LLMS_Admin_Notification $notification ): bool { + + if ( ! $notification->start_date && ! $notification->end_date ) { + return true; + } + + $start = strtotime( $notification->start_date ); + $end = strtotime( $notification->end_date ); + + if ( ! $start && ! $end ) { + return true; + } + + $time = time(); + + if ( $time < $start ) { + return false; + } + + if ( $time > $end ) { + return false; + } + return true; } From 6ea265f22ad1b46ead495cbd7070c7bcd0344455 Mon Sep 17 00:00:00 2001 From: SEO Themes Date: Wed, 12 Jul 2023 21:50:24 +0800 Subject: [PATCH 04/10] Notification conditional logic improvements --- .../admin/class-llms-admin-notification.php | 113 ---------- ...lass-llms-admin-notification-condition.php | 201 ++++++++++++++++++ .../class-llms-admin-notification.php | 175 +++++++++++++++ .../class-llms-admin-notifications.php | 99 +-------- includes/class-llms-loader.php | 4 +- 5 files changed, 381 insertions(+), 211 deletions(-) delete mode 100644 includes/admin/class-llms-admin-notification.php create mode 100644 includes/admin/notifications/class-llms-admin-notification-condition.php create mode 100644 includes/admin/notifications/class-llms-admin-notification.php rename includes/admin/{ => notifications}/class-llms-admin-notifications.php (64%) diff --git a/includes/admin/class-llms-admin-notification.php b/includes/admin/class-llms-admin-notification.php deleted file mode 100644 index 7cf37ef636..0000000000 --- a/includes/admin/class-llms-admin-notification.php +++ /dev/null @@ -1,113 +0,0 @@ -id = $object->id ?? 0; - $this->title = $object->title ?? esc_html__( 'Untitled Notification', 'lifterlms' ); - $this->content = $object->content ?? ''; - $this->start_date = $object->start_date ?? date( 'd/m/Y' ); - $this->end_date = $object->end_date ?? date( 'd/m/Y', strtotime( '+1 week' ) ); - $this->type = $object->type ?? 'info'; - $this->icon = $object->icon ?? 'info-outline'; - $this->priority = $object->priority ?? 10; - $this->dismissible = $object->dismissible ?? true; - $this->conditions = (array) ( $object->conditions ?? [] ); - - } - - -} diff --git a/includes/admin/notifications/class-llms-admin-notification-condition.php b/includes/admin/notifications/class-llms-admin-notification-condition.php new file mode 100644 index 0000000000..12df872cd5 --- /dev/null +++ b/includes/admin/notifications/class-llms-admin-notification-condition.php @@ -0,0 +1,201 @@ +show_hide = $args->show_hide ?? 'show'; + $this->callback = $args->callback ?? null; + $this->operator = $args->operator ?? null; + $this->values = array_map( + 'trim', + explode( ',', $args->values ) + ) ?? []; + + } + + /** + * Check if the condition is met. + * + * @since [version] + * + * @return bool + */ + public function check_callback(): bool { + $return = false; + $callback = $this->callback; + $operator = $this->operator; + + if ( method_exists( $this, $callback ) ) { + if ( $operator ) { + $return = $this->$callback( $this->values, $this->operator ); + } else { + $return = $this->$callback( $this->values ); + } + } + + if ( function_exists( $callback ) ) { + $function = $callback; + + if ( $operator ) { + $return = $function( $this->values, $this->operator ); + } else { + $return = $function( $this->values ); + } + } + + return $this->show_hide === 'show' ? $return : ! $return; + + } + + /** + * Checks if all plugins in array are active. + * + * First checks for short slug match, e.g. "lifterlms" before + * checking full plugin basename "lifterlms/lifterlms.php". + * + * @since [version] + * + * @param array $values Array of plugin slugs. + * @return bool + */ + private function plugins_active( array $values ): bool { + + $plugins_active = false; + + foreach ( $values as $value ) { + if ( $this->plugin_active( $value ) ) { + $plugins_active = true; + } + } + + return $plugins_active; + + } + + /** + * Checks if a single plugin is active. + * + * @since [version] + * + * @param string $value Plugin slug. + * @return bool + */ + private function plugin_active( string $value ): bool { + + $active_plugins = get_option( 'active_plugins', array() ); + + if ( in_array( $value, $active_plugins, true ) ) { + return true; + } + + foreach ( $active_plugins as $active_plugin ) { + $without_php = basename( $active_plugin, '.php' ); + $plugin_parts = explode( '/', $active_plugin ); + $folder_name = $plugin_parts[0]; + $file_name = $plugin_parts[1]; + $file_without_php = basename( $file_name, '.php' ); + + if ( $value === $without_php || $value === $folder_name || $value === $file_name || $value === $file_without_php ) { + return true; + } + + } + + return false; + + } + + /** + * Checks if all plugins in array are inactive. + * + * @since [version] + * + * @param array $values Array of plugin slugs. + * @return bool + */ + private function plugins_inactive( array $values ): bool { + + $plugins_inactive = false; + + foreach ( $values as $value ) { + if ( $this->plugin_inactive( $value ) ) { + $plugins_inactive = true; + } + } + + return $plugins_inactive; + + } + + /** + * Checks if a single plugin is inactive. + * + * @since [version] + * + * @param string $value Plugin slug. + * @return bool + */ + private function plugin_inactive( string $value ): bool { + return ! $this->plugin_active( $value ); + } + + /** + * Checks LifterLMS version. + * + * @since [version] + * + * @param array $values Array of versions. + * @param string $operator Comparison operator. + * @return bool + */ + private function llms_version( array $values, string $operator ): bool { + + return version_compare( llms()->version, $values[0], $operator ); + + } + +} diff --git a/includes/admin/notifications/class-llms-admin-notification.php b/includes/admin/notifications/class-llms-admin-notification.php new file mode 100644 index 0000000000..c4917071a9 --- /dev/null +++ b/includes/admin/notifications/class-llms-admin-notification.php @@ -0,0 +1,175 @@ +id = $args->id ?? 0; + $this->title = $args->title ?? esc_html__( 'Untitled Notification', 'lifterlms' ); + $this->content = $args->content ?? ''; + $this->start_date = $args->start_date ?? date( 'Y-m-d' ); + $this->end_date = $args->end_date ?? date( 'Y-m-d', strtotime( '+1 week' ) ); + $this->type = $args->type ?? 'info'; + $this->icon = $args->icon ?? 'info-outline'; + $this->priority = $args->priority ?? 10; + $this->dismissible = $args->dismissible ?? true; + $this->conditions = array_map( + static fn( object $condition ) => new LLMS_Admin_Notification_Condition( $condition ), + (array) ( $args->conditions ?? [] ) + ); + + } + + /** + * Check if the notification should be displayed. + * + * @since [version] + * + * @return bool + */ + public function check_conditions() { + + if ( ! $this->check_date_range() ) { + return false; + } + + if ( ! $this->conditions ) { + return true; + } + + foreach ( $this->conditions as $condition ) { + if ( ! $condition->check_callback() ) { + return false; + } + } + + return true; + + } + + /** + * Checks if a notification is within date range. + * + * @since [version] + * + * @return bool + */ + private function check_date_range(): bool { + + if ( ! $this->start_date && ! $this->end_date ) { + return false; + } + + $start = strtotime( $this->start_date ); + $end = strtotime( $this->end_date ); + + if ( ! $start && ! $end ) { + return false; + } + + $time = time(); + + if ( $time < $start ) { + return false; + } + + if ( $time > $end ) { + return false; + } + + return true; + } + +} diff --git a/includes/admin/class-llms-admin-notifications.php b/includes/admin/notifications/class-llms-admin-notifications.php similarity index 64% rename from includes/admin/class-llms-admin-notifications.php rename to includes/admin/notifications/class-llms-admin-notifications.php index 7af5bf21f6..461f48e898 100644 --- a/includes/admin/class-llms-admin-notifications.php +++ b/includes/admin/notifications/class-llms-admin-notifications.php @@ -2,7 +2,7 @@ /** * LLMS_Admin_Notifications class file. * - * @package LifterLMS/Admin/Classes + * @package LifterLMS/Admin/Notifications/Classes * * @since [version] * @version [version] @@ -72,7 +72,7 @@ public function add_notifications( WP_Screen $current_screen ): void { foreach ( $notifications as $notification ) { - if ( ! $this->check_conditions( $notification ) ) { + if ( ! $notification->check_conditions() ) { continue; } @@ -195,101 +195,6 @@ private function fetch_notifications(): array { } - /** - * Checks if a notification should be displayed. - * - * @since [version] - * - * @param LLMS_Admin_Notification $notification Notification object. - * @return bool - */ - private function check_conditions( LLMS_Admin_Notification $notification ): bool { - - if ( ! $this->check_date_range( $notification ) ) { - return false; - } - - if ( ! $notification->conditions ) { - return true; - } - - foreach ( $notification->conditions as $condition ) { - if ( ! $this->check_condition( (object) $condition ) ) { - return false; - } - } - - return true; - } - - /** - * Checks if a single condition is met. - * - * @since [version] - * - * @param object $condition Condition object. - * @return bool - */ - private function check_condition( object $condition ): bool { - $value = explode( ',', $condition->value ); - - $callbacks = [ - 'plugin_active' => static fn( $value ) => is_plugin_active( $value ), - 'plugin_inactive' => static fn( $value ) => ! is_plugin_active( $value ), - 'lifterlms_version' => static fn( $value, $operator ) => version_compare( - llms()->version, - $value, - $operator - ), - 'lifterlms_license' => static fn( $value, $operator ) => version_compare( - llms()->version, - $value, - $operator - ), - ]; - - if ( ! isset( $callbacks[ $condition->type ] ) ) { - return false; - } - - return $callbacks[ $condition->type ]( $value, $condition->operator ); - - } - - /** - * Checks if a notification is within date range. - * - * @since [version] - * - * @param LLMS_Admin_Notification $notification Notification object. - * @return bool - */ - private function check_date_range( LLMS_Admin_Notification $notification ): bool { - - if ( ! $notification->start_date && ! $notification->end_date ) { - return true; - } - - $start = strtotime( $notification->start_date ); - $end = strtotime( $notification->end_date ); - - if ( ! $start && ! $end ) { - return true; - } - - $time = time(); - - if ( $time < $start ) { - return false; - } - - if ( $time > $end ) { - return false; - } - - return true; - } - } LLMS_Admin_Notifications::instance(); diff --git a/includes/class-llms-loader.php b/includes/class-llms-loader.php index 207a3f6c68..c23f471c42 100644 --- a/includes/class-llms-loader.php +++ b/includes/class-llms-loader.php @@ -338,7 +338,9 @@ public function includes_admin() { require_once LLMS_PLUGIN_DIR . 'includes/admin/class-llms-mailhawk.php'; require_once LLMS_PLUGIN_DIR . 'includes/admin/class-llms-sendwp.php'; require_once LLMS_PLUGIN_DIR . 'includes/forms/class-llms-forms-unsupported-versions.php'; - require_once LLMS_PLUGIN_DIR . 'includes/admin/class-llms-admin-notifications.php'; + require_once LLMS_PLUGIN_DIR . 'includes/admin/notifications/class-llms-admin-notification-condition.php'; + require_once LLMS_PLUGIN_DIR . 'includes/admin/notifications/class-llms-admin-notification.php'; + require_once LLMS_PLUGIN_DIR . 'includes/admin/notifications/class-llms-admin-notifications.php'; // Admin classes (files to be renamed). require_once LLMS_PLUGIN_DIR . 'includes/admin/class.llms.admin.dashboard.php'; From f139f89645d91756a94a3dc2ca9657613960dcb3 Mon Sep 17 00:00:00 2001 From: SEO Themes Date: Wed, 2 Aug 2023 16:22:11 +0800 Subject: [PATCH 05/10] Move admin notice HTML to `admin/notices/notice.php` template --- includes/admin/class.llms.admin.notices.php | 89 +++++++------------ templates/admin/notices/notice.php | 98 +++++++++++++++++++++ 2 files changed, 129 insertions(+), 58 deletions(-) create mode 100644 templates/admin/notices/notice.php diff --git a/includes/admin/class.llms.admin.notices.php b/includes/admin/class.llms.admin.notices.php index 82cf9778e5..2888c5fddd 100644 --- a/includes/admin/class.llms.admin.notices.php +++ b/includes/admin/class.llms.admin.notices.php @@ -5,7 +5,7 @@ * @package LifterLMS/Admin/Classes * * @since 3.0.0 - * @version 7.1.0 + * @version [version] */ defined( 'ABSPATH' ) || exit; @@ -302,71 +302,44 @@ function( $notice ) { * @since 3.7.4 Unknown. * @since 5.2.0 Ensure `template_path` and `default_path` are properly passed to `llms_get_template()`. * @since 5.3.1 Delete empty notices and do not display them. + * @since [version] Move HTML to `admin/notices/notice.php` template. * - * @param string $notice_id Notice id. + * @param string $id Notice id. * @return void */ - public static function output_notice( $notice_id ) { + public static function output_notice( string $id ) { - if ( current_user_can( 'manage_options' ) ) { + if ( ! current_user_can( 'manage_options' ) ) { + return; + } - $notice = self::get_notice( $notice_id ); + $notice = self::get_notice( $id ); - // Don't output those rogue empty notices I can't find. - // @todo find the source. - if ( empty( $notice ) || ( empty( $notice['template'] ) && empty( $notice['html'] ) ) ) { - self::delete_notice( $notice_id ); + // Don't output those rogue empty notices I can't find. + // @todo find the source. + if ( empty( $notice ) || ( empty( $notice['template'] ) && empty( $notice['html'] ) ) ) { + self::delete_notice( $id ); - return; - } + return; + } - $icon = $notice['icon'] ?? 'lifterlms'; - $icon_name = ucwords( str_replace('-', ' ', $icon ) ); - - ?> -
-
- -
- - - -
- -
- - - -
- -
- -
- - - - - - - - - - - - - - - - -

- -
-
- +
+
+ +
+ + + +
+ +
+ + + +
+ +
+ +
+ + + + + + + + +

+ + + + + + + + + + + + + + +

+ + + +

+ +
+
From d0cff08b730631b1b403d60298bfe2d02d297863 Mon Sep 17 00:00:00 2001 From: SEO Themes Date: Wed, 2 Aug 2023 16:23:08 +0800 Subject: [PATCH 06/10] Update admin notifications to allow user pausing and debugging --- assets/scss/admin/_main.scss | 4 +- .../admin/class-llms-admin-notifications.php | 494 ++++++++++++++++++ ...lass-llms-admin-notification-condition.php | 201 ------- .../class-llms-admin-notification.php | 175 ------- .../class-llms-admin-notifications.php | 200 ------- includes/class-llms-loader.php | 4 +- 6 files changed, 498 insertions(+), 580 deletions(-) create mode 100644 includes/admin/class-llms-admin-notifications.php delete mode 100644 includes/admin/notifications/class-llms-admin-notification-condition.php delete mode 100644 includes/admin/notifications/class-llms-admin-notification.php delete mode 100644 includes/admin/notifications/class-llms-admin-notifications.php diff --git a/assets/scss/admin/_main.scss b/assets/scss/admin/_main.scss index 6aa2a8675e..b6ff280371 100644 --- a/assets/scss/admin/_main.scss +++ b/assets/scss/admin/_main.scss @@ -236,6 +236,8 @@ div[id^="lifterlms-"] .inside { background-position: center center; background-repeat: no-repeat; background-size: 30px; + height: 30px; + width: 30px; } .llms-admin-notice-content { @@ -251,7 +253,7 @@ div[id^="lifterlms-"] .inside { font-size: 18px; font-weight: 700; line-height: 25px; - margin: 0 0 15px 0; + margin: 0 0 10px 0; } button, diff --git a/includes/admin/class-llms-admin-notifications.php b/includes/admin/class-llms-admin-notifications.php new file mode 100644 index 0000000000..dcb6ec8034 --- /dev/null +++ b/includes/admin/class-llms-admin-notifications.php @@ -0,0 +1,494 @@ +base, 'llms' ) && ! str_contains( $current_screen->id, 'llms' ) ) { + return; + } + + $notification = $this->get_next_notification(); + + if ( null === $notification || $this->is_paused() ) { + return; + } + + $this->display_notification( $notification ); + } + + /** + * Handle dismissing notifications. + * + * Handles the request to archive a notification, updating the + * user meta with the new list of archived notifications. + * + * @since [version] + * + * @return void + */ + public function dismiss_notification(): void { + if ( ! wp_verify_nonce( llms_filter_input( INPUT_GET, 'llms_admin_notification_nonce' ), 'llms_admin_notification_nonce' ) ) { + return; + } + + $id = llms_filter_input( INPUT_GET, 'llms_admin_notification_pause', FILTER_SANITIZE_NUMBER_INT ); + + if ( ! $id ) { + return; + } + + $current_user = get_current_user_id(); + $archived = $this->get_archived_notifications(); + $archived[ $id ] = date_i18n( 'c' ); + + update_user_meta( $current_user, 'llms_archived_notifications', $archived ); + } + + /** + * Get the next applicable notification. + * + * @since [version] + * + * @return stdClass|null The next notification object or null if none found. + */ + private function get_next_notification(): ?stdClass { + $notifications = $this->get_notifications(); + + if ( empty( $notifications ) ) { + return null; + } + + $notifications = $this->filter_and_sort_notifications( $notifications ); + $debug_id = $this->get_debug_id(); + + if ( $debug_id ) { + foreach ( $notifications as $notification ) { + if ( $notification->id === $debug_id ) { + return $notification; + } + } + } + + return reset( $notifications ) ?: null; + } + + /** + * Get all applicable notifications. + * + * @since [version] + * + * @return array Array of applicable notification objects. + */ + private function get_notifications(): array { + $name = 'llms_notificiations_' . llms()->version; + $notifications = get_transient( $name ); + + if ( ! $notifications || $this->get_debug_id() ) { + $notifications = $this->fetch_notifications(); + set_transient( $name, $notifications, DAY_IN_SECONDS ); + } + + foreach ( $notifications as $key => $notification ) { + if ( ! $this->is_applicable( $notification ) ) { + unset( $notifications[ $key ] ); + } + + // Map properties. + $notification->html = wp_kses_post( $notification->content ?? '' ); + $notification->icon = $notification->icon ?? $notification->dashicon ?? 'lifterlms'; + $notification->dismiss_url = wp_nonce_url( + add_query_arg( 'llms_admin_notification_pause', $notification->id ), + 'llms_admin_notification_nonce', + 'llms_admin_notification_nonce' + ); + + if ( ( $notification->type ?? '' ) === 'general' ) { + $notification->type = 'info'; + } + + $notifications[ $key ] = $notification; + } + + return $notifications; + } + + /** + * Fetch notifications from the server. + * + * @since [version] + * + * @return array Array of notification objects. + */ + private function fetch_notifications(): array { + $url = 'https://notifications.paidmembershipspro.com/v2/notifications.json'; + $url = defined( 'LLMS_ADMIN_NOTIFICATIONS_URL' ) ? LLMS_ADMIN_NOTIFICATIONS_URL : $url; + $url = apply_filters( 'llms_admin_notifications_url', $url ); + + $response = wp_remote_get( esc_url( $url ) ); + + if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) { + return []; + } + + $body = wp_remote_retrieve_body( $response ); + $notifications = json_decode( $body, false ); + + return is_array( $notifications ) ? $notifications : []; + } + + /** + * Filter out archived notifications and sort by priority. + * + * @since [version] + * + * @param array $notifications Array of notification objects. + * @return array Filtered and sorted array of notification objects. + */ + private function filter_and_sort_notifications( array $notifications ): array { + if ( $this->get_debug_id() ) { + return $notifications; + } + + $archived = $this->get_archived_notifications(); + + $notifications = array_filter( + $notifications, + static fn( stdClass $notification ): bool => ! isset( $archived[ $notification->id ] ) + ); + + usort( + $notifications, + static fn( stdClass $a, stdClass $b ): bool => $a->priority - $b->priority + ); + + return $notifications; + } + + /** + * Display the notification HTML. + * + * @since [version] + * + * @param object $notification The notification object. + * @return void + */ + private function display_notification( object $notification ): void { + llms_get_template( 'admin/notices/notice.php', (array) $notification ); + } + + /** + * Check if notifications should be paused. + * + * @since [version] + * + * @return bool + */ + private function is_paused(): bool { + if ( $this->get_debug_id() ) { + return false; + } + + $archived = $this->get_archived_notifications(); + $archived_count = count( $archived ); + + if ( 0 === $archived_count ) { + return false; + } + + $last_notification_date = end( $archived ); + $now = time(); + $delay = defined( 'LLMS_ADMIN_NOTIFICATIONS_DELAY' ) ? LLMS_ADMIN_NOTIFICATIONS_DELAY : 12 * HOUR_IN_SECONDS; + $delay = apply_filters( 'llms_admin_notifications_url', $delay ); + + // Delay notifications for 12 hours after the last notification. + if ( strtotime( $last_notification_date ) > ( $now - $delay ) ) { + return true; + } + + if ( $archived_count >= 3 && strtotime( $last_notification_date ) > ( $now - 7 * DAY_IN_SECONDS ) ) { + return true; + } + + return false; + + } + + /** + * Get the archived notifications for the current user. + * + * @since [version] + * + * @return array Array of archived notification IDs. + */ + private function get_archived_notifications(): array { + $current_user_id = get_current_user_id(); + $archived = get_user_meta( $current_user_id, 'llms_archived_notifications', true ); + + return is_array( $archived ) ? $archived : []; + } + + /** + * Check rules for a notification. + * + * @since [version] + * + * @param object $notification The notification object. + * @returns bool true if notification should be shown, false if not. + */ + private function is_applicable( object $notification ): bool { + + // If one is specified by URL parameter, it's allowed. + if ( $this->get_debug_id() ) { + return true; + } + + // Hide if today's date is before notification start date. + if ( date( 'Y-m-d', current_time( 'timestamp' ) ) < $notification->starts ) { + return false; + } + + // Hide if today's date is after end date. + if ( date( 'Y-m-d', current_time( 'timestamp' ) ) > $notification->ends ) { + return false; + } + + // Check priority, e.g. if only security notifications should be shown. + if ( $notification->priority > 5 ) { + return false; + } + + // Check show rules. + if ( ! $this->should_show( $notification ) ) { + return false; + } + + // Check hide rules. + if ( $this->should_hide( $notification ) ) { + return false; + } + + // If we get here, show it. + return true; + } + + /** + * Get the debug ID from the URL if set. + * + * @since [version] + * + * @return ?int The debug ID or false if not set. + */ + private function get_debug_id(): ?int { + return (int) llms_filter_input( INPUT_GET, 'notification_id', FILTER_SANITIZE_NUMBER_INT ) ?? null; + } + + /** + * Check a notification to see if we should show it + * based on the rules set. + * Shows if ALL rules are true. (AND) + * + * @since [version] + * + * @param object $notification The notification object. + * @return bool Whether the notification should be shown. + */ + private function should_show( object $notification ): bool { + $show = true; + + if ( empty( $notification->show_if ) ) { + return true; + } + + foreach ( $notification->show_if as $callback => $args ) { + if ( method_exists( $this, $callback ) ) { + if ( ! $this->$callback( ...$args ) ) { + $show = false; + break; + } + } + } + + return $show; + } + + /** + * Check a notification to see if we should hide it + * based on the rules set. + * + * Hides if ANY rule is true (OR). + * + * @since [version] + * + * @param object $notification The notification object. + * @return bool Whether the notification should be hidden. + */ + private function should_hide( object $notification ): bool { + $hide = false; + + if ( empty( $notification->hide_if ) ) { + return false; + } + + foreach ( $notification->hide_if as $callback => $args ) { + if ( method_exists( $this, $callback ) ) { + if ( $this->$callback( ...$args ) ) { + $hide = true; + break; + } + } + } + + return $hide; + } + + /** + * Checks if all plugins in array are active. + * + * First checks for short slug match, e.g. "lifterlms" before + * checking full plugin basename "lifterlms/lifterlms.php". + * + * @since [version] + * + * @param ...string $values Array of plugin slugs. + * @return bool + */ + private function plugins_active( ...$values ): bool { + + $plugins_active = false; + + foreach ( $values as $value ) { + if ( $this->plugin_active( $value ) ) { + $plugins_active = true; + } + } + + return $plugins_active; + + } + + /** + * Checks if a single plugin is active. + * + * @since [version] + * + * @param string $value Plugin slug. + * @return bool + */ + private function plugin_active( string $value ): bool { + + $active_plugins = get_option( 'active_plugins', array() ); + + if ( in_array( $value, $active_plugins, true ) ) { + return true; + } + + foreach ( $active_plugins as $active_plugin ) { + $without_php = basename( $active_plugin, '.php' ); + $plugin_parts = explode( '/', $active_plugin ); + $folder_name = $plugin_parts[0]; + $file_name = $plugin_parts[1]; + $file_without_php = basename( $file_name, '.php' ); + + if ( $value === $without_php || $value === $folder_name || $value === $file_name || $value === $file_without_php ) { + return true; + } + + } + + return false; + + } + + /** + * Checks if all plugins in array are inactive. + * + * @since [version] + * + * @param string[] ...$values Array of plugin slugs. + * @return bool + */ + private function plugins_inactive( array ...$values ): bool { + + $plugins_inactive = false; + + foreach ( $values as $value ) { + if ( $this->plugin_inactive( $value ) ) { + $plugins_inactive = true; + } + } + + return $plugins_inactive; + + } + + /** + * Checks if a single plugin is inactive. + * + * @since [version] + * + * @param string $value Plugin slug. + * @return bool + */ + private function plugin_inactive( string $value ): bool { + return ! $this->plugin_active( $value ); + } + + /** + * Checks LifterLMS version. + * + * @since [version] + * + * @param string[] ...$values Array of versions. + * @return bool + */ + private function llms_version( array ...$values ): bool { + $operator = $values[0] ?? '==='; + $version = $values[1] ?? false; + + return version_compare( llms()->version, $version, $operator ); + } +} + +return new LLMS_Admin_Notifications(); diff --git a/includes/admin/notifications/class-llms-admin-notification-condition.php b/includes/admin/notifications/class-llms-admin-notification-condition.php deleted file mode 100644 index 12df872cd5..0000000000 --- a/includes/admin/notifications/class-llms-admin-notification-condition.php +++ /dev/null @@ -1,201 +0,0 @@ -show_hide = $args->show_hide ?? 'show'; - $this->callback = $args->callback ?? null; - $this->operator = $args->operator ?? null; - $this->values = array_map( - 'trim', - explode( ',', $args->values ) - ) ?? []; - - } - - /** - * Check if the condition is met. - * - * @since [version] - * - * @return bool - */ - public function check_callback(): bool { - $return = false; - $callback = $this->callback; - $operator = $this->operator; - - if ( method_exists( $this, $callback ) ) { - if ( $operator ) { - $return = $this->$callback( $this->values, $this->operator ); - } else { - $return = $this->$callback( $this->values ); - } - } - - if ( function_exists( $callback ) ) { - $function = $callback; - - if ( $operator ) { - $return = $function( $this->values, $this->operator ); - } else { - $return = $function( $this->values ); - } - } - - return $this->show_hide === 'show' ? $return : ! $return; - - } - - /** - * Checks if all plugins in array are active. - * - * First checks for short slug match, e.g. "lifterlms" before - * checking full plugin basename "lifterlms/lifterlms.php". - * - * @since [version] - * - * @param array $values Array of plugin slugs. - * @return bool - */ - private function plugins_active( array $values ): bool { - - $plugins_active = false; - - foreach ( $values as $value ) { - if ( $this->plugin_active( $value ) ) { - $plugins_active = true; - } - } - - return $plugins_active; - - } - - /** - * Checks if a single plugin is active. - * - * @since [version] - * - * @param string $value Plugin slug. - * @return bool - */ - private function plugin_active( string $value ): bool { - - $active_plugins = get_option( 'active_plugins', array() ); - - if ( in_array( $value, $active_plugins, true ) ) { - return true; - } - - foreach ( $active_plugins as $active_plugin ) { - $without_php = basename( $active_plugin, '.php' ); - $plugin_parts = explode( '/', $active_plugin ); - $folder_name = $plugin_parts[0]; - $file_name = $plugin_parts[1]; - $file_without_php = basename( $file_name, '.php' ); - - if ( $value === $without_php || $value === $folder_name || $value === $file_name || $value === $file_without_php ) { - return true; - } - - } - - return false; - - } - - /** - * Checks if all plugins in array are inactive. - * - * @since [version] - * - * @param array $values Array of plugin slugs. - * @return bool - */ - private function plugins_inactive( array $values ): bool { - - $plugins_inactive = false; - - foreach ( $values as $value ) { - if ( $this->plugin_inactive( $value ) ) { - $plugins_inactive = true; - } - } - - return $plugins_inactive; - - } - - /** - * Checks if a single plugin is inactive. - * - * @since [version] - * - * @param string $value Plugin slug. - * @return bool - */ - private function plugin_inactive( string $value ): bool { - return ! $this->plugin_active( $value ); - } - - /** - * Checks LifterLMS version. - * - * @since [version] - * - * @param array $values Array of versions. - * @param string $operator Comparison operator. - * @return bool - */ - private function llms_version( array $values, string $operator ): bool { - - return version_compare( llms()->version, $values[0], $operator ); - - } - -} diff --git a/includes/admin/notifications/class-llms-admin-notification.php b/includes/admin/notifications/class-llms-admin-notification.php deleted file mode 100644 index c4917071a9..0000000000 --- a/includes/admin/notifications/class-llms-admin-notification.php +++ /dev/null @@ -1,175 +0,0 @@ -id = $args->id ?? 0; - $this->title = $args->title ?? esc_html__( 'Untitled Notification', 'lifterlms' ); - $this->content = $args->content ?? ''; - $this->start_date = $args->start_date ?? date( 'Y-m-d' ); - $this->end_date = $args->end_date ?? date( 'Y-m-d', strtotime( '+1 week' ) ); - $this->type = $args->type ?? 'info'; - $this->icon = $args->icon ?? 'info-outline'; - $this->priority = $args->priority ?? 10; - $this->dismissible = $args->dismissible ?? true; - $this->conditions = array_map( - static fn( object $condition ) => new LLMS_Admin_Notification_Condition( $condition ), - (array) ( $args->conditions ?? [] ) - ); - - } - - /** - * Check if the notification should be displayed. - * - * @since [version] - * - * @return bool - */ - public function check_conditions() { - - if ( ! $this->check_date_range() ) { - return false; - } - - if ( ! $this->conditions ) { - return true; - } - - foreach ( $this->conditions as $condition ) { - if ( ! $condition->check_callback() ) { - return false; - } - } - - return true; - - } - - /** - * Checks if a notification is within date range. - * - * @since [version] - * - * @return bool - */ - private function check_date_range(): bool { - - if ( ! $this->start_date && ! $this->end_date ) { - return false; - } - - $start = strtotime( $this->start_date ); - $end = strtotime( $this->end_date ); - - if ( ! $start && ! $end ) { - return false; - } - - $time = time(); - - if ( $time < $start ) { - return false; - } - - if ( $time > $end ) { - return false; - } - - return true; - } - -} diff --git a/includes/admin/notifications/class-llms-admin-notifications.php b/includes/admin/notifications/class-llms-admin-notifications.php deleted file mode 100644 index 461f48e898..0000000000 --- a/includes/admin/notifications/class-llms-admin-notifications.php +++ /dev/null @@ -1,200 +0,0 @@ -url = $this->get_notifications_url(); - - add_action( 'current_screen', array( $this, 'add_notifications' ) ); - } - - /** - * Conditionally displays notifications. - * - * @since [version] - * - * @param WP_Screen $current_screen WP_Screen instance. - * @return void - */ - public function add_notifications( WP_Screen $current_screen ): void { - - if ( ! str_contains( $current_screen->base, 'llms' ) && ! str_contains( $current_screen->id, 'llms' ) ) { - return; - } - - $notifications = $this->get_notifications( true ); - - if ( ! $notifications ) { - return; - } - - foreach ( $notifications as $notification ) { - - if ( ! $notification->check_conditions() ) { - continue; - } - - LLMS_Admin_Notices::add_notice( - $notification->id, - $notification->content, - array( - 'dismissible' => $notification->dismissible, - 'dismiss_for_days' => 7, - 'flash' => false, - 'html' => '', - 'remind_in_days' => 7, - 'remindable' => false, - 'type' => $notification->type, - 'icon' => $notification->icon, - 'template' => false, - 'template_path' => '', - 'default_path' => '', - 'priority' => $notification->priority, - ) - ); - } - } - - /** - * Returns the filtered URL to the notifications JSON file. - * - * @since [version] - * - * @return string - */ - private function get_notifications_url(): string { - - /** - * Filter the notifications URL. - * - * @since [version] - * - * @param string $url The URL. - */ - $url = apply_filters( - 'llms_notifications_url', - 'https://lifter-telemetry.local/wp-content/cache/llms/notifications/notifications.json' - ); - - return esc_url( $url ); - } - - /** - * Returns array of all notifications. - * - * @since [version] - * - * @param bool $fetch Force a refresh of the notifications. - * @return LLMS_Admin_Notification[] Array of applicable notification objects. - */ - private function get_notifications( bool $fetch = false ): array { - - $notifications = get_transient( $this->name ); - - if ( ! $notifications || $fetch ) { - $notifications = $this->fetch_notifications(); - - set_transient( $this->name, $notifications, DAY_IN_SECONDS ); - } - - /** - * Allows filtering of notifications. - * - * @since [version] - * - * @param array $notifications Array of notification objects. - */ - $filtered = apply_filters( $this->name, $notifications ); - - $notifications = array(); - - foreach ( $filtered as $notification ) { - $notifications[] = new LLMS_Admin_Notification( $notification ); - } - - usort( - $notifications, - static function ( - LLMS_Admin_Notification $a, - LLMS_Admin_Notification $b - ): bool { - return $a->priority - $b->priority; - } - ); - - return $notifications; - - } - - /** - * Fetches notifications from the server. - * - * @since [version] - * - * @return array Array of notification objects. - */ - private function fetch_notifications(): array { - - $response = wp_remote_get( - $this->get_notifications_url(), - array( - 'timeout' => 5, - 'sslverify' => false, - ) - ); - - if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) { - return []; - } - - $body = wp_remote_retrieve_body( $response ); - - return json_decode( $body, false ) ?? []; - - } - -} - -LLMS_Admin_Notifications::instance(); diff --git a/includes/class-llms-loader.php b/includes/class-llms-loader.php index c23f471c42..207a3f6c68 100644 --- a/includes/class-llms-loader.php +++ b/includes/class-llms-loader.php @@ -338,9 +338,7 @@ public function includes_admin() { require_once LLMS_PLUGIN_DIR . 'includes/admin/class-llms-mailhawk.php'; require_once LLMS_PLUGIN_DIR . 'includes/admin/class-llms-sendwp.php'; require_once LLMS_PLUGIN_DIR . 'includes/forms/class-llms-forms-unsupported-versions.php'; - require_once LLMS_PLUGIN_DIR . 'includes/admin/notifications/class-llms-admin-notification-condition.php'; - require_once LLMS_PLUGIN_DIR . 'includes/admin/notifications/class-llms-admin-notification.php'; - require_once LLMS_PLUGIN_DIR . 'includes/admin/notifications/class-llms-admin-notifications.php'; + require_once LLMS_PLUGIN_DIR . 'includes/admin/class-llms-admin-notifications.php'; // Admin classes (files to be renamed). require_once LLMS_PLUGIN_DIR . 'includes/admin/class.llms.admin.dashboard.php'; From 9cae66ade3daae56b2abe68049c51097a2625213 Mon Sep 17 00:00:00 2001 From: SEO Themes Date: Thu, 3 Aug 2023 17:30:51 +0800 Subject: [PATCH 07/10] Use custom `wp_kses` instead of `wp_kses_post` for notification html --- .../admin/class-llms-admin-notifications.php | 43 ++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/includes/admin/class-llms-admin-notifications.php b/includes/admin/class-llms-admin-notifications.php index dcb6ec8034..ba6f3fa9b0 100644 --- a/includes/admin/class-llms-admin-notifications.php +++ b/includes/admin/class-llms-admin-notifications.php @@ -135,8 +135,35 @@ private function get_notifications(): array { unset( $notifications[ $key ] ); } + $allowed_html = array ( + 'a' => array ( + 'class' => array(), + 'href' => array(), + 'target' => array(), + 'title' => array(), + ), + 'button' => array ( + 'class' => array(), + ), + 'div' => array( + 'class' => array(), + ), + 'p' => array( + 'class' => array(), + ), + 'b' => array( + 'class' => array(), + ), + 'em' => array( + 'class' => array(), + ), + 'br' => array(), + 'strike' => array(), + 'strong' => array(), + ); + // Map properties. - $notification->html = wp_kses_post( $notification->content ?? '' ); + $notification->html = wp_kses( $notification->content ?? '', $allowed_html ); $notification->icon = $notification->icon ?? $notification->dashicon ?? 'lifterlms'; $notification->dismiss_url = wp_nonce_url( add_query_arg( 'llms_admin_notification_pause', $notification->id ), @@ -198,6 +225,20 @@ private function filter_and_sort_notifications( array $notifications ): array { static fn( stdClass $notification ): bool => ! isset( $archived[ $notification->id ] ) ); + $max_priority = (int) get_option( 'lifterlms_max_notifications_priority', 5 ); + + foreach ( $notifications as $key => $notification ) { + $priority = (int) $notification->priority ?? 5; + + if ( $priority > 5 ) { + $notification->priority = 5; + } + + if ( $priority > $max_priority ) { + unset( $notifications[ $key ] ); + } + } + usort( $notifications, static fn( stdClass $a, stdClass $b ): bool => $a->priority - $b->priority From 9da2c922f195126274cb6033554d24c6e6bea6c7 Mon Sep 17 00:00:00 2001 From: SEO Themes Date: Thu, 3 Aug 2023 17:32:42 +0800 Subject: [PATCH 08/10] Add max notification priority communication settings --- .../settings/class.llms.settings.general.php | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/includes/admin/settings/class.llms.settings.general.php b/includes/admin/settings/class.llms.settings.general.php index b93ed7790a..e82640ed3e 100644 --- a/includes/admin/settings/class.llms.settings.general.php +++ b/includes/admin/settings/class.llms.settings.general.php @@ -5,7 +5,7 @@ * @package LifterLMS/Admin/Settings/Classes * * @since 1.0.0 - * @version 6.0.0 + * @version [version] */ defined( 'ABSPATH' ) || exit; @@ -45,6 +45,7 @@ public function __construct() { * @since 3.13.0 Unknown. * @since 5.6.0 use LLMS_Roles::get_all_role_names() to retrieve the list of roles who can bypass enrollments. * Add content protection setting. + * @since [version] Add communication settings. * * @return array */ @@ -120,6 +121,35 @@ function ( $role ) { 'type' => 'sectionend', ); + $settings[] = array( + 'id' => 'section_communication', + 'type' => 'sectionstart', + 'class' => 'top', + ); + + $settings[] = array( + 'id' => 'communication', + 'title' => __( 'Communication Settings', 'lifterlms' ), + 'type' => 'title', + ); + + $settings[] = array( + 'title' => __( 'Notifications', 'lifterlms' ), + 'type' => 'select', + 'default' => 5, + 'desc' => __( 'Control the display of the notifications occasionally shown on the LifterLMS settings pages.', 'lifterlms' ), + 'id' => 'lifterlms_max_notifications_priority', + 'options' => array( + 5 => __( 'All (security and marketing)', 'lifterlms' ), + 1 => __( 'Security only', 'lifterlms' ), + ), + ); + + $settings[] = array( + 'id' => 'section_communication', + 'type' => 'sectionend', + ); + return apply_filters( 'lifterlms_general_settings', $settings ); } From ba1d3672042a20b8bf9152a8916710ee47598266 Mon Sep 17 00:00:00 2001 From: SEO Themes Date: Wed, 9 Aug 2023 21:06:29 +0800 Subject: [PATCH 09/10] Add ajax for admin notifications --- .../admin/class-llms-admin-notifications.php | 123 +++++++++++++----- includes/assets/llms-assets-scripts.php | 5 + src/js/admin-notifications.js | 80 ++++++++++++ templates/admin/notices/notice.php | 4 +- webpack.config.js | 5 +- 5 files changed, 177 insertions(+), 40 deletions(-) create mode 100644 src/js/admin-notifications.js diff --git a/includes/admin/class-llms-admin-notifications.php b/includes/admin/class-llms-admin-notifications.php index ba6f3fa9b0..a32fb53fb8 100644 --- a/includes/admin/class-llms-admin-notifications.php +++ b/includes/admin/class-llms-admin-notifications.php @@ -25,37 +25,32 @@ class LLMS_Admin_Notifications { * @return void */ public function __construct() { - add_action( 'current_screen', [ $this, 'show_notifications' ] ); - add_action( 'current_screen', [ $this, 'dismiss_notification' ], 11 ); + add_action( 'wp_ajax_llms_dismiss_notification', [ $this, 'dismiss_notification' ] ); + add_action( 'wp_ajax_llms_show_notification', [ $this, 'show_notification' ] ); + add_action( 'admin_enqueue_scripts', [ $this, 'admin_scripts' ] ); } /** - * AJAX callback to check and display notifications. - * - * Checks permissions, retrieves the next notification, checks if notifications are paused, - * and finally, outputs the next notification if one is available. + * Enqueue admin scripts. * * @since [version] * - * @param WP_Screen $current_screen WP_Screen instance. * @return void */ - public function show_notifications( WP_Screen $current_screen ): void { - if ( ! current_user_can( 'manage_options' ) ) { - return; - } - - if ( ! str_contains( $current_screen->base, 'llms' ) && ! str_contains( $current_screen->id, 'llms' ) ) { - return; - } - - $notification = $this->get_next_notification(); - - if ( null === $notification || $this->is_paused() ) { - return; - } - - $this->display_notification( $notification ); + public function admin_scripts() { + $handle = 'llms-admin-notifications'; + + llms()->assets->enqueue_script( $handle ); + + wp_localize_script( + $handle, + 'llmsAdminNotifications', + [ + 'ajaxurl' => admin_url( 'admin-ajax.php' ), + 'nonce' => wp_create_nonce( 'llms_admin_notification_nonce' ), + 'paused' => $this->is_paused() ? 'true' : 'false', + ] + ); } /** @@ -69,21 +64,71 @@ public function show_notifications( WP_Screen $current_screen ): void { * @return void */ public function dismiss_notification(): void { - if ( ! wp_verify_nonce( llms_filter_input( INPUT_GET, 'llms_admin_notification_nonce' ), 'llms_admin_notification_nonce' ) ) { - return; + if ( ! wp_verify_nonce( llms_filter_input( INPUT_POST, 'nonce' ), 'llms_admin_notification_nonce' ) ) { + wp_send_json_error( __( 'Invalid nonce.', 'lifterlms' ) ); } - $id = llms_filter_input( INPUT_GET, 'llms_admin_notification_pause', FILTER_SANITIZE_NUMBER_INT ); + $id = llms_filter_input( INPUT_POST, 'id', FILTER_SANITIZE_NUMBER_INT ); if ( ! $id ) { - return; + wp_send_json_error( __( 'Invalid notification ID.', 'lifterlms' ) ); + } + + $current_user = get_current_user_id(); + + if ( ! $current_user ) { + wp_send_json_error( __( 'Invalid user.', 'lifterlms' ) ); } - $current_user = get_current_user_id(); $archived = $this->get_archived_notifications(); $archived[ $id ] = date_i18n( 'c' ); update_user_meta( $current_user, 'llms_archived_notifications', $archived ); + + wp_send_json_success(); + } + + /** + * AJAX callback to check and display notifications. + * + * Checks permissions, retrieves the next notification, checks if notifications are paused, + * and finally, outputs the next notification if one is available. + * + * @since [version] + * + * @return void + */ + public function show_notification(): void { + if ( ! current_user_can( 'manage_options' ) ) { + wp_send_json_error( __( 'You do not have permission to view notifications.', 'lifterlms' ) ); + } + + $nonce = llms_filter_input( INPUT_POST, 'nonce', FILTER_SANITIZE_STRING ); + + if ( ! wp_verify_nonce( $nonce, 'llms_admin_notification_nonce' ) ) { + wp_send_json_error( __( 'Invalid nonce.', 'lifterlms' ) ); + } + + $notification = $this->get_next_notification(); + + if ( null === $notification ) { + wp_send_json_error( __( 'No notifications available.', 'lifterlms' ) ); + } + + $html = $this->display_notification( $notification ); + + if ( ! $html ) { + wp_send_json_error( __( 'An error occurred while displaying the notification.', 'lifterlms' ) ); + } else { + + $html = str_replace( + array( "\n", "\r", "\t" ), + array( '', '', '' ), + $html + ); + + wp_send_json_success( $html ); + } } /** @@ -124,13 +169,19 @@ private function get_next_notification(): ?stdClass { private function get_notifications(): array { $name = 'llms_notificiations_' . llms()->version; $notifications = get_transient( $name ); + $debug_id = $this->get_debug_id(); - if ( ! $notifications || $this->get_debug_id() ) { + if ( ! $notifications || $debug_id ) { $notifications = $this->fetch_notifications(); + set_transient( $name, $notifications, DAY_IN_SECONDS ); } foreach ( $notifications as $key => $notification ) { + if ( $debug_id && $notification->id === $debug_id ) { + return [ $notification ]; + } + if ( ! $this->is_applicable( $notification ) ) { unset( $notifications[ $key ] ); } @@ -165,11 +216,7 @@ private function get_notifications(): array { // Map properties. $notification->html = wp_kses( $notification->content ?? '', $allowed_html ); $notification->icon = $notification->icon ?? $notification->dashicon ?? 'lifterlms'; - $notification->dismiss_url = wp_nonce_url( - add_query_arg( 'llms_admin_notification_pause', $notification->id ), - 'llms_admin_notification_nonce', - 'llms_admin_notification_nonce' - ); + $notification->dismiss_url = ''; if ( ( $notification->type ?? '' ) === 'general' ) { $notification->type = 'info'; @@ -253,10 +300,14 @@ private function filter_and_sort_notifications( array $notifications ): array { * @since [version] * * @param object $notification The notification object. - * @return void + * @return string */ - private function display_notification( object $notification ): void { + private function display_notification( object $notification ): string { + ob_start(); + llms_get_template( 'admin/notices/notice.php', (array) $notification ); + + return ob_get_clean(); } /** diff --git a/includes/assets/llms-assets-scripts.php b/includes/assets/llms-assets-scripts.php index c8bf2773d8..79eace791e 100644 --- a/includes/assets/llms-assets-scripts.php +++ b/includes/assets/llms-assets-scripts.php @@ -70,6 +70,11 @@ 'asset_file' => true, 'suffix' => '', ), + 'llms-admin-notifications' => array( + 'asset_file' => true, + 'suffix' => '', + 'dependencies' => array( 'jquery' ), + ), // Modules. 'llms-components' => array( diff --git a/src/js/admin-notifications.js b/src/js/admin-notifications.js new file mode 100644 index 0000000000..43ea18c579 --- /dev/null +++ b/src/js/admin-notifications.js @@ -0,0 +1,80 @@ +( ( $ ) => { + const llmsAdminNotifications = window?.llmsAdminNotifications || {}; + + if ( ! llmsAdminNotifications ) { + return; + } + + if ( llmsAdminNotifications?.paused === 'true' ) { + return; + } + + $.post( + llmsAdminNotifications.ajaxurl, + { + 'action': 'llms_show_notification', + 'nonce': llmsAdminNotifications.nonce, + }, + ( response ) => { + console.log(response); + + if ( ! response?.success ) { + return; + } + + const data = response?.data ?? {}; + + if ( ! data ) { + return; + } + + let insideWrap = document.querySelectorAll( '.lifterlms-settings form > .llms-inside-wrap' )[0]; + + if ( ! insideWrap ) { + insideWrap = document.querySelectorAll( '.lifterlms-settings .llms-inside-wrap > *' )[0]; + } + + if ( ! insideWrap ) { + return; + } + + // Convert response to DOM element. + const parser= new DOMParser(); + const parsed = parser.parseFromString( data, 'text/html' ); + const div= parsed.querySelector( 'div' ); + + if ( ! div ) { + return; + } + + insideWrap.parentNode.insertBefore( div, insideWrap ); + + const id = div.getAttribute( 'id' )?.replace( 'llms-notice-', '' ); + + const dismissButton = div.querySelector( '.notice-dismiss' ); + + if ( ! dismissButton ) { + return; + } + + dismissButton.addEventListener( + 'click', + () => { + $.post( + llmsAdminNotifications.ajaxurl, + { + 'action': 'llms_dismiss_notification', + 'nonce': llmsAdminNotifications.nonce, + 'id': id, + }, + ( response ) => { + if ( response?.success ) { + div.remove(); + } + } + ); + } + ); + } + ); +} )( jQuery ); diff --git a/templates/admin/notices/notice.php b/templates/admin/notices/notice.php index 3836bb8e83..8b1f1e658c 100644 --- a/templates/admin/notices/notice.php +++ b/templates/admin/notices/notice.php @@ -42,7 +42,7 @@ $remind_url = $remind_url ?? null; ?> -
+
@@ -61,7 +61,7 @@
- + > diff --git a/webpack.config.js b/webpack.config.js index c2af9e749b..4f0d87b450 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -16,6 +16,7 @@ const { resolve } = require( 'path' ), 'admin-addons', 'admin-award-certificate', 'admin-certificate-editor', + 'admin-notifications', 'quill-wordcount', // Module packages. @@ -24,7 +25,7 @@ const { resolve } = require( 'path' ), 'spinner', 'utils', ], - css: [ + css: [ 'admin-addons' ], } ); @@ -46,7 +47,7 @@ config.plugins.push( new CleanWebpackPlugin( { // config.entry.fontawesome = resolve( './src/scss/fontawesome.scss' ); -module.exports = [ +module.exports = [ blocksConfig, config ]; From f3cc5f7d12567a8326d9e6c5541cd81b702f6228 Mon Sep 17 00:00:00 2001 From: Jason Coleman Date: Wed, 15 Jan 2025 14:55:12 -0500 Subject: [PATCH 10/10] Fixing some PHP errors and now pointing at the LifterLMS notifications server. --- includes/admin/class-llms-admin-notifications.php | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/includes/admin/class-llms-admin-notifications.php b/includes/admin/class-llms-admin-notifications.php index a32fb53fb8..9927e48a7e 100644 --- a/includes/admin/class-llms-admin-notifications.php +++ b/includes/admin/class-llms-admin-notifications.php @@ -103,7 +103,7 @@ public function show_notification(): void { wp_send_json_error( __( 'You do not have permission to view notifications.', 'lifterlms' ) ); } - $nonce = llms_filter_input( INPUT_POST, 'nonce', FILTER_SANITIZE_STRING ); + $nonce = llms_filter_input( INPUT_POST, 'nonce' ); if ( ! wp_verify_nonce( $nonce, 'llms_admin_notification_nonce' ) ) { wp_send_json_error( __( 'Invalid nonce.', 'lifterlms' ) ); @@ -236,7 +236,7 @@ private function get_notifications(): array { * @return array Array of notification objects. */ private function fetch_notifications(): array { - $url = 'https://notifications.paidmembershipspro.com/v2/notifications.json'; + $url = 'https://notifications.lifterlms.com/v1/notifications.json'; $url = defined( 'LLMS_ADMIN_NOTIFICATIONS_URL' ) ? LLMS_ADMIN_NOTIFICATIONS_URL : $url; $url = apply_filters( 'llms_admin_notifications_url', $url ); @@ -286,10 +286,8 @@ private function filter_and_sort_notifications( array $notifications ): array { } } - usort( - $notifications, - static fn( stdClass $a, stdClass $b ): bool => $a->priority - $b->priority - ); + // Sort by priority. + $notifications = wp_list_sort( $notifications, 'priority' ); return $notifications; }