PluginProbe
wpForo Forum / 3.1.6
wpForo Forum v3.1.6
3.1.6 3.1.5 3.1.4 3.1.2 3.1.1 3.1.0 3.0.9 3.0.8 3.0.7 trunk 1.0.0 1.0.1 1.0.2 1.1.0 1.1.1 1.1.2 1.2.0 1.3.0 1.3.1 1.4.0 1.4.1 1.4.10 1.4.11 1.4.12 1.4.13 All 138 releases
wpforo / admin / pages / news / src / Services / NoticesService.php

NoticesService.php in wpForo Forum 3.1.6, at admin/pages/news/src/Services/NoticesService.php

161 lines 6.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace gVectors\News\Services;
4
5 use gVectors\News\Config;
6 use gVectors\News\NewsModule;
7
8 // Exit if accessed directly
9 if( ! defined( 'ABSPATH' ) ) exit;
10
11 /**
12 * Dismissible admin notices for news items (WordPress.org Guideline 11):
13 * non-intrusive, max 3 at once, per-user dismissal stored in user meta.
14 */
15 class NoticesService {
16 /** news type → WP notice class */
17 private const TYPE_CLASSES = [
18 'new_addon' => 'notice-success',
19 'new_feature' => 'notice-warning',
20 'new_version' => 'notice-warning', // releases may carry required actions — needs attention
21 'discount' => 'notice-success',
22 'announcement' => 'notice-info',
23 ];
24
25 private $config;
26 private $consent;
27 private $cron;
28 private $prefs;
29
30 public function __construct( Config $config, ConsentService $consent, CronService $cron, PrefsService $prefs ) {
31 $this->config = $config;
32 $this->consent = $consent;
33 $this->cron = $cron;
34 $this->prefs = $prefs;
35
36 add_action( 'admin_notices', [ $this, 'render_notices' ] );
37 add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
38 add_action( 'wp_ajax_' . $this->config->get_prefix() . 'dismiss_news', [ $this, 'ajax_dismiss' ] );
39 }
40
41 /**
42 * Non-dismissed, non-expired news items for the current admin, oldest first —
43 * filtered through the site + per-admin notices channel matrix
44 * (category and installed/not-installed addon relevance).
45 */
46 public function get_visible_items(): array {
47 if( ! $this->consent->is_enabled() || ! current_user_can( 'activate_plugins' ) ) return [];
48
49 $items = $this->cron->get_cached_news();
50 if( empty( $items ) ) return [];
51
52 $user_id = get_current_user_id();
53 $dismissed = get_user_meta( $user_id, $this->config->get_dismissed_news_meta(), true );
54 $dismissed = is_array( $dismissed ) ? $dismissed : [];
55 $now = time();
56
57 $visible = [];
58 foreach( $items as $item ) {
59 if( in_array( $item['id'], $dismissed, true ) ) continue;
60 if( ! empty( $item['expires_at'] ) && strtotime( $item['expires_at'] ) <= $now ) continue;
61 if( ! $this->prefs->news_item_allowed( $item, PrefsService::CHANNEL_NOTICES, $user_id ) ) continue;
62 $visible[] = $item;
63 }
64
65 // Oldest first, capped (Guideline 11: never flood the admin)
66 usort( $visible, function( $a, $b ) {
67 return strtotime( $a['published_at'] ?? 'now' ) <=> strtotime( $b['published_at'] ?? 'now' );
68 } );
69
70 return array_slice( $visible, 0, $this->config->get_max_notices() );
71 }
72
73 public function render_notices(): void {
74 // No slug argument: notices show on the admin pages of ANY registered
75 // gVectors plugin. This service is shared, so each news item prints
76 // exactly once even when several gVectors plugins are installed.
77 if( ! NewsModule::is_notice_page() ) return;
78 $items = $this->get_visible_items();
79 if( empty( $items ) ) return;
80
81 foreach( $items as $item ) {
82 $type_class = self::TYPE_CLASSES[ $item['type'] ] ?? 'notice-info';
83 ?>
84 <div class="notice <?php echo esc_attr( $type_class ); ?> gvectors-news-notice" data-news-id="<?php echo esc_attr( $item['id'] ); ?>">
85 <p>
86 <strong><?php echo esc_html( $item['title'] ); ?></strong>
87 <?php if( ! empty( $item['body'] ) ) : ?>
88 &mdash; <?php echo esc_html( wp_trim_words( $item['body'], 40 ) ); ?>
89 <?php endif; ?>
90 <?php if( ! empty( $item['link_url'] ) ) : ?>
91 <a href="<?php echo esc_url( $item['link_url'] ); ?>" target="_blank" rel="noopener">
92 <?php echo esc_html( ! empty( $item['link_label'] ) ? $item['link_label'] : __( 'Learn more', 'gvectors' ) ); ?>
93 </a>
94 <?php endif; ?>
95 </p>
96 <button type="button" class="notice-dismiss gvectors-news-dismiss">
97 <span class="screen-reader-text"><?php esc_html_e( 'Dismiss this notice.', 'gvectors' ); ?></span>
98 </button>
99 </div>
100 <?php
101 }
102 }
103
104 /**
105 * Enqueue the tiny dismiss/opt-in script only when this module has something on screen.
106 */
107 public function enqueue_assets(): void {
108 if( ! NewsModule::is_notice_page() ) return;
109 if( empty( $this->get_visible_items() ) && ! $this->consent->should_show_optin_notice() ) return;
110
111 // Shared handle/config object — this service exists once no matter how
112 // many gVectors plugins are installed, so assets load exactly once.
113 $module_url = $this->config->get_news_module_url() . '/admin/assets';
114 $module_dir = dirname( __DIR__, 2 ) . '/admin/assets';
115
116 wp_enqueue_style(
117 'gvectors-news-admin',
118 $module_url . '/css/notices.css',
119 [],
120 filemtime( $module_dir . '/css/notices.css' )
121 );
122 wp_enqueue_script(
123 'gvectors-news-admin',
124 $module_url . '/js/notices.js',
125 [ 'jquery' ],
126 filemtime( $module_dir . '/js/notices.js' ),
127 true
128 );
129 wp_localize_script( 'gvectors-news-admin', 'gvectorsNews', [
130 'ajaxUrl' => admin_url( 'admin-ajax.php' ),
131 'nonce' => wp_create_nonce( $this->config->get_nonce_action() ),
132 'ajaxPrefix' => $this->config->get_prefix(),
133 ] );
134 }
135
136 /**
137 * AJAX: dismiss one news notice for the current admin.
138 */
139 public function ajax_dismiss(): void {
140 check_ajax_referer( $this->config->get_nonce_action(), 'nonce' );
141 if( ! current_user_can( 'activate_plugins' ) ) {
142 wp_send_json_error( [ 'message' => 'Permission denied' ], 403 );
143 }
144
145 $id = isset( $_POST['news_id'] ) ? sanitize_text_field( wp_unslash( $_POST['news_id'] ) ) : '';
146 if( ! preg_match( '/^[a-f0-9\-]{36}$/', $id ) ) {
147 wp_send_json_error( [ 'message' => 'Invalid news id' ], 400 );
148 }
149
150 $user_id = get_current_user_id();
151 $meta_key = $this->config->get_dismissed_news_meta();
152 $dismissed = get_user_meta( $user_id, $meta_key, true );
153 $dismissed = is_array( $dismissed ) ? $dismissed : [];
154
155 $dismissed[] = $id;
156 update_user_meta( $user_id, $meta_key, array_values( array_unique( $dismissed ) ) );
157
158 wp_send_json_success();
159 }
160 }
161