PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 6.2.2
Defender Security – Malware Scanner, Login Security & Firewall v6.2.2
6.3.0 6.2.3 6.2.4 6.2.0 6.2.1 6.2.2 6.1.0 5.3.1 5.4.0 5.4.1 5.5.0 5.5.1 5.6.0 5.6.1 5.6.2 5.7.0 5.7.1 5.7.2 5.8.0 5.8.1 5.9.0 6.0.0 6.0.1 3.0.1 3.1.0 All 141 releases
defender-security / src / component / class-recipient-verification.php

class-recipient-verification.php in Defender Security – Malware Scanner, Login Security & Firewall 6.2.2, at src/component/class-recipient-verification.php

307 lines 9.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Manages recipient verification for notification modules.
4 *
5 * @package WP_Defender\Component
6 */
7
8 namespace WP_Defender\Component;
9
10 use WP_Defender\Component;
11 use WP_Defender\Model\Notification as Model_Notification;
12 use WP_Defender\Traits\User;
13
14 /**
15 * Handles recipient verification and subscription confirmation.
16 */
17 class Recipient_Verification extends Component {
18
19 use User;
20
21 /**
22 * Notification service.
23 *
24 * @var Notification
25 */
26 private Notification $notification;
27
28 /**
29 * Initialize dependencies.
30 */
31 public function __construct() {
32 $this->notification = wd_di()->get( Notification::class );
33 }
34
35 /**
36 * Process a resend-verification request.
37 *
38 * @param array $data Validated request data.
39 * @return array Result with success flag and response data.
40 */
41 public function resend_verification( array $data ): array {
42 $recipient_id = trim( (string) ( $data['id'] ?? '' ) );
43 $raw_pending = $data['pending'] ?? null;
44 $statuses = array_map( 'sanitize_key', is_array( $raw_pending ) ? $raw_pending : array() );
45
46 if ( '' === $recipient_id || array() === $statuses ) {
47 return $this->response_data( false, esc_html__( 'Invalid request.', 'defender-security' ) );
48 }
49
50 $in_house = is_numeric( $recipient_id );
51 $user_id = $in_house ? (int) $recipient_id : 0;
52 $email_key = $this->resolve_email_key( $in_house, $user_id, $recipient_id );
53
54 $collected = $this->collect_pending_modules( $statuses, $in_house, $email_key, $user_id );
55 $subscriber = $collected['subscriber'];
56 $modules = $collected['modules'];
57
58 if ( null === $subscriber ) {
59 return $this->response_data( false, esc_html__( 'Recipient not found.', 'defender-security' ) );
60 }
61
62 if ( ! is_array( $modules ) || array() === $modules ) {
63 return $this->response_data( false, esc_html__( 'Module not found.', 'defender-security' ) );
64 }
65
66 return $this->send_verification_email( $subscriber, $modules );
67 }
68
69 /**
70 * Parse notification slugs from query string parameters.
71 *
72 * @param mixed $slug Single uid query param.
73 * @param mixed $uids Comma-separated uids query param.
74 * @return array Sanitized slug list.
75 */
76 public function parse_subscription_slugs( $slug, $uids ): array {
77 if ( is_string( $uids ) && '' !== trim( $uids ) ) {
78 return array_filter( array_map( 'sanitize_key', explode( ',', $uids ) ), fn( string $v ): bool => '' !== $v );
79 }
80 if ( is_string( $slug ) && '' !== trim( $slug ) ) {
81 return array( sanitize_key( $slug ) );
82 }
83 return array();
84 }
85
86 /**
87 * Confirm matching subscriptions.
88 *
89 * @param array $slugs Notification slugs.
90 * @param string $hash Subscription hash.
91 * @param string $inhouse Whether the request is for an in-house recipient.
92 * @param bool $is_bulk Whether the request came from a multi-module invite link.
93 * @return array Confirmation result data.
94 */
95 public function confirm_subscriptions( array $slugs, string $hash, string $inhouse, bool $is_bulk ): array {
96 $result = $this->empty_confirmation_result();
97 $group = '1' === $inhouse ? 'in_house_recipients' : 'out_house_recipients';
98 $module_map = $this->notification->get_module_map();
99
100 foreach ( $slugs as $module_slug ) {
101 $model = $module_map[ $module_slug ] ?? null;
102 if ( ! $model instanceof Model_Notification ) {
103 continue;
104 }
105 $result['found_module'] = true;
106
107 $recipients = 'in_house_recipients' === $group ? $model->in_house_recipients : $model->out_house_recipients;
108 $match = $this->confirm_recipients_in_group( $recipients, $hash, $group );
109 if ( null === $match ) {
110 continue;
111 }
112
113 if ( 'in_house_recipients' === $group ) {
114 $model->in_house_recipients = $match['recipients'];
115 } else {
116 $model->out_house_recipients = $match['recipients'];
117 }
118 $model->save();
119
120 $result['processed'] = true;
121 $result['redirect_slug'] = '' === $result['redirect_slug'] ? $model->slug : $result['redirect_slug'];
122 if ( $is_bulk ) {
123 $result['confirmed_modules'][] = $model;
124 $result['confirmed_email'] = $match['email'];
125 $result['confirmed_name'] = $match['name'];
126 } else {
127 $this->notification->send_subscribed_email( $match['email'], $model, $match['name'] );
128 }
129 }
130
131 if ( $is_bulk && array() !== $result['confirmed_modules'] ) {
132 $this->notification->send_subscribed_all_email(
133 $result['confirmed_email'],
134 $result['confirmed_modules'],
135 $result['confirmed_name']
136 );
137 }
138
139 return $result;
140 }
141
142 /**
143 * Build a standard service response data shape.
144 *
145 * @param bool $success Whether the operation succeeded.
146 * @param string $message Response message.
147 * @return array
148 */
149 private function response_data( bool $success, string $message ): array {
150 return array(
151 'success' => $success,
152 'data' => array( 'message' => $message ),
153 );
154 }
155
156 /**
157 * Collect notification modules matching the given slugs and resolve the subscriber.
158 *
159 * @param array $statuses Sanitized module slugs.
160 * @param bool $in_house Whether the recipient is a WP user.
161 * @param string $email_key Email key for recipient lookup.
162 * @param int $user_id WP user ID (0 for out-of-house).
163 * @return array{subscriber: array|null, modules: array}
164 */
165 private function collect_pending_modules( array $statuses, bool $in_house, string $email_key, int $user_id ): array {
166 $module_map = $this->notification->get_module_map();
167 $subscriber = null;
168 $modules = array();
169
170 foreach ( $statuses as $slug ) {
171 $model = $module_map[ $slug ] ?? null;
172 if ( ! $model instanceof Model_Notification ) {
173 continue;
174 }
175 if ( null === $subscriber ) {
176 $subscriber = $this->resolve_subscriber( $model, $in_house, $email_key, $user_id );
177 }
178 $modules[] = $model;
179 }
180
181 return array(
182 'subscriber' => $subscriber,
183 'modules' => $modules,
184 );
185 }
186
187 /**
188 * Resolve the email key used to look up a recipient in a module's recipients array.
189 *
190 * @param bool $in_house Whether the recipient is a WP user.
191 * @param int $user_id WP user ID.
192 * @param string $recipient_id Raw recipient ID (email for out-of-house).
193 * @return string
194 */
195 private function resolve_email_key( bool $in_house, int $user_id, string $recipient_id ): string {
196 if ( ! $in_house ) {
197 return $recipient_id;
198 }
199 $user_data = get_userdata( $user_id );
200 return $user_data ? $user_data->user_email : '';
201 }
202
203 /**
204 * Resolve a subscriber array from a notification model.
205 *
206 * @param Model_Notification $model Notification model.
207 * @param bool $in_house Whether the recipient is a WP user.
208 * @param string $email_key Email or recipient ID used as array key.
209 * @param int $user_id WP user ID (0 for out-of-house).
210 * @return array|null Subscriber array or null if not found.
211 */
212 private function resolve_subscriber( Model_Notification $model, bool $in_house, string $email_key, int $user_id ): ?array {
213 if ( $in_house ) {
214 $recipient = $model->in_house_recipients[ $email_key ] ?? null;
215 if ( is_array( $recipient ) ) {
216 return array(
217 'id' => $user_id,
218 'name' => $recipient['name'],
219 'email' => $recipient['email'],
220 );
221 }
222 return null;
223 }
224
225 $recipient = $model->out_house_recipients[ $email_key ] ?? null;
226 if ( is_array( $recipient ) ) {
227 return array(
228 'name' => $recipient['name'],
229 'email' => $recipient['email'],
230 );
231 }
232 return null;
233 }
234
235 /**
236 * Send the appropriate verification email for a recipient and module list.
237 *
238 * @param array $subscriber Subscriber data array.
239 * @param array $modules Matched notification module objects.
240 * @return array Result with success flag and response data.
241 */
242 private function send_verification_email( array $subscriber, array $modules ): array {
243 if ( 1 === count( $modules ) ) {
244 $sent = $this->notification->send_email( $subscriber, $modules[0]->export() );
245 } else {
246 $sent = $this->notification->send_invite_all_email( $subscriber, $modules );
247 }
248
249 if ( $sent ) {
250 return $this->response_data( true, esc_html__( 'Verification email sent.', 'defender-security' ) );
251 }
252
253 return $this->response_data( false, esc_html__( 'Could not send verification email, please try again.', 'defender-security' ) );
254 }
255
256 /**
257 * Build an empty confirmation result.
258 *
259 * @return array
260 */
261 private function empty_confirmation_result(): array {
262 return array(
263 'found_module' => false,
264 'processed' => false,
265 'redirect_slug' => '',
266 'confirmed_modules' => array(),
267 'confirmed_email' => '',
268 'confirmed_name' => '',
269 );
270 }
271
272 /**
273 * Find and confirm the matching recipient within a recipients group.
274 *
275 * @param array $recipients Recipients array.
276 * @param string $hash Subscription hash to match.
277 * @param string $group Recipient group property.
278 * @return array|null Array with keys 'recipients', 'email', 'name', or null.
279 */
280 private function confirm_recipients_in_group( array $recipients, string $hash, string $group ): ?array {
281 foreach ( $recipients as &$recipient ) {
282 if ( Model_Notification::USER_SUBSCRIBED === $recipient['status'] ) {
283 continue;
284 }
285 $email = $recipient['email'];
286 if ( ! hash_equals( $hash, hash( 'sha256', $email . AUTH_SALT ) ) ) {
287 continue;
288 }
289
290 if ( 'in_house_recipients' === $group && ( ! is_user_logged_in() || $email !== $this->get_current_user_email() ) ) {
291 wp_die( esc_html__( 'Invalid request.', 'defender-security' ) );
292 }
293
294 $name = $recipient['name'];
295 $recipient['status'] = Model_Notification::USER_SUBSCRIBED;
296
297 return array(
298 'recipients' => $recipients,
299 'email' => $email,
300 'name' => $name,
301 );
302 }
303
304 return null;
305 }
306 }
307