Analytics
2 months ago
Database
5 months ago
Elementor_Enhancer.php
6 months ago
EmbedPress_Core_Installer.php
6 years ago
EmbedPress_Notice.php
3 months ago
EmbedPress_Plugin_Usage_Tracker.php
2 months ago
Extend_CustomPlayer_Controls.php
1 year ago
Extend_Elementor_Controls.php
1 year ago
FeatureNoticeManager.php
8 months ago
FeatureNotices.php
8 months ago
Feature_Enhancer.php
2 months ago
Helper.php
2 months ago
Pdf_Thumbnail_Handler.php
2 months ago
PermalinkHelper.php
10 months ago
README_FEATURE_NOTICES.md
8 months ago
FeatureNoticeManager.php
336 lines
| 1 | <?php |
| 2 | namespace EmbedPress\Includes\Classes; |
| 3 | |
| 4 | /** |
| 5 | * Feature Notice Manager |
| 6 | * |
| 7 | * A modern, reusable notice system for announcing new features and important updates. |
| 8 | * Displays beautiful notices in the WordPress admin with customizable content, buttons, and actions. |
| 9 | * |
| 10 | * @package EmbedPress |
| 11 | * @since 4.1.0 |
| 12 | */ |
| 13 | |
| 14 | (defined('ABSPATH') && defined('EMBEDPRESS_IS_LOADED')) or die("No direct script access allowed."); |
| 15 | |
| 16 | class FeatureNoticeManager { |
| 17 | |
| 18 | /** |
| 19 | * Option name for storing dismissed notices |
| 20 | */ |
| 21 | const DISMISSED_NOTICES_OPTION = 'embedpress_dismissed_feature_notices'; |
| 22 | |
| 23 | /** |
| 24 | * Option name for storing skipped notices |
| 25 | */ |
| 26 | const SKIPPED_NOTICES_OPTION = 'embedpress_skipped_feature_notices'; |
| 27 | |
| 28 | /** |
| 29 | * Registered notices |
| 30 | * @var array |
| 31 | */ |
| 32 | private $notices = []; |
| 33 | |
| 34 | /** |
| 35 | * Singleton instance |
| 36 | * @var FeatureNoticeManager |
| 37 | */ |
| 38 | private static $instance = null; |
| 39 | |
| 40 | /** |
| 41 | * Get singleton instance |
| 42 | * |
| 43 | * @return FeatureNoticeManager |
| 44 | */ |
| 45 | public static function get_instance() { |
| 46 | if (self::$instance === null) { |
| 47 | self::$instance = new self(); |
| 48 | } |
| 49 | return self::$instance; |
| 50 | } |
| 51 | |
| 52 | /** |
| 53 | * Constructor |
| 54 | */ |
| 55 | private function __construct() { |
| 56 | // Render tooltip in admin footer instead of admin_notices |
| 57 | add_action('admin_footer', [$this, 'display_menu_tooltip']); |
| 58 | add_action('wp_ajax_embedpress_dismiss_feature_notice', [$this, 'ajax_dismiss_notice']); |
| 59 | add_action('wp_ajax_embedpress_skip_feature_notice', [$this, 'ajax_skip_notice']); |
| 60 | add_action('wp_ajax_embedpress_view_feature_notice', [$this, 'ajax_view_notice']); |
| 61 | |
| 62 | // Add menu badge indicator |
| 63 | add_action('admin_menu', [$this, 'add_menu_badge'], 999); |
| 64 | } |
| 65 | |
| 66 | /** |
| 67 | * Register a new feature notice |
| 68 | * |
| 69 | * @param string $id Unique notice ID |
| 70 | * @param array $args Notice configuration |
| 71 | * |
| 72 | * Example usage: |
| 73 | * FeatureNoticeManager::get_instance()->register_notice('analytics_dashboard', [ |
| 74 | * 'title' => 'New Features', |
| 75 | * 'icon' => '🎉', |
| 76 | * 'message' => 'New In EmbedPress: Introducing Analytics dashboard...', |
| 77 | * 'button_text' => 'View Analytics', |
| 78 | * 'button_url' => admin_url('admin.php?page=embedpress&page_type=analytics'), |
| 79 | * 'skip_text' => 'Skip', |
| 80 | * 'screens' => ['toplevel_page_embedpress'], // Show only on specific screens |
| 81 | * 'capability' => 'manage_options', |
| 82 | * 'start_date' => '2024-01-01', // Optional: when to start showing |
| 83 | * 'end_date' => '2024-12-31', // Optional: when to stop showing |
| 84 | * 'priority' => 10, |
| 85 | * ]); |
| 86 | */ |
| 87 | public function register_notice($id, $args = []) { |
| 88 | $defaults = [ |
| 89 | 'title' => 'New Features', |
| 90 | 'icon' => '<svg width="11" height="13" viewBox="0 0 11 13" fill="none" xmlns="http://www.w3.org/2000/svg"> |
| 91 | <path d="M0 1.61956C1.26645e-05 1.53091 0.0177073 1.44315 0.0520487 1.36142C0.0863901 1.27968 0.136689 1.20562 0.2 1.14356C0.926563 0.431321 1.89723 0.0227172 2.91444 0.000919081C3.93164 -0.0208791 4.91893 0.345767 5.67533 1.02623L5.90933 1.2449C6.39562 1.67096 7.02013 1.90585 7.66667 1.90585C8.3132 1.90585 8.93771 1.67096 9.424 1.2449L9.59 1.09356C9.99667 0.771563 10.608 1.0289 10.6633 1.54423L10.6667 1.61956V7.61956C10.6667 7.70822 10.649 7.79598 10.6146 7.87771C10.5803 7.95944 10.53 8.03351 10.4667 8.09556C9.7401 8.8078 8.76943 9.21641 7.75223 9.23821C6.73502 9.26 5.74774 8.89336 4.99133 8.2129L4.75733 7.99423C4.28636 7.58157 3.68518 7.3478 3.05915 7.33391C2.43311 7.32001 1.82216 7.52687 1.33333 7.91823V12.2862C1.33314 12.4561 1.26808 12.6196 1.15143 12.7431C1.03479 12.8667 0.875365 12.9411 0.705737 12.951C0.536109 12.961 0.36908 12.9058 0.238778 12.7967C0.108476 12.6877 0.0247357 12.533 0.00466665 12.3642L0 12.2862V1.61956Z" fill="#25396F"/> |
| 92 | </svg>', |
| 93 | 'message' => '', |
| 94 | 'button_text' => 'Learn More', |
| 95 | 'button_url' => '', |
| 96 | 'button_target' => '_self', |
| 97 | 'skip_text' => 'Skip', |
| 98 | 'screens' => [], // Empty means show on all admin pages |
| 99 | 'capability' => 'manage_options', |
| 100 | 'start_date' => null, |
| 101 | 'end_date' => null, |
| 102 | 'priority' => 10, |
| 103 | 'dismissible' => true, |
| 104 | 'type' => 'info', // info, success, warning, error |
| 105 | ]; |
| 106 | |
| 107 | $this->notices[$id] = wp_parse_args($args, $defaults); |
| 108 | $this->notices[$id]['id'] = $id; |
| 109 | } |
| 110 | |
| 111 | /** |
| 112 | * Check if a notice should be displayed |
| 113 | * |
| 114 | * @param string $id Notice ID |
| 115 | * @return bool |
| 116 | */ |
| 117 | private function should_display_notice($id) { |
| 118 | if (!isset($this->notices[$id])) { |
| 119 | return false; |
| 120 | } |
| 121 | |
| 122 | $notice = $this->notices[$id]; |
| 123 | |
| 124 | // Only show on dashboard page |
| 125 | $screen = get_current_screen(); |
| 126 | if (!$screen || $screen->id !== 'dashboard') { |
| 127 | return false; |
| 128 | } |
| 129 | |
| 130 | // Check capability |
| 131 | if (!current_user_can($notice['capability'])) { |
| 132 | return false; |
| 133 | } |
| 134 | |
| 135 | // Check if dismissed (any action = permanent dismiss) |
| 136 | $dismissed = get_option(self::DISMISSED_NOTICES_OPTION, []); |
| 137 | if (in_array($id, $dismissed)) { |
| 138 | return false; |
| 139 | } |
| 140 | |
| 141 | // Check date range |
| 142 | if ($notice['start_date'] && strtotime($notice['start_date']) > time()) { |
| 143 | return false; |
| 144 | } |
| 145 | |
| 146 | if ($notice['end_date'] && strtotime($notice['end_date']) < time()) { |
| 147 | return false; |
| 148 | } |
| 149 | |
| 150 | return true; |
| 151 | } |
| 152 | |
| 153 | /** |
| 154 | * Add badge indicator to EmbedPress menu |
| 155 | */ |
| 156 | public function add_menu_badge() { |
| 157 | global $menu; |
| 158 | |
| 159 | // Check if there's any active notice |
| 160 | $has_active_notice = false; |
| 161 | foreach ($this->notices as $id => $notice) { |
| 162 | if ($this->should_display_notice($id)) { |
| 163 | $has_active_notice = true; |
| 164 | break; |
| 165 | } |
| 166 | } |
| 167 | |
| 168 | if (!$has_active_notice) { |
| 169 | return; |
| 170 | } |
| 171 | } |
| 172 | |
| 173 | /** |
| 174 | * Display menu tooltip |
| 175 | * |
| 176 | * Renders tooltip/popover next to EmbedPress menu item |
| 177 | */ |
| 178 | public function display_menu_tooltip() { |
| 179 | // Get the highest priority notice |
| 180 | $notice_to_display = null; |
| 181 | $notice_id = null; |
| 182 | |
| 183 | foreach ($this->notices as $id => $notice) { |
| 184 | if ($this->should_display_notice($id)) { |
| 185 | if ($notice_to_display === null || $notice['priority'] < $notice_to_display['priority']) { |
| 186 | $notice_to_display = $notice; |
| 187 | $notice_id = $id; |
| 188 | } |
| 189 | } |
| 190 | } |
| 191 | |
| 192 | if (!$notice_to_display) { |
| 193 | return; |
| 194 | } |
| 195 | |
| 196 | // Render tooltip HTML to be injected via JavaScript |
| 197 | ?> |
| 198 | <script type="text/html" id="embedpress-feature-tooltip-template"> |
| 199 | <?php $this->render_tooltip($notice_id, $notice_to_display); ?> |
| 200 | </script> |
| 201 | <script type="text/javascript"> |
| 202 | jQuery(document).ready(function($) { |
| 203 | // Inject tooltip into menu item |
| 204 | var $menuItem = $('#toplevel_page_embedpress'); |
| 205 | if ($menuItem.length) { |
| 206 | var tooltipHtml = $('#embedpress-feature-tooltip-template').html(); |
| 207 | $menuItem.append(tooltipHtml); |
| 208 | } |
| 209 | }); |
| 210 | </script> |
| 211 | <?php |
| 212 | } |
| 213 | |
| 214 | /** |
| 215 | * Render tooltip/popover |
| 216 | * |
| 217 | * @param string $id Notice ID |
| 218 | * @param array $notice Notice configuration |
| 219 | */ |
| 220 | private function render_tooltip($id, $notice) { |
| 221 | |
| 222 | error_log(print_r($notice, true)); |
| 223 | |
| 224 | $icon = !empty($notice['icon']) ? $notice['icon'] : ''; |
| 225 | $title = esc_html($notice['title']); |
| 226 | $message = wp_kses_post($notice['message']); |
| 227 | $button_text = esc_html($notice['button_text']); |
| 228 | $button_url = esc_url($notice['button_url']); |
| 229 | $button_target = esc_attr($notice['button_target']); |
| 230 | $skip_text = esc_html($notice['skip_text']); |
| 231 | $notice_id = esc_attr($id); |
| 232 | $type = esc_attr($notice['type']); |
| 233 | |
| 234 | ?> |
| 235 | <div id="embedpress-feature-tooltip" class="embedpress-feature-tooltip embedpress-feature-tooltip--<?php echo $type; ?>" data-notice-id="<?php echo $notice_id; ?>" style="visibility: hidden; opacity: 0;"> |
| 236 | <div class="embedpress-feature-tooltip__arrow"></div> |
| 237 | <button type="button" class="embedpress-feature-tooltip__close" aria-label="Close"> |
| 238 | <span class="dashicons dashicons-no-alt"></span> |
| 239 | </button> |
| 240 | <div class="embedpress-feature-tooltip__header"> |
| 241 | <span class="embedpress-feature-tooltip__icon"><?php echo $icon; ?></span> |
| 242 | <h3 class="embedpress-feature-tooltip__title"><?php echo $title; ?></h3> |
| 243 | </div> |
| 244 | <div class="embedpress-feature-tooltip__content"> |
| 245 | <div class="embedpress-feature-tooltip__message"> |
| 246 | <?php echo $message; ?> |
| 247 | </div> |
| 248 | <div class="embedpress-feature-tooltip__actions"> |
| 249 | <?php if ($notice['skip_text']): ?> |
| 250 | <button type="button" class="embedpress-feature-tooltip__skip" data-action="skip"> |
| 251 | <?php echo $skip_text; ?> |
| 252 | </button> |
| 253 | <?php endif; ?> |
| 254 | |
| 255 | <?php if ($notice['button_text'] && $notice['button_url']): ?> |
| 256 | <a href="<?php echo $button_url; ?>" |
| 257 | target="<?php echo $button_target; ?>" |
| 258 | class="embedpress-feature-tooltip__button"> |
| 259 | <?php echo $button_text; ?> |
| 260 | </a> |
| 261 | <?php endif; ?> |
| 262 | </div> |
| 263 | </div> |
| 264 | </div> |
| 265 | <?php |
| 266 | } |
| 267 | |
| 268 | /** |
| 269 | * AJAX handler for dismissing a notice |
| 270 | */ |
| 271 | public function ajax_dismiss_notice() { |
| 272 | check_ajax_referer('embedpress_feature_notice', 'nonce'); |
| 273 | |
| 274 | $notice_id = isset($_POST['notice_id']) ? sanitize_text_field($_POST['notice_id']) : ''; |
| 275 | |
| 276 | if (empty($notice_id)) { |
| 277 | wp_send_json_error(['message' => 'Invalid notice ID']); |
| 278 | } |
| 279 | |
| 280 | $dismissed = get_option(self::DISMISSED_NOTICES_OPTION, []); |
| 281 | if (!in_array($notice_id, $dismissed)) { |
| 282 | $dismissed[] = $notice_id; |
| 283 | update_option(self::DISMISSED_NOTICES_OPTION, $dismissed); |
| 284 | } |
| 285 | |
| 286 | wp_send_json_success(['message' => 'Notice dismissed']); |
| 287 | } |
| 288 | |
| 289 | /** |
| 290 | * AJAX handler for skipping a notice |
| 291 | * Skip = Permanent dismiss (same as close button) |
| 292 | */ |
| 293 | public function ajax_skip_notice() { |
| 294 | check_ajax_referer('embedpress_feature_notice', 'nonce'); |
| 295 | |
| 296 | $notice_id = isset($_POST['notice_id']) ? sanitize_text_field($_POST['notice_id']) : ''; |
| 297 | |
| 298 | if (empty($notice_id)) { |
| 299 | wp_send_json_error(['message' => 'Invalid notice ID']); |
| 300 | } |
| 301 | |
| 302 | // Permanently dismiss (same as close button) |
| 303 | $dismissed = get_option(self::DISMISSED_NOTICES_OPTION, []); |
| 304 | if (!in_array($notice_id, $dismissed)) { |
| 305 | $dismissed[] = $notice_id; |
| 306 | update_option(self::DISMISSED_NOTICES_OPTION, $dismissed); |
| 307 | } |
| 308 | |
| 309 | wp_send_json_success(['message' => 'Notice dismissed']); |
| 310 | } |
| 311 | |
| 312 | /** |
| 313 | * AJAX handler for viewing a notice (clicking primary button) |
| 314 | * View = Permanent dismiss (user engaged with the notice) |
| 315 | */ |
| 316 | public function ajax_view_notice() { |
| 317 | check_ajax_referer('embedpress_feature_notice', 'nonce'); |
| 318 | |
| 319 | $notice_id = isset($_POST['notice_id']) ? sanitize_text_field($_POST['notice_id']) : ''; |
| 320 | |
| 321 | if (empty($notice_id)) { |
| 322 | wp_send_json_error(['message' => 'Invalid notice ID']); |
| 323 | } |
| 324 | |
| 325 | // Permanently dismiss (user clicked the button) |
| 326 | $dismissed = get_option(self::DISMISSED_NOTICES_OPTION, []); |
| 327 | if (!in_array($notice_id, $dismissed)) { |
| 328 | $dismissed[] = $notice_id; |
| 329 | update_option(self::DISMISSED_NOTICES_OPTION, $dismissed); |
| 330 | } |
| 331 | |
| 332 | wp_send_json_success(['message' => 'Notice dismissed']); |
| 333 | } |
| 334 | } |
| 335 | |
| 336 |