2026-02-05 17:08:59 +03:00

709 lines
29 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
class OCVMSendEmails {
use OCVMVulnerabilities;
private $settings;
private $emailDir;
public $site_url;
public $vm_page_link;
public $vm_list_link;
const CLOSE_LI = '</li>'; // to reduce code smells
private $powered_by_url = 'https://patchstack.com';
private $vmPageSlug = 'admin.php?page=onecom-wp-health-monitor#vm-settings';
private $vm_list = 'admin.php?page=onecom-wp-health-monitor#vm-page';
private $list_checkmark = 'https://wpaddon-static.group-cdn.one/images/wp/onecom/green-checkmark-16x16.png';
private $support_mail_filter_api = MIDDLEWARE_URL . '/spam/filter-support-emails';
private $guide_links = array(
'en' => 'https://help.one.com/hc/en-us/articles/4402283373841-Roll-back-plugins-and-themes-to-a-previous-version',
'da' => 'https://help.one.com/hc/da/articles/4402283373841-Rul-plugins-og-temaer-tilbage-til-en-tidligere-version',
'de' => 'https://help.one.com/hc/de/articles/4402283373841-Zur%C3%BCcksetzen-von-Plugins-und-Themes-auf-eine-fr%C3%BChere-Version',
'es' => 'https://help.one.com/hc/es/articles/4402283373841-Revertir-plugins-y-temas-a-una-versi%C3%B3n-anterior-',
'fr' => 'https://help.one.com/hc/fr/articles/4402283373841-R%C3%A9initialisation-des-plugins-et-des-th%C3%A8mes-%C3%A0-une-version-ant%C3%A9rieure',
'fi' => 'https://help.one.com/hc/fi/articles/4402283373841-Palauta-pluginit-ja-teemat-edelliseen-versioon',
'it' => 'https://help.one.com/hc/it/articles/4402283373841-Ripristina-plugin-e-temi-alla-versione-precedente',
'nl' => 'https://help.one.com/hc/nl/articles/4402283373841-Zet-plugins-en-thema-s-terug-naar-een-vorige-versie',
'nb' => 'https://help.one.com/hc/no/articles/4402283373841-Tilbakerull-plugins-og-temaer-til-en-tidligere-versjon',
'pt' => 'https://help.one.com/hc/pt/articles/4402283373841-Reverter-plugins-e-temas-para-uma-vers%C3%A3o-anterior',
'sv' => 'https://help.one.com/hc/sv/articles/4402283373841-%C3%85terladdade-till%C3%A4gg-och-teman-till-en-f%C3%B6reg%C3%A5ende-version-',
);
public function __construct() {
$this->vm_page_link = is_multisite() ? get_admin_url( $this->vmPageSlug ) : admin_url( $this->vmPageSlug );
$this->vm_list_link = is_multisite() ? get_admin_url( $this->vm_list ) : admin_url( $this->vm_list );
$this->settings = new OCVMSettings();
$this->emailDir = plugin_dir_path( __DIR__ ) . 'templates/partials/';
$this->site_url = get_bloginfo( 'url' );
$this->site_url = str_replace( 'http://', '', $this->site_url );
$this->site_url = str_replace( 'https://', '', $this->site_url );
$this->site_url = str_replace( 'www.', '', $this->site_url );
}
/**
* Check if email for this vulnerability is already sent
*/
public function emailAlreadySent() {
// check in DB if any mail was already sent or not.
}
/**
* Prepare subject
*/
public function prepareSubject( $type ): string {
$subject = sprintf( __( 'Vulnerability found on %s by one.com WordPress Vulnerability Monitor.', 'onecom-wp' ), $this->site_url );
if ( 'vulsFixed' === $type ) {
$subject = sprintf( __( 'Vulnerability fixed on %s by one.com WordPress Vulnerability Monitor.', 'onecom-wp' ), $this->site_url );
} elseif ( 'vulsNotAutoFixed' === $type ) {
$subject = sprintf( __( 'Vulnerability found in your WordPress installation on %s cannot be fixed automatically', 'onecom-wp' ), $this->site_url );
}
return $subject;
}
/**
* Prepare reference text
*/
public function prepareReference( $type ): string {
$text = sprintf( __( 'Vulnerability detected in your WordPress installation on %s', 'onecom-wp' ), $this->site_url );
if ( 'vulsFixed' === $type ) {
$text = sprintf( __( 'Vulnerability fixed in your WordPress installation on %s', 'onecom-wp' ), $this->site_url );
} elseif ( 'vulsNotAutoFixed' === $type ) {
$text = sprintf( __( 'Vulnerability found in your WordPress installation on %s cannot be fixed automatically', 'onecom-wp' ), $this->site_url );
}
return $text;
}
/**
* Fix steps for VM emails (self-mwp cu & mwp with auto-update disable case)
* @return VM emails description string (actually html)
*/
public function mailDescription(): string {
// Default mail description (mainly used in volsFound with auto-update disable)
$mail_description = __( "Note that vulnerabilities can exist both in your live website and staging environment. Please, do a manual update if you don't have automatic updates enabled in both locations.", 'onecom-wp' );
// If self-mwp customer, override description
$settings = $this->settings->get();
if ( ! $this->settings->isPremium() ) {
$mail_description = __( 'Note that vulnerabilities can exist both in your live website and staging environment.', 'onecom-wp' ) . ' ' . __( 'Please, do a manual update to fix the vulnerabilities.', 'onecom-wp' );
// append one time VM intro text
if ( ! isset( $settings['self_mwp_intro_mail_sent'] ) || $settings['self_mwp_intro_mail_sent'] != 1 ) {
$mail_description .= $this->prepareVMIntro();
// Immediately update in db that it is sent once
$settings['self_mwp_intro_mail_sent'] = 1;
$this->settings->update( $settings );
}
}
return $mail_description;
}
/**
* Prepare VM Intro mail for self-mwp customers
* This will be sent only once based on 'self_mwp_intro_mail_sent' value in db
*/
public function prepareVMIntro(): string {
$html = '<div style="margin-top: 16px; margin-bottom: 8px; font-size: 16px; line-height: 24px; letter-spacing: 0.5px;">' .
__( 'What is the Vulnerability Monitor?', 'onecom-wp' ) . '</div>';
$img_tag = '<img style="margin-right: 8px; vertical-align: middle" width="16" height="16" src="' . $this->list_checkmark . '" />';
$vm_setting_link = '<a style="color:#0078c8; text-decoration: none" href="' . $this->vm_page_link . '" target="_blank">';
$html .= "<ul style='margin: 0; padding: 0;'><li style='margin-top: 8px; list-style:none;'>{$img_tag}" . __( 'Warns against serious vulnerabilities that need to be secured', 'onecom-wp' ) . '</li>';
$html .= "<li style='margin-top: 8px; list-style:none;'>{$img_tag}" . __( "Checks against world's biggest database of vulnerabilities", 'onecom-wp' ) . '</li>';
$html .= "<li style='margin-top: 8px; list-style:none;'>{$img_tag}" . __( 'Compliments other security plugins', 'onecom-wp' ) . '</li>';
$html .= "<li style='margin-top: 8px; list-style:none;'>{$img_tag}" . __( 'Its included in your current plan — for free!', 'onecom-wp' ) . '</li></ul>';
$html .= "<div style='margin-right: 8px; list-style:none; margin-top: 16px; vertical-align: middle'>"
. sprintf( __( 'You can manage email notifications in the %sVulnerability Monitor settings%s.', 'onecom-wp' ), $vm_setting_link, '</a>' ) . '</div>';
return $html;
}
/**
* Prepare disclaimer text
*/
public function prepareDisclaimer( $type, $items ) {
$names_arr = array();
// seat belt (prepare default array)
$items = array_merge(
array(
'plugins' => array(),
'themes' => array(),
),
$items
);
foreach ( $items['plugins'] as $data ) {
$names_arr[] = $data['name'] . ' v' . $data['installed_version'];
}
foreach ( $items['themes'] as $data ) {
$names_arr[] = $data['name'] . ' v' . $data['installed_version'];
}
return sprintf( 'Please, disregard this email if you already updated %s to a newer version.', implode( ', ', $names_arr ) );
}
/**
* Prepare Email HTML body
*/
public function prepareEmail( $type, $items = array() ): string {
if ( empty( $items ) ) {
return '';
}
// Get VM settings data
$settings = $this->settings->get();
/**
* VM emails for multiple scenarios:
* * If self-mWP -> mail-vul-found-self-mwp.html
* * If VM auto-updates are disabled -> mail-vul-found-auto-update-disabled.html
* * If Vuls are fixed -> mail-vul-fixed.html
* * If Vuls are not fixed due to no fix version -> mail-vul-found-no-fix.html
*/
if ( ! $this->settings->isPremium() ) {
$template_path = $this->emailDir . 'mail-vul-found-self-mwp.html';
} elseif ( $settings['settings']['auto_update'] === 0 ) {
$template_path = $this->emailDir . 'mail-vul-found-auto-update-disabled.html';
} elseif ( $type === 'vulsFixed' ) {
$template_path = $this->emailDir . 'mail-vul-fixed.html';
} elseif ( $type === 'vulsNotAutoFixed' ) {
$template_path = $this->emailDir . 'mail-vul-found-no-fix.html';
}
$email_html = file_get_contents( $template_path, true );
if ( empty( $email_html ) ) {
error_log( 'Could not load email html. Aborting email send task...' );
return '';
}
// Common texts
$site_url_inside = get_bloginfo( 'url' ); // renamed to reduce code smell
$email_html = str_replace( '{{site_url}}', $site_url_inside, $email_html );
$email_html = str_replace( '{{subject}}', $this->prepareSubject( $type ), $email_html );
$email_html = str_replace( '{{reference}}', $this->prepareReference( $type ), $email_html );
$email_html = str_replace( '{{action_title}}', __( 'Action required', 'onecom-wp' ), $email_html );
$email_html = str_replace( '{{hi}}', __( 'Hi,', 'onecom-wp' ), $email_html );
$email_html = str_replace( '{{reference_text}}', __( 'Reference', 'onecom-wp' ), $email_html );
$email_html = str_replace( '{{have_a_question}}', __( "Have a question? We're here to help.", 'onecom-wp' ), $email_html );
$powered_by_link = '<a style="color:#0078c8; text-decoration: none;" href="' . $this->powered_by_url . '" target="_blank">';
$email_html = str_replace( '{{powered_by}}', sprintf( __( 'Vulnerability monitor powered by %sPatchstack%s', 'onecom-wp' ), $powered_by_link, '</a>' ), $email_html );
$email_html = str_replace( '{{contact_us}}', __( 'Contact us today', 'onecom-wp' ), $email_html );
$email_html = str_replace( '{{business_terms}}', __( 'Terms', 'onecom-wp' ), $email_html );
$email_html = str_replace( '{{privacy_policy}}', __( 'Privacy policy', 'onecom-wp' ), $email_html );
$email_html = str_replace( '{{unsubscribe_text}}', __( 'Unsubscribe', 'onecom-wp' ), $email_html );
$email_html = str_replace( '{{view_site_cta}}', __( 'View your site', 'onecom-wp' ), $email_html );
$email_html = str_replace( '{{view_site_link}}', home_url(), $email_html );
$email_html = str_replace( '{{starter_type_found}}', __( 'Our Vulnerability Monitor for WordPress found a potential security risk in your WordPress installation on', 'onecom-wp' ), $email_html );
$siteLink = "<a style='color:#0078c8; text-decoration: underline' href=" . $site_url_inside . " target='_blank'>" . $site_url_inside . '</a>';
if ( 'vulsFound' === $type ) {
$email_html = str_replace( '{{disclaimer}}', $this->prepareDisclaimer( $type, $items ), $email_html );
$email_html = str_replace( '{{fix_title}}', __( 'To fix this:', 'onecom-wp' ), $email_html );
$email_html = str_replace( '{{fix_steps}}', $this->mailDescription(), $email_html );
$email_html = str_replace( '{{vulnerabilities_title}}', __( 'Vulnerabilities', 'onecom-wp' ), $email_html );
$email_html = str_replace( '{{update_now}}', __( 'Update now', 'onecom-wp' ), $email_html );
$email_html = str_replace( '{{see_more_cta}}', __( 'See more', 'onecom-wp' ), $email_html );
$email_html = str_replace( '{{upgrade_mwp_upsell_text}}', sprintf( __( 'Upgrade to %sManaged WordPress%s to automatically fix vulnerabilities.', 'onecom-wp' ), "<span style='font-weight: 500'>", '</span>' ), $email_html );
$email_html = str_replace( '{{learn_cta}}', __( 'Learn more', 'onecom-wp' ), $email_html );
$email_html = str_replace( '{{tip_auto_update}}', __( 'Tip! Enable the auto-update feature for the Vulnerability monitor in your WordPress dashboard to fix issues automatically.', 'onecom-wp' ), $email_html );
$email_html = str_replace( '{{tip_link_text}}', __( 'Click here to enable auto-update feature.', 'onecom-wp' ), $email_html );
} elseif ( 'vulsFixed' === $type ) {
$locale = explode( '_', get_locale() )[0];
if ( ! $locale || ! array_key_exists( $locale, $this->guide_links ) ) {
$locale = 'en';
}
$email_html = str_replace( '{{vm_fixed_title}}', __( 'Vulnerabilities fixed', 'onecom-wp' ), $email_html );
$email_html = str_replace( '{{starter_type_fixed}}', sprintf( __( 'Our Vulnerability Monitor for WordPress fixed a potential security risk in your WordPress installation on %s by updating the below mentioned.', 'onecom-wp' ), $siteLink ), $email_html );
$link = '<a style="color:#0078c8; text-decoration: none;" href="' . $this->guide_links[ $locale ] . '" target="_blank">';
$recomm_text = sprintf( __( 'We recommend that you check your website to see if everything is still working as expected. If you experience issues, please follow our guide to %sroll back to a previous version%s.', 'onecom-wp' ), $link, '</a>' );
$recomm_text .= '<br><br>';
$recomm_text .= __( "Note that vulnerabilities can exist both in your live website and staging environment. Please, do a manual update if you don't have automatic updates enabled in both locations.", 'onecom-wp' );
$email_html = str_replace( '{{fix_steps}}', $recomm_text, $email_html );
$email_html = str_replace( '{{vulnerabilities_title}}', __( 'Fixed vulnerabilities', 'onecom-wp' ), $email_html );
$email_html = str_replace( '{{view_site}}', __( 'View site', 'onecom-wp' ), $email_html );
// Vuls found but no fix version
} else {
$recomm_text = sprintf( __( 'Our Vulnerability Monitor for WordPress found a potential security risk in your WordPress installation on %s, but was not able to automatically update the vulnerable plugin/theme to the latest version. This likely means the plugin/theme is outdated and no longer actively maintained by the developers.', 'onecom-wp' ), $siteLink );
$recomm_text .= '<br><br>';
$recomm_text .= __( 'We recommend you remove the vulnerable plugin or theme and look for an alternative instead. Note that vulnerabilities can exist both in your live website and staging environment.', 'onecom-wp' );
$email_html = str_replace( '{{fix_steps}}', $recomm_text, $email_html );
$email_html = str_replace( '{{vulnerabilities_title}}', __( 'Vulnerabilities', 'onecom-wp' ), $email_html );
$email_html = str_replace( '{{view_site}}', __( 'View site', 'onecom-wp' ), $email_html );
}
$admin_url = is_multisite() ? get_admin_url() : admin_url();
$update_link = $admin_url . 'update-core.php?force-check=1';
$email_html = str_replace( '{{admin_url}}', $admin_url, $email_html );
$email_html = str_replace( '{{update_link}}', $update_link, $email_html );
$vuls_html = '';
//Update unsubscribe link
$email_html = str_replace( '{{vm_page_link}}', $this->vm_page_link, $email_html );
$email_html = str_replace( '{{vm_list_page}}', $this->vm_list_link, $email_html );
if ( 'vulsFound' === $type || 'vulsNotAutoFixed' === $type ) {
$items_arr = array();
if ( ! empty( $items['plugins'] ) ) {
$items_arr = array_merge( $items_arr, $items['plugins'] );
}
if ( ! empty( $items['themes'] ) ) {
$items_arr = array_merge( $items_arr, $items['themes'] );
}
if ( ! empty( $items['wp'] ) ) {
$items_arr['WordPress core'] = $items['wp'];
}
foreach ( $items_arr as $key => $item ) {
if ( array_key_exists( 'plugins', $items ) && in_array( $item, $items['plugins'] ) ) {
$item_type = 'plugins';
} elseif ( array_key_exists( 'themes', $items ) && in_array( $item, $items['themes'] ) ) {
$item_type = 'themes';
} elseif ( array_key_exists( 'wp', $items ) && in_array( $item, $items['wp'] ) ) {
$item_type = 'core';
}
//skip all vuls that have any fix version from the vuls couldn't be fixed automatically email
if ( 'vulsNotAutoFixed' === $type && $item['fixed_in'] !== '' ) {
unset( $items_arr[ $key ] );
continue;
}
$max_cvss_score = max( array_column( $item['vulnerabilities'], 'cvss_score' ) );
$final_cvss_score = is_numeric( $max_cvss_score ) ? $max_cvss_score : 10;
$count_vul = count( $item['vulnerabilities'] );
$exploitable_vul = array_search( true, array_column( $item['vulnerabilities'], 'is_exploited' ) );
$vuls_html .= '<span style="font-size: 16px; line-height: 24px; letter-spacing: 0.5px;">' . $item['name'] . ' v' . $item['installed_version'] . '</span>';
$vuls_html .= $this->get_vul_severity_label( $final_cvss_score );
if ( $this->settings->isPremium() && $exploitable_vul !== false && $count_vul > 1 ) {
if ( $item_type === 'plugins' ) {
$vuls_html .= '<p style="color:#D20019;font-size: 14px;font-style: normal;font-weight: 400;line-height: 22px;letter-spacing: 0.25px;margin-top: 0;margin-bottom: 0">' . sprintf( __( '%s has a vulnerability that is known to be exploited', 'onecom-wp' ), __( 'This plugin', 'onecom-wp' ) ) . '</p>';
} elseif ( $item_type === 'themes' ) {
$vuls_html .= '<p style="color:#D20019;font-size: 14px;font-style: normal;font-weight: 400;line-height: 22px;letter-spacing: 0.25px;margin-top: 0;margin-bottom: 0">' . sprintf( __( '%s has a vulnerability that is known to be exploited', 'onecom-wp' ), __( 'This theme', 'onecom-wp' ) ) . '</p>';
} elseif ( $item_type === 'core' ) {
$vuls_html .= '<p style="color:#D20019;font-size: 14px;font-style: normal;font-weight: 400;line-height: 22px;letter-spacing: 0.25px;margin-top: 0;margin-bottom: 0">' . sprintf( __( '%s has a vulnerability that is known to be exploited', 'onecom-wp' ), __( 'This core version', 'onecom-wp' ) ) . '</p>';
}
} elseif ( $this->settings->isPremium() && $exploitable_vul !== false && $count_vul === 1 ) {
$vuls_html .= '<p style="color:#D20019;font-size: 14px;font-style: normal;font-weight: 400;line-height: 22px;letter-spacing: 0.25px;margin-top: 0;margin-bottom: 0">' . __( 'This vulnerability is known to be exploited', 'onecom-wp' ) . '</p>';
}
$vuls_html .= "<ul style='margin-top: 8px'>";
// iterate through vulnerabilities belonging to this item
foreach ( $item['vulnerabilities'] as $v ) {
//skip all vuls that have any fix version
if ( 'vulsNotAutoFixed' === $type && $v['fixed_in'] !== '' ) {
continue;
}
// modified to manipulate the vuln_type for new API
$vtype = 'wp_vul_' . strtolower( str_replace( array( ' ', '(', ')' ), array( '_', '', '' ), $v['vuln_type'] ) );
// if vuln_type is not found in trait then fallback to the description
if ( $this->vulTranslation( $vtype ) !== '' ) {
$desc = $this->vulTranslation( $vtype );
} elseif ( $this->vulTranslation( $vtype ) === '' && isset( $v['description'] ) ) {
$desc = $v['description'];
} else {
// fallback if even description is not present
$desc = __( 'wp_vul_unknown', 'onecom-wp' );
}
$vuls_html .= "<li style='line-height: 22px;'>" . $desc . '<br>';
$vulurl = $v['url'] ?? '';
$vuls_html .= sprintf( "<a style='color:#0078c8;' href='%s'>%s</a></li><br>", $vulurl, __( 'See full Patchstack report', 'onecom-wp' ) );
}
$vuls_html .= '</ul>';
}
} elseif ( 'vulsFixed' === $type ) {
// iterate through items
$now_updated_text = __( 'now updated to %s', 'onecom-wp' );
foreach ( $items as $item ) {
$updateVer = sprintf( $now_updated_text, 'v' . $item['new_version'] );
$vuls_html .= '<span style="font-size: 16px; line-height: 24px; letter-spacing: 0.5px;">' . $item['name'] . ' v' . $item['old_version'] . ' <em>(' . $updateVer . ')</em>' . '</span>';
$vuls_html .= '<ul>';
// iterate through vulnerabilities belonging to this item
foreach ( $item['vuls'] as $vul ) {
$vulid = isset( $vul['id'] ) ? __( $vul['id'], 'onecom-wp' ) : '';
$vulurl = $vul['url'] ?? '';
$vuls_html .= "<li style='line-height: 22px;'>" . $vulid . '<br>';
$vuls_html .= "<a style='color:#0078c8;' href='{$vulurl}'>" . __( 'See full Patchstack report', 'onecom-wp' ) . '</a></li><br>';
}
$vuls_html .= '</ul>';
}
}
// put things together
$email_html = str_replace( '{{vulnerabilities}}', $vuls_html, $email_html );
return $email_html;
}
/**
* Returns vulnerability severity lebel: critical, high, medium or low
*/
public function get_vul_severity_label( $cvss ): string {
if ( $cvss < 4 ) {
$severity_label = "<span style='color: #FF8603; border: 1px solid #FF8603; margin-left: 8px;padding: 3px 8px; letter-spacing: 0.4px; line-height: 18px; font-size: 12px; border-radius: 4px;'>" .
__( 'Low', 'onecom-wp' ) . '</span>';
} elseif ( $cvss < 7 ) {
$severity_label = "<span style='color: #E85E0F; border: 1px solid #E85E0F; margin-left: 8px;padding: 3px 8px; letter-spacing: 0.4px; line-height: 18px; font-size: 12px; border-radius: 4px;'>" .
__( 'Medium', 'onecom-wp' ) . '</span>';
} elseif ( $cvss < 9 ) {
$severity_label = "<span style='color: #F0001E; border: 1px solid #F0001E; margin-left: 8px;padding: 3px 8px; letter-spacing: 0.4px; line-height: 18px; font-size: 12px; border-radius: 4px;'>" .
__( 'High', 'onecom-wp' ) . '</span>';
} else {
$severity_label = "<span style='color: #D20019; border: 1px solid #D20019; margin-left: 8px;padding: 3px 8px; letter-spacing: 0.4px; line-height: 18px; font-size: 12px; border-radius: 4px;'>" .
__( 'Critical', 'onecom-wp' ) . '</span>';
}
return $severity_label;
}
/**
* Get recipient emails
*/
public function getRecipients(): array {
$settings = $this->settings->get();
$notify_admins = $settings['settings']['notify_admin'];
$emails = array();
if ( 1 === $notify_admins ) {
// WP_User_Query arguments
$args = array(
'role' => 'Administrator',
'fields' => array( 'user_email' ),
);
// The User Query
$user_query = new WP_User_Query( $args );
$all_users = $user_query->results;
foreach ( $all_users as $user ) {
$emails[] = $user->user_email;
}
}
// merge custom emails into all emails
if ( ! empty( $settings['settings']['custom_emails'] ) ) {
$emails = array_merge( $emails, $settings['settings']['custom_emails'] );
}
// filter emails to prevent sending mailers to support
if ( ! empty( $emails ) ) {
$emails = $this->oci_support_mail_filter( $emails );
}
return $emails;
}
// Filter VM emails to exclude support mails
public function oci_support_mail_filter( $emails ): array {
$args = array(
'body' => wp_json_encode( $emails ),
);
$filtered_emails = wp_remote_post( $this->support_mail_filter_api, $args );
$response_body = json_decode( wp_remote_retrieve_body( $filtered_emails ), true );
$response_code = wp_remote_retrieve_response_code( $filtered_emails );
// If valid response found, return filtered emails
// if all emails excluded (null returned via api), send empty array
if ( $response_code === 200 && is_array( $response_body ) && $response_body['success'] ) {
return $response_body['data'] ?? array();
} else {
// if api error, return original emails
return $emails;
}
}
/**
* Remove update-attempt records once the email is sent
*/
public function removeAttemptRecords( $type = 'successful' ) {
// get latest state from DB
$settings = $this->settings->get();
// remove the item for which we have attempted an update
unset( $settings['update_attempts'][ $type ] );
$this->settings->update( $settings );
}
/**
* Default mail headers for Vulnerability monitor emails
*/
public function getMailHeaders( $recipients = array() ): string {
// get site URL and parse it
$siteHostName = wp_parse_url( get_site_url() );
// get hostname from site URL
$siteHostName = $siteHostName['host'];
// add header X-OneCom-Vmon along with default mail headers. More details: WPIN-2823
$headers = 'Content-Type: text/html;' . "\r\n";
$headers .= 'charset=UTF-8' . "\r\n";
$headers .= 'X-OneCom-Vmon:' . $siteHostName . "\r\n";
// add Cc headers
foreach ( $recipients as $recipient ) {
$headers .= 'Bcc: ' . $recipient . "\r\n";
}
error_log( 'Prepared these headers: ' . json_encode( $headers ) );
return $headers;
}
/**
* Vuls fixed mail
*/
public function vulsFixedMail( $type ) {
$vuls = $this->settings->get();
if ( empty( $vuls['update_attempts']['successful'] ) ) {
error_log( 'Skipping send_mail by rule...[Could not find any successful update attempt]' );
return;
}
$email_html = $this->prepareEmail( $type, $vuls['update_attempts']['successful'] );
$emailSubject = $this->prepareSubject( $type );
$recipients = $this->getRecipients();
$to = $recipients[0];
unset( $recipients[0] );
$headers = $this->getMailHeaders( $recipients );
add_filter( 'wp_mail_from_name', array( $this, 'mail_from_name' ) );
$mail_sent = wp_mail( $to, $emailSubject, $email_html, $headers );
remove_filter( 'wp_mail_from_name', array( $this, 'mail_from_name' ) );
if ( $mail_sent ) {
error_log( '~~~ Email sent successfully!~~~' );
$this->{$type . 'Stats'}( $vuls['update_attempts'] );
$this->removeAttemptRecords();
return true;
} else {
error_log( "FAILED to send emails vulsFixedMail $emailSubject" );
return false;
}
}
/**
* Vulnerability not auto fixed
* @param $type
* @return bool
*/
public function vulsNotAutoFixedMail( $type ) {
$vuls = $this->settings->get();
$actual_vul = $vuls['vulnerabilities'];
if (
empty( $actual_vul['wp'] ) &&
empty( $actual_vul['plugins'] ) &&
empty( $actual_vul['themes'] )
) {
error_log( 'Skip sending email if no vulnerability found' );
return;
}
$email_html = $this->prepareEmail( $type, $actual_vul );
$emailSubject = $this->prepareSubject( $type );
$recipients = $this->getRecipients();
$to = $recipients[0];
unset( $recipients[0] );
$headers = $this->getMailHeaders( $recipients );
add_filter( 'wp_mail_from_name', array( $this, 'mail_from_name' ) );
$mail_sent = wp_mail( $to, $emailSubject, $email_html, $headers );
remove_filter( 'wp_mail_from_name', array( $this, 'mail_from_name' ) );
if ( $mail_sent ) {
error_log( '~~~ Email sent successfully!~~~' );
//stats for vmNotAutoFixed
$this->{$type . 'Stats'}( $actual_vul );
return true;
} else {
error_log( "FAILED to send emails in vulsNotAutoFixed $emailSubject" );
return false;
}
}
/**
* Vuls found mail
*/
public function vulsFoundMail( $type ) {
$vuls = $this->settings->get();
if ( empty( $vuls['vulnerabilities'] ) ) {
error_log( 'Skipping send_mail by rule...[Could not find any vulnerabilities]' );
return false;
}
$email_html = $this->prepareEmail( $type, $vuls['vulnerabilities'] );
$emailSubject = $this->prepareSubject( $type );
$recipients = $this->getRecipients();
$to = $recipients[0];
unset( $recipients[0] );
$headers = $this->getMailHeaders( $recipients );
add_filter( 'wp_mail_from_name', array( $this, 'mail_from_name' ) );
$mail_sent = wp_mail( $to, $emailSubject, $email_html, $headers );
remove_filter( 'wp_mail_from_name', array( $this, 'mail_from_name' ) );
if ( $mail_sent ) {
error_log( '~~~ Email sent successfully!~~~' );
$this->{$type . 'Stats'}( $vuls['vulnerabilities'] );
return true;
} else {
error_log( "FAILED to send email $emailSubject" );
return false;
}
}
/**
* Prepare and Send Email
*/
public function sendEmail( $type = 'vulsFound' ) {
$this->{$type . 'Mail'}( $type );
}
/**
* Send stats for vulsNotAutoFixed
* @param $vuls
* @return void
*/
public function vulsNotAutoFixedStats( $vuls = array() ): void {
if ( empty( $vuls ) ) {
return;
}
$slugs = array();
//Check is fixed in missing in WP
if ( array_key_exists( 'wp', $vuls ) && ! empty( $vuls['wp'] ) && '' === min( array_column( $vuls['wp']['vulnerabilities'], 'fixed_in' ) ) ) {
$slugs['wp'] = $vuls['wp'];
}
foreach ( $vuls as $key => $items ) {
foreach ( $items as $slug => $item ) {
if ( empty( $item['vulnerabilities'] ) ) {
continue;
}
//continue if fixed_in not empty
if ( '' !== min( array_column( $item['vulnerabilities'], 'fixed_in' ) ) ) {
continue;
}
$slugs[ $key ][] = array(
'slug' => $slug,
'version' => max( array_column( $item['vulnerabilities'], 'introduced_in' ) ),
);
}
}
class_exists( 'OCPushStats' ) ?
OCPushStats::push_vul_monitor_stats( 'lookup', 'setting', 'vulnerability_monitor', array( 'vmNotFixedIn' => $slugs ) ) :
'';
}
/**
* Send vulsFound stats
* @param array $vuls
*/
public function vulsFoundStats( $vuls = array() ): void {
if ( empty( $vuls ) ) {
return;
}
$slugs = array();
if ( array_key_exists( 'wp', $vuls ) ) {
$slugs['wp'] = $vuls['wp'];
}
foreach ( $vuls as $key => $items ) {
foreach ( $items as $slug => $item ) {
if ( empty( $item['vulnerabilities'] ) ) {
continue;
}
$slugs[ $key ][] = array(
'slug' => $slug,
'version' => max( array_column( $item['vulnerabilities'], 'introduced_in' ) ),
);
}
}
class_exists( 'OCPushStats' ) ?
OCPushStats::push_vul_monitor_stats( 'lookup', 'setting', 'vulnerability_monitor', array( 'vulnerabilities' => $slugs ) ) :
'';
}
/**
* Send vulsFixed stats
* @param array $vuls
*/
public function vulsFixedStats( $attempts = array() ): void {
if ( empty( $attempts['successful'] ) ) {
return;
}
class_exists( 'OCPushStats' ) ?
OCPushStats::push_vul_monitor_stats( 'upgrade', 'setting', 'vulnerability_monitor', array( 'vulnerabilities' => $attempts ) ) :
'';
}
/**
* Change the sender name in emails
* @return string
*/
public function mail_from_name() {
return 'WordPress Vulnerability Monitor';
}
}