AlkantarClanX12

Your IP : 216.73.217.24


Current Path : /www/capitalgmcbuickregina_830/public/wp-content/plugins/wp-cerber/includes/
Upload File :
Current File : /www/capitalgmcbuickregina_830/public/wp-content/plugins/wp-cerber/includes/CRB_Messaging.php

<?php

use PHPMailer\PHPMailer\PHPMailer;

/**
 * Handles outbound messages, including security alerts, reports,
 * system notifications, and user-facing messages such as 2FA codes.
 *
 * Builds message content based on message type, applies channel policies and
 * rate limits, and delivers messages via supported transports (email, Pushbullet).
 * Also manages email delivery details such as SMTP configuration, fallback to
 * default wp_mail(), deferred sending, and error handling.
 *
 * This class acts as a single entry point for sending all outgoing messages.
 */
final class CRB_Messaging {

	const SEND_ISSUE_CODE = 'email_send_error';

	private static ?self $instance = null;
	private bool $smpt_enabled = false;
	private bool $no_smtp = false; // If true, it temporarily disables using the WP Cerber's SMTP settings
	private ?PHPMailer $mailer = null; // Last used mailer
	private array $email_args = array(); // Holds email parameters in case of postponed sending
	private bool $error = false; // If an error occurred while sending

	private static function instance() : self {
		if ( self::$instance === null ) {
			self::$instance = new self();
		}
		return self::$instance;
	}

	/**
	 * Sends outbound messages via configured delivery channels.
	 *
	 * Sends administrative notifications, reports, and user-facing messages
	 * (including 2FA codes) via enabled delivery channels, applying rate limits
	 * and channel policies.
	 *
	 * Replacement for cerber_send_message().
	 *
	 * @param string $msg_type Message type identifier.
	 * @param array $msg_parts Message content parts and optional variants.
	 * @param array $channels Delivery channels to use.
	 * @param bool $ignore Whether to ignore global rate limits.
	 * @param array $args Additional context and message parameters.
	 *
	 * @return array|false List of recipients on success, false on failure.
	 *
	 * @since 9.6.16
	 */
	public static function send( $msg_type, $msg_parts = array(), $channels = array(), $ignore = false, $args = array() ) {
		return self::instance()->send_message( $msg_type, $msg_parts, $channels, $ignore, $args );
	}

