PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 6.0.0
Defender Security – Malware Scanner, Login Security & Firewall v6.0.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 / model / class-notification.php

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

497 lines 12.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Handles interactions with the database table for notifications.
4 *
5 * @package WP_Defender\Model
6 */
7
8 namespace WP_Defender\Model;
9
10 use WP_User;
11 use DateTime;
12 use Exception;
13 use DateInterval;
14 use Calotes\Model\Setting;
15 use WP_Defender\Traits\User;
16 use WP_Defender\Traits\Formats;
17 use WP_Defender\Model\Notification\Malware_Report;
18 use function wp_timezone;
19
20 /**
21 * Model for the notifications table.
22 */
23 abstract class Notification extends Setting {
24
25 use User;
26 use Formats;
27
28 public const STATUS_DISABLED = 'disabled', STATUS_ACTIVE = 'enabled';
29 public const USER_SUBSCRIBED = 'subscribed', USER_SUBSCRIBE_WAITING = 'waiting', USER_SUBSCRIBE_CANCELED = 'cancelled', USER_SUBSCRIBE_NA = 'na';
30
31 /**
32 * Notification title.
33 *
34 * @var string
35 * @defender_property
36 * @sanitize_text_field
37 */
38 public $title;
39
40 /**
41 * Unique ID for this notification.
42 *
43 * @var string
44 * @defender_property
45 * @sanitize_text_field
46 */
47 public $slug;
48
49 /**
50 * Table column for description.
51 *
52 * @var string
53 * @defender_property
54 */
55 public $description;
56
57 /**
58 * This is the status of the current notification, can be active or disabled.
59 *
60 * @var string
61 * @defender_property
62 * @sanitize_text_field
63 */
64 public $status;
65
66 /**
67 * This is notification or report.
68 *
69 * @var string
70 * @defender_property
71 * @sanitize_text_field
72 */
73 public $type;
74
75 /**
76 * Report sending frequency. Only when $type is report.
77 *
78 * @var string
79 * @defender_property
80 * @sanitize_text_field
81 */
82 public $frequency;
83
84 /**
85 * Report sending day. Only when $type is report.
86 *
87 * @var string
88 * @defender_property
89 * @sanitize_text_field
90 */
91 public $day;
92
93 /**
94 * This is for when user select report as monthly, we will have the day number, instead of text.
95 *
96 * @var int
97 * @sanitize_text_field
98 * @defender_property
99 */
100 public int $day_n = 1;
101
102 /**
103 * Same as $day.
104 *
105 * @var string
106 * @defender_property
107 * @sanitize_text_field
108 */
109 public $time;
110
111 /**
112 * Holding a list of site user ids, so when sending, we send though this list.
113 *
114 * @var array
115 * @defender_property
116 * @sanitize_text_field
117 */
118 public $in_house_recipients = array();
119
120 /**
121 * For additional users, this should contain a list of email and name.
122 *
123 * @var array
124 * @defender_property
125 * @sanitize_text_field
126 */
127 public $out_house_recipients = array();
128
129 /**
130 * This when we want to run the report/notification without any email sending.
131 *
132 * @var bool
133 * @defender_property
134 */
135 public $dry_run = false;
136
137 /**
138 * This is contains the meta settings of this notification.
139 *
140 * @var array
141 * @defender_property
142 */
143 public $configs = array();
144
145 /**
146 * Tracking.
147 *
148 * @var int
149 * @defender_property
150 */
151 public $last_sent = 0;
152
153 /**
154 * Table column for estimated timestamp.
155 *
156 * @var int
157 * @defender_property
158 */
159 public $est_timestamp;
160
161 /**
162 * Return the default user, we will use this if there is no user in the notification.
163 *
164 * @return array
165 */
166 protected function get_default_user(): array {
167 if ( ! is_user_logged_in() ) {
168 return array();
169 }
170
171 $user_id = get_current_user_id();
172
173 $email = $this->get_current_user_email( $user_id );
174
175 return array(
176 $email => array(
177 'name' => $this->get_user_display( $user_id ),
178 'id' => $user_id,
179 'email' => $email,
180 'role' => $this->get_current_user_role( $user_id ),
181 'avatar' => get_avatar_url( $email ),
182 'status' => self::USER_SUBSCRIBED,
183 ),
184 );
185 }
186
187 /**
188 * Check if the current moment is right for sending.
189 *
190 * @return bool
191 */
192 public function maybe_send() {
193 // @since 2.7.0 We can remove 'dry_run'-condition in the next version.
194 if ( true === $this->dry_run ) {
195 // No send, but need to track as sent, so we can requeue it.
196 if ( 'report' === $this->type ) {
197 $this->last_sent = $this->est_timestamp;
198
199 if ( $this->get_next_run() instanceof DateTime ) {
200 $this->est_timestamp = $this->get_next_run()->getTimestamp();
201 }
202
203 $this->save();
204 }
205
206 return false;
207 }
208
209 if ( ! $this->check_active_status() ) {
210 return false;
211 }
212
213 if ( 'notification' === $this->type ) {
214 return true;
215 }
216
217 if ( 0 === $this->last_sent ) {
218 return false;
219 }
220
221 $now = new DateTime( 'now', wp_timezone() );
222 $time = apply_filters( 'defender_current_time_for_report', $now );
223 // Testing.
224 if ( defined( 'WP_DEFENDER_TESTING' ) && true === constant( 'WP_DEFENDER_TESTING' ) ) {
225 return true;
226 }
227
228 return $time->getTimestamp() >= $this->est_timestamp;
229 }
230
231 /**
232 * Calculates the next run date based on the current date and the frequency of the notification.
233 *
234 * @return DateTime|false The next run date or false if the notification type is 'notification' or the status is
235 * not active.
236 * @throws Exception If an error occurs while creating the DateTime objects or modifying the date.
237 */
238 public function get_next_run() {
239 if ( 'notification' === $this->type ) {
240 return false;
241 }
242 if ( ! $this->check_active_status() ) {
243 return false;
244 }
245
246 // Create estimate object.
247 $est = new DateTime( 'now', wp_timezone() );
248 if ( $this->last_sent > 0 ) {
249 // set the timestamp of previous.
250 $est->setTimestamp( $this->last_sent );
251 }
252
253 // Est should be set as the last send. Create now timestamp.
254 $now = new DateTime( 'now', wp_timezone() );
255 $interval = DateInterval::createFromDateString( (string) $est->getOffset() . 'seconds' );
256 $time_parts = array_pad( explode( ':', (string) $this->time ), 2, 0 );
257 [ $hour, $min ] = $time_parts;
258 $hour = (int) $hour;
259 $min = (int) $min;
260 switch ( $this->frequency ) {
261 case 'daily':
262 // Set the time.
263 $est->add( $interval );
264 $est->setTime( $hour, $min, 0 );
265 // Convert to current timezone.
266 while ( $est->getTimestamp() < $now->getTimestamp() ) {
267 $est->add( new DateInterval( 'P1D' ) );
268 $est->setTime( $hour, $min, 0 );
269 }
270 break;
271 case 'weekly':
272 if ( empty( $this->day ) ) {
273 break;
274 }
275 $est->modify( 'this ' . $this->day );
276 $est->add( $interval );
277 $est->setTime( $hour, $min, 0 );
278 while ( $est->getTimestamp() < $now->getTimestamp() ) {
279 $est->modify( 'next ' . $this->day );
280 $est->setTime( $hour, $min, 0 );
281 }
282 break;
283 case 'monthly':
284 // We will need to check if the date is passed today, if not, use this, if yes, then queue for next month.
285 $est->setDate( (int) $est->format( 'Y' ), (int) $est->format( 'm' ), 1 );
286 if ( 31 === $this->day_n ) {
287 $this->day_n = (int) $est->format( 't' );
288 }
289 $est->add( new DateInterval( 'P' . ( $this->day_n - 1 ) . 'D' ) );
290 $est->setTime( $hour, $min, 0 );
291 while ( $est->getTimestamp() < $now->getTimestamp() ) {
292 // Already over, move to next month.
293 $est->modify( 'next month' );
294 $est->setTime( $hour, $min, 0 );
295 }
296 break;
297 }
298
299 return $est;
300 }
301
302 /**
303 * We have multiple issues where the email keep sending for no reason, this for debugging later.
304 *
305 * @param string $email Email address.
306 */
307 public function save_log( $email ): void {
308 $track = new Email_Track();
309 $track->timestamp = time();
310 $track->source = $this->slug;
311 $track->to = $email;
312 $track->save();
313 }
314
315 /**
316 * Checks the active status of the notification.
317 *
318 * @return bool Returns true if the notification is active, false otherwise.
319 */
320 public function check_active_status(): bool {
321 // Exception after migrating Scheduled scanning to Scan settings.
322 if ( Malware_Report::SLUG === $this->slug
323 && ( new \WP_Defender\Model\Setting\Scan() )->is_enabled_scheduled_scanning()
324 ) {
325 return true;
326 }
327
328 return self::STATUS_ACTIVE === $this->status;
329 }
330
331 /**
332 * This will return the interval at string.
333 *
334 * @return string
335 * @throws Exception Emits Exception in case of an error.
336 */
337 public function to_string(): string {
338 if ( ! $this->check_active_status() ) {
339 return '-';
340 }
341 $date = new DateTime( 'now', wp_timezone() );
342 $date->setTimestamp( $this->est_timestamp );
343 switch ( $this->frequency ) {
344 case 'daily':
345 return sprintf(
346 /* translators: 1: Notification sending frequency, 2: Time of a day. */
347 esc_html__( '%1$s at %2$s', 'defender-security' ),
348 ucfirst( $this->frequency ),
349 $date->format( 'h:i A' )
350 );
351 case 'weekly':
352 return sprintf(
353 /* translators: 1: Notification sending frequency, 2: Day of the week, 3: Time of a day. */
354 esc_html__( '%1$s on %2$s at %3$s', 'defender-security' ),
355 ucfirst( $this->frequency ),
356 ucfirst( $this->day ),
357 $date->format( 'h:i A' )
358 );
359 case 'monthly':
360 default:
361 return sprintf(
362 /* translators: 1: Notification sending frequency, 2: Day of the month, 3: Time of a day. */
363 esc_html__( '%1$s/%2$d, %3$s', 'defender-security' ),
364 ucfirst( $this->frequency ),
365 $this->day_n,
366 $date->format( 'h:i A' )
367 );
368 }
369 }
370
371 /**
372 * Returns the next run date as a string, based on the type of notification and the active status.
373 *
374 * @param bool $for_hub Whether the next run date is for the hub. Default is false.
375 *
376 * @return bool|string Returns false if the notification type is 'notification' or the active status is false.
377 * Returns the next run date as a string in the format specified by the date_format and
378 * time_format options. Returns 'Never' if the active status is false.
379 * @throws Exception If an error occurs while creating the DateTime objects or modifying the date.
380 */
381 public function get_next_run_as_string( bool $for_hub = false ) {
382 if ( 'notification' === $this->type ) {
383 return $for_hub ? false : esc_html__( 'Never', 'defender-security' );
384 }
385
386 $est_timestamp = ! is_int( $this->est_timestamp ) ? (int) $this->est_timestamp : $this->est_timestamp;
387 if ( $for_hub ) {
388 return $this->check_active_status()
389 ? $this->persistent_hub_datetime_format( $est_timestamp )
390 : false;
391 } elseif ( $this->check_active_status() ) {
392 $format = get_option( 'date_format' ) . ' ' . get_option( 'time_format' );
393 $date = new DateTime( 'now', wp_timezone() );
394 $date->setTimestamp( $est_timestamp );
395
396 return $date->format( $format );
397 } else {
398 return esc_html__( 'Never', 'defender-security' );
399 }
400 }
401
402 /**
403 * We still need to validate the out house recipients email.
404 */
405 protected function after_validate(): void {
406 foreach ( $this->out_house_recipients as $recipient ) {
407 $recipient['email'] = trim( $recipient['email'] );
408 if ( '' === $recipient['email'] ) {
409 continue;
410 }
411 if ( ! filter_var( $recipient['email'], FILTER_VALIDATE_EMAIL ) ) {
412 /* translators: %s: Email address of a recipient. */
413 $this->errors[] = sprintf( esc_html__( 'Email %s is invalid format', 'defender-security' ), $recipient['email'] );
414 }
415 }
416 }
417
418 /**
419 * Saves the current state of the object.
420 *
421 * @return void
422 */
423 public function save(): void {
424 if ( $this->last_sent <= 0 ) {
425 $this->last_sent = time();
426 }
427 $next_run = $this->get_next_run();
428 if ( is_object( $next_run ) ) {
429 $this->est_timestamp = $next_run->getTimestamp();
430 }
431 parent::save();
432 }
433
434 /**
435 * Inject next run to parent function.
436 *
437 * @return array
438 */
439 public function export(): array {
440 $data = parent::export();
441
442 $data['next_run'] = $this->get_next_run_as_string();
443 $data['all_subscribers'] = array_merge( $this->in_house_recipients, $this->out_house_recipients );
444
445 return $data;
446 }
447
448 /**
449 * Overrided method to manipulate user details dynamically.
450 */
451 protected function after_load(): void {
452 $in_house_recipients = array();
453
454 foreach ( $this->in_house_recipients as $recipient ) {
455 $id = $recipient['id'];
456 $user_data = get_userdata( $id );
457
458 if ( $user_data instanceof WP_User ) {
459 $in_house_recipients[ $user_data->user_email ] = array(
460 'name' => $user_data->display_name,
461 'id' => $user_data->ID,
462 'email' => $user_data->user_email,
463 'role' => $this->get_first_user_role( $user_data ),
464 'avatar' => get_avatar_url( $id ),
465 'status' => $recipient['status'] ?? '',
466 );
467 }
468 }
469
470 $this->in_house_recipients = $in_house_recipients;
471 $this->out_house_recipients = $this->get_email_indexed_recipients( $this->out_house_recipients );
472 }
473
474 /**
475 * Indexes recipients by email.
476 *
477 * @param array $recipients Recipients list.
478 *
479 * @return array
480 */
481 private function get_email_indexed_recipients( array $recipients ): array {
482 $email_indexed_recipients = array();
483
484 foreach ( $recipients as $recipient ) {
485 if ( empty( $recipient['email'] ) ) {
486 continue;
487 }
488
489 $email = trim( $recipient['email'] );
490 $recipient['email'] = $email;
491 $email_indexed_recipients[ $email ] = $recipient;
492 }
493
494 return $email_indexed_recipients;
495 }
496 }
497