PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / trunk
Defender Security – Malware Scanner, Login Security & Firewall vtrunk
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 3.1.1 All 140 releases
defender-security / src / controller / class-recipients.php

class-recipients.php in Defender Security – Malware Scanner, Login Security & Firewall trunk, at src/controller/class-recipients.php

420 lines 11.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Manages notification recipients.
4 *
5 * @package WP_Defender\Controller
6 */
7
8 namespace WP_Defender\Controller;
9
10 use WP_Defender\Event;
11 use Calotes\Helper\HTTP;
12 use Calotes\Component\Request;
13 use Calotes\Component\Response;
14 use WP_Defender\Component\Recipient_Directory;
15 use WP_Defender\Component\Recipient_Verification;
16
17 /**
18 * Handles recipient routes.
19 */
20 class Recipients extends Event {
21
22 /**
23 * Route slug.
24 *
25 * @var string
26 */
27 protected $slug = 'wdf-recipients';
28
29 /**
30 * Notification service.
31 *
32 * @var \WP_Defender\Component\Notification
33 */
34 protected $service;
35
36 /**
37 * Recipient service.
38 *
39 * @var \WP_Defender\Component\Recipients
40 */
41 protected $recipient_service;
42
43 /**
44 * Recipient directory service.
45 *
46 * @var Recipient_Directory
47 */
48 protected $recipient_directory;
49
50 /**
51 * Recipient verification service.
52 *
53 * @var Recipient_Verification
54 */
55 protected $recipient_verification;
56
57 /**
58 * Register routes and hooks.
59 */
60 public function __construct() {
61 $this->register_routes();
62 $this->service = wd_di()->get( \WP_Defender\Component\Notification::class );
63 $this->recipient_service = wd_di()->get( \WP_Defender\Component\Recipients::class );
64 $this->recipient_directory = wd_di()->get( Recipient_Directory::class );
65 $this->recipient_verification = wd_di()->get( Recipient_Verification::class );
66 add_action( 'wp_ajax_' . Notification::SLUG_SUBSCRIBE, array( $this, 'verify_subscriber' ) );
67 add_action( 'wp_ajax_nopriv_' . Notification::SLUG_SUBSCRIBE, array( $this, 'verify_subscriber' ) );
68 }
69
70 /**
71 * Add a recipient to selected notification modules.
72 *
73 * @param Request $request Request data.
74 * @return Response Response data.
75 * @defender_route
76 */
77 public function add_recipient( Request $request ): Response {
78 if ( ! $this->check_permission() ) {
79 return new Response( false, array( 'message' => esc_html__( 'You do not have permission to perform this action.', 'defender-security' ) ) );
80 }
81
82 $data = $request->get_data(
83 array(
84 'name' => array(
85 'type' => 'string',
86 'sanitize' => 'sanitize_text_field',
87 ),
88 'email' => array(
89 'type' => 'string',
90 'sanitize' => 'sanitize_email',
91 ),
92 'statuses' => array( 'type' => 'array' ),
93 '_inHouse' => array( 'type' => 'bool' ),
94 'id' => array( 'type' => 'integer' ),
95 )
96 );
97
98 $name = trim( (string) ( $data['name'] ?? '' ) );
99 $email = trim( (string) ( $data['email'] ?? '' ) );
100 $statuses = array_map( 'sanitize_key', is_array( $data['statuses'] ?? null ) ? $data['statuses'] : array() );
101 $user_id = absint( $data['id'] ?? 0 );
102 $in_house = (bool) ( $data['_inHouse'] ?? false ) && $user_id > 0;
103
104 if ( '' === $name || ! filter_var( $email, FILTER_VALIDATE_EMAIL ) ) {
105 return new Response( false, array( 'message' => esc_html__( 'Invalid recipient data.', 'defender-security' ) ) );
106 }
107
108 if ( ! $in_house && ! preg_match( '/^[\p{L}\s\-\']+$/u', $name ) ) {
109 return new Response( false, array( 'message' => esc_html__( 'Only letters, spaces, hyphens and apostrophes allowed.', 'defender-security' ) ) );
110 }
111
112 // Block duplicate recipient emails. Messages render as React text, so keep them unescaped.
113 if ( $this->recipient_directory->email_exists( $email ) ) {
114 return new Response(
115 false,
116 array(
117 'message' => __( 'This email address is already added as a recipient. Edit the existing recipient to add more report types.', 'defender-security' ),
118 'code' => 'recipient_notice',
119 )
120 );
121 }
122
123 // Registered users must be added via user search, not invited by email.
124 if ( ! $in_house && get_user_by( 'email', $email ) ) {
125 return new Response(
126 false,
127 array(
128 'message' => __( "Registered user email can't be invited, you can add them directly at Search WordPress user tab.", 'defender-security' ),
129 'code' => 'recipient_notice',
130 )
131 );
132 }
133
134 if ( array() === $statuses ) {
135 return new Response( false, array( 'message' => esc_html__( 'Please enable at least one notification module before inviting a recipient.', 'defender-security' ) ) );
136 }
137
138 $subscriber = array(
139 'name' => $name,
140 'email' => $email,
141 );
142 if ( $in_house ) {
143 $subscriber['id'] = $user_id;
144 }
145
146 $this->recipient_service->upsert_recipient_to_modules( $statuses, $subscriber );
147
148 return new Response( true, $this->data_frontend() );
149 }
150
151 /**
152 * Update a recipient across notification modules.
153 *
154 * @param Request $request Request.
155 * @return Response Response data.
156 * @defender_route
157 */
158 public function update_recipient( Request $request ): Response {
159 if ( ! $this->check_permission() ) {
160 return new Response( false, array( 'message' => esc_html__( 'You do not have permission to perform this action.', 'defender-security' ) ) );
161 }
162
163 $data = $request->get_data(
164 array(
165 'id' => array(
166 'type' => 'string',
167 'sanitize' => 'sanitize_text_field',
168 ),
169 'statuses' => array( 'type' => 'array' ),
170 'profile' => array( 'type' => 'array' ),
171 'unsubscribed' => array( 'type' => 'array' ),
172 )
173 );
174
175 $recipient_id = trim( (string) ( $data['id'] ?? '' ) );
176 $statuses = array_map( 'sanitize_key', is_array( $data['statuses'] ?? null ) ? $data['statuses'] : array() );
177 $profile = is_array( $data['profile'] ?? null ) ? $data['profile'] : array();
178 $unsubscribed = array_map( 'sanitize_key', is_array( $data['unsubscribed'] ?? null ) ? $data['unsubscribed'] : array() );
179
180 if ( '' === $recipient_id ) {
181 return new Response( false, array( 'message' => esc_html__( 'Invalid recipient ID.', 'defender-security' ) ) );
182 }
183
184 $name = sanitize_text_field( trim( (string) ( $profile['name'] ?? '' ) ) );
185 $email = sanitize_email( trim( (string) ( $profile['email'] ?? '' ) ) );
186 $in_house = is_numeric( $recipient_id );
187
188 if ( '' === $name || ! filter_var( $email, FILTER_VALIDATE_EMAIL ) ) {
189 return new Response( false, array( 'message' => esc_html__( 'Invalid recipient data.', 'defender-security' ) ) );
190 }
191
192 if ( ! $in_house && ! preg_match( '/^[\p{L}\s\-\']+$/u', $name ) ) {
193 return new Response( false, array( 'message' => esc_html__( 'Only letters, spaces, hyphens and apostrophes allowed.', 'defender-security' ) ) );
194 }
195
196 $subscriber = array(
197 'name' => $name,
198 'email' => $email,
199 );
200 if ( $in_house ) {
201 $subscriber['id'] = (int) $recipient_id;
202 $subscriber['first_name'] = sanitize_text_field( trim( (string) ( $profile['firstName'] ?? '' ) ) );
203 $subscriber['last_name'] = sanitize_text_field( trim( (string) ( $profile['lastName'] ?? '' ) ) );
204 }
205
206 $this->recipient_service->upsert_recipient_to_modules( $statuses, $subscriber, $unsubscribed );
207
208 return new Response( true, $this->data_frontend() );
209 }
210
211 /**
212 * Delete a recipient from all notification modules.
213 *
214 * @param Request $request Request data.
215 * @return Response Response data.
216 * @defender_route
217 */
218 public function delete_recipient( Request $request ): Response {
219 if ( ! $this->check_permission() ) {
220 return new Response( false, array( 'message' => esc_html__( 'You do not have permission to perform this action.', 'defender-security' ) ) );
221 }
222
223 $data = $request->get_data(
224 array(
225 'id' => array(
226 'type' => 'string',
227 'sanitize' => 'sanitize_text_field',
228 ),
229 )
230 );
231
232 $recipient_id = trim( (string) ( $data['id'] ?? '' ) );
233 if ( '' === $recipient_id ) {
234 return new Response( false, array( 'message' => esc_html__( 'Invalid recipient ID.', 'defender-security' ) ) );
235 }
236
237 $this->recipient_directory->delete_recipient( $recipient_id );
238
239 return new Response( true, $this->data_frontend() );
240 }
241
242 /**
243 * Search WordPress users by name or email.
244 *
245 * @param Request $request Request data.
246 * @return Response Response data.
247 * @defender_route
248 */
249 public function search_users( Request $request ): Response {
250 if ( ! $this->check_permission() ) {
251 return new Response( false, array( 'message' => esc_html__( 'You do not have permission to perform this action.', 'defender-security' ) ) );
252 }
253
254 $data = $request->get_data(
255 array(
256 'search' => array(
257 'type' => 'string',
258 'sanitize' => 'sanitize_text_field',
259 ),
260 )
261 );
262 $search = trim( (string) ( $data['search'] ?? '' ) );
263
264 return new Response( true, array( 'users' => $this->recipient_directory->search_users( $search ) ) );
265 }
266
267 /**
268 * Resend verification email for a pending recipient.
269 *
270 * @param Request $request Request data.
271 * @return Response Response data.
272 * @defender_route
273 */
274 public function resend_verification( Request $request ): Response {
275 if ( ! $this->check_permission() ) {
276 return new Response( false, array( 'message' => esc_html__( 'You do not have permission to perform this action.', 'defender-security' ) ) );
277 }
278
279 $data = $request->get_data(
280 array(
281 'id' => array(
282 'type' => 'string',
283 'sanitize' => 'sanitize_text_field',
284 ),
285 'pending' => array( 'type' => 'array' ),
286 )
287 );
288
289 $result = $this->recipient_verification->resend_verification( $data );
290
291 return new Response( $result['success'], $result['data'] );
292 }
293
294 /**
295 * Confirm a subscriber from an email link.
296 *
297 * @return void
298 */
299 public function verify_subscriber(): void {
300 $hash = HTTP::get( 'hash', '' );
301 $slug = HTTP::get( 'uid', '' );
302 $uids = HTTP::get( 'uids', '' );
303 $inhouse = HTTP::get( 'inhouse', '0' );
304
305 if ( '1' === $inhouse && ! is_user_logged_in() ) {
306 auth_redirect();
307 }
308 if ( ! is_string( $hash ) || '' === trim( $hash ) ) {
309 wp_die( esc_html__( 'You shall not pass.', 'defender-security' ) );
310 }
311
312 $slugs = $this->recipient_verification->parse_subscription_slugs( $slug, $uids );
313 if ( array() === $slugs ) {
314 wp_die( esc_html__( 'You shall not pass.', 'defender-security' ) );
315 }
316
317 $is_bulk = is_string( $uids ) && '' !== trim( $uids );
318 $result = $this->recipient_verification->confirm_subscriptions( $slugs, $hash, $inhouse, $is_bulk );
319
320 if ( ! $result['found_module'] ) {
321 wp_die( esc_html__( 'You shall not pass.', 'defender-security' ) );
322 }
323
324 $this->redirect_after_verify( $inhouse, $result );
325 exit; // Required after wp_safe_redirect().
326 }
327
328 /**
329 * Redirect the user after subscription verification.
330 *
331 * @param string $inhouse Whether the recipient is in-house ('1' or '0').
332 * @param array $result Confirmation result from recipient service.
333 * @return void
334 */
335 private function redirect_after_verify( string $inhouse, array $result ): void {
336 if ( '1' === $inhouse && $result['processed'] ) {
337 wp_safe_redirect(
338 add_query_arg(
339 array(
340 'slug' => $result['redirect_slug'],
341 'context' => 'subscribed',
342 ),
343 get_edit_profile_url()
344 )
345 );
346 } elseif ( $result['processed'] ) {
347 wp_safe_redirect(
348 add_query_arg(
349 array(
350 'defender_subscription' => 'confirmed',
351 'slug' => $result['redirect_slug'],
352 'hash' => HTTP::get( 'hash', '' ),
353 ),
354 home_url()
355 )
356 );
357 } else {
358 wp_safe_redirect( home_url() );
359 }
360 }
361
362 /**
363 * Get recipient data for the frontend.
364 *
365 * @return array Frontend recipient data.
366 */
367 public function data_frontend(): array {
368 return array_merge(
369 $this->dump_routes_and_nonces(),
370 array(
371 'recipients' => $this->recipient_directory->get_all_recipients(),
372 'notifications' => $this->service->get_modules(),
373 )
374 );
375 }
376
377 /**
378 * Remove saved settings.
379 *
380 * @return void
381 */
382 public function remove_settings(): void {
383 }
384
385 /**
386 * Remove stored data.
387 *
388 * @return void
389 */
390 public function remove_data(): void {
391 }
392
393 /**
394 * Export controller data.
395 *
396 * @return array Exported controller data.
397 */
398 public function to_array(): array {
399 return array();
400 }
401
402 /**
403 * Import controller data.
404 *
405 * @param array $data Import data.
406 * @return void
407 */
408 public function import_data( array $data ): void {
409 }
410
411 /**
412 * Get translatable strings.
413 *
414 * @return array Translatable strings.
415 */
416 public function export_strings(): array {
417 return array();
418 }
419 }
420