	/**
	 * Sends outbound messages via configured delivery channels.
	 *
	 * Builds message content based on message type, applies channel selection and
	 * rate limits, and dispatches delivery via supported transports.
	 *
	 * @param string $msg_type Message type identifier.
	 * @param array $msg_parts Message content parts and optional variants.
	 * @param array $channels Delivery channels to use.
	 * @param bool $ignore Whether to ignore global rate limits.
	 * @param array $args Additional context and message parameters.
	 *
	 * @return array|false List of recipients on success, false on failure.
	 *
	 * @since 9.5.5.1
	 */
	private function send_message( $msg_type, $msg_parts = array(), $channels = array(), $ignore = false, $args = array() ) {

		$msg_type = $msg_type ?: 'generic';

		$sb = $msg_parts['subj'] ?? '';
		$msg = $msg_parts['text'] ?? '';
		$msg_masked = $msg_parts['text_masked'] ?? '';
		$more = $msg_parts['more'] ?? '';
		$ip = $msg_parts['ip'] ?? '';

		$channels = array_merge( CRB_CHANNELS, $channels );

		if ( ! array_filter( $channels ) ) {
			return false;
		}

		if ( ! $ignore
		     && ! is_admin()
		     && in_array( $msg_type, array( 'lockout', 'send_alert' ) ) ) {

			$channels = $this->check_limits( $channels );

			if ( ! array_filter( $channels ) ) {
				return false;
			}
		}

		$html_mode = false;
		$blogname = crb_get_blogname_decoded();

		if ( ! in_array( $msg_type, array( '2fa', 'new_version' ) ) ) {
			$subj = '[' . $blogname . '] WP Cerber: ' . $sb;
		}
		else {
			$subj = '[' . $blogname . '] ' . $sb;
		}

		$body = '';
		$body_masked = '';

		if ( is_array( $msg ) ) {
			$msg = implode( "\n\n", $msg ) . "\n\n";
		}

		if ( is_array( $msg_masked ) ) {
			$msg_masked = implode( "\n\n", $msg_masked ) . "\n\n";
		}

		$last = null;

		switch ( $msg_type ) {
			case 'citadel_mode':
				$max = cerber_db_get_var( 'SELECT MAX(stamp) FROM ' . CERBER_LOG_TABLE . ' WHERE  activity = ' . CRB_EV_LFL );
				if ( $max ) {
					$last_date = cerber_date( $max, false );
					$last = cerber_db_get_row( 'SELECT * FROM ' . CERBER_LOG_TABLE . ' WHERE stamp = ' . $max . ' AND activity = ' . CRB_EV_LFL, MYSQL_FETCH_OBJECT );
				}

				if ( ! $last ) { // workaround for the empty log table
					$last = new stdClass();
					$last->ip = CERBER_NO_REMOTE_IP;
					$last->user_login = 'test';
				}

				$subj .= __( 'Citadel mode is active', 'wp-cerber' );

				/* translators: %1$d is the number of failed login attempts, %2$d is the time period in minutes. */
				$body = sprintf( __( 'Citadel mode has been activated after %1$d failed login attempts in %2$d minutes.', 'wp-cerber' ), crb_get_settings( 'cilimit' ), crb_get_settings( 'ciperiod' ) ) . "\n\n";
				/* translators: %1$s is the date and time, %2$s is the IP address, %3$s is the username. */
				$body .= sprintf( __( 'Last failed attempt was at %1$s from IP %2$s using username: %3$s.', 'wp-cerber' ), $last_date, $last->ip, $last->user_login ) . "\n\n";

				/* translators: %s is the URL to view activity in the Dashboard. */
				$more = sprintf( __( 'View activity in the Dashboard: %s', 'wp-cerber' ), cerber_admin_link( 'activity', array(), false, false ) ) . "\n\n";
				break;
			case 'lockout':
				$max = cerber_db_get_var( 'SELECT MAX(stamp) FROM ' . CERBER_LOG_TABLE . ' WHERE  activity IN (' . CRB_EV_IPL . ',' . CRB_EV_NEL . ')' );

				if ( $max ) {
					$last_date = cerber_date( $max, false );
					$last = cerber_db_get_row( 'SELECT * FROM ' . CERBER_LOG_TABLE . ' WHERE stamp = ' . $max . ' AND activity IN (' . CRB_EV_IPL . ',' . CRB_EV_NEL . ')', MYSQL_FETCH_OBJECT );
				}
				else {
					$last_date = '';

					// workaround for the empty log table
					$last = new stdClass();
					$last->ip = CERBER_NO_REMOTE_IP;
					$last->user_login = 'test';
				}

				$active = crb_get_locked_out_num();

				if ( $last->ip && ( $block = crb_get_lockout( $last->ip ) ) ) {
					$reason = $block->reason;
				}
				else {
					$reason = __( 'unspecified', 'wp-cerber' );
				}

				/* translators: %d is the number of active lockouts. */
				$subj .= sprintf( __( 'Number of lockouts is increasing (%d)', 'wp-cerber' ), $active );

				/* translators: %d is the number of active lockouts. */
				$body = sprintf( __( 'Number of active lockouts at the moment: %d', 'wp-cerber' ), $active ) . "\n\n";

				if ( $last_date ) {
					/* translators: %1$s is the IP address with hostname, %2$s is the date and time of the lockout. */
					$body .= sprintf( __( 'Last security action: IP address %1$s was locked out at %2$s', 'wp-cerber' ), $last->ip . ' (' . @gethostbyaddr( $last->ip ) . ')', $last_date ) . "\n\n";
					/* translators: %s is the lockout reason. */
					$body .= sprintf( __( 'Reason: %s', 'wp-cerber' ), strip_tags( $reason ) ) . "\n\n";
				}

				/* translators: %s is the URL to view activity for the IP. */
				$more = sprintf( __( 'View activity for this IP: %s', 'wp-cerber' ), cerber_admin_link( 'activity', array(), false, false ) . '&filter_ip=' . $last->ip ) . "\n\n";
				/* translators: %s is the URL to view all lockouts. */
				$more .= sprintf( __( 'View all lockouts: %s', 'wp-cerber' ), cerber_admin_link( 'lockouts', array(), false, false ) ) . "\n\n";

				/* translators: %s is the URL to learn more about alerting. */
				$more .= sprintf( __( 'Learn more about advanced alerting in WP Cerber: %s', 'wp-cerber' ), 'https://wpcerber.com/wordpress-notifications-made-easy/' );
				break;
			case 'new_version':
				$body = $msg . "\n\n";
				/* translators: %s is the website name. */
				$more = sprintf( __( 'Website: %s', 'wp-cerber' ), $blogname );
				break;
			case 'shutdown':
				$d = __( 'The WP Cerber Security plugin has been deactivated', 'wp-cerber' );
				$subj = '[' . $blogname . '] ' . $d;
				$body .= "\n" . $d . "\n\n";
				if ( ! is_user_logged_in() ) {
					$u = __( 'Unknown', 'wp-cerber' );
				}
				else {
					$user = wp_get_current_user();
					$u = $user->display_name;
				}
				/* translators: %s is the website name. */
				$body .= sprintf( __( 'Website: %s', 'wp-cerber' ), $blogname ) . "\n";

				/* translators: %s is the user name. */
				$more = sprintf( __( 'By the user: %s', 'wp-cerber' ), $u ) . "\n";
				/* translators: %s is the IP address. */
				$more .= sprintf( __( 'From the IP address: %s', 'wp-cerber' ), cerber_get_remote_ip() ) . "\n";

				$ip_rdap = CRB_RDAP_Client::get_parsed_ip_info( $ip );

				if ( ! crb_is_wp_error( $ip_rdap ) && ! empty( $ip_rdap->country ) ) {
					/* translators: %s is the country name. */
					$more .= sprintf( __( 'From the country: %s', 'wp-cerber' ), crb_get_country_name( $ip_rdap->country ) );
				}

				break;
			case 'plugin_activated':
				$subj = '[' . $blogname . '] ' . __( 'The WP Cerber Security plugin is now active', 'wp-cerber' );
				$body = "\n" . __( 'WP Cerber is now active and has started protecting your site', 'wp-cerber' ) . "\n\n";
				$body .= __( 'Getting Started Guide', 'wp-cerber' ) . "\n\n";
				$body .= 'https://wpcerber.com/getting-started/' . "\n\n";
				$body .= 'Is your website under Cloudflare? You have to enable a crucial WP Cerber setting.' . "\n\n";
				$body .= 'https://wpcerber.com/cloudflare-and-wordpress-cerber/' . "\n\n";
				$body .= 'Be in touch with the developer.' . "\n\n";
				$body .= 'Follow Cerber on X: https://twitter.com/wpcerber' . "\n\n";
				$body .= "Subscribe to Cerber's newsletter: https://wpcerber.com/subscribe-newsletter/" . "\n\n";
				break;
			case 'newlurl':
				$subj .= __( 'New Custom login URL', 'wp-cerber' );
				$body .= $msg;
				break;
			case 'send_alert':
				$body = __( 'A new activity has occurred', 'wp-cerber' ) . "\n\n";
				$body_masked = $body;
				$body .= $msg;
				$body_masked .= $msg_masked;
				break;
			case 'report':
				list ( $title, $body ) = cerber_generate_email_report( $args );
				$subj .= $title;
				$link = cerber_admin_link( 'notifications', array(), false, false );
				$body .= '<br/>' . __( 'To change reporting settings visit', 'wp-cerber' ) . ' <a href="' . $link . '">' . $link . '</a>';
				$body .= $msg;
				$html_mode = true;
				break;
			case 'scan':
				$subj .= __( 'Scanner Report', 'wp-cerber' );
				$body = $msg;

				$link = cerber_admin_link( 'scan_main', array(), false, false );
				$body .= '<p>' . __( 'To view full report, navigate to:', 'wp-cerber' ) . ' <a href="' . $link . '">' . $link . '</a></p>';

				$link = cerber_admin_link( 'scan_schedule', array(), false, false );
				$body .= '<br/>' . __( 'To modify your reporting settings, navigate to:', 'wp-cerber' ) . ' <a href="' . $link . '">' . $link . '</a>';

				$html_mode = true;
				break;
			case 'generic':
			case '2fa':
			default:
				$body = $msg;
				break;
		}

		$to_list = array();
		$to = '';

		if ( $channels['email'] ) {
			$to_list = crb_build_email_recipients( $msg_type, $args );
			$to = implode( ', ', $to_list );
		}

		$body_filtered = apply_filters( 'cerber_notify_body', $body, array(
			'type'    => $msg_type,
			'IP'      => $ip,
			'to'      => $to,
			'subject' => $subj
		) );

		if ( $body_filtered && is_string( $body_filtered ) ) {
			$body = $body_filtered;
		}

		if ( ! $body ) {
			//return new WP_Error( 'notifications', 'No text of the message provided.' );
			return false;
		}

		$footer = '';
		$mf = crb_get_settings( 'email_format' );

		if ( ( 1 > $mf )
		     && ! in_array( $msg_type, array( 'shutdown', 'generic', '2fa' ) )
		     && $lolink = cerber_get_custom_login_url() ) {

			$lourl = urldecode( $lolink );

			if ( $html_mode ) {
				$lourl = '<a href="' . $lolink . '">' . $lourl . '</a>';
			}

			/* translators: %s is the custom login page URL. */
			$footer .= "\n\n" . sprintf( __( 'Your login page: %s', 'wp-cerber' ), $lourl );

		}

		if ( ( 1 > $mf )
		     && $msg_type == 'report'
		     && $date = lab_lab( 1 ) ) {
			/* translators: %s is the license expiration date. */
			$footer .= "\n\n" . sprintf( __( 'Your license is valid until %s', 'wp-cerber' ), $date );
		}

		if ( $msg_type != '2fa' ) {
			/* translators: %s is the plugin name and version. */
			$footer .= "\n\n\n" . sprintf( __( 'This message was created by %s', 'wp-cerber' ), 'WP Cerber Security ' . ( lab_lab() ? 'Professional ' : '' ) . ( 1 > $mf ? CERBER_VER : '' ) );
			/* translators: %s is the date. */
			$footer .= "\n" . sprintf( __( 'Date: %s', 'wp-cerber' ), cerber_date( time(), false ) );
			$footer .= "\n" . 'https://wpcerber.com';
		}

		// Everything is prepared, let's send it out

		$results = array();
		$recipients = array();
		$success = false;

		$go = 'pushbullet';
		if ( $channels[ $go ] && ! $html_mode ) {
			$body_go = ( $msg_type == 'send_alert' && crb_get_settings( 'pb_mask' ) ) ? $body_masked : $body;
			$res = cerber_pb_send( $subj, $body_go, $more, $footer );
			if ( $res && ! crb_is_wp_error( $res ) ) {
				$results[ $go ] = true;
				$recipients[ $go ] = cerber_pb_get_active();
				$success = true;
			}
		}

		$go = 'email';
		if ( $channels[ $go ] ) {
			$body_go = ( $msg_type == 'send_alert' && crb_get_settings( 'email_mask' ) ) ? $body_masked : $body;
			if ( $results[ $go ] = $this->send_email( $msg_type, $html_mode, $to_list, $subj, $body_go, $more, $footer, $ip ) ) {
				$recipients[ $go ] = $to;
				$success = true;
			}
		}

		if ( ! $success ) {

			return false;
		}

		$sent = cerber_get_set( '_cerber_last_send' );

		if ( ! is_array( $sent ) ) {
			$sent = array();
		}

		foreach ( $results as $channel_id => $result ) {
			$sent[ $channel_id ] = ( $result ) ? time() : 0;
		}

		cerber_update_set( '_cerber_last_send', $sent );

		return $recipients;
	}

