PluginProbe
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / 28.5
Yoast SEO – Advanced SEO with real-time guidance and built-in AI v28.5
28.5 28.4 28.3 28.2 28.1 28.0 27.9 27.8 27.7 27.6 27.5 trunk 18.0 18.1 18.2 18.3 18.4 18.4.1 18.5 18.5.1 18.6 18.7 18.8 18.9 19.0 All 129 releases
wordpress-seo / admin / class-yoast-notification-center.php

class-yoast-notification-center.php in Yoast SEO – Advanced SEO with real-time guidance and built-in AI 28.5, at admin/class-yoast-notification-center.php

959 lines 26.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * WPSEO plugin file.
4 *
5 * @package WPSEO\Admin\Notifications
6 */
7
8 use Yoast\WP\SEO\Presenters\Abstract_Presenter;
9
10 /**
11 * Handles notifications storage and display.
12 */
13 class Yoast_Notification_Center {
14
15 /**
16 * Option name to store notifications on.
17 *
18 * @var string
19 */
20 public const STORAGE_KEY = 'yoast_notifications';
21
22 /**
23 * The singleton instance of this object.
24 *
25 * @var Yoast_Notification_Center|null
26 */
27 private static $instance = null;
28
29 /**
30 * Holds the notifications.
31 *
32 * @var Yoast_Notification[][]
33 */
34 private $notifications = [];
35
36 /**
37 * Notifications there are newly added.
38 *
39 * @var array
40 */
41 private $new = [];
42
43 /**
44 * Notifications that were resolved this execution.
45 *
46 * @var int
47 */
48 private $resolved = 0;
49
50 /**
51 * Internal storage for transaction before notifications have been retrieved from storage.
52 *
53 * @var array
54 */
55 private $queued_transactions = [];
56
57 /**
58 * Internal flag for whether notifications have been retrieved from storage.
59 *
60 * @var bool
61 */
62 private $notifications_retrieved = false;
63
64 /**
65 * Internal flag for whether notifications need to be updated in storage.
66 *
67 * @var bool
68 */
69 private $notifications_need_storage = false;
70
71 /**
72 * Construct.
73 */
74 private function __construct() {
75
76 add_action( 'init', [ $this, 'setup_current_notifications' ], 1 );
77
78 add_action( 'all_admin_notices', [ $this, 'display_notifications' ] );
79
80 add_action( 'wp_ajax_yoast_get_notifications', [ $this, 'ajax_get_notifications' ] );
81
82 add_action( 'wpseo_deactivate', [ $this, 'deactivate_hook' ] );
83 add_action( 'shutdown', [ $this, 'update_storage' ] );
84 }
85
86 /**
87 * Singleton getter.
88 *
89 * @return Yoast_Notification_Center
90 */
91 public static function get() {
92
93 self::$instance ??= new self();
94
95 return self::$instance;
96 }
97
98 /**
99 * Dismiss a notification.
100 *
101 * @return void
102 */
103 public static function ajax_dismiss_notification() {
104 $notification_center = self::get();
105
106 if ( ! isset( $_POST['notification'] ) || ! is_string( $_POST['notification'] ) ) {
107 exit( '-1' );
108 }
109
110 $notification_id = sanitize_text_field( wp_unslash( $_POST['notification'] ) );
111
112 if ( empty( $notification_id ) ) {
113 exit( '-1' );
114 }
115
116 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are using the variable as a nonce.
117 if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( wp_unslash( $_POST['nonce'] ), $notification_id ) ) {
118 exit( '-1' );
119 }
120
121 $notification = $notification_center->get_notification_by_id( $notification_id );
122 if ( ( $notification instanceof Yoast_Notification ) === false ) {
123
124 // Permit legacy.
125 $options = [
126 'id' => $notification_id,
127 'dismissal_key' => $notification_id,
128 ];
129 $notification = new Yoast_Notification( '', $options );
130 }
131
132 if ( self::maybe_dismiss_notification( $notification ) ) {
133 exit( '1' );
134 }
135
136 exit( '-1' );
137 }
138
139 /**
140 * Check if the user has dismissed a notification.
141 *
142 * @param Yoast_Notification $notification The notification to check for dismissal.
143 * @param int|null $user_id User ID to check on.
144 *
145 * @return bool
146 */
147 public static function is_notification_dismissed( Yoast_Notification $notification, $user_id = null ) {
148
149 $user_id = self::get_user_id( $user_id );
150 $dismissal_key = $notification->get_dismissal_key();
151
152 // This checks both the site-specific user option and the meta value.
153 $current_value = get_user_option( $dismissal_key, $user_id );
154
155 // Migrate old user meta to user option on-the-fly.
156 if ( ! empty( $current_value )
157 && metadata_exists( 'user', $user_id, $dismissal_key )
158 && update_user_option( $user_id, $dismissal_key, $current_value ) ) {
159 delete_user_meta( $user_id, $dismissal_key );
160 }
161
162 return ! empty( $current_value );
163 }
164
165 /**
166 * Checks if the notification is being dismissed.
167 *
168 * @param Yoast_Notification $notification Notification to check dismissal of.
169 * @param string $meta_value Value to set the meta value to if dismissed.
170 *
171 * @return bool True if dismissed.
172 */
173 public static function maybe_dismiss_notification( Yoast_Notification $notification, $meta_value = 'seen' ) {
174
175 // Only persistent notifications are dismissible.
176 if ( ! $notification->is_persistent() ) {
177 return false;
178 }
179
180 // If notification is already dismissed, we're done.
181 if ( self::is_notification_dismissed( $notification ) ) {
182 return true;
183 }
184
185 $dismissal_key = $notification->get_dismissal_key();
186 $notification_id = $notification->get_id();
187
188 $is_dismissing = ( $dismissal_key === self::get_user_input( 'notification' ) );
189 if ( ! $is_dismissing ) {
190 $is_dismissing = ( $notification_id === self::get_user_input( 'notification' ) );
191 }
192
193 // Fallback to ?dismissal_key=1&nonce=bla when JavaScript fails.
194 if ( ! $is_dismissing ) {
195 $is_dismissing = ( self::get_user_input( $dismissal_key ) === '1' );
196 }
197
198 if ( ! $is_dismissing ) {
199 return false;
200 }
201
202 $user_nonce = self::get_user_input( 'nonce' );
203 if ( wp_verify_nonce( $user_nonce, $notification_id ) === false ) {
204 return false;
205 }
206
207 return self::dismiss_notification( $notification, $meta_value );
208 }
209
210 /**
211 * Dismisses a notification.
212 *
213 * @param Yoast_Notification $notification Notification to dismiss.
214 * @param string $meta_value Value to save in the dismissal.
215 *
216 * @return bool True if dismissed, false otherwise.
217 */
218 public static function dismiss_notification( Yoast_Notification $notification, $meta_value = 'seen' ) {
219 // Dismiss notification.
220 return update_user_option( get_current_user_id(), $notification->get_dismissal_key(), $meta_value ) !== false;
221 }
222
223 /**
224 * Restores a notification.
225 *
226 * @param Yoast_Notification $notification Notification to restore.
227 *
228 * @return bool True if restored, false otherwise.
229 */
230 public static function restore_notification( Yoast_Notification $notification ) {
231
232 $user_id = get_current_user_id();
233 $dismissal_key = $notification->get_dismissal_key();
234
235 // Restore notification.
236 $restored = delete_user_option( $user_id, $dismissal_key );
237
238 // Delete unprefixed user meta too for backward-compatibility.
239 if ( metadata_exists( 'user', $user_id, $dismissal_key ) ) {
240 $restored = delete_user_meta( $user_id, $dismissal_key ) && $restored;
241 }
242
243 return $restored;
244 }
245
246 /**
247 * Clear dismissal information for the specified Notification.
248 *
249 * When a cause is resolved, the next time it is present we want to show
250 * the message again.
251 *
252 * @param string|Yoast_Notification $notification Notification to clear the dismissal of.
253 *
254 * @return bool
255 */
256 public function clear_dismissal( $notification ) {
257
258 global $wpdb;
259
260 if ( $notification instanceof Yoast_Notification ) {
261 $dismissal_key = $notification->get_dismissal_key();
262 }
263
264 if ( is_string( $notification ) ) {
265 $dismissal_key = $notification;
266 }
267
268 if ( empty( $dismissal_key ) ) {
269 return false;
270 }
271
272 // Remove notification dismissal for all users.
273 $deleted = delete_metadata( 'user', 0, $wpdb->get_blog_prefix() . $dismissal_key, '', true );
274
275 // Delete unprefixed user meta too for backward-compatibility.
276 $deleted = delete_metadata( 'user', 0, $dismissal_key, '', true ) || $deleted;
277
278 return $deleted;
279 }
280
281 /**
282 * Retrieves notifications from the storage and merges in previous notification changes.
283 *
284 * The current user in WordPress is not loaded shortly before the 'init' hook, but the plugin
285 * sometimes needs to add or remove notifications before that. In such cases, the transactions
286 * are not actually executed, but added to a queue. That queue is then handled in this method,
287 * after notifications for the current user have been set up.
288 *
289 * @return void
290 */
291 public function setup_current_notifications() {
292 $this->retrieve_notifications_from_storage( get_current_user_id() );
293
294 foreach ( $this->queued_transactions as $transaction ) {
295 list( $callback, $args ) = $transaction;
296
297 call_user_func_array( $callback, $args );
298 }
299
300 $this->queued_transactions = [];
301 }
302
303 /**
304 * Add notification to the cookie.
305 *
306 * @param Yoast_Notification $notification Notification object instance.
307 *
308 * @return void
309 */
310 public function add_notification( Yoast_Notification $notification ) {
311
312 $callback = [ $this, __FUNCTION__ ];
313 $args = func_get_args();
314 if ( $this->queue_transaction( $callback, $args ) ) {
315 return;
316 }
317
318 // Don't add if the user can't see it.
319 if ( ! $notification->display_for_current_user() ) {
320 return;
321 }
322
323 $notification_id = $notification->get_id();
324 $user_id = $notification->get_user_id();
325
326 // Empty notifications are always added.
327 if ( $notification_id !== '' ) {
328
329 // If notification ID exists in notifications, don't add again.
330 $present_notification = $this->get_notification_by_id( $notification_id, $user_id );
331 if ( $present_notification !== null ) {
332 $this->remove_notification( $present_notification, false );
333 }
334
335 if ( $present_notification === null ) {
336 $this->new[] = $notification_id;
337 }
338 }
339
340 // Add to list.
341 $this->notifications[ $user_id ][] = $notification;
342
343 $this->notifications_need_storage = true;
344 }
345
346 /**
347 * Get the notification by ID and user ID.
348 *
349 * @param string $notification_id The ID of the notification to search for.
350 * @param int|null $user_id The ID of the user.
351 *
352 * @return Yoast_Notification|null
353 */
354 public function get_notification_by_id( $notification_id, $user_id = null ) {
355 $user_id = self::get_user_id( $user_id );
356
357 $notifications = $this->get_notifications_for_user( $user_id );
358
359 foreach ( $notifications as $notification ) {
360 if ( $notification_id === $notification->get_id() ) {
361 return $notification;
362 }
363 }
364
365 return null;
366 }
367
368 /**
369 * Display the notifications.
370 *
371 * @param bool $echo_as_json True when notifications should be printed directly.
372 *
373 * @return void
374 */
375 public function display_notifications( $echo_as_json = false ) {
376
377 // Never display notifications for network admin.
378 if ( is_network_admin() ) {
379 return;
380 }
381
382 $sorted_notifications = $this->get_sorted_notifications();
383 $notifications = array_filter( $sorted_notifications, [ $this, 'is_notification_persistent' ] );
384
385 if ( empty( $notifications ) ) {
386 return;
387 }
388
389 array_walk( $notifications, [ $this, 'remove_notification' ] );
390
391 $notifications = array_unique( $notifications );
392 if ( $echo_as_json ) {
393 $notification_json = [];
394
395 foreach ( $notifications as $notification ) {
396 $notification_json[] = $notification->render();
397 }
398
399 // phpcs:ignore WordPress.Security.EscapeOutput -- Reason: WPSEO_Utils::format_json_encode is safe.
400 echo WPSEO_Utils::format_json_encode( $notification_json );
401
402 return;
403 }
404
405 foreach ( $notifications as $notification ) {
406 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Reason: Temporarily disabled, see: https://github.com/Yoast/wordpress-seo-premium/issues/2510 and https://github.com/Yoast/wordpress-seo-premium/issues/2511.
407 echo $notification;
408 }
409 }
410
411 /**
412 * Remove notification after it has been displayed.
413 *
414 * @param Yoast_Notification $notification Notification to remove.
415 * @param bool $resolve Resolve as fixed.
416 *
417 * @return void
418 */
419 public function remove_notification( Yoast_Notification $notification, $resolve = true ) {
420
421 $callback = [ $this, __FUNCTION__ ];
422 $args = func_get_args();
423 if ( $this->queue_transaction( $callback, $args ) ) {
424 return;
425 }
426
427 $index = false;
428
429 // ID of the user to show the notification for, defaults to current user id.
430 $user_id = $notification->get_user_id();
431 $notifications = $this->get_notifications_for_user( $user_id );
432
433 // Match persistent Notifications by ID, non persistent by item in the array.
434 if ( $notification->is_persistent() ) {
435 foreach ( $notifications as $current_index => $present_notification ) {
436 if ( $present_notification->get_id() === $notification->get_id() ) {
437 $index = $current_index;
438 break;
439 }
440 }
441 }
442 else {
443 $index = array_search( $notification, $notifications, true );
444 }
445
446 if ( $index === false ) {
447 return;
448 }
449
450 if ( $notification->is_persistent() && $resolve ) {
451 ++$this->resolved;
452 $this->clear_dismissal( $notification );
453 }
454
455 unset( $notifications[ $index ] );
456 $this->notifications[ $user_id ] = array_values( $notifications );
457
458 $this->notifications_need_storage = true;
459 }
460
461 /**
462 * Removes a notification by its ID.
463 *
464 * @param string $notification_id The notification id.
465 * @param bool $resolve Resolve as fixed.
466 *
467 * @return void
468 */
469 public function remove_notification_by_id( $notification_id, $resolve = true ) {
470 $notification = $this->get_notification_by_id( $notification_id );
471
472 if ( $notification === null ) {
473 return;
474 }
475
476 $this->remove_notification( $notification, $resolve );
477 $this->notifications_need_storage = true;
478 }
479
480 /**
481 * Get the notification count.
482 *
483 * @param bool $dismissed Count dismissed notifications.
484 *
485 * @return int Number of notifications
486 */
487 public function get_notification_count( $dismissed = false ) {
488
489 $notifications = $this->get_notifications_for_user( get_current_user_id() );
490 $notifications = array_filter( $notifications, [ $this, 'filter_persistent_notifications' ] );
491
492 if ( ! $dismissed ) {
493 $notifications = array_filter( $notifications, [ $this, 'filter_dismissed_notifications' ] );
494 }
495
496 return count( $notifications );
497 }
498
499 /**
500 * Get the number of notifications resolved this execution.
501 *
502 * These notifications have been resolved and should be counted when active again.
503 *
504 * @return int
505 */
506 public function get_resolved_notification_count() {
507
508 return $this->resolved;
509 }
510
511 /**
512 * Return the notifications sorted on type and priority.
513 *
514 * @return Yoast_Notification[] Sorted Notifications
515 */
516 public function get_sorted_notifications() {
517 $notifications = $this->get_notifications_for_user( get_current_user_id() );
518 if ( empty( $notifications ) ) {
519 return [];
520 }
521
522 // Sort by severity, error first.
523 usort( $notifications, [ $this, 'sort_notifications' ] );
524
525 return $notifications;
526 }
527
528 /**
529 * AJAX display notifications.
530 *
531 * @return void
532 */
533 public function ajax_get_notifications() {
534 $echo = false;
535 // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are not processing form data.
536 if ( isset( $_POST['version'] ) && is_string( $_POST['version'] ) ) {
537 // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are only comparing the variable in a condition.
538 $echo = wp_unslash( $_POST['version'] ) === '2';
539 }
540
541 // Display the notices.
542 $this->display_notifications( $echo );
543
544 // AJAX die.
545 exit();
546 }
547
548 /**
549 * Remove storage when the plugin is deactivated.
550 *
551 * @return void
552 */
553 public function deactivate_hook() {
554
555 $this->clear_notifications();
556 }
557
558 /**
559 * Returns the given user ID if it exists.
560 * Otherwise, this function returns the ID of the current user.
561 *
562 * @param int $user_id The user ID to check.
563 *
564 * @return int The user ID to use.
565 */
566 private static function get_user_id( $user_id ) {
567 if ( $user_id ) {
568 return $user_id;
569 }
570 return get_current_user_id();
571 }
572
573 /**
574 * Splits the notifications on user ID.
575 *
576 * In other terms, it returns an associative array,
577 * mapping user ID to a list of notifications for this user.
578 *
579 * @param Yoast_Notification[] $notifications The notifications to split.
580 *
581 * @return array The notifications, split on user ID.
582 */
583 private function split_on_user_id( $notifications ) {
584 $split_notifications = [];
585 foreach ( $notifications as $notification ) {
586 $split_notifications[ $notification->get_user_id() ][] = $notification;
587 }
588 return $split_notifications;
589 }
590
591 /**
592 * Save persistent notifications to storage.
593 *
594 * We need to be able to retrieve these so they can be dismissed at any time during the execution.
595 *
596 * @since 3.2
597 *
598 * @return void
599 */
600 public function update_storage() {
601 /**
602 * Plugins might exit on the plugins_loaded hook.
603 * This prevents the pluggable.php file from loading, as it's loaded after the plugins_loaded hook.
604 * As we need functions defined in pluggable.php, make sure it's loaded.
605 */
606 require_once ABSPATH . WPINC . '/pluggable.php';
607
608 $notifications = $this->notifications;
609
610 /**
611 * One array of Yoast_Notifications, merged from multiple arrays.
612 *
613 * @var Yoast_Notification[] $merged_notifications
614 */
615 $merged_notifications = [];
616 if ( ! empty( $notifications ) ) {
617 $merged_notifications = array_merge( ...$notifications );
618 }
619
620 /**
621 * Filter: 'yoast_notifications_before_storage' - Allows developer to filter notifications before saving them.
622 *
623 * @param Yoast_Notification[] $notifications
624 */
625 $filtered_merged_notifications = apply_filters( 'yoast_notifications_before_storage', $merged_notifications );
626
627 // The notifications were filtered and therefore need to be stored.
628 if ( $merged_notifications !== $filtered_merged_notifications ) {
629 $merged_notifications = $filtered_merged_notifications;
630 $this->notifications_need_storage = true;
631 }
632
633 $notifications = $this->split_on_user_id( $merged_notifications );
634
635 // No notifications to store, clear storage if it was previously present.
636 if ( empty( $notifications ) ) {
637 $this->remove_storage();
638
639 return;
640 }
641
642 // Only store notifications if changes are made.
643 if ( $this->notifications_need_storage ) {
644 array_walk( $notifications, [ $this, 'store_notifications_for_user' ] );
645 }
646 }
647
648 /**
649 * Stores the notifications to its respective user's storage.
650 *
651 * @param Yoast_Notification[] $notifications The notifications to store.
652 * @param int $user_id The ID of the user for which to store the notifications.
653 *
654 * @return void
655 */
656 private function store_notifications_for_user( $notifications, $user_id ) {
657 $notifications_as_arrays = array_map( [ $this, 'notification_to_array' ], $notifications );
658 update_user_option( $user_id, self::STORAGE_KEY, $notifications_as_arrays );
659 }
660
661 /**
662 * Provide a way to verify present notifications.
663 *
664 * @return Yoast_Notification[] Registered notifications.
665 */
666 public function get_notifications() {
667 if ( ! $this->notifications ) {
668 return [];
669 }
670 return array_merge( ...$this->notifications );
671 }
672
673 /**
674 * Returns the notifications for the given user.
675 *
676 * @param int $user_id The id of the user to check.
677 *
678 * @return Yoast_Notification[] The notifications for the user with the given ID.
679 */
680 public function get_notifications_for_user( $user_id ) {
681 if ( array_key_exists( $user_id, $this->notifications ) ) {
682 return $this->notifications[ $user_id ];
683 }
684 return [];
685 }
686
687 /**
688 * Get newly added notifications.
689 *
690 * @return array
691 */
692 public function get_new_notifications() {
693
694 return array_map( [ $this, 'get_notification_by_id' ], $this->new );
695 }
696
697 /**
698 * Get information from the User input.
699 *
700 * Note that this function does not handle nonce verification.
701 *
702 * @param string $key Key to retrieve.
703 *
704 * @return string non-sanitized value of key if set, an empty string otherwise.
705 */
706 private static function get_user_input( $key ) {
707 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.NonceVerification.Missing -- Reason: We are not processing form information and only using this variable in a comparison.
708 $request_method = isset( $_SERVER['REQUEST_METHOD'] ) && is_string( $_SERVER['REQUEST_METHOD'] ) ? strtoupper( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ) : '';
709 // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: This function does not sanitize variables.
710 // phpcs:disable WordPress.Security.NonceVerification.Recommended,WordPress.Security.NonceVerification.Missing -- Reason: This function does not verify a nonce.
711 if ( $request_method === 'POST' ) {
712 if ( isset( $_POST[ $key ] ) && is_string( $_POST[ $key ] ) ) {
713 return wp_unslash( $_POST[ $key ] );
714 }
715 }
716 elseif ( isset( $_GET[ $key ] ) && is_string( $_GET[ $key ] ) ) {
717 return wp_unslash( $_GET[ $key ] );
718 }
719 // phpcs:enable WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
720 return '';
721 }
722
723 /**
724 * Retrieve the notifications from storage and fill the relevant property.
725 *
726 * @param int $user_id The ID of the user to retrieve notifications for.
727 *
728 * @return void
729 */
730 private function retrieve_notifications_from_storage( $user_id ) {
731 if ( $this->notifications_retrieved ) {
732 return;
733 }
734
735 $this->notifications_retrieved = true;
736
737 $stored_notifications = get_user_option( self::STORAGE_KEY, $user_id );
738
739 // Check if notifications are stored.
740 if ( empty( $stored_notifications ) ) {
741 return;
742 }
743
744 if ( is_array( $stored_notifications ) ) {
745 $notifications = array_map( [ $this, 'array_to_notification' ], $stored_notifications );
746
747 // Apply array_values to ensure we get a 0-indexed array.
748 $notifications = array_values( array_filter( $notifications, [ $this, 'filter_notification_current_user' ] ) );
749
750 $this->notifications[ $user_id ] = $notifications;
751 }
752 }
753
754 /**
755 * Sort on type then priority.
756 *
757 * @param Yoast_Notification $a Compare with B.
758 * @param Yoast_Notification $b Compare with A.
759 *
760 * @return int 1, 0 or -1 for sorting offset.
761 */
762 private function sort_notifications( Yoast_Notification $a, Yoast_Notification $b ) {
763
764 $a_type = $a->get_type();
765 $b_type = $b->get_type();
766
767 if ( $a_type === $b_type ) {
768 return WPSEO_Utils::calc( $b->get_priority(), 'compare', $a->get_priority() );
769 }
770
771 if ( $a_type === 'error' ) {
772 return -1;
773 }
774
775 if ( $b_type === 'error' ) {
776 return 1;
777 }
778
779 return 0;
780 }
781
782 /**
783 * Clear local stored notifications.
784 *
785 * @return void
786 */
787 private function clear_notifications() {
788
789 $this->notifications = [];
790 $this->notifications_retrieved = false;
791 }
792
793 /**
794 * Filter out non-persistent notifications.
795 *
796 * @since 3.2
797 *
798 * @param Yoast_Notification $notification Notification to test for persistent.
799 *
800 * @return bool
801 */
802 private function filter_persistent_notifications( Yoast_Notification $notification ) {
803
804 return $notification->is_persistent();
805 }
806
807 /**
808 * Filter out dismissed notifications.
809 *
810 * @param Yoast_Notification $notification Notification to check.
811 *
812 * @return bool
813 */
814 private function filter_dismissed_notifications( Yoast_Notification $notification ) {
815
816 return ! self::maybe_dismiss_notification( $notification );
817 }
818
819 /**
820 * Convert Notification to array representation.
821 *
822 * @since 3.2
823 *
824 * @param Yoast_Notification $notification Notification to convert.
825 *
826 * @return array
827 */
828 private function notification_to_array( Yoast_Notification $notification ) {
829
830 $notification_data = $notification->to_array();
831
832 if ( isset( $notification_data['nonce'] ) ) {
833 unset( $notification_data['nonce'] );
834 }
835
836 return $notification_data;
837 }
838
839 /**
840 * Convert stored array to Notification.
841 *
842 * @param array $notification_data Array to convert to Notification.
843 *
844 * @return Yoast_Notification
845 */
846 private function array_to_notification( $notification_data ) {
847
848 if ( isset( $notification_data['options']['nonce'] ) ) {
849 unset( $notification_data['options']['nonce'] );
850 }
851
852 if ( isset( $notification_data['message'] )
853 && is_subclass_of( $notification_data['message'], Abstract_Presenter::class, false )
854 ) {
855 $notification_data['message'] = $notification_data['message']->present();
856 }
857
858 if ( isset( $notification_data['options']['user'] ) ) {
859 $notification_data['options']['user_id'] = $notification_data['options']['user']->ID;
860 unset( $notification_data['options']['user'] );
861
862 $this->notifications_need_storage = true;
863 }
864
865 return new Yoast_Notification(
866 $notification_data['message'],
867 $notification_data['options'],
868 );
869 }
870
871 /**
872 * Filter notifications that should not be displayed for the current user.
873 *
874 * @param Yoast_Notification $notification Notification to test.
875 *
876 * @return bool
877 */
878 private function filter_notification_current_user( Yoast_Notification $notification ) {
879 return $notification->display_for_current_user();
880 }
881
882 /**
883 * Checks if given notification is persistent.
884 *
885 * @param Yoast_Notification $notification The notification to check.
886 *
887 * @return bool True when notification is not persistent.
888 */
889 private function is_notification_persistent( Yoast_Notification $notification ) {
890 return ! $notification->is_persistent();
891 }
892
893 /**
894 * Queues a notification transaction for later execution if notifications are not yet set up.
895 *
896 * @param callable $callback Callback that performs the transaction.
897 * @param array $args Arguments to pass to the callback.
898 *
899 * @return bool True if transaction was queued, false if it can be performed immediately.
900 */
901 private function queue_transaction( $callback, $args ) {
902 if ( $this->notifications_retrieved ) {
903 return false;
904 }
905
906 $this->add_transaction_to_queue( $callback, $args );
907
908 return true;
909 }
910
911 /**
912 * Adds a notification transaction to the queue for later execution.
913 *
914 * @param callable $callback Callback that performs the transaction.
915 * @param array $args Arguments to pass to the callback.
916 *
917 * @return void
918 */
919 private function add_transaction_to_queue( $callback, $args ) {
920 $this->queued_transactions[] = [ $callback, $args ];
921 }
922
923 /**
924 * Removes all notifications from storage.
925 *
926 * @return bool True when notifications got removed.
927 */
928 protected function remove_storage() {
929 if ( ! $this->has_stored_notifications() ) {
930 return false;
931 }
932
933 delete_user_option( get_current_user_id(), self::STORAGE_KEY );
934 return true;
935 }
936
937 /**
938 * Checks if there are stored notifications.
939 *
940 * @return bool True when there are stored notifications.
941 */
942 protected function has_stored_notifications() {
943 $stored_notifications = $this->get_stored_notifications();
944
945 return ! empty( $stored_notifications );
946 }
947
948 /**
949 * Retrieves the stored notifications.
950 *
951 * @codeCoverageIgnore
952 *
953 * @return array|false Array with notifications or false when not set.
954 */
955 protected function get_stored_notifications() {
956 return get_user_option( self::STORAGE_KEY, get_current_user_id() );
957 }
958 }
959