PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 5.7.2
Defender Security – Malware Scanner, Login Security & Firewall v5.7.2
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 5.7.2, at src/model/class-notification.php

460 lines 11.6 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 $user_id = get_current_user_id();
168
169 return array(
170 'name' => $this->get_user_display( $user_id ),
171 'id' => $user_id,
172 'email' => $this->get_current_user_email( $user_id ),
173 'role' => $this->get_current_user_role( $user_id ),
174 'avatar' => get_avatar_url( $this->get_current_user_email( $user_id ) ),
175 'status' => self::USER_SUBSCRIBED,
176 );
177 }
178
179 /**
180 * Check if the current moment is right for sending.
181 *
182 * @return bool
183 */
184 public function maybe_send() {
185 // @since 2.7.0 We can remove 'dry_run'-condition in the next version.
186 if ( true === $this->dry_run ) {
187 // No send, but need to track as sent, so we can requeue it.
188 if ( 'report' === $this->type ) {
189 $this->last_sent = $this->est_timestamp;
190
191 if ( $this->get_next_run() instanceof DateTime ) {
192 $this->est_timestamp = $this->get_next_run()->getTimestamp();
193 }
194
195 $this->save();
196 }
197
198 return false;
199 }
200
201 if ( ! $this->check_active_status() ) {
202 return false;
203 }
204
205 if ( 'notification' === $this->type ) {
206 return true;
207 }
208
209 if ( 0 === $this->last_sent ) {
210 return false;
211 }
212
213 $now = new DateTime( 'now', wp_timezone() );
214 $time = apply_filters( 'defender_current_time_for_report', $now );
215 // Testing.
216 if ( defined( 'WP_DEFENDER_TESTING' ) && true === constant( 'WP_DEFENDER_TESTING' ) ) {
217 return true;
218 }
219
220 return $time->getTimestamp() >= $this->est_timestamp;
221 }
222
223 /**
224 * Calculates the next run date based on the current date and the frequency of the notification.
225 *
226 * @return DateTime|false The next run date or false if the notification type is 'notification' or the status is
227 * not active.
228 * @throws Exception If an error occurs while creating the DateTime objects or modifying the date.
229 */
230 public function get_next_run() {
231 if ( 'notification' === $this->type ) {
232 return false;
233 }
234 if ( ! $this->check_active_status() ) {
235 return false;
236 }
237
238 // Create estimate object.
239 $est = new DateTime( 'now', wp_timezone() );
240 if ( $this->last_sent > 0 ) {
241 // set the timestamp of previous.
242 $est->setTimestamp( $this->last_sent );
243 }
244
245 // Est should be set as the last send. Create now timestamp.
246 $now = new DateTime( 'now', wp_timezone() );
247 $interval = DateInterval::createFromDateString( (string) $est->getOffset() . 'seconds' );
248 [ $hour, $min ] = explode( ':', $this->time );
249 $hour = (int) $hour;
250 $min = (int) $min;
251 switch ( $this->frequency ) {
252 case 'daily':
253 // Set the time.
254 $est->add( $interval );
255 $est->setTime( $hour, $min, 0 );
256 // Convert to current timezone.
257 while ( $est->getTimestamp() < $now->getTimestamp() ) {
258 $est->add( new DateInterval( 'P1D' ) );
259 $est->setTime( $hour, $min, 0 );
260 }
261 break;
262 case 'weekly':
263 $est->modify( 'this ' . $this->day );
264 $est->add( $interval );
265 $est->setTime( $hour, $min, 0 );
266 while ( $est->getTimestamp() < $now->getTimestamp() ) {
267 $est->modify( 'next ' . $this->day );
268 $est->setTime( $hour, $min, 0 );
269 }
270 break;
271 case 'monthly':
272 // We will need to check if the date is passed today, if not, use this, if yes, then queue for next month.
273 $est->setDate( (int) $est->format( 'Y' ), (int) $est->format( 'm' ), 1 );
274 if ( 31 === $this->day_n ) {
275 $this->day_n = (int) $est->format( 't' );
276 }
277 $est->add( new DateInterval( 'P' . ( $this->day_n - 1 ) . 'D' ) );
278 $est->setTime( $hour, $min, 0 );
279 while ( $est->getTimestamp() < $now->getTimestamp() ) {
280 // Already over, move to next month.
281 $est->modify( 'next month' );
282 $est->setTime( $hour, $min, 0 );
283 }
284 break;
285 }
286
287 return $est;
288 }
289
290 /**
291 * We have multiple issues where the email keep sending for no reason, this for debugging later.
292 *
293 * @param string $email Email address.
294 */
295 public function save_log( $email ): void {
296 $track = new Email_Track();
297 $track->timestamp = time();
298 $track->source = $this->slug;
299 $track->to = $email;
300 $track->save();
301 }
302
303 /**
304 * Checks the active status of the notification.
305 *
306 * @return bool Returns true if the notification is active, false otherwise.
307 */
308 public function check_active_status(): bool {
309 // Exception after migrating Scheduled scanning to Scan settings.
310 if ( Malware_Report::SLUG === $this->slug
311 && true === ( new \WP_Defender\Model\Setting\Scan() )->scheduled_scanning
312 ) {
313 return true;
314 }
315
316 return self::STATUS_ACTIVE === $this->status;
317 }
318
319 /**
320 * This will return the interval at string.
321 *
322 * @return string
323 * @throws Exception Emits Exception in case of an error.
324 */
325 public function to_string(): string {
326 if ( ! $this->check_active_status() ) {
327 return '-';
328 }
329 $date = new DateTime( 'now', wp_timezone() );
330 $date->setTimestamp( $this->est_timestamp );
331 switch ( $this->frequency ) {
332 case 'daily':
333 return sprintf(
334 /* translators: 1: Notification sending frequency, 2: Time of a day. */
335 esc_html__( '%1$s at %2$s', 'defender-security' ),
336 ucfirst( $this->frequency ),
337 $date->format( 'h:i A' )
338 );
339 case 'weekly':
340 return sprintf(
341 /* translators: 1: Notification sending frequency, 2: Day of the week, 3: Time of a day. */
342 esc_html__( '%1$s on %2$s at %3$s', 'defender-security' ),
343 ucfirst( $this->frequency ),
344 ucfirst( $this->day ),
345 $date->format( 'h:i A' )
346 );
347 case 'monthly':
348 default:
349 return sprintf(
350 /* translators: 1: Notification sending frequency, 2: Day of the month, 3: Time of a day. */
351 esc_html__( '%1$s/%2$d, %3$s', 'defender-security' ),
352 ucfirst( $this->frequency ),
353 $this->day_n,
354 $date->format( 'h:i A' )
355 );
356 }
357 }
358
359 /**
360 * Returns the next run date as a string, based on the type of notification and the active status.
361 *
362 * @param bool $for_hub Whether the next run date is for the hub. Default is false.
363 *
364 * @return bool|string Returns false if the notification type is 'notification' or the active status is false.
365 * Returns the next run date as a string in the format specified by the date_format and
366 * time_format options. Returns 'Never' if the active status is false.
367 * @throws Exception If an error occurs while creating the DateTime objects or modifying the date.
368 */
369 public function get_next_run_as_string( bool $for_hub = false ) {
370 if ( 'notification' === $this->type ) {
371 return $for_hub ? false : esc_html__( 'Never', 'defender-security' );
372 }
373
374 if ( $for_hub ) {
375 return $this->check_active_status()
376 ? $this->persistent_hub_datetime_format( $this->est_timestamp )
377 : false;
378 } elseif ( $this->check_active_status() ) {
379 $format = get_option( 'date_format' ) . ' ' . get_option( 'time_format' );
380 $date = new DateTime( 'now', wp_timezone() );
381 $date->setTimestamp( (int) $this->est_timestamp );
382
383 return $date->format( $format );
384 } else {
385 return esc_html__( 'Never', 'defender-security' );
386 }
387 }
388
389 /**
390 * We still need to validate the out house recipients email.
391 */
392 protected function after_validate(): void {
393 foreach ( $this->out_house_recipients as $recipient ) {
394 $recipient['email'] = trim( $recipient['email'] );
395 if ( '' === $recipient['email'] ) {
396 continue;
397 }
398 if ( ! filter_var( $recipient['email'], FILTER_VALIDATE_EMAIL ) ) {
399 /* translators: %s: Email address of a recipient. */
400 $this->errors[] = sprintf( esc_html__( 'Email %s is invalid format', 'defender-security' ), $recipient['email'] );
401 }
402 }
403 }
404
405 /**
406 * Saves the current state of the object.
407 *
408 * @return void
409 */
410 public function save(): void {
411 if ( $this->last_sent <= 0 ) {
412 $this->last_sent = time();
413 }
414 $next_run = $this->get_next_run();
415 if ( is_object( $next_run ) ) {
416 $this->est_timestamp = $next_run->getTimestamp();
417 }
418 parent::save();
419 }
420
421 /**
422 * Inject next run to parent function.
423 *
424 * @return array
425 */
426 public function export(): array {
427 $data = parent::export();
428
429 $data['next_run'] = $this->get_next_run_as_string();
430 $data['all_subscribers'] = array_merge( $this->in_house_recipients, $this->out_house_recipients );
431
432 return $data;
433 }
434
435 /**
436 * Overrided method to manipulate user details dynamically.
437 */
438 protected function after_load(): void {
439 $in_house_recipients = array();
440
441 foreach ( $this->in_house_recipients as $recipient ) {
442 $id = $recipient['id'];
443 $user_data = get_userdata( $id );
444
445 if ( $user_data instanceof WP_User ) {
446 $in_house_recipients[] = array(
447 'name' => $user_data->display_name,
448 'id' => $user_data->ID,
449 'email' => $user_data->user_email,
450 'role' => $this->get_first_user_role( $user_data ),
451 'avatar' => get_avatar_url( $id ),
452 'status' => $recipient['status'],
453 );
454 }
455 }
456
457 $this->in_house_recipients = $in_house_recipients;
458 }
459 }
460