PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 6.1.0
Defender Security – Malware Scanner, Login Security & Firewall v6.1.0
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.1.0, at src/component/class-recipient-verification.php

302 lines 9.2 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 || empty( $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 ( empty( $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 ) ) );
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 $match = $this->confirm_recipients_in_group( $model->$group, $hash, $group );
108 if ( null === $match ) {
109 continue;
110 }
111
112 $model->$group = $match['recipients'];
113 $model->save();
114
115 $result['processed'] = true;
116 $result['redirect_slug'] = '' === $result['redirect_slug'] ? $model->slug : $result['redirect_slug'];
117 if ( $is_bulk ) {
118 $result['confirmed_modules'][] = $model;
119 $result['confirmed_email'] = $match['email'];
120 $result['confirmed_name'] = $match['name'];
121 } else {
122 $this->notification->send_subscribed_email( $match['email'], $model, $match['name'] );
123 }
124 }
125
126 if ( $is_bulk && array() !== $result['confirmed_modules'] ) {
127 $this->notification->send_subscribed_all_email(
128 $result['confirmed_email'],
129 $result['confirmed_modules'],
130 $result['confirmed_name']
131 );
132 }
133
134 return $result;
135 }
136
137 /**
138 * Build a standard service response data shape.
139 *
140 * @param bool $success Whether the operation succeeded.
141 * @param string $message Response message.
142 * @return array
143 */
144 private function response_data( bool $success, string $message ): array {
145 return array(
146 'success' => $success,
147 'data' => array( 'message' => $message ),
148 );
149 }
150
151 /**
152 * Collect notification modules matching the given slugs and resolve the subscriber.
153 *
154 * @param array $statuses Sanitized module slugs.
155 * @param bool $in_house Whether the recipient is a WP user.
156 * @param string $email_key Email key for recipient lookup.
157 * @param int $user_id WP user ID (0 for out-of-house).
158 * @return array{subscriber: array|null, modules: array}
159 */
160 private function collect_pending_modules( array $statuses, bool $in_house, string $email_key, int $user_id ): array {
161 $module_map = $this->notification->get_module_map();
162 $subscriber = null;
163 $modules = array();
164
165 foreach ( $statuses as $slug ) {
166 $model = $module_map[ $slug ] ?? null;
167 if ( ! $model instanceof Model_Notification ) {
168 continue;
169 }
170 if ( null === $subscriber ) {
171 $subscriber = $this->resolve_subscriber( $model, $in_house, $email_key, $user_id );
172 }
173 $modules[] = $model;
174 }
175
176 return array(
177 'subscriber' => $subscriber,
178 'modules' => $modules,
179 );
180 }
181
182 /**
183 * Resolve the email key used to look up a recipient in a module's recipients array.
184 *
185 * @param bool $in_house Whether the recipient is a WP user.
186 * @param int $user_id WP user ID.
187 * @param string $recipient_id Raw recipient ID (email for out-of-house).
188 * @return string
189 */
190 private function resolve_email_key( bool $in_house, int $user_id, string $recipient_id ): string {
191 if ( ! $in_house ) {
192 return $recipient_id;
193 }
194 $user_data = get_userdata( $user_id );
195 return $user_data ? $user_data->user_email : '';
196 }
197
198 /**
199 * Resolve a subscriber array from a notification model.
200 *
201 * @param Model_Notification $model Notification model.
202 * @param bool $in_house Whether the recipient is a WP user.
203 * @param string $email_key Email or recipient ID used as array key.
204 * @param int $user_id WP user ID (0 for out-of-house).
205 * @return array|null Subscriber array or null if not found.
206 */
207 private function resolve_subscriber( Model_Notification $model, bool $in_house, string $email_key, int $user_id ): ?array {
208 if ( $in_house ) {
209 $recipient = $model->in_house_recipients[ $email_key ] ?? null;
210 if ( is_array( $recipient ) ) {
211 return array(
212 'id' => $user_id,
213 'name' => $recipient['name'],
214 'email' => $recipient['email'],
215 );
216 }
217 return null;
218 }
219
220 $recipient = $model->out_house_recipients[ $email_key ] ?? null;
221 if ( is_array( $recipient ) ) {
222 return array(
223 'name' => $recipient['name'],
224 'email' => $recipient['email'],
225 );
226 }
227 return null;
228 }
229
230 /**
231 * Send the appropriate verification email for a recipient and module list.
232 *
233 * @param array $subscriber Subscriber data array.
234 * @param array $modules Matched notification module objects.
235 * @return array Result with success flag and response data.
236 */
237 private function send_verification_email( array $subscriber, array $modules ): array {
238 if ( 1 === count( $modules ) ) {
239 $sent = $this->notification->send_email( $subscriber, $modules[0]->export() );
240 } else {
241 $sent = $this->notification->send_invite_all_email( $subscriber, $modules );
242 }
243
244 if ( $sent ) {
245 return $this->response_data( true, esc_html__( 'Verification email sent.', 'defender-security' ) );
246 }
247
248 return $this->response_data( false, esc_html__( 'Could not send verification email, please try again.', 'defender-security' ) );
249 }
250
251 /**
252 * Build an empty confirmation result.
253 *
254 * @return array
255 */
256 private function empty_confirmation_result(): array {
257 return array(
258 'found_module' => false,
259 'processed' => false,
260 'redirect_slug' => '',
261 'confirmed_modules' => array(),
262 'confirmed_email' => '',
263 'confirmed_name' => '',
264 );
265 }
266
267 /**
268 * Find and confirm the matching recipient within a recipients group.
269 *
270 * @param array $recipients Recipients array.
271 * @param string $hash Subscription hash to match.
272 * @param string $group Recipient group property.
273 * @return array|null Array with keys 'recipients', 'email', 'name', or null.
274 */
275 private function confirm_recipients_in_group( array $recipients, string $hash, string $group ): ?array {
276 foreach ( $recipients as &$recipient ) {
277 if ( Model_Notification::USER_SUBSCRIBED === $recipient['status'] ) {
278 continue;
279 }
280 $email = $recipient['email'];
281 if ( ! hash_equals( $hash, hash( 'sha256', $email . AUTH_SALT ) ) ) {
282 continue;
283 }
284
285 if ( 'in_house_recipients' === $group && ( ! is_user_logged_in() || $email !== $this->get_current_user_email() ) ) {
286 wp_die( esc_html__( 'Invalid request.', 'defender-security' ) );
287 }
288
289 $name = $recipient['name'];
290 $recipient['status'] = Model_Notification::USER_SUBSCRIBED;
291
292 return array(
293 'recipients' => $recipients,
294 'email' => $email,
295 'name' => $name,
296 );
297 }
298
299 return null;
300 }
301 }
302