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

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

340 lines 11.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Handles the scheduling and sending of malware-related notifications.
4 *
5 * @package WP_Defender\Model\Notification
6 */
7
8 namespace WP_Defender\Model\Notification;
9
10 use Countable;
11 use WP_Defender\Traits\IO;
12 use WP_Defender\Model\Scan;
13 use WP_Defender\Component\Mail;
14 use WP_Defender\Component\Notification;
15 use WP_Defender\Model\Scan_Item;
16 use WP_Defender\Model\Notification\Malware_Report;
17 use WP_Defender\Traits\Scan_Email_Template;
18 use WP_Defender\Traits\Scan_Upsell;
19 use WP_Defender\Behavior\WPMUDEV;
20
21 /**
22 * Handles the scheduling and sending of malware-related notifications.
23 */
24 class Malware_Notification extends \WP_Defender\Model\Notification {
25
26 use IO;
27 use Scan_Email_Template;
28 use Scan_Upsell;
29
30 /**
31 * Table name.
32 *
33 * @var string
34 */
35 protected $table = 'wd_malware_scanning_notification';
36 /**
37 * Slug identifier for the malware notification.
38 *
39 * @var string
40 */
41 public const SLUG = 'malware-notification';
42
43 /**
44 * Retrieves the default values for the malware notification.
45 *
46 * @return array The default values for the malware notification.
47 */
48 public function get_default_values(): array {
49 $template = $this->get_email_template();
50
51 return array(
52 'subject_issue_found' => $template['found']['subject'],
53 'subject_issue_not_found' => $template['not_found']['subject'],
54 'subject_error' => $template['error']['subject'],
55 'content_issue_found' => $template['found']['body'],
56 'content_issue_not_found' => $template['not_found']['body'],
57 'content_error' => $template['error']['body'],
58 );
59 }
60
61 /**
62 * Sets default values for the malware notification.
63 *
64 * @return void
65 */
66 protected function before_load(): void {
67 $default = array(
68 'slug' => self::SLUG,
69 'title' => esc_html__( 'Issue - Alert', 'defender-security' ),
70 'status' => self::STATUS_DISABLED,
71 'description' => esc_html__(
72 'Get email alerts when Defender has finished manual scans.',
73 'defender-security'
74 ),
75 // @since 3.0.0 Fix 'Guest'-line.
76 'in_house_recipients' => array(),
77 'out_house_recipients' => array(),
78 'type' => 'notification',
79 'dry_run' => false,
80 'configs' => array(
81 'always_send' => false,
82 'error_send' => false,
83 'template' => $this->get_email_template(),
84 ),
85 );
86 $this->import( $default );
87 }
88
89 /**
90 * Sends the malware notification email to all subscribed users.
91 *
92 * @param Scan $scan The scan object.
93 * @param object $recipient_list The object containing the recipient lists.
94 * @param string $subject The subject of the email.
95 * @param string $content The content of the email.
96 * @param array $issues The list of issues to include in the email.
97 */
98 protected function email_engine( Scan $scan, $recipient_list, $subject, $content, $issues ): void {
99 $service = wd_di()->get( Notification::class );
100 foreach ( $recipient_list->in_house_recipients as $user ) {
101 if ( self::USER_SUBSCRIBED !== $user['status'] ) {
102 continue;
103 }
104 $this->send_to_user( $user['email'], $user['name'], $subject, $content, $issues, $service );
105 }
106 foreach ( $recipient_list->out_house_recipients as $user ) {
107 if ( self::USER_SUBSCRIBED !== $user['status'] ) {
108 continue;
109 }
110 $this->send_to_user( $user['email'], $user['name'], $subject, $content, $issues, $service );
111 }
112 }
113
114 /**
115 * Sends the malware notification email.
116 * Constructs and sends the malware notification email to the recipients.
117 *
118 * @param Scan $scan The scan object.
119 *
120 * @return void
121 */
122 public function send( Scan $scan ): void {
123 // Manual scan: always notify only the triggering user, no opt-in or status check required.
124 if ( ! $scan->is_automation ) {
125 $user_id = (int) get_transient( 'defender_scan_triggered_by_' . $scan->id );
126 delete_transient( 'defender_scan_triggered_by_' . $scan->id );
127 if ( $user_id > 0 ) {
128 $user = get_userdata( $user_id );
129 if ( $user ) {
130 $issues = $scan->get_issues( null, Scan_Item::STATUS_ACTIVE );
131 $templates = count( $issues ) ? $this->configs['template']['found'] : $this->configs['template']['not_found'];
132 $subject = $templates['subject'];
133 $content = nl2br( $templates['body'] );
134 $service = wd_di()->get( Notification::class );
135 $this->send_to_user( $user->user_email, $user->display_name, $subject, $content, $issues, $service );
136 }
137 return;
138 }
139 }
140
141 $object = $this;
142 if ( $scan->is_automation ) {
143 $object = wd_di()->get( Malware_Report::class );
144 // If Malware Reporting is unchecked, then return.
145 if ( self::STATUS_ACTIVE !== $object->status ) {
146 return;
147 }
148 // @since 2.7.0 Remove 'dry_run'-condition.
149 }
150 $issues = $scan->get_issues( null, Scan_Item::STATUS_ACTIVE );
151 /**
152 * When the Defender is unable to scan files for some reason and the 'error_send' option is checked.
153 * Possible reasons:
154 * - no connection to Scan API,
155 * - no checksums received from wp.org,
156 * - scan process stuck.
157 */
158 if (
159 isset( $object->configs['error_send'] )
160 && $object->configs['error_send']
161 && in_array( $scan->status, array( Scan::STATUS_ERROR, Scan::STATUS_IDLE ), true )
162 ) {
163 $subject = $object->configs['template']['error']['subject'];
164 $content = nl2br( $object->configs['template']['error']['body'] );
165
166 $this->email_engine( $scan, $object, $subject, $content, $issues );
167
168 return;
169 }
170
171 // Sometimes value may be as empty string for disabled 'always_send'.
172 if (
173 ( ! isset( $object->configs['always_send'] ) || true !== $object->configs['always_send'] )
174 && 0 === count( $issues )
175 ) {
176 return;
177 }
178 // For another case.
179 $templates = count( $issues ) ? $object->configs['template']['found'] : $object->configs['template']['not_found'];
180 $subject = $templates['subject'];
181 $content = nl2br( $templates['body'] );
182
183 $this->email_engine( $scan, $object, $subject, $content, $issues );
184 }
185
186 /**
187 * Sends the malware notification email to a specific user.
188 * Constructs and sends the malware notification email to the specified recipient.
189 *
190 * @param string $email The recipient's email address.
191 * @param string $name The recipient's name.
192 * @param string $subject The email subject.
193 * @param string $content The email content.
194 * @param array $issues The malware issues detected.
195 * @param object $service The notification service object.
196 *
197 * @return void
198 */
199 private function send_to_user( $email, $name, $subject, $content, $issues, $service ) {
200 $controller_scan = wd_di()->get( \WP_Defender\Controller\Scan::class );
201 $replacers = array(
202 'issues_count' => is_array( $issues ) || $issues instanceof \Countable ? (string) count( $issues ) : '0',
203 'issues_list' => $controller_scan->render_partial(
204 'email/scan-issue-list',
205 array(
206 'issues' => $issues,
207 'email' => $email,
208 ),
209 false
210 ),
211 'site_url' => network_site_url(),
212 'user_name' => $name,
213 );
214
215 /**
216 * Filters the replaced data. It's without replacement.
217 *
218 * @param array $replacers Email params.
219 *
220 * @deprecated 3.2.0
221 */
222 $replacers = apply_filters_deprecated( 'wd_notification_email_params', array( $replacers ), '3.2.0' );
223
224 $filter_name = ( is_array( $issues ) || $issues instanceof Countable ? count( $issues ) : 0 ) > 0
225 ? 'wd_notification_email_subject_issue'
226 : 'wd_notification_email_subject';
227 $subject = apply_filters( $filter_name, $subject );
228
229 /**
230 * It's without replacement.
231 *
232 * @deprecated 3.2.0
233 */
234 $content = apply_filters_deprecated(
235 'wd_notification_email_content_before',
236 array( $content ),
237 '3.2.0'
238 );
239 foreach ( $replacers as $key => $replacer ) {
240
241 if ( is_scalar( $replacer ) && ! is_string( $replacer ) ) {
242 $replacer = (string) $replacer;
243 }
244
245 $content = str_replace( "{{$key}}", $replacer, $content );
246 $content = str_replace( '{' . strtoupper( $key ) . '}', $replacer, $content );
247 $subject = str_replace( "{{$key}}", $replacer, $subject );
248 $subject = str_replace( '{' . strtoupper( $key ) . '}', $replacer, $subject );
249 }
250 /**
251 * It's without replacement.
252 *
253 * @deprecated 3.2.0
254 */
255 $content = apply_filters_deprecated(
256 'wd_notification_email_content_after',
257 array( $content ),
258 '3.2.0'
259 );
260 $unsubscribe_link = $service->create_unsubscribe_url( $this->slug, $email );
261 $template = $controller_scan->render_partial(
262 'email/index',
263 array(
264 'title' => esc_html__( 'Issues report', 'defender-security' ),
265 'content_body' => sprintf( '<div style="font-size: 16px">%s</div>', $content ),
266 'unsubscribe_link' => $unsubscribe_link,
267 ),
268 false
269 );
270
271 $headers = wd_di()->get( Mail::class )->get_headers(
272 defender_noreply_email( 'wd_scan_noreply_email' ),
273 self::SLUG
274 );
275
276 $ret = wp_mail( $email, $subject, $template, $headers );
277 if ( $ret ) {
278 $this->save_log( $email );
279 }
280 }
281
282 /**
283 * Defines labels for the malware notification settings.
284 *
285 * @return array The array of malware notification settings labels.
286 */
287 public function labels(): array {
288 return array(
289 'notification' => esc_html__( 'Issue - Alert', 'defender-security' ),
290 'always_send_notification' => esc_html__( 'Send alerts when no issues are detected', 'defender-security' ),
291 'error_send' => esc_html__(
292 "Send alerts when Defender couldn't scan the files",
293 'defender-security'
294 ),
295 'email_subject_issue_found' => esc_html__( 'Email subject when an issue is found', 'defender-security' ),
296 'email_subject_issue_not_found' => esc_html__( 'Email subject when no issues are found', 'defender-security' ),
297 'email_subject_error' => esc_html__( 'Email subject when failed to scan', 'defender-security' ),
298 'email_content_issue_found' => esc_html__( 'Email body when an issue is found', 'defender-security' ),
299 'email_content_issue_not_found' => esc_html__( 'Email body when no issues are found', 'defender-security' ),
300 'email_content_error' => esc_html__( 'When failed to scan', 'defender-security' ),
301 'notification_subscribers' => esc_html__( 'Recipients', 'defender-security' ),
302 );
303 }
304
305 /**
306 * Checks if the malware scanning notification options are enabled.
307 * Checks whether the malware scanning notification is enabled or if the malware reporting is enabled or the scan
308 * is automated.
309 *
310 * @return bool True if the malware scanning notification options are enabled, false otherwise.
311 */
312 public function check_options(): bool {
313 return self::STATUS_ACTIVE === $this->status
314 || self::STATUS_ACTIVE === wd_di()->get( Malware_Report::class )->status;
315 }
316
317 /**
318 * Additional converting rules.
319 *
320 * @param array $configs The configuration data.
321 *
322 * @return array The type-casted configuration data.
323 * @since 3.1.0
324 */
325 public function type_casting( array $configs ): array {
326 $configs['always_send'] = (bool) $configs['always_send'];
327 $configs['error_send'] = (bool) $configs['error_send'];
328 // Additional security layer because parent 'configs'-property doesn't have sanitized stuff for nested properties.
329 $template = $configs['template'];
330 $configs['template']['found']['subject'] = sanitize_text_field( $template['found']['subject'] );
331 $configs['template']['found']['body'] = sanitize_textarea_field( $template['found']['body'] );
332 $configs['template']['not_found']['subject'] = sanitize_text_field( $template['not_found']['subject'] );
333 $configs['template']['not_found']['body'] = sanitize_textarea_field( $template['not_found']['body'] );
334 $configs['template']['error']['subject'] = sanitize_text_field( $template['error']['subject'] );
335 $configs['template']['error']['body'] = sanitize_textarea_field( $template['error']['body'] );
336
337 return $configs;
338 }
339 }
340