	/**
	 * Sends emails. Acts as a go_send_email() wrapper.
	 *
	 * @see self::go_send_email()
	 *
	 * @param string $type
	 * @param bool $html_mode
	 * @param array $to_list
	 * @param string $subj
	 * @param string $body
	 * @param string $more
	 * @param string $footer
	 * @param string $ip
	 *
	 * @return bool
	 *
	 * @since 9.5.5.1
	 */
	private function send_email( $type, $html_mode, $to_list, $subj, $body, $more, $footer, $ip ) {

		if ( function_exists( 'wp_mail' ) ) {
			return $this->go_send_email( $type, $html_mode, $to_list, $subj, $body, $more, $footer, $ip );
		}

		// Here wp_mail() is not yet defined
		// We postpone sending to the moment when we expect it is defined

		$this->email_args[] = func_get_args();

		add_action( 'plugins_loaded', array( $this, 'launch_send_email' ) );

		// If 'plugins_loaded' is not invoked - e.g. HTTP redirection occurred before
		register_shutdown_function( [ $this, 'launch_send_email' ] );

		// We have to return the result of sending, but at this pont, we have no idea how the postponed sending will end
		// We hope it will be OK.
		return true;
	}

	/**
	 * Sends emails if sending was postponed due to wp_mail() is not defined
	 *
	 * @return void
	 *
	 * @since 9.5.5.1
	 */
	public function launch_send_email() {

		if ( ! $this->email_args ) {
			return;
		}

		foreach ( $this->email_args as $item ) {
			$this->go_send_email( ...$item );
		}

		$this->email_args = array();
	}

	/**
	 * Sends email
	 *
	 * @param string $type
	 * @param bool $html_mode
	 * @param array $to_list
	 * @param string $subj
	 * @param string $body
	 * @param string $more
	 * @param string $footer
	 * @param string $ip
	 *
	 * @return bool
	 *
	 * @since 8.9.6.1
	 */
	private function go_send_email( $type, $html_mode, $to_list, $subj, $body, $more, $footer, $ip ) {

		$this->error = false;
		add_action( 'wp_mail_failed', array( $this, 'process_email_errors' ) );

		if ( $html_mode ) {
			add_filter( 'wp_mail_content_type', 'cerber_enable_html' );
			$footer = str_replace( "\n", '<br/>', $footer );
		}

		if ( crb_get_settings( 'email_format' ) < 2 ) {
			$body .= $more;
		}

		$this->enable_smtp();

		$result = false;
		$to = implode( ', ', $to_list );

		if ( $to_list && $subj && $body ) {

			$lang = crb_get_bloginfo( 'language' );

			if ( $type == 'report') {

				$result = true;

				foreach ( $to_list as $email ) {

					$lastus = '';

					if ( ( $user = get_user_by( 'email', $email ) )
					     && $last = crb_get_last_user_login( $user->ID ) ) {

						$last_ip = crb_get_settings( 'email_mask' ) ? crb_mask_ip( $last['ip'] ) : $last['ip'];

						/* translators: Here the first placeholder %s is a date of the last user login and the second placeholder %s is the user's IP address. */
						$lastus = sprintf( __( 'Your last sign-in was at %1$s from the IP address %2$s', 'wp-cerber' ), cerber_date( $last['ts'], false ), $last_ip );

						if ( $country = crb_get_country_name( $last['cn'] ) ) {
							$lastus .= ' (' . $country . ')';
						}

						if ( $html_mode ) {
							$lastus = '<br/><br/>' . $lastus;
						}
						else {
							$lastus = "\n\n" . $lastus;
						}
					}

					$body = '<html lang="' . $lang . '">' . $body . $lastus . $footer . '</html>';

					if ( ! $this->transmit_email( $email, $subj, $body ) ) {
						$result = false;
					}
				}
			}
			else {

				$result = true;
				$body = $body . $footer;

				if ( $html_mode ) {
					$body = '<html lang="' . $lang . '">' . $body . '</html>';
				}

				foreach ( $to_list as $email ) {
					if ( ! $this->transmit_email( $email, $subj, $body ) ) {
						$result = false;
					}
				}
			}
		}

		$this->disable_smtp();

		remove_filter('wp_mail_content_type', 'cerber_enable_html');

		remove_action( 'wp_mail_failed', array( $this, 'process_email_errors' ) );

		$params = array( 'type' => $type, 'IP' => $ip, 'to' => $to, 'subject' => $subj );

		if ( $result ) {
			do_action( 'cerber_notify_sent', $body, $params );

			if ( ! $this->error ) { // Delete error occurred during a previous sending only
				CRB_Issues::delete_item( self::SEND_ISSUE_CODE);
			}
		}
		else {
			do_action( 'cerber_notify_fail', $body, $params );
		}

		return $result;
	}

	/**
	 * Send out one email message.
	 * If SMTP sending has failed, use generic wp_mail() without SMTP.
	 *
	 * @param string|string[] $email
	 * @param string $subj
	 * @param string $body
	 *
	 * @return bool
	 *
	 * @since 9.5.5.1
	 */
	private function transmit_email( $email, $subj, $body ) {

		crb_load_dependencies( 'wp_mail' );  // It's crucial in some configurations

		if ( ( ! $result = wp_mail( $email, $subj, $body ) )
		     && $this->smpt_enabled ) {

			$this->no_smtp = true;

			if ( ! $result = wp_mail( $email, $subj, $body ) ) {
				$this->no_smtp = false;
			}
		}

		return $result;
	}

	/**
	 * @param PHPMailer $pm
	 *
	 * @return void
	 *
	 * @since 8.9.6.3
	 */
	public function set_smtp_credentials( $pm ) {
		if ( ! lab_lab()
		     || $this->no_smtp ) {
			return;
		}

		$config = crb_get_settings();

		$pm->isSMTP();
		$pm->SMTPAuth = true;
		$pm->Timeout = 5;
		$pm->Host = $config['smtp_host'];
		$pm->Port = $config['smtp_port'];
		$pm->Username = $config['smtp_user'];
		$pm->Password = $config['smtp_pwd'];

		// For better deliverability we use "smtp_user"
		$pm->From = ( $config['smtp_from'] ) ?: $config['smtp_user'];

		if ( $config['smtp_from_name'] ) {
			$pm->FromName = $config['smtp_from_name'];
		}

		if ( $config['smtp_encr'] ) {
			$pm->SMTPSecure = $config['smtp_encr'];
		}

		$this->save_mailer( array( &$pm ) );

	}

	/**
	 * Set up hooks to configure PHPMailer if SMTP configured
	 *
	 * @return void
	 *
	 * @since 9.5.5.1
	 */
	private function enable_smtp() {
		if ( ! $this->smpt_enabled = (bool) crb_get_settings( 'use_smtp' ) ) {
			return;
		}

		add_action( 'phpmailer_init', array( $this, 'set_smtp_credentials' ) );
	}

	/**
	 * Remove our hooks
	 *
	 * @return void
	 *
	 * @since 9.5.5.1
	 */
	private function disable_smtp() {
		if ( ! $this->smpt_enabled ) {
			return;
		}

		remove_action( 'phpmailer_init', array( $this, 'set_smtp_credentials' ) );

		// Warn the website admin if the wp_mail() function is redefined somewhere or/and our SMTP settings were not used

		if ( is_admin() && crb_get_query_params( 'cerber_admin_do' ) ) {
			$mailer = $this->mailer;

			if ( ! ( $mailer instanceof PHPMailer )
			     || $mailer->Host != crb_get_settings( 'smtp_host' )
			     || $mailer->Port != crb_get_settings( 'smtp_port' )
			     || $mailer->Username != crb_get_settings( 'smtp_user' ) ) {
				cerber_admin_notice( "Warning: The WP Cerber SMTP settings were not used while sending email. They can be altered by another plugin." );
				$alien = true;
			}
			else {
				$alien = false;
			}

			if ( $alien && $mailer->Host ) {
				cerber_admin_notice( 'Warning: The email was sent using the host ' . $mailer->Host . ' and the user ' . $mailer->Username );
			}
		}
	}

