709 lines
29 KiB
PHP
709 lines
29 KiB
PHP
<?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}" . __( 'It’s 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';
|
||
}
|
||
}
|