PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 6.3.0
Defender Security – Malware Scanner, Login Security & Firewall v6.3.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
← All changes | src/controller/class-notification.php +334 -214 5.8.1 → 6.3.0 View file →
@@ -14,9 +14,12 @@
14 14 use Calotes\Component\Request;
15 15 use WP_Defender\Traits\Formats;
16 16 use Calotes\Component\Response;
17 17 use WP_Defender\Component\Network_Cron_Manager;
18 +use WP_Defender\Model\Notification\Audit_Report;
18 19 use WP_Defender\Model\Notification\Malware_Report;
20 +use WP_Defender\Model\Notification\Tweak_Reminder;
21 +use WP_Defender\Model\Notification\Firewall_Report;
19 22 use WP_Defender\Component\Config\Config_Hub_Helper;
20 23 use WP_Defender\Model\Notification as Model_Notification;
21 24
22 25 /**
@@ -57,77 +60,45 @@
57 60 /**
58 61 * Initializes the model and service, registers routes, and sets up scheduled events if the model is active.
59 62 */
60 63 public function __construct() {
61 - $this->register_page(
62 - esc_html__( 'Notifications', 'defender-security' ),
63 - $this->slug,
64 - array( $this, 'main_view' ),
65 - $this->parent_slug
66 - );
67 64 $this->register_routes();
68 65 $this->service = wd_di()->get( \WP_Defender\Component\Notification::class );
69 66 add_action( 'defender_enqueue_assets', array( $this, 'enqueue_assets' ) );
70 67 // We use custom ajax endpoint here as the nonce would fail with other user.
71 - add_action( 'wp_ajax_' . self::SLUG_SUBSCRIBE, array( $this, 'verify_subscriber' ) );
72 - add_action( 'wp_ajax_nopriv_' . self::SLUG_SUBSCRIBE, array( $this, 'verify_subscriber' ) );
73 68 add_action( 'wp_ajax_' . self::SLUG_UNSUBSCRIBE, array( $this, 'unsubscribe_and_send_email' ) );
74 69 add_action( 'wp_ajax_nopriv_' . self::SLUG_UNSUBSCRIBE, array( $this, 'unsubscribe_and_send_email' ) );
75 70 add_action( 'defender_notify', array( $this, 'send_notify' ), 10, 2 );
76 - /**
77 - * Network Cron Manager
78 - *
79 - * @var Network_Cron_Manager $network_cron_manager
80 - */
81 - $network_cron_manager = wd_di()->get( Network_Cron_Manager::class );
82 - $timestamp = gmmktime( wp_date( 'H' ), 0, 0 );
83 - $network_cron_manager->register_callback(
84 - 'wdf_maybe_send_report',
85 - array( $this->service, 'maybe_dispatch_report' ),
86 - 30 * MINUTE_IN_SECONDS,
87 - $timestamp
88 - );
89 71 add_action( 'admin_notices', array( $this, 'show_actions_with_subscription' ) );
72 + add_action( 'wp_footer', array( $this, 'show_public_subscription_notice' ) );
90 73 }
91 74
92 75 /**
93 76 * For users who have subscribed or unsubscribed confirmation.
94 77 *
95 - * @return null|void
78 + * @return void
96 79 */
97 80 public function show_actions_with_subscription() {
98 81 if ( ! defined( 'IS_PROFILE_PAGE' ) || false === constant( 'IS_PROFILE_PAGE' ) ) {
99 - return null;
82 + return;
100 83 }
101 84 $slug = defender_get_data_from_request( 'slug', 'g' );
102 85 if ( ! is_string( $slug ) || '' === trim( $slug ) ) {
103 - return null;
86 + return;
104 87 }
105 88 $m = $this->service->find_module_by_slug( $slug );
106 89 if ( ! is_object( $m ) ) {
107 - return null;
90 + return;
108 91 }
109 92 $context = defender_get_data_from_request( 'context', 'g' );
110 93 if ( 'subscribed' === $context ) {
111 94 $unsubscribe_link = $this->service->create_unsubscribe_url( $m->slug, $this->get_current_user_email() );
112 - $strings = sprintf(
113 - /* translators: 1. Module title. 2. Unsubscribed link. */
114 - esc_html__(
115 - 'You are now subscribed to receive %1$s. Made a mistake? %2$s',
116 - 'defender-security'
117 - ),
118 - '<strong>' . $m->title . '</strong>',
119 - '<a href="' . esc_url_raw( $unsubscribe_link ) . '" style="text-decoration: none">' . esc_html__( 'Unsubscribe', 'defender-security' ) . '</a>'
120 - );
121 - } elseif ( 'unsubscribe' === $context ) {
122 - $strings = sprintf(
123 - /* translators: %s: Module title. */
124 - esc_html__( 'You are now unsubscribed from %s.', 'defender-security' ),
125 - '<strong>' . $m->title . '</strong>'
126 - );
127 - } else {
128 - return null;
129 95 }
96 +
97 + $strings = $this->get_subscription_notice_message( $m, $context, $unsubscribe_link ?? '' );
98 + if ( '' === $strings ) {
99 + return;
100 + }
130 101 ?>
131 102 <div class="notice notice-success" style="position:relative;">
132 103 <p><?php echo wp_kses_post( $strings ); ?></p>
133 104 <a href="<?php echo esc_url_raw( get_edit_profile_url() ); ?>" class="notice-dismiss"
@@ -138,8 +109,96 @@
138 109 <?php
139 110 }
140 111
141 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 + /**
142 201 * Renders the main view for this page.
143 202 */
144 203 public function main_view() {
145 204 $this->render( 'main' );
@@ -186,8 +245,77 @@
186 245 }
187 246 }
188 247
189 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 + /**
190 318 * Unsubscribe process.
191 319 */
192 320 public function unsubscribe_and_send_email() {
193 321 $slug = HTTP::get( 'slug', '' );
@@ -199,9 +327,10 @@
199 327 $m = $this->service->find_module_by_slug( $slug );
200 328 if ( ! is_object( $m ) ) {
201 329 wp_die( esc_html__( 'You shall not pass.', 'defender-security' ) );
202 330 }
203 - $inhouse = false;
331 + $inhouse = false;
332 + $processed = false;
204 333 foreach ( $m->in_house_recipients as &$recipient ) {
205 334 $email = $recipient['email'];
206 335 if ( hash_equals( $hash, hash( 'sha256', $email . AUTH_SALT ) ) ) {
207 336 // We skip even an un-logged user, because the admin can change the user's access without notice.
@@ -212,8 +341,9 @@
212 341 $inhouse = true;
213 342 }
214 343 $recipient['status'] = Model_Notification::USER_SUBSCRIBE_CANCELED;
215 344 $m->save();
345 + $processed = true;
216 346 // Send email.
217 347 $this->service->send_unsubscribe_email( $m, $email, $inhouse, $recipient['name'] );
218 348 break;
219 349 }
@@ -225,8 +355,9 @@
225 355 $email = $recipient['email'];
226 356 if ( hash_equals( $hash, hash( 'sha256', $email . AUTH_SALT ) ) ) {
227 357 $recipient['status'] = Model_Notification::USER_SUBSCRIBE_CANCELED;
228 358 $m->save();
359 + $processed = true;
229 360 $this->service->send_unsubscribe_email( $m, $email, $inhouse, $recipient['name'] );
230 361 }
231 362 }
232 363 }
@@ -239,8 +370,18 @@
239 370 ),
240 371 get_edit_profile_url()
241 372 )
242 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 + );
243 384 } else {
244 385 wp_safe_redirect( get_home_url() );
245 386 }
246 387 exit;
@@ -270,9 +411,14 @@
270 411 // Check config-values.
271 412 $data['configs'] = $model->type_casting( $data['configs'] );
272 413
273 414 $model->import( $data );
274 - $model->status = Model_Notification::STATUS_ACTIVE;
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 + }
275 421 if ( $model->validate() ) {
276 422 if ( 0 === $model->last_sent ) {
277 423 // This means that the notification or report never sent, we will use the moment that it get activate.
278 424 $model->last_sent = time();
@@ -319,20 +465,15 @@
319 465 */
320 466 public function save_bulk( Request $request ): Response {
321 467 $data = $request->get_data(
322 468 array(
323 - 'reports' => array(
469 + 'items' => array(
324 470 'type' => 'array',
325 471 'sanitize' => 'sanitize_textarea_field',
326 472 ),
327 - 'notifications' => array(
328 - 'type' => 'array',
329 - 'sanitize' => 'sanitize_textarea_field',
330 - ),
331 473 )
332 474 );
333 - $this->save_reports( $data['reports'] );
334 - $this->save_notifications( $data['notifications'] );
475 + $this->save_items( $data['items'] );
335 476 Config_Hub_Helper::set_clear_active_flag();
336 477
337 478 return new Response(
338 479 true,
@@ -348,89 +489,102 @@
348 489 );
349 490 }
350 491
351 492 /**
352 - * Process bulk reports saving.
493 + * Sync the shared reports schedule to notification models that persist their own next-run timestamp.
353 494 *
354 - * @param array $data Data to save.
495 + * @param Request $request The request object.
355 496 *
356 - * @throws Exception Emits Exception in case of an error.
497 + * @defender_route
498 + * @return Response
357 499 */
358 - private function save_reports( $data ) {
359 - foreach ( $data['configs'] as $datum ) {
360 - $slug = $datum['slug'];
361 - $model = $this->service->find_module_by_slug( $slug );
362 - if ( ! is_object( $model ) ) {
363 - continue;
364 - }
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 + );
365 521
366 - $import = array(
367 - // Saving after Bulk-Update must always change the status to Active.
368 - 'status' => Model_Notification::STATUS_ACTIVE,
369 - 'configs' => $model->type_casting( $datum ),
370 - 'in_house_recipients' => $data['in_house_recipients'],
371 - 'out_house_recipients' => $data['out_house_recipients'],
372 - );
373 - // since 2.7.0.
374 - if ( Malware_Report::SLUG !== $slug ) {
375 - $import['frequency'] = $data['frequency'];
376 - $import['day_n'] = (int) $data['day_n'];
377 - $import['day'] = $data['day'];
378 - $import['time'] = $data['time'];
379 - }
380 - foreach ( $import['out_house_recipients'] as $key => $val ) {
381 - if ( ! filter_var( $val['email'], FILTER_VALIDATE_EMAIL ) ) {
382 - unset( $import['out_house_recipients'][ $key ] );
383 - }
384 - }
385 - $model->import( $import );
386 - if ( $model->validate() ) {
387 - if ( 0 === $model->last_sent ) {
388 - $model->last_sent = time();
389 - }
390 - $model->save();
391 - $this->service->send_subscription_confirm_email( $model );
392 - // Track.
393 - if ( $this->is_tracking_active() ) {
394 - $track_data = array(
395 - 'Notification type' => $model->title,
396 - 'Notification schedule' => 'tweak-reminder' === $slug
397 - ? ucfirst( $data['configs']['reminder'] )
398 - : ucfirst( $data['frequency'] ),
399 - );
400 - $this->track_feature( 'def_notification_activated', $track_data );
401 - }
402 - }
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();
403 537 }
538 +
539 + Config_Hub_Helper::set_clear_active_flag();
540 +
541 + return new Response( true, $this->data_frontend() );
404 542 }
405 543
406 544 /**
407 - * Saves the notifications based on the provided data.
545 + * Process bulk saving of reports or notifications.
408 546 *
409 - * @param array $data The data containing the configurations for the notifications.
547 + * @param array $data Data to save.
410 548 *
411 - * @return void
412 549 * @throws Exception Emits Exception in case of an error.
413 550 */
414 - private function save_notifications( $data ) {
415 - foreach ( $data['configs'] as $datum ) {
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 +
416 560 $slug = $datum['slug'];
417 561 $model = $this->service->find_module_by_slug( $slug );
418 562 if ( ! is_object( $model ) ) {
419 563 continue;
420 564 }
565 +
421 566 $import = array(
422 - // Saving after Bulk-Update must always change the status to Active.
423 - 'status' => Model_Notification::STATUS_ACTIVE,
567 + 'status' => $model->status,
424 568 'configs' => $model->type_casting( $datum ),
425 - 'in_house_recipients' => $data['in_house_recipients'],
426 - 'out_house_recipients' => $data['out_house_recipients'],
569 + 'in_house_recipients' => $datum['in_house_recipients'] ?? $in_house_recipients,
570 + 'out_house_recipients' => $datum['out_house_recipients'] ?? $out_house_recipients,
427 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 + }
428 579 foreach ( $import['out_house_recipients'] as $key => $val ) {
429 580 if ( ! filter_var( $val['email'], FILTER_VALIDATE_EMAIL ) ) {
430 581 unset( $import['out_house_recipients'][ $key ] );
431 582 }
432 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 +
433 587 $model->import( $import );
434 588 if ( $model->validate() ) {
435 589 if ( 0 === $model->last_sent ) {
436 590 $model->last_sent = time();
@@ -435,12 +589,30 @@
435 589 if ( 0 === $model->last_sent ) {
436 590 $model->last_sent = time();
437 591 }
438 592 $model->save();
439 - $this->service->send_subscription_confirm_email( $model );
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 + }
440 606 // Track.
441 607 if ( $this->is_tracking_active() ) {
442 - $this->track_feature( 'def_notification_activated', array( 'Notification type' => $model->title ) );
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 );
443 615 }
444 616 }
445 617 }
446 618 }
@@ -568,78 +740,8 @@
568 740 );
569 741 }
570 742
571 743 /**
572 - * This is a receiver to process subscribe confirmation from email.
573 - */
574 - public function verify_subscriber() {
575 - $hash = HTTP::get( 'hash', '' );
576 - $slug = HTTP::get( 'uid', '' );
577 - $inhouse = HTTP::get( 'inhouse', '0' );
578 -
579 - if ( '1' === $inhouse && ! is_user_logged_in() ) {
580 - // This is in-house, so we need to redirect.
581 - auth_redirect();
582 - }
583 - if ( ! is_string( $hash ) || '' === trim( $hash ) || ! is_string( $slug ) || '' === trim( $slug ) ) {
584 - wp_die( esc_html__( 'You shall not pass.', 'defender-security' ) );
585 - }
586 - $m = $this->service->find_module_by_slug( $slug );
587 - if ( ! is_object( $m ) ) {
588 - wp_die( esc_html__( 'You shall not pass.', 'defender-security' ) );
589 - }
590 - if ( '1' === $inhouse ) {
591 - $processed = false;
592 - foreach ( $m->in_house_recipients as &$recipient ) {
593 - if ( Model_Notification::USER_SUBSCRIBED === $recipient['status'] ) {
594 - continue;
595 - }
596 -
597 - $email = $recipient['email'];
598 - $name = $recipient['name'];
599 - if ( hash_equals( $hash, hash( 'sha256', $email . AUTH_SALT ) )
600 - && $email === $this->get_current_user_email() ) {
601 - $recipient['status'] = Model_Notification::USER_SUBSCRIBED;
602 - $this->service->send_subscribed_email( $email, $m, $name );
603 - $processed = true;
604 - }
605 - }
606 - } else {
607 - foreach ( $m->out_house_recipients as &$recipient ) {
608 - if ( Model_Notification::USER_SUBSCRIBED === $recipient['status'] ) {
609 - continue;
610 - }
611 -
612 - $email = $recipient['email'];
613 - $name = $recipient['name'];
614 - if ( hash_equals( $hash, hash( 'sha256', $email . AUTH_SALT ) ) ) {
615 - $recipient['status'] = Model_Notification::USER_SUBSCRIBED;
616 - $this->service->send_subscribed_email( $email, $m, $name );
617 - }
618 - }
619 - }
620 - $m->save();
621 - if ( '1' === $inhouse ) {
622 - if ( $processed ) {
623 - wp_safe_redirect(
624 - add_query_arg(
625 - array(
626 - 'slug' => $m->slug,
627 - 'context' => 'subscribed',
628 - ),
629 - get_edit_profile_url()
630 - )
631 - );
632 - } else {
633 - wp_safe_redirect( home_url() );
634 - }
635 - } else {
636 - wp_safe_redirect( home_url() );
637 - }
638 - exit;
639 - }
640 -
641 - /**
642 744 * Enqueues scripts and styles for this page.
643 745 * Only enqueues assets if the page is active.
644 746 */
645 747 public function enqueue_assets() {
@@ -645,22 +747,8 @@
645 747 public function enqueue_assets() {
646 748 if ( ! $this->is_page_active() ) {
647 749 return;
648 750 }
649 - wp_localize_script(
650 - 'def-notification',
651 - 'notification',
652 - array_merge( $this->data_frontend(), $this->dump_routes_and_nonces() )
653 - );
654 - wp_enqueue_script( 'def-notification' );
655 - $this->enqueue_main_assets();
656 - wp_enqueue_style(
657 - 'def-select2',
658 - defender_asset_url( '/assets/css/select2.min.css' ),
659 - array(),
660 - DEFENDER_VERSION,
661 - true
662 - );
663 751 }
664 752
665 753 /**
666 754 * An endpoint for fetching users pool.
@@ -789,29 +877,17 @@
789 877 *
790 878 * @return array An array of data for the frontend.
791 879 */
792 880 public function data_frontend(): array {
793 - return array(
794 - 'notifications' => $this->service->get_modules(),
795 - 'inactive_notifications' => $this->service->get_inactive_modules(),
796 - 'active_count' => $this->service->count_active(),
797 - 'next_run' => $this->service->get_next_run(),
798 - 'misc' => array(
799 - 'days_of_week' => $this->get_days_of_week(),
800 - 'times_of_day' => $this->get_times(),
801 - 'timezone_text' => sprintf(
802 - /* translators: 1. Timezone. 2. Time. */
803 - esc_html__(
804 - 'Your timezone is set to %1$s, so your current time is %2$s.',
805 - 'defender-security'
806 - ),
807 - '<strong>' . wp_timezone_string() . '</strong>',
808 - '<strong>' . wp_date( 'H:i', time() ) . '</strong>'
809 - ),
810 - 'default_recipient' => $this->get_default_recipient(),
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(),
811 888 ),
812 - 'hub_connector' => wd_di()->get( Hub_Connector::class )->data_frontend(),
813 - 'antibot' => wd_di()->get( Antibot_Global_Firewall::class )->data_frontend(),
889 + $this->dump_routes_and_nonces()
814 890 );
815 891 }
816 892
817 893 /**
@@ -901,9 +977,9 @@
901 977 if ( isset( $data['id'] ) && is_int( $data['id'] ) && 0 < $data['id'] ) {
902 978 $subscriber['id'] = $data['id'];
903 979 }
904 980 // Resend invite email now.
905 - $sent = $this->service->send_email( $subscriber, $model );
981 + $sent = $this->service->send_email( $subscriber, $model->export() );
906 982
907 983 if ( $sent ) {
908 984 return new Response( true, array( 'message' => esc_html__( 'Invitation sent successfully.', 'defender-security' ) ) );
909 985 }
@@ -924,6 +1000,50 @@
924 1000 public function get_user_roles() {
925 1001 $user_roles = $this->service->get_user_roles();
926 1002
927 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() );
928 1048 }
929 1049 }