diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 2a1b28b..3dd9f52 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -15,10 +15,9 @@ jobs: fail-fast: false matrix: php-versions: - - '7.3' - - '7.4' - - '8.0' - '8.1' + - '8.2' + - '8.3' steps: - uses: actions/checkout@v2 @@ -52,10 +51,9 @@ jobs: fail-fast: false matrix: php-versions: - - '7.3' - - '7.4' - - '8.0' - '8.1' + - '8.2' + - '8.3' steps: - uses: actions/checkout@v2 @@ -90,10 +88,9 @@ jobs: fail-fast: false matrix: php-versions: - - '7.3' - - '7.4' - - '8.0' - '8.1' + - '8.2' + - '8.3' services: mysql: diff --git a/CHANGELOG.md b/CHANGELOG.md index ed54684..790ea77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Added +- Add an `EmailManager` to configure `PHPMailer` via environment variables ([#22](https://github.com/studiometa/wp-toolkit/pull/22)) - Add a `Plugin::disable` method to the `Plugin` helper class ([#26](https://github.com/studiometa/wp-toolkit/pull/26)) - Add a `request` helper function ([#26](https://github.com/studiometa/wp-toolkit/pull/26)) - Add a `Request` helper class ([#26](https://github.com/studiometa/wp-toolkit/pull/26)) diff --git a/composer.json b/composer.json index 6b7679a..638a769 100644 --- a/composer.json +++ b/composer.json @@ -6,10 +6,12 @@ "type": "library", "require": { "php": "^8.1", - "symfony/yaml": "^6.4|^7.0", + "monolog/monolog": "^2.9|^3.0", + "psr/log": "^1.1", "studiometa/webpack-config": "^5.0", - "wecodemore/wordpress-early-hook": "^1.2", - "symfony/http-foundation": "^6.4|^7.0" + "symfony/http-foundation": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0", + "wecodemore/wordpress-early-hook": "^1.2" }, "require-dev": { "squizlabs/php_codesniffer": "^3.4", @@ -46,6 +48,7 @@ "allow-plugins": { "dealerdirect/phpcodesniffer-composer-installer": true, "phpstan/extension-installer": true - } + }, + "sort-packages": true } } diff --git a/composer.lock b/composer.lock index 405f3e3..f87375c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f46208b8750d1d7d0f6a899a2cf219b8", + "content-hash": "89f9329c94e91984010b7c885d1eda52", "packages": [ { "name": "anahkiasen/html-object", @@ -52,6 +52,158 @@ }, "time": "2017-05-31T07:52:45+00:00" }, + { + "name": "monolog/monolog", + "version": "2.9.1", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "f259e2b15fb95494c83f52d3caad003bbf5ffaa1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/f259e2b15fb95494c83f52d3caad003bbf5ffaa1", + "reference": "f259e2b15fb95494c83f52d3caad003bbf5ffaa1", + "shasum": "" + }, + "require": { + "php": ">=7.2", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "provide": { + "psr/log-implementation": "1.0.0 || 2.0.0 || 3.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^2.4.9 || ^3.0", + "doctrine/couchdb": "~1.0@dev", + "elasticsearch/elasticsearch": "^7 || ^8", + "ext-json": "*", + "graylog2/gelf-php": "^1.4.2 || ^2@dev", + "guzzlehttp/guzzle": "^7.4", + "guzzlehttp/psr7": "^2.2", + "mongodb/mongodb": "^1.8", + "php-amqplib/php-amqplib": "~2.4 || ^3", + "phpspec/prophecy": "^1.15", + "phpstan/phpstan": "^0.12.91", + "phpunit/phpunit": "^8.5.14", + "predis/predis": "^1.1 || ^2.0", + "rollbar/rollbar": "^1.3 || ^2 || ^3", + "ruflin/elastica": "^7", + "swiftmailer/swiftmailer": "^5.3|^6.0", + "symfony/mailer": "^5.4 || ^6", + "symfony/mime": "^5.4 || ^6" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler", + "ext-mbstring": "Allow to work properly with unicode symbols", + "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", + "ext-openssl": "Required to send log messages using SSL", + "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "https://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "support": { + "issues": "https://github.com/Seldaek/monolog/issues", + "source": "https://github.com/Seldaek/monolog/tree/2.9.1" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" + } + ], + "time": "2023-02-06T13:44:46+00:00" + }, + { + "name": "psr/log", + "version": "1.1.4", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/1.1.4" + }, + "time": "2021-05-03T11:20:27+00:00" + }, { "name": "studiometa/webpack-config", "version": "5.3.0", diff --git a/src/Managers/EmailManager.php b/src/Managers/EmailManager.php new file mode 100644 index 0000000..eeda05d --- /dev/null +++ b/src/Managers/EmailManager.php @@ -0,0 +1,115 @@ +logger = $logger; + } elseif (env('MAIL_LOG')) { + $this->logger = new Logger('email'); + $this->logger->pushHandler(new StreamHandler(env('MAIL_LOG'))); + } + } + + /** + * {@inheritdoc} + */ + public function run() + { + if ('smtp' === env('MAIL_MAILER')) { + add_action('phpmailer_init', array( $this, 'configure_smtp' )); + } + + if ($this->logger) { + add_action('wp_mail_succeeded', array( $this, 'log_success' )); + add_action('wp_mail_failed', array( $this, 'log_failure' )); + } + } + + /** + * Configure SMTP server to send mails. + * + * @param PHPMailer $mailer The PHPMailer instance. + * @return void + */ + public function configure_smtp(PHPMailer $mailer): void + { + // phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase + $mailer->Host = env('MAIL_HOST'); + $mailer->Port = (int) env('MAIL_PORT'); + $mailer->Username = env('MAIL_USERNAME'); + $mailer->Password = env('MAIL_PASSWORD'); + $mailer->SMTPAuth = 'true' === env('MAIL_AUTH'); + $mailer->SMTPSecure = env('MAIL_ENCRYPTION'); + $mailer->IsSMTP(); + // phpcs:enable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase + } + + /** + * Log successfully sent mails. + * + * @param array{ + * to: string[], + * subject: string, + * message: string, + * headers: string[], + * attachments: string[] + * } $mail_data An array containing the email recipient(s), subject, message, headers, and attachments. + * + * @return void + */ + public function log_success(array $mail_data): void + { + if ($this->logger) { + $this->logger->info('Mail sent', $mail_data); + } + } + + /** + * Log failure happening when sending mails. + * + * @param WP_Error $error The error sent. + * + * @return void + */ + public function log_failure(WP_Error $error): void + { + if ($this->logger) { + /** + * Mail data. + * + * @var array + */ + $mail_data = $error->get_error_data(); + $this->logger->error($error->get_error_message(), $mail_data); + } + } +} diff --git a/tests/Managers/EmailManagerTest.php b/tests/Managers/EmailManagerTest.php new file mode 100644 index 0000000..617fe1f --- /dev/null +++ b/tests/Managers/EmailManagerTest.php @@ -0,0 +1,48 @@ +run(); + + // Trigger phpmailer configuration action. + global $phpmailer; + assert($phpmailer instanceof PHPMailer); + do_action_ref_array('phpmailer_init', array( &$phpmailer )); + + // phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase + $this->assertSame($phpmailer->Mailer, 'smtp'); + $this->assertSame($phpmailer->Host, '127.0.0.1'); + $this->assertSame($phpmailer->Port, 1025); + $this->assertSame($phpmailer->Username, 'test'); + $this->assertSame($phpmailer->Password, 'test'); + $this->assertSame($phpmailer->SMTPAuth, true); + $this->assertSame($phpmailer->SMTPSecure, 'tls'); + // phpcs:enable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase + } +}