From d6f7a348c04252d65a405feeefb922142284d96c Mon Sep 17 00:00:00 2001
From: Rebecca Hum <rebecca.hum@automattic.com>
Date: Wed, 25 Oct 2023 15:58:02 -0600
Subject: [PATCH] CANTINA-953: Add `vip_block_wp_mail` filter (#4975)

* Add vip_block_wp_mail filter

* Adds tests

Add tests

* PHP 8.2 fix for dynamic property

PHP 8.2 fix for dynamic property
---
 tests/mock-constants.php |  16 ++++++
 tests/test-vip-mail.php  | 107 +++++++++++++++++++++++++++++++++++++--
 vip-mail.php             |  19 ++++++-
 3 files changed, 137 insertions(+), 5 deletions(-)

diff --git a/tests/mock-constants.php b/tests/mock-constants.php
index 835f70083a..88a169aefc 100644
--- a/tests/mock-constants.php
+++ b/tests/mock-constants.php
@@ -200,3 +200,19 @@ function constant( $constant ) {
 		return Constant_Mocker::constant( $constant );
 	}
 }
+
+namespace Automattic\VIP\Mail {
+	use Automattic\Test\Constant_Mocker;
+
+	function define( $constant, $value ) {
+		return Constant_Mocker::define( $constant, $value );
+	}
+
+	function defined( $constant ) {
+		return Constant_Mocker::defined( $constant );
+	}
+
+	function constant( $constant ) {
+		return Constant_Mocker::constant( $constant );
+	}
+}
diff --git a/tests/test-vip-mail.php b/tests/test-vip-mail.php
index d73a4d0561..535e17f5d3 100644
--- a/tests/test-vip-mail.php
+++ b/tests/test-vip-mail.php
@@ -1,19 +1,28 @@
 <?php
+namespace Automattic\VIP\Mail;
 
 use PHPMailer\PHPMailer\PHPMailer;
+use Automattic\Test\Constant_Mocker;
 
 // phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- PHPMailer does not follow the conventions
 // phpcs:disable WordPressVIPMinimum.Functions.RestrictedFunctions.wp_mail_wp_mail -- we are testing it
 
-class VIP_Mail_Test extends WP_UnitTestCase {
+class VIP_Mail_Test extends \WP_UnitTestCase {
 	public function setUp(): void {
 		parent::setUp();
 		reset_phpmailer_instance();
-		if ( ! defined( 'USE_VIP_PHPMAILER' ) ) {
-			define( 'USE_VIP_PHPMAILER', true );
+		if ( ! Constant_Mocker::defined( 'USE_VIP_PHPMAILER' ) ) {
+			Constant_Mocker::define( 'USE_VIP_PHPMAILER', true );
 		}
 	}
 
+	public function tearDown(): void {
+		parent::tearDown();
+
+		Constant_Mocker::clear();
+		remove_all_filters( 'vip_block_wp_mail' );
+	}
+
 	public function test__all_smtp_servers__not_array() {
 		$GLOBALS['all_smtp_servers'] = false;
 
@@ -67,7 +76,7 @@ public function test__has_tracking_header_with_key() {
 	}
 
 	public function test_load_VIP_PHPMailer() {
-		$this->assertTrue( class_exists( 'VIP_PHPMailer', false ) );
+		$this->assertTrue( class_exists( '\Automattic\VIP\Mail\VIP_PHPMailer', false ) );
 	}
 
 	/**
@@ -132,4 +141,94 @@ public function test_filter_removal(): void {
 
 		self::assertEquals( $expected, $actual );
 	}
+
+	public function test_noop_mailer__filter_only() {
+		set_error_handler( // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_set_error_handler
+			static function ( $errno, $errstr ) {
+				restore_error_handler();
+				throw new \Exception( $errstr, $errno ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
+			},
+			E_ALL
+		);
+		
+		add_filter( 'vip_block_wp_mail', '__return_true' );
+
+		$this->expectException( \Exception::class );
+		$this->expectExceptionMessage( 'VIP_Noop_Mailer::send: skipped sending email with subject `Test` to test@example.com' );
+
+		wp_mail( 'test@example.com', 'Test', 'Should not be sent' );
+
+		restore_error_handler();
+	}
+
+	public function test_noop_mailer__constant_true_filter_false() {
+		set_error_handler( // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_set_error_handler
+			static function ( $errno, $errstr ) {
+				restore_error_handler();
+				throw new \Exception( $errstr, $errno ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
+			},
+			E_ALL
+		);
+
+		Constant_Mocker::define( 'VIP_BLOCK_WP_MAIL', true );
+		add_filter( 'vip_block_wp_mail', '__return_false' );
+
+		$this->expectException( \Exception::class );
+		$this->expectExceptionMessage( 'VIP_Noop_Mailer::send: skipped sending email with subject `Test` to test@example.com' );
+
+		wp_mail( 'test@example.com', 'Test', 'Should not be sent' );
+
+		restore_error_handler();
+	}
+
+	public function test_noop_mailer__constant_only() {
+		set_error_handler( // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_set_error_handler
+			static function ( $errno, $errstr ) {
+				restore_error_handler();
+				throw new \Exception( $errstr, $errno ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
+			},
+			E_ALL
+		);
+
+		Constant_Mocker::define( 'VIP_BLOCK_WP_MAIL', true );
+
+		$this->expectException( \Exception::class );
+		$this->expectExceptionMessage( 'VIP_Noop_Mailer::send: skipped sending email with subject `Test` to test@example.com' );
+
+		wp_mail( 'test@example.com', 'Test', 'Should not be sent' );
+
+		restore_error_handler();
+	}
+
+	public function test_noop_mailer__constant_and_filter_false() {
+		Constant_Mocker::define( 'VIP_BLOCK_WP_MAIL', false );
+		add_filter( 'vip_block_wp_mail', '__return_false' );
+
+		$body = 'Testing should send';
+		wp_mail( 'test@example.com', 'Test', $body );
+
+		$mailer = tests_retrieve_phpmailer_instance();
+
+		$this->assertEquals( $body, $mailer->Body );
+	}
+
+	public function test_noop_mailer__constant_false_filter_true() {
+		set_error_handler( // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_set_error_handler
+			static function ( $errno, $errstr ) {
+				restore_error_handler();
+				throw new \Exception( $errstr, $errno ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
+			},
+			E_ALL
+		);
+
+		Constant_Mocker::define( 'VIP_BLOCK_WP_MAIL', false );
+		add_filter( 'vip_block_wp_mail', '__return_true' );
+
+		$this->expectException( \Exception::class );
+		$this->expectExceptionMessage( 'VIP_Noop_Mailer::send: skipped sending email with subject `Test` to test@example.com' );
+
+		wp_mail( 'test@example.com', 'Test', 'Should not be sent' );
+
+		restore_error_handler();
+	}
 }
diff --git a/vip-mail.php b/vip-mail.php
index 14a030cc7f..9726374494 100644
--- a/vip-mail.php
+++ b/vip-mail.php
@@ -10,6 +10,7 @@
 
 // phpcs:disable Generic.Files.OneObjectStructurePerFile.MultipleFound -- needs refactoring
 // phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- PHPMailer does not follow the conventions
+namespace Automattic\VIP\Mail;
 
 use PHPMailer\PHPMailer\PHPMailer;
 
@@ -45,6 +46,17 @@ protected static function isPermittedPath( $path ) {
 }
 
 class VIP_Noop_Mailer {
+
+	/**
+	 * @var string
+	 */
+	public $subject;
+
+	/**
+	 * @var string
+	 */
+	public $recipients;
+
 	public function __construct( $phpmailer ) {
 		$this->subject    = $phpmailer->Subject ?? '[No Subject]';
 		$this->recipients = implode( ', ', array_keys( $phpmailer->getAllRecipientAddresses() ) );
@@ -80,7 +92,12 @@ private function __construct() {
 	 * @param PHPMailer $phpmailer 
 	 */
 	public function phpmailer_init( &$phpmailer ): void {
-		if ( defined( 'VIP_BLOCK_WP_MAIL' ) && true === constant( 'VIP_BLOCK_WP_MAIL' ) ) {
+		if ( defined( 'VIP_BLOCK_WP_MAIL' ) && true === constant( 'VIP_BLOCK_WP_MAIL' ) ) { // Constant will take precedence over filter
+			$phpmailer = new VIP_Noop_Mailer( $phpmailer );
+			return;
+		}
+
+		if ( true === apply_filters( 'vip_block_wp_mail', false ) ) {
 			$phpmailer = new VIP_Noop_Mailer( $phpmailer );
 			return;
 		}