PluginProbe
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / trunk
Yoast SEO – Advanced SEO with real-time guidance and built-in AI vtrunk
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
← All changes | admin/class-yoast-notification-center.php +81 -35 18.4.1 → trunk View file →
@@ -16,14 +16,14 @@
16 16 * Option name to store notifications on.
17 17 *
18 18 * @var string
19 19 */
20 - const STORAGE_KEY = 'yoast_notifications';
20 + public const STORAGE_KEY = 'yoast_notifications';
21 21
22 22 /**
23 23 * The singleton instance of this object.
24 24 *
25 - * @var \Yoast_Notification_Center
25 + * @var Yoast_Notification_Center|null
26 26 */
27 27 private static $instance = null;
28 28
29 29 /**
@@ -28,9 +28,9 @@
28 28
29 29 /**
30 30 * Holds the notifications.
31 31 *
32 - * @var \Yoast_Notification[][]
32 + * @var Yoast_Notification[][]
33 33 */
34 34 private $notifications = [];
35 35
36 36 /**
@@ -89,11 +89,9 @@
89 89 * @return Yoast_Notification_Center
90 90 */
91 91 public static function get() {
92 92
93 - if ( self::$instance === null ) {
94 - self::$instance = new self();
95 - }
93 + self::$instance ??= new self();
96 94
97 95 return self::$instance;
98 96 }
99 97
@@ -98,18 +96,29 @@
98 96 }
99 97
100 98 /**
101 99 * Dismiss a notification.
100 + *
101 + * @return void
102 102 */
103 103 public static function ajax_dismiss_notification() {
104 + $notification_center = self::get();
104 105
105 - $notification_center = self::get();
106 + if ( ! isset( $_POST['notification'] ) || ! is_string( $_POST['notification'] ) ) {
107 + exit( '-1' );
108 + }
106 109
107 - $notification_id = filter_input( INPUT_POST, 'notification' );
110 + $notification_id = sanitize_text_field( wp_unslash( $_POST['notification'] ) );
111 +
108 112 if ( empty( $notification_id ) ) {
109 - die( '-1' );
113 + exit( '-1' );
110 114 }
111 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 +
112 121 $notification = $notification_center->get_notification_by_id( $notification_id );
113 122 if ( ( $notification instanceof Yoast_Notification ) === false ) {
114 123
115 124 // Permit legacy.
@@ -120,12 +129,12 @@
120 129 $notification = new Yoast_Notification( '', $options );
121 130 }
122 131
123 132 if ( self::maybe_dismiss_notification( $notification ) ) {
124 - die( '1' );
133 + exit( '1' );
125 134 }
126 135
127 - die( '-1' );
136 + exit( '-1' );
128 137 }
129 138
130 139 /**
131 140 * Check if the user has dismissed a notification.
@@ -294,12 +303,14 @@
294 303 /**
295 304 * Add notification to the cookie.
296 305 *
297 306 * @param Yoast_Notification $notification Notification object instance.
307 + *
308 + * @return void
298 309 */
299 310 public function add_notification( Yoast_Notification $notification ) {
300 311
301 - $callback = [ $this, __METHOD__ ];
312 + $callback = [ $this, __FUNCTION__ ];
302 313 $args = func_get_args();
303 314 if ( $this->queue_transaction( $callback, $args ) ) {
304 315 return;
305 316 }
@@ -316,13 +327,13 @@
316 327 if ( $notification_id !== '' ) {
317 328
318 329 // If notification ID exists in notifications, don't add again.
319 330 $present_notification = $this->get_notification_by_id( $notification_id, $user_id );
320 - if ( ! is_null( $present_notification ) ) {
331 + if ( $present_notification !== null ) {
321 332 $this->remove_notification( $present_notification, false );
322 333 }
323 334
324 - if ( is_null( $present_notification ) ) {
335 + if ( $present_notification === null ) {
325 336 $this->new[] = $notification_id;
326 337 }
327 338 }
328 339
@@ -401,12 +412,14 @@
401 412 * Remove notification after it has been displayed.
402 413 *
403 414 * @param Yoast_Notification $notification Notification to remove.
404 415 * @param bool $resolve Resolve as fixed.
416 + *
417 + * @return void
405 418 */
406 419 public function remove_notification( Yoast_Notification $notification, $resolve = true ) {
407 420
408 - $callback = [ $this, __METHOD__ ];
421 + $callback = [ $this, __FUNCTION__ ];
409 422 $args = func_get_args();
410 423 if ( $this->queue_transaction( $callback, $args ) ) {
411 424 return;
412 425 }
@@ -497,9 +510,9 @@
497 510
498 511 /**
499 512 * Return the notifications sorted on type and priority.
500 513 *
501 - * @return array|Yoast_Notification[] Sorted Notifications
514 + * @return Yoast_Notification[] Sorted Notifications
502 515 */
503 516 public function get_sorted_notifications() {
504 517 $notifications = $this->get_notifications_for_user( get_current_user_id() );
505 518 if ( empty( $notifications ) ) {
@@ -513,21 +526,30 @@
513 526 }
514 527
515 528 /**
516 529 * AJAX display notifications.
530 + *
531 + * @return void
517 532 */
518 533 public function ajax_get_notifications() {
519 - $echo = filter_input( INPUT_POST, 'version' ) === '2';
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 + }
520 540
521 541 // Display the notices.
522 542 $this->display_notifications( $echo );
523 543
524 544 // AJAX die.
525 - exit;
545 + exit();
526 546 }
527 547
528 548 /**
529 549 * Remove storage when the plugin is deactivated.
550 + *
551 + * @return void
530 552 */
531 553 public function deactivate_hook() {
532 554
533 555 $this->clear_notifications();
@@ -553,9 +575,9 @@
553 575 *
554 576 * In other terms, it returns an associative array,
555 577 * mapping user ID to a list of notifications for this user.
556 578 *
557 - * @param array|Yoast_Notification[] $notifications The notifications to split.
579 + * @param Yoast_Notification[] $notifications The notifications to split.
558 580 *
559 581 * @return array The notifications, split on user ID.
560 582 */
561 583 private function split_on_user_id( $notifications ) {
@@ -575,8 +597,14 @@
575 597 *
576 598 * @return void
577 599 */
578 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';
579 607
580 608 $notifications = $this->notifications;
581 609
582 610 /**
@@ -591,9 +619,9 @@
591 619
592 620 /**
593 621 * Filter: 'yoast_notifications_before_storage' - Allows developer to filter notifications before saving them.
594 622 *
595 - * @api Yoast_Notification[] $notifications
623 + * @param Yoast_Notification[] $notifications
596 624 */
597 625 $filtered_merged_notifications = apply_filters( 'yoast_notifications_before_storage', $merged_notifications );
598 626
599 627 // The notifications were filtered and therefore need to be stored.
@@ -619,10 +647,10 @@
619 647
620 648 /**
621 649 * Stores the notifications to its respective user's storage.
622 650 *
623 - * @param array|Yoast_Notification[] $notifications The notifications to store.
624 - * @param int $user_id The ID of the user for which to store the notifications.
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.
625 653 *
626 654 * @return void
627 655 */
628 656 private function store_notifications_for_user( $notifications, $user_id ) {
@@ -632,9 +660,9 @@
632 660
633 661 /**
634 662 * Provide a way to verify present notifications.
635 663 *
636 - * @return array|Yoast_Notification[] Registered notifications.
664 + * @return Yoast_Notification[] Registered notifications.
637 665 */
638 666 public function get_notifications() {
639 667 if ( ! $this->notifications ) {
640 668 return [];
@@ -668,22 +696,29 @@
668 696
669 697 /**
670 698 * Get information from the User input.
671 699 *
700 + * Note that this function does not handle nonce verification.
701 + *
672 702 * @param string $key Key to retrieve.
673 703 *
674 - * @return mixed value of key if set.
704 + * @return string non-sanitized value of key if set, an empty string otherwise.
675 705 */
676 706 private static function get_user_input( $key ) {
677 -
678 - $filter_input_type = INPUT_GET;
679 - $request_method = isset( $_SERVER['REQUEST_METHOD'] ) ? filter_var( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ) : '';
680 -
681 - if ( strtoupper( $request_method ) === 'POST' ) {
682 - $filter_input_type = INPUT_POST;
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 + }
683 715 }
684 -
685 - return filter_input( $filter_input_type, $key );
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 '';
686 721 }
687 722
688 723 /**
689 724 * Retrieve the notifications from storage and fill the relevant property.
@@ -692,9 +727,8 @@
692 727 *
693 728 * @return void
694 729 */
695 730 private function retrieve_notifications_from_storage( $user_id ) {
696 -
697 731 if ( $this->notifications_retrieved ) {
698 732 return;
699 733 }
700 734
@@ -708,8 +742,9 @@
708 742 }
709 743
710 744 if ( is_array( $stored_notifications ) ) {
711 745 $notifications = array_map( [ $this, 'array_to_notification' ], $stored_notifications );
746 +
712 747 // Apply array_values to ensure we get a 0-indexed array.
713 748 $notifications = array_values( array_filter( $notifications, [ $this, 'filter_notification_current_user' ] ) );
714 749
715 750 $this->notifications[ $user_id ] = $notifications;
@@ -745,8 +780,10 @@
745 780 }
746 781
747 782 /**
748 783 * Clear local stored notifications.
784 + *
785 + * @return void
749 786 */
750 787 private function clear_notifications() {
751 788
752 789 $this->notifications = [];
@@ -812,16 +849,23 @@
812 849 unset( $notification_data['options']['nonce'] );
813 850 }
814 851
815 852 if ( isset( $notification_data['message'] )
816 - && \is_subclass_of( $notification_data['message'], Abstract_Presenter::class, false )
853 + && is_subclass_of( $notification_data['message'], Abstract_Presenter::class, false )
817 854 ) {
818 855 $notification_data['message'] = $notification_data['message']->present();
819 856 }
820 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 +
821 865 return new Yoast_Notification(
822 866 $notification_data['message'],
823 - $notification_data['options']
867 + $notification_data['options'],
824 868 );
825 869 }
826 870
827 871 /**
@@ -868,8 +912,10 @@
868 912 * Adds a notification transaction to the queue for later execution.
869 913 *
870 914 * @param callable $callback Callback that performs the transaction.
871 915 * @param array $args Arguments to pass to the callback.
916 + *
917 + * @return void
872 918 */
873 919 private function add_transaction_to_queue( $callback, $args ) {
874 920 $this->queued_transactions[] = [ $callback, $args ];
875 921 }