PluginProbe
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) / 1.8.13
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) v1.8.13
1.8.13 1.8.12.3 1.8.12.2 1.8.12.1 1.8.12 1.8.11.3 1.8.11.2 1.8.11.1 1.8.11 1.6.6 1.6.60 1.6.7 1.6.8 1.6.9 1.7.0 1.7.0.1 1.7.0.11 1.7.0.12 1.7.0.14 1.7.0.2 1.7.0.3 1.7.0.5 1.7.0.6 1.7.0.7 1.7.0.9 All 211 releases
charitable / includes / admin / notifications / class-charitable-new-feed.php

class-charitable-new-feed.php in Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) 1.8.13, at includes/admin/notifications/class-charitable-new-feed.php

169 lines 4.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Charitable New Feed.
4 *
5 * Fetches the new Charitable announcement feed (separate from the legacy feed).
6 * Non-dismissible items stay pinned until removed from the feed itself.
7 *
8 * @package Charitable/Classes/Charitable_New_Feed
9 * @since 1.8.12
10 */
11
12 if ( ! defined( 'ABSPATH' ) ) {
13 exit;
14 }
15
16 class Charitable_New_Feed {
17
18 private static $instance = null;
19
20 /**
21 * New announcement feed URL.
22 * Placeholder — update when endpoint is live.
23 */
24 const SOURCE_URL = 'https://plugin.wpcharitable.com/wp-content/charitable-announcements.json';
25
26 public static function get_instance() {
27 if ( is_null( self::$instance ) ) {
28 self::$instance = new self();
29 }
30 return self::$instance;
31 }
32
33 public function init() {
34 // Inject at priority 1 — before everything else (new feed items go to very top).
35 add_filter( 'charitable_notifications_get', [ $this, 'inject_new_feed' ], 1 );
36 }
37
38 /**
39 * Inject new feed notifications at the top of the feed.
40 *
41 * @param array $notifications
42 * @return array
43 */
44 public function inject_new_feed( array $notifications ) {
45 $settings = Charitable_Notification_Settings::get_instance();
46 if ( ! $settings->is_category_enabled( 'new_feed' ) ) {
47 return $notifications;
48 }
49
50 $items = $this->get_stored();
51 if ( empty( $items ) ) {
52 return $notifications;
53 }
54
55 $dismissed = Charitable_Notifications::get_instance()->get_option()['dismissed'];
56
57 $to_inject = [];
58 foreach ( $items as $item ) {
59 if ( empty( $item['id'] ) ) {
60 continue;
61 }
62
63 $dismissible = ! isset( $item['dismissible'] ) || (bool) $item['dismissible'];
64
65 // Non-dismissible: always show regardless of dismissed array.
66 if ( ! $dismissible ) {
67 $item['dismissible'] = false;
68 $to_inject[] = $item;
69 continue;
70 }
71
72 // Dismissible: respect dismissed array.
73 if ( ! in_array( (string) $item['id'], $dismissed, true ) ) {
74 $to_inject[] = $item;
75 }
76 }
77
78 // Sort: non-dismissible first.
79 usort( $to_inject, function( $a, $b ) {
80 $da = ! isset( $a['dismissible'] ) || (bool) $a['dismissible'];
81 $db = ! isset( $b['dismissible'] ) || (bool) $b['dismissible'];
82 if ( $da === $db ) return 0;
83 return $da ? 1 : -1; // non-dismissible first.
84 } );
85
86 return array_merge( $to_inject, $notifications );
87 }
88
89 /**
90 * Fetch the remote feed and store results. Called by cron.
91 */
92 public function fetch_and_store() {
93 $response = wp_remote_get( self::SOURCE_URL, [
94 'timeout' => 10,
95 'sslverify' => false,
96 'user-agent' => charitable_get_default_user_agent(),
97 ] );
98
99 if ( is_wp_error( $response ) ) {
100 return;
101 }
102
103 $body = wp_remote_retrieve_body( $response );
104 if ( empty( $body ) ) {
105 return;
106 }
107
108 $items = json_decode( $body, true );
109 if ( ! is_array( $items ) ) {
110 return;
111 }
112
113 // Validate and sanitize each item.
114 $clean = [];
115 foreach ( $items as $item ) {
116 if ( empty( $item['id'] ) || empty( $item['content'] ) ) {
117 continue;
118 }
119
120 // Sanitize each field before storing.
121 $sanitized = [];
122
123 $sanitized['id'] = sanitize_key( $item['id'] );
124 $sanitized['title'] = isset( $item['title'] ) ? sanitize_text_field( $item['title'] ) : '';
125 $sanitized['content'] = wp_kses_post( $item['content'] );
126 $sanitized['badge_label'] = 'Announcement';
127 $sanitized['badge_type'] = 'announcement';
128 $sanitized['category'] = 'new_feed';
129 $sanitized['notification_type'] = isset( $item['notification_type'] ) ? sanitize_text_field( $item['notification_type'] ) : '';
130
131 // Sanitize btns array.
132 $sanitized['btns'] = [];
133 if ( ! empty( $item['btns'] ) && is_array( $item['btns'] ) ) {
134 foreach ( $item['btns'] as $btn_key => $btn ) {
135 if ( is_array( $btn ) ) {
136 $sanitized['btns'][ sanitize_key( $btn_key ) ] = [
137 'url' => isset( $btn['url'] ) ? esc_url_raw( $btn['url'] ) : '',
138 'text' => isset( $btn['text'] ) ? sanitize_text_field( $btn['text'] ) : '',
139 ];
140 }
141 }
142 }
143
144 // Preserve dismissible as boolean.
145 if ( isset( $item['dismissible'] ) ) {
146 $sanitized['dismissible'] = (bool) $item['dismissible'];
147 }
148
149 // Preserve start as sanitized text.
150 if ( isset( $item['start'] ) ) {
151 $sanitized['start'] = sanitize_text_field( $item['start'] );
152 }
153
154 $clean[] = $sanitized;
155 }
156
157 Charitable_Notifications::get_instance()->save_option_partial( [ 'new_feed' => $clean ] );
158 }
159
160 /**
161 * Get stored new feed items.
162 *
163 * @return array
164 */
165 private function get_stored() {
166 return Charitable_Notifications::get_instance()->get_option()['new_feed'];
167 }
168 }
169