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-notification.php

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

1,050 lines 29.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Handles notification operations.
4 *
5 * @package WP_Defender\Controller
6 */
7
8 namespace WP_Defender\Controller;
9
10 use Exception;
11 use WP_Defender\Event;
12 use Calotes\Helper\HTTP;
13 use WP_Defender\Traits\User;
14 use Calotes\Component\Request;
15 use WP_Defender\Traits\Formats;
16 use Calotes\Component\Response;
17 use WP_Defender\Component\Network_Cron_Manager;
18 use WP_Defender\Model\Notification\Audit_Report;
19 use WP_Defender\Model\Notification\Malware_Report;
20 use WP_Defender\Model\Notification\Tweak_Reminder;
21 use WP_Defender\Model\Notification\Firewall_Report;
22 use WP_Defender\Component\Config\Config_Hub_Helper;
23 use WP_Defender\Model\Notification as Model_Notification;
24
25 /**
26 * Methods for handling notifications.
27 */
28 class Notification extends Event {
29
30 use User;
31 use Formats;
32
33 /**
34 * Slug identifier for subscribe page.
35 *
36 * @var string
37 */
38 public const SLUG_SUBSCRIBE = 'defender_listen_user_subscribe';
39
40 /**
41 * Slug identifier for unsubscribe page.
42 *
43 * @var string
44 */
45 public const SLUG_UNSUBSCRIBE = 'defender_listen_user_unsubscribe';
46 /**
47 * The slug identifier for this controller.
48 *
49 * @var string
50 */
51 public $slug = 'wdf-notification';
52
53 /**
54 * Service for handling logic.
55 *
56 * @var \WP_Defender\Component\Notification
57 */
58 protected $service;
59
60 /**
61 * Initializes the model and service, registers routes, and sets up scheduled events if the model is active.
62 */
63 public function __construct() {
64 $this->register_routes();
65 $this->service = wd_di()->get( \WP_Defender\Component\Notification::class );
66 add_action( 'defender_enqueue_assets', array( $this, 'enqueue_assets' ) );
67 // We use custom ajax endpoint here as the nonce would fail with other user.
68 add_action( 'wp_ajax_' . self::SLUG_UNSUBSCRIBE, array( $this, 'unsubscribe_and_send_email' ) );
69 add_action( 'wp_ajax_nopriv_' . self::SLUG_UNSUBSCRIBE, array( $this, 'unsubscribe_and_send_email' ) );
70 add_action( 'defender_notify', array( $this, 'send_notify' ), 10, 2 );
71 add_action( 'admin_notices', array( $this, 'show_actions_with_subscription' ) );
72 add_action( 'wp_footer', array( $this, 'show_public_subscription_notice' ) );
73 }
74
75 /**
76 * For users who have subscribed or unsubscribed confirmation.
77 *
78 * @return void
79 */
80 public function show_actions_with_subscription() {
81 if ( ! defined( 'IS_PROFILE_PAGE' ) || false === constant( 'IS_PROFILE_PAGE' ) ) {
82 return;
83 }
84 $slug = defender_get_data_from_request( 'slug', 'g' );
85 if ( ! is_string( $slug ) || '' === trim( $slug ) ) {
86 return;
87 }
88 $m = $this->service->find_module_by_slug( $slug );
89 if ( ! is_object( $m ) ) {
90 return;
91 }
92 $context = defender_get_data_from_request( 'context', 'g' );
93 if ( 'subscribed' === $context ) {
94 $unsubscribe_link = $this->service->create_unsubscribe_url( $m->slug, $this->get_current_user_email() );
95 }
96
97 $strings = $this->get_subscription_notice_message( $m, $context, $unsubscribe_link ?? '' );
98 if ( '' === $strings ) {
99 return;
100 }
101 ?>
102 <div class="notice notice-success" style="position:relative;">
103 <p><?php echo wp_kses_post( $strings ); ?></p>
104 <a href="<?php echo esc_url_raw( get_edit_profile_url() ); ?>" class="notice-dismiss"
105 style="text-decoration: none">
106 <span class="screen-reader-text"><?php esc_attr_e( 'Dismiss this notice.', 'defender-security' ); ?></span>
107 </a>
108 </div>
109 <?php
110 }
111
112 /**
113 * Show public confirmation notice for out-of-house recipients.
114 *
115 * @return void
116 */
117 public function show_public_subscription_notice(): void {
118 $context = defender_get_data_from_request( 'defender_subscription', 'g' );
119 if ( 'confirmed' !== $context && 'unsubscribe' !== $context ) {
120 return;
121 }
122
123 $slug = defender_get_data_from_request( 'slug', 'g' );
124 $hash = defender_get_data_from_request( 'hash', 'g' );
125 if ( ! is_string( $slug ) || '' === trim( $slug ) ) {
126 return;
127 }
128 if ( 'confirmed' === $context && ( ! is_string( $hash ) || '' === trim( $hash ) ) ) {
129 return;
130 }
131
132 $m = $this->service->find_module_by_slug( sanitize_key( $slug ) );
133 if ( ! is_object( $m ) ) {
134 return;
135 }
136
137 $unsubscribe_link = '';
138 if ( 'confirmed' === $context ) {
139 $unsubscribe_link = add_query_arg(
140 array(
141 'action' => self::SLUG_UNSUBSCRIBE,
142 'hash' => sanitize_text_field( $hash ),
143 'slug' => $m->slug,
144 ),
145 admin_url( 'admin-ajax.php' )
146 );
147 }
148
149 $message = $this->get_subscription_notice_message(
150 $m,
151 'confirmed' === $context ? 'subscribed' : 'unsubscribe',
152 $unsubscribe_link
153 );
154 if ( '' === $message ) {
155 return;
156 }
157 ?>
158 <div class="wpdef-public-subscription-notice" style="position:fixed;right:20px;bottom:20px;z-index:99999;max-width:420px;padding:14px 18px;border-left:4px solid #00a32a;background:#fff;box-shadow:0 2px 12px rgba(0,0,0,.18);font:14px/1.5 -apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;color:#1d2327;">
159 <?php echo wp_kses_post( $message ); ?>
160 </div>
161 <?php
162 }
163
164 /**
165 * Build subscription notice message.
166 *
167 * @param object $module Notification module.
168 * @param string $context Notice context.
169 * @param string $unsubscribe_link Unsubscribe URL.
170 * @return string
171 */
172 private function get_subscription_notice_message( object $module, string $context, string $unsubscribe_link = '' ): string {
173 if ( 'subscribed' === $context ) {
174 if ( '' === trim( $unsubscribe_link ) ) {
175 return '';
176 }
177
178 return sprintf(
179 /* translators: 1. Module title. 2. Unsubscribe link. */
180 esc_html__(
181 'You are now subscribed to receive %1$s. Made a mistake? %2$s',
182 'defender-security'
183 ),
184 '<strong>' . esc_html( $module->title ) . '</strong>',
185 '<a href="' . esc_url( $unsubscribe_link ) . '" style="text-decoration: none">' . esc_html__( 'Unsubscribe', 'defender-security' ) . '</a>'
186 );
187 }
188
189 if ( 'unsubscribe' === $context ) {
190 return sprintf(
191 /* translators: %s: Module title. */
192 esc_html__( 'You are now unsubscribed from %s.', 'defender-security' ),
193 '<strong>' . esc_html( $module->title ) . '</strong>'
194 );
195 }
196
197 return '';
198 }
199
200 /**
201 * Renders the main view for this page.
202 */
203 public function main_view() {
204 $this->render( 'main' );
205 }
206
207 /**
208 * Dispatch notification.
209 *
210 * @param string $slug Module slug to identify the notification handler.
211 * @param object $args The arguments to pass to the notification.
212 */
213 public function send_notify( $slug, $args ) {
214 $this->service->dispatch_notification( $slug, $args );
215 }
216
217 /**
218 * Validates an email address provided in the request data.
219 *
220 * @param Request $request The request object .The request object containing the data to validate.
221 *
222 * @return Response The response object indicating the validation result.
223 * @defender_route
224 */
225 public function validate_email( Request $request ): Response {
226 $data = $request->get_data(
227 array(
228 'email' => array(
229 'type' => 'string',
230 'sanitize' => 'sanitize_text_field',
231 ),
232 )
233 );
234 $email = $data['email'] ?? false;
235 if ( filter_var( $email, FILTER_VALIDATE_EMAIL ) ) {
236 return new Response(
237 true,
238 array(
239 'error' => false,
240 'avatar' => get_avatar_url( $data['email'] ),
241 )
242 );
243 } else {
244 return new Response( false, array( 'error' => esc_html__( 'Invalid email address.', 'defender-security' ) ) );
245 }
246 }
247
248 /**
249 * Updates a WordPress user profile for an in-house recipient.
250 *
251 * @param Request $request The request object.
252 *
253 * @return Response The response object.
254 * @defender_route
255 */
256 public function update_user_profile( Request $request ): Response {
257 $data = $request->get_data(
258 array(
259 'id' => array(
260 'type' => 'int',
261 'sanitize' => 'sanitize_text_field',
262 ),
263 'first_name' => array(
264 'type' => 'string',
265 'sanitize' => 'sanitize_text_field',
266 ),
267 'last_name' => array(
268 'type' => 'string',
269 'sanitize' => 'sanitize_text_field',
270 ),
271 'display_name' => array(
272 'type' => 'string',
273 'sanitize' => 'sanitize_text_field',
274 ),
275 )
276 );
277
278 $user_id = absint( $data['id'] ?? 0 );
279 $display_name = trim( (string) ( $data['display_name'] ?? '' ) );
280
281 if ( 0 === $user_id || '' === $display_name ) {
282 return new Response( false, array( 'message' => esc_html__( 'Invalid user data.', 'defender-security' ) ) );
283 }
284
285 if ( ! current_user_can( 'edit_user', $user_id ) ) {
286 return new Response( false, array( 'message' => esc_html__( 'You are not allowed to edit this user.', 'defender-security' ) ) );
287 }
288
289 if ( ! get_user_by( 'id', $user_id ) ) {
290 return new Response( false, array( 'message' => esc_html__( 'User not found.', 'defender-security' ) ) );
291 }
292
293 $result = wp_update_user(
294 array(
295 'ID' => $user_id,
296 'first_name' => trim( (string) ( $data['first_name'] ?? '' ) ),
297 'last_name' => trim( (string) ( $data['last_name'] ?? '' ) ),
298 'display_name' => $display_name,
299 )
300 );
301
302 if ( is_wp_error( $result ) ) {
303 return new Response( false, array( 'message' => $result->get_error_message() ) );
304 }
305
306 return new Response(
307 true,
308 array(
309 'id' => $user_id,
310 'first_name' => trim( (string) ( $data['first_name'] ?? '' ) ),
311 'last_name' => trim( (string) ( $data['last_name'] ?? '' ) ),
312 'display_name' => $display_name,
313 )
314 );
315 }
316
317 /**
318 * Unsubscribe process.
319 */
320 public function unsubscribe_and_send_email() {
321 $slug = HTTP::get( 'slug', '' );
322 $hash = HTTP::get( 'hash', '' );
323 $slug = sanitize_text_field( $slug );
324 if ( ! is_string( $slug ) || '' === trim( $slug ) || ! is_string( $hash ) || '' === trim( $hash ) ) {
325 wp_die( esc_html__( 'You shall not pass.', 'defender-security' ) );
326 }
327 $m = $this->service->find_module_by_slug( $slug );
328 if ( ! is_object( $m ) ) {
329 wp_die( esc_html__( 'You shall not pass.', 'defender-security' ) );
330 }
331 $inhouse = false;
332 $processed = false;
333 foreach ( $m->in_house_recipients as &$recipient ) {
334 $email = $recipient['email'];
335 if ( hash_equals( $hash, hash( 'sha256', $email . AUTH_SALT ) ) ) {
336 // We skip even an un-logged user, because the admin can change the user's access without notice.
337 if ( is_user_logged_in() ) {
338 if ( $email !== $this->get_current_user_email() ) {
339 wp_die( esc_html__( 'Invalid request.', 'defender-security' ) );
340 }
341 $inhouse = true;
342 }
343 $recipient['status'] = Model_Notification::USER_SUBSCRIBE_CANCELED;
344 $m->save();
345 $processed = true;
346 // Send email.
347 $this->service->send_unsubscribe_email( $m, $email, $inhouse, $recipient['name'] );
348 break;
349 }
350 }
351
352 if ( false === $inhouse ) {
353 // No match on in-house, check the outhouse list.
354 foreach ( $m->out_house_recipients as &$recipient ) {
355 $email = $recipient['email'];
356 if ( hash_equals( $hash, hash( 'sha256', $email . AUTH_SALT ) ) ) {
357 $recipient['status'] = Model_Notification::USER_SUBSCRIBE_CANCELED;
358 $m->save();
359 $processed = true;
360 $this->service->send_unsubscribe_email( $m, $email, $inhouse, $recipient['name'] );
361 }
362 }
363 }
364 if ( $inhouse ) {
365 wp_safe_redirect(
366 add_query_arg(
367 array(
368 'slug' => $slug,
369 'context' => 'unsubscribe',
370 ),
371 get_edit_profile_url()
372 )
373 );
374 } elseif ( $processed ) {
375 wp_safe_redirect(
376 add_query_arg(
377 array(
378 'defender_subscription' => 'unsubscribe',
379 'slug' => $slug,
380 ),
381 get_home_url()
382 )
383 );
384 } else {
385 wp_safe_redirect( get_home_url() );
386 }
387 exit;
388 }
389
390 /**
391 * An endpoint for saving single config from frontend.
392 *
393 * @param Request $request The request object .The request object containing the data to save.
394 *
395 * @defender_route
396 * @return Response
397 * @throws Exception Emits Exception in case of an error.
398 */
399 public function save( Request $request ): Response {
400 $raw_data = $request->get_data();
401 if ( ! isset( $raw_data['slug'] ) || ! is_string( $raw_data['slug'] ) || '' === trim( $raw_data['slug'] ) ) {
402 return new Response( false, array( 'message' => esc_html__( 'Invalid data.', 'defender-security' ) ) );
403 }
404 $slug = sanitize_textarea_field( $raw_data['slug'] );
405 $model = $this->service->find_module_by_slug( $slug );
406
407 if ( ! is_object( $model ) ) {
408 return new Response( false, array( 'message' => esc_html__( 'Invalid data.', 'defender-security' ) ) );
409 }
410 $data = $request->get_data_by_model( $model );
411 // Check config-values.
412 $data['configs'] = $model->type_casting( $data['configs'] );
413
414 $model->import( $data );
415 // Ensure the model is activated. The save route is called when the user explicitly
416 // saves settings, so the notification must be active after saving. If the imported
417 // data already carries an active status this is a no-op; otherwise we activate it.
418 if ( \WP_Defender\Model\Notification::STATUS_ACTIVE !== $model->status ) {
419 $model->status = \WP_Defender\Model\Notification::STATUS_ACTIVE;
420 }
421 if ( $model->validate() ) {
422 if ( 0 === $model->last_sent ) {
423 // This means that the notification or report never sent, we will use the moment that it get activate.
424 $model->last_sent = time();
425 }
426 $model->save();
427 $this->service->send_subscription_confirm_email( $model );
428 Config_Hub_Helper::set_clear_active_flag();
429 // Track.
430 if ( $this->is_tracking_active() ) {
431 $track_data = array( 'Notification type' => $raw_data['title'] );
432 // For reports. Separated check for 'Security Recommendations - Notification'.
433 if ( 'report' === $raw_data['type'] ) {
434 $track_data['Notification schedule'] = ucfirst( $data['frequency'] );
435 } elseif ( 'tweak-reminder' === $raw_data['slug'] ) {
436 $track_data['Notification schedule'] = ucfirst( $data['configs']['reminder'] );
437 }
438 $this->track_feature( 'def_notification_activated', $track_data );
439 }
440
441 return new Response(
442 true,
443 array_merge(
444 array(
445 'message' => esc_html__(
446 'You have activated the notification successfully. Note, recipients will need to confirm their subscriptions to begin receiving notifications.',
447 'defender-security'
448 ),
449 ),
450 $this->data_frontend()
451 )
452 );
453 }
454
455 return new Response( false, array( 'message' => $model->get_formatted_errors() ) );
456 }
457
458 /**
459 * Bulk update and save changes.
460 *
461 * @param Request $request The request object.
462 *
463 * @defender_route
464 * @return Response
465 */
466 public function save_bulk( Request $request ): Response {
467 $data = $request->get_data(
468 array(
469 'items' => array(
470 'type' => 'array',
471 'sanitize' => 'sanitize_textarea_field',
472 ),
473 )
474 );
475 $this->save_items( $data['items'] );
476 Config_Hub_Helper::set_clear_active_flag();
477
478 return new Response(
479 true,
480 array_merge(
481 $this->data_frontend(),
482 array(
483 'message' => esc_html__(
484 'Your settings have been updated successfully. Any new recipients will receive an email to confirm their subscription.',
485 'defender-security'
486 ),
487 )
488 )
489 );
490 }
491
492 /**
493 * Sync the shared reports schedule to notification models that persist their own next-run timestamp.
494 *
495 * @param Request $request The request object.
496 *
497 * @defender_route
498 * @return Response
499 */
500 public function sync_report_schedule( Request $request ): Response {
501 $data = $request->get_data(
502 array(
503 'frequency' => array(
504 'type' => 'string',
505 'sanitize' => 'sanitize_text_field',
506 ),
507 'day' => array(
508 'type' => 'string',
509 'sanitize' => 'sanitize_text_field',
510 ),
511 'day_n' => array(
512 'type' => 'int',
513 'sanitize' => 'sanitize_text_field',
514 ),
515 'time' => array(
516 'type' => 'string',
517 'sanitize' => 'sanitize_text_field',
518 ),
519 )
520 );
521
522 $report_classes = array(
523 Firewall_Report::class,
524 Audit_Report::class,
525 Malware_Report::class,
526 Tweak_Reminder::class,
527 );
528
529 foreach ( $report_classes as $report_class ) {
530 $model = wd_di()->get( $report_class );
531 $model->frequency = $data['frequency'];
532 $model->day = $data['day'];
533 $model->day_n = (int) $data['day_n'];
534 $model->time = $data['time'];
535
536 $model->save();
537 }
538
539 Config_Hub_Helper::set_clear_active_flag();
540
541 return new Response( true, $this->data_frontend() );
542 }
543
544 /**
545 * Process bulk saving of reports or notifications.
546 *
547 * @param array $data Data to save.
548 *
549 * @throws Exception Emits Exception in case of an error.
550 */
551 private function save_items( array $data ): void {
552 $in_house_recipients = $data['in_house_recipients'] ?? array();
553 $out_house_recipients = $data['out_house_recipients'] ?? array();
554
555 foreach ( $data as $datum ) {
556 if ( ! is_array( $datum ) || ! isset( $datum['slug'] ) ) {
557 continue;
558 }
559
560 $slug = $datum['slug'];
561 $model = $this->service->find_module_by_slug( $slug );
562 if ( ! is_object( $model ) ) {
563 continue;
564 }
565
566 $import = array(
567 'status' => $model->status,
568 'configs' => $model->type_casting( $datum ),
569 'in_house_recipients' => $datum['in_house_recipients'] ?? $in_house_recipients,
570 'out_house_recipients' => $datum['out_house_recipients'] ?? $out_house_recipients,
571 );
572 // since 2.7.0.
573 if ( isset( $datum['is_report'] ) && (bool) $datum['is_report'] && Malware_Report::SLUG !== $slug ) {
574 $import['frequency'] = $datum['frequency'] ?? $model->frequency;
575 $import['day_n'] = (int) ( $datum['day_n'] ?? $model->day_n );
576 $import['day'] = $datum['day'] ?? $model->day;
577 $import['time'] = $datum['time'] ?? $model->time;
578 }
579 foreach ( $import['out_house_recipients'] as $key => $val ) {
580 if ( ! filter_var( $val['email'], FILTER_VALIDATE_EMAIL ) ) {
581 unset( $import['out_house_recipients'][ $key ] );
582 }
583 }
584 $prev_in_house_emails = array_column( $model->in_house_recipients, null, 'email' );
585 $prev_out_house_emails = array_column( $model->out_house_recipients, null, 'email' );
586
587 $model->import( $import );
588 if ( $model->validate() ) {
589 if ( 0 === $model->last_sent ) {
590 $model->last_sent = time();
591 }
592 $model->save();
593
594 $new_in_house_emails = array_column( $import['in_house_recipients'], 'email' );
595 $new_out_house_emails = array_column( $import['out_house_recipients'], 'email' );
596 foreach ( $prev_in_house_emails as $email => $recipient ) {
597 if ( ! in_array( $email, $new_in_house_emails, true ) ) {
598 $this->service->send_unsubscribe_email( $model, $email, true, $recipient['name'] ?? '' );
599 }
600 }
601 foreach ( $prev_out_house_emails as $email => $recipient ) {
602 if ( ! in_array( $email, $new_out_house_emails, true ) ) {
603 $this->service->send_unsubscribe_email( $model, $email, false, $recipient['name'] ?? '' );
604 }
605 }
606 // Track.
607 if ( $this->is_tracking_active() ) {
608 $track_data = array( 'Notification type' => $model->title );
609 if ( isset( $datum['is_report'] ) && (bool) $datum['is_report'] ) {
610 $track_data['Notification schedule'] = 'tweak-reminder' === $slug
611 ? ucfirst( $datum['reminder'] ?? '' )
612 : ucfirst( $datum['frequency'] ?? '' );
613 }
614 $this->track_feature( 'def_notification_activated', $track_data );
615 }
616 }
617 }
618 }
619
620 /**
621 * Bulk activate.
622 *
623 * @param Request $request The request object.
624 *
625 * @defender_route
626 * @return Response
627 * @throws Exception Emits Exception in case of an error.
628 */
629 public function bulk_activate( Request $request ): Response {
630 $data = $request->get_data(
631 array(
632 'slugs' => array(
633 'type' => 'array',
634 'sanitize' => 'sanitize_text_field',
635 ),
636 )
637 );
638 $slugs = $data['slugs'];
639 if ( ! is_array( $slugs ) || array() === $slugs ) {
640 return new Response( false, array() );
641 }
642
643 foreach ( $slugs as $slug ) {
644 $model = $this->service->find_module_by_slug( $slug );
645 if ( is_object( $model ) ) {
646 $model->status = Model_Notification::STATUS_ACTIVE;
647 if ( 0 === $model->last_sent ) {
648 // This means that the notification or report never sent, we will use the moment that it get activate.
649 $model->last_sent = time();
650 }
651 $model->save();
652 }
653 }
654
655 return new Response(
656 true,
657 array_merge(
658 array(
659 'message' => 'You have activated the notification successfully. Note, recipients will need to confirm their subscriptions to begin receiving notifications.',
660 ),
661 $this->data_frontend()
662 )
663 );
664 }
665
666 /**
667 * Bulk deactivate.
668 *
669 * @param Request $request The request object.
670 *
671 * @defender_route
672 * @return Response
673 */
674 public function bulk_deactivate( Request $request ): Response {
675 $data = $request->get_data(
676 array(
677 'slugs' => array(
678 'type' => 'array',
679 'sanitize' => 'sanitize_text_field',
680 ),
681 )
682 );
683 $slugs = $data['slugs'];
684 if ( ! is_array( $slugs ) || array() === $slugs ) {
685 return new Response( false, array() );
686 }
687
688 foreach ( $slugs as $slug ) {
689 $model = $this->service->find_module_by_slug( $slug );
690 if ( is_object( $model ) ) {
691 $model->status = Model_Notification::STATUS_DISABLED;
692 $model->save();
693 }
694 }
695
696 return new Response(
697 true,
698 array_merge(
699 array( 'message' => esc_html__( 'You have deactivated the notifications successfully.', 'defender-security' ) ),
700 $this->data_frontend()
701 )
702 );
703 }
704
705 /**
706 * Disable a notification module.
707 *
708 * @param Request $request The request object .The request object containing the data to disable the module.
709 *
710 * @defender_route
711 * @return Response The response object indicating the success or failure of the operation.
712 */
713 public function disable( Request $request ): Response {
714 $data = $request->get_data(
715 array(
716 'slug' => array(
717 'type' => 'string',
718 'sanitize' => 'sanitize_text_field',
719 ),
720 )
721 );
722
723 $slug = $data['slug'];
724 $model = $this->service->find_module_by_slug( $slug );
725 if ( ! is_object( $model ) ) {
726 return new Response( false, array( 'message' => esc_html__( 'Invalid data.', 'defender-security' ) ) );
727 }
728
729 $model->status = Model_Notification::STATUS_DISABLED;
730 $model->save();
731
732 return new Response(
733 true,
734 array_merge(
735 $this->data_frontend(),
736 array(
737 'message' => esc_html__( 'You have deactivated the notification successfully.', 'defender-security' ),
738 )
739 )
740 );
741 }
742
743 /**
744 * Enqueues scripts and styles for this page.
745 * Only enqueues assets if the page is active.
746 */
747 public function enqueue_assets() {
748 if ( ! $this->is_page_active() ) {
749 return;
750 }
751 }
752
753 /**
754 * An endpoint for fetching users pool.
755 *
756 * @param Request $request The request object .Request data.
757 *
758 * @defender_route
759 */
760 public function get_users( Request $request ) {
761 $data = $request->get_data(
762 array(
763 'paged' => array(
764 'type' => 'int',
765 'sanitize' => 'sanitize_text_field',
766 ),
767 'search' => array(
768 'type' => 'string',
769 'sanitize' => 'sanitize_text_field',
770 ),
771 'exclude' => array(
772 'type' => 'array',
773 ),
774 'module' => array(
775 'type' => 'string',
776 'sanitize' => 'sanitize_text_field',
777 ),
778 'user_role_filter' => array(
779 'type' => 'string',
780 'sanitize' => 'sanitize_text_field',
781 ),
782 'user_sort' => array(
783 'type' => 'string',
784 'sanitize' => 'sanitize_text_field',
785 ),
786 )
787 );
788 $paged = 1;
789 $exclude = $data['exclude'] ?? array();
790 $username = $data['search'] ?? '';
791 $slug = $data['module'] ?? null;
792 $role = '';
793
794 if (
795 isset( $data['user_role_filter'] ) &&
796 'all' !== $data['user_role_filter']
797 ) {
798 $role = $data['user_role_filter'];
799 }
800
801 $order_by = 'ID';
802 $order = 'DESC';
803 if ( isset( $data['user_sort'] ) ) {
804 switch ( $data['user_sort'] ) {
805 case 'recent':
806 $order_by = 'registered';
807 $order = 'DESC';
808 break;
809 case 'alpha_asc':
810 $order_by = 'display_name';
811 $order = 'ASC';
812 break;
813 case 'alpha_desc':
814 default:
815 $order_by = 'display_name';
816 $order = 'DESC';
817 break;
818 }
819 }
820
821 if ( strlen( $username ) ) {
822 $username = "*$username*";
823 }
824
825 $users = $this->service->get_users_pool(
826 $exclude,
827 $role,
828 $username,
829 $order_by,
830 $order,
831 10,
832 $paged
833 );
834
835 if ( ! is_null( $slug ) ) {
836 $notification = $this->service->find_module_by_slug( $slug );
837 if ( is_object( $notification ) ) {
838 foreach ( $notification->in_house_recipients as $recipient ) {
839 foreach ( $users as &$user ) {
840 if ( $user['email'] === $recipient['email'] ) {
841 $user['status'] = $recipient['status'];
842 }
843 }
844 }
845 }
846 }
847
848 wp_send_json_success( $users );
849 }
850
851 /**
852 * Removes settings for all submodules.
853 */
854 public function remove_settings() {
855 foreach ( $this->service->get_modules_as_objects() as $module ) {
856 $module->delete();
857 }
858 }
859
860 /**
861 * Delete all the data & the cache.
862 */
863 public function remove_data() {
864 }
865
866 /**
867 * Converts the current object state to an array.
868 *
869 * @return array The array representation of the object.
870 */
871 public function to_array(): array {
872 return array();
873 }
874
875 /**
876 * Provides data for the frontend.
877 *
878 * @return array An array of data for the frontend.
879 */
880 public function data_frontend(): array {
881 return array_merge(
882 array(
883 'alert_model' => $this->service->get_alert_model(),
884 'reports_model' => $this->service->get_reports_model(),
885 'active_count' => $this->service->count_active(),
886 'hub_connector' => wd_di()->get( Hub_Connector::class )->data_frontend(),
887 'antibot' => wd_di()->get( Antibot_Global_Firewall::class )->data_frontend(),
888 ),
889 $this->dump_routes_and_nonces()
890 );
891 }
892
893 /**
894 * Imports data into the model.
895 *
896 * @param array $data Data to be imported into the model.
897 */
898 public function import_data( array $data ) {
899 }
900
901 /**
902 * Exports strings.
903 *
904 * @return array An array of strings.
905 */
906 public function export_strings(): array {
907 $modules = wd_di()->get( self::class )->service->get_modules_as_objects();
908 $strings = array();
909 foreach ( $modules as $module ) {
910 /* translators: %s - module title, %s - module status */
911 $string = esc_html__( '%1$s: %2$s', 'defender-security' );
912 if ( 'notification' === $module->type ) {
913 $string = sprintf(
914 $string,
915 $module->title,
916 Model_Notification::STATUS_ACTIVE === $module->status ? esc_html__(
917 'Enabled',
918 'defender-security'
919 ) : esc_html__( 'Disabled', 'defender-security' )
920 );
921 } else {
922 $string = sprintf(
923 $string,
924 $module->title,
925 Model_Notification::STATUS_ACTIVE === $module->status ? $module->to_string() : esc_html__(
926 'Disabled',
927 'defender-security'
928 )
929 );
930 }
931 $strings[] = $string;
932 }
933
934 return $strings;
935 }
936
937 /**
938 * Resend invite email.
939 *
940 * @param Request $request The request object.
941 *
942 * @defender_route
943 * @return Response
944 */
945 public function resend_invite_email( Request $request ): Response {
946 $data = $request->get_data(
947 array(
948 'slug' => array(
949 'type' => 'string',
950 'sanitize' => 'sanitize_textarea_field',
951 ),
952 'email' => array(
953 'type' => 'string',
954 'sanitize' => 'sanitize_text_field',
955 ),
956 'id' => array(
957 'type' => 'integer',
958 ),
959 'name' => array(
960 'type' => 'string',
961 'sanitize' => 'sanitize_text_field',
962 ),
963 )
964 );
965
966 $model = $this->service->find_module_by_slug( $data['slug'] );
967
968 if ( ! is_object( $model ) ) {
969 return new Response( false, array( 'message' => esc_html__( 'Module not found.', 'defender-security' ) ) );
970 }
971
972 $subscriber = array(
973 'email' => $data['email'],
974 'name' => $data['name'],
975 );
976
977 if ( isset( $data['id'] ) && is_int( $data['id'] ) && 0 < $data['id'] ) {
978 $subscriber['id'] = $data['id'];
979 }
980 // Resend invite email now.
981 $sent = $this->service->send_email( $subscriber, $model->export() );
982
983 if ( $sent ) {
984 return new Response( true, array( 'message' => esc_html__( 'Invitation sent successfully.', 'defender-security' ) ) );
985 }
986
987 return new Response(
988 false,
989 array(
990 'message' => esc_html__( 'Sorry! We could not send the invitation, Please try again later.', 'defender-security' ),
991 )
992 );
993 }
994
995 /**
996 * Get user roles with count.
997 *
998 * @defender_route
999 */
1000 public function get_user_roles() {
1001 $user_roles = $this->service->get_user_roles();
1002
1003 wp_send_json_success( $user_roles );
1004 }
1005
1006 /**
1007 * Sends a single invite email listing all notification modules the recipient was added to.
1008 *
1009 * @param Request $request Request with 'email' and 'name'.
1010 * @defender_route
1011 * @return Response
1012 */
1013 public function send_invite_all_email( Request $request ): Response {
1014 $data = $request->get_data(
1015 array(
1016 'email' => array(
1017 'type' => 'string',
1018 'sanitize' => 'sanitize_email',
1019 ),
1020 'name' => array(
1021 'type' => 'string',
1022 'sanitize' => 'sanitize_text_field',
1023 ),
1024 'id' => array( 'type' => 'integer' ),
1025 )
1026 );
1027 $email = $data['email'] ?? '';
1028 $name = $data['name'] ?? '';
1029 $subscriber = array(
1030 'email' => $email,
1031 'name' => $name,
1032 );
1033 if ( isset( $data['id'] ) && is_int( $data['id'] ) && 0 < $data['id'] ) {
1034 $subscriber['id'] = $data['id'];
1035 }
1036 $active_slugs = array_column( $this->service->get_modules(), 'slug' );
1037 $modules = array_values(
1038 array_filter(
1039 $this->service->get_modules_as_objects(),
1040 static function ( $module ) use ( $active_slugs ) {
1041 return in_array( $module->slug, $active_slugs, true );
1042 }
1043 )
1044 );
1045 $sent = $this->service->send_invite_all_email( $subscriber, $modules );
1046
1047 return new Response( $sent, array() );
1048 }
1049 }
1050