	/**
	 * Saves and provides access to last used PHPMailer configuration
	 *
	 * @param PHPMailer $pm
	 *
	 * @return PHPMailer|null
	 *
	 * @since 8.9.6.4
	 */
	private function save_mailer( $pm = null ) {

		if ( isset( $pm[0] ) ) {
			$this->mailer = $pm[0];
		}

		return $this->mailer;
	}

	/**
	 * A wp_mail() error handler
	 *
	 * @param WP_Error $error
	 *
	 * @return void
	 *
	 * @since 9.5.5.1
	 */
	public function process_email_errors( $error ) {

		$this->error = true;

		if ( ! $error instanceof WP_Error ) {
			return;
		}

		$mailer_data = $error->get_error_data();
		if ( ! is_array( $mailer_data ) ) {
			$mailer_data = array();
		}

		$exception_code = $mailer_data['phpmailer_exception_code'] ?? '';

		if ( is_admin() && crb_get_query_params( 'cerber_admin_do' ) ) {
			cerber_admin_notice( strip_tags( $error->get_error_message() ) . ' (' . $exception_code . ')' );

			return;
		}

		$to = $mailer_data['to'] ?? array();
		if ( ! is_array( $to ) ) {
			$to = array( $to );
		}

		$smtp_host = '';
		$smtp_user = '';
		if ( $this->mailer instanceof PHPMailer ) {
			$smtp_host = (string) $this->mailer->Host;
			$smtp_user = (string) $this->mailer->Username;
		}

		$context_data = array(
			'format_version' => 1,
			'ts'             => time(),
			'remote_ip'      => cerber_get_remote_ip(),
			'error_message'  => strip_tags( $error->get_error_message() ),
			'exception_code' => $exception_code,
			'smtp_host'      => $smtp_host,
			'smtp_user'      => $smtp_user,
			'to'             => $to,
			'subject'        => (string) ( $mailer_data['subject'] ?? '' ),
		);

		CRB_Issues::add( self::SEND_ISSUE_CODE,	__( 'An error occurred while sending email. Security alerts and reports may not reach you. Check email notification settings and recipient addresses.', 'wp-cerber' ),
			array(
				'type'    => CRB_Issues::TYPE_EVENT,
				'context' => array(
					'mailer_data' => $context_data,
				),
			)
		);

	}


	/**
	 * Check sending limits and disable channels if it's needed
	 *
	 * @param array $channels
	 *
	 * @return array
	 *
	 * @since 8.9.6.1
	 *
	 */
	private function check_limits( $channels ) {
		static $ref = array( 'email' => 'emailrate', 'pushbullet' => 'pbrate' );

		if ( ! lab_lab() ) {
			$ref ['pushbullet'] = 'emailrate';
		}

		$limits = array_filter( array_intersect_key( crb_get_settings(), array_flip( $ref ) ) );

		if ( ! $limits ) {
			return $channels;
		}

		$sent = cerber_get_set( '_cerber_last_send' );

		if ( empty( $sent ) ) {
			return $channels;
		}

		foreach ( $ref as $channel_id => $key ) {
			$rate = absint( $limits[ $key ] ?? 0 );
			if ( $rate && ( $sent[ $channel_id ] ?? 0 ) > ( time() - 3600 / $rate ) ) {
				$channels[ $channel_id ] = 0; // Do not send
			}
		}

		return $channels;
	}
}