get_email_template(); return array( 'subject_issue_found' => $template['found']['subject'], 'subject_issue_not_found' => $template['not_found']['subject'], 'subject_error' => $template['error']['subject'], 'content_issue_found' => $template['found']['body'], 'content_issue_not_found' => $template['not_found']['body'], 'content_error' => $template['error']['body'], ); } /** * Sets default values for the malware notification. * * @return void */ protected function before_load(): void { $default = array( 'slug' => self::SLUG, 'title' => esc_html__( 'Issue - Alert', 'defender-security' ), 'status' => self::STATUS_DISABLED, 'description' => esc_html__( 'Get email alerts when Defender has finished manual scans.', 'defender-security' ), // @since 3.0.0 Fix 'Guest'-line. 'in_house_recipients' => array(), 'out_house_recipients' => array(), 'type' => 'notification', 'dry_run' => false, 'configs' => array( 'always_send' => false, 'error_send' => false, 'template' => $this->get_email_template(), ), ); $this->import( $default ); } /** * Sends the malware notification email to all subscribed users. * * @param Scan $scan The scan object. * @param object $recipient_list The object containing the recipient lists. * @param string $subject The subject of the email. * @param string $content The content of the email. * @param array $issues The list of issues to include in the email. */ protected function email_engine( Scan $scan, $recipient_list, $subject, $content, $issues ): void { $service = wd_di()->get( Notification::class ); foreach ( $recipient_list->in_house_recipients as $user ) { if ( self::USER_SUBSCRIBED !== $user['status'] ) { continue; } $this->send_to_user( $user['email'], $user['name'], $subject, $content, $issues, $service ); } foreach ( $recipient_list->out_house_recipients as $user ) { if ( self::USER_SUBSCRIBED !== $user['status'] ) { continue; } $this->send_to_user( $user['email'], $user['name'], $subject, $content, $issues, $service ); } } /** * Sends a manual scan notification to the user who triggered the scan. * * @param Scan $scan The scan object. * * @return bool Whether the manual scan notification was handled. */ private function send_manual_scan( Scan $scan ): bool { $user_id = (int) get_transient( 'defender_scan_triggered_by_' . $scan->id ); delete_transient( 'defender_scan_triggered_by_' . $scan->id ); if ( $user_id <= 0 ) { return false; } $user = get_userdata( $user_id ); if ( ! $user ) { return true; } $issues = $scan->get_issues( null, Scan_Item::STATUS_ACTIVE ); if ( in_array( $scan->status, array( Scan::STATUS_ERROR, Scan::STATUS_IDLE ), true ) ) { $templates = $this->configs['template']['error']; } elseif ( 0 === count( $issues ) ) { return true; } else { $templates = $this->configs['template']['found']; } $subject = $templates['subject']; $content = nl2br( $templates['body'] ); $service = wd_di()->get( Notification::class ); $this->send_to_user( $user->user_email, $user->display_name, $subject, $content, $issues, $service ); return true; } /** * Sends the malware notification email. * Constructs and sends the malware notification email to the recipients. * * @param Scan $scan The scan object. * * @return void */ public function send( Scan $scan ): void { // Manual scan: notify only the triggering user when issues are found or the scan fails. if ( ! $scan->is_automation && $this->send_manual_scan( $scan ) ) { return; } $object = $this; if ( $scan->is_automation ) { $object = wd_di()->get( Malware_Report::class ); // If Malware Reporting is unchecked, then return. if ( self::STATUS_ACTIVE !== $object->status ) { return; } // @since 2.7.0 Remove 'dry_run'-condition. } $issues = $scan->get_issues( null, Scan_Item::STATUS_ACTIVE ); /** * When the Defender is unable to scan files for some reason. * The 'error_send' option was removed in v6. Its stored value is preserved for backward * compatibility (downgrade safety), but error notifications are now always sent regardless * of what the legacy value is. * Possible reasons: * - no connection to Scan API, * - no checksums received from wp.org, * - scan process stuck. */ if ( in_array( $scan->status, array( Scan::STATUS_ERROR, Scan::STATUS_IDLE ), true ) ) { $subject = $object->configs['template']['error']['subject']; $content = nl2br( $object->configs['template']['error']['body'] ); $this->email_engine( $scan, $object, $subject, $content, $issues ); return; } // The 'always_send' option was removed in v6. Its stored value is preserved for backward // compatibility (downgrade safety), but it must have no effect here — never send when // there are no issues, regardless of what the legacy value is. if ( 0 === count( $issues ) ) { return; } // For another case. $templates = count( $issues ) ? $object->configs['template']['found'] : $object->configs['template']['not_found']; $subject = $templates['subject']; $content = nl2br( $templates['body'] ); $this->email_engine( $scan, $object, $subject, $content, $issues ); } /** * Sends the malware notification email to a specific user. * Constructs and sends the malware notification email to the specified recipient. * * @param string $email The recipient's email address. * @param string $name The recipient's name. * @param string $subject The email subject. * @param string $content The email content. * @param array $issues The malware issues detected. * @param object $service The notification service object. * * @return void */ private function send_to_user( $email, $name, $subject, $content, $issues, $service ) { $controller_scan = wd_di()->get( \WP_Defender\Controller\Scan::class ); $replacers = array( 'issues_count' => is_array( $issues ) || $issues instanceof \Countable ? (string) count( $issues ) : '0', 'issues_list' => $controller_scan->render_partial( 'email/scan-issue-list', array( 'issues' => $issues, 'email' => $email, ), false ), 'site_url' => network_site_url(), 'user_name' => $name, ); /** * Filters the replaced data. It's without replacement. * * @param array $replacers Email params. * * @deprecated 3.2.0 */ $replacers = apply_filters_deprecated( 'wd_notification_email_params', array( $replacers ), '3.2.0' ); $filter_name = ( is_array( $issues ) || $issues instanceof Countable ? count( $issues ) : 0 ) > 0 ? 'wd_notification_email_subject_issue' : 'wd_notification_email_subject'; $subject = apply_filters( $filter_name, $subject ); /** * It's without replacement. * * @deprecated 3.2.0 */ $content = apply_filters_deprecated( 'wd_notification_email_content_before', array( $content ), '3.2.0' ); foreach ( $replacers as $key => $replacer ) { if ( is_scalar( $replacer ) && ! is_string( $replacer ) ) { $replacer = (string) $replacer; } $content = str_replace( "{{$key}}", $replacer, $content ); $content = str_replace( '{' . strtoupper( $key ) . '}', $replacer, $content ); $subject = str_replace( "{{$key}}", $replacer, $subject ); $subject = str_replace( '{' . strtoupper( $key ) . '}', $replacer, $subject ); } /** * It's without replacement. * * @deprecated 3.2.0 */ $content = apply_filters_deprecated( 'wd_notification_email_content_after', array( $content ), '3.2.0' ); $unsubscribe_link = $service->create_unsubscribe_url( $this->slug, $email ); $template = $controller_scan->render_partial( 'email/index', array( 'title' => esc_html__( 'Issues report', 'defender-security' ), 'content_body' => sprintf( '