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 / controller / class-notification.php

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

1,052 lines 29.1 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 // todo: add changes when the design is ready.
752 }
753
754 /**
755 * An endpoint for fetching users pool.
756 *
757 * @param Request $request The request object .Request data.
758 *
759 * @defender_route
760 */
761 public function get_users( Request $request ) {
762 $data = $request->get_data(
763 array(
764 'paged' => array(
765 'type' => 'int',
766 'sanitize' => 'sanitize_text_field',
767 ),
768 'search' => array(
769 'type' => 'string',
770 'sanitize' => 'sanitize_text_field',
771 ),
772 'exclude' => array(
773 'type' => 'array',
774 ),
775 'module' => array(
776 'type' => 'string',
777 'sanitize' => 'sanitize_text_field',
778 ),
779 'user_role_filter' => array(
780 'type' => 'string',
781 'sanitize' => 'sanitize_text_field',
782 ),
783 'user_sort' => array(
784 'type' => 'string',
785 'sanitize' => 'sanitize_text_field',
786 ),
787 )
788 );
789 $paged = 1;
790 $exclude = $data['exclude'] ?? array();
791 $username = $data['search'] ?? '';
792 $slug = $data['module'] ?? null;
793 $role = '';
794
795 if (
796 isset( $data['user_role_filter'] ) &&
797 'all' !== $data['user_role_filter']
798 ) {
799 $role = $data['user_role_filter'];
800 }
801
802 $order_by = 'ID';
803 $order = 'DESC';
804 if ( isset( $data['user_sort'] ) ) {
805 switch ( $data['user_sort'] ) {
806 case 'recent':
807 $order_by = 'registered';
808 $order = 'DESC';
809 break;
810 case 'alpha_asc':
811 $order_by = 'display_name';
812 $order = 'ASC';
813 break;
814 case 'alpha_desc':
815 default:
816 $order_by = 'display_name';
817 $order = 'DESC';
818 break;
819 }
820 }
821
822 if ( strlen( $username ) ) {
823 $username = "*$username*";
824 }
825
826 $users = $this->service->get_users_pool(
827 $exclude,
828 $role,
829 $username,
830 $order_by,
831 $order,
832 10,
833 $paged
834 );
835
836 if ( ! is_null( $slug ) ) {
837 $notification = $this->service->find_module_by_slug( $slug );
838 if ( is_object( $notification ) ) {
839 foreach ( $notification->in_house_recipients as $recipient ) {
840 foreach ( $users as &$user ) {
841 if ( $user['email'] === $recipient['email'] ) {
842 $user['status'] = $recipient['status'];
843 }
844 }
845 }
846 }
847 }
848
849 wp_send_json_success( $users );
850 }
851
852 /**
853 * Removes settings for all submodules.
854 */
855 public function remove_settings() {
856 foreach ( $this->service->get_modules_as_objects() as $module ) {
857 $module->delete();
858 }
859 }
860
861 /**
862 * Delete all the data & the cache.
863 */
864 public function remove_data() {
865 }
866
867 /**
868 * Converts the current object state to an array.
869 *
870 * @return array The array representation of the object.
871 */
872 public function to_array(): array {
873 return array();
874 }
875
876 /**
877 * Todo: add changes when the design is ready.
878 * Provides data for the frontend.
879 *
880 * @return array An array of data for the frontend.
881 */
882 public function data_frontend(): array {
883 return array_merge(
884 array(
885 'alert_model' => $this->service->get_alert_model(),
886 'reports_model' => $this->service->get_reports_model(),
887 'active_count' => $this->service->count_active(),
888 'hub_connector' => wd_di()->get( Hub_Connector::class )->data_frontend(),
889 'antibot' => wd_di()->get( Antibot_Global_Firewall::class )->data_frontend(),
890 ),
891 $this->dump_routes_and_nonces()
892 );
893 }
894
895 /**
896 * Imports data into the model.
897 *
898 * @param array $data Data to be imported into the model.
899 */
900 public function import_data( array $data ) {
901 }
902
903 /**
904 * Exports strings.
905 *
906 * @return array An array of strings.
907 */
908 public function export_strings(): array {
909 $modules = wd_di()->get( self::class )->service->get_modules_as_objects();
910 $strings = array();
911 foreach ( $modules as $module ) {
912 /* translators: %s - module title, %s - module status */
913 $string = esc_html__( '%1$s: %2$s', 'defender-security' );
914 if ( 'notification' === $module->type ) {
915 $string = sprintf(
916 $string,
917 $module->title,
918 Model_Notification::STATUS_ACTIVE === $module->status ? esc_html__(
919 'Enabled',
920 'defender-security'
921 ) : esc_html__( 'Disabled', 'defender-security' )
922 );
923 } else {
924 $string = sprintf(
925 $string,
926 $module->title,
927 Model_Notification::STATUS_ACTIVE === $module->status ? $module->to_string() : esc_html__(
928 'Disabled',
929 'defender-security'
930 )
931 );
932 }
933 $strings[] = $string;
934 }
935
936 return $strings;
937 }
938
939 /**
940 * Resend invite email.
941 *
942 * @param Request $request The request object.
943 *
944 * @defender_route
945 * @return Response
946 */
947 public function resend_invite_email( Request $request ): Response {
948 $data = $request->get_data(
949 array(
950 'slug' => array(
951 'type' => 'string',
952 'sanitize' => 'sanitize_textarea_field',
953 ),
954 'email' => array(
955 'type' => 'string',
956 'sanitize' => 'sanitize_text_field',
957 ),
958 'id' => array(
959 'type' => 'integer',
960 ),
961 'name' => array(
962 'type' => 'string',
963 'sanitize' => 'sanitize_text_field',
964 ),
965 )
966 );
967
968 $model = $this->service->find_module_by_slug( $data['slug'] );
969
970 if ( ! is_object( $model ) ) {
971 return new Response( false, array( 'message' => esc_html__( 'Module not found.', 'defender-security' ) ) );
972 }
973
974 $subscriber = array(
975 'email' => $data['email'],
976 'name' => $data['name'],
977 );
978
979 if ( isset( $data['id'] ) && is_int( $data['id'] ) && 0 < $data['id'] ) {
980 $subscriber['id'] = $data['id'];
981 }
982 // Resend invite email now.
983 $sent = $this->service->send_email( $subscriber, $model->export() );
984
985 if ( $sent ) {
986 return new Response( true, array( 'message' => esc_html__( 'Invitation sent successfully.', 'defender-security' ) ) );
987 }
988
989 return new Response(
990 false,
991 array(
992 'message' => esc_html__( 'Sorry! We could not send the invitation, Please try again later.', 'defender-security' ),
993 )
994 );
995 }
996
997 /**
998 * Get user roles with count.
999 *
1000 * @defender_route
1001 */
1002 public function get_user_roles() {
1003 $user_roles = $this->service->get_user_roles();
1004
1005 wp_send_json_success( $user_roles );
1006 }
1007
1008 /**
1009 * Sends a single invite email listing all notification modules the recipient was added to.
1010 *
1011 * @param Request $request Request with 'email' and 'name'.
1012 * @defender_route
1013 * @return Response
1014 */
1015 public function send_invite_all_email( Request $request ): Response {
1016 $data = $request->get_data(
1017 array(
1018 'email' => array(
1019 'type' => 'string',
1020 'sanitize' => 'sanitize_email',
1021 ),
1022 'name' => array(
1023 'type' => 'string',
1024 'sanitize' => 'sanitize_text_field',
1025 ),
1026 'id' => array( 'type' => 'integer' ),
1027 )
1028 );
1029 $email = $data['email'] ?? '';
1030 $name = $data['name'] ?? '';
1031 $subscriber = array(
1032 'email' => $email,
1033 'name' => $name,
1034 );
1035 if ( isset( $data['id'] ) && is_int( $data['id'] ) && 0 < $data['id'] ) {
1036 $subscriber['id'] = $data['id'];
1037 }
1038 $active_slugs = array_column( $this->service->get_modules(), 'slug' );
1039 $modules = array_values(
1040 array_filter(
1041 $this->service->get_modules_as_objects(),
1042 static function ( $module ) use ( $active_slugs ) {
1043 return in_array( $module->slug, $active_slugs, true );
1044 }
1045 )
1046 );
1047 $sent = $this->service->send_invite_all_email( $subscriber, $modules );
1048
1049 return new Response( $sent, array() );
1050 }
1051 }
1052