PluginProbe
Members – Membership & User Role Editor Plugin / 3.2.24
Members – Membership & User Role Editor Plugin v3.2.24
3.2.25 3.2.26 3.2.24 3.2.23 3.2.22 3.2.21 trunk 0.1 0.1.1 0.2 0.2.1 0.2.2 0.2.3 0.2.4 0.2.5 1.0.0 1.0.1 1.0.2 1.1.0 1.1.1 1.1.2 1.1.3 2.0.0 2.0.1 2.0.2 All 66 releases
members / admin / class-notifications.php

class-notifications.php in Members – Membership & User Role Editor Plugin 3.2.24, at admin/class-notifications.php

752 lines 29.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace Members\Admin;
3
4 if(!defined('ABSPATH')) {die('You are not allowed to call this page directly.');}
5
6 /**
7 * PrliNotifications.
8 *
9 * Class for logging in-plugin notifications.
10 * Includes:
11 * Notifications from our remote feed
12 * Plugin-related notifications (e.g. recent sales performances)
13 */
14 class Notifications {
15
16 /**
17 * Source of notifications content.
18 *
19 * @var string
20 */
21 const SOURCE_URL = 'https://mbr.press/hWndcw';
22 const SOURCE_URL_ARGS = [];
23
24 /**
25 * Option value.
26 *
27 * @var bool|array
28 */
29 public $option = false;
30
31 public $members_settings;
32
33 /**
34 * Initialize class.
35 */
36 public function init() {
37
38 add_action( 'admin_enqueue_scripts', [ $this, 'enqueues' ] );
39 add_action( 'admin_footer', array( $this, 'admin_menu_append_count' ) );
40 add_action( 'admin_init', array( $this, 'schedule_fetch' ) );
41
42 add_action( 'admin_footer', [ $this, 'output' ] );
43
44 add_action( 'members_admin_notifications_update', [ $this, 'update' ] );
45 add_action( 'wp_ajax_members_notification_dismiss', [ $this, 'dismiss' ] );
46 }
47
48 /**
49 * Check if user has access and is enabled.
50 *
51 * @return bool
52 */
53 public static function has_access() {
54
55 $access = false;
56
57 if (
58 current_user_can( 'manage_options' )
59 && ! get_option( 'members_hide_announcements' )
60 ) {
61 $access = true;
62 }
63
64 return apply_filters( 'members_admin_notifications_has_access', $access );
65 }
66
67 /**
68 * Get option value.
69 *
70 * @param bool $cache Reference property cache if available.
71 *
72 * @return array
73 */
74 public function get_option( $cache = true ) {
75
76 if ( $this->option && $cache ) {
77 return $this->option;
78 }
79
80 $option = get_option( 'members_notifications', [] );
81
82 $this->option = [
83 'update' => ! empty( $option['update'] ) ? $option['update'] : 0,
84 'events' => ! empty( $option['events'] ) ? $option['events'] : [],
85 'feed' => ! empty( $option['feed'] ) ? $option['feed'] : [],
86 'dismissed' => ! empty( $option['dismissed'] ) ? $option['dismissed'] : [],
87 ];
88
89 return $this->option;
90 }
91
92 /**
93 * Make sure the feed is fetched when needed.
94 *
95 * @return void
96 */
97 public function schedule_fetch() {
98
99 $option = $this->get_option();
100
101 // Update notifications using async task.
102 if ( empty( $option['update'] ) || time() > $option['update'] + 3 * HOUR_IN_SECONDS ) {
103 if ( false === wp_next_scheduled( 'members_admin_notifications_update' ) ) {
104 wp_schedule_single_event( time() + 10, 'members_admin_notifications_update' );
105 }
106 }
107 }
108
109 /**
110 * Fetch notifications from remote feed.
111 *
112 * @return array
113 */
114 public function fetch_feed() {
115
116 $res = wp_remote_get( self::SOURCE_URL, self::SOURCE_URL_ARGS );
117
118 if ( is_wp_error( $res ) ) {
119 return [];
120 }
121
122 $body = wp_remote_retrieve_body( $res );
123
124 if ( empty( $body ) ) {
125 return [];
126 }
127
128 return $this->verify( json_decode( $body, true ) );
129 }
130
131 /**
132 * Verify notification data before it is saved.
133 *
134 * @param array $notifications Array of notifications items to verify.
135 *
136 * @return array
137 */
138 public function verify( $notifications ) {
139
140 $data = [];
141
142 if ( ! is_array( $notifications ) || empty( $notifications ) ) {
143 return $data;
144 }
145
146 $option = $this->get_option();
147
148 foreach ( $notifications as $id => $notification ) {
149
150 // The message should never be empty - if is, ignore.
151 if ( empty( $notification['content'] ) ) {
152 continue;
153 }
154
155 // Ignore if expired.
156 if ( ! empty( $notification['end'] ) && time() > strtotime( $notification['end'] ) ) {
157 continue;
158 }
159
160 // Ignore if notifcation has already been dismissed.
161 if ( ! empty( $option['dismissed'] ) && array_key_exists( $notification['id'], $option['dismissed'] ) ) { // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
162 continue;
163 }
164
165 // Ignore if notification existed before installing the plugin.
166 // Prevents bombarding the user with notifications after activation.
167 $activated = get_option( 'members_activated' );
168
169 if ( empty( $activated ) ) {
170 $activated = time();
171 update_option( 'members_activated', $activated );
172 }
173
174 if (
175 ! empty( $activated ) &&
176 ! empty( $notification['start'] ) &&
177 $activated > strtotime( $notification['start'] )
178 ) {
179 continue;
180 }
181
182 $data[$id] = $notification;
183
184 // Check if this notification has already been saved with a timestamp
185 if ( ! empty( $option['feed'][$id] ) ) { // Already exists in feed, so use saved time
186 $data[$id]['saved'] = $option['feed'][$id]['saved'];
187 } else if ( ! empty( $option['events'][$id] ) ) { // Already exists in events, so use saved time
188 $data[$id]['saved'] = $option['events'][$id]['saved'];
189 } else { // Doesn't exist in feed or events, so save current time
190 $data[$id]['saved'] = time();
191 }
192 }
193
194 return $data;
195 }
196
197 /**
198 * Verify saved notification data for active notifications.
199 *
200 * @param array $notifications Array of notifications items to verify.
201 *
202 * @return array
203 */
204 public function verify_active( $notifications ) {
205
206 if ( ! is_array( $notifications ) || empty( $notifications ) ) {
207 return [];
208 }
209
210 // Remove notfications that are not active.
211 foreach ( $notifications as $key => $notification ) {
212 if (
213 ( ! empty( $notification['start'] ) && strtotime( $notification['start'] . ' America/Denver' ) < strtotime( $notification['start'] . ' America/Denver' ) ) ||
214 ( ! empty( $notification['end'] ) && strtotime( $notification['end'] . ' America/Denver' ) > strtotime( $notification['end'] . ' America/Denver' ) )
215 ) {
216 unset( $notifications[ $key ] );
217 }
218 }
219
220 return $notifications;
221 }
222
223 /**
224 * Get notification data.
225 *
226 * @return array
227 */
228 public function get() {
229
230 if ( ! self::has_access() ) {
231 return [];
232 }
233
234 $option = $this->get_option();
235
236 $events = ! empty( $option['events'] ) ? $this->verify_active( $option['events'] ) : array();
237 $feed = ! empty( $option['feed'] ) ? $this->verify_active( $option['feed'] ) : array();
238
239 $notifications = array();
240 $notifications['active'] = array_merge( $events, $feed );
241 $notifications['active'] = $this->get_notifications_with_human_readeable_start_time( $notifications['active'] );
242 $notifications['active'] = $this->get_notifications_with_formatted_content( $notifications['active'] );
243 $notifications['dismissed'] = ! empty( $option['dismissed'] ) ? $option['dismissed'] : array();
244 $notifications['dismissed'] = $this->get_notifications_with_human_readeable_start_time( $notifications['dismissed'] );
245 $notifications['dismissed'] = $this->get_notifications_with_formatted_content( $notifications['dismissed'] );
246
247 return $notifications;
248 }
249
250
251 /**
252 * Improve format of the content of notifications before display. By default just runs wpautop.
253 *
254 * @param array $notifications The notifications to be parsed.
255 *
256 * @return mixed
257 */
258 public function get_notifications_with_formatted_content( $notifications ) {
259 if ( ! is_array( $notifications ) || empty( $notifications ) ) {
260 return $notifications;
261 }
262
263 foreach ( $notifications as $key => $notification ) {
264 if ( ! empty( $notification['content'] ) ) {
265 $notifications[ $key ]['content'] = wpautop( $notification['content'] );
266 $notifications[ $key ]['content'] = apply_filters( 'members_notification_content_display', $notifications[ $key ]['content'] );
267 }
268 }
269
270 return $notifications;
271 }
272
273 /**
274 * Get notifications start time with human time difference
275 *
276 * @return array $notifications
277 */
278 public function get_notifications_with_human_readeable_start_time( $notifications ) {
279
280 if ( ! is_array( $notifications ) || empty( $notifications ) ) {
281 return;
282 }
283
284 foreach ( $notifications as $key => $notification ) {
285 if ( ! isset( $notification['start'] ) || empty( $notification['start'] ) ) {
286 continue;
287 }
288
289 // Translators: Readable time to display
290 $modified_start_time = sprintf( __( '%1$s ago', 'members' ), human_time_diff( strtotime( $notification['start'] ), current_time( 'timestamp' ) ) );
291 $notifications[ $key ]['start'] = $modified_start_time;
292 }
293
294 return $notifications;
295 }
296
297 /**
298 * Get notification count.
299 *
300 * @return int
301 */
302 public function get_count() {
303 return ! empty( $this->get()['active'] ) ? count( $this->get()['active'] ) : 0;
304 }
305
306 /**
307 * Add an event notification. This is NOT for feed notifications.
308 * Event notifications are for alerting the user to something internally (e.g. recent sales performances).
309 *
310 * @param array $notification Notification data.
311 */
312 public function add( $notification ) {
313
314 if ( empty( $notification['id'] ) ) {
315 return;
316 }
317
318 $option = $this->get_option();
319
320 // Already dismissed
321 if ( array_key_exists( $notification['id'], $option['dismissed'] ) ) {
322 return;
323 }
324
325 // Already in events
326 foreach ( $option['events'] as $item ) {
327 if ( $item['id'] === $notification['id'] ) {
328 return;
329 }
330 }
331
332 // Associative key is notification id.
333 $notification = $this->verify( [ $notification['id'] => $notification ] );
334
335 // The only thing changing here is adding the notification to the events
336 update_option(
337 'members_notifications',
338 [
339 'update' => $option['update'],
340 'feed' => $option['feed'],
341 'events' => array_merge( $notification, $option['events'] ),
342 'dismissed' => $option['dismissed'],
343 ]
344 );
345 }
346
347 /**
348 * Update notification data from feed.
349 * This pulls the latest notifications from our remote feed.
350 */
351 public function update() {
352
353 $feed = $this->fetch_feed();
354 $option = $this->get_option();
355
356 update_option(
357 'members_notifications',
358 [
359 'update' => time(),
360 'feed' => $feed,
361 'events' => $option['events'],
362 'dismissed' => $option['dismissed'],
363 ]
364 );
365 }
366
367 /**
368 * Admin area enqueues.
369 */
370 public function enqueues() {
371
372 if ( ! self::has_access() || ! members_is_admin_page() ) {
373 return;
374 }
375
376 $notifications = $this->get();
377
378 if ( empty( $notifications ) ) {
379 return;
380 }
381
382 $min = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
383
384 $notifications_style_file = members_plugin()->dir . "css/admin-notifications{$min}.css";
385 $notifications_style_ver = file_exists( $notifications_style_file ) ? filemtime( $notifications_style_file ) : false;
386
387 wp_enqueue_style(
388 'members-admin-notifications',
389 members_plugin()->uri . "css/admin-notifications{$min}.css",
390 [],
391 $notifications_style_ver
392 );
393
394 $notifications_script_file = members_plugin()->dir . "js/admin-notifications{$min}.js";
395 $notifications_script_ver = file_exists( $notifications_script_file ) ? filemtime( $notifications_script_file ) : false;
396
397 wp_enqueue_script(
398 'members-admin-notifications',
399 members_plugin()->uri . "js/admin-notifications{$min}.js",
400 [ 'jquery' ],
401 $notifications_script_ver,
402 true
403 );
404
405 wp_localize_script(
406 'members-admin-notifications',
407 'MembersAdminNotifications',
408 [
409 "ajax_url" => admin_url('admin-ajax.php'),
410 "nonce" => wp_create_nonce( "members-admin-notifications" )
411 ]
412 );
413 }
414
415 /**
416 * Admin script for adding notification count to the MemberPress admin menu list item.
417 */
418 public function admin_menu_append_count() {
419
420 if ( ! self::has_access() ) {
421 return;
422 }
423
424 $option = get_option( 'members_notifications' );
425 $notifications = ! empty( $option['feed'] ) ? $option['feed'] : array();
426 $active_notifications = isset( $this->get()['active'] ) ? $this->get()['active'] : array();
427
428 if ( ! empty( $active_notifications ) && count( $active_notifications ) > 0 ) {
429 ob_start();
430
431 ?>
432
433 <span class="awaiting-mod">
434 <span class="pending-count" id="membersAdminMenuUnreadCount" aria-hidden="true"><?php echo count( $active_notifications ); ?></span>
435 <span class="comments-in-moderation-text screen-reader-text"><?php printf( _n( '%s unread message', '%s unread messages', count( $active_notifications ), 'members' ), count( $active_notifications ) ); ?></span>
436 </span>
437
438 <?php $admin_menu_output = ob_get_clean(); ?>
439
440 <script>
441 jQuery(document).ready(function($) {
442 $('li.toplevel_page_members .wp-menu-name').append(`<?php echo $admin_menu_output; ?>`);
443 });
444 </script>
445 <?php
446 }
447
448 if ( members_is_admin_page() ) {
449 ob_start();
450
451 ?>
452
453 <button id="membersAdminHeaderNotifications" class="members-notifications-button button-secondary">
454 <svg width="22" height="14" viewBox="0 0 22 14" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M21.6944 6.5625C21.8981 6.85417 22 7.18229 22 7.54687V12.25C22 12.7361 21.8218 13.1493 21.4653 13.4896C21.1088 13.8299 20.6759 14 20.1667 14H1.83333C1.32407 14 0.891204 13.8299 0.534722 13.4896C0.178241 13.1493 0 12.7361 0 12.25V7.54687C0 7.18229 0.101852 6.85417 0.305556 6.5625L4.35417 0.765625C4.45602 0.644097 4.58333 0.522569 4.73611 0.401042C4.91435 0.279514 5.10532 0.182292 5.30903 0.109375C5.51273 0.0364583 5.7037 0 5.88194 0H16.1181C16.3981 0 16.6782 0.0850694 16.9583 0.255208C17.2639 0.401042 17.4931 0.571181 17.6458 0.765625L21.6944 6.5625ZM6.1875 2.33333L2.94097 7H7.63889L8.86111 9.33333H13.1389L14.3611 7H19.059L15.8125 2.33333H6.1875Z" fill="#2679C1"></path></svg>
455 <?php if ( ! empty( $notifications ) && count( $notifications ) > 0 ) : ?>
456 <span id="membersAdminHeaderNotificationsCount" class="members-notifications-count"><?php echo count( $notifications ); ?></span>
457 <?php endif; ?>
458 </button>
459
460 <?php $messages_toggle_output = ob_get_clean(); ?>
461
462 <script>
463 jQuery(document).ready(function($) {
464 var $header = $('#members-admin-header');
465 var $button = $(`<?php echo $messages_toggle_output; ?>`);
466 if ($header.length) {
467 $header.append($button);
468 } else {
469 // Fallback: original behavior for non-branded pages.
470 $('#wpbody-content .wrap > h1').append($button);
471 }
472 });
473 </script>
474 <?php
475 }
476 }
477
478 /**
479 * Output notifications in MemberPress admin area.
480 */
481 public function output() {
482
483 // Only run on a Members page
484 if ( ! members_is_admin_page() ) {
485 return;
486 }
487
488 $notifications = $this->get();
489
490 $notifications_html = '<div class="active-messages">';
491 if ( ! empty( $notifications['active'] ) ) {
492 foreach ( $notifications['active'] as $notification ) {
493
494 // Buttons HTML.
495 $buttons_html = '';
496 if ( ! empty( $notification['buttons'] ) && is_array( $notification['buttons'] ) ) {
497 foreach ( $notification['buttons'] as $btn_type => $btn ) {
498 if ( empty( $btn['url'] ) || empty( $btn['text'] ) ) {
499 continue;
500 }
501 $buttons_html .= sprintf(
502 '<a href="%1$s" class="button button-%2$s"%3$s>%4$s</a>',
503 ! empty( $btn['url'] ) ? esc_url( $btn['url'] ) : '',
504 $btn_type === 'main' ? 'primary' : 'secondary',
505 ! empty( $btn['target'] ) && $btn['target'] === '_blank' ? ' target="_blank" rel="noopener noreferrer"' : '',
506 ! empty( $btn['text'] ) ? sanitize_text_field( $btn['text'] ) : ''
507 );
508 }
509 $buttons_html .= sprintf( '<button class="members-notice-dismiss" data-message-id="%s">%s</button>', $notification['id'], __( 'Dismiss', 'members' ) );
510 $buttons_html = ! empty( $buttons_html ) ? '<div class="members-notifications-buttons">' . $buttons_html . '</div>' : '';
511 }
512
513 $time_diff = ceil( ( time() - $notification['saved'] ) );
514 $time_diff_string = '';
515 if ( $time_diff < MINUTE_IN_SECONDS ) {
516 $time_diff_string = sprintf( _n( '%s second ago', '%s seconds ago', $time_diff, 'members' ), $time_diff );
517 } else if ( $time_diff < HOUR_IN_SECONDS ) {
518 $time_diff_string = sprintf( _n( '%s minute ago', '%s minutes ago', ceil( ( $time_diff / MINUTE_IN_SECONDS ) ), 'members' ), ceil( ( $time_diff / MINUTE_IN_SECONDS ) ) );
519 } else if ( $time_diff < DAY_IN_SECONDS ) {
520 $time_diff_string = sprintf( _n( '%s hour ago', '%s hours ago', ceil( ( $time_diff / HOUR_IN_SECONDS ) ), 'members' ), ceil( ( $time_diff / HOUR_IN_SECONDS ) ) );
521 } else if ( $time_diff < WEEK_IN_SECONDS ) {
522 $time_diff_string = sprintf( _n( '%s day ago', '%s days ago', ceil( ( $time_diff / DAY_IN_SECONDS ) ), 'members' ), ceil( ( $time_diff / DAY_IN_SECONDS ) ) );
523 } else if ( $time_diff < MONTH_IN_SECONDS ) {
524 $time_diff_string = sprintf( _n( '%s week ago', '%s weeks ago', ceil( ( $time_diff / WEEK_IN_SECONDS ) ), 'members' ), ceil( ( $time_diff / WEEK_IN_SECONDS ) ) );
525 } else if ( $time_diff < YEAR_IN_SECONDS ) {
526 $time_diff_string = sprintf( _n( '%s month ago', '%s months ago', ceil( ( $time_diff / MONTH_IN_SECONDS ) ), 'members' ), ceil( ( $time_diff / MONTH_IN_SECONDS ) ) );
527 } else {
528 $time_diff_string = sprintf( _n( '%s year ago', '%s years ago', ceil( ( $time_diff / YEAR_IN_SECONDS ) ), 'members' ), ceil( ( $time_diff / YEAR_IN_SECONDS ) ) );
529 }
530
531 // Notification HTML.
532 $notifications_html .= sprintf(
533 '<div id="members-notifications-message-%4$s" class="members-notifications-message" data-message-id="%4$s">
534 <div class="members-notification-icon-title">
535 <img src="%5$s" width="32" height="32">
536 <h3 class="members-notifications-title">%1$s</h3>
537 <time datetime="%6$s">%7$s</time>
538 </div>
539 <div class="members-notifications-content">%2$s</div>
540 %3$s
541 </div>',
542 ! empty( $notification['title'] ) ? sanitize_text_field( $notification['title'] ) : '',
543 ! empty( $notification['content'] ) ? apply_filters( 'the_content', $notification['content'] ) : '',
544 $buttons_html,
545 ! empty( $notification['id'] ) ? esc_attr( sanitize_text_field( $notification['id'] ) ) : 0,
546 ! empty( $notification['icon'] ) ? esc_url( sanitize_text_field( $notification['icon'] ) ) : '',
547 date( 'Y-m-d G:i a', $notification['saved'] ),
548 $time_diff_string
549 );
550 }
551 }
552 $notifications_html .= sprintf( '<div class="members-notifications-none" %s>%s</div>', empty( $notifications['active'] ) || count( $notifications['active'] ) < 1 ? '' : 'style="display: none;"', __( 'You\'re all caught up!', 'members' ) );
553 $notifications_html .= '</div>';
554
555 $notifications_html .= '<div class="dismissed-messages">';
556 if ( ! empty( $notifications['dismissed'] ) ) {
557 foreach ( $notifications['dismissed'] as $notification ) {
558
559 // Buttons HTML.
560 $buttons_html = '';
561 if ( ! empty( $notification['buttons'] ) && is_array( $notification['buttons'] ) ) {
562 foreach ( $notification['buttons'] as $btn_type => $btn ) {
563 if ( empty( $btn['url'] ) || empty( $btn['text'] ) ) {
564 continue;
565 }
566 $buttons_html .= sprintf(
567 '<a href="%1$s" class="button button-%2$s"%3$s>%4$s</a>',
568 ! empty( $btn['url'] ) ? esc_url( $btn['url'] ) : '',
569 $btn_type === 'main' ? 'primary' : 'secondary',
570 ! empty( $btn['target'] ) && $btn['target'] === '_blank' ? ' target="_blank" rel="noopener noreferrer"' : '',
571 ! empty( $btn['text'] ) ? sanitize_text_field( $btn['text'] ) : ''
572 );
573 }
574 $buttons_html .= sprintf( '<button class="members-notice-dismiss" data-message-id="%s">%s</button>', $notification['id'], __( 'Dismiss', 'members' ) );
575 $buttons_html = ! empty( $buttons_html ) ? '<div class="members-notifications-buttons">' . $buttons_html . '</div>' : '';
576 }
577
578 $time_diff = ceil( ( time() - $notification['saved'] ) );
579 $time_diff_string = '';
580 if ( $time_diff < MINUTE_IN_SECONDS ) {
581 $time_diff_string = sprintf( _n( '%s second ago', '%s seconds ago', $time_diff, 'members' ), $time_diff );
582 } else if ( $time_diff < HOUR_IN_SECONDS ) {
583 $time_diff_string = sprintf( _n( '%s minute ago', '%s minutes ago', ceil( ( $time_diff / MINUTE_IN_SECONDS ) ), 'members' ), ceil( ( $time_diff / MINUTE_IN_SECONDS ) ) );
584 } else if ( $time_diff < DAY_IN_SECONDS ) {
585 $time_diff_string = sprintf( _n( '%s hour ago', '%s hours ago', ceil( ( $time_diff / HOUR_IN_SECONDS ) ), 'members' ), ceil( ( $time_diff / HOUR_IN_SECONDS ) ) );
586 } else if ( $time_diff < WEEK_IN_SECONDS ) {
587 $time_diff_string = sprintf( _n( '%s day ago', '%s days ago', ceil( ( $time_diff / DAY_IN_SECONDS ) ), 'members' ), ceil( ( $time_diff / DAY_IN_SECONDS ) ) );
588 } else if ( $time_diff < MONTH_IN_SECONDS ) {
589 $time_diff_string = sprintf( _n( '%s week ago', '%s weeks ago', ceil( ( $time_diff / WEEK_IN_SECONDS ) ), 'members' ), ceil( ( $time_diff / WEEK_IN_SECONDS ) ) );
590 } else if ( $time_diff < YEAR_IN_SECONDS ) {
591 $time_diff_string = sprintf( _n( '%s month ago', '%s months ago', ceil( ( $time_diff / MONTH_IN_SECONDS ) ), 'members' ), ceil( ( $time_diff / MONTH_IN_SECONDS ) ) );
592 } else {
593 $time_diff_string = sprintf( _n( '%s year ago', '%s years ago', ceil( ( $time_diff / YEAR_IN_SECONDS ) ), 'members' ), ceil( ( $time_diff / YEAR_IN_SECONDS ) ) );
594 }
595
596 // Notification HTML.
597 $notifications_html .= sprintf(
598 '<div id="members-notifications-message-%4$s" class="members-notifications-message" data-message-id="%4$s">
599 <div class="members-notification-icon-title">
600 <img src="%5$s" width="32" height="32">
601 <h3 class="members-notifications-title">%1$s</h3>
602 <time datetime="%6$s">%7$s</time>
603 </div>
604 <div class="members-notifications-content">%2$s</div>
605 %3$s
606 </div>',
607 ! empty( $notification['title'] ) ? sanitize_text_field( $notification['title'] ) : '',
608 ! empty( $notification['content'] ) ? apply_filters( 'the_content', $notification['content'] ) : '',
609 $buttons_html,
610 ! empty( $notification['id'] ) ? esc_attr( sanitize_text_field( $notification['id'] ) ) : 0,
611 ! empty( $notification['icon'] ) ? esc_url( sanitize_text_field( $notification['icon'] ) ) : '',
612 date( 'Y-m-d G:i a', $notification['saved'] ),
613 $time_diff_string
614 );
615 }
616 }
617 $notifications_html .= '</div>';
618 ?>
619
620 <div id="members-notifications">
621
622 <div class="members-notifications-container">
623
624 <div class="members-notifications-top-title">
625 <div class="members-notifications-top-title__left">
626 <svg width="24" height="15" viewBox="0 0 24 15" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M23.6667 7.03125C23.8889 7.34375 24 7.69531 24 8.08594V13.125C24 13.6458 23.8056 14.0885 23.4167 14.4531C23.0278 14.8177 22.5556 15 22 15H2C1.44444 15 0.972222 14.8177 0.583333 14.4531C0.194444 14.0885 0 13.6458 0 13.125V8.08594C0 7.69531 0.111111 7.34375 0.333333 7.03125L4.75 0.820312C4.86111 0.690104 5 0.559896 5.16667 0.429688C5.36111 0.299479 5.56944 0.195312 5.79167 0.117188C6.01389 0.0390625 6.22222 0 6.41667 0H17.5833C17.8889 0 18.1944 0.0911458 18.5 0.273438C18.8333 0.429688 19.0833 0.611979 19.25 0.820312L23.6667 7.03125ZM6.75 2.5L3.20833 7.5H8.33333L9.66667 10H14.3333L15.6667 7.5H20.7917L17.25 2.5H6.75Z" fill="white"></path></svg>
627 <h3><?php _e( 'Inbox', 'members' ); ?></h3>
628 </div>
629 <div class="members-notifications-top-title__right actions">
630 <a href="#" id="viewDismissed"><?php _e( 'View Dismissed', 'members' ); ?></a>
631 <a href="#" id="viewActive"><?php _e( 'View Active', 'members' ); ?></a>
632 <a href="#" id="membersNotificationsClose" class="close" title="<?php _e( 'Close', 'members' ); ?>">
633 <svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M8.28409 6L11.6932 9.40909C11.8977 9.61364 12 9.86364 12 10.1591C12 10.4545 11.8977 10.7159 11.6932 10.9432L10.9432 11.6932C10.7159 11.8977 10.4545 12 10.1591 12C9.86364 12 9.61364 11.8977 9.40909 11.6932L6 8.28409L2.59091 11.6932C2.38636 11.8977 2.13636 12 1.84091 12C1.54545 12 1.28409 11.8977 1.05682 11.6932L0.306818 10.9432C0.102273 10.7159 0 10.4545 0 10.1591C0 9.86364 0.102273 9.61364 0.306818 9.40909L3.71591 6L0.306818 2.59091C0.102273 2.38636 0 2.13636 0 1.84091C0 1.54545 0.102273 1.28409 0.306818 1.05682L1.05682 0.306818C1.28409 0.102273 1.54545 0 1.84091 0C2.13636 0 2.38636 0.102273 2.59091 0.306818L6 3.71591L9.40909 0.306818C9.61364 0.102273 9.86364 0 10.1591 0C10.4545 0 10.7159 0.102273 10.9432 0.306818L11.6932 1.05682C11.8977 1.28409 12 1.54545 12 1.84091C12 2.13636 11.8977 2.38636 11.6932 2.59091L8.28409 6Z" fill="white"></path></svg>
634 </a>
635 </div>
636 </div>
637 <div class="members-notifications-header <?php echo ! empty( $notifications['active'] ) && count( $notifications['active'] ) < 10 ? 'single-digit' : ''; ?>">
638 <div class="members-notifications-header-bell">
639 <div class="members-notifications-bell">
640 <svg viewBox="0 0 512 512" width="30" xmlns="http://www.w3.org/2000/svg"><path fill="#777777" d="m381.7 225.9c0-97.6-52.5-130.8-101.6-138.2 0-.5.1-1 .1-1.6 0-12.3-10.9-22.1-24.2-22.1s-23.8 9.8-23.8 22.1c0 .6 0 1.1.1 1.6-49.2 7.5-102 40.8-102 138.4 0 113.8-28.3 126-66.3 158h384c-37.8-32.1-66.3-44.4-66.3-158.2z"/><path fill="#777777" d="m256.2 448c26.8 0 48.8-19.9 51.7-43h-103.4c2.8 23.1 24.9 43 51.7 43z"/></svg>
641 <?php if ( ! empty( $notifications['active'] ) ) : ?>
642 <span id="membersNotificationsCountTray" class="members-notifications-count"><?php echo count( $notifications['active'] ); ?></span>
643 <?php endif; ?>
644 </div>
645 <div class="members-notifications-title"><?php esc_html_e( 'Notifications', 'members' ); ?></div>
646 </div>
647 <?php if ( ! empty( $notifications['active'] ) ) : ?>
648 <button id="dismissAll" class="dismiss-all"><?php _e( 'Dismiss All', 'members' ); ?></button>
649 <?php endif; ?>
650 </div>
651
652 <div class="members-notifications-body">
653 <div class="members-notifications-messages">
654 <?php echo $notifications_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
655 </div>
656 </div>
657
658 </div>
659
660 </div>
661 <?php
662 }
663
664 /**
665 * Dismiss notification(s) via AJAX.
666 */
667 public function dismiss() {
668
669 // Run a security check.
670 check_ajax_referer( 'members-admin-notifications', 'nonce' );
671
672 // Check for access and required param.
673 if ( ! self::has_access() || empty( $_POST['id'] ) ) {
674 wp_send_json_error();
675 }
676
677 $id = sanitize_text_field( wp_unslash( $_POST['id'] ) );
678 $option = $this->get_option();
679
680 if ( 'all' === $id ) { // Dismiss all notifications
681
682 // Feed notifications
683 if ( ! empty( $option['feed'] ) ) {
684 foreach ( $option['feed'] as $key => $notification ) {
685 $option['dismissed'][$key] = $option['feed'][$key];
686 unset( $option['feed'][$key] );
687 }
688 }
689
690 // Event notifications
691 if ( ! empty( $option['events'] ) ) {
692 foreach ( $option['events'] as $key => $notification ) {
693 $option['dismissed'][$key] = $option['events'][$key];
694 unset( $option['events'][$key] );
695 }
696 }
697
698 } else { // Dismiss one notification
699
700 // Event notifications need a prefix to distinguish them from feed notifications
701 // For a naming convention, we'll use "event_{timestamp}"
702 // If the notification ID includes "event_", we know it's an even notification
703 $type = false !== strpos( $id, 'event_' ) ? 'events' : 'feed';
704
705 if( $type == 'events' ){
706 if( !empty($option[$type]) ){
707 foreach( $option[$type] as $index => $event_notification ){
708 if( $event_notification['id'] == $id ){
709 unset( $option[$type][$index] );
710 break;
711 }
712 }
713 }
714 }else{
715 if ( ! empty( $option[$type][$id] ) ) {
716 $option['dismissed'][$id] = $option[$type][$id];
717 unset( $option[$type][$id] );
718 }
719 }
720 }
721
722
723 update_option( 'members_notifications', $option );
724
725 wp_send_json_success();
726 }
727
728 public function dismiss_events( $type ) {
729
730 $option = $this->get_option();
731
732 // Event notifications.
733 if ( ! empty( $option['events'] ) ) {
734 $found = 0;
735 foreach ( $option['events'] as $key => $notification ) {
736 // We found event.
737 if( $type === $notification['type'] ){
738 unset($option['events'][$key]);
739 $found = 1;
740 }
741 }
742
743 if( $found ){
744 update_option( 'members_notifications', $option );
745 }
746 }
747 }
748 }
749
750 $members_notifications = new Notifications;
751 $members_notifications->init();
752