diff --git a/mailpoet/lib/API/JSON/v1/Settings.php b/mailpoet/lib/API/JSON/v1/Settings.php index 382f1fd545..d225de83d9 100644 --- a/mailpoet/lib/API/JSON/v1/Settings.php +++ b/mailpoet/lib/API/JSON/v1/Settings.php @@ -22,6 +22,7 @@ use MailPoet\Settings\SettingsController; use MailPoet\Settings\TrackingConfig; use MailPoet\Statistics\StatisticsOpensRepository; use MailPoet\Subscribers\SubscribersCountsController; +use MailPoet\Util\Notices\DisabledMailFunctionNotice; use MailPoet\WooCommerce\TransactionalEmails; use MailPoet\WP\Functions as WPFunctions; use MailPoetVendor\Carbon\Carbon; @@ -147,6 +148,17 @@ class Settings extends APIEndpoint { $this->messageController->updateSuccessMessages(); } + $sendingMethodSet = $settings['mta']['method'] ?? null; + if ($sendingMethodSet === 'PHPMail') { + // check for valid mail function + $this->settings->set(DisabledMailFunctionNotice::QUEUE_DISABLED_MAIL_FUNCTION_CHECK, true); + } else { + // when the user switch to a new sending method + // do not display the DisabledMailFunctionNotice + $this->settings->set(DisabledMailFunctionNotice::QUEUE_DISABLED_MAIL_FUNCTION_CHECK, false); + $this->settings->set(DisabledMailFunctionNotice::OPTION_NAME, false); // do not display notice + } + // Tracking and re-engagement Emails $meta['showNotice'] = false; if ($oldSettings['tracking'] !== $this->settings->get('tracking')) { diff --git a/mailpoet/lib/Config/Activator.php b/mailpoet/lib/Config/Activator.php index ebb339ff5e..e09fd42516 100644 --- a/mailpoet/lib/Config/Activator.php +++ b/mailpoet/lib/Config/Activator.php @@ -7,6 +7,7 @@ use MailPoet\Cron\CronTrigger; use MailPoet\InvalidStateException; use MailPoet\Migrator\Migrator; use MailPoet\Settings\SettingsController; +use MailPoet\Util\Notices\DisabledMailFunctionNotice; use MailPoet\WP\Functions as WPFunctions; use MailPoetVendor\Doctrine\DBAL\Connection; @@ -81,6 +82,8 @@ class Activator { $localizer = new Localizer(); $localizer->forceInstallLanguagePacks($this->wp); + + $this->checkForDisabledMailFunction(); } public function deactivate() { @@ -128,6 +131,15 @@ class Activator { } } + private function checkForDisabledMailFunction() { + $sendingMethodSet = $this->settings->get('mta.method', false); + + if ($sendingMethodSet === 'PHPMail' || is_bool($sendingMethodSet)) { + // check for valid mail function + $this->settings->set(DisabledMailFunctionNotice::QUEUE_DISABLED_MAIL_FUNCTION_CHECK, true); + } + } + private function deleteAllMailPoetTablesAndData(): void { $prefix = Env::$dbPrefix; if (!$prefix) { diff --git a/mailpoet/lib/Util/Notices/DisabledMailFunctionNotice.php b/mailpoet/lib/Util/Notices/DisabledMailFunctionNotice.php index 4d4c40facf..24835ba59d 100644 --- a/mailpoet/lib/Util/Notices/DisabledMailFunctionNotice.php +++ b/mailpoet/lib/Util/Notices/DisabledMailFunctionNotice.php @@ -3,6 +3,7 @@ namespace MailPoet\Util\Notices; use MailPoet\Mailer\Mailer; +use MailPoet\Mailer\MailerFactory; use MailPoet\Settings\SettingsController; use MailPoet\Util\Helpers; use MailPoet\Util\License\Features\Subscribers as SubscribersFeature; @@ -11,7 +12,9 @@ use MailPoet\WP\Notice; class DisabledMailFunctionNotice { - const OPTION_NAME = 'disabled-mail-function'; + const OPTION_NAME = 'disabled_mail_function_check'; + + const QUEUE_DISABLED_MAIL_FUNCTION_CHECK = 'queue_disabled_mail_function_check'; /** @var SettingsController */ private $settings; @@ -22,18 +25,25 @@ class DisabledMailFunctionNotice { /** @var SubscribersFeature */ private $subscribersFeature; + /** @var MailerFactory */ + private $mailerFactory; + + private $isInQueueForChecking = false; + public function __construct( WPFunctions $wp, SettingsController $settings, - SubscribersFeature $subscribersFeature + SubscribersFeature $subscribersFeature, + MailerFactory $mailerFactory ) { $this->settings = $settings; $this->wp = $wp; $this->subscribersFeature = $subscribersFeature; + $this->mailerFactory = $mailerFactory; } public function init($shouldDisplay): ?string { - $shouldDisplay = $shouldDisplay && $this->checkRequirements(); + $shouldDisplay = $shouldDisplay && $this->checkMisConfiguredFunction() && $this->checkRequirements(); if (!$shouldDisplay) { return null; } @@ -42,13 +52,43 @@ class DisabledMailFunctionNotice { } private function checkRequirements(): bool { + if ($this->isInQueueForChecking) { + $this->settings->set(self::QUEUE_DISABLED_MAIL_FUNCTION_CHECK, false); + } + $sendingMethod = $this->settings->get('mta.method', false); $isPhpMailSendingMethod = $sendingMethod === Mailer::METHOD_PHPMAIL; + if (!$isPhpMailSendingMethod) { + return false; // fails requirements check + } + $functionName = 'mail'; $isMailFunctionDisabled = $this->isFunctionDisabled($functionName); - return $isPhpMailSendingMethod && $isMailFunctionDisabled; + if ($isMailFunctionDisabled) { + $this->settings->set(DisabledMailFunctionNotice::OPTION_NAME, true); + return true; + } + + $isMailFunctionProperlyConfigured = $this->testMailFunctionIsCorrectlyConfigured(); + + return !$isMailFunctionProperlyConfigured; + } + + /* + * Check MisConfigured Function + * + * This method will cause this class to only display the notice if the settings option + * + * disabled_mail_function_check === true + * or + * queue_disabled_mail_function_check === true + * + */ + public function checkMisConfiguredFunction(): bool { + $this->isInQueueForChecking = $this->settings->get(self::QUEUE_DISABLED_MAIL_FUNCTION_CHECK, false); + return $this->settings->get(self::OPTION_NAME, false) || $this->isInQueueForChecking; } public function isFunctionDisabled(string $function): bool { @@ -90,4 +130,87 @@ class DisabledMailFunctionNotice { $link = $this->wp->escAttr($buttonLink); return '
' . __('Connect MailPoet', 'mailpoet') . '
'; } + + /* + * Test Mail Function Is Correctly Configured + * + * This is a workaround for detecting the user PHP mail() function is Correctly Configured and not disabled by the host + */ + private function testMailFunctionIsCorrectlyConfigured(): bool { + if ($this->settings->get(DisabledMailFunctionNotice::OPTION_NAME, false)) { + return false; // skip sending mail again + } + + $mailBody = 'Yup, it works! You can start blasting away emails to the moon.'; + $sendTestMailData = [ + 'mailer' => $this->settings->get('mta'), + 'newsletter' => [ + 'subject' => 'This is a Sending Method Test', + 'body' => [ + 'html' => "$mailBody
", + 'text' => $mailBody, + ], + ], + 'subscriber' => 'blackhole@mailpoet.com', + ]; + + $sendMailResult = $this->sendTestMail($sendTestMailData); + + if (!$sendMailResult) { + // Error with PHP mail() function + // keep displaying notice + $this->settings->set(DisabledMailFunctionNotice::OPTION_NAME, true); + } + + return $sendMailResult; + } + + /* + * Send Test Mail + * used to check for valid PHP mail() + * + * returns true if valid and okay + * else returns false if invalid. + * + * We determine the mail function is invalid by checking against the Exception error thrown by PHPMailer + * error message: Could not instantiate mail function. + * + * if the error is not equal to error message, we consider it okay. + */ + public function sendTestMail($data = []): bool { + try { + $mailer = $this->mailerFactory->buildMailer( + $data['mailer'] ?? null, + $data['sender'] ?? null, + $data['reply_to'] ?? null + ); + // report this as 'sending_test' in metadata since this endpoint is only used to test sending methods for now + $extraParams = [ + 'meta' => [ + 'email_type' => 'sending_test', + 'subscriber_status' => 'unknown', + 'subscriber_source' => 'administrator', + ], + ]; + $result = $mailer->send($data['newsletter'], $data['subscriber'], $extraParams); + + if ($result['response'] === false) { + $errorMessage = $result['error']->getMessage(); + return !$this->checkForErrorMessage($errorMessage); + } + + } catch (\Exception $e) { + $errorMessage = $e->getMessage(); + return !$this->checkForErrorMessage($errorMessage); + } + + return true; + } + + private function checkForErrorMessage($errorMessage): bool { + $phpmailerError = 'Could not instantiate mail function'; + $substringIndex = stripos($errorMessage, $phpmailerError); + + return $substringIndex !== false; + } } diff --git a/mailpoet/lib/Util/Notices/PermanentNotices.php b/mailpoet/lib/Util/Notices/PermanentNotices.php index bc255e9604..51338992c9 100644 --- a/mailpoet/lib/Util/Notices/PermanentNotices.php +++ b/mailpoet/lib/Util/Notices/PermanentNotices.php @@ -3,6 +3,7 @@ namespace MailPoet\Util\Notices; use MailPoet\Config\Menu; +use MailPoet\Mailer\MailerFactory; use MailPoet\Settings\SettingsController; use MailPoet\Settings\TrackingConfig; use MailPoet\Subscribers\SubscribersRepository; @@ -52,7 +53,8 @@ class PermanentNotices { TrackingConfig $trackingConfig, SubscribersRepository $subscribersRepository, SettingsController $settings, - SubscribersFeature $subscribersFeature + SubscribersFeature $subscribersFeature, + MailerFactory $mailerFactory ) { $this->wp = $wp; $this->phpVersionWarnings = new PHPVersionWarnings(); @@ -65,7 +67,7 @@ class PermanentNotices { $this->emailWithInvalidListNotice = new EmailWithInvalidSegmentNotice($wp); $this->changedTrackingNotice = new ChangedTrackingNotice($wp); $this->deprecatedFilterNotice = new DeprecatedFilterNotice($wp); - $this->disabledMailFunctionNotice = new DisabledMailFunctionNotice($wp, $settings, $subscribersFeature); + $this->disabledMailFunctionNotice = new DisabledMailFunctionNotice($wp, $settings, $subscribersFeature, $mailerFactory); } public function init() { diff --git a/mailpoet/tests/integration/Util/Notices/DisabledMailFunctionNoticeTest.php b/mailpoet/tests/integration/Util/Notices/DisabledMailFunctionNoticeTest.php index 30e37304ed..8b02fc7722 100644 --- a/mailpoet/tests/integration/Util/Notices/DisabledMailFunctionNoticeTest.php +++ b/mailpoet/tests/integration/Util/Notices/DisabledMailFunctionNoticeTest.php @@ -4,6 +4,7 @@ namespace MailPoet\Util\Notices; use Codeception\Util\Stub; use MailPoet\Mailer\Mailer; +use MailPoet\Mailer\MailerFactory; use MailPoet\Settings\SettingsController; use MailPoet\Util\License\Features\Subscribers as SubscribersFeature; use MailPoet\WP\Functions as WPFunctions; @@ -21,6 +22,7 @@ class DisabledMailFunctionNoticeTest extends \MailPoetTest $this->settings = SettingsController::getInstance(); $this->wp = new WPFunctions; $this->settings->set('mta.method', Mailer::METHOD_PHPMAIL); + $this->settings->set(DisabledMailFunctionNotice::QUEUE_DISABLED_MAIL_FUNCTION_CHECK, true); $this->wp->setTransient(SubscribersFeature::SUBSCRIBERS_COUNT_CACHE_KEY, 50, SubscribersFeature::SUBSCRIBERS_COUNT_CACHE_EXPIRATION_MINUTES * 60); } @@ -35,9 +37,13 @@ class DisabledMailFunctionNoticeTest extends \MailPoetTest } private function generateNotice($methodOverride = []) { + $mailerFactoryMock = $this->createMock(MailerFactory::class); + $mailerFactoryMock->method('buildMailer') + ->willReturn($this->createMock(Mailer::class)); + return Stub::construct( DisabledMailFunctionNotice::class, - [$this->wp, $this->settings, $this->diContainer->get(SubscribersFeature::class)], + [$this->wp, $this->settings, $this->diContainer->get(SubscribersFeature::class), $mailerFactoryMock], $methodOverride ); } @@ -81,4 +87,23 @@ class DisabledMailFunctionNoticeTest extends \MailPoetTest expect($result)->equals(false); } + public function testItResetQueueCheck() { + $this->settings->set(DisabledMailFunctionNotice::QUEUE_DISABLED_MAIL_FUNCTION_CHECK, true); + $disabledMailFunctionNotice = $this->generateNotice(); + $notice = $disabledMailFunctionNotice->init(true); + + expect($notice)->equals(null); + $status = $this->settings->get(DisabledMailFunctionNotice::QUEUE_DISABLED_MAIL_FUNCTION_CHECK, false); + expect($status)->equals(false); + } + + public function testItDisplayNoticeWhenMailIsMisConfigured() { + $disabledMailFunctionNotice = $this->generateNotice(['sendTestMail' => false]); + $notice = $disabledMailFunctionNotice->init(true); + + expect($notice)->stringContainsString('Get ready to send your first campaign'); + + $status = $this->settings->get(DisabledMailFunctionNotice::OPTION_NAME, false); + expect($status)->equals(true); + } }