diff --git a/.github/workflows/auto-merge.yml b/.github/workflows/auto-merge.yml
index 6a94f3a4..4cdd3013 100644
--- a/.github/workflows/auto-merge.yml
+++ b/.github/workflows/auto-merge.yml
@@ -16,10 +16,10 @@ jobs:
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
- - uses: actions/checkout@v6.0.2
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
token: ${{ secrets.GITHUB_TOKEN }}
- - uses: ahmadnassri/action-dependabot-auto-merge@v2.6.6
+ - uses: ahmadnassri/action-dependabot-auto-merge@45fc124d949b19b6b8bf6645b6c9d55f4f9ac61a # v2.6.6
with:
target: patch
github-token: ${{ secrets.ADMIN_GITHUB_TOKEN }}
diff --git a/.github/workflows/deploy-application.yml b/.github/workflows/deploy-application.yml
index ecdc540b..1a0a78d9 100644
--- a/.github/workflows/deploy-application.yml
+++ b/.github/workflows/deploy-application.yml
@@ -27,17 +27,21 @@ jobs:
steps:
# Deploys specific branch/commit to target environment
- name: Deploy to target
- uses: appleboy/ssh-action@v1.2.5
+ uses: appleboy/ssh-action@0ff4204d59e8e51228ff73bce53f80d53301dee2 # v1.2.5
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
port: ${{ secrets.SSH_PORT }}
key: ${{ secrets.DESTINATION_SSH_PRIVATE_KEY }}
script: |
+ set -euo pipefail
# Non-interactive SSH does not load ~/.bashrc; nvm lives there.
export NVM_DIR="${NVM_DIR:-$HOME/.nvm}"
- [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
+ if [ -s "$NVM_DIR/nvm.sh" ]; then
+ . "$NVM_DIR/nvm.sh"
+ fi
cd ${{ secrets.SSH_PATH }}
+ git stash -m "Stashed by ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}/job/${{ job.check_run_id }}"
git pull
cd web/app/plugins/cbf-multisite
npm ci && npm run build:assets
@@ -54,7 +58,7 @@ jobs:
steps:
# Sets the Node version
- name: Setup Node.js
- uses: actions/setup-node@v6.3.0
+ uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 24
- name: Check site
diff --git a/.github/workflows/deploy-push.yml b/.github/workflows/deploy-push.yml
index 503ac42e..69fadcb8 100644
--- a/.github/workflows/deploy-push.yml
+++ b/.github/workflows/deploy-push.yml
@@ -23,21 +23,21 @@ jobs:
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- - uses: actions/checkout@v6.0.2
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup PHP
- uses: shivammathur/setup-php@2.9.0
+ uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2.37.2
with:
php-version: 8.5
- name: Cache Composer dependencies
- uses: actions/cache@v5.0.5
+ uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: /tmp/composer-cache
key: ${{ runner.os }}-${{ hashFiles('**/composer.lock') }}
- name: Install dependencies
- uses: php-actions/composer@v6.1.2
+ uses: php-actions/composer@8a65f0d3c6a1d17ca4800491a40b5756a4c164f3 # v6.1.2
- name: Install phpcs
run: vendor/bin/phpcs -i
@@ -54,12 +54,12 @@ jobs:
steps:
- name: Checkout repo
- uses: actions/checkout@v6.0.2
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
token: ${{ secrets.ADMIN_GITHUB_TOKEN }}
- name: Setup Node.js
- uses: actions/setup-node@v6.3.0
+ uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 24
diff --git a/.gitignore b/.gitignore
index 1d3e7766..5493cf50 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,6 +20,7 @@ web/app/plugins/buddypages/.git
!web/app/plugins/wp-job-manager-resumes
!web/app/plugins/wp-job-manager-stats
!web/app/plugins/wp-job-manager-wc-paid-listings
+!web/app/plugins/wp-mail-smtp-pro
!web/app/plugins/wp-rocket
!web/app/plugins/wpforms
web/app/mu-plugins/*
diff --git a/.lando.yml b/.lando.yml
index 026f144e..881416d4 100644
--- a/.lando.yml
+++ b/.lando.yml
@@ -2,12 +2,13 @@ name: codingblackfemales
recipe: wordpress
config:
database: mariadb
- php: '8.3'
+ php: "8.5"
via: apache
webroot: web
xdebug: false
config:
php: .lando/config/php/php.ini
+ database: mysql.cnf
# Multisite networks must have each site listed below to work under lando
# and the URLs must match those in the wp_blogs table
proxy:
@@ -17,7 +18,7 @@ proxy:
- jobs.codingblackfemales.lndo.site
services:
appserver:
- type: "php:8.3"
+ type: "php:8.5"
api: 3
composer_version: 2-latest
build_as_root:
@@ -44,7 +45,7 @@ services:
PHP_MEMORY_LIMIT: 256M
NODE_MAJOR: 20
database:
- type: mysql:8.0
+ type: mysql:8.4
api: 3
portforward: 3307
# https://github.com/lando/lando/issues/1668#issuecomment-557090549
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cf5154b8..29be323c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,7 @@
All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines.
+## [0.1.51](https://github.com/CodingBlackFemales/wordpress/compare/v0.1.50...v0.1.51) (2026-07-31)
## [0.1.50](https://github.com/CodingBlackFemales/wordpress/compare/v0.1.49...v0.1.50) (2026-06-04)
diff --git a/composer.json b/composer.json
index f58158be..255e03e7 100644
--- a/composer.json
+++ b/composer.json
@@ -94,14 +94,13 @@
"wp-plugin/redis-cache": "^2.7",
"wp-plugin/remove-dashboard-access-for-non-admins": "^1.2.1",
"wp-plugin/webberzone-code-block-highlighting": "^1.0",
- "wp-plugin/woocommerce": "^10.5.0",
+ "wp-plugin/woocommerce": "^11.0",
"wp-plugin/woocommerce-gateway-stripe": "^10.3.1",
"wp-plugin/woocommerce-paypal-payments": "^4.1.1",
"wp-plugin/woocommerce-services": "^3.4.0",
"wp-plugin/wp-graphql": "^2.8.0",
"wp-plugin/wp-job-manager": "^2.4.0",
"wp-plugin/wp-job-manager-xml-csv-listings-import": "^1.2.1",
- "wp-plugin/wp-mail-smtp": "^4.7.1",
"wp-plugin/wp-maintenance-mode": "^2.6.20",
"wp-plugin/wp-rest-api-authentication": "^4.2.0",
"wp-plugin/zapier": "^1.5.3",
diff --git a/composer.lock b/composer.lock
index be32d57b..f4e2eb08 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": "03560ac9926b36bdaf1747bd370d1fb9",
+ "content-hash": "25418c3dc8870c69d786c3bf6e7236cf",
"packages": [
{
"name": "aws/aws-crt-php",
@@ -62,16 +62,16 @@
},
{
"name": "aws/aws-sdk-php",
- "version": "3.390.0",
+ "version": "3.393.2",
"source": {
"type": "git",
"url": "https://github.com/aws/aws-sdk-php.git",
- "reference": "1aaa45bad3638400da3a7682c9d9f0a96ca46de4"
+ "reference": "0a46ff5138d21adbb312729f564a1c14f54909c9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/1aaa45bad3638400da3a7682c9d9f0a96ca46de4",
- "reference": "1aaa45bad3638400da3a7682c9d9f0a96ca46de4",
+ "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/0a46ff5138d21adbb312729f564a1c14f54909c9",
+ "reference": "0a46ff5138d21adbb312729f564a1c14f54909c9",
"shasum": ""
},
"require": {
@@ -153,9 +153,9 @@
"support": {
"forum": "https://github.com/aws/aws-sdk-php/discussions",
"issues": "https://github.com/aws/aws-sdk-php/issues",
- "source": "https://github.com/aws/aws-sdk-php/tree/3.390.0"
+ "source": "https://github.com/aws/aws-sdk-php/tree/3.393.2"
},
- "time": "2026-07-30T18:09:09+00:00"
+ "time": "2026-08-19T18:38:54+00:00"
},
{
"name": "composer/installers",
@@ -717,21 +717,21 @@
},
{
"name": "guzzlehttp/guzzle",
- "version": "8.0.1",
+ "version": "8.0.2",
"source": {
"type": "git",
"url": "https://github.com/guzzle/guzzle.git",
- "reference": "3aeea0406aab88cbbd86531313d7cebf8ae149a4"
+ "reference": "d1cbca76970939a9c2ced55b1e25ea26f34fc773"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/guzzle/guzzle/zipball/3aeea0406aab88cbbd86531313d7cebf8ae149a4",
- "reference": "3aeea0406aab88cbbd86531313d7cebf8ae149a4",
+ "url": "https://api.github.com/repos/guzzle/guzzle/zipball/d1cbca76970939a9c2ced55b1e25ea26f34fc773",
+ "reference": "d1cbca76970939a9c2ced55b1e25ea26f34fc773",
"shasum": ""
},
"require": {
"ext-json": "*",
- "guzzlehttp/promises": "^3.0",
+ "guzzlehttp/promises": "^3.0.1",
"guzzlehttp/psr7": "^3.0",
"php": "^7.4 || ^8.0",
"psr/http-client": "^1.0",
@@ -823,7 +823,7 @@
],
"support": {
"issues": "https://github.com/guzzle/guzzle/issues",
- "source": "https://github.com/guzzle/guzzle/tree/8.0.1"
+ "source": "https://github.com/guzzle/guzzle/tree/8.0.2"
},
"funding": [
{
@@ -839,20 +839,20 @@
"type": "tidelift"
}
],
- "time": "2026-07-26T23:23:24+00:00"
+ "time": "2026-08-05T19:50:26+00:00"
},
{
"name": "guzzlehttp/promises",
- "version": "3.0.0",
+ "version": "3.0.1",
"source": {
"type": "git",
"url": "https://github.com/guzzle/promises.git",
- "reference": "d961a21387cd680ec1cc6f52aa1fdcef33105e24"
+ "reference": "64f38b87fa7d371853804161bfc701c9bc2cc00a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/guzzle/promises/zipball/d961a21387cd680ec1cc6f52aa1fdcef33105e24",
- "reference": "d961a21387cd680ec1cc6f52aa1fdcef33105e24",
+ "url": "https://api.github.com/repos/guzzle/promises/zipball/64f38b87fa7d371853804161bfc701c9bc2cc00a",
+ "reference": "64f38b87fa7d371853804161bfc701c9bc2cc00a",
"shasum": ""
},
"require": {
@@ -906,7 +906,7 @@
],
"support": {
"issues": "https://github.com/guzzle/promises/issues",
- "source": "https://github.com/guzzle/promises/tree/3.0.0"
+ "source": "https://github.com/guzzle/promises/tree/3.0.1"
},
"funding": [
{
@@ -922,7 +922,7 @@
"type": "tidelift"
}
],
- "time": "2026-07-20T13:44:17+00:00"
+ "time": "2026-08-05T19:35:04+00:00"
},
{
"name": "guzzlehttp/psr7",
@@ -1098,20 +1098,20 @@
},
{
"name": "johnpbloch/wordpress",
- "version": "6.9.5",
+ "version": "6.9.7",
"source": {
"type": "git",
"url": "https://github.com/johnpbloch/wordpress.git",
- "reference": "7f77a4a790c9f2d2c0d861d8f2740ce9da2fadf5"
+ "reference": "596eaa64c2f1f8760f14130f206833846e7ea540"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/johnpbloch/wordpress/zipball/7f77a4a790c9f2d2c0d861d8f2740ce9da2fadf5",
- "reference": "7f77a4a790c9f2d2c0d861d8f2740ce9da2fadf5",
+ "url": "https://api.github.com/repos/johnpbloch/wordpress/zipball/596eaa64c2f1f8760f14130f206833846e7ea540",
+ "reference": "596eaa64c2f1f8760f14130f206833846e7ea540",
"shasum": ""
},
"require": {
- "johnpbloch/wordpress-core": "6.9.5",
+ "johnpbloch/wordpress-core": "6.9.7",
"johnpbloch/wordpress-core-installer": "^1.0 || ^2.0",
"php": ">=7.0.0"
},
@@ -1140,20 +1140,20 @@
"issues": "https://core.trac.wordpress.org/",
"source": "https://core.trac.wordpress.org/browser"
},
- "time": "2026-07-17T18:48:22+00:00"
+ "time": "2026-08-12T22:40:34+00:00"
},
{
"name": "johnpbloch/wordpress-core",
- "version": "6.9.5",
+ "version": "6.9.7",
"source": {
"type": "git",
"url": "https://github.com/johnpbloch/wordpress-core.git",
- "reference": "80e1be210fa43e36b0719f703282c6cff8efa6e0"
+ "reference": "6b9fb51da302606a7f4e43da0153a77281d24fdd"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/johnpbloch/wordpress-core/zipball/80e1be210fa43e36b0719f703282c6cff8efa6e0",
- "reference": "80e1be210fa43e36b0719f703282c6cff8efa6e0",
+ "url": "https://api.github.com/repos/johnpbloch/wordpress-core/zipball/6b9fb51da302606a7f4e43da0153a77281d24fdd",
+ "reference": "6b9fb51da302606a7f4e43da0153a77281d24fdd",
"shasum": ""
},
"require": {
@@ -1161,7 +1161,7 @@
"php": ">=7.2.24"
},
"provide": {
- "wordpress/core-implementation": "6.9.5"
+ "wordpress/core-implementation": "6.9.7"
},
"type": "wordpress-core",
"notification-url": "https://packagist.org/downloads/",
@@ -1188,7 +1188,7 @@
"source": "https://core.trac.wordpress.org/browser",
"wiki": "https://codex.wordpress.org/"
},
- "time": "2026-07-17T18:48:19+00:00"
+ "time": "2026-08-12T22:40:30+00:00"
},
{
"name": "johnpbloch/wordpress-core-installer",
@@ -1246,10 +1246,10 @@
},
{
"name": "koodimonni-language/en_gb",
- "version": "7.0.2",
+ "version": "7.1",
"dist": {
"type": "zip",
- "url": "https://downloads.wordpress.org/translation/core/7.0.2/en_GB.zip"
+ "url": "https://downloads.wordpress.org/translation/core/7.1/en_GB.zip"
},
"require": {
"koodimonni-plugin-language/admin-color-schemer-en_gb": "*",
@@ -1275,6 +1275,7 @@
"koodimonni-plugin-language/optimization-detective-en_gb": "*",
"koodimonni-plugin-language/performant-translations-en_gb": "*",
"koodimonni-plugin-language/rss-importer-en_gb": "*",
+ "koodimonni-plugin-language/secure-custom-fields-en_gb": "*",
"koodimonni-plugin-language/speculation-rules-en_gb": "*",
"koodimonni-plugin-language/sqlite-database-integration-en_gb": "*",
"koodimonni-plugin-language/stp-importer-en_gb": "*",
@@ -1338,10 +1339,10 @@
},
{
"name": "koodimonni-plugin-language/ai-en_gb",
- "version": "1.2.0",
+ "version": "1.3.0",
"dist": {
"type": "zip",
- "url": "https://downloads.wordpress.org/translation/plugin/ai/1.2.0/en_GB.zip"
+ "url": "https://downloads.wordpress.org/translation/plugin/ai/1.3.0/en_GB.zip"
},
"require": {
"koodimonni/composer-dropin-installer": ">=0.2.3"
@@ -1395,10 +1396,10 @@
},
{
"name": "koodimonni-plugin-language/ai-provider-for-openai-en_gb",
- "version": "1.0.3",
+ "version": "1.1.0",
"dist": {
"type": "zip",
- "url": "https://downloads.wordpress.org/translation/plugin/ai-provider-for-openai/1.0.3/en_GB.zip"
+ "url": "https://downloads.wordpress.org/translation/plugin/ai-provider-for-openai/1.1.0/en_GB.zip"
},
"require": {
"koodimonni/composer-dropin-installer": ">=0.2.3"
@@ -1414,10 +1415,10 @@
},
{
"name": "koodimonni-plugin-language/akismet-en_gb",
- "version": "5.7",
+ "version": "5.7.2",
"dist": {
"type": "zip",
- "url": "https://downloads.wordpress.org/translation/plugin/akismet/5.7/en_GB.zip"
+ "url": "https://downloads.wordpress.org/translation/plugin/akismet/5.7.2/en_GB.zip"
},
"require": {
"koodimonni/composer-dropin-installer": ">=0.2.3"
@@ -1699,10 +1700,10 @@
},
{
"name": "koodimonni-plugin-language/optimization-detective-en_gb",
- "version": "1.0.0-beta5",
+ "version": "1.0.0-beta6",
"dist": {
"type": "zip",
- "url": "https://downloads.wordpress.org/translation/plugin/optimization-detective/1.0.0-beta5/en_GB.zip"
+ "url": "https://downloads.wordpress.org/translation/plugin/optimization-detective/1.0.0-beta6/en_GB.zip"
},
"require": {
"koodimonni/composer-dropin-installer": ">=0.2.3"
@@ -1754,6 +1755,25 @@
"rss-importer"
]
},
+ {
+ "name": "koodimonni-plugin-language/secure-custom-fields-en_gb",
+ "version": "6.9.5",
+ "dist": {
+ "type": "zip",
+ "url": "https://downloads.wordpress.org/translation/plugin/secure-custom-fields/6.9.5/en_GB.zip"
+ },
+ "require": {
+ "koodimonni/composer-dropin-installer": ">=0.2.3"
+ },
+ "type": "wordpress-language",
+ "description": "WordPress secure-custom-fields translations for English (UK) - en_gb",
+ "keywords": [
+ "Translation",
+ "WordPress",
+ "en_gb",
+ "secure-custom-fields"
+ ]
+ },
{
"name": "koodimonni-plugin-language/speculation-rules-en_gb",
"version": "1.6.0",
@@ -1775,10 +1795,10 @@
},
{
"name": "koodimonni-plugin-language/sqlite-database-integration-en_gb",
- "version": "2.2.23",
+ "version": "3.0.0",
"dist": {
"type": "zip",
- "url": "https://downloads.wordpress.org/translation/plugin/sqlite-database-integration/2.2.23/en_GB.zip"
+ "url": "https://downloads.wordpress.org/translation/plugin/sqlite-database-integration/3.0.0/en_GB.zip"
},
"require": {
"koodimonni/composer-dropin-installer": ">=0.2.3"
@@ -1889,10 +1909,10 @@
},
{
"name": "koodimonni-plugin-language/woocommerce-en_gb",
- "version": "10.9.4",
+ "version": "11.0.1",
"dist": {
"type": "zip",
- "url": "https://downloads.wordpress.org/translation/plugin/woocommerce/10.9.4/en_GB.zip"
+ "url": "https://downloads.wordpress.org/translation/plugin/woocommerce/11.0.1/en_GB.zip"
},
"require": {
"koodimonni/composer-dropin-installer": ">=0.2.3"
@@ -2393,16 +2413,16 @@
},
{
"name": "mck89/peast",
- "version": "v1.17.7",
+ "version": "v1.18.0",
"source": {
"type": "git",
"url": "https://github.com/mck89/peast.git",
- "reference": "89ee39f81e82cb7ed36cc5abaf2edd43cef71770"
+ "reference": "9c211ef67a0cf7511f864b6d390fe5939a837d05"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/mck89/peast/zipball/89ee39f81e82cb7ed36cc5abaf2edd43cef71770",
- "reference": "89ee39f81e82cb7ed36cc5abaf2edd43cef71770",
+ "url": "https://api.github.com/repos/mck89/peast/zipball/9c211ef67a0cf7511f864b6d390fe5939a837d05",
+ "reference": "9c211ef67a0cf7511f864b6d390fe5939a837d05",
"shasum": ""
},
"require": {
@@ -2415,7 +2435,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.17.7-dev"
+ "dev-master": "1.18.0-dev"
}
},
"autoload": {
@@ -2436,9 +2456,9 @@
"description": "Peast is PHP library that generates AST for JavaScript code",
"support": {
"issues": "https://github.com/mck89/peast/issues",
- "source": "https://github.com/mck89/peast/tree/v1.17.7"
+ "source": "https://github.com/mck89/peast/tree/v1.18.0"
},
- "time": "2026-07-21T11:56:06+00:00"
+ "time": "2026-08-19T09:36:10+00:00"
},
{
"name": "mtdowling/jmespath.php",
@@ -3745,20 +3765,20 @@
},
{
"name": "wp-cli/ability-command",
- "version": "v1.0.1",
+ "version": "v1.0.2",
"source": {
"type": "git",
"url": "https://github.com/wp-cli/ability-command.git",
- "reference": "e7f8f9ab62d8b32b9375dc01b29073a73d3bb9a3"
+ "reference": "4c65b3bf9e012a41f71374de5b621d4d603e273d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/wp-cli/ability-command/zipball/e7f8f9ab62d8b32b9375dc01b29073a73d3bb9a3",
- "reference": "e7f8f9ab62d8b32b9375dc01b29073a73d3bb9a3",
+ "url": "https://api.github.com/repos/wp-cli/ability-command/zipball/4c65b3bf9e012a41f71374de5b621d4d603e273d",
+ "reference": "4c65b3bf9e012a41f71374de5b621d4d603e273d",
"shasum": ""
},
"require": {
- "wp-cli/wp-cli": "^2.13"
+ "wp-cli/wp-cli": "^3.0"
},
"require-dev": {
"wp-cli/wp-cli-tests": "^5"
@@ -3799,30 +3819,30 @@
"homepage": "https://github.com/wp-cli/ability-command",
"support": {
"issues": "https://github.com/wp-cli/ability-command/issues",
- "source": "https://github.com/wp-cli/ability-command/tree/v1.0.1"
+ "source": "https://github.com/wp-cli/ability-command/tree/v1.0.2"
},
- "time": "2026-06-18T14:38:09+00:00"
+ "time": "2026-08-04T08:30:53+00:00"
},
{
"name": "wp-cli/ai-command",
- "version": "v1.0.1",
+ "version": "v1.0.3",
"source": {
"type": "git",
"url": "https://github.com/wp-cli/ai-command.git",
- "reference": "e2cb8bfdbe1ec2b3333c0a5180365f25194ad32e"
+ "reference": "25089a664b177f88e5a56f1bcd78b52f78207ae8"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/wp-cli/ai-command/zipball/e2cb8bfdbe1ec2b3333c0a5180365f25194ad32e",
- "reference": "e2cb8bfdbe1ec2b3333c0a5180365f25194ad32e",
+ "url": "https://api.github.com/repos/wp-cli/ai-command/zipball/25089a664b177f88e5a56f1bcd78b52f78207ae8",
+ "reference": "25089a664b177f88e5a56f1bcd78b52f78207ae8",
"shasum": ""
},
"require": {
- "wp-cli/wp-cli": "^2.13"
+ "wp-cli/wp-cli": "^3.0"
},
"require-dev": {
- "wp-cli/entity-command": "^2",
- "wp-cli/extension-command": "^2",
+ "wp-cli/entity-command": "^2 || ^3",
+ "wp-cli/extension-command": "^2 || ^3",
"wp-cli/wp-cli-tests": "^5"
},
"type": "wp-cli-package",
@@ -3858,29 +3878,29 @@
"homepage": "https://github.com/wp-cli/ai-command",
"support": {
"issues": "https://github.com/wp-cli/ai-command/issues",
- "source": "https://github.com/wp-cli/ai-command/tree/v1.0.1"
+ "source": "https://github.com/wp-cli/ai-command/tree/v1.0.3"
},
- "time": "2026-06-29T09:55:36+00:00"
+ "time": "2026-08-04T11:48:40+00:00"
},
{
"name": "wp-cli/block-command",
- "version": "v1.0.1",
+ "version": "v1.0.3",
"source": {
"type": "git",
"url": "https://github.com/wp-cli/block-command.git",
- "reference": "259b8da86f84a386e262189be3f4b912183f563d"
+ "reference": "f7dd14da742b8722877d0517326320cc5041bafb"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/wp-cli/block-command/zipball/259b8da86f84a386e262189be3f4b912183f563d",
- "reference": "259b8da86f84a386e262189be3f4b912183f563d",
+ "url": "https://api.github.com/repos/wp-cli/block-command/zipball/f7dd14da742b8722877d0517326320cc5041bafb",
+ "reference": "f7dd14da742b8722877d0517326320cc5041bafb",
"shasum": ""
},
"require": {
- "wp-cli/wp-cli": "^2.13"
+ "wp-cli/wp-cli": "^3.0"
},
"require-dev": {
- "wp-cli/extension-command": "^2",
+ "wp-cli/extension-command": "^2 || ^3",
"wp-cli/wp-cli-tests": "^5"
},
"type": "wp-cli-package",
@@ -3935,26 +3955,26 @@
"homepage": "https://github.com/wp-cli/block-command",
"support": {
"issues": "https://github.com/wp-cli/block-command/issues",
- "source": "https://github.com/wp-cli/block-command/tree/v1.0.1"
+ "source": "https://github.com/wp-cli/block-command/tree/v1.0.3"
},
- "time": "2026-03-28T12:09:20+00:00"
+ "time": "2026-08-04T11:49:18+00:00"
},
{
"name": "wp-cli/cache-command",
- "version": "v2.2.2",
+ "version": "v3.0.0",
"source": {
"type": "git",
"url": "https://github.com/wp-cli/cache-command.git",
- "reference": "c4d94e7d2219868c968a3e0796042c63144b4dfb"
+ "reference": "477da08b60fd52dd6dfdb9ff2c407c802331dbf8"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/wp-cli/cache-command/zipball/c4d94e7d2219868c968a3e0796042c63144b4dfb",
- "reference": "c4d94e7d2219868c968a3e0796042c63144b4dfb",
+ "url": "https://api.github.com/repos/wp-cli/cache-command/zipball/477da08b60fd52dd6dfdb9ff2c407c802331dbf8",
+ "reference": "477da08b60fd52dd6dfdb9ff2c407c802331dbf8",
"shasum": ""
},
"require": {
- "wp-cli/wp-cli": "^2.13"
+ "wp-cli/wp-cli": "^3.0"
},
"require-dev": {
"wp-cli/entity-command": "^1.3 || ^2",
@@ -4014,29 +4034,29 @@
"homepage": "https://github.com/wp-cli/cache-command",
"support": {
"issues": "https://github.com/wp-cli/cache-command/issues",
- "source": "https://github.com/wp-cli/cache-command/tree/v2.2.2"
+ "source": "https://github.com/wp-cli/cache-command/tree/v3.0.0"
},
- "time": "2026-03-26T19:52:34+00:00"
+ "time": "2026-08-04T08:32:51+00:00"
},
{
"name": "wp-cli/checksum-command",
- "version": "v2.3.7",
+ "version": "v3.0.0",
"source": {
"type": "git",
"url": "https://github.com/wp-cli/checksum-command.git",
- "reference": "2919dd70de697dce48e551b4c9ced825d4154bfe"
+ "reference": "dc1dc576f054962eb68dfea3cbfa4d9c8c8249b6"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/wp-cli/checksum-command/zipball/2919dd70de697dce48e551b4c9ced825d4154bfe",
- "reference": "2919dd70de697dce48e551b4c9ced825d4154bfe",
+ "url": "https://api.github.com/repos/wp-cli/checksum-command/zipball/dc1dc576f054962eb68dfea3cbfa4d9c8c8249b6",
+ "reference": "dc1dc576f054962eb68dfea3cbfa4d9c8c8249b6",
"shasum": ""
},
"require": {
- "wp-cli/wp-cli": "^2.13"
+ "wp-cli/wp-cli": "^3.0"
},
"require-dev": {
- "wp-cli/extension-command": "^1.2 || ^2",
+ "wp-cli/extension-command": "^1.2 || ^2 || ^3",
"wp-cli/wp-cli-tests": "^5"
},
"type": "wp-cli-package",
@@ -4073,30 +4093,30 @@
"homepage": "https://github.com/wp-cli/checksum-command",
"support": {
"issues": "https://github.com/wp-cli/checksum-command/issues",
- "source": "https://github.com/wp-cli/checksum-command/tree/v2.3.7"
+ "source": "https://github.com/wp-cli/checksum-command/tree/v3.0.0"
},
- "time": "2026-06-30T12:37:02+00:00"
+ "time": "2026-08-04T11:53:37+00:00"
},
{
"name": "wp-cli/config-command",
- "version": "v2.5.2",
+ "version": "v3.0.0",
"source": {
"type": "git",
"url": "https://github.com/wp-cli/config-command.git",
- "reference": "5afa61c8b9012bf889a8ebc1fb4c5a24b8a1a49d"
+ "reference": "c09dd7a1cbd91d34a36d08cf7c6dd98bf490d57a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/wp-cli/config-command/zipball/5afa61c8b9012bf889a8ebc1fb4c5a24b8a1a49d",
- "reference": "5afa61c8b9012bf889a8ebc1fb4c5a24b8a1a49d",
+ "url": "https://api.github.com/repos/wp-cli/config-command/zipball/c09dd7a1cbd91d34a36d08cf7c6dd98bf490d57a",
+ "reference": "c09dd7a1cbd91d34a36d08cf7c6dd98bf490d57a",
"shasum": ""
},
"require": {
- "wp-cli/wp-cli": "^2.13",
+ "wp-cli/wp-cli": "^3.0",
"wp-cli/wp-config-transformer": "^1.4.0"
},
"require-dev": {
- "wp-cli/db-command": "^1.3 || ^2",
+ "wp-cli/db-command": "^1.3 || ^2 || ^3",
"wp-cli/wp-cli-tests": "^5"
},
"type": "wp-cli-package",
@@ -4149,33 +4169,33 @@
"homepage": "https://github.com/wp-cli/config-command",
"support": {
"issues": "https://github.com/wp-cli/config-command/issues",
- "source": "https://github.com/wp-cli/config-command/tree/v2.5.2"
+ "source": "https://github.com/wp-cli/config-command/tree/v3.0.0"
},
- "time": "2026-04-22T08:45:41+00:00"
+ "time": "2026-08-04T11:54:40+00:00"
},
{
"name": "wp-cli/core-command",
- "version": "v2.1.32",
+ "version": "v3.0.1",
"source": {
"type": "git",
"url": "https://github.com/wp-cli/core-command.git",
- "reference": "7c63cee9fd53b333fea46764f6d38ddd9a41f8d6"
+ "reference": "fe10efb2db0a07b5dcfa87a50394241cbd0546dd"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/wp-cli/core-command/zipball/7c63cee9fd53b333fea46764f6d38ddd9a41f8d6",
- "reference": "7c63cee9fd53b333fea46764f6d38ddd9a41f8d6",
+ "url": "https://api.github.com/repos/wp-cli/core-command/zipball/fe10efb2db0a07b5dcfa87a50394241cbd0546dd",
+ "reference": "fe10efb2db0a07b5dcfa87a50394241cbd0546dd",
"shasum": ""
},
"require": {
"composer/semver": "^1.4 || ^2 || ^3",
- "wp-cli/wp-cli": "^2.13"
+ "wp-cli/wp-cli": "^3.0"
},
"require-dev": {
- "wp-cli/checksum-command": "^1 || ^2",
- "wp-cli/db-command": "^1.3 || ^2",
- "wp-cli/entity-command": "^1.3 || ^2",
- "wp-cli/extension-command": "^1.2 || ^2",
+ "wp-cli/checksum-command": "^1 || ^2 || ^3",
+ "wp-cli/db-command": "^1.3 || ^2 || ^3",
+ "wp-cli/entity-command": "^1.3 || ^2 || ^3",
+ "wp-cli/extension-command": "^1.2 || ^2 || ^3",
"wp-cli/wp-cli-tests": "^5"
},
"type": "wp-cli-package",
@@ -4195,7 +4215,7 @@
"core version"
],
"branch-alias": {
- "dev-main": "2.x-dev"
+ "dev-main": "3.x-dev"
}
},
"autoload": {
@@ -4221,31 +4241,31 @@
"homepage": "https://github.com/wp-cli/core-command",
"support": {
"issues": "https://github.com/wp-cli/core-command/issues",
- "source": "https://github.com/wp-cli/core-command/tree/v2.1.32"
+ "source": "https://github.com/wp-cli/core-command/tree/v3.0.1"
},
- "time": "2026-06-08T16:57:22+00:00"
+ "time": "2026-08-05T09:03:15+00:00"
},
{
"name": "wp-cli/cron-command",
- "version": "v2.3.10",
+ "version": "v3.0.0",
"source": {
"type": "git",
"url": "https://github.com/wp-cli/cron-command.git",
- "reference": "7fa5feab2d34d9c81ddfeb4d7a5a896fa72a3d73"
+ "reference": "8bc9d5324e75ec24f1a636058c53a7638175579b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/wp-cli/cron-command/zipball/7fa5feab2d34d9c81ddfeb4d7a5a896fa72a3d73",
- "reference": "7fa5feab2d34d9c81ddfeb4d7a5a896fa72a3d73",
+ "url": "https://api.github.com/repos/wp-cli/cron-command/zipball/8bc9d5324e75ec24f1a636058c53a7638175579b",
+ "reference": "8bc9d5324e75ec24f1a636058c53a7638175579b",
"shasum": ""
},
"require": {
- "wp-cli/wp-cli": "^2.13"
+ "wp-cli/wp-cli": "^3.0"
},
"require-dev": {
- "wp-cli/entity-command": "^1.3 || ^2",
- "wp-cli/eval-command": "^2.0",
- "wp-cli/server-command": "^2.0",
+ "wp-cli/entity-command": "^1.3 || ^2 || ^3",
+ "wp-cli/eval-command": "^2.0 || ^3",
+ "wp-cli/server-command": "^2.0 || ^3",
"wp-cli/wp-cli-tests": "^5"
},
"type": "wp-cli-package",
@@ -4290,29 +4310,29 @@
"homepage": "https://github.com/wp-cli/cron-command",
"support": {
"issues": "https://github.com/wp-cli/cron-command/issues",
- "source": "https://github.com/wp-cli/cron-command/tree/v2.3.10"
+ "source": "https://github.com/wp-cli/cron-command/tree/v3.0.0"
},
- "time": "2026-07-09T09:38:22+00:00"
+ "time": "2026-08-04T12:00:36+00:00"
},
{
"name": "wp-cli/db-command",
- "version": "v2.1.6",
+ "version": "v3.0.1",
"source": {
"type": "git",
"url": "https://github.com/wp-cli/db-command.git",
- "reference": "4de3dfa1c1846241f1e046feffd3745d09211371"
+ "reference": "d1f779e23484006432c9749eecfc642dd52c3515"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/wp-cli/db-command/zipball/4de3dfa1c1846241f1e046feffd3745d09211371",
- "reference": "4de3dfa1c1846241f1e046feffd3745d09211371",
+ "url": "https://api.github.com/repos/wp-cli/db-command/zipball/d1f779e23484006432c9749eecfc642dd52c3515",
+ "reference": "d1f779e23484006432c9749eecfc642dd52c3515",
"shasum": ""
},
"require": {
- "wp-cli/wp-cli": "^2.13"
+ "wp-cli/wp-cli": "^3.0"
},
"require-dev": {
- "wp-cli/entity-command": "^1.3 || ^2",
+ "wp-cli/entity-command": "^1.3 || ^2 || ^3",
"wp-cli/wp-cli-tests": "^5"
},
"type": "wp-cli-package",
@@ -4338,7 +4358,7 @@
"db columns"
],
"branch-alias": {
- "dev-main": "2.x-dev"
+ "dev-main": "3.x-dev"
}
},
"autoload": {
@@ -4364,29 +4384,29 @@
"homepage": "https://github.com/wp-cli/db-command",
"support": {
"issues": "https://github.com/wp-cli/db-command/issues",
- "source": "https://github.com/wp-cli/db-command/tree/v2.1.6"
+ "source": "https://github.com/wp-cli/db-command/tree/v3.0.1"
},
- "time": "2026-05-14T09:14:16+00:00"
+ "time": "2026-08-14T12:22:09+00:00"
},
{
"name": "wp-cli/embed-command",
- "version": "v2.1.2",
+ "version": "v3.0.0",
"source": {
"type": "git",
"url": "https://github.com/wp-cli/embed-command.git",
- "reference": "287303a5efe8680da093aca57a305705c433ea55"
+ "reference": "6e7facee11722ea2955a203d9e96d4f746cbe404"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/wp-cli/embed-command/zipball/287303a5efe8680da093aca57a305705c433ea55",
- "reference": "287303a5efe8680da093aca57a305705c433ea55",
+ "url": "https://api.github.com/repos/wp-cli/embed-command/zipball/6e7facee11722ea2955a203d9e96d4f746cbe404",
+ "reference": "6e7facee11722ea2955a203d9e96d4f746cbe404",
"shasum": ""
},
"require": {
- "wp-cli/wp-cli": "^2.13"
+ "wp-cli/wp-cli": "^3.0"
},
"require-dev": {
- "wp-cli/entity-command": "^1.3 || ^2",
+ "wp-cli/entity-command": "^1.3 || ^2 || ^3",
"wp-cli/wp-cli-tests": "^5"
},
"type": "wp-cli-package",
@@ -4431,33 +4451,33 @@
"homepage": "https://github.com/wp-cli/embed-command",
"support": {
"issues": "https://github.com/wp-cli/embed-command/issues",
- "source": "https://github.com/wp-cli/embed-command/tree/v2.1.2"
+ "source": "https://github.com/wp-cli/embed-command/tree/v3.0.0"
},
- "time": "2026-03-28T11:58:59+00:00"
+ "time": "2026-08-04T12:04:32+00:00"
},
{
"name": "wp-cli/entity-command",
- "version": "v2.8.12",
+ "version": "v3.0.2",
"source": {
"type": "git",
"url": "https://github.com/wp-cli/entity-command.git",
- "reference": "51b45fb0d5429a4bba36250e8c84f8422c38b2c9"
+ "reference": "41a409de8633053b2d9625410c83b974b25199d2"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/wp-cli/entity-command/zipball/51b45fb0d5429a4bba36250e8c84f8422c38b2c9",
- "reference": "51b45fb0d5429a4bba36250e8c84f8422c38b2c9",
+ "url": "https://api.github.com/repos/wp-cli/entity-command/zipball/41a409de8633053b2d9625410c83b974b25199d2",
+ "reference": "41a409de8633053b2d9625410c83b974b25199d2",
"shasum": ""
},
"require": {
- "wp-cli/wp-cli": "^2.13"
+ "wp-cli/wp-cli": "^3.0"
},
"require-dev": {
- "wp-cli/cache-command": "^1 || ^2",
- "wp-cli/db-command": "^1.3 || ^2",
- "wp-cli/extension-command": "^1.2 || ^2",
- "wp-cli/media-command": "^1.1 || ^2",
- "wp-cli/super-admin-command": "^1 || ^2",
+ "wp-cli/cache-command": "^1 || ^2 || ^3",
+ "wp-cli/db-command": "^1.3 || ^2 || ^3",
+ "wp-cli/extension-command": "^1.2 || ^2 || ^3",
+ "wp-cli/media-command": "^1.1 || ^2 || ^3",
+ "wp-cli/super-admin-command": "^1 || ^2 || ^3",
"wp-cli/wp-cli-tests": "^5"
},
"type": "wp-cli-package",
@@ -4694,7 +4714,7 @@
"user update"
],
"branch-alias": {
- "dev-main": "2.x-dev"
+ "dev-main": "3.x-dev"
}
},
"autoload": {
@@ -4725,26 +4745,26 @@
"homepage": "https://github.com/wp-cli/entity-command",
"support": {
"issues": "https://github.com/wp-cli/entity-command/issues",
- "source": "https://github.com/wp-cli/entity-command/tree/v2.8.12"
+ "source": "https://github.com/wp-cli/entity-command/tree/v3.0.2"
},
- "time": "2026-06-18T14:38:31+00:00"
+ "time": "2026-08-14T12:24:50+00:00"
},
{
"name": "wp-cli/eval-command",
- "version": "v2.2.10",
+ "version": "v3.0.0",
"source": {
"type": "git",
"url": "https://github.com/wp-cli/eval-command.git",
- "reference": "b542cd39289f466efd95ef863476198e992e3686"
+ "reference": "fbccba5595de2efb91a242610f9c55dd69eea78b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/wp-cli/eval-command/zipball/b542cd39289f466efd95ef863476198e992e3686",
- "reference": "b542cd39289f466efd95ef863476198e992e3686",
+ "url": "https://api.github.com/repos/wp-cli/eval-command/zipball/fbccba5595de2efb91a242610f9c55dd69eea78b",
+ "reference": "fbccba5595de2efb91a242610f9c55dd69eea78b",
"shasum": ""
},
"require": {
- "wp-cli/wp-cli": "^2.13"
+ "wp-cli/wp-cli": "^3.0"
},
"require-dev": {
"wp-cli/wp-cli-tests": "^5"
@@ -4783,34 +4803,34 @@
"homepage": "https://github.com/wp-cli/eval-command",
"support": {
"issues": "https://github.com/wp-cli/eval-command/issues",
- "source": "https://github.com/wp-cli/eval-command/tree/v2.2.10"
+ "source": "https://github.com/wp-cli/eval-command/tree/v3.0.0"
},
- "time": "2026-03-28T12:10:22+00:00"
+ "time": "2026-08-04T08:32:56+00:00"
},
{
"name": "wp-cli/export-command",
- "version": "v2.1.18",
+ "version": "v3.0.0",
"source": {
"type": "git",
"url": "https://github.com/wp-cli/export-command.git",
- "reference": "8a879256ce3009a4e281f99bddee77f03e5264e3"
+ "reference": "adba9016c906d162610406aa89acb86d3ae6805d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/wp-cli/export-command/zipball/8a879256ce3009a4e281f99bddee77f03e5264e3",
- "reference": "8a879256ce3009a4e281f99bddee77f03e5264e3",
+ "url": "https://api.github.com/repos/wp-cli/export-command/zipball/adba9016c906d162610406aa89acb86d3ae6805d",
+ "reference": "adba9016c906d162610406aa89acb86d3ae6805d",
"shasum": ""
},
"require": {
"nb/oxymel": "~0.1.0",
- "wp-cli/wp-cli": "^2.13"
+ "wp-cli/wp-cli": "^3.0"
},
"require-dev": {
- "wp-cli/db-command": "^1.3 || ^2",
- "wp-cli/entity-command": "^1.3 || ^2",
- "wp-cli/extension-command": "^1.2 || ^2",
- "wp-cli/import-command": "^1 || ^2",
- "wp-cli/media-command": "^1 || ^2",
+ "wp-cli/db-command": "^1.3 || ^2 || ^3",
+ "wp-cli/entity-command": "^1.3 || ^2 || ^3",
+ "wp-cli/extension-command": "^1.2 || ^2 || ^3",
+ "wp-cli/import-command": "^1 || ^2 || ^3",
+ "wp-cli/media-command": "^1 || ^2 || ^3",
"wp-cli/wp-cli-tests": "^5"
},
"type": "wp-cli-package",
@@ -4846,33 +4866,33 @@
"homepage": "https://github.com/wp-cli/export-command",
"support": {
"issues": "https://github.com/wp-cli/export-command/issues",
- "source": "https://github.com/wp-cli/export-command/tree/v2.1.18"
+ "source": "https://github.com/wp-cli/export-command/tree/v3.0.0"
},
- "time": "2026-05-22T10:19:08+00:00"
+ "time": "2026-08-04T12:45:56+00:00"
},
{
"name": "wp-cli/extension-command",
- "version": "v2.3.6",
+ "version": "v3.0.0",
"source": {
"type": "git",
"url": "https://github.com/wp-cli/extension-command.git",
- "reference": "edbfb7b0801b1767031948062c0ca33f11ba9e0f"
+ "reference": "f8e9aaa344af713c24c10072d5a7f7112075ae3b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/wp-cli/extension-command/zipball/edbfb7b0801b1767031948062c0ca33f11ba9e0f",
- "reference": "edbfb7b0801b1767031948062c0ca33f11ba9e0f",
+ "url": "https://api.github.com/repos/wp-cli/extension-command/zipball/f8e9aaa344af713c24c10072d5a7f7112075ae3b",
+ "reference": "f8e9aaa344af713c24c10072d5a7f7112075ae3b",
"shasum": ""
},
"require": {
"composer/semver": "^1.4 || ^2 || ^3",
- "wp-cli/wp-cli": "^2.13"
+ "wp-cli/wp-cli": "^3.0"
},
"require-dev": {
- "wp-cli/cache-command": "^2.0",
- "wp-cli/entity-command": "^1.3 || ^2",
- "wp-cli/language-command": "^2.0",
- "wp-cli/scaffold-command": "^1.2 || ^2",
+ "wp-cli/cache-command": "^2.0 || ^3",
+ "wp-cli/entity-command": "^1.3 || ^2 || ^3",
+ "wp-cli/language-command": "^2.0 || ^3",
+ "wp-cli/scaffold-command": "^1.2 || ^2 || ^3",
"wp-cli/wp-cli-tests": "^5"
},
"type": "wp-cli-package",
@@ -4959,32 +4979,32 @@
"homepage": "https://github.com/wp-cli/extension-command",
"support": {
"issues": "https://github.com/wp-cli/extension-command/issues",
- "source": "https://github.com/wp-cli/extension-command/tree/v2.3.6"
+ "source": "https://github.com/wp-cli/extension-command/tree/v3.0.0"
},
- "time": "2026-06-10T17:07:52+00:00"
+ "time": "2026-08-04T13:41:39+00:00"
},
{
"name": "wp-cli/i18n-command",
- "version": "v2.7.3",
+ "version": "v3.0.0",
"source": {
"type": "git",
"url": "https://github.com/wp-cli/i18n-command.git",
- "reference": "a42366245fef8b11a5d895db3f38d3c606d21d06"
+ "reference": "170a75093c0f780b5c6590f79e5cf06cb8962eb7"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/wp-cli/i18n-command/zipball/a42366245fef8b11a5d895db3f38d3c606d21d06",
- "reference": "a42366245fef8b11a5d895db3f38d3c606d21d06",
+ "url": "https://api.github.com/repos/wp-cli/i18n-command/zipball/170a75093c0f780b5c6590f79e5cf06cb8962eb7",
+ "reference": "170a75093c0f780b5c6590f79e5cf06cb8962eb7",
"shasum": ""
},
"require": {
"eftec/bladeone": "3.52",
"gettext/gettext": "^4.8",
"mck89/peast": "^1.13.11",
- "wp-cli/wp-cli": "^2.13"
+ "wp-cli/wp-cli": "^3.0"
},
"require-dev": {
- "wp-cli/scaffold-command": "^1.2 || ^2",
+ "wp-cli/scaffold-command": "^1.2 || ^2 || ^3",
"wp-cli/wp-cli-tests": "^5.0.0"
},
"suggest": {
@@ -5029,32 +5049,32 @@
"homepage": "https://github.com/wp-cli/i18n-command",
"support": {
"issues": "https://github.com/wp-cli/i18n-command/issues",
- "source": "https://github.com/wp-cli/i18n-command/tree/v2.7.3"
+ "source": "https://github.com/wp-cli/i18n-command/tree/v3.0.0"
},
- "time": "2026-04-28T13:55:33+00:00"
+ "time": "2026-08-04T12:46:11+00:00"
},
{
"name": "wp-cli/import-command",
- "version": "v2.0.18",
+ "version": "v3.0.0",
"source": {
"type": "git",
"url": "https://github.com/wp-cli/import-command.git",
- "reference": "e0ff5f78101f061b56be99b233f6ae863da9a20a"
+ "reference": "08835fd8ca4c301a964c35f286327fac8772e924"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/wp-cli/import-command/zipball/e0ff5f78101f061b56be99b233f6ae863da9a20a",
- "reference": "e0ff5f78101f061b56be99b233f6ae863da9a20a",
+ "url": "https://api.github.com/repos/wp-cli/import-command/zipball/08835fd8ca4c301a964c35f286327fac8772e924",
+ "reference": "08835fd8ca4c301a964c35f286327fac8772e924",
"shasum": ""
},
"require": {
- "wp-cli/wp-cli": "^2.13"
+ "wp-cli/wp-cli": "^3.0"
},
"require-dev": {
"wordpress/wordpress-importer": "^0.9",
- "wp-cli/entity-command": "^1.3 || ^2",
- "wp-cli/export-command": "^1 || ^2",
- "wp-cli/extension-command": "^1.2 || ^2",
+ "wp-cli/entity-command": "^1.3 || ^2 || ^3",
+ "wp-cli/export-command": "^1 || ^2 || ^3",
+ "wp-cli/extension-command": "^1.2 || ^2 || ^3",
"wp-cli/wp-cli-tests": "^5"
},
"type": "wp-cli-package",
@@ -5090,31 +5110,31 @@
"homepage": "https://github.com/wp-cli/import-command",
"support": {
"issues": "https://github.com/wp-cli/import-command/issues",
- "source": "https://github.com/wp-cli/import-command/tree/v2.0.18"
+ "source": "https://github.com/wp-cli/import-command/tree/v3.0.0"
},
- "time": "2026-04-22T08:46:02+00:00"
+ "time": "2026-08-04T12:46:23+00:00"
},
{
"name": "wp-cli/language-command",
- "version": "v2.0.27",
+ "version": "v3.0.0",
"source": {
"type": "git",
"url": "https://github.com/wp-cli/language-command.git",
- "reference": "6032dd1a51e6253503a385f26083bedab40e453a"
+ "reference": "a6b999e64971f78352aa0614601061794e96c86e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/wp-cli/language-command/zipball/6032dd1a51e6253503a385f26083bedab40e453a",
- "reference": "6032dd1a51e6253503a385f26083bedab40e453a",
+ "url": "https://api.github.com/repos/wp-cli/language-command/zipball/a6b999e64971f78352aa0614601061794e96c86e",
+ "reference": "a6b999e64971f78352aa0614601061794e96c86e",
"shasum": ""
},
"require": {
- "wp-cli/wp-cli": "^2.13"
+ "wp-cli/wp-cli": "^3.0"
},
"require-dev": {
- "wp-cli/db-command": "^1.3 || ^2",
- "wp-cli/entity-command": "^1.3 || ^2",
- "wp-cli/extension-command": "^1.2 || ^2",
+ "wp-cli/db-command": "^1.3 || ^2 || ^3",
+ "wp-cli/entity-command": "^1.3 || ^2 || ^3",
+ "wp-cli/extension-command": "^1.2 || ^2 || ^3",
"wp-cli/wp-cli-tests": "^5"
},
"type": "wp-cli-package",
@@ -5170,26 +5190,26 @@
"homepage": "https://github.com/wp-cli/language-command",
"support": {
"issues": "https://github.com/wp-cli/language-command/issues",
- "source": "https://github.com/wp-cli/language-command/tree/v2.0.27"
+ "source": "https://github.com/wp-cli/language-command/tree/v3.0.0"
},
- "time": "2026-03-26T19:53:20+00:00"
+ "time": "2026-08-04T12:25:07+00:00"
},
{
"name": "wp-cli/maintenance-mode-command",
- "version": "v2.1.4",
+ "version": "v3.0.0",
"source": {
"type": "git",
"url": "https://github.com/wp-cli/maintenance-mode-command.git",
- "reference": "d769c19ecbfe4e731649876105f8a11c90294aa3"
+ "reference": "201001fa3a53951b05f4e67a5e7e5f73331bd92a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/wp-cli/maintenance-mode-command/zipball/d769c19ecbfe4e731649876105f8a11c90294aa3",
- "reference": "d769c19ecbfe4e731649876105f8a11c90294aa3",
+ "url": "https://api.github.com/repos/wp-cli/maintenance-mode-command/zipball/201001fa3a53951b05f4e67a5e7e5f73331bd92a",
+ "reference": "201001fa3a53951b05f4e67a5e7e5f73331bd92a",
"shasum": ""
},
"require": {
- "wp-cli/wp-cli": "^2.13"
+ "wp-cli/wp-cli": "^3.0"
},
"require-dev": {
"wp-cli/wp-cli-tests": "^5"
@@ -5231,30 +5251,30 @@
"homepage": "https://github.com/wp-cli/maintenance-mode-command",
"support": {
"issues": "https://github.com/wp-cli/maintenance-mode-command/issues",
- "source": "https://github.com/wp-cli/maintenance-mode-command/tree/v2.1.4"
+ "source": "https://github.com/wp-cli/maintenance-mode-command/tree/v3.0.0"
},
- "time": "2026-03-28T12:03:12+00:00"
+ "time": "2026-08-04T08:40:50+00:00"
},
{
"name": "wp-cli/media-command",
- "version": "v2.2.8",
+ "version": "v3.0.0",
"source": {
"type": "git",
"url": "https://github.com/wp-cli/media-command.git",
- "reference": "99bb14490651388b44b78df1b8ba5fae8b00521a"
+ "reference": "25dc57685212edd26b60137392aecbce11610d3a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/wp-cli/media-command/zipball/99bb14490651388b44b78df1b8ba5fae8b00521a",
- "reference": "99bb14490651388b44b78df1b8ba5fae8b00521a",
+ "url": "https://api.github.com/repos/wp-cli/media-command/zipball/25dc57685212edd26b60137392aecbce11610d3a",
+ "reference": "25dc57685212edd26b60137392aecbce11610d3a",
"shasum": ""
},
"require": {
- "wp-cli/wp-cli": "^2.13"
+ "wp-cli/wp-cli": "^3.0"
},
"require-dev": {
- "wp-cli/entity-command": "^1.3 || ^2",
- "wp-cli/extension-command": "^2.0",
+ "wp-cli/entity-command": "^1.3 || ^2 || ^3",
+ "wp-cli/extension-command": "^2.0 || ^3",
"wp-cli/wp-cli-tests": "^5"
},
"type": "wp-cli-package",
@@ -5296,9 +5316,9 @@
"homepage": "https://github.com/wp-cli/media-command",
"support": {
"issues": "https://github.com/wp-cli/media-command/issues",
- "source": "https://github.com/wp-cli/media-command/tree/v2.2.8"
+ "source": "https://github.com/wp-cli/media-command/tree/v3.0.0"
},
- "time": "2026-05-05T08:37:58+00:00"
+ "time": "2026-08-04T12:46:34+00:00"
},
{
"name": "wp-cli/mustangostang-spyc",
@@ -5416,23 +5436,23 @@
},
{
"name": "wp-cli/rewrite-command",
- "version": "v2.0.18",
+ "version": "v3.0.0",
"source": {
"type": "git",
"url": "https://github.com/wp-cli/rewrite-command.git",
- "reference": "d473ebff594913b9b4866d3545a475d9e41edb44"
+ "reference": "2c4a30ac11f08ae37449718d7dcb935089581efd"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/wp-cli/rewrite-command/zipball/d473ebff594913b9b4866d3545a475d9e41edb44",
- "reference": "d473ebff594913b9b4866d3545a475d9e41edb44",
+ "url": "https://api.github.com/repos/wp-cli/rewrite-command/zipball/2c4a30ac11f08ae37449718d7dcb935089581efd",
+ "reference": "2c4a30ac11f08ae37449718d7dcb935089581efd",
"shasum": ""
},
"require": {
- "wp-cli/wp-cli": "^2.13"
+ "wp-cli/wp-cli": "^3.0"
},
"require-dev": {
- "wp-cli/entity-command": "^1.3 || ^2",
+ "wp-cli/entity-command": "^1.3 || ^2 || ^3",
"wp-cli/wp-cli-tests": "^5"
},
"type": "wp-cli-package",
@@ -5471,26 +5491,26 @@
"homepage": "https://github.com/wp-cli/rewrite-command",
"support": {
"issues": "https://github.com/wp-cli/rewrite-command/issues",
- "source": "https://github.com/wp-cli/rewrite-command/tree/v2.0.18"
+ "source": "https://github.com/wp-cli/rewrite-command/tree/v3.0.0"
},
- "time": "2026-03-28T15:28:03+00:00"
+ "time": "2026-08-04T12:22:01+00:00"
},
{
"name": "wp-cli/role-command",
- "version": "v2.0.17",
+ "version": "v3.0.0",
"source": {
"type": "git",
"url": "https://github.com/wp-cli/role-command.git",
- "reference": "b9b3a9191899d423608d5e7274290aca220f646a"
+ "reference": "cf7a41cbc089b1eb9d9a1b7707070f904aae1b61"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/wp-cli/role-command/zipball/b9b3a9191899d423608d5e7274290aca220f646a",
- "reference": "b9b3a9191899d423608d5e7274290aca220f646a",
+ "url": "https://api.github.com/repos/wp-cli/role-command/zipball/cf7a41cbc089b1eb9d9a1b7707070f904aae1b61",
+ "reference": "cf7a41cbc089b1eb9d9a1b7707070f904aae1b61",
"shasum": ""
},
"require": {
- "wp-cli/wp-cli": "^2.13"
+ "wp-cli/wp-cli": "^3.0"
},
"require-dev": {
"wp-cli/wp-cli-tests": "^5"
@@ -5537,33 +5557,38 @@
"homepage": "https://github.com/wp-cli/role-command",
"support": {
"issues": "https://github.com/wp-cli/role-command/issues",
- "source": "https://github.com/wp-cli/role-command/tree/v2.0.17"
+ "source": "https://github.com/wp-cli/role-command/tree/v3.0.0"
},
- "time": "2026-03-25T10:03:06+00:00"
+ "time": "2026-08-04T12:20:14+00:00"
},
{
"name": "wp-cli/scaffold-command",
- "version": "v2.5.7",
+ "version": "v3.0.1",
"source": {
"type": "git",
"url": "https://github.com/wp-cli/scaffold-command.git",
- "reference": "fcd1b7d8a0b26de379c1028a6bbe39926d6fca2e"
+ "reference": "f0319408c13646b262f8e5a28026fd8f74ff122f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/wp-cli/scaffold-command/zipball/fcd1b7d8a0b26de379c1028a6bbe39926d6fca2e",
- "reference": "fcd1b7d8a0b26de379c1028a6bbe39926d6fca2e",
+ "url": "https://api.github.com/repos/wp-cli/scaffold-command/zipball/f0319408c13646b262f8e5a28026fd8f74ff122f",
+ "reference": "f0319408c13646b262f8e5a28026fd8f74ff122f",
"shasum": ""
},
"require": {
- "wp-cli/wp-cli": "^2.13"
+ "wp-cli/wp-cli": "^3.0"
},
"require-dev": {
- "wp-cli/extension-command": "^1.2 || ^2",
+ "wp-cli/extension-command": "^1.2 || ^2 || ^3",
"wp-cli/wp-cli-tests": "^5"
},
"type": "wp-cli-package",
"extra": {
+ "readme": {
+ "package_description": {
+ "post": ".readme-partials/disclaimer.md"
+ }
+ },
"bundled": true,
"commands": [
"scaffold",
@@ -5577,7 +5602,7 @@
"scaffold theme-tests"
],
"branch-alias": {
- "dev-main": "2.x-dev"
+ "dev-main": "3.x-dev"
}
},
"autoload": {
@@ -5603,31 +5628,31 @@
"homepage": "https://github.com/wp-cli/scaffold-command",
"support": {
"issues": "https://github.com/wp-cli/scaffold-command/issues",
- "source": "https://github.com/wp-cli/scaffold-command/tree/v2.5.7"
+ "source": "https://github.com/wp-cli/scaffold-command/tree/v3.0.1"
},
- "time": "2026-05-21T14:09:10+00:00"
+ "time": "2026-08-04T15:59:07+00:00"
},
{
"name": "wp-cli/search-replace-command",
- "version": "v2.1.14",
+ "version": "v3.0.1",
"source": {
"type": "git",
"url": "https://github.com/wp-cli/search-replace-command.git",
- "reference": "e1c42e4678444fe60e5d3969210bc09f76a81149"
+ "reference": "5df5c192a2bc974bf657e47aa0b70cf1bb7cf106"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/wp-cli/search-replace-command/zipball/e1c42e4678444fe60e5d3969210bc09f76a81149",
- "reference": "e1c42e4678444fe60e5d3969210bc09f76a81149",
+ "url": "https://api.github.com/repos/wp-cli/search-replace-command/zipball/5df5c192a2bc974bf657e47aa0b70cf1bb7cf106",
+ "reference": "5df5c192a2bc974bf657e47aa0b70cf1bb7cf106",
"shasum": ""
},
"require": {
- "wp-cli/wp-cli": "^2.13"
+ "wp-cli/wp-cli": "^3.0"
},
"require-dev": {
- "wp-cli/db-command": "^1.3 || ^2",
- "wp-cli/entity-command": "^1.3 || ^2",
- "wp-cli/extension-command": "^1.2 || ^2",
+ "wp-cli/db-command": "^1.3 || ^2 || ^3",
+ "wp-cli/entity-command": "^1.3 || ^2 || ^3",
+ "wp-cli/extension-command": "^1.2 || ^2 || ^3",
"wp-cli/wp-cli-tests": "^5"
},
"type": "wp-cli-package",
@@ -5637,7 +5662,7 @@
"search-replace"
],
"branch-alias": {
- "dev-main": "2.x-dev"
+ "dev-main": "3.x-dev"
}
},
"autoload": {
@@ -5663,30 +5688,30 @@
"homepage": "https://github.com/wp-cli/search-replace-command",
"support": {
"issues": "https://github.com/wp-cli/search-replace-command/issues",
- "source": "https://github.com/wp-cli/search-replace-command/tree/v2.1.14"
+ "source": "https://github.com/wp-cli/search-replace-command/tree/v3.0.1"
},
- "time": "2026-06-20T18:28:04+00:00"
+ "time": "2026-08-05T09:16:38+00:00"
},
{
"name": "wp-cli/server-command",
- "version": "v2.0.19",
+ "version": "v3.0.0",
"source": {
"type": "git",
"url": "https://github.com/wp-cli/server-command.git",
- "reference": "c5c64ee37074e74cbb3614c5dd4a4aa3dff02495"
+ "reference": "4edf159383dcf0cceece510f96f892dd666a23c6"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/wp-cli/server-command/zipball/c5c64ee37074e74cbb3614c5dd4a4aa3dff02495",
- "reference": "c5c64ee37074e74cbb3614c5dd4a4aa3dff02495",
+ "url": "https://api.github.com/repos/wp-cli/server-command/zipball/4edf159383dcf0cceece510f96f892dd666a23c6",
+ "reference": "4edf159383dcf0cceece510f96f892dd666a23c6",
"shasum": ""
},
"require": {
- "wp-cli/wp-cli": "^2.13"
+ "wp-cli/wp-cli": "^3.0"
},
"require-dev": {
- "wp-cli/entity-command": "^2",
- "wp-cli/rewrite-command": "^2",
+ "wp-cli/entity-command": "^2 || ^3",
+ "wp-cli/rewrite-command": "^2 || ^3",
"wp-cli/wp-cli-tests": "^5"
},
"type": "wp-cli-package",
@@ -5722,26 +5747,26 @@
"homepage": "https://github.com/wp-cli/server-command",
"support": {
"issues": "https://github.com/wp-cli/server-command/issues",
- "source": "https://github.com/wp-cli/server-command/tree/v2.0.19"
+ "source": "https://github.com/wp-cli/server-command/tree/v3.0.0"
},
- "time": "2026-04-14T15:29:58+00:00"
+ "time": "2026-08-04T12:18:33+00:00"
},
{
"name": "wp-cli/shell-command",
- "version": "v2.0.18",
+ "version": "v3.0.0",
"source": {
"type": "git",
"url": "https://github.com/wp-cli/shell-command.git",
- "reference": "ebe2608705ea1a3d6dc726b5e91cd847c93f91ea"
+ "reference": "adc6c5ab82465d71570b2e5ecbb3261737b54979"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/wp-cli/shell-command/zipball/ebe2608705ea1a3d6dc726b5e91cd847c93f91ea",
- "reference": "ebe2608705ea1a3d6dc726b5e91cd847c93f91ea",
+ "url": "https://api.github.com/repos/wp-cli/shell-command/zipball/adc6c5ab82465d71570b2e5ecbb3261737b54979",
+ "reference": "adc6c5ab82465d71570b2e5ecbb3261737b54979",
"shasum": ""
},
"require": {
- "wp-cli/wp-cli": "^2.13"
+ "wp-cli/wp-cli": "^3.0"
},
"require-dev": {
"wp-cli/wp-cli-tests": "^5"
@@ -5779,26 +5804,26 @@
"homepage": "https://github.com/wp-cli/shell-command",
"support": {
"issues": "https://github.com/wp-cli/shell-command/issues",
- "source": "https://github.com/wp-cli/shell-command/tree/v2.0.18"
+ "source": "https://github.com/wp-cli/shell-command/tree/v3.0.0"
},
- "time": "2026-05-02T18:21:20+00:00"
+ "time": "2026-08-04T08:33:05+00:00"
},
{
"name": "wp-cli/site-health-command",
- "version": "v1.0.0",
+ "version": "v1.0.1",
"source": {
"type": "git",
"url": "https://github.com/wp-cli/site-health-command.git",
- "reference": "c7c1505f2fcca51370efe309b8a5084157f5d2cd"
+ "reference": "09c03e931f75ba0b26f8b64dae4e592ebd46c271"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/wp-cli/site-health-command/zipball/c7c1505f2fcca51370efe309b8a5084157f5d2cd",
- "reference": "c7c1505f2fcca51370efe309b8a5084157f5d2cd",
+ "url": "https://api.github.com/repos/wp-cli/site-health-command/zipball/09c03e931f75ba0b26f8b64dae4e592ebd46c271",
+ "reference": "09c03e931f75ba0b26f8b64dae4e592ebd46c271",
"shasum": ""
},
"require": {
- "wp-cli/wp-cli": "^2.11"
+ "wp-cli/wp-cli": "^3.0"
},
"require-dev": {
"wp-cli/wp-cli-tests": "^5"
@@ -5832,29 +5857,29 @@
"homepage": "https://github.com/wp-cli/site-health-command",
"support": {
"issues": "https://github.com/wp-cli/site-health-command/issues",
- "source": "https://github.com/wp-cli/site-health-command/tree/v1.0.0"
+ "source": "https://github.com/wp-cli/site-health-command/tree/v1.0.1"
},
- "time": "2026-03-16T15:20:35+00:00"
+ "time": "2026-08-04T08:33:06+00:00"
},
{
"name": "wp-cli/super-admin-command",
- "version": "v2.0.18",
+ "version": "v3.0.0",
"source": {
"type": "git",
"url": "https://github.com/wp-cli/super-admin-command.git",
- "reference": "e887cc93116c94be4f33f8ac79a4c92e0f6a6a86"
+ "reference": "682858f844cc74f3246fd808998d94b8595afdd6"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/wp-cli/super-admin-command/zipball/e887cc93116c94be4f33f8ac79a4c92e0f6a6a86",
- "reference": "e887cc93116c94be4f33f8ac79a4c92e0f6a6a86",
+ "url": "https://api.github.com/repos/wp-cli/super-admin-command/zipball/682858f844cc74f3246fd808998d94b8595afdd6",
+ "reference": "682858f844cc74f3246fd808998d94b8595afdd6",
"shasum": ""
},
"require": {
- "wp-cli/wp-cli": "^2.13"
+ "wp-cli/wp-cli": "^3.0"
},
"require-dev": {
- "wp-cli/entity-command": "^1.3 || ^2",
+ "wp-cli/entity-command": "^1.3 || ^2 || ^3",
"wp-cli/wp-cli-tests": "^5"
},
"type": "wp-cli-package",
@@ -5893,29 +5918,29 @@
"homepage": "https://github.com/wp-cli/super-admin-command",
"support": {
"issues": "https://github.com/wp-cli/super-admin-command/issues",
- "source": "https://github.com/wp-cli/super-admin-command/tree/v2.0.18"
+ "source": "https://github.com/wp-cli/super-admin-command/tree/v3.0.0"
},
- "time": "2026-03-28T12:04:44+00:00"
+ "time": "2026-08-04T12:47:22+00:00"
},
{
"name": "wp-cli/widget-command",
- "version": "v2.2.3",
+ "version": "v3.0.0",
"source": {
"type": "git",
"url": "https://github.com/wp-cli/widget-command.git",
- "reference": "07cd4f3db12f037aac20e22ee25ae2fae9efbd68"
+ "reference": "ecab9a9531244b35b34f5b37c52e6d913812a1f0"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/wp-cli/widget-command/zipball/07cd4f3db12f037aac20e22ee25ae2fae9efbd68",
- "reference": "07cd4f3db12f037aac20e22ee25ae2fae9efbd68",
+ "url": "https://api.github.com/repos/wp-cli/widget-command/zipball/ecab9a9531244b35b34f5b37c52e6d913812a1f0",
+ "reference": "ecab9a9531244b35b34f5b37c52e6d913812a1f0",
"shasum": ""
},
"require": {
- "wp-cli/wp-cli": "^2.13"
+ "wp-cli/wp-cli": "^3.0"
},
"require-dev": {
- "wp-cli/extension-command": "^1.2 || ^2",
+ "wp-cli/extension-command": "^1.2 || ^2 || ^3",
"wp-cli/wp-cli-tests": "^5"
},
"type": "wp-cli-package",
@@ -5963,9 +5988,9 @@
"homepage": "https://github.com/wp-cli/widget-command",
"support": {
"issues": "https://github.com/wp-cli/widget-command/issues",
- "source": "https://github.com/wp-cli/widget-command/tree/v2.2.3"
+ "source": "https://github.com/wp-cli/widget-command/tree/v3.0.0"
},
- "time": "2026-03-28T12:03:43+00:00"
+ "time": "2026-08-04T12:47:35+00:00"
},
{
"name": "wp-cli/wp-cli",
@@ -5973,12 +5998,12 @@
"source": {
"type": "git",
"url": "https://github.com/wp-cli/wp-cli.git",
- "reference": "aa9adcca16bf352d60519ff8b74905f4971cb7e3"
+ "reference": "6f7facf268e64e89b359c08196524f18353cb552"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/wp-cli/wp-cli/zipball/aa9adcca16bf352d60519ff8b74905f4971cb7e3",
- "reference": "aa9adcca16bf352d60519ff8b74905f4971cb7e3",
+ "url": "https://api.github.com/repos/wp-cli/wp-cli/zipball/6f7facf268e64e89b359c08196524f18353cb552",
+ "reference": "6f7facf268e64e89b359c08196524f18353cb552",
"shasum": ""
},
"require": {
@@ -5990,11 +6015,11 @@
"require-dev": {
"justinrainbow/json-schema": "^6.3",
"roave/security-advisories": "dev-latest",
- "wp-cli/db-command": "^2",
- "wp-cli/entity-command": "^2",
- "wp-cli/extension-command": "^2",
- "wp-cli/language-command": "^2",
- "wp-cli/package-command": "^2",
+ "wp-cli/db-command": "^2 || ^3",
+ "wp-cli/entity-command": "^2 || ^3",
+ "wp-cli/extension-command": "^2 || ^3",
+ "wp-cli/language-command": "^2 || ^3",
+ "wp-cli/package-command": "^2 || ^3",
"wp-cli/wp-cli-tests": "^5"
},
"suggest": {
@@ -6023,7 +6048,7 @@
"cli version"
],
"branch-alias": {
- "dev-main": "2.13.x-dev"
+ "dev-main": "3.0.x-dev"
}
},
"autoload": {
@@ -6050,7 +6075,7 @@
"issues": "https://github.com/wp-cli/wp-cli/issues",
"source": "https://github.com/wp-cli/wp-cli"
},
- "time": "2026-07-30T07:41:21+00:00"
+ "time": "2026-08-17T10:21:29+00:00"
},
{
"name": "wp-cli/wp-cli-bundle",
@@ -6058,12 +6083,12 @@
"source": {
"type": "git",
"url": "https://github.com/wp-cli/wp-cli-bundle.git",
- "reference": "63e7d5c1851967e2ff68938e9b50ba7756c4ecd6"
+ "reference": "8796efea130e1592844fa65f1321eda4e00a25f1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/wp-cli/wp-cli-bundle/zipball/63e7d5c1851967e2ff68938e9b50ba7756c4ecd6",
- "reference": "63e7d5c1851967e2ff68938e9b50ba7756c4ecd6",
+ "url": "https://api.github.com/repos/wp-cli/wp-cli-bundle/zipball/8796efea130e1592844fa65f1321eda4e00a25f1",
+ "reference": "8796efea130e1592844fa65f1321eda4e00a25f1",
"shasum": ""
},
"require": {
@@ -6071,36 +6096,36 @@
"wp-cli/ability-command": "^1",
"wp-cli/ai-command": "^1",
"wp-cli/block-command": "^1",
- "wp-cli/cache-command": "^2",
- "wp-cli/checksum-command": "^2.1",
- "wp-cli/config-command": "^2.1",
- "wp-cli/core-command": "^2.1",
- "wp-cli/cron-command": "^2",
- "wp-cli/db-command": "^2",
- "wp-cli/embed-command": "^2",
- "wp-cli/entity-command": "^2",
- "wp-cli/eval-command": "^2",
- "wp-cli/export-command": "^2",
- "wp-cli/extension-command": "^2.1",
- "wp-cli/i18n-command": "^2",
- "wp-cli/import-command": "^2",
- "wp-cli/language-command": "^2",
- "wp-cli/maintenance-mode-command": "^2",
- "wp-cli/media-command": "^2",
- "wp-cli/rewrite-command": "^2",
- "wp-cli/role-command": "^2",
- "wp-cli/scaffold-command": "^2",
- "wp-cli/search-replace-command": "^2",
- "wp-cli/server-command": "^2",
- "wp-cli/shell-command": "^2",
+ "wp-cli/cache-command": "^3",
+ "wp-cli/checksum-command": "^3",
+ "wp-cli/config-command": "^3",
+ "wp-cli/core-command": "^3",
+ "wp-cli/cron-command": "^3",
+ "wp-cli/db-command": "^3",
+ "wp-cli/embed-command": "^3",
+ "wp-cli/entity-command": "^3",
+ "wp-cli/eval-command": "^3",
+ "wp-cli/export-command": "^3",
+ "wp-cli/extension-command": "^3",
+ "wp-cli/i18n-command": "^3",
+ "wp-cli/import-command": "^3",
+ "wp-cli/language-command": "^3",
+ "wp-cli/maintenance-mode-command": "^3",
+ "wp-cli/media-command": "^3",
+ "wp-cli/rewrite-command": "^3",
+ "wp-cli/role-command": "^3",
+ "wp-cli/scaffold-command": "^3",
+ "wp-cli/search-replace-command": "^3",
+ "wp-cli/server-command": "^3",
+ "wp-cli/shell-command": "^3",
"wp-cli/site-health-command": "^1",
- "wp-cli/super-admin-command": "^2",
- "wp-cli/widget-command": "^2",
+ "wp-cli/super-admin-command": "^3",
+ "wp-cli/widget-command": "^3",
"wp-cli/wp-cli": "dev-main"
},
"require-dev": {
"roave/security-advisories": "dev-latest",
- "wp-cli/package-command": "^2.1",
+ "wp-cli/package-command": "^3",
"wp-cli/wp-cli-tests": "^5"
},
"suggest": {
@@ -6129,7 +6154,7 @@
"issues": "https://github.com/wp-cli/wp-cli-bundle/issues",
"source": "https://github.com/wp-cli/wp-cli-bundle"
},
- "time": "2026-07-29T15:25:16+00:00"
+ "time": "2026-08-19T10:40:00+00:00"
},
{
"name": "wp-cli/wp-config-transformer",
@@ -6182,15 +6207,15 @@
},
{
"name": "wp-plugin/akismet",
- "version": "5.7",
+ "version": "5.7.2",
"source": {
"type": "svn",
"url": "https://plugins.svn.wordpress.org/akismet/",
- "reference": "tags/5.7"
+ "reference": "tags/5.7.2"
},
"dist": {
"type": "zip",
- "url": "https://downloads.wordpress.org/plugin/akismet.5.7.zip"
+ "url": "https://downloads.wordpress.org/plugin/akismet.5.7.2.zip"
},
"require": {
"composer/installers": "~1.0|~2.0"
@@ -6209,7 +6234,7 @@
"issues": "https://wordpress.org/support/plugin/akismet",
"source": "https://plugins.svn.wordpress.org/akismet"
},
- "time": "2026-05-01T00:26:06+00:00"
+ "time": "2026-08-18T23:45:55+00:00"
},
{
"name": "wp-plugin/breadcrumb-navxt",
@@ -6271,7 +6296,7 @@
"issues": "https://wordpress.org/support/plugin/code-profiler",
"source": "https://plugins.svn.wordpress.org/code-profiler"
},
- "time": "2026-06-12T06:35:51+00:00"
+ "time": "2026-08-16T12:13:13+00:00"
},
{
"name": "wp-plugin/conditional-blocks",
@@ -6336,15 +6361,15 @@
},
{
"name": "wp-plugin/easy-table-of-contents",
- "version": "2.0.85",
+ "version": "2.0.86",
"source": {
"type": "svn",
"url": "https://plugins.svn.wordpress.org/easy-table-of-contents/",
- "reference": "tags/2.0.85"
+ "reference": "tags/2.0.86"
},
"dist": {
"type": "zip",
- "url": "https://downloads.wordpress.org/plugin/easy-table-of-contents.2.0.85.zip"
+ "url": "https://downloads.wordpress.org/plugin/easy-table-of-contents.2.0.86.zip"
},
"require": {
"composer/installers": "~1.0|~2.0"
@@ -6363,7 +6388,7 @@
"issues": "https://wordpress.org/support/plugin/easy-table-of-contents",
"source": "https://plugins.svn.wordpress.org/easy-table-of-contents"
},
- "time": "2026-06-10T12:56:23+00:00"
+ "time": "2026-08-04T08:16:26+00:00"
},
{
"name": "wp-plugin/facebook-for-woocommerce",
@@ -6429,15 +6454,15 @@
},
{
"name": "wp-plugin/import-meetup-events",
- "version": "1.7.4",
+ "version": "1.7.5",
"source": {
"type": "svn",
"url": "https://plugins.svn.wordpress.org/import-meetup-events/",
- "reference": "tags/1.7.4"
+ "reference": "tags/1.7.5"
},
"dist": {
"type": "zip",
- "url": "https://downloads.wordpress.org/plugin/import-meetup-events.1.7.4.zip"
+ "url": "https://downloads.wordpress.org/plugin/import-meetup-events.1.7.5.zip"
},
"require": {
"composer/installers": "~1.0|~2.0"
@@ -6456,19 +6481,19 @@
"issues": "https://wordpress.org/support/plugin/import-meetup-events",
"source": "https://plugins.svn.wordpress.org/import-meetup-events"
},
- "time": "2026-07-28T08:01:44+00:00"
+ "time": "2026-08-18T10:56:13+00:00"
},
{
"name": "wp-plugin/jetpack",
- "version": "16.0.1",
+ "version": "16.1.1",
"source": {
"type": "svn",
"url": "https://plugins.svn.wordpress.org/jetpack/",
- "reference": "tags/16.0.1"
+ "reference": "tags/16.1.1"
},
"dist": {
"type": "zip",
- "url": "https://downloads.wordpress.org/plugin/jetpack.16.0.1.zip"
+ "url": "https://downloads.wordpress.org/plugin/jetpack.16.1.1.zip"
},
"require": {
"composer/installers": "~1.0|~2.0"
@@ -6487,19 +6512,19 @@
"issues": "https://wordpress.org/support/plugin/jetpack",
"source": "https://plugins.svn.wordpress.org/jetpack"
},
- "time": "2026-07-27T17:25:44+00:00"
+ "time": "2026-08-11T22:16:13+00:00"
},
{
"name": "wp-plugin/login-as-user",
- "version": "1.7.2",
+ "version": "1.7.3",
"source": {
"type": "svn",
"url": "https://plugins.svn.wordpress.org/login-as-user/",
- "reference": "tags/1.7.2"
+ "reference": "tags/1.7.3"
},
"dist": {
"type": "zip",
- "url": "https://downloads.wordpress.org/plugin/login-as-user.1.7.2.zip"
+ "url": "https://downloads.wordpress.org/plugin/login-as-user.1.7.3.zip"
},
"require": {
"composer/installers": "~1.0|~2.0"
@@ -6518,19 +6543,19 @@
"issues": "https://wordpress.org/support/plugin/login-as-user",
"source": "https://plugins.svn.wordpress.org/login-as-user"
},
- "time": "2026-05-25T21:41:12+00:00"
+ "time": "2026-08-20T00:15:54+00:00"
},
{
"name": "wp-plugin/mailchimp-for-woocommerce",
- "version": "6.1.1",
+ "version": "6.2",
"source": {
"type": "svn",
"url": "https://plugins.svn.wordpress.org/mailchimp-for-woocommerce/",
- "reference": "tags/6.1.1"
+ "reference": "tags/6.2"
},
"dist": {
"type": "zip",
- "url": "https://downloads.wordpress.org/plugin/mailchimp-for-woocommerce.6.1.1.zip"
+ "url": "https://downloads.wordpress.org/plugin/mailchimp-for-woocommerce.6.2.zip"
},
"require": {
"composer/installers": "~1.0|~2.0"
@@ -6549,7 +6574,7 @@
"issues": "https://wordpress.org/support/plugin/mailchimp-for-woocommerce",
"source": "https://plugins.svn.wordpress.org/mailchimp-for-woocommerce"
},
- "time": "2026-05-28T16:11:21+00:00"
+ "time": "2026-08-07T17:26:09+00:00"
},
{
"name": "wp-plugin/members",
@@ -6645,15 +6670,15 @@
},
{
"name": "wp-plugin/otter-blocks",
- "version": "3.2.1",
+ "version": "3.2.2",
"source": {
"type": "svn",
"url": "https://plugins.svn.wordpress.org/otter-blocks/",
- "reference": "tags/3.2.1"
+ "reference": "tags/3.2.2"
},
"dist": {
"type": "zip",
- "url": "https://downloads.wordpress.org/plugin/otter-blocks.3.2.1.zip"
+ "url": "https://downloads.wordpress.org/plugin/otter-blocks.3.2.2.zip"
},
"require": {
"composer/installers": "~1.0|~2.0"
@@ -6672,19 +6697,19 @@
"issues": "https://wordpress.org/support/plugin/otter-blocks",
"source": "https://plugins.svn.wordpress.org/otter-blocks"
},
- "time": "2026-07-22T07:05:45+00:00"
+ "time": "2026-08-12T12:56:51+00:00"
},
{
"name": "wp-plugin/pdf-embedder",
- "version": "5.0.1",
+ "version": "5.0.2",
"source": {
"type": "svn",
"url": "https://plugins.svn.wordpress.org/pdf-embedder/",
- "reference": "tags/5.0.1"
+ "reference": "tags/5.0.2"
},
"dist": {
"type": "zip",
- "url": "https://downloads.wordpress.org/plugin/pdf-embedder.5.0.1.zip"
+ "url": "https://downloads.wordpress.org/plugin/pdf-embedder.5.0.2.zip"
},
"require": {
"composer/installers": "~1.0|~2.0"
@@ -6696,14 +6721,14 @@
"name": "Syed Balkhi"
}
],
- "description": "Seamlessly embed PDFs into your content, with customizations and intelligent responsive resizing, and no third-party services or iframes.",
+ "description": "Embed PDF files in WordPress posts and pages with a responsive PDF viewer block, live Block Editor preview, and no third-party services.",
"homepage": "https://wp-pdf.com",
"support": {
"changelog": "https://wordpress.org/plugins/pdf-embedder/#developers",
"issues": "https://wordpress.org/support/plugin/pdf-embedder",
"source": "https://plugins.svn.wordpress.org/pdf-embedder"
},
- "time": "2026-05-25T13:46:16+00:00"
+ "time": "2026-08-14T17:01:37+00:00"
},
{
"name": "wp-plugin/pdf-viewer-block",
@@ -6738,15 +6763,15 @@
},
{
"name": "wp-plugin/pods",
- "version": "3.3.9",
+ "version": "3.3.9.1",
"source": {
"type": "svn",
"url": "https://plugins.svn.wordpress.org/pods/",
- "reference": "tags/3.3.9"
+ "reference": "tags/3.3.9.1"
},
"dist": {
"type": "zip",
- "url": "https://downloads.wordpress.org/plugin/pods.3.3.9.zip"
+ "url": "https://downloads.wordpress.org/plugin/pods.3.3.9.1.zip"
},
"require": {
"composer/installers": "~1.0|~2.0"
@@ -6765,7 +6790,7 @@
"issues": "https://wordpress.org/support/plugin/pods",
"source": "https://plugins.svn.wordpress.org/pods"
},
- "time": "2026-05-29T15:30:55+00:00"
+ "time": "2026-08-14T19:06:45+00:00"
},
{
"name": "wp-plugin/post-type-switcher",
@@ -6893,15 +6918,15 @@
},
{
"name": "wp-plugin/webberzone-code-block-highlighting",
- "version": "1.1.0",
+ "version": "1.2.0",
"source": {
"type": "svn",
"url": "https://plugins.svn.wordpress.org/webberzone-code-block-highlighting/",
- "reference": "tags/1.1.0"
+ "reference": "tags/1.2.0"
},
"dist": {
"type": "zip",
- "url": "https://downloads.wordpress.org/plugin/webberzone-code-block-highlighting.1.1.0.zip"
+ "url": "https://downloads.wordpress.org/plugin/webberzone-code-block-highlighting.1.2.0.zip"
},
"require": {
"composer/installers": "~1.0|~2.0"
@@ -6920,19 +6945,19 @@
"issues": "https://wordpress.org/support/plugin/webberzone-code-block-highlighting",
"source": "https://plugins.svn.wordpress.org/webberzone-code-block-highlighting"
},
- "time": "2026-07-23T12:35:59+00:00"
+ "time": "2026-08-13T09:01:46+00:00"
},
{
"name": "wp-plugin/woocommerce",
- "version": "10.9.4",
+ "version": "11.0.1",
"source": {
"type": "svn",
"url": "https://plugins.svn.wordpress.org/woocommerce/",
- "reference": "tags/10.9.4"
+ "reference": "tags/11.0.1"
},
"dist": {
"type": "zip",
- "url": "https://downloads.wordpress.org/plugin/woocommerce.10.9.4.zip"
+ "url": "https://downloads.wordpress.org/plugin/woocommerce.11.0.1.zip"
},
"require": {
"composer/installers": "~1.0|~2.0"
@@ -6951,19 +6976,19 @@
"issues": "https://wordpress.org/support/plugin/woocommerce",
"source": "https://plugins.svn.wordpress.org/woocommerce"
},
- "time": "2026-07-30T19:01:25+00:00"
+ "time": "2026-08-18T10:07:28+00:00"
},
{
"name": "wp-plugin/woocommerce-gateway-stripe",
- "version": "10.8.4",
+ "version": "10.9.0",
"source": {
"type": "svn",
"url": "https://plugins.svn.wordpress.org/woocommerce-gateway-stripe/",
- "reference": "tags/10.8.4"
+ "reference": "tags/10.9.0"
},
"dist": {
"type": "zip",
- "url": "https://downloads.wordpress.org/plugin/woocommerce-gateway-stripe.10.8.4.zip"
+ "url": "https://downloads.wordpress.org/plugin/woocommerce-gateway-stripe.10.9.0.zip"
},
"require": {
"composer/installers": "~1.0|~2.0"
@@ -6982,19 +7007,19 @@
"issues": "https://wordpress.org/support/plugin/woocommerce-gateway-stripe",
"source": "https://plugins.svn.wordpress.org/woocommerce-gateway-stripe"
},
- "time": "2026-07-15T22:25:52+00:00"
+ "time": "2026-08-19T15:21:35+00:00"
},
{
"name": "wp-plugin/woocommerce-paypal-payments",
- "version": "4.1.1",
+ "version": "4.1.2",
"source": {
"type": "svn",
"url": "https://plugins.svn.wordpress.org/woocommerce-paypal-payments/",
- "reference": "tags/4.1.1"
+ "reference": "tags/4.1.2"
},
"dist": {
"type": "zip",
- "url": "https://downloads.wordpress.org/plugin/woocommerce-paypal-payments.4.1.1.zip"
+ "url": "https://downloads.wordpress.org/plugin/woocommerce-paypal-payments.4.1.2.zip"
},
"require": {
"composer/installers": "~1.0|~2.0"
@@ -7013,19 +7038,19 @@
"issues": "https://wordpress.org/support/plugin/woocommerce-paypal-payments",
"source": "https://plugins.svn.wordpress.org/woocommerce-paypal-payments"
},
- "time": "2026-07-07T13:56:09+00:00"
+ "time": "2026-08-04T15:26:20+00:00"
},
{
"name": "wp-plugin/woocommerce-services",
- "version": "3.6.10",
+ "version": "3.6.12",
"source": {
"type": "svn",
"url": "https://plugins.svn.wordpress.org/woocommerce-services/",
- "reference": "tags/3.6.10"
+ "reference": "tags/3.6.12"
},
"dist": {
"type": "zip",
- "url": "https://downloads.wordpress.org/plugin/woocommerce-services.3.6.10.zip"
+ "url": "https://downloads.wordpress.org/plugin/woocommerce-services.3.6.12.zip"
},
"require": {
"composer/installers": "~1.0|~2.0"
@@ -7044,19 +7069,19 @@
"issues": "https://wordpress.org/support/plugin/woocommerce-services",
"source": "https://plugins.svn.wordpress.org/woocommerce-services"
},
- "time": "2026-07-27T06:56:49+00:00"
+ "time": "2026-08-10T12:31:40+00:00"
},
{
"name": "wp-plugin/wp-graphql",
- "version": "2.18.0",
+ "version": "2.20.0",
"source": {
"type": "svn",
"url": "https://plugins.svn.wordpress.org/wp-graphql/",
- "reference": "tags/2.18.0"
+ "reference": "tags/2.20.0"
},
"dist": {
"type": "zip",
- "url": "https://downloads.wordpress.org/plugin/wp-graphql.2.18.0.zip"
+ "url": "https://downloads.wordpress.org/plugin/wp-graphql.2.20.0.zip"
},
"require": {
"composer/installers": "~1.0|~2.0"
@@ -7075,19 +7100,19 @@
"issues": "https://wordpress.org/support/plugin/wp-graphql",
"source": "https://plugins.svn.wordpress.org/wp-graphql"
},
- "time": "2026-07-23T17:46:12+00:00"
+ "time": "2026-08-18T16:56:07+00:00"
},
{
"name": "wp-plugin/wp-job-manager",
- "version": "2.4.5",
+ "version": "2.4.6",
"source": {
"type": "svn",
"url": "https://plugins.svn.wordpress.org/wp-job-manager/",
- "reference": "tags/2.4.5"
+ "reference": "tags/2.4.6"
},
"dist": {
"type": "zip",
- "url": "https://downloads.wordpress.org/plugin/wp-job-manager.2.4.5.zip"
+ "url": "https://downloads.wordpress.org/plugin/wp-job-manager.2.4.6.zip"
},
"require": {
"composer/installers": "~1.0|~2.0"
@@ -7106,7 +7131,7 @@
"issues": "https://wordpress.org/support/plugin/wp-job-manager",
"source": "https://plugins.svn.wordpress.org/wp-job-manager"
},
- "time": "2026-07-08T15:21:03+00:00"
+ "time": "2026-08-19T15:11:03+00:00"
},
{
"name": "wp-plugin/wp-job-manager-xml-csv-listings-import",
@@ -7139,37 +7164,6 @@
},
"time": "2026-07-30T22:30:51+00:00"
},
- {
- "name": "wp-plugin/wp-mail-smtp",
- "version": "4.9.0",
- "source": {
- "type": "svn",
- "url": "https://plugins.svn.wordpress.org/wp-mail-smtp/",
- "reference": "tags/4.9.0"
- },
- "dist": {
- "type": "zip",
- "url": "https://downloads.wordpress.org/plugin/wp-mail-smtp.4.9.0.zip"
- },
- "require": {
- "composer/installers": "~1.0|~2.0"
- },
- "type": "wordpress-plugin",
- "notification-url": "https://wp-packages.org/downloads",
- "authors": [
- {
- "name": "Syed Balkhi"
- }
- ],
- "description": "Make email delivery easy for WordPress. Connect with SMTP, Gmail, Outlook, SendGrid, Mailgun, SES, Zoho, + more. Rated #1 WordPress SMTP Email plugin.",
- "homepage": "https://wpmailsmtp.com/",
- "support": {
- "changelog": "https://wordpress.org/plugins/wp-mail-smtp/#developers",
- "issues": "https://wordpress.org/support/plugin/wp-mail-smtp",
- "source": "https://plugins.svn.wordpress.org/wp-mail-smtp"
- },
- "time": "2026-06-25T08:56:20+00:00"
- },
{
"name": "wp-plugin/wp-maintenance-mode",
"version": "2.6.23",
@@ -7202,15 +7196,15 @@
},
{
"name": "wp-plugin/wp-rest-api-authentication",
- "version": "4.4.0",
+ "version": "4.6.0",
"source": {
"type": "svn",
"url": "https://plugins.svn.wordpress.org/wp-rest-api-authentication/",
- "reference": "tags/4.4.0"
+ "reference": "tags/4.6.0"
},
"dist": {
"type": "zip",
- "url": "https://downloads.wordpress.org/plugin/wp-rest-api-authentication.4.4.0.zip"
+ "url": "https://downloads.wordpress.org/plugin/wp-rest-api-authentication.4.6.0.zip"
},
"require": {
"composer/installers": "~1.0|~2.0"
@@ -7229,7 +7223,7 @@
"issues": "https://wordpress.org/support/plugin/wp-rest-api-authentication",
"source": "https://plugins.svn.wordpress.org/wp-rest-api-authentication"
},
- "time": "2026-06-01T10:09:53+00:00"
+ "time": "2026-08-04T08:31:12+00:00"
},
{
"name": "wp-plugin/zapier",
@@ -7289,15 +7283,15 @@
},
{
"name": "wp-theme/twentytwentythree",
- "version": "1.6",
+ "version": "1.7",
"source": {
"type": "svn",
"url": "https://themes.svn.wordpress.org/twentytwentythree/",
- "reference": "1.6"
+ "reference": "1.7"
},
"dist": {
"type": "zip",
- "url": "https://downloads.wordpress.org/theme/twentytwentythree.1.6.zip"
+ "url": "https://downloads.wordpress.org/theme/twentytwentythree.1.7.zip"
},
"require": {
"composer/installers": "~1.0|~2.0"
@@ -7311,7 +7305,7 @@
"issues": "https://wordpress.org/support/theme/twentytwentythree",
"source": "https://themes.svn.wordpress.org/twentytwentythree"
},
- "time": "2026-03-23T19:23:21+00:00"
+ "time": "2026-08-19T16:46:58+00:00"
},
{
"name": "wpackagist-plugin/crisp",
@@ -7333,7 +7327,7 @@
},
{
"name": "wpackagist-plugin/social-networks-auto-poster-facebook-twitter-g",
- "version": "4.4.7",
+ "version": "4.4.8",
"source": {
"type": "svn",
"url": "https://plugins.svn.wordpress.org/social-networks-auto-poster-facebook-twitter-g/",
@@ -7341,7 +7335,7 @@
},
"dist": {
"type": "zip",
- "url": "https://downloads.wordpress.org/plugin/social-networks-auto-poster-facebook-twitter-g.zip?timestamp=1772146112"
+ "url": "https://downloads.wordpress.org/plugin/social-networks-auto-poster-facebook-twitter-g.zip?timestamp=1786634481"
},
"require": {
"composer/installers": "^1.0 || ^2.0"
@@ -8095,12 +8089,12 @@
"source": {
"type": "git",
"url": "https://github.com/Roave/SecurityAdvisories.git",
- "reference": "65b89988ea149da6864f93c3030fd5c24991431d"
+ "reference": "4a8b10995f6b62488cf92b121cfcc644d060cd94"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/65b89988ea149da6864f93c3030fd5c24991431d",
- "reference": "65b89988ea149da6864f93c3030fd5c24991431d",
+ "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/4a8b10995f6b62488cf92b121cfcc644d060cd94",
+ "reference": "4a8b10995f6b62488cf92b121cfcc644d060cd94",
"shasum": ""
},
"conflict": {
@@ -8141,7 +8135,7 @@
"aoe/restler": "<1.7.1",
"apache-solr-for-typo3/solr": "<2.8.3",
"apereo/phpcas": "<1.6",
- "api-platform/core": "<4.1.29|>=4.2,<4.2.25|>=4.3,<4.3.8",
+ "api-platform/core": "<4.1.30|>=4.2,<4.2.26|>=4.3,<4.3.12",
"api-platform/graphql": "<3.4.17|>=4,<4.0.22|>=4.1,<4.1.5",
"api-platform/hal": ">=4,<4.1.29|>=4.2,<4.2.25|>=4.3,<4.3.8",
"api-platform/json-api": ">=4,<4.1.29|>=4.2,<4.2.25|>=4.3,<4.3.8",
@@ -8199,13 +8193,13 @@
"brotkrueml/codehighlight": "<2.7",
"brotkrueml/schema": "<1.13.1|>=2,<2.5.1",
"brotkrueml/typo3-matomo-integration": "<1.3.2",
- "buddypress/buddypress": "<7.2.1",
+ "buddypress/buddypress": "<14.5",
"bugsnag/bugsnag-laravel": ">=2,<2.0.2",
"bvbmedia/multishop": "<2.0.39",
"bytefury/crater": "<6.0.2",
"cachethq/cachet": "<2.5.1",
"cadmium-org/cadmium-cms": "<=0.4.9",
- "cakephp/authentication": "<3.3.6|>=4,<4.1.1",
+ "cakephp/authentication": "<2.11.1|>=3,<3.3.6|>=4,<4.1.1",
"cakephp/cakephp": "<4.5.11|>=4.6,<4.6.4|>=5,<5.1.7|>=5.2,<5.2.13|>=5.3,<5.3.6",
"cakephp/database": ">=4.2,<4.2.12|>=4.3,<4.3.11|>=4.4,<4.4.10",
"cardgate/magento2": "<2.0.33",
@@ -8230,7 +8224,7 @@
"code16/sharp": "<9.22.3",
"codeception/codeception": "<3.1.3|>=4,<4.1.22",
"codeigniter/framework": "<3.1.10",
- "codeigniter4/framework": "<4.7.2",
+ "codeigniter4/framework": "<4.7.4",
"codeigniter4/shield": "<1.0.0.0-beta8",
"codiad/codiad": "<=2.8.4",
"codingms/additional-tca": ">=1.7,<1.15.17|>=1.16,<1.16.9",
@@ -8255,7 +8249,7 @@
"cpsit/typo3-mailqueue": "<0.4.5|>=0.5,<0.5.2",
"craftcms/aws-s3": ">=2.0.2,<=2.2.4",
"craftcms/azure-blob": ">=2.0.0.0-beta1,<=2.1",
- "craftcms/cms": "<4.18|>=5,<5.10",
+ "craftcms/cms": "<4.18.3|>=5,<5.10.8",
"craftcms/commerce": ">=4,<=4.11.1|>=5,<=5.6.4",
"craftcms/composer": ">=4.0.0.0-RC1-dev,<=4.10|>=5.0.0.0-RC1-dev,<=5.5.1",
"craftcms/craft": ">=3.5,<=4.16.17|>=5.0.0.0-RC1-dev,<=5.8.21",
@@ -8338,7 +8332,7 @@
"drupal/umami_analytics": "<1.0.1",
"duncanmcclean/guest-entries": "<3.1.2",
"dweeves/magmi": "<=0.7.24",
- "easycorp/easyadmin-bundle": ">=4,<4.29.10|>=5,<5.0.13",
+ "easycorp/easyadmin-bundle": ">=4,<4.29.16|>=5,<5.5.1",
"ec-cube/ec-cube": "<2.4.4|>=2.11,<=2.17.1|>=3,<=3.0.18.0-patch4|>=4,<=4.3.1",
"ecodev/newsletter": "<=4",
"ectouch/ectouch": "<=2.7.2",
@@ -8423,7 +8417,7 @@
"friendsoftypo3/tt-address": "<8.1.2|>=9,<9.1.1|>=10,<10.0.1",
"froala/wysiwyg-editor": "<=4.3",
"frosh/adminer-platform": "<2.2.1",
- "froxlor/froxlor": "<2.3.7",
+ "froxlor/froxlor": "<2.3.8",
"frozennode/administrator": "<=5.0.12",
"fuel/core": "<1.8.1",
"funadmin/funadmin": "<=7.1.0.0-RC6",
@@ -8432,7 +8426,7 @@
"georgringer/news": "<10.0.4|>=11,<11.4.4|>=12,<12.3.2|>=13,<13.0.2|>=14,<14.0.3",
"geshi/geshi": "<=1.0.9.1",
"getformwork/formwork": "<=2.3.3",
- "getgrav/grav": "<=2.0.0.0-RC8",
+ "getgrav/grav": "<2.0.4",
"getgrav/grav-plugin-api": "<1.0.0.0-beta15",
"getgrav/grav-plugin-form": "<9.1",
"getkirby/cms": "<=4.9.3|>=5,<=5.4.3",
@@ -8452,7 +8446,7 @@
"gregwar/rst": "<1.0.3",
"grumpydictator/firefly-iii": "<=6.6.2",
"gugoan/economizzer": "<=0.9.0.0-beta1",
- "guzzlehttp/guzzle": "<7.15.1",
+ "guzzlehttp/guzzle": "<7.15.2|>=8,<8.0.1",
"guzzlehttp/guzzle-services": "<1.5.4",
"guzzlehttp/oauth-subscriber": "<0.8.1",
"guzzlehttp/psr7": "<2.12.3",
@@ -8564,16 +8558,16 @@
"lavalite/cms": "<=10.1",
"lavitto/typo3-form-to-database": "<2.2.5|>=3,<3.2.2|>=4,<4.2.3|>=5,<5.0.2",
"lcobucci/jwt": ">=3.4,<3.4.6|>=4,<4.0.4|>=4.1,<4.1.5",
- "league/commonmark": "<=2.8.1",
+ "league/commonmark": "<2.9",
"league/flysystem": "<1.1.4|>=2,<2.1.1",
"league/oauth2-server": ">=8.3.2,<8.4.2|>=8.5,<8.5.3",
"leantime/leantime": "<3.3",
"lexik/jwt-authentication-bundle": "<2.10.7|>=2.11,<2.11.3",
"libreform/libreform": ">=2,<=2.0.8",
- "librenms/librenms": "<26.3",
+ "librenms/librenms": "<26.7",
"liftkit/database": "<2.13.2",
"lightsaml/lightsaml": "<1.3.5",
- "limesurvey/limesurvey": "<6.15.4",
+ "limesurvey/limesurvey": "<=7.0.0.0-beta1",
"livehelperchat/livehelperchat": "<=3.91",
"livewire-filemanager/filemanager": "<=1.0.4",
"livewire/livewire": "<2.12.7|>=3.0.0.0-beta1,<3.6.4",
@@ -8607,6 +8601,7 @@
"mautic/grapes-js-builder-bundle": ">=4,<4.4.18|>=5,<5.2.9|>=6,<6.0.7",
"maximebf/debugbar": "<1.19",
"mckenziearts/livewire-markdown-editor": "<1.3",
+ "mcp/sdk": ">=0.5,<0.7.1",
"mdanter/ecc": "<2",
"mediawiki/abuse-filter": "<1.39.9|>=1.40,<1.41.3|>=1.42,<1.42.2",
"mediawiki/cargo": "<3.8.3",
@@ -8628,7 +8623,7 @@
"microsoft/microsoft-graph-core": "<2.0.2",
"microweber/microweber": "<2.0.20",
"mikehaertl/php-shellcommand": "<1.6.1",
- "mineadmin/mineadmin": "<=3.0.9",
+ "mineadmin/mineadmin": "<3.2.0.0-alpha2",
"miniorange/miniorange-saml": "<1.4.3",
"miraheze/ts-portal": "<=33",
"mittwald/typo3_forum": "<1.2.1",
@@ -8716,7 +8711,7 @@
"pagekit/pagekit": "<=1.0.18",
"paragonie/ecc": "<2.0.1",
"paragonie/random_compat": "<2",
- "paragonie/sodium_compat": "<1.24|>=2,<2.5",
+ "paragonie/sodium_compat": "<1.24.1|>=2,<2.5.1",
"passbolt/passbolt_api": "<4.6.2",
"paymenter/paymenter": "<=1.5.4",
"paypal/adaptivepayments-sdk-php": "<=3.9.2",
@@ -8739,6 +8734,7 @@
"php-standard-library/h2": ">=6.1,<6.1.2|>=6.2,<6.2.1",
"php-standard-library/php-standard-library": ">=6.1,<6.1.2|>=6.2,<6.2.1",
"phpbb/phpbb": "<3.3.16|==4.0.0.0-alpha1",
+ "phpcsstandards/phpcsutils": ">=1.0.0.0-alpha1,<1.2.3",
"phpems/phpems": ">=6,<=6.1.3",
"phpfastcache/phpfastcache": "<6.1.5|>=7,<7.1.2|>=8,<8.0.7",
"phpmailer/phpmailer": "<6.5",
@@ -8766,10 +8762,11 @@
"pimcore/demo": "<10.3",
"pimcore/ecommerce-framework-bundle": "<1.0.10",
"pimcore/perspective-editor": "<1.5.1",
- "pimcore/pimcore": "<=12.3.8|>=2026.1,<2026.1.3",
+ "pimcore/pimcore": "<12.3.9|>=2026.1,<=2026.1.4",
"pimcore/web2print-tools-bundle": "<=5.2.1|>=6.0.0.0-RC1-dev,<=6.1",
"piwik/piwik": "<1.11",
"pixelfed/pixelfed": "<0.12.5",
+ "plank/laravel-mediable": "<7",
"plotly/plotly.js": "<2.25.2",
"pocketmine/bedrock-protocol": "<8.0.2",
"pocketmine/pocketmine-mp": "<5.42.1",
@@ -8814,7 +8811,7 @@
"rap2hpoutre/laravel-log-viewer": "<0.13",
"react/http": ">=0.7,<1.9",
"really-simple-plugins/complianz-gdpr": "<6.4.2",
- "redaxo/source": "<5.21",
+ "redaxo/source": "<5.21.1",
"remdex/livehelperchat": "<4.29",
"renolit/reint-downloadmanager": "<4.0.2|>=5,<5.0.1",
"reportico-web/reportico": "<=8.1",
@@ -8871,8 +8868,8 @@
"silverstripe/versioned-admin": ">=1,<1.11.1",
"simogeo/filemanager": "<=2.5",
"simple-updates/phpwhois": "<=1",
- "simplesamlphp/saml2": "<=4.20.2|>=5,<5.0.6|>=6,<6.2.1",
- "simplesamlphp/saml2-legacy": "<=4.20.2",
+ "simplesamlphp/saml2": "<4.19.3|>=4.20,<=4.20.2|>=5,<5.0.6|>=6,<6.2.1",
+ "simplesamlphp/saml2-legacy": "<4.19.3|>=4.20,<=4.20.2",
"simplesamlphp/simplesamlphp": "<=2.4.6|>=2.5,<=2.5.1",
"simplesamlphp/simplesamlphp-module-casserver": "<=7.0.2",
"simplesamlphp/simplesamlphp-module-infocard": "<1.0.1",
@@ -8888,8 +8885,8 @@
"slim/psr7": "<1.4.1|>=1.5,<1.5.1|>=1.6,<1.6.1",
"slim/slim": "<2.6|>=4.4,<=4.15.1",
"slub/slub-events": "<3.0.3",
- "smarty/smarty": "<4.5.3|>=5,<5.1.1",
- "snipe/snipe-it": "<=8.6.1",
+ "smarty/smarty": "<4.5.7|>=5,<5.8.4",
+ "snipe/snipe-it": "<8.6.3",
"socalnick/scn-social-auth": "<1.15.2",
"socialiteproviders/steam": "<1.1",
"solidinvoice/solidinvoice": "<=2.3.15",
@@ -8905,13 +8902,13 @@
"spomky-labs/otphp": "<11.4.3",
"spoon/library": "<1.4.1",
"spoonity/tcpdf": "<6.2.22",
- "squizlabs/php_codesniffer": ">=1,<2.8.1|>=3,<3.0.1",
+ "squizlabs/php_codesniffer": "<3.13.6|>=4,<4.0.2",
"ssddanbrown/bookstack": "<24.05.1",
"starcitizentools/citizen-skin": ">=1.9.4,<3.9",
"starcitizentools/short-description": ">=4,<4.0.1",
"starcitizentools/tabber-neue": ">=1.9.1,<2.7.2|>=3,<3.1.1",
"starcitizenwiki/embedvideo": "<=4",
- "statamic/cms": "<5.74|>=6,<6.20.3",
+ "statamic/cms": "<5.74.3|>=6,<6.24.2",
"stormpath/sdk": "<9.9.99",
"studio-42/elfinder": "<=2.1.67",
"studiomitte/friendlycaptcha": "<0.1.4",
@@ -8928,6 +8925,7 @@
"sylius/admin-bundle": ">=1,<1.0.17|>=1.1,<1.1.9|>=1.2,<1.2.2",
"sylius/grid": ">=1,<1.1.19|>=1.2,<1.2.18|>=1.3,<1.3.13|>=1.4,<1.4.5|>=1.5,<1.5.1",
"sylius/grid-bundle": "<1.10.1",
+ "sylius/mollie-plugin": "<2.2.8|>=3,<3.2.4|>=3.3,<3.3.1",
"sylius/paypal-plugin": "<1.6.2|>=1.7,<1.7.2|>=2,<2.0.2",
"sylius/resource-bundle": ">=1,<1.3.14|>=1.4,<1.4.7|>=1.5,<1.5.2|>=1.6,<1.6.4",
"sylius/sylius": "<1.9.12|>=1.10,<1.10.16|>=1.11,<1.11.17|>=1.12,<=1.12.22|>=1.13,<=1.13.14|>=1.14,<=1.14.17|>=2,<2.0.18|>=2.1,<2.1.15|>=2.2,<2.2.6",
@@ -8999,7 +8997,7 @@
"thelia/thelia": ">=2.0.0.0-beta1,<2.1.3",
"theonedemon/phpwhois": "<=4.2.5",
"thinkcmf/thinkcmf": "<6.0.8",
- "thorsten/phpmyfaq": "<4.1.4",
+ "thorsten/phpmyfaq": "<4.2.0.0-alpha",
"tikiwiki/tiki-manager": "<=17.1",
"timber/timber": ">=0.16.6,<1.23.1|>=1.24,<1.24.1|>=2,<2.1",
"tinymce/tinymce": "<7.9.3|>=8,<8.5.1",
@@ -9026,7 +9024,7 @@
"typo3/cms-backend": "<10.4.57|>=11,<11.5.51|>=12,<12.4.46|>=13,<13.4.31|>=14,<14.3.3",
"typo3/cms-belog": ">=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2",
"typo3/cms-beuser": ">=9,<9.5.55|>=10,<10.4.54|>=11,<11.5.48|>=12,<12.4.37|>=13,<13.4.18",
- "typo3/cms-core": "<10.4.57|>=11,<11.5.51|>=12,<12.4.46|>=13,<13.4.31|>=14,<14.3.3",
+ "typo3/cms-core": "<10.4.57|>=11,<11.5.51|>=12,<12.4.46|>=13,<13.4.34|>=14,<14.3.6",
"typo3/cms-dashboard": ">=10,<10.4.54|>=11,<11.5.48|>=12,<12.4.37|>=13,<13.4.18",
"typo3/cms-extbase": "<6.2.24|>=7,<7.6.8|==8.1.1",
"typo3/cms-extensionmanager": ">=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2",
@@ -9096,8 +9094,8 @@
"wikibase/wikibase": "<=1.39.3",
"wikimedia/parsoid": "<0.12.2",
"willdurand/js-translation-bundle": "<2.1.1",
- "winter/wn-backend-module": "<1.2.12",
- "winter/wn-cms-module": "<=1.2.9",
+ "winter/wn-backend-module": "<1.2.13",
+ "winter/wn-cms-module": "<=1.2.12",
"winter/wn-dusk-plugin": "<2.1",
"winter/wn-system-module": "<1.2.4",
"wintercms/winter": "<=1.2.3",
@@ -9106,7 +9104,7 @@
"woocommerce/woocommerce": "<6.6|>=8.8,<8.8.5|>=8.9,<8.9.3",
"wp-cli/wp-cli": ">=0.12,<2.5",
"wp-coding-standards/wpcs": ">=0.14.1,<3.4.1",
- "wp-graphql/wp-graphql": "<=1.14.5",
+ "wp-graphql/wp-graphql": "<=2.6",
"wp-premium/gravityforms": "<2.4.21",
"wpanel/wpanel4-cms": "<=4.3.1",
"wpcloud/wp-stateless": "<3.2",
@@ -9212,7 +9210,7 @@
"type": "tidelift"
}
],
- "time": "2026-07-30T23:07:05+00:00"
+ "time": "2026-08-19T20:26:39+00:00"
},
{
"name": "slevomat/coding-standard",
@@ -9281,16 +9279,16 @@
},
{
"name": "squizlabs/php_codesniffer",
- "version": "3.13.5",
+ "version": "3.13.6",
"source": {
"type": "git",
"url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git",
- "reference": "0ca86845ce43291e8f5692c7356fccf3bcf02bf4"
+ "reference": "4c378e1a528ea066890fc2397cbdd2f94eb2fc91"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/0ca86845ce43291e8f5692c7356fccf3bcf02bf4",
- "reference": "0ca86845ce43291e8f5692c7356fccf3bcf02bf4",
+ "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/4c378e1a528ea066890fc2397cbdd2f94eb2fc91",
+ "reference": "4c378e1a528ea066890fc2397cbdd2f94eb2fc91",
"shasum": ""
},
"require": {
@@ -9356,20 +9354,20 @@
"type": "thanks_dev"
}
],
- "time": "2025-11-04T16:30:35+00:00"
+ "time": "2026-08-06T00:17:32+00:00"
},
{
"name": "symfony/console",
- "version": "v8.1.2",
+ "version": "v8.1.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
- "reference": "535e18a1b8925f6c01a55b171d157ab66c2ace15"
+ "reference": "68efa2ebfd9a362951eb5a8b09fd177c66ddec24"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/535e18a1b8925f6c01a55b171d157ab66c2ace15",
- "reference": "535e18a1b8925f6c01a55b171d157ab66c2ace15",
+ "url": "https://api.github.com/repos/symfony/console/zipball/68efa2ebfd9a362951eb5a8b09fd177c66ddec24",
+ "reference": "68efa2ebfd9a362951eb5a8b09fd177c66ddec24",
"shasum": ""
},
"require": {
@@ -9436,7 +9434,7 @@
"terminal"
],
"support": {
- "source": "https://github.com/symfony/console/tree/v8.1.2"
+ "source": "https://github.com/symfony/console/tree/v8.1.4"
},
"funding": [
{
@@ -9456,7 +9454,7 @@
"type": "tidelift"
}
],
- "time": "2026-07-27T13:58:19+00:00"
+ "time": "2026-07-31T12:43:13+00:00"
},
{
"name": "symfony/polyfill-intl-grapheme",
@@ -9975,12 +9973,12 @@
"source": {
"type": "git",
"url": "https://github.com/WordPress/WordPress-Coding-Standards.git",
- "reference": "ec2ff942335f33683a5957a85d138753876a05cf"
+ "reference": "a31c3198d5f2618dc42b86766936696298dfd890"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/ec2ff942335f33683a5957a85d138753876a05cf",
- "reference": "ec2ff942335f33683a5957a85d138753876a05cf",
+ "url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/a31c3198d5f2618dc42b86766936696298dfd890",
+ "reference": "a31c3198d5f2618dc42b86766936696298dfd890",
"shasum": ""
},
"require": {
@@ -9991,7 +9989,7 @@
"php": ">=7.2",
"phpcsstandards/phpcsextra": "^1.5.1",
"phpcsstandards/phpcsutils": "^1.2.3",
- "squizlabs/php_codesniffer": "^3.13.5"
+ "squizlabs/php_codesniffer": "^3.13.6"
},
"require-dev": {
"php-parallel-lint/php-console-highlighter": "^1.0.0",
@@ -10034,7 +10032,7 @@
"type": "custom"
}
],
- "time": "2026-07-27T11:53:23+00:00"
+ "time": "2026-08-10T10:25:32+00:00"
}
],
"aliases": [],
diff --git a/mysql.cnf b/mysql.cnf
new file mode 100644
index 00000000..7de80aea
--- /dev/null
+++ b/mysql.cnf
@@ -0,0 +1,3 @@
+[mysqld]
+mysql-native-password = ON
+binlog_expire_logs_seconds = 864000
diff --git a/package-lock.json b/package-lock.json
index 89b586e5..7da39813 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "cbf-wordpress",
- "version": "0.1.50",
+ "version": "0.1.51",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "cbf-wordpress",
- "version": "0.1.50",
+ "version": "0.1.51",
"devDependencies": {
"@commitlint/cli": "^19.0",
"@commitlint/config-conventional": "^19.0",
diff --git a/package.json b/package.json
index 1fe35a9d..50be971f 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "cbf-wordpress",
- "version": "0.1.50",
+ "version": "0.1.51",
"description": "[](https://packagist.org/packages/roots/bedrock) [](https://travis-ci.org/roots/bedrock)",
"devDependencies": {
"@commitlint/cli": "^19.0",
diff --git a/web/app/plugins/wp-mail-smtp-pro/CHANGELOG.md b/web/app/plugins/wp-mail-smtp-pro/CHANGELOG.md
new file mode 100644
index 00000000..594b3873
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/CHANGELOG.md
@@ -0,0 +1,1019 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+## [4.9.0] - 2026-06-24
+
+### Added:
+- WP-CLI commands to configure WP Mail SMTP from the command line, including SMTP and API-key mailer setup, individual setting management, and alert provider configuration.
+- Code Snippets tab on the Tools page to browse, preview, and install WP Mail SMTP's WPCode snippets.
+- Support for the WordPress Abilities API (WordPress 6.9), letting connected tools and AI assistants securely read your email logs, sending statistics, and debug events.
+- Your Pro license now activates automatically after installing the plugin.
+- A one-click option to deactivate a conflicting plugin directly from the conflict notice.
+- Confirmation prompt before enabling the "Hide Email Delivery Errors" option, so the setting is not turned on by accident.
+
+### Changed:
+- Improved error handling and troubleshooting guidance when an email fails to send.
+- Updated Action Scheduler library to 4.0.0.
+
+### Fixed:
+- The WPMS_LOG_BLOCKED_EMAILS constant in wp-config.php did not affect the Log Blocked Emails setting; defining the constant now correctly enforces the value, disables the option in the admin area, and shows a notice indicating it is set by a constant.
+- AWS SES mailer triggered a PHP `is_readable()` warning when reading AWS config files on servers with an `open_basedir` restriction.
+- Failed email alerts linked to the wrong Email Log entry when a backup mailer succeeded after the primary failed; the alert now links to the failed primary's Email Log entry instead of the backup's success entry.
+- Hosts using database read replicas could show duplicate Email Log entries and missing error details.
+- Alert notifications flooded scheduled actions and external channels during provider outages; automatic alerts are now limited to one notification every 5 minutes.
+- The "Last week" stat in the Lite weekly summary email showed the just-started current week's count instead of the actual previous week's total, because the email is sent on Monday at 2pm.
+- Connecting the Zoho mailer could trigger a fatal error when Zoho returned an unexpected account response.
+- Hardened permission checks and request handling across several admin and alert features to address potential security issues.
+
+## [4.8.0] - 2026-04-16
+### Added:
+- SendLayer Quick Connect - allows connecting to SendLayer without DNS setup in 2 minutes.
+
+### Changed:
+- Improved plugin security.
+
+### Fixed:
+- Fatal TypeError on PHP 8.x when Outlook One-Click OAuth token expires.
+- JavaScript error when adding the 10th or more smart routes.
+
+## [4.7.1] - 2025-11-26
+### Fixed:
+- Text domain in a couple of strings.
+### Changed:
+- Switched Return-Path option to enabled by default to align with WordPress core.
+
+## [4.7.0] - 2025-11-12
+### Added:
+- New transactional mailer: Resend integration.
+
+### Fixed:
+- Recurring task filling up task meta table.
+- Outlook one-click setup redirection after authentication.
+
+## [4.6.0] - 2025-08-28
+### Added:
+- New transactional mailer: Mandrill integration.
+
+### Fixed:
+- Incorrect deliverability verification webhook status for SMTP.com mailer.
+- References and In-Reply-To email headers are now correctly preserved for API-based mailers.
+- License verification failures on WPML or Polylang multidomain setups.
+
+## [4.5.0] - 2025-06-04
+### IMPORTANT
+- Support for PHP 7.2 has been discontinued. If you are running this version, you MUST upgrade PHP before installing or upgrading to WP Mail SMTP v4.5. Failure to do that will disable WP Mail SMTP functionality.
+
+### Added:
+- New transactional mailer: MailerSend integration.
+- WhatsApp integration for the Alerts feature.
+- Filter that allows to change Amazon SES mailer email sending request arguments.
+- Ability to store email logs if "Do Not Send" option is enabled.
+
+### Fixed:
+- Microsoft Outlook basic auth deprecation notice dismissal.
+
+### Changed:
+- Updated the list of conflicting plugins (added Site Mailer, SureMail, Gravity SMTP).
+
+## [4.4.0] - 2025-03-04
+### Added:
+- Push notifications channel for the Alerts feature.
+
+### Fixed:
+- SMTP.com emails delivery status verification via webhooks.
+- Emails queue runner Action Scheduler task deadlock issue.
+- Rate limiting for the emails resend feature.
+
+## [4.3.1] - 2025-01-21
+### Fixed:
+- Email Log and Reports search if subject contains special characters like ampersand.
+- Undefined array key "wp_mail_smtp_reports_widget_pro" warning in the dashboard widget.
+
+## [4.3.0] - 2024-12-10
+### Added:
+- One-click setup for Outlook mailer (the easiest way to set up Outlook mailer).
+- New transactional mailer: Elastic Email integration.
+
+### Changed:
+- The "Tools -> Scheduled Actions" menu is now always visible when WooCommerce or the Action Scheduler plugin is active.
+
+### Fixed:
+- SMTP password and username fields ignored `WPMS_SMTP_AUTH` constant.
+
+## [4.2.0] - 2024-11-05
+### Added:
+- New transactional mailer: Mailjet integration.
+- Discord integration for the Alerts feature.
+- New Zoho region: Canada.
+
+### Changed:
+- Improved security for sensitive data (API keys).
+- Improved performance of Email Logs and Email Reports queries.
+
+### Fixed:
+- Fatal error on the Alerts screen when alert title translation are missing.
+- SMTP2GO mailer special characters handling in from name.
+- Incomplete CSV and XLSX email log export.
+
+## [4.1.1] - 2024-08-14
+### Changed:
+- Delete `wp-mail-smtp` uploads folder on plugin uninstall, if the "Misc > Uninstall" option is enabled.
+- Decreased `PHPMailer` timeout value to 30 seconds from 5 minutes.
+- Improved Weekly Summary Email sending.
+
+### Fixed:
+- Missing Reply-To support in SMTP2GO mailer.
+- Setup Wizard translations not working correctly.
+
+## [4.1.0] - 2024-07-16
+### Added:
+- New transactional mailer: SMTP2GO.
+- Microsoft Teams integration for the Alerts feature.
+
+### Changed:
+- Recurring email queue tasks are now removed after completion.
+
+### Fixed:
+- Gmail mailer compatibility with network-wide settings on multisite setups.
+- Wrong namespace in PHP 8.0x Symfony polyfills.
+- All pending background tasks are now canceled on plugin deactivation.
+- PHP warnings in Lite translations update checks.
+- Slack alerts not working on sites with long titles.
+- License key validation issues after license changes.
+- Click Link Tracking PHP warning with malformed HTML emails.
+- Long email headers incorrectly truncated.
+
+## [4.0.2] - 2024-03-04
+### Fixed:
+- Click link tracking for links with HTML entities.
+
+## [4.0.1] - 2024-02-28
+### Fixed:
+- Gmail mailer fatal error in the Monolog vendor library on PHP 8.1 and higher.
+- Click link tracking for links with HTML encoded ampersand.
+
+## [4.0.0] - 2024-02-27
+### IMPORTANT
+- Support for WordPress versions 5.4.x or lower has been discontinued. If you are using one of those versions, you MUST upgrade WordPress before installing or upgrading to WP Mail SMTP v4.0. Failure to do that will disable WP Mail SMTP functionality.
+
+### Added:
+- Email Rate Limiting - prevents your emails from being rejected by your email provider's limitations.
+- Optimized Email Sending - move email sending requests in the background process and speed up your site.
+- Automatic database table structure migrations after plugin update.
+
+### Changed:
+- Improved open email and click link tracking injection.
+- Updated AWS SDK library to 3.298.5.
+- Improved error handling when sending emails.
+
+### Fixed:
+- Plugin update via WP-CLI.
+- Email Log: delivery status verification for the Brevo mailer on soft bounced emails.
+- Database error while adding debug events if the `wpmailsmtp_debug_events` table does not exist.
+
+## [3.11.1] - 2024-01-23
+### Fixed:
+- Setup Wizard texts.
+- Compatibility for List-Unsubscribe header.
+
+## [3.11.0] - 2023-12-12
+### Added:
+- One-click setup for Gmail mailer (the easiest way to set up Gmail mailer).
+- Filter to customize the capability required for managing the plugin.
+
+### Changed:
+- Hide test tab movement notice for new users.
+- Improved keyboard navigation styles for the Setup Wizard.
+- Removed `WPMailSMTP\Admin\PluginsInstallUpgrader` class and switched to the WordPress Core `Plugin_Upgrader` class.
+
+### Fixed:
+- Fatal error in the Pro Site Health module if the Lite module was not loaded.
+- PHP deprecation notices in the Setup Wizard on WordPress 6.4 and above.
+- PHP deprecation notice for `mb_convert_encoding` function on PHP 8.2 and above.
+
+## [3.10.1] - 2023-11-15
+### Added:
+- Cleanup of duplicate Action Scheduler tasks.
+
+### Fixed:
+- Compatibility issue with Action Scheduler lower than 3.3.0.
+
+## [3.10.0] - 2023-11-07
+### Added:
+- Connection (mailer) selector for single and bulk emails resend.
+- Ability to switch additional connection to the primary connection.
+- Ability to trigger alert when deliverability verification process detects a hard bounce.
+- Allow visibility toggle of the Amazon SES Identities through the PHP constant `WPMS_AMAZONSES_DISPLAY_IDENTITIES`.
+- Filter that allows to use self website Google OAuth redirect URL.
+
+### Changed:
+- Improved plugin settings UI.
+- Replaced our moment.js library with the WP Core's bundled one.
+
+### Fixed:
+- Translation strings on the Dashboard widget.
+- Error when email content failed to be encoded while click link tracking injection.
+- Missing "Delete Connection" link on the single additional connection page for some connections.
+
+## [3.9.0] - 2023-08-29
+### Added:
+- Pro plugin translations for: Spanish (Spain), German, Portuguese (Brazil), Italian, French, Japanese, Polish, Dutch, Russian, Turkish.
+- Email Log: ability to manually re-check email delivery status.
+- Alerts: ability to test alerts.
+
+### Changed:
+- Moved the Email Test tab from the settings page to the tools page.
+- Improved the dashboard widget settings' user experience.
+- Removed Sendinblue SDK library because it was deprecated.
+- Updated AWS SDK library to 3.277.11.
+- Mailgun API instructions and webhooks verification process.
+
+### Fixed:
+- Debug Event details popup scrolling.
+- Email logs import from the WP Mail Logging plugin caused by long non-English subjects.
+- Conflict with other plugins (Alt Manager) that made the WP Plugins install page unusable.
+- Fixed: Missed email sending debug log for the Brevo mailer.
+
+## [3.8.2] - 2023-07-18
+### Changed:
+- Improved notifications formatting and styles.
+- Sendinblue rebranded to Brevo.
+- Updated Action Scheduler library to 3.6.1.
+
+### Fixed:
+- Explicitly set "Content-Type" header for the HTML test email.
+
+## [3.8.1] - 2023-06-23
+### Fixed:
+- Print email log headers data security issue.
+
+## [3.8.0] - 2023-04-25
+### IMPORTANT
+- Support for PHP 5.6, 7.0, and 7.1 has been discontinued. If you are running one of those versions, you MUST upgrade PHP before installing or upgrading to WP Mail SMTP v3.8. Failure to do that will disable WP Mail SMTP functionality.
+
+### Added:
+- New Zoho region: Japan.
+- Email Log Importer for the WP Mail Logging plugin.
+- Ability to search email logs by specific error message or source.
+
+### Changed:
+- Email Reports default order. New default order by total.
+- Updated Moment.js library to 2.29.4.
+- Removed unneeded sodium_compat library.
+- Improved Email Reports page user experience.
+
+### Fixed:
+- Email Reports date dropdown style in Firefox browser.
+- Pro plugin deactivating on WP Multisite installation (network level) if lite plugin is active on a subsite.
+- Email address with apostrophes in the Email Test page.
+- License-related notices displayed for non-admin users.
+- Review request notice display on subsites admin area in WP Multisite installation.
+- Email logs attachments and tracking data remove on scheduled email logs cleanup.
+- Email Alerts sending when "Do Not Send" option is enabled.
+- Redirection to Setup Wizard on multisite installations with the global network-wide option enabled after plugin activation on the subsite.
+- Setup Wizard playing UA anthem for certain WP sites.
+
+## [3.7.0] - 2022-12-13
+### Added:
+- Backup Connection. Allows to set a Backup Connection which will send the email, if the Primary Connection fails.
+- Smart Routing. Send emails from different additional connections based on your configured conditions.
+- Additional Connections. Allows configuring additional mailers that can be used as a Backup Connection or in Smart Routing.
+
+### Changed:
+- Improved Action Scheduler data cleanup on plugin uninstall.
+- Improved performance for database table's validation checks.
+
+### Fixed:
+- Tasks meta database table error.
+- Gmail mailer authorization error if the oAuth app already had other non mail scopes attached.
+- Email address validation in Setup wizard.
+- Removed unneeded composer libraries autoload code.
+- Conflict detection for plugin Sendinblue - WooCommerce Email Marketing (v3.0+)
+
+## [3.6.1] - 2022-10-05
+### Fixed:
+- Setup Wizard steps navigation, when going backwards.
+
+## [3.6.0] - 2022-10-04
+### Added:
+- Source column to Email Log CSV and XLSX export.
+- The `wp_mail` function call backtrace to the Debug Events if the "Debug Email Sending" option is enabled.
+- Email Logs Configuration step in the Setup Wizard.
+- Plugin's DB tables re-creation process in WP Site Health.
+- Debug Events retention period setting.
+
+### Changed:
+- Updated the list of conflicting plugins (added Zoho Mail).
+- Improved conflicting plugins' admin notices (display multiple at once).
+- Switched to the WP Core function `is_email` for verifying email addresses.
+- Improved the detection if `wp_mail` function is overwritten.
+
+### Fixed:
+- One-time fatal error on the Email Reports page, right after Email Logs were enabled.
+- Gmail mailer not using the correct From Email Address in Domain Checker.
+- Exported (.csv and .xlsx) email log values could be executed as formulas.
+- Email Log `people` DROP index DB error.
+
+## [3.5.2] - 2022-08-17
+### Fixed:
+- The check if `wp_mail` function is overwritten on Windows servers.
+
+## [3.5.1] - 2022-07-14
+### Changed:
+- Removed MailPoet from the list of conflicting plugins.
+
+### Fixed:
+- PHP warning for undefined variable when using the Default (none) mailer.
+
+## [3.5.0] - 2022-07-12
+### Added:
+- Alerts for failed emails (Email, Slack, SMS, and Webhook notifications).
+- Check if `wp_mail` function is overwritten.
+- DB table (`wpmailsmtp_tasks_meta`) cleanup after scheduled actions execution. Keeps DB size small.
+
+### Changed:
+- Updated the list of conflicting plugins (added Branda and MailPoet).
+- Improved WP Site Health license check.
+- Improved Outlook mailer authorization flow error handling.
+- Updated Action Scheduler library to 3.4.2.
+- Improved Amazon SES (added support for V2 client).
+
+### Fixed:
+- SMTP.com mailer email content-encoding.
+- Amazon SES long Return-Path header encoding.
+- Dashboard widget graph when there is no email logs data.
+- Email Log retention period cleanup task DB table check.
+- Missing Sendinblue email body WP filter.
+- Chart.js library conflicts with other plugins.
+
+## [3.4.0] - 2022-04-27
+### Added:
+- New transactional mailer: SendLayer integration.
+- Support for changing From Email address in Outlook mailer (Send As, Shared Mailboxes, and Groups).
+- Support for bigger attachments in Outlook mailer.
+
+### Changed:
+- Improved Mailgun API error message extraction.
+- Standardized error messages format and improved WP remote request errors extraction.
+
+### Fixed:
+- Lite plugin uninstall actions clearing plugin options while Pro version is active.
+- Amazon SES identities verification data retrieval for more than 100 identities.
+- Hiding unrelated network admin notices on WP Mail SMTP pages.
+
+## [3.3.0] - 2022-02-15
+### IMPORTANT
+- Support for WordPress versions 5.1.x or lower has been discontinued. If you are using one of those versions, you MUST upgrade WordPress before installing or upgrading to WP Mail SMTP v3.3. Failure to do that will disable WP Mail SMTP functionality.
+
+### Added:
+- Email Log: sent status verification AS tasks for Postmark and SparkPost.
+- New Amazon AWS SES region: Osaka.
+- PHP 8.1 compatibility.
+
+### Changed:
+- Email Log: improved sent status verification accuracy with webhooks for: SMTP.com, Sendiblue, Mailgun, Postmark and SparkPost.
+- Updated the list of conflicting plugins (added FluentSMTP and WP HTML Mail).
+- Domain verification process for AWS SES mailer (now using DKIM verification instead of the simple TXT record verification).
+- Improved debug error message for the Other SMTP mailer in Debug Events.
+- Updated Action Scheduler library to 3.4.0.
+- Improved Action Scheduler performance.
+
+### Fixed:
+- PHP deprecated notices in Sendinblue library (PHP 7.4+).
+- Email Log: resend issue with incorrectly stored headers.
+- Non-network admin was allowed to change the Debug Event setting, if network-wide setting was enabled.
+- DB tables row in Site Health Info section is now private.
+- Debug Events' screen options visible on general Tools page.
+- Screen Options right alignment.
+
+## [3.2.1] - 2021-11-17
+### Fixed:
+- PHP 8 compatibility when existing Gmail mailer connection is revoked.
+
+## [3.2.0] - 2021-11-09
+### Added:
+- New transactional mailer: SparkPost integration.
+- One-click migration from FluentSMTP plugin.
+- Plugin constants integration in Setup Wizard.
+
+### Fixed:
+- Early plugin deactivation issue with activity log plugins.
+
+## [3.1.0] - 2021-09-28
+### Added:
+- New transactional mailer: Postmark integration.
+- Email Log: added CC and BCC columns to the table.
+- Support for string attachments (added via PHPMailer object).
+
+### Changed:
+- Improved Weekly Email Summary Setup Wizard control.
+- Improved Email Source detection in Debug Events and Email Log for WP Core sent emails.
+- Improved uninstall process. It now removes all plugin DB data and tables.
+
+### Fixed:
+- Email Source detection in Debug Events and Email Log for file paths with backslashes.
+- Amazon SES automatic closest region detection (keycdn API request).
+- Added SSL check for Amazon SES and Outlook mailers in the Setup Wizard.
+- Blurry image assets in Weekly Email Summary.
+- PHP extension mb_strings not polyfilled correctly.
+- New user registration email template for Email Controls feature.
+- Added missing is_email_sent filters for Sendinblue, Mailgun, and Gmail mailers.
+- Debug Events double-entry DB save, because of a bug in is_email_sent method for certain mailers.
+- Network subsite switcher in WP Multisite installations with subdomain configuration.
+
+## [3.0.3] - 2021-08-09
+### Fixed:
+- Weekly Summary Email sending when migration code didn't trigger.
+
+## [3.0.2] - 2021-08-05
+### Fixed:
+- Fatal PHP error on WP version 5.2 and lower (missing wp_timezone function).
+
+## [3.0.1] - 2021-08-05
+### Fixed:
+- Email Log and Debug Events Source column detection.
+
+## [3.0.0] - 2021-08-03
+### Added:
+- Email Reports - Email Log statistics grouped by email subject.
+- Weekly Email Summary - email sending statistics sent to your inbox.
+- Debug Events - logging all email sending errors and debug events.
+- Support for automatic pro plugin updates.
+- Quick admin area links.
+- New Amazon AWS SES regions: Milan, Cape Town, Bahrain, and GovCloud.
+- Email Log: the source of the sent email. Which plugin/theme (or WP Core) sent the email.
+
+### Changed:
+- Updated the successful Email Test screen.
+- How plugin DB tables are created on WP Multisite installations.
+- Included Microsoft 365 in our existing Outlook mailer labels, for easier recognition.
+- Updated Action Scheduler library to 3.2.1.
+
+### Fixed:
+- Outlook oAuth processing conflicting with other plugins (WP Amelia).
+- WP core admin spinner for the dashboard widget.
+- Email Log: non-ascii character encoding when link tracking is enabled.
+- PHP error when objects implementing `__invoke()` method were used as hook callbacks for admin notices.
+
+## [2.9.1] - 2021-07-05
+### Fixed:
+- Email Log: mailto links not working correctly when link tracking is enabled.
+
+## [2.9.0] - 2021-06-22
+### Added:
+- Email Log: store email attachments.
+- Email Log: export email logs in EML format.
+- Email Log: archive email log table column filters in screen options.
+- Email Log: aggregated email log view on Network Admin Email Log page.
+- Email Log: resend emails.
+- Email Log: open and click tracking.
+- Action Scheduler table to the Tools page.
+- Dashboard Widget with total emails sent counter (Lite only).
+
+### Changed:
+- Notifications design.
+- Sodium Compat library loading improved.
+- Improved the Zoho Mail OAuth error detection.
+- Removed the ability to set dynamic reply-to address for Zoho Mail because they don't support it.
+
+### Fixed:
+- Plugin conflict with plugins that populate $_POST data (Ultimate Addons for Elementor, WishList Member, ...).
+- Sendinblue WooCommerce Email Marketing plugin conflict admin notice display.
+- Email Log: empty email logs caused by sent status verification tasks.
+- jQuery deprecated notices.
+- Action Scheduler library loading issue.
+- Canceled Setup Wizard's OAuth authorization redirecting to the Setup Wizard when connecting via regular plugin settings.
+- Meta DB table not existing error notices on a WP Multisite subsite.
+
+## [2.8.0] - 2021-05-04
+### Added:
+- Email Log: export email logs in CSV and XLSX (Excel) formats.
+- Email Log: print email log.
+- Email Log: date range filter control.
+- Email Log: filter hook for changing required WP capabilities to view and manage Email Logs.
+- Email Log: settings constants.
+- Filter hooks for the Outlook mailer URLs, enabling GCC High compatibility.
+- WP Site Health status check for the sending domain.
+- WP Mail SMTP WordPress admin menu position filter hook.
+
+### Changed:
+- Updated the single Email Log page style.
+- Updated the list of conflicting plugins.
+- Moved the Email Test tab to the new WP Mail SMTP > Tools page.
+
+### Fixed:
+- Removed the empty admin dashboard menu item for the Setup Wizard.
+- The Setup Wizard conflicting issue with Admin 2020 plugin.
+- The plugin settings pages not opening when WishList Member plugin was active.
+- Empty error message for Outlook mailer.
+- Dashboard Widget missing database table error when Email Logging was never enabled.
+- Mailgun mailer sent status verification task accuracy.
+
+## [2.7.0] - 2021-03-23
+### IMPORTANT
+- Support for PHP 5.5 has been discontinued. If you are running one of those versions, you MUST upgrade PHP before installing or upgrading to WP Mail SMTP v2.7. Failure to do that will disable WP Mail SMTP functionality.
+
+### Added:
+- Dashboard widget with email sending statistic.
+- Email Log: email sent status filters.
+
+### Changed:
+- Updated About us plugin page.
+- Improved Domain Check Results section in Email Test tab.
+- Allow the use of different Gmail aliases as From Email address by disabling the Force From Email setting.
+- The Setup Wizard can now be launched via a button in the plugin settings. The Setup Wizard admin dashboard menu item was removed.
+
+### Fixed:
+- WP Multisite subsite admins couldn't remove oAuth connections (in Gmail, Outlook and Zoho mailers).
+- Accessible WPMS subsite settings when network-wide setting was enabled.
+
+## [2.6.0] - 2021-02-02
+### Added:
+- New plugin Setup Wizard.
+- One-click plugin upgrade (Lite to Pro).
+- New Amazon SES regions: North California, Paris, Stockholm.
+- Ability to clear error notices in multisite setups with plugin options editable network-wide.
+- SendGrid Invalid API key error message and mitigation steps on the Email Test page.
+
+### Fixed:
+- Plugin settings overwriting checkboxes with default values when a network-wide option is enabled on Multisite installation.
+- Unused Return Path option removed for the Amazon SES mailer.
+- Zoho mailer error when "reply-to" header was set to the Zoho authorized email address.
+- Outlook mailer not sending emails with custom email headers missing prepended 'X-'.
+- Email Log: Save correct sent date/time (UTC).
+- License type text displayed in plugin settings.
+- PHP fatal error on undefined Pro mailer object when switching to Lite version.
+- PHP warning in Sendinblue email sent verification task.
+- PHP 8 support.
+
+## [2.5.3] - 2020-11-23
+### Fixed:
+- Other SMTP password decryption on network-wide enabled WPMS subsites.
+
+## [2.5.2] - 2020-11-17
+### Fixed:
+- Outlook oAuth Redirect URI for network-wide enabled WP Multisites.
+- WPMS network-wide plugin settings update on network subsites (fixes oAuth token refresh issues).
+
+## [2.5.1] - 2020-10-28
+### Fixed:
+- The automatic updates issue with Gmail mailer token refresh.
+- The 'wp-amil-smtp' typo in a plugin text-domain and a HTML class.
+
+## [2.5.0] - 2020-10-20
+### Added:
+- Email Log: save and display the email sending errors in email log entries.
+- Email Log: button to delete all email log entries at once.
+- Email Log: display a proper delivery status for each sent email for these mailers: SMTP.com, Sendinblue, Mailgun.
+- Other SMTP mailer password encryption/decryption.
+
+### Changed:
+- Gmail mailer suggested steps for `invalid_grant` error on the Settings > Email Test page.
+- Gmail mailer redirect URI was changed to fix issues with `mod_security` or redirect blocking plugins/solutions.
+
+### Fixed:
+- Plugin options re-saving with unescaped magic quote characters.
+- Display correct From Email for the Outlook and Zoho mailers.
+- SMTP.com mailer email delivery for certain SMTP servers if attachment's encoded string is too long.
+
+## [2.4.0] - 2020-09-15
+### Added:
+- Prefix all 3rd-party libraries to avoid compatibility issues with other plugins using different versions of the same libraries.
+- Email controls to disable plugin and theme auto-update emails.
+- New Amazon AWS SES regions: London, Frankfurt, Ohio, Canada (Central), Mumbai, Tokyo, Seoul, Singapore, Sydney and São Paulo.
+- Amazon SES Mailer: support domains verification.
+
+### Changed:
+- Sendinblue SDK library version to 6.4.
+- Google apiclient library version to 2.7.
+- Improve plugin settings input and toggle focus states.
+- Amazon AWS SES library. Migrated to the official AWS SDK library.
+
+### Fixed:
+- Email controls to disable user and site registration/creation/activation.
+- Hide admin bar menu when errors are present and the "Hide Email Delivery Errors" setting is enabled.
+- CSS sourcemaps warning messages in browser dev tools.
+- Zoho mailer issue with comma in the "from name" field.
+- Action Scheduler tasks being registered too early and not getting assigned to the plugin group.
+- PHP notices in Amazon SES mailer automatic region detection.
+- Admin bar CSS asset loading when the admin bar is not showing.
+- License key verification triggered on non license plugin settings input enter event.
+- Fatal error on plugin uninstall if `ActionScheduler_QueueRunner::unhook_dispatch_async_request` method does not exist.
+- PHP Deprecated notice for `base_convert()` function usage in the Mailgun mailer.
+
+## [2.3.1] - 2020-08-18
+### Fixed:
+- Fatal error on plugin settings and WP Site Health pages if the plugin mailer option is empty for some reason.
+
+## [2.3.0] - 2020-08-18
+### Added:
+- Mailer: Zoho Mail integration.
+- Email Log: add "View Email" action log items in a table.
+- A WP Mail SMTP admin bar menu if there is an email delivery error or notifications.
+- WordPress 5.5 compatibility.
+
+### Changed:
+- Improve copy button user experience for Gmail and Outlook Authorized redirect URI option.
+- Email delivery error admin notices are now displayed only on plugin pages.
+- Improve plugin settings UI, by hiding certain options, if the mailer does not support them.
+- Pepipost API mailer is no longer available for new installs.
+
+### Fixed:
+- Fatal error in Review class if mailer plugin setting is empty.
+- WP Site Health check for missing DB tables.
+- Email Log: fix empty `$wpdb->actionscheduler_actions` DB table errors, when Action Scheduler is not usable yet.
+- PHP errors if Action Scheduler DB tables don't exist when registering plugin tasks.
+- PHP error in Action Scheduler on plugin uninstall.
+
+## [2.2.1] - 2020-07-07
+### Fixed:
+- Fatal error when Gmail mailer has empty Client ID and Client Secret.
+
+## [2.2.0] - 2020-07-07
+### Added:
+- Network-wide plugin settings for WordPress MultiSite.
+- Gmail mailer now supports aliases.
+- Support both old PHPMailer v5 (WordPress <=5.4) and PHPMailer v6 (WordPress >=5.5).
+- Email Log: display possible DB-related issues as a notice.
+
+### Changed:
+- Pepipost mailer is now using the native API v5 instead of the SendGrid migration API.
+
+### Fixed:
+- Incorrect Mailgun Domain Name option was not showing an email delivery error.
+- Empty debug errors for the Sendinblue mailer are no more.
+- Properly compare From Email option value with a correct default email address from WP core.
+- Site Health check for connectivity with wpmailsmtp.com server should always work properly.
+- Email Log: fix DB table creation for Database tables with `utf8mb4` collates.
+
+## [2.1.2] - 2020-06-22
+### Fixed:
+- Verify original From Email address when used in Reply-To header.
+- Added missing DB table check in the WordPress Site Health functionality.
+- Email Log: fix screen options not saving on WP 5.4.2+.
+- Email Log: properly log unsent emails when only one in a batch failed.
+- Email Log: fix DB table creation when collate is not set or when MySQL version is lower than 5.6.
+
+## [2.1.1] - 2020-06-08
+### Changed:
+- Remove current automatic default reply-to address and add WP filter `wp_mail_smtp_processor_set_default_reply_to` for setting default reply-to addresses.
+- Improve description for several options with links to an article about how to properly use constants.
+
+### Fixed:
+- PHP parse error connected to Monolog library on PHP versions < 7.x.
+
+## [2.1.0] - 2020-06-04
+### Added:
+- Async/scheduled tasks management support (e.g. cleaning up emails log table).
+- Email Log: log retention period.
+- New warning notification for selecting the "Default (none)" mailer and saving the plugin settings.
+
+### Changed:
+- Email Log: Change the logs DB table engine to improve compatibility with various MySQL versions and setups.
+- Disable valid plugin license key editing to prevent accidental changes.
+- Set the original From Email as Reply-To address if it was overwritten by the Force From Email option.
+- The Force From Email option is now enabled by default, for new plugin installs.
+- Reply-To header is now set when not provided, equals to From Name/Email.
+
+### Fixed:
+- Display a non-empty PHPMailer error when some non-SMTP mailers generate errors.
+- Display a more accurate message, when the "channel - not found" error is triggered by SMTP.com API.
+- Save and display debug errors for the "Other SMTP" mailer.
+- Email Log: mark unsent emails correctly when SMTP error occurs.
+- Improve the debug details for the "Invalid address (setFrom)" error in the Email Test tab.
+- Improve the debug details for SMTP CA verification fail, Gmail Guzzle requirements, and Gmail invalid grant errors.
+- Improve the uninstall cleanup procedure.
+
+## [2.0.1] - 2020-05-07
+### Changed:
+- Improved description of the "Do Not Send" plugin option.
+
+### Fixed:
+- Due to Pepipost API changes we now convert new lines so they are preserved in plain text emails.
+- Downgrade internal Guzzle dependency to 6.4 to temporarily fix compatibility issues with WordPress and Guzzle 6.5. Affects Gmail and Outlook mailers.
+
+## [2.0.0] - 2020-04-23
+### IMPORTANT
+- Support for PHP 5.2-5.4 has been discontinued. If you are running one of those versions, you MUST upgrade PHP before installing or upgrading to WP Mail SMTP v2.0. Failure to do that will disable WP Mail SMTP functionality.
+
+### Added:
+- Mailer: SMTP.com integration.
+
+### Changed:
+- Auto-download translations on plugin activation.
+- Email Log: Check that the DB table exists only after an email was sent.
+- Plugin filters that change the FROM Name/Email in emails are now always running last.
+
+### Fixed:
+- Email Log: Always display the logged From email address in a table.
+- Email Log: MS Outlook mailer should log the actually used FROM Email, and not defined in plugin settings.
+- `false` value of the `WPMS_SMTP_AUTH`/`WPMS_SMTP_AUTOTLS` constants was not properly handled in UI.
+- MS Outlook: do not generate an error on the Email Test page when the mailer is not configured.
+- Various minor code and internal links improvements.
+
+## [1.9.0] - 2020-03-23
+### Added:
+- Email Log: Screen Options to change the displayed number of emails per page.
+- Email Log: searchable/clickable emails in From/To columns.
+- Site Health: add various Status tests and Info section on Tools > Site Health page.
+- Notify admin if there are unsaved changes in plugin admin area options.
+
+### Fixed:
+- Test email now has a proper bottom margin for better look.
+- Adjust the way we process MS Outlook connection set up (allow dots in auth codes).
+- Email Log: bottom bulk actions do not work.
+- Email Log: incorrect plain text emails display (like "Lost Password") in "View Email" popup.
+- Email Log: discrepancy between Email Log archive and single Email log From/To values for Gmail mailer.
+
+### Changed:
+- Email Log: change the "Preview" button label to more logical "View Email".
+- Update "About us" plugin page with relevant information.
+- Settings: save default WordPress FROM Email address when incorrect FROM Email address is saved by a user.
+
+## [1.8.1] - 2019-12-13
+### Fixed:
+- Revert Guzzle version to 6.4.1 because Sendinblue and Gmail mailers may experience issues under certain circumstances while sending emails (not all sites are affected).
+- Make compatible the WordPress PhpMailer class inline attachments management with the Sendgrid API.
+
+## [1.8.0] - 2019-12-12
+### Added:
+- New recommended mailer: Pepipost.
+- "Suggest a Mailer" link in a list of mailers to send us your ideas about new ones.
+
+### Fixed:
+- Sendgrid: Content ID for attachments missing.
+
+### Changed:
+- Timeout to HTTP requests (pepipost, sendgrid, mailgun), same as max_execution_time, to prevent fails when sending emails with big attachments.
+
+## [1.7.1] - 2019-11-11
+### Fixed:
+- Compatibility with WordPress 5.3.
+- `get_default_email()` always returns empty value when the server incorrectly configured.
+- [Pro] Incorrect 'From Email' in the email log table for Gmail/Outlook mailers.
+
+## [1.7.0] - 2019-10-24
+### Added:
+- [Pro] Email Log: search in emails for email addresses, headers and content.
+- Add a new constant `WPMS_DO_NOT_SEND` to block email sending.
+- [Pro] Safety check for Email Log: DB table should exist.
+### Changed:
+- [Pro] Email Log: emails with empty subjects should be valid.
+### Fixed:
+- Default email (wordpress@example.com) rewriting in CLI mode.
+- Incorrect conflicts detection with certain plugins.
+
+## [1.6.2] - 2019-09-02
+### Fixed:
+- Race condition when loading with certain plugins, that send emails very early. Makes email delivery more reliable.
+
+## [1.6.1] - 2019-08-21
+### Fixed:
+- [Pro] Correctly run DB migration for new users, needed for Email Log.
+
+## [1.6.0] - 2019-08-21
+### Added:
+- New transactional mailer: Sendinblue.
+- Educate users to use transactional mailers for better deliverability.
+- New option and filter to disable admin area delivery error notices.
+- [Pro] Retrieve Lite translations when Pro is installed to fully translate the plugin.
+- [Pro] New geolocation endpoint to determine the nearest Amazon SES region.
+### Fixed:
+- [Pro] Correctly support old and unmaintained versions of MySQL 5.1+.
+### Changed:
+- Hide private API key saved in the DB for API based mailers using `input[type=password]`.
+- Update links to various docs, pointing now to https://wpmailsmtp.com.
+
+## [1.5.3] - 2019-08-06
+### Fixed:
+- Outlook mailer: make sure that the refreshed token is always used when sending emails.
+
+## [1.5.2] - 2019-07-18
+### Fixed:
+- "Redirect URI mismatch" error for "Gmail" mailer when trying to re-authorize an account that was initially created with version < v1.5.0.
+### Changed:
+- Make "Authentication" setting in "Other SMTP" mailer ON by default for new users.
+- Mailers docs links now point to wpmailsmtp.com own site.
+
+## [1.5.1] - 2019-07-12
+### Fixed:
+- Duplicated emails sent to the first recipient in a loop (and others not receiving their emails).
+
+## [1.5.0] - 2019-07-09
+### Added:
+- Email Log.
+- Email Control.
+- New mailer - AmazonSES.
+- New mailer - Microsoft Office 365/Outlook.
+- Support plugin auto-update with a valid license key.
+- Loсo plugin support.
+- "About us" admin area page.
+- Display in debug output a possible conflicting plugin existence.
+- Lots of actions and filters to improve flexibility of the plugin.
+### Changed:
+- Plugin menu is now top level.
+- Hide secrets/API keys in page DOM in plugin admin area.
+- Do not save constant values into the database when plugin settings are saved.
+- Lots of i18n improvements to support translation for both free and paid version of the plugin.
+- Gmail mailer - allow to change From Name email header.
+- Gmail mailer - display email used to create a connection.
+- WordPress 4.9 is the minimum WordPress version we support.
+### Fixed:
+- X-Mailer header should be present in all emails.
+- PHP notices when migrating under certain circumstances from 0.x version of the plugin.
+- Options::get_group() now supports values set via constants.
+
+## [1.4.2] - 2019-03-23
+### Changed:
+- Tested up to WordPress 5.1.x.
+- Removed TGMPA library.
+
+## [1.4.1] - 2018-12-03
+### Fixed:
+- Correctly process backslashes in SMTP passwords defined via constants.
+
+### Changed:
+- Allow to send a Test Email when Default (none) mailer is selected in plugin settings.
+
+## [1.4.0] - 2018-11-29
+### Added:
+- New option: Do Not Send - block emails from being sent.
+- New option: Send HTML or plain text emails when doing an Email Test.
+- New option: Mailgun region selection - US and EU (US is default to preserve compatibility).
+
+### Fixed:
+- Compatibility with WordPress 3.6+.
+- Compatibility with WordPress 5.0.
+- Constants usage is much more reliable now, works correctly on Multisite. Constants are global accross the whole network.
+- Preserve multipart emails when using Sendgrid/Mailgun mailers (were converted to HTML-only).
+- Security hardening.
+
+### Changed:
+- Prefill Email Test page From field with currently logged in user email.
+- Update libraries: google/apiclient-services, google/auth, phpseclib/phpseclib and their dependecies.
+- Display in debug output cURL version if Gmail mailing failed.
+- Display in debug output OpenSSL version if it exists if Gmail/SMTP mailing failed.
+- Display plugin version in dashboard error notice when emailing failed.
+- Do not allow to send Test Email if mailer not configured properly.
+- Notify in plugin admin area that Gmail doesn't allow to redefine From Name/Email etc.
+- List all constants with descriptions in plugin main file: wp_mail_smtp.php.
+- TGMPA: change descriptions from "Required" to "Recommended" (labels were incorrect).
+
+## [1.3.3] - 2018-07-05
+### Fixed:
+- Compatibility with other plugins, that are using Google Service or Google Client classes.
+
+### Changed:
+- Optimize code loading.
+
+## [1.3.2] - 2018-06-29
+### Fixed:
+- Make sure that other plugins/themes are not conflicting with our TGMPA library.
+
+## [1.3.1] - 2018-06-29
+### Fixed:
+- Other SMTP: Clear new Debug messages about failed email delivery on next successful email sending.
+- Introduce conditional autoloader to workaround Gmail PHP 5.5 requirement and its library compatibility issues vs PHP 5.3+ minimum viable plugin version.
+
+## [1.3.0] - 2018-06-28
+### Added:
+- New option: force From Email rewrite regardless of the current value.
+- New option: force From Name rewrite regardless of the current value.
+- New option: remove all plugin data on plugin uninstall (when user deletes it).
+- Notify site admins in wp-admin area with a notice about last failed email delivery. Cleans up on successful delivery.
+- Notify site admins in wp-admin area with a notice about possible compatibility issues with other SMTP and email delivery plugins.
+- Improve User Debug Experience when doing Email Test - display helpful description and steps to fix the issue.
+- New users: provide default SMTP Port value for new users based on Encryption selection.
+- New users: notify about not configured plugin settings.
+- New users: Recommend free WPForms Lite plugin for those who don't have it.
+- SendGrid/Mailgun: provide support for multipart/alternative types of emails.
+- Gmail: new button to remove connection and to connect a new Google account.
+
+### Fixed:
+- Support plugin installation into /mu-plugins/ directory.
+- SendGrid: required text/plain part of email being the first one - fixes plain text emails not having links.
+- SendGrid and Mailgun: improperly sending plain text emails in html format.
+- SMTP Debug output was empty in some cases.
+- Compatibility with lots of other plugins that use Google Analytics library of different versions.
+- "client_id is empty" is no more a problem, should be fixed.
+
+### Changed:
+- For SendGrid and Mailgun allow using custom defined attachments names if present. Fallback to file name.
+- Gmail: switch to a wider scope to prevent possible issues in certain circumstances.
+- Remove whitespaces start/end of keys, secrets etc.
+- Improved helpful description tests of various options.
+- Improved plugin autoloading functionality.
+
+## [1.2.5] - 2018-02-05
+### Fixed:
+- `Return path` can't be turned off.
+- `Authentication` sometimes can't be turned off.
+- `Auto TLS` sometimes can't be turned off.
+- BCC support for Gmail was broken.
+- Debug output improved to handle SELinux and grsecurity.
+- Strip slashes from plugin settings (useful for `From Name` option).
+- Change the way sanitization is done to prevent accidental removal of useful data.
+- Plugin activation will not overwrite settings back to defaults.
+- Properly set `Auto TLS` option on plugin activation.
+- Providers autoloading improved for certain Windows-based installs.
+- Use the proper path to load translations from plugin's `/languages` directory.
+
+### Changed:
+- Do not autoload on each page request plugin settings from WordPress options table.
+- Do not autoload Pepipost classes unless it's saved as active mailer in settings.
+
+## [1.2.4] - 2018-01-28
+### Fixed:
+- Improved escaping in debug reporting.
+
+## [1.2.3] - 2018-01-22
+### Fixed:
+- Gmail tokens were reset after clicking Save Settings.
+- Slight typo in Gmail success message.
+
+## [1.2.2] - 2017-12-27
+### Fixed:
+- Correctly handle Mailgun debug message for an incorrect api key.
+- Fatal error for Gmail and SMTP mailers with Nginx web-server (without Apache at all).
+
+### Changed:
+- Update X-Mailer emails header to show the real sender with a mailer and plugin version.
+
+## [1.2.1] - 2017-12-21
+### Fixed:
+- Failed SMTP connections generate fatal errors.
+
+## [1.2.0] - 2017-12-21
+### Fixed:
+- Decrease the factual minimum WordPress version from 3.9 to 3.6.
+
+### Changed:
+- Improve debug output for all mail providers.
+
+## [1.1.0] - 2017-12-18
+### Added:
+- New option "Auto TLS" for SMTP mailer. Default is enabled. Migration routine for all sites.
+
+### Changed:
+- Improve debug output - clear styles and context-aware content.
+- Better exceptions handling for Google authentication process.
+- Do not sanitize passwords, api keys etc - as they may contain special characters in certain order and sanitization will break those values.
+- Improve wording of some helpful texts inside plugin admin area.
+
+### Fixed:
+- Do not include certain files in dependency libraries that are not used by Google mailer. This should stop flagging plugin by Wordfence and VaultPress.
+- Constants usage is working now, to define the SMTP password, for example.
+- Notice for default mailer.
+
+## [1.0.2] - 2017-12-12
+### Fixed
+- PHPMailer using incorrect SMTPSecure value.
+
+## [1.0.1] - 2017-12-12
+### Fixed
+- Global POST processing conflict.
+
+## [1.0.0] - 2017-12-12
+### Added
+- Automatic migration tool to move options from older storage format to a new one.
+- Added Gmail & G Suite email provider integration - without your email and password.
+- Added SendGrid email provider integration - using the API key only.
+- Added Mailgun email provider integration - using the API key and configured domain only.
+- New compatibility mode - for PHP 5.2 old plugin will be loaded, for PHP 5.3 and higher - new version of admin area and new functionality.
+
+### Changed
+- The new look of the admin area.
+- SMTP password field now has "password" type.
+- SMTP password field does not display real password at all when using constants in `wp-config.php` to define it.
+- Escape properly all translations.
+- More helpful test email content (with a mailer name).
+
+## [0.11.2] - 2017-11-28
+### Added
+- Setting to hide announcement feed.
+
+### Changed
+- Announcement feed data.
+
+## [0.11.1] - 2017-10-30
+### Changed
+- Older PHP compatibility fix.
+
+## [0.11] - 2017-10-30
+### Added
+- Composer support.
+- PHPCS support.
+- Build system based on `gulp`.
+- Helper description to Return Path option.
+- Filter `wp_mail_smtp_admin_test_email_smtp_debug` to increase the debug message verbosity.
+- PHP 5.2 notice.
+- Announcement feed.
+
+### Changed
+- Localization fixes, proper locale name.
+- Code style improvements and optimizations for both HTML and PHP.
+- Inputs for emails now have a proper type `email`, instead of a generic `text`.
+- Turn off `$phpmailer->SMTPAutoTLS` when `No encryption` option is set to prevent error while sending emails.
+- Hide Pepipost for those who are not using it.
+- WP CLI support improved.
diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/css/admin-bar.min.css b/web/app/plugins/wp-mail-smtp-pro/assets/css/admin-bar.min.css
new file mode 100644
index 00000000..892001d7
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/assets/css/admin-bar.min.css
@@ -0,0 +1 @@
+#wpadminbar .wp-mail-smtp-admin-bar-menu-notification-counter,#wpadminbar .wp-mail-smtp-admin-bar-menu-error{display:inline-block !important;vertical-align:top !important;box-sizing:border-box !important;margin:7px 0 0 2px !important;padding:0 5px !important;min-width:18px !important;height:18px !important;border-radius:9px !important;background-color:#d63638 !important;color:#fff !important;font-size:11px !important;line-height:1.6 !important;text-align:center !important}#wpadminbar .wp-mail-smtp-admin-bar-menu-notification-counter span,#wpadminbar .wp-mail-smtp-admin-bar-menu-error span{line-height:1 !important;font-size:11px !important}body.wp-admin #adminmenu #toplevel_page_wp-mail-smtp .wp-menu-image.svg{background-position-x:10px;background-position-y:8px}@media screen and (max-width: 782px){body.wp-admin #adminmenu #toplevel_page_wp-mail-smtp .wp-menu-image.svg{background-position-x:9px}}
\ No newline at end of file
diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/css/admin-notifications.min.css b/web/app/plugins/wp-mail-smtp-pro/assets/css/admin-notifications.min.css
new file mode 100644
index 00000000..dc1225e2
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/assets/css/admin-notifications.min.css
@@ -0,0 +1 @@
+#wp-mail-smtp-notifications{position:relative;background:#fff 0 0 no-repeat padding-box;box-shadow:0 2px 4px rgba(0,0,0,.05);border-radius:6px;opacity:1;min-height:48px;margin:0 0 14px 0}#wp-mail-smtp-notifications *{box-sizing:border-box}#wp-mail-smtp-notifications .wp-mail-smtp-notifications-header{display:flex;align-items:center;padding:8px 16px;border-bottom:1px solid rgba(204,208,212,.5)}#wp-mail-smtp-notifications .wp-mail-smtp-notifications-header .wp-mail-smtp-notifications-bell{position:relative;top:2px;margin-right:10px}#wp-mail-smtp-notifications .wp-mail-smtp-notifications-header .wp-mail-smtp-notifications-title{font-style:normal;font-weight:500;font-size:14px;line-height:17px;color:#23282d}#wp-mail-smtp-notifications .wp-mail-smtp-notifications-body{position:relative}#wp-mail-smtp-notifications .wp-mail-smtp-notifications-messages{padding:16px 100px 16px 16px}#wp-mail-smtp-notifications .wp-mail-smtp-notifications-messages .wp-mail-smtp-notifications-message{display:none}#wp-mail-smtp-notifications .wp-mail-smtp-notifications-messages .wp-mail-smtp-notifications-message.current{display:block}#wp-mail-smtp-notifications .wp-mail-smtp-notifications-messages .wp-mail-smtp-notifications-title{font-weight:600;font-size:17px;line-height:17px;margin:0;color:#2c3337}#wp-mail-smtp-notifications .wp-mail-smtp-notifications-messages .wp-mail-smtp-notifications-content{font-weight:normal;font-size:14px;line-height:18px;margin:8px 0 41px 0;color:#50575e}#wp-mail-smtp-notifications .wp-mail-smtp-notifications-messages .wp-mail-smtp-notifications-content p{font-size:inherit;line-height:inherit;margin:0 0 5px}#wp-mail-smtp-notifications .wp-mail-smtp-notifications-messages .wp-mail-smtp-notifications-buttons{margin:-30px 80px 0 0}#wp-mail-smtp-notifications .wp-mail-smtp-notifications-messages .wp-mail-smtp-notifications-buttons a{margin:0 10px 0 0;padding:8px 10px;line-height:13px;font-size:13px;min-height:unset}#wp-mail-smtp-notifications .wp-mail-smtp-notifications-messages .wp-mail-smtp-notifications-buttons .button-secondary{border:1px solid #0071a1}#wp-mail-smtp-notifications .dismiss{position:absolute;top:15px;right:16px;width:16px;height:16px;color:#a0a5aa;font-size:16px;cursor:pointer;text-align:center;vertical-align:middle;line-height:16px}#wp-mail-smtp-notifications .dismiss:hover{color:#d63638}#wp-mail-smtp-notifications .dismiss i{width:100%;height:100%;font-size:inherit}#wp-mail-smtp-notifications .navigation{position:absolute;bottom:20px;right:16px;width:63px;height:30px}#wp-mail-smtp-notifications .navigation a{display:block;width:30px;height:30px;border:1px solid #7e8993;border-radius:3px;font-size:16px;line-height:1.625;text-align:center;cursor:pointer;background-color:#fff;color:#41454a}#wp-mail-smtp-notifications .navigation a:hover{background-color:#f1f1f1}#wp-mail-smtp-notifications .navigation a .dashicons{margin-top:8px;font-size:12px}#wp-mail-smtp-notifications .navigation .prev{float:left}#wp-mail-smtp-notifications .navigation .next{float:right}#wp-mail-smtp-notifications .navigation .disabled{border-color:#ddd;color:#a0a5aa;cursor:default}#wp-mail-smtp-notifications .navigation .disabled:hover{background-color:#fff}@media screen and (max-width: 768px){#wp-mail-smtp-notifications .wp-mail-smtp-notifications-messages{padding:15px 50px 20px 16px}#wp-mail-smtp-notifications .wp-mail-smtp-notifications-messages .wp-mail-smtp-notifications-title{margin:0 30px 0 0;line-height:22px}#wp-mail-smtp-notifications .wp-mail-smtp-notifications-messages .wp-mail-smtp-notifications-content{font-size:16px;line-height:22px}#wp-mail-smtp-notifications .wp-mail-smtp-notifications-messages .wp-mail-smtp-notifications-buttons{margin:-30px 80px 0 0}#wp-mail-smtp-notifications .wp-mail-smtp-notifications-messages .wp-mail-smtp-notifications-buttons a{margin:0;display:table}#wp-mail-smtp-notifications .wp-mail-smtp-notifications-messages .wp-mail-smtp-notifications-buttons .button-secondary{margin-top:6px}}
\ No newline at end of file
diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/css/admin-site-health.min.css b/web/app/plugins/wp-mail-smtp-pro/assets/css/admin-site-health.min.css
new file mode 100644
index 00000000..1b139784
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/assets/css/admin-site-health.min.css
@@ -0,0 +1 @@
+#health-check-accordion-block-wp_mail_smtp_email_domain_check>p:first-child{margin-top:0}#wp-mail-smtp-domain-check-details h2{color:#2c3338;font-weight:600;font-size:13px;margin:0 0 15px}#wp-mail-smtp-domain-check-details .wp-mail-smtp-domain-check-details-check-list-item{padding:10px 0 0;border-top:1px solid #e4e4e4;display:flex}#wp-mail-smtp-domain-check-details .wp-mail-smtp-domain-check-details-check-list-item:last-child{border-bottom:1px solid #e4e4e4}#wp-mail-smtp-domain-check-details .wp-mail-smtp-domain-check-details-check-list-item-icon{width:18px;height:16px;margin-right:10px}#wp-mail-smtp-domain-check-details .wp-mail-smtp-domain-check-details-check-list-item-content h3{text-transform:uppercase;font-weight:500;font-size:14px;line-height:17px;margin:0 0 4px 0;color:#2c3337}#wp-mail-smtp-domain-check-details .wp-mail-smtp-domain-check-details-check-list-item-content p{margin:0 0 10px 0;color:#50575e}
\ No newline at end of file
diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/css/dashboard-widget.min.css b/web/app/plugins/wp-mail-smtp-pro/assets/css/dashboard-widget.min.css
new file mode 100644
index 00000000..777b7e1f
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/assets/css/dashboard-widget.min.css
@@ -0,0 +1 @@
+#dashboard-widgets .meta-box-sortables .postbox:last-child .wp-mail-smtp-dash-widget-settings-menu{top:auto;bottom:50px}@media(min-width: 800px){#dashboard-widgets .meta-box-sortables .postbox:last-child .wp-mail-smtp-dash-widget-settings-menu{bottom:40px}}#wp_mail_smtp_reports_widget_pro .inside,#wp_mail_smtp_reports_widget_lite .inside{margin:0;padding:0}.wp-mail-smtp-dash-widget button:focus{outline:none}.wp-mail-smtp-dash-widget-block{position:relative;padding:0 12px;margin-top:12px}.wp-mail-smtp-dash-widget-block h3{display:inline-block;line-height:2}.wp-mail-smtp-dash-widget-block p{margin-top:0;margin-bottom:0}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-chart-block-container{position:relative}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-chart-block{height:291px;border-bottom:1px solid #eee;padding:0 12px 12px;padding-inline-end:14px}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-block-settings{display:flex;align-items:start;justify-content:space-between;margin-top:0;padding:13px;background-color:#fafafa}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-block-settings>div{display:flex}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-block-settings>div:first-child{flex-wrap:wrap}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-block-settings>div:first-child a{line-height:30px}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-block-settings select{line-height:2;min-height:30px;height:30px}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-block-settings button{min-height:30px;height:30px}@media(max-width: 980px){.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-block-settings{padding:13px 4px}}@media(max-width: 960px){.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-block-settings{padding:13px}}@media(max-width: 856px){.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-block-settings{padding:13px 2px}}@media(max-width: 799px){.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-block-settings{padding:13px}}@media(max-width: 436px){.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-block-settings{flex-direction:column;align-items:normal}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-block-settings select{width:100%;margin:0 0 10px}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-block-settings .wp-mail-smtp-dash-widget-select-timespan{width:calc(100% - 38px)}}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-block-settings a{vertical-align:middle}@media(max-width: 1150px){.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-block-settings a{display:none}}@media(min-width: 1500px)and (max-width: 1630px){.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-block-settings a{display:none}}@media(min-width: 1801px)and (max-width: 2080px){.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-block-settings a{display:none}}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-block-settings .wp-mail-smtp-dash-widget-select-email-type{margin-right:6px}@media(max-width: 830px){.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-block-settings .wp-mail-smtp-dash-widget-select-email-type{margin-right:2px}}@media(max-width: 436px){.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-block-settings .wp-mail-smtp-dash-widget-select-email-type{margin-right:0}}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-block-settings .wp-mail-smtp-dash-widget-settings-container{display:inline-block;position:relative;vertical-align:top;margin-left:6px}@media(max-width: 830px){.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-block-settings .wp-mail-smtp-dash-widget-settings-container{margin-left:2px}}@media(max-width: 799px){.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-block-settings .wp-mail-smtp-dash-widget-settings-container{margin-left:6px}}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-block-settings .wp-mail-smtp-dash-widget-settings-button{background:#fafafa;color:#787c82;border-color:#787c82;padding:1px 6px 0;margin-bottom:0;line-height:1;display:flex;align-items:center;justify-content:center}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-block-settings .wp-mail-smtp-dash-widget-settings-button:hover,.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-block-settings .wp-mail-smtp-dash-widget-settings-button:active,.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-block-settings .wp-mail-smtp-dash-widget-settings-button:focus{color:#2271b1;border-color:#2271b1}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-block-settings .wp-mail-smtp-dash-widget-settings-button:hover svg path,.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-block-settings .wp-mail-smtp-dash-widget-settings-button:active svg path,.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-block-settings .wp-mail-smtp-dash-widget-settings-button:focus svg path{fill:#2271b1}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-block-settings .wp-mail-smtp-dash-widget-settings-button.open svg path{transform:rotate(90deg)}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-block-settings .wp-mail-smtp-dash-widget-settings-button svg{display:inline-block;width:19px;height:19px}@media(max-width: 782px){.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-block-settings .wp-mail-smtp-dash-widget-settings-button svg{padding:5px}}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-block-settings .wp-mail-smtp-dash-widget-settings-button svg path{fill:#787c82;transition:transform .2s;transform-origin:center center}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-block-settings .wp-mail-smtp-dash-widget-settings-menu{display:none;position:absolute;background-color:#fff;width:160px;border-radius:3px;box-shadow:0px 5px 15px rgba(0,0,0,.15);padding:10px;z-index:9999;right:0;top:50px}@media(min-width: 800px){.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-block-settings .wp-mail-smtp-dash-widget-settings-menu{top:40px}}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-block-settings .wp-mail-smtp-dash-widget-settings-menu--style,.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-block-settings .wp-mail-smtp-dash-widget-settings-menu--color{border-bottom:1px solid #eee}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-block-settings .wp-mail-smtp-dash-widget-settings-menu--style h4,.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-block-settings .wp-mail-smtp-dash-widget-settings-menu--color h4{text-transform:uppercase !important;color:#3c434a !important;font-size:11px !important;font-weight:500 !important;line-height:13px !important;margin:0 0 10px !important}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-block-settings .wp-mail-smtp-dash-widget-settings-menu--style .wp-mail-smtp-dash-widget-settings-menu-item,.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-block-settings .wp-mail-smtp-dash-widget-settings-menu--color .wp-mail-smtp-dash-widget-settings-menu-item{display:flex;align-items:center;color:#3c434a;font-size:13px;line-height:16px;margin:0 0 13px}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-block-settings .wp-mail-smtp-dash-widget-settings-menu--style .wp-mail-smtp-dash-widget-settings-menu-item:last-child,.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-block-settings .wp-mail-smtp-dash-widget-settings-menu--color .wp-mail-smtp-dash-widget-settings-menu-item:last-child{margin:0 0 10px}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-block-settings .wp-mail-smtp-dash-widget-settings-menu--style .wp-mail-smtp-dash-widget-settings-menu-item input[type=radio],.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-block-settings .wp-mail-smtp-dash-widget-settings-menu--color .wp-mail-smtp-dash-widget-settings-menu-item input[type=radio]{margin-right:5px}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-block-settings .wp-mail-smtp-dash-widget-settings-menu--style .wp-mail-smtp-dash-widget-settings-menu-item label,.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-block-settings .wp-mail-smtp-dash-widget-settings-menu--color .wp-mail-smtp-dash-widget-settings-menu-item label{vertical-align:top;margin-top:-3px}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-block-settings .wp-mail-smtp-dash-widget-settings-menu--color{margin-top:10px}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-block-settings .wp-mail-smtp-dash-widget-settings-menu-save{margin-top:10px}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-email-stats-block{margin-top:0}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-email-stats-block table{width:calc(100% + 24px);margin:0 -12px}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-email-stats-block table .wp-mail-smtp-dash-widget-email-stats-table-cell{padding:10px 12px;border-right:1px solid #eee;border-top:1px solid #eee;color:#3c434a;font-size:14px;width:50%;line-height:17px}@media(max-width: 436px){.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-email-stats-block table .wp-mail-smtp-dash-widget-email-stats-table-cell{display:block;width:100%}}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-email-stats-block table .wp-mail-smtp-dash-widget-email-stats-table-cell--2:not(:first-child){border-right:none}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-email-stats-block table .wp-mail-smtp-dash-widget-email-stats-table-cell--3{width:33.33%}@media(max-width: 436px){.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-email-stats-block table .wp-mail-smtp-dash-widget-email-stats-table-cell--3{width:100%}}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-email-stats-block table .wp-mail-smtp-dash-widget-email-stats-table-cell--3:last-child{border-right:none}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-email-stats-block table .wp-mail-smtp-dash-widget-email-stats-table-cell-container{display:flex;align-items:center}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-email-stats-block table .wp-mail-smtp-dash-widget-email-stats-table-cell-container img{width:16px;margin-right:10px}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-email-stats-block .wp-mail-smtp-dash-widget-overlay{background-position-y:50%}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-recommended-plugin-block{display:flex;padding:12px 7px 12px 12px;border-top:1px solid #eee;color:#787c82;font-size:13px;line-height:16px}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-recommended-plugin-block b{color:#3c434a}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-recommended-plugin-block .sep{display:inline-block;margin:0 3px;color:#ddd}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-recommended-plugin-block .sep-vertical{color:#ddd}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-recommended-plugin-block p{margin:0}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-recommended-plugin-block .wp-mail-smtp-dash-widget-recommended-plugin{display:inline-block;width:calc(100% - 24px)}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-recommended-plugin-block .wp-mail-smtp-dash-widget-recommended-plugin span{display:inline-block}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-recommended-plugin-block .wp-mail-smtp-dash-widget-dismiss-recommended-plugin-block{display:inline-block;border:0;color:gray;opacity:.3;vertical-align:baseline;cursor:pointer;background:none;padding:0 2px}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-recommended-plugin-block .wp-mail-smtp-dash-widget-dismiss-recommended-plugin-block:hover{opacity:1}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-recommended-plugin-block .wp-mail-smtp-dash-widget-dismiss-recommended-plugin-block .dashicons{font-size:16px;height:16px;width:16px}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-email-alerts-education{align-items:center;border-left:4px solid #d63638;border-top:1px solid #eee;display:flex;gap:10px;margin-top:0;padding:10px}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-email-alerts-education-error-icon{display:flex;height:44px;width:40px}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-email-alerts-education-error-icon img{height:35px;width:35px;margin:auto}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-email-alerts-education-content{padding-right:14px}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-email-alerts-education-content p{color:#1d2327;font-size:14px;line-height:20px}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-email-alerts-education-content p a{color:#2271b1;font-weight:500}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-email-alerts-education-content p strong{font-weight:500}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-email-alerts-education .wp-mail-smtp-dash-widget-dismiss-email-alert-block{background:none;border:0;cursor:pointer;position:absolute;right:6px;top:10px}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-email-alerts-education .wp-mail-smtp-dash-widget-dismiss-email-alert-block .dashicons{color:#a0a5aa;font-size:16px;height:17px;width:16px}.wp-mail-smtp-dash-widget .wp-mail-smtp-error{text-align:center}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-chart-upgrade,.wp-mail-smtp-dash-widget .wp-mail-smtp-error-no-data-chart{position:absolute;left:0;top:0;height:100%;width:100%;overflow:hidden;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;background:hsla(0,0%,100%,0);background:-moz-radial-gradient(center, ellipse cover, rgba(255, 255, 255, 0) 0%, rgb(255, 255, 255) 100%);background:-webkit-gradient(radial, center center, 0px, center center, 100%, color-stop(0%, rgba(255, 255, 255, 0)), color-stop(100%, rgb(255, 255, 255)));background:-webkit-radial-gradient(center, ellipse cover, rgba(255, 255, 255, 0) 0%, rgb(255, 255, 255) 100%);background:-o-radial-gradient(center, ellipse cover, rgba(255, 255, 255, 0) 0%, rgb(255, 255, 255) 100%);background:-ms-radial-gradient(center, ellipse cover, rgba(255, 255, 255, 0) 0%, rgb(255, 255, 255) 100%);background:radial-gradient(ellipse at center, rgba(255, 255, 255, 0) 0%, rgb(255, 255, 255) 100%)}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-chart-upgrade .wp-mail-smtp-dash-widget-modal,.wp-mail-smtp-dash-widget .wp-mail-smtp-error-no-data-chart .wp-mail-smtp-dash-widget-modal{position:absolute;top:calc(50% - 2em);left:50%;transform:translate(-50%, -50%);padding:1.5em 2em 2em 2em;box-shadow:0 0 25px 10px rgba(0,0,0,.08);border-radius:3px;background-color:#fff;text-align:center;width:20em}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-chart-upgrade .wp-mail-smtp-dash-widget-modal h2,.wp-mail-smtp-dash-widget .wp-mail-smtp-error-no-data-chart .wp-mail-smtp-dash-widget-modal h2{padding:0;margin-bottom:5px;font-size:18px}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-chart-upgrade .wp-mail-smtp-dash-widget-modal p,.wp-mail-smtp-dash-widget .wp-mail-smtp-error-no-data-chart .wp-mail-smtp-dash-widget-modal p{line-height:1.3}.wp-mail-smtp-dash-widget .wp-mail-smtp-error-no-data-email-stats{padding-top:16px;padding-bottom:16px}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-block-logs-disabled{padding:21px 25px 30px;text-align:center}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-block-logs-disabled .wp-mail-smtp-dash-widget-block-logo{width:75px}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-block-logs-disabled h2{font-size:16px;line-height:20px;font-weight:500;padding:10px 0 0}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-block-logs-disabled p{font-size:14px;line-height:20px;margin:7px 0 19px}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-block-logs-disabled a.button:first-of-type:not(:only-of-type){margin-right:10px}.wp-mail-smtp-dash-widget .wp-mail-smtp-dash-widget-overlay{width:100%;position:absolute;top:0;right:0;bottom:0;left:0;background:url(../images/wp-spinner.gif) no-repeat center calc(50% - 12px);background-color:#fff;z-index:10}.wp-mail-smtp-dash-widget i.wp-mail-smtp-dash-widget-loader{background-image:url(../images/wp-spinner.gif);background-repeat:no-repeat;background-position:center;background-size:cover}.wp-mail-smtp-dash-widget .hidden{display:none !important}#wp_mail_smtp_reports_widget_lite .wp-mail-smtp-dash-widget-chart-block-container #wp-mail-smtp-dash-widget-chart{opacity:.35}#wp_mail_smtp_reports_widget_lite .wp-mail-smtp-dash-widget-chart-upgrade{background:none;display:flex;align-items:center;justify-content:center}#wp_mail_smtp_reports_widget_lite .wp-mail-smtp-dash-widget-chart-upgrade h2{font-size:20px;line-height:20px;font-weight:500;color:#3c434a;margin-bottom:15px}#wp_mail_smtp_reports_widget_lite .wp-mail-smtp-dash-widget-chart-upgrade p{font-size:14px;line-height:18px;color:#787c82;margin-bottom:15px}#wp_mail_smtp_reports_widget_lite .wp-mail-smtp-dash-widget-chart-upgrade p:last-child{margin-bottom:0}#wp_mail_smtp_reports_widget_lite .wp-mail-smtp-dash-widget-chart-upgrade a.button.button-hero{font-weight:500;font-size:14px;line-height:20px;padding:9px 15px;min-height:40px}#wp_mail_smtp_reports_widget_lite .wp-mail-smtp-dash-widget-chart-upgrade .wp-mail-smtp-dash-widget-modal{position:relative;top:inherit;left:inherit;transform:none;width:24em;padding:2.3em;box-shadow:0 5px 25px rgba(0,0,0,.15);border-radius:6px}#wp_mail_smtp_reports_widget_lite .wp-mail-smtp-dash-widget-dismiss-chart-upgrade{position:absolute;width:16px;height:17px;right:5px;top:5px;color:#a0a5aa;text-decoration:none}#wp_mail_smtp_reports_widget_lite .wp-mail-smtp-dash-widget-dismiss-chart-upgrade .dashicons{width:16px;height:17px;font-style:normal;font-weight:normal;font-size:16px;line-height:17px}#wp_mail_smtp_reports_widget_lite .wp-mail-smtp-dash-widget-settings-menu input[type=radio]:checked::before{background-color:#a0a5aa}#wp_mail_smtp_reports_widget_lite .wp-mail-smtp-dash-widget-settings-menu *{cursor:not-allowed}#wp_mail_smtp_reports_widget_lite .wp-mail-smtp-dash-widget-summary-report-email-block{margin-top:0;padding-top:10px;padding-bottom:10px;border-top:1px solid #eee;font-size:13px;line-height:16px}#wp_mail_smtp_reports_widget_lite .wp-mail-smtp-dash-widget-summary-report-email-block>div{padding:10px;border-radius:4px;background:#f0f6fc}#wp_mail_smtp_reports_widget_lite .wp-mail-smtp-dash-widget-summary-report-email-block-setting{display:flex;align-items:center}#wp_mail_smtp_reports_widget_lite .wp-mail-smtp-dash-widget-summary-report-email-block-setting label{display:flex;margin:0 10px 0 0;color:#3c434a}#wp_mail_smtp_reports_widget_lite .wp-mail-smtp-dash-widget-summary-report-email-block-setting label input,#wp_mail_smtp_reports_widget_lite .wp-mail-smtp-dash-widget-summary-report-email-block-setting label i{margin:0 10px 0 0}#wp_mail_smtp_reports_widget_lite .wp-mail-smtp-dash-widget-summary-report-email-block-setting label i{display:none;width:16px;height:16px}#wp_mail_smtp_reports_widget_lite .wp-mail-smtp-dash-widget-summary-report-email-block-setting label b{font-weight:500}#wp_mail_smtp_reports_widget_lite .wp-mail-smtp-dash-widget-summary-report-email-block-setting a{white-space:nowrap;margin-right:10px}#wp_mail_smtp_reports_widget_lite .wp-mail-smtp-dash-widget-summary-report-email-block-setting .dashicons-dismiss{margin-left:auto;width:16px;height:16px;font-size:16px;color:#a0a5aa;cursor:pointer}#wp_mail_smtp_reports_widget_lite .wp-mail-smtp-dash-widget-summary-report-email-block-setting .dashicons-dismiss:hover{color:#2271b1}#wp_mail_smtp_reports_widget_lite .wp-mail-smtp-dash-widget-summary-report-email-block-applied{display:flex}#wp_mail_smtp_reports_widget_lite .wp-mail-smtp-dash-widget-summary-report-email-block-applied .wp-mail-smtp-dashicons-yes-alt-green{margin-right:10px;width:16px;height:16px;background-image:url("../images/dash-widget/yes-green.svg")}#wp_mail_smtp_reports_widget_lite .wp-mail-smtp-dash-widget-upgrade-footer{padding:12px;margin-top:0;border-top:1px solid #eee}#wp_mail_smtp_reports_widget_lite .wp-mail-smtp-dash-widget-upgrade-footer p{font-size:13px;line-height:16px}#wp_mail_smtp_reports_widget_lite .wp-mail-smtp-dash-widget-upgrade-footer--hide{display:none}
\ No newline at end of file
diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/css/emails/summary-report-email.css b/web/app/plugins/wp-mail-smtp-pro/assets/css/emails/summary-report-email.css
new file mode 100644
index 00000000..c3a8a235
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/assets/css/emails/summary-report-email.css
@@ -0,0 +1,297 @@
+/* General styles. */
+
+#outlook a {
+ padding: 0;
+}
+
+.ExternalClass {
+ width: 100%;
+}
+
+.ExternalClass, .ExternalClass p, .ExternalClass span, .ExternalClass font, .ExternalClass td, .ExternalClass div {
+ line-height: 100%;
+}
+
+#backgroundTable {
+ margin: 0;
+ padding: 0;
+ width: 100% !important;
+ line-height: 100% !important;
+}
+
+/* Prevent blue links in subject. */
+.stats-subject-heading a {
+ color: inherit !important;
+ text-decoration: none !important;
+}
+
+/* Mobile styles. */
+
+@media only screen and (max-width: 599px) {
+ table.body .container {
+ width: 94% !important;
+ max-width: 600px !important;
+ }
+
+ .header img {
+ width: 240px !important;
+ height: auto !important;
+ }
+
+ .content {
+ padding: 30px !important;
+ }
+
+ .main-heading {
+ font-size: 16px !important;
+ line-height: 20px !important;
+ }
+
+ .main-description {
+ margin-bottom: 20px !important;
+ }
+
+ .stats-totals-wrapper.two .stats-totals-item-wrapper {
+ width: 50% !important;
+ }
+
+ .stats-totals-wrapper.three .stats-totals-item-wrapper {
+ width: 33.3% !important;
+ }
+
+ .stats-totals-wrapper.four .stats-totals-item-wrapper {
+ width: 25% !important;
+ }
+
+ .stats-totals-wrapper {
+ border-collapse: separate !important;
+ border: 1px solid #DDDDDD !important;
+ border-radius: 4px !important;
+ }
+
+ .stats-total-item {
+ width: 100% !important;
+ min-width: 100% !important;
+ }
+
+ .stats-total-item-inner {
+ border: none !important;
+ }
+
+ .stats-total-item-icon-wrapper {
+ height: 24px !important;
+ }
+
+ .stats-total-item-icon {
+ width: 24px !important;
+ height: 24px !important;
+ }
+
+ .stats-totals-wrapper.three .stats-total-item-title,
+ .stats-totals-wrapper.four .stats-total-item-title {
+ display: none !important;
+ }
+
+ .stats-total-item-value {
+ font-size: 18px !important;
+ line-height: 22px !important;
+ }
+
+ .stats-total-item-percent {
+ font-size: 12px !important;
+ line-height: 14px !important;
+ }
+
+ .stats-total-item-percent img {
+ width: 9px !important;
+ height: 9px !important;
+ }
+
+ .stats-heading th {
+ display: block !important;
+ width: 100% !important;
+ min-width: 100% !important;
+ padding-right: 0 !important;
+ padding-left: 0 !important;
+ text-align: center !important;
+ }
+
+ .stats-heading .first-col {
+ padding-top: 20px !important;
+ padding-bottom: 5px !important;
+ }
+
+ .stats-heading .second-col {
+ padding-top: 0 !important;
+ padding-bottom: 20px !important;
+ }
+
+ .stats-heading h2 {
+ text-align: center !important;
+ font-size: 15px !important;
+ line-height: 18px !important;
+ }
+
+ .stats-heading a {
+ font-size: 13px !important;
+ line-height: 16px !important;
+ }
+
+ .stats-subject-heading {
+ text-align: center !important;
+ }
+
+ .stats-subject-row {
+ text-align: center !important;
+ }
+
+ .stats-subject-column.total,
+ .stats-subject-column.sent,
+ .stats-subject-column.confirmed,
+ .stats-subject-column.unconfirmed,
+ .stats-subject-column.unsent {
+ max-width: 64px !important;
+ }
+
+ .stats-subject-column.opened,
+ .stats-subject-column.clicked {
+ max-width: 111px !important;
+ }
+
+ .spacer-40 {
+ line-height: 20px !important;
+ height: 20px !important;
+ }
+
+ .upgrade-heading {
+ font-size: 18px !important;
+ line-height: 22px !important;
+ }
+
+ .upgrade-text {
+ font-size: 14px !important;
+ line-height: 20px !important;
+ }
+}
+
+@media only screen and (max-width: 360px) {
+ .content {
+ padding: 20px !important;
+ }
+
+ .stats-subject-column.total,
+ .stats-subject-column.sent,
+ .stats-subject-column.confirmed,
+ .stats-subject-column.unconfirmed,
+ .stats-subject-column.unsent {
+ max-width: 61px !important;
+ }
+}
+
+/* Dark Mode. */
+
+@media (prefers-color-scheme: dark) {
+ .dark-body-bg {
+ background: #1C1E20 !important;
+ }
+
+ .dark-content-bg {
+ background: #23282C !important;
+ }
+
+ .dark-bg {
+ background: #202326 !important;
+ }
+
+ .dark-white-color {
+ color: #ffffff !important;
+ }
+
+ .dark-img {
+ display: block !important;
+ width: auto !important;
+ overflow: visible !important;
+ float: none !important;
+ max-height: inherit !important;
+ max-width: inherit !important;
+ line-height: auto !important;
+ margin-top: 0px !important;
+ visibility: inherit !important;
+ }
+
+ .light-img {
+ display: none !important;
+ }
+
+ .stats-total-item-inner,
+ .stats-heading {
+ border-color: #395360 !important;
+ }
+
+ .stats-subject-column-value,
+ .stats-total-item-percent,
+ .footer {
+ color: #8C8F94 !important;
+ }
+
+ .stats-subject-column-value span {
+ color: #4A5057 !important;
+ }
+
+ .upgrade-text {
+ color: #8C8F94 !important;
+ }
+}
+
+/* Dark Mode Outlook. */
+
+[data-ogsc] .dark-body-bg {
+ background: #1C1E20 !important;
+}
+
+[data-ogsc] .dark-content-bg {
+ background: #23282C !important;
+}
+
+[data-ogsc] .dark-bg {
+ background: #202326 !important;
+}
+
+[data-ogsc] .dark-white-color {
+ color: #ffffff !important;
+}
+
+[data-ogsc] .dark-img {
+ display: block !important;
+ width: auto !important;
+ overflow: visible !important;
+ float: none !important;
+ max-height: inherit !important;
+ max-width: inherit !important;
+ line-height: auto !important;
+ margin-top: 0px !important;
+ visibility: inherit !important;
+}
+
+[data-ogsc] .light-img {
+ display: none !important;
+}
+
+[data-ogsc] .stats-total-item-inner,
+[data-ogsc] .stats-heading {
+ border-color: #395360 !important;
+}
+
+[data-ogsc] .stats-subject-column-value,
+[data-ogsc] .stats-total-item-percent,
+[data-ogsc] .footer {
+ color: #8C8F94 !important;
+}
+
+[data-ogsc] .stats-subject-column-value span {
+ color: #4A5057 !important;
+}
+
+[data-ogsc] .upgrade-text {
+ color: #8C8F94 !important;
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/css/smtp-about.min.css b/web/app/plugins/wp-mail-smtp-pro/assets/css/smtp-about.min.css
new file mode 100644
index 00000000..b2326f5f
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/assets/css/smtp-about.min.css
@@ -0,0 +1 @@
+.wp-mail-smtp-page-about{padding:0 !important}.wp-mail-smtp-page-about *,.wp-mail-smtp-page-about *::before,.wp-mail-smtp-page-about *::after{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.wp-mail-smtp-page-about.wp-mail-smtp-tab-about-about .wp-mail-smtp-admin-about-plugins{padding:0 10px}.wp-mail-smtp-page-about.wp-mail-smtp-tab-about-about .wp-mail-smtp-admin-about-plugins .plugins-container{margin-left:-20px;margin-right:-20px}.wp-mail-smtp-page-about.wp-mail-smtp-tab-about-about .wp-mail-smtp-admin-about-plugins .plugins-container .plugin-container{padding:0 10px;float:left;width:33.333333%;margin-bottom:20px}@media(max-width: 1249px){.wp-mail-smtp-page-about.wp-mail-smtp-tab-about-about .wp-mail-smtp-admin-about-plugins .plugins-container .plugin-container{width:50%}}@media(max-width: 781px){.wp-mail-smtp-page-about.wp-mail-smtp-tab-about-about .wp-mail-smtp-admin-about-plugins .plugins-container .plugin-container{width:100%;margin-bottom:20px}}.wp-mail-smtp-page-about.wp-mail-smtp-tab-about-about .wp-mail-smtp-admin-about-plugins .plugins-container h5{font-size:17px;font-weight:600}.wp-mail-smtp-page-about.wp-mail-smtp-tab-about-about .wp-mail-smtp-admin-about-plugins .plugins-container .plugin-item{background-color:#fff;border:1px solid #ddd;border-radius:3px;margin:0}@media(max-width: 781px){.wp-mail-smtp-page-about.wp-mail-smtp-tab-about-about .wp-mail-smtp-admin-about-plugins .plugins-container .plugin-item{margin:0 0}}.wp-mail-smtp-page-about.wp-mail-smtp-tab-about-about .wp-mail-smtp-admin-about-plugins .plugins-container .plugin-item img{border:1px solid #eee;float:left;max-width:75px;padding:10px}.wp-mail-smtp-page-about.wp-mail-smtp-tab-about-about .wp-mail-smtp-admin-about-plugins .plugins-container .plugin-item img[src*=-mi]{padding:13px}.wp-mail-smtp-page-about.wp-mail-smtp-tab-about-about .wp-mail-smtp-admin-about-plugins .plugins-container .plugin-item h5{margin:0 0 10px 100px;font-size:16px}.wp-mail-smtp-page-about.wp-mail-smtp-tab-about-about .wp-mail-smtp-admin-about-plugins .plugins-container .plugin-item p{margin:0 0 0 100px}.wp-mail-smtp-page-about.wp-mail-smtp-tab-about-about .wp-mail-smtp-admin-about-plugins .plugins-container .plugin-item .details{padding:20px}.wp-mail-smtp-page-about.wp-mail-smtp-tab-about-about .wp-mail-smtp-admin-about-plugins .plugins-container .plugin-item .actions{background-color:#f7f7f7;border-top:1px solid #ddd;padding:20px;position:relative}.wp-mail-smtp-page-about.wp-mail-smtp-tab-about-about .wp-mail-smtp-admin-about-plugins .plugins-container .plugin-item .actions .msg{background-color:#f7f7f7;position:absolute;text-align:center;font-weight:600;top:0;left:0;height:100%;width:100%;z-index:99;padding:20px;display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;-webkit-align-items:center;-ms-align-items:center;align-items:center;justify-content:center}.wp-mail-smtp-page-about.wp-mail-smtp-tab-about-about .wp-mail-smtp-admin-about-plugins .plugins-container .plugin-item .actions .msg.success{color:#00a32a}.wp-mail-smtp-page-about.wp-mail-smtp-tab-about-about .wp-mail-smtp-admin-about-plugins .plugins-container .plugin-item .actions .msg.error{color:#d63638}.wp-mail-smtp-page-about.wp-mail-smtp-tab-about-about .wp-mail-smtp-admin-about-plugins .plugins-container .plugin-item .status{float:left;padding-top:8px}.wp-mail-smtp-page-about.wp-mail-smtp-tab-about-about .wp-mail-smtp-admin-about-plugins .plugins-container .plugin-item .status .status-inactive{color:#d63638}.wp-mail-smtp-page-about.wp-mail-smtp-tab-about-about .wp-mail-smtp-admin-about-plugins .plugins-container .plugin-item .status .status-download{color:#666}.wp-mail-smtp-page-about.wp-mail-smtp-tab-about-about .wp-mail-smtp-admin-about-plugins .plugins-container .plugin-item .status .status-active{color:#00a32a}.wp-mail-smtp-page-about.wp-mail-smtp-tab-about-about .wp-mail-smtp-admin-about-plugins .plugins-container .plugin-item .action-button{float:right}.wp-mail-smtp-page-about.wp-mail-smtp-tab-about-about .wp-mail-smtp-admin-about-plugins .plugins-container .plugin-item .action-button button{cursor:pointer;width:140px;text-align:center;display:inline-block;text-decoration:none;font-size:13px;line-height:26px;height:28px;margin:0;padding:0 10px 1px;-webkit-appearance:none;border-radius:3px;border:1px solid #ccc;background:#f7f7f7;box-shadow:0 1px 0 #ccc;font-weight:normal}.wp-mail-smtp-page-about.wp-mail-smtp-tab-about-about .wp-mail-smtp-admin-about-plugins .plugins-container .plugin-item .action-button button:hover,.wp-mail-smtp-page-about.wp-mail-smtp-tab-about-about .wp-mail-smtp-admin-about-plugins .plugins-container .plugin-item .action-button button:focus,.wp-mail-smtp-page-about.wp-mail-smtp-tab-about-about .wp-mail-smtp-admin-about-plugins .plugins-container .plugin-item .action-button button.loading{background:#fafafa;border-color:#999;color:#23282d}.wp-mail-smtp-page-about.wp-mail-smtp-tab-about-about .wp-mail-smtp-admin-about-plugins .plugins-container .plugin-item .action-button button .fa{margin-right:8px}.wp-mail-smtp-page-about.wp-mail-smtp-tab-about-about .wp-mail-smtp-admin-about-plugins .plugins-container .plugin-item .action-button button .fa.fa-spinner{margin-right:0}.wp-mail-smtp-page-about.wp-mail-smtp-tab-about-about .wp-mail-smtp-admin-about-plugins .plugins-container .plugin-item .action-button button.button-primary{background:#0085ba;border-color:#0073aa #006799 #006799;box-shadow:0 1px 0 #006799;color:#fff;text-shadow:0 -1px 1px #006799,1px 0 1px #006799,0 1px 1px #006799,-1px 0 1px #006799}.wp-mail-smtp-page-about.wp-mail-smtp-tab-about-about .wp-mail-smtp-admin-about-plugins .plugins-container .plugin-item .action-button button.button-primary:hover,.wp-mail-smtp-page-about.wp-mail-smtp-tab-about-about .wp-mail-smtp-admin-about-plugins .plugins-container .plugin-item .action-button button.button-primary:focus{background:#008ec2;border-color:#006799;color:#fff}.wp-mail-smtp-page-about.wp-mail-smtp-tab-about-about .wp-mail-smtp-admin-about-plugins .plugins-container .plugin-item .action-button button.button-primary .fa-spinner{color:#fff}.wp-mail-smtp-page-about.wp-mail-smtp-tab-about-about .wp-mail-smtp-admin-about-plugins .plugins-container .plugin-item .action-button button.button-secondary{color:#50575e;border-color:#ccc;background:#f7f7f7;box-shadow:0 1px 0 #ccc}.wp-mail-smtp-page-about.wp-mail-smtp-tab-about-about .wp-mail-smtp-admin-about-plugins .plugins-container .plugin-item .action-button button.button-secondary:hover,.wp-mail-smtp-page-about.wp-mail-smtp-tab-about-about .wp-mail-smtp-admin-about-plugins .plugins-container .plugin-item .action-button button.button-secondary:focus{background:#fafafa;border-color:#999;color:#23282d}.wp-mail-smtp-page-about.wp-mail-smtp-tab-about-about .wp-mail-smtp-admin-about-plugins .plugins-container .plugin-item .action-button button.status-active .fa{color:#00a32a}.wp-mail-smtp-page-about.wp-mail-smtp-tab-about-about .wp-mail-smtp-admin-about-plugins .plugins-container .plugin-item .action-button button.status-inactive .fa{color:#d63638}.wp-mail-smtp-page-about.wp-mail-smtp-tab-about-about .wp-mail-smtp-admin-about-plugins .plugins-container .plugin-item .action-button button.status-download .fa{color:#999}.wp-mail-smtp-page-about.wp-mail-smtp-tab-about-about .wp-mail-smtp-admin-about-plugins .plugins-container .plugin-item .action-button button.loading .fa{color:#666}.wp-mail-smtp-page-about.wp-mail-smtp-tab-about-about .wp-mail-smtp-admin-about-plugins .plugins-container .plugin-item .action-button button.disabled{cursor:default}.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section{margin:0 0 20px;padding:30px;background:#fff;border:1px solid #e5e6e6;line-height:2}.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section h1,.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section h2,.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section h3,.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section h4,.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section h5{margin-top:0;padding-top:0;line-height:1.6}.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section h2{font-size:24px}.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section h3{font-size:18px;margin-bottom:30px;color:#23282c}.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section ul,.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section p{font-size:16px}.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section p{margin-bottom:20px}.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section p.bigger{font-size:18px}.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section p.smaller{font-size:14px}.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section p:last-child{margin-bottom:0}.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section hr{margin:30px 0}.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section figure{margin:0}.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section figure img{width:100%}.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section figure figcaption{font-size:14px;color:#646970;margin-top:5px;text-align:center;line-height:initial}.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section .wp-mail-smtp-admin-column-40{padding-left:15px}@media(max-width: 781px){.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section .wp-mail-smtp-admin-column-40{width:100%;padding-left:0;padding-top:20px}}.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section .wp-mail-smtp-admin-column-60{padding-right:15px}@media(max-width: 781px){.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section .wp-mail-smtp-admin-column-60{width:100%;padding-right:0}}.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section ul.list-plain{margin-top:0;margin-bottom:0}.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section ul.list-plain li{margin-bottom:0}.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section ul.list-features li .fa{color:#00a32a;margin:0 8px 0 0}.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section .fa-star{color:gold}.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section .no-margin{margin:0 !important}.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section .no-padding{padding:0 !important}.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section .centered{text-align:center !important}.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section-hero{padding:0}.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section-hero .wp-mail-smtp-admin-about-section-hero-main,.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section-hero .wp-mail-smtp-admin-about-section-hero-extra{padding:30px}.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section-hero .wp-mail-smtp-admin-about-section-hero-extra table{width:100%}@media(max-width: 781px){.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section-hero .wp-mail-smtp-admin-about-section-hero-extra .wp-mail-smtp-admin-column-50{float:none;width:100%}}.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section-hero .wp-mail-smtp-admin-about-section-hero-main{background-color:#fafafa;border-bottom:1px solid #e5e6e6}.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section-hero .wp-mail-smtp-admin-about-section-hero-main.no-border{border-bottom:0}.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section-hero .wp-mail-smtp-admin-about-section-hero-main p{color:#666}.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section-hero h3.call-to-action{margin-bottom:-10px}.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section-hero span.price-off{color:#00a32a}.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section-squashed{margin-bottom:0}.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section-squashed:not(:last-of-type){border-bottom:0}.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section-post h2{margin-bottom:-10px}.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section-post h3{margin-bottom:15px}.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section-post p:last-of-type{margin-bottom:30px}.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section-post .wp-mail-smtp-admin-column-20{padding-right:20px;width:auto}.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section-post .wp-mail-smtp-admin-column-20 img{width:270px}@media(max-width: 781px){.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section-post .wp-mail-smtp-admin-column-20{width:20%}.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section-post .wp-mail-smtp-admin-column-20 img{width:auto;max-width:100%}}.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section-post .wp-mail-smtp-admin-column-80{padding-left:20px;width:calc(100% - 20px - 270px)}@media(max-width: 781px){.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section-post .wp-mail-smtp-admin-column-80{width:80%}}.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section-post .wp-mail-smtp-admin-about-section-post-link{padding:10px 15px;background-color:#df7739;color:#fff;border-radius:3px;text-decoration:none;margin-top:15px;font-size:14px}.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section-post .wp-mail-smtp-admin-about-section-post-link:hover{background-color:#b85a1b}.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section-post .wp-mail-smtp-admin-about-section-post-link .fa{color:#edba9e;vertical-align:middle;margin-left:8px}.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section-table table{border-collapse:collapse}.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section-table table tr td{border-bottom:1px solid #e5e6e6;border-right:1px solid #e5e6e6;padding:30px}.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section-table table tr td:last-of-type{border-right:0}.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section-table table tr:last-child td{border-bottom:none}.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section-table table p{background-repeat:no-repeat;background-size:15px auto;background-position:0 6px;margin:0}.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section-table table p.features-full{padding-left:30px;background-image:url(../images/about/icon-full.svg)}.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section-table table p.features-none{padding-left:30px;background-image:url(../images/about/icon-none.svg)}.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section-table table p.features-partial{padding-left:30px;background-position:-3px 0;background-size:23px auto;background-image:url(../images/about/icon-partial.svg)}.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section-table .wp-mail-smtp-admin-about-section-hero-main{padding:0}.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section-table .wp-mail-smtp-admin-about-section-hero-main h3{padding:30px 30px 30px 60px}.wp-mail-smtp-page-about .wp-mail-smtp-admin-about-section-table .wp-mail-smtp-admin-about-section-hero-main .wp-mail-smtp-admin-column-33:first-child h3{padding:30px}
\ No newline at end of file
diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/css/smtp-activelayer-wc.min.css b/web/app/plugins/wp-mail-smtp-pro/assets/css/smtp-activelayer-wc.min.css
new file mode 100644
index 00000000..fdeb88c6
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/assets/css/smtp-activelayer-wc.min.css
@@ -0,0 +1 @@
+.wpms-activelayer-wc{position:relative;display:flex;align-items:center;gap:20px;max-width:800px;margin:24px 0;padding:20px 24px;background:#fff;border-radius:6px;box-shadow:0 1px 2px rgba(0,0,0,.04)}.wpms-activelayer-wc__icon{flex:0 0 64px}.wpms-activelayer-wc__icon img{display:block;width:64px;height:auto}.wpms-activelayer-wc__body{flex:1 1 auto;min-width:0}.wpms-activelayer-wc__heading{margin:0 0 8px;padding:0;font-size:16px;font-weight:600;color:#1d2327}.wpms-activelayer-wc__description{margin:0;font-size:13px;color:#4a5568}.wpms-activelayer-wc__cta{display:inline-block;margin-top:10px;padding:8px 14px;border:1px solid #047857;border-radius:3px;background-color:#047857;color:#fff;font-size:13px;font-weight:600;line-height:1.4;text-decoration:none;cursor:pointer}.wpms-activelayer-wc__cta:hover,.wpms-activelayer-wc__cta:active,.wpms-activelayer-wc__cta:focus{background-color:#065f46;border-color:#065f46;color:#fff}.wpms-activelayer-wc__cta:focus{box-shadow:0 0 0 2px #065f46;outline:none}.wpms-activelayer-wc__cta.disabled{opacity:.6;cursor:default}.wpms-activelayer-wc__dismiss{position:absolute;top:8px;right:8px;padding:4px;border:none;background:none;color:#787c82;font-size:16px;line-height:1;cursor:pointer}.wpms-activelayer-wc__dismiss:hover{color:#d63638}.wpms-activelayer-wc .wp-mail-smtp-activelayer-error{margin:8px 0 0;font-size:13px;color:#d63638}@media (max-width:782px){.wpms-activelayer-wc{flex-direction:column;align-items:flex-start;gap:12px}}
diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/css/smtp-admin.min.css b/web/app/plugins/wp-mail-smtp-pro/assets/css/smtp-admin.min.css
new file mode 100644
index 00000000..66c00c96
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/assets/css/smtp-admin.min.css
@@ -0,0 +1,3 @@
+/*! tailwindcss v4.3.0 | MIT License | https://tailwindcss.com */
+@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000}}}@layer theme{:root,:host{--wpms-color-gray-700:var(--wpms-color-gray-700);--wpms-color-gray-900:var(--wpms-color-gray-900);--wpms-color-white:#fff;--wpms-text-sm:.875rem;--wpms-text-sm--line-height:calc(1.25 / .875);--wpms-font-weight-medium:500;--wpms-font-weight-semibold:600;--wpms-font-weight-bold:700;--wpms-leading-normal:var(--wpms-leading-normal);--wpms-color-primary:var(--wpms-color-primary);--wpms-color-secondary:var(--wpms-color-secondary);--wpms-color-tertiary:var(--wpms-color-tertiary);--wpms-color-link:var(--wpms-color-link);--wpms-color-success:var(--wpms-color-success);--wpms-color-error:var(--wpms-color-error);--wpms-color-surface-background-white:var(--wpms-color-surface-background-white);--wpms-color-surface-background-light:var(--wpms-color-surface-background-light);--wpms-color-surface-divider:var(--wpms-color-surface-divider);--wpms-color-surface-border:var(--wpms-color-surface-border);--wpms-color-utility-yellow-50:var(--wpms-color-utility-yellow-50);--wpms-radius-table:var(--wpms-radius-table);--wpms-spacing-sm:var(--wpms-spacing-sm);--wpms-spacing-md:var(--wpms-spacing-md);--wpms-spacing-lg:var(--wpms-spacing-lg);--wpms-shadow-subtle:var(--wpms-shadow-subtle);--wpms-shadow-soft:var(--wpms-shadow-soft)}}@layer utilities{#wp-mail-smtp .wpms\:m-0\!,
+#wp-mail-smtp .wpms\:m-\[0\]\!{margin:0!important}#wp-mail-smtp .wpms\:mx-auto{margin-inline:auto}#wp-mail-smtp .wpms\:mt-\[30px\]{margin-top:30px}#wp-mail-smtp .wpms\:mt-auto\!{margin-top:auto!important}#wp-mail-smtp .wpms\:ml-\[10px\]{margin-left:10px}#wp-mail-smtp .wpms\:icon-\[custom--badge-percent\]{width:1em;height:1em;-webkit-mask-image:var(--svg);-webkit-mask-image:var(--svg);-webkit-mask-image:var(--svg);mask-image:var(--svg);--svg:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' width='16' height='16'%3E%3Cpath fill='black' d='M8 0c1.125 0 2.125.656 2.625 1.625 1.031-.344 2.188-.094 3.031.719.813.812 1.031 2 .719 3.031C15.344 5.875 16 6.875 16 8a2.98 2.98 0 01-1.625 2.656 2.92 2.92 0 01-.719 3 3.07 3.07 0 01-3.031.75C10.125 15.375 9.125 16 8 16c-1.156 0-2.156-.625-2.656-1.594a3.07 3.07 0 01-3.032-.75c-.812-.812-1.03-1.969-.718-3C.625 10.156 0 9.156 0 8c0-1.125.625-2.125 1.594-2.625a2.97 2.97 0 01.718-3.031c.844-.813 2-1.063 3.032-.719C5.844.656 6.844 0 8 0M6 7c.531 0 1-.438 1-1 0-.531-.469-1-1-1-.563 0-1 .469-1 1 0 .563.438 1 1 1m4 2c-.563 0-1 .469-1 1 0 .563.438 1 1 1 .531 0 1-.438 1-1 0-.531-.469-1-1-1m.5-2.469c.313-.281.313-.75 0-1.062a.736.736 0 00-1.031 0l-4 4c-.313.312-.313.781 0 1.062a.684.684 0 001.031 0z'/%3E%3C/svg%3E");background-color:currentColor;display:inline-block;-webkit-mask-size:100% 100%;mask-size:100% 100%;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat}#wp-mail-smtp .wpms\:icon-\[fa6-regular--copy\]{width:.88em;height:1em;-webkit-mask-image:var(--svg);-webkit-mask-image:var(--svg);-webkit-mask-image:var(--svg);mask-image:var(--svg);--svg:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 448 512' width='448' height='512'%3E%3Cpath fill='black' d='M384 336H192c-8.8 0-16-7.2-16-16V64c0-8.8 7.2-16 16-16h140.1l67.9 67.9V320c0 8.8-7.2 16-16 16m-192 48h192c35.3 0 64-28.7 64-64V115.9c0-12.7-5.1-24.9-14.1-33.9l-67.8-67.9c-9-9-21.2-14.1-33.9-14.1H192c-35.3 0-64 28.7-64 64v256c0 35.3 28.7 64 64 64M64 128c-35.3 0-64 28.7-64 64v256c0 35.3 28.7 64 64 64h192c35.3 0 64-28.7 64-64v-32h-48v32c0 8.8-7.2 16-16 16H64c-8.8 0-16-7.2-16-16V192c0-8.8 7.2-16 16-16h32v-48z'/%3E%3C/svg%3E");background-color:currentColor;display:inline-block;-webkit-mask-size:100% 100%;mask-size:100% 100%;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat}#wp-mail-smtp .wpms\:icon-\[fa6-solid--arrow-left-long\]{width:1em;height:1em;-webkit-mask-image:var(--svg);-webkit-mask-image:var(--svg);-webkit-mask-image:var(--svg);mask-image:var(--svg);--svg:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512' width='512' height='512'%3E%3Cpath fill='black' d='M9.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l128 128c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L109.3 288H480c17.7 0 32-14.3 32-32s-14.3-32-32-32H109.3l73.4-73.4c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-128 128z'/%3E%3C/svg%3E");background-color:currentColor;display:inline-block;-webkit-mask-size:100% 100%;mask-size:100% 100%;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat}#wp-mail-smtp .wpms\:icon-\[fa6-solid--arrow-right\]{width:.88em;height:1em;-webkit-mask-image:var(--svg);-webkit-mask-image:var(--svg);-webkit-mask-image:var(--svg);mask-image:var(--svg);--svg:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 448 512' width='448' height='512'%3E%3Cpath fill='black' d='M438.6 278.6c12.5-12.5 12.5-32.8 0-45.3l-160-160c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L338.8 224H32c-17.7 0-32 14.3-32 32s14.3 32 32 32h306.7L233.4 393.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0l160-160z'/%3E%3C/svg%3E");background-color:currentColor;display:inline-block;-webkit-mask-size:100% 100%;mask-size:100% 100%;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat}#wp-mail-smtp .wpms\:icon-\[fa6-solid--bug\]{width:1em;height:1em;-webkit-mask-image:var(--svg);-webkit-mask-image:var(--svg);-webkit-mask-image:var(--svg);mask-image:var(--svg);--svg:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512' width='512' height='512'%3E%3Cpath fill='black' d='M256 0c53 0 96 43 96 96v3.6c0 15.7-12.7 28.4-28.4 28.4H188.5c-15.7 0-28.4-12.7-28.4-28.4V96c0-53 43-96 96-96zM41.4 105.4c12.5-12.5 32.8-12.5 45.3 0l64 64c.7.7 1.3 1.4 1.9 2.1c14.2-7.3 30.4-11.4 47.5-11.4h112c17.1 0 33.2 4.1 47.5 11.4c.6-.7 1.2-1.4 1.9-2.1l64-64c12.5-12.5 32.8-12.5 45.3 0s12.5 32.8 0 45.3l-64 64c-.7.7-1.4 1.3-2.1 1.9c6.2 12 10.1 25.3 11.1 39.5h64.3c17.7 0 32 14.3 32 32s-14.3 32-32 32h-64c0 24.6-5.5 47.8-15.4 68.6q3.3 1.95 6 4.8l64 64c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0l-63.1-63.1c-24.5 21.8-55.8 36.2-90.3 39.6V240c0-8.8-7.2-16-16-16s-16 7.2-16 16v239.2c-34.5-3.4-65.8-17.8-90.3-39.6l-63.1 63c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3l64-64c1.9-1.9 3.9-3.4 6-4.8C101.5 367.8 96 344.6 96 320H32c-17.7 0-32-14.3-32-32s14.3-32 32-32h64.3c1.1-14.1 5-27.5 11.1-39.5c-.7-.6-1.4-1.2-2.1-1.9l-64-64c-12.5-12.5-12.5-32.8 0-45.3z'/%3E%3C/svg%3E");background-color:currentColor;display:inline-block;-webkit-mask-size:100% 100%;mask-size:100% 100%;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat}#wp-mail-smtp .wpms\:icon-\[fa6-solid--chart-column\]{width:1em;height:1em;-webkit-mask-image:var(--svg);-webkit-mask-image:var(--svg);-webkit-mask-image:var(--svg);mask-image:var(--svg);--svg:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512' width='512' height='512'%3E%3Cpath fill='black' d='M32 32c17.7 0 32 14.3 32 32v336c0 8.8 7.2 16 16 16h400c17.7 0 32 14.3 32 32s-14.3 32-32 32H80c-44.2 0-80-35.8-80-80V64c0-17.7 14.3-32 32-32m128 192c17.7 0 32 14.3 32 32v64c0 17.7-14.3 32-32 32s-32-14.3-32-32v-64c0-17.7 14.3-32 32-32m128-64v160c0 17.7-14.3 32-32 32s-32-14.3-32-32V160c0-17.7 14.3-32 32-32s32 14.3 32 32m64 32c17.7 0 32 14.3 32 32v96c0 17.7-14.3 32-32 32s-32-14.3-32-32v-96c0-17.7 14.3-32 32-32m128-96v224c0 17.7-14.3 32-32 32s-32-14.3-32-32V96c0-17.7 14.3-32 32-32s32 14.3 32 32'/%3E%3C/svg%3E");background-color:currentColor;display:inline-block;-webkit-mask-size:100% 100%;mask-size:100% 100%;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat}#wp-mail-smtp .wpms\:icon-\[fa6-solid--check\]{width:.88em;height:1em;-webkit-mask-image:var(--svg);-webkit-mask-image:var(--svg);-webkit-mask-image:var(--svg);mask-image:var(--svg);--svg:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 448 512' width='448' height='512'%3E%3Cpath fill='black' d='M438.6 105.4c12.5 12.5 12.5 32.8 0 45.3l-256 256c-12.5 12.5-32.8 12.5-45.3 0l-128-128c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0L160 338.7l233.4-233.3c12.5-12.5 32.8-12.5 45.3 0z'/%3E%3C/svg%3E");background-color:currentColor;display:inline-block;-webkit-mask-size:100% 100%;mask-size:100% 100%;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat}#wp-mail-smtp .wpms\:icon-\[fa6-solid--circle-check\]{width:1em;height:1em;-webkit-mask-image:var(--svg);-webkit-mask-image:var(--svg);-webkit-mask-image:var(--svg);mask-image:var(--svg);--svg:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512' width='512' height='512'%3E%3Cpath fill='black' d='M256 512a256 256 0 1 0 0-512a256 256 0 1 0 0 512m113-303L241 337c-9.4 9.4-24.6 9.4-33.9 0l-64-64c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l47 47L335 175c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9z'/%3E%3C/svg%3E");background-color:currentColor;display:inline-block;-webkit-mask-size:100% 100%;mask-size:100% 100%;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat}#wp-mail-smtp .wpms\:icon-\[fa6-solid--circle-exclamation\]{width:1em;height:1em;-webkit-mask-image:var(--svg);-webkit-mask-image:var(--svg);-webkit-mask-image:var(--svg);mask-image:var(--svg);--svg:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512' width='512' height='512'%3E%3Cpath fill='black' d='M256 512a256 256 0 1 0 0-512a256 256 0 1 0 0 512m0-384c13.3 0 24 10.7 24 24v112c0 13.3-10.7 24-24 24s-24-10.7-24-24V152c0-13.3 10.7-24 24-24m-32 224a32 32 0 1 1 64 0a32 32 0 1 1-64 0'/%3E%3C/svg%3E");background-color:currentColor;display:inline-block;-webkit-mask-size:100% 100%;mask-size:100% 100%;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat}#wp-mail-smtp .wpms\:icon-\[fa6-solid--circle-question\]{width:1em;height:1em;-webkit-mask-image:var(--svg);-webkit-mask-image:var(--svg);-webkit-mask-image:var(--svg);mask-image:var(--svg);--svg:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512' width='512' height='512'%3E%3Cpath fill='black' d='M256 512a256 256 0 1 0 0-512a256 256 0 1 0 0 512m-86.2-346.7c7.9-22.3 29.1-37.3 52.8-37.3h58.3c34.9 0 63.1 28.3 63.1 63.1c0 22.6-12.1 43.5-31.7 54.8L280 264.4c-.2 13-10.9 23.6-24 23.6c-13.3 0-24-10.7-24-24v-13.5c0-8.6 4.6-16.5 12.1-20.8l44.3-25.4c4.7-2.7 7.6-7.7 7.6-13.1c0-8.4-6.8-15.1-15.1-15.1h-58.3c-3.4 0-6.4 2.1-7.5 5.3l-.4 1.2c-4.4 12.5-18.2 19-30.6 14.6s-19-18.2-14.6-30.6l.4-1.2zM224 352a32 32 0 1 1 64 0a32 32 0 1 1-64 0'/%3E%3C/svg%3E");background-color:currentColor;display:inline-block;-webkit-mask-size:100% 100%;mask-size:100% 100%;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat}#wp-mail-smtp .wpms\:icon-\[fa6-solid--lightbulb\]{width:.75em;height:1em;-webkit-mask-image:var(--svg);-webkit-mask-image:var(--svg);-webkit-mask-image:var(--svg);mask-image:var(--svg);--svg:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 384 512' width='384' height='512'%3E%3Cpath fill='black' d='M272 384c9.6-31.9 29.5-59.1 49.2-86.2c5.2-7.1 10.4-14.2 15.4-21.4c19.8-28.5 31.4-63 31.4-100.3C368 78.8 289.2 0 192 0S16 78.8 16 176c0 37.3 11.6 71.9 31.4 100.3c5 7.2 10.2 14.3 15.4 21.4c19.8 27.1 39.7 54.4 49.2 86.2h160zm-80 128c44.2 0 80-35.8 80-80v-16H112v16c0 44.2 35.8 80 80 80m-80-336c0 8.8-7.2 16-16 16s-16-7.2-16-16c0-61.9 50.1-112 112-112c8.8 0 16 7.2 16 16s-7.2 16-16 16c-44.2 0-80 35.8-80 80'/%3E%3C/svg%3E");background-color:currentColor;display:inline-block;-webkit-mask-size:100% 100%;mask-size:100% 100%;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat}#wp-mail-smtp .wpms\:icon-\[fa6-solid--magnifying-glass\]{width:1em;height:1em;-webkit-mask-image:var(--svg);-webkit-mask-image:var(--svg);-webkit-mask-image:var(--svg);mask-image:var(--svg);--svg:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512' width='512' height='512'%3E%3Cpath fill='black' d='M416 208c0 45.9-14.9 88.3-40 122.7l126.6 126.7c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L330.7 376c-34.4 25.2-76.8 40-122.7 40C93.1 416 0 322.9 0 208S93.1 0 208 0s208 93.1 208 208M208 352a144 144 0 1 0 0-288a144 144 0 1 0 0 288'/%3E%3C/svg%3E");background-color:currentColor;display:inline-block;-webkit-mask-size:100% 100%;mask-size:100% 100%;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat}#wp-mail-smtp .wpms\:icon-\[fa6-solid--rectangle-list\]{width:1.13em;height:1em;-webkit-mask-image:var(--svg);-webkit-mask-image:var(--svg);-webkit-mask-image:var(--svg);mask-image:var(--svg);--svg:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 576 512' width='576' height='512'%3E%3Cpath fill='black' d='M0 96c0-35.3 28.7-64 64-64h448c35.3 0 64 28.7 64 64v320c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64zm128 192a32 32 0 1 0 0-64a32 32 0 1 0 0 64m32-128a32 32 0 1 0-64 0a32 32 0 1 0 64 0m-32 224a32 32 0 1 0 0-64a32 32 0 1 0 0 64m96-248c-13.3 0-24 10.7-24 24s10.7 24 24 24h224c13.3 0 24-10.7 24-24s-10.7-24-24-24zm0 96c-13.3 0-24 10.7-24 24s10.7 24 24 24h224c13.3 0 24-10.7 24-24s-10.7-24-24-24zm0 96c-13.3 0-24 10.7-24 24s10.7 24 24 24h224c13.3 0 24-10.7 24-24s-10.7-24-24-24z'/%3E%3C/svg%3E");background-color:currentColor;display:inline-block;-webkit-mask-size:100% 100%;mask-size:100% 100%;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat}#wp-mail-smtp .wpms\:block{display:block}#wp-mail-smtp .wpms\:flex{display:flex}#wp-mail-smtp .wpms\:grid{display:grid}#wp-mail-smtp .wpms\:inline-flex\!{display:inline-flex!important}#wp-mail-smtp .wpms\:h-\[12px\]{height:12px}#wp-mail-smtp .wpms\:h-\[14px\]{height:14px}#wp-mail-smtp .wpms\:h-\[16px\]{height:16px}#wp-mail-smtp .wpms\:h-\[24px\]{height:24px}#wp-mail-smtp .wpms\:h-\[32px\]{height:32px}#wp-mail-smtp .wpms\:h-\[60px\]{height:60px}#wp-mail-smtp .wpms\:h-auto{height:auto}#wp-mail-smtp .wpms\:h-full{height:100%}#wp-mail-smtp .wpms\:w-\[12px\]{width:12px}#wp-mail-smtp .wpms\:w-\[13px\]{width:13px}#wp-mail-smtp .wpms\:w-\[14px\]{width:14px}#wp-mail-smtp .wpms\:w-\[16px\]{width:16px}#wp-mail-smtp .wpms\:w-\[24px\]{width:24px}#wp-mail-smtp .wpms\:w-\[60px\]{width:60px}#wp-mail-smtp .wpms\:w-\[300px\]{width:300px}#wp-mail-smtp .wpms\:w-\[310px\]{width:310px}#wp-mail-smtp .wpms\:w-\[400px\]{width:400px}#wp-mail-smtp .wpms\:w-auto{width:auto}#wp-mail-smtp .wpms\:w-full{width:100%}#wp-mail-smtp .wpms\:max-w-\[40px\]{max-width:40px}#wp-mail-smtp .wpms\:max-w-\[467px\]{max-width:467px}#wp-mail-smtp .wpms\:max-w-\[1200px\]{max-width:1200px}#wp-mail-smtp .wpms\:max-w-full{max-width:100%}#wp-mail-smtp .wpms\:min-w-\[0\]{min-width:0}#wp-mail-smtp .wpms\:min-w-\[300px\]{min-width:300px}#wp-mail-smtp .wpms\:min-w-\[1010px\]{min-width:1010px}#wp-mail-smtp .wpms\:flex-1{flex:1}#wp-mail-smtp .wpms\:shrink-0{flex-shrink:0}#wp-mail-smtp .wpms\:grow-0{flex-grow:0}#wp-mail-smtp .wpms\:basis-\[554px\]{flex-basis:554px}#wp-mail-smtp .wpms\:cursor-not-allowed{cursor:not-allowed}#wp-mail-smtp .wpms\:list-none{list-style-type:none}#wp-mail-smtp .wpms\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}#wp-mail-smtp .wpms\:flex-col{flex-direction:column}#wp-mail-smtp .wpms\:flex-wrap{flex-wrap:wrap}#wp-mail-smtp .wpms\:items-center{align-items:center}#wp-mail-smtp .wpms\:items-start{align-items:flex-start}#wp-mail-smtp .wpms\:items-stretch{align-items:stretch}#wp-mail-smtp .wpms\:justify-between{justify-content:space-between}#wp-mail-smtp .wpms\:justify-center{justify-content:center}#wp-mail-smtp .wpms\:gap-\[4px\]\!{gap:4px!important}#wp-mail-smtp .wpms\:gap-\[5px\]{gap:5px}#wp-mail-smtp .wpms\:gap-\[7px\]{gap:7px}#wp-mail-smtp .wpms\:gap-\[8px\]{gap:8px}#wp-mail-smtp .wpms\:gap-\[10px\]{gap:10px}#wp-mail-smtp .wpms\:gap-\[12px\]{gap:12px}#wp-mail-smtp .wpms\:gap-\[20px\]{gap:20px}#wp-mail-smtp .wpms\:gap-\[30px\]{gap:30px}#wp-mail-smtp .wpms\:gap-\[40px\]{gap:40px}#wp-mail-smtp .wpms\:gap-md{gap:var(--wpms-spacing-md)}#wp-mail-smtp .wpms\:gap-sm{gap:var(--wpms-spacing-sm)}#wp-mail-smtp .wpms\:gap-x-\[20px\]{column-gap:20px}#wp-mail-smtp .wpms\:gap-y-\[30px\]{row-gap:30px}#wp-mail-smtp .wpms\:self-start{align-self:flex-start}#wp-mail-smtp .wpms\:self-stretch{align-self:stretch}#wp-mail-smtp .wpms\:overflow-hidden{overflow:hidden}#wp-mail-smtp .wpms\:overflow-x-auto{overflow-x:auto}#wp-mail-smtp .wpms\:rounded-\[4px\]{border-radius:4px}#wp-mail-smtp .wpms\:rounded-\[8px\]{border-radius:8px}#wp-mail-smtp .wpms\:border{border-style:var(--tw-border-style);border-width:1px}#wp-mail-smtp .wpms\:border-t{border-top-style:var(--tw-border-style);border-top-width:1px}#wp-mail-smtp .wpms\:border-r{border-right-style:var(--tw-border-style);border-right-width:1px}#wp-mail-smtp .wpms\:border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}#wp-mail-smtp .wpms\:border-l{border-left-style:var(--tw-border-style);border-left-width:1px}#wp-mail-smtp .wpms\:border-\[\#dadadf\]{border-color:#dadadf}#wp-mail-smtp .wpms\:border-\[\#dcdcde\]{border-color:#dcdcde}#wp-mail-smtp .wpms\:border-\[\#f3f4f6\]{border-color:#f3f4f6}#wp-mail-smtp .wpms\:border-surface-divider{border-color:var(--wpms-color-surface-divider)}#wp-mail-smtp .wpms\:bg-\[\#f3f4f6\]{background-color:#f3f4f6}#wp-mail-smtp .wpms\:bg-\[\#f6f6f6\]{background-color:#f6f6f6}#wp-mail-smtp .wpms\:bg-\[\#fcf9e8\]{background-color:#fcf9e8}#wp-mail-smtp .wpms\:bg-surface-background-light{background-color:var(--wpms-color-surface-background-light)}#wp-mail-smtp .wpms\:bg-surface-background-white{background-color:var(--wpms-color-surface-background-white)}#wp-mail-smtp .wpms\:bg-white{background-color:var(--wpms-color-white)}#wp-mail-smtp .wpms\:object-cover{object-fit:cover}#wp-mail-smtp .wpms\:p-\[0\]\!{padding:0!important}#wp-mail-smtp .wpms\:p-\[20px\]{padding:20px}#wp-mail-smtp .wpms\:p-md{padding:var(--wpms-spacing-md)}#wp-mail-smtp .wpms\:px-\[36px\]{padding-inline:36px}#wp-mail-smtp .wpms\:px-\[40px\]{padding-inline:40px}#wp-mail-smtp .wpms\:px-md{padding-inline:var(--wpms-spacing-md)}#wp-mail-smtp .wpms\:py-\[40px\]{padding-block:40px}#wp-mail-smtp .wpms\:py-sm{padding-block:var(--wpms-spacing-sm)}#wp-mail-smtp .wpms\:pb-\[32px\]{padding-bottom:32px}#wp-mail-smtp .wpms\:text-center{text-align:center}#wp-mail-smtp .wpms\:text-sm{font-size:var(--wpms-text-sm);line-height:var(--tw-leading,var(--wpms-text-sm--line-height))}#wp-mail-smtp .wpms\:text-sm\!{font-size:var(--wpms-text-sm)!important;line-height:var(--tw-leading,var(--wpms-text-sm--line-height))!important}#wp-mail-smtp .wpms\:text-\[13px\]{font-size:13px}#wp-mail-smtp .wpms\:text-\[14px\]\!{font-size:14px!important}#wp-mail-smtp .wpms\:text-\[16px\]\!{font-size:16px!important}#wp-mail-smtp .wpms\:text-\[20px\]\!{font-size:20px!important}#wp-mail-smtp .wpms\:leading-\[20px\]\!{--tw-leading:20px!important;line-height:20px!important}#wp-mail-smtp .wpms\:leading-\[24px\]\!{--tw-leading:24px!important;line-height:24px!important}#wp-mail-smtp .wpms\:leading-\[28px\]\!{--tw-leading:28px!important;line-height:28px!important}#wp-mail-smtp .wpms\:font-bold\!{--tw-font-weight:var(--wpms-font-weight-bold)!important;font-weight:var(--wpms-font-weight-bold)!important}#wp-mail-smtp .wpms\:font-medium\!{--tw-font-weight:var(--wpms-font-weight-medium)!important;font-weight:var(--wpms-font-weight-medium)!important}#wp-mail-smtp .wpms\:font-semibold{--tw-font-weight:var(--wpms-font-weight-semibold);font-weight:var(--wpms-font-weight-semibold)}#wp-mail-smtp .wpms\:tracking-\[0\.25px\]{--tw-tracking:.25px;letter-spacing:.25px}#wp-mail-smtp .wpms\:text-error{color:var(--wpms-color-error)}#wp-mail-smtp .wpms\:text-gray-700{color:var(--wpms-color-gray-700)}#wp-mail-smtp .wpms\:text-gray-900\!{color:var(--wpms-color-gray-900)!important}#wp-mail-smtp .wpms\:text-link{color:var(--wpms-color-link)}#wp-mail-smtp .wpms\:text-link\!{color:var(--wpms-color-link)!important}#wp-mail-smtp .wpms\:text-primary{color:var(--wpms-color-primary)}#wp-mail-smtp .wpms\:text-secondary{color:var(--wpms-color-secondary)}#wp-mail-smtp .wpms\:text-success{color:var(--wpms-color-success)}#wp-mail-smtp .wpms\:text-tertiary{color:var(--wpms-color-tertiary)}#wp-mail-smtp .wpms\:text-utility-yellow-50{color:var(--wpms-color-utility-yellow-50)}#wp-mail-smtp .wpms\:line-through{text-decoration-line:line-through}#wp-mail-smtp .wpms\:underline{text-decoration-line:underline}#wp-mail-smtp .wpms\:opacity-50{opacity:.5}#wp-mail-smtp .wpms\:opacity-70{opacity:.7}#wp-mail-smtp .wpms\:shadow-soft{--tw-shadow:var(--wpms-shadow-soft);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}#wp-mail-smtp .wpms\:shadow-subtle{--tw-shadow:var(--wpms-shadow-subtle);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}#wp-mail-smtp .wpms\:focus\:shadow-none\!:focus{--tw-shadow:0 0 #0000!important;box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)!important}#wp-mail-smtp .wpms\:focus\:outline-none\!:focus{--tw-outline-style:none!important;outline-style:none!important}@media not all and (min-width:782px){#wp-mail-smtp .wpms\:max-tablet\:h-\[316px\]{height:316px}#wp-mail-smtp .wpms\:max-tablet\:w-full{width:100%}#wp-mail-smtp .wpms\:max-tablet\:grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}#wp-mail-smtp .wpms\:max-tablet\:flex-col{flex-direction:column}#wp-mail-smtp .wpms\:max-tablet\:items-stretch{align-items:stretch}}}@layer components{#wp-mail-smtp .wpms-test-email-success-banner{border:1px solid var(--wpms-color-surface-border);margin-bottom:var(--wpms-spacing-sm);background-color:#fff;border-radius:3px;position:relative;overflow:hidden}#wp-mail-smtp .wpms-test-email-success-banner__heading{align-items:center;gap:var(--wpms-spacing-sm);display:flex}#wp-mail-smtp .wpms-test-email-success-banner__heading h2{color:var(--wpms-color-primary)!important;margin:0!important;padding:0!important;font-size:18px!important;font-weight:500!important}#wp-mail-smtp .wpms-test-email-success-banner__card{background-color:var(--wpms-color-surface-background-white);border:1px solid var(--wpms-color-surface-divider);padding:var(--wpms-spacing-md);box-shadow:var(--wpms-shadow-subtle);border-radius:4px;flex-direction:column;flex:0 380px;gap:21px;min-width:300px;display:flex;overflow:hidden}#wp-mail-smtp .wpms-test-email-success-banner__card-msg{color:var(--wpms-color-success);font-size:13px;font-weight:500;line-height:18px;margin:0!important;padding:0!important}#wp-mail-smtp .wp-mail-smtp-link-loading{opacity:.5}#wp-mail-smtp .wpms-test-email-pro-tip-strip__loader{vertical-align:middle;margin-left:6px;display:inline-block}#wp-mail-smtp .wpms-test-email-pro-tip-strip .wp-mail-smtp-loading-spin:after{border-top-color:#0000!important;border-right-color:#0000!important;border-bottom-color:var(--wpms-color-link)!important;border-left-color:var(--wpms-color-link)!important}}:root{--wpms-color-primary:#2c3338;--wpms-color-secondary:#50575e;--wpms-color-tertiary:#646970;--wpms-color-gray-900:#111827;--wpms-color-gray-700:#374151;--wpms-color-link:#056aab;--wpms-color-success:#008a20;--wpms-color-error:#d63638;--wpms-color-surface-background-white:#fff;--wpms-color-surface-background-light:#f6f6f6;--wpms-color-surface-divider:#dcdcde;--wpms-color-surface-border:#c3c4c7;--wpms-color-utility-yellow-50:#dba617;--wpms-spacing-sm:10px;--wpms-spacing-md:20px;--wpms-spacing-lg:30px;--wpms-radius-table:4px;--wpms-shadow-subtle:0 2px 4px 0 #00000012;--wpms-shadow-soft:0 2px 4px 0 #0000000d;--wpms-leading-normal:1.2}#wp-mail-smtp .wp-mail-smtp-page-content .wpms-get-pro-tab h1,#wp-mail-smtp .wp-mail-smtp-page-content .wpms-get-pro-tab h2{font-size:24px;font-weight:700;line-height:var(--wpms-leading-normal);color:#222;margin:0;padding:0}#wp-mail-smtp .wp-mail-smtp-page-content .wpms-get-pro-tab h3{font-size:18px;font-weight:500;line-height:var(--wpms-leading-normal);color:var(--wpms-color-gray-900);margin:0;padding:0}#wp-mail-smtp .wp-mail-smtp-page-content .wpms-get-pro-tab p{color:var(--wpms-color-gray-700);margin:0;font-size:16px;font-weight:400;line-height:20px}#wpcontent{position:relative;padding-left:0!important}@media (max-width:600px){#wpcontent{padding-top:46px}#wpbody{padding-top:0}}@keyframes wp-mail-smtp-fade-in-stop-out{0%,to{opacity:0}35%{opacity:1}65%{opacity:1}}.wp-mail-smtp-text-blue{color:#056aab}.wp-mail-smtp-text-italic{font-style:italic}body.wp-mail-smtp-admin-page-body div.jconfirm *,body.wp-mail-smtp-admin-page-body div.jconfirm :before,body.wp-mail-smtp-admin-page-body div.jconfirm :after{box-sizing:border-box}body.wp-mail-smtp-admin-page-body div.jconfirm .jconfirm-bg{opacity:1;background-color:#72777cbf}body.wp-mail-smtp-admin-page-body div.jconfirm .jconfirm-box{border-top-width:4px;border-radius:6px;padding:30px;box-shadow:0 3px 6px #00000026}body.wp-mail-smtp-admin-page-body div.jconfirm .jconfirm-box div.jconfirm-closeIcon{color:#0006;top:8px;right:8px}body.wp-mail-smtp-admin-page-body div.jconfirm .jconfirm-box div.jconfirm-closeIcon:hover{color:#000c}body.wp-mail-smtp-admin-page-body div.jconfirm .jconfirm-box div.jconfirm-title-c{margin:0 0 15px;padding:0}body.wp-mail-smtp-admin-page-body div.jconfirm .jconfirm-box div.jconfirm-title-c .jconfirm-icon-c{margin:0 0 30px;transition:none;transform:none!important}body.wp-mail-smtp-admin-page-body div.jconfirm .jconfirm-box div.jconfirm-title-c .jconfirm-icon-c:empty,body.wp-mail-smtp-admin-page-body div.jconfirm .jconfirm-box div.jconfirm-title-c .jconfirm-icon-c i:empty{display:none}body.wp-mail-smtp-admin-page-body div.jconfirm .jconfirm-box div.jconfirm-title-c .jconfirm-title{color:#2c3337;font-size:24px;font-weight:500;line-height:29px;display:block}body.wp-mail-smtp-admin-page-body div.jconfirm .jconfirm-box div.jconfirm-content-pane{margin-bottom:30px;display:block}body.wp-mail-smtp-admin-page-body div.jconfirm .jconfirm-box div.jconfirm-content-pane .jconfirm-content{overflow:inherit;margin-bottom:0}body.wp-mail-smtp-admin-page-body div.jconfirm .jconfirm-box div.jconfirm-content{color:#2c3337;font-size:16px;line-height:1.4}body.wp-mail-smtp-admin-page-body div.jconfirm .jconfirm-box div.jconfirm-content p{margin:0 0 20px;font-size:16px;line-height:1.5}body.wp-mail-smtp-admin-page-body div.jconfirm .jconfirm-box div.jconfirm-content p:last-of-type{margin:0}body.wp-mail-smtp-admin-page-body div.jconfirm .jconfirm-box div.jconfirm-content p.large{font-size:18px}body.wp-mail-smtp-admin-page-body div.jconfirm .jconfirm-box div.jconfirm-content p.small,body.wp-mail-smtp-admin-page-body div.jconfirm .jconfirm-box div.jconfirm-content .wp-mail-smtp-debug-event-preview p{font-size:14px}body.wp-mail-smtp-admin-page-body div.jconfirm .jconfirm-box div.jconfirm-content .wp-mail-smtp-btn{text-transform:uppercase;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;-webkit-user-select:none;user-select:none;-webkit-tap-highlight-color:#0000;background-image:none;border:0;border-radius:4px;outline:none;min-height:1em;padding:10px 20px;font-size:14px;font-weight:600;line-height:1.4;transition:background .1s;display:inline-block}body.wp-mail-smtp-admin-page-body div.jconfirm .jconfirm-box div.jconfirm-content .wp-mail-smtp-btn.with-loader{padding:12px 12px 7px}body.wp-mail-smtp-admin-page-body div.jconfirm .jconfirm-box div.jconfirm-buttons{padding:0}body.wp-mail-smtp-admin-page-body div.jconfirm .jconfirm-box div.jconfirm-buttons button{box-shadow:none;margin:0 10px;transition:all .2s}body.wp-mail-smtp-admin-page-body div.jconfirm .jconfirm-box div.jconfirm-buttons button:hover{box-shadow:inset 0 0 0 100px #0000001a}body.wp-mail-smtp-admin-page-body div.jconfirm .jconfirm-box div.jconfirm-buttons button.btn-confirm{color:#fff;background-color:#e27730;border:1px solid #e27730;outline:none}body.wp-mail-smtp-admin-page-body div.jconfirm .jconfirm-box div.jconfirm-buttons button.btn-cancel{color:#777;background:#f8f8f8;border:1px solid #ccc;outline:none}body.wp-mail-smtp-admin-page-body div.jconfirm .jconfirm-box div.jconfirm-buttons button,body.wp-mail-smtp-admin-page-body div.jconfirm .jconfirm-box div.jconfirm-buttons button.btn-default{color:#666}body.wp-mail-smtp-admin-page-body div.jconfirm .jconfirm-box div.jconfirm-buttons button.btn-hide{display:none}body.wp-mail-smtp-admin-page-body div.jconfirm .jconfirm-box div.jconfirm-buttons button.btn-block{text-align:center;width:100%;display:block;margin:0 0 10px!important}body.wp-mail-smtp-admin-page-body div.jconfirm .jconfirm-box div.jconfirm-buttons button.btn-normal-case{text-transform:none!important}body.wp-mail-smtp-admin-page-body div.jconfirm .jconfirm-box.jconfirm-type-green{border-color:#00a32a}body.wp-mail-smtp-admin-page-body div.jconfirm .jconfirm-box.jconfirm-type-green div.jconfirm-buttons button.btn-confirm{background-color:#00a32a;border-color:#00a32a}body.wp-mail-smtp-admin-page-body div.jconfirm .jconfirm-box.jconfirm-type-orange{border-color:#e27730}body.wp-mail-smtp-admin-page-body div.jconfirm .jconfirm-box.jconfirm-type-orange div.jconfirm-buttons button.btn-confirm{background-color:#e27730;border-color:#e27730}body.wp-mail-smtp-admin-page-body div.jconfirm .jconfirm-box.jconfirm-type-red{border-color:#d63638}body.wp-mail-smtp-admin-page-body div.jconfirm .jconfirm-box.jconfirm-type-red div.jconfirm-buttons button.btn-confirm{background-color:#d63638;border-color:#d63638}body.wp-mail-smtp-admin-page-body div.jconfirm .jconfirm-box.jconfirm-type-blue{border-color:#3582c4}body.wp-mail-smtp-admin-page-body div.jconfirm .jconfirm-box.jconfirm-type-blue div.jconfirm-buttons button.btn-confirm{background-color:#3582c4;border-color:#3582c4}body.wp-mail-smtp-admin-page-body div.jconfirm .jconfirm-box.loading{height:160px;border-top-color:#fff!important}body.wp-mail-smtp-admin-page-body div.jconfirm .jconfirm-box.loading .jconfirm-title-c{z-index:4;position:relative;display:block!important}body.wp-mail-smtp-admin-page-body div.jconfirm .jconfirm-box.loading .jconfirm-title-c .jconfirm-icon-c{display:none}body.wp-mail-smtp-admin-page-body div.jconfirm .jconfirm-box.loading:before{z-index:2}body.wp-mail-smtp-admin-page-body div.jconfirm .jconfirm-box.loading:after{content:"";opacity:1;z-index:3;border:6px solid #0000;border-bottom-color:#e27730;width:40px;height:40px;margin-left:-20px;position:absolute;top:68%;left:50%}body.wp-mail-smtp-admin-page-body div.jconfirm .jconfirm-box.wp-mail-smtp-upgrade-mailer-education-modal{border-top:none;padding:30px 30px 15px}body.wp-mail-smtp-admin-page-body div.jconfirm .jconfirm-box.wp-mail-smtp-upgrade-mailer-education-modal div.jconfirm-title-c{margin:0 0 26px;padding:0}body.wp-mail-smtp-admin-page-body div.jconfirm .jconfirm-box.wp-mail-smtp-upgrade-mailer-education-modal div.jconfirm-title-c .jconfirm-icon-c{margin-bottom:20px;color:#c4c4c4!important}body.wp-mail-smtp-admin-page-body div.jconfirm .jconfirm-box.wp-mail-smtp-upgrade-mailer-education-modal div.jconfirm-title-c .jconfirm-icon-c svg{height:35px}body.wp-mail-smtp-admin-page-body div.jconfirm .jconfirm-box.wp-mail-smtp-upgrade-mailer-education-modal div.jconfirm-content-pane{margin-bottom:25px}body.wp-mail-smtp-admin-page-body div.jconfirm .jconfirm-box.wp-mail-smtp-upgrade-mailer-education-modal div.jconfirm-buttons button.btn-confirm{background-color:#e27730;border-color:#e27730}body.wp-mail-smtp-admin-page-body div.jconfirm .jconfirm-box.wp-mail-smtp-upgrade-mailer-education-modal .discount-note{text-align:center;margin:45px 0 0}body.wp-mail-smtp-admin-page-body div.jconfirm .jconfirm-box.wp-mail-smtp-upgrade-mailer-education-modal .discount-note p{color:#4d4d4d;background-color:#fcf9e8;margin:0 -30px;padding:24px 60px 20px;font-size:15px;position:relative}body.wp-mail-smtp-admin-page-body div.jconfirm .jconfirm-box.wp-mail-smtp-upgrade-mailer-education-modal .discount-note p:after{content:url(../images/font-awesome/check-circle-solid-green.svg);background-color:#fff;border-radius:50%;width:40px;height:40px;margin-right:-18px;padding:5px 6px;position:absolute;top:-20px;right:50%}body.wp-mail-smtp-admin-page-body div.jconfirm .jconfirm-box.wp-mail-smtp-upgrade-mailer-education-modal .discount-note span{color:#00a32a;font-weight:600}body.wp-mail-smtp-admin-page-body div.jconfirm .jconfirm-box.wp-mail-smtp-upgrade-mailer-education-modal .discount-note a{color:#aaa;margin-top:12px;display:block}@media screen and (max-width:1023px){#wp-mail-smtp-notice-bar{display:none!important}}#wp-mail-smtp-notice-bar{max-height:35px;margin-bottom:-3px;transition:all .3s ease-out;position:relative;overflow:hidden}#wp-mail-smtp-notice-bar.out{max-height:3px}#wp-mail-smtp-notice-bar .wp-mail-smtp-notice-bar-container{color:#3a3a56;text-align:center;background-color:#fcf9e8;border-top:3px solid #e27730;padding:7px}#wp-mail-smtp-notice-bar a{color:#e27730;font-weight:700}#wp-mail-smtp-notice-bar a:hover{color:#b85a1b}#wp-mail-smtp-notice-bar strong{color:#008a20;font-weight:700}#wp-mail-smtp-notice-bar .dismiss{color:#72777c;cursor:pointer;background:0 0;border:none;margin-top:4px;padding:5px;position:absolute;top:0;right:0}#wp-mail-smtp-notice-bar .dismiss:before{color:#72777c;content:"";speak:none;text-align:center;-webkit-font-smoothing:antialiased;background:0 0;width:20px;height:20px;font:20px/20px dashicons;display:block}#screen-meta-links .screen-meta-toggle{position:absolute;top:auto;right:20px}#wp-mail-smtp .wp-mail-smtp-product-education__row{border-bottom:1px solid #e4e4e4;max-width:1005px;padding:25px 0 5px;font-size:14px;line-height:1.3}#wp-mail-smtp .wp-mail-smtp-product-education__row>:last-child{border-bottom:none!important}#wp-mail-smtp .wp-mail-smtp-product-education__row--inactive{opacity:.5;pointer-events:none}#wp-mail-smtp .wp-mail-smtp-product-education__row--full-width{max-width:unset}#wp-mail-smtp .wp-mail-smtp-product-education__row--no-border{border-bottom:none!important}#wp-mail-smtp .wp-mail-smtp-product-education__row--no-padding{padding:0!important}#wp-mail-smtp .wp-mail-smtp-product-education__row:first-of-type{padding-top:10px}#wp-mail-smtp .wp-mail-smtp-product-education__row:last-of-type+.wp-mail-smtp-btn-upgrade{margin-top:25px}#wp-mail-smtp .wp-mail-smtp-product-education__heading{color:#2c3337;align-items:center;margin-top:0;margin-bottom:10px;font-size:20px;line-height:24px;display:flex}#wp-mail-smtp .wp-mail-smtp-product-education__heading:after{content:"";background-image:url(../images/pro-badge-small.svg);background-repeat:no-repeat;background-size:contain;width:35px;height:16px;margin-left:10px}#wp-mail-smtp .wp-mail-smtp-product-education__description{color:#50575e;margin-top:0;margin-bottom:20px;font-size:14px;line-height:1.5em}#wp-mail-smtp .wp-mail-smtp-product-education__upgrade-btn--top{margin-bottom:20px}#wp-mail-smtp .wp-mail-smtp-product-education__screenshots{flex-wrap:wrap;display:flex}@media (max-width:599px){#wp-mail-smtp .wp-mail-smtp-product-education__screenshots{flex-direction:column}}#wp-mail-smtp .wp-mail-smtp-product-education__screenshots>div{flex-direction:column;align-items:center;margin:0 25px 20px 0;display:flex}@media (max-width:599px){#wp-mail-smtp .wp-mail-smtp-product-education__screenshots>div{margin-right:0;max-width:unset!important}}#wp-mail-smtp .wp-mail-smtp-product-education__screenshots>div:last-child{margin-right:0}#wp-mail-smtp .wp-mail-smtp-product-education__screenshots>div a{box-sizing:border-box;background:#fff;border:5px solid #fff;border-radius:4px;margin-bottom:10px;position:relative}#wp-mail-smtp .wp-mail-smtp-product-education__screenshots>div a:after{content:"";opacity:0;background-color:#44444426;background-image:url(../../assets/images/icons/zoom.svg);background-position:50%;background-repeat:no-repeat;background-size:50px;width:100%;height:100%;transition:all .3s;position:absolute;top:0;left:0}#wp-mail-smtp .wp-mail-smtp-product-education__screenshots>div a:hover:after{opacity:1}#wp-mail-smtp .wp-mail-smtp-product-education__screenshots>div img{max-width:100%;height:auto;display:block}#wp-mail-smtp .wp-mail-smtp-product-education__screenshots>div span{text-align:center;color:#50575e;font-size:14px;line-height:18px}#wp-mail-smtp .wp-mail-smtp-product-education__screenshots--two>div{max-width:487px}#wp-mail-smtp .wp-mail-smtp-product-education__screenshots--three>div{max-width:316px}#wp-mail-smtp .wp-mail-smtp-product-education__list>h4{color:#2c3337;margin-top:0;margin-bottom:20px;font-size:16px;line-height:20px}#wp-mail-smtp .wp-mail-smtp-product-education__list>div{flex-wrap:wrap;display:flex}#wp-mail-smtp .wp-mail-smtp-product-education__list ul{width:290px;margin:0 40px 20px 0}#wp-mail-smtp .wp-mail-smtp-product-education__list ul li{color:#50575e;align-items:center;margin-bottom:0;font-size:14px;line-height:20px;display:flex}#wp-mail-smtp .wp-mail-smtp-product-education__list ul li+li{margin-top:20px}#wp-mail-smtp .wp-mail-smtp-product-education__list ul li:before{content:url(../../assets/images/icons/check-solid.svg);width:16px;margin-right:10px}#wp-mail-smtp #wp-mail-smtp-tools-export-email-logs-product-education section{cursor:not-allowed;margin-bottom:20px}#wp-mail-smtp #wp-mail-smtp-tools-export-email-logs-product-education section:last-of-type{margin-bottom:20px}#wp-mail-smtp #wp-mail-smtp-tools-export-email-logs-product-education section h5{clear:both;color:#2c3337;margin:0 0 10px;font-size:16px;font-weight:600}#wp-mail-smtp #wp-mail-smtp-tools-export-email-logs-product-education section label{clear:both;float:left;margin:3px 0}#wp-mail-smtp #wp-mail-smtp-tools-export-email-logs-product-education section .wp-mail-smtp-date-selector{cursor:pointer;width:200px}#wp-mail-smtp #wp-mail-smtp-tools-export-email-logs-product-education section .wp-mail-smtp-search-box-field{width:200px}#wp-mail-smtp #wp-mail-smtp-tools-export-email-logs-product-education section .wp-mail-smtp-search-box-term{width:300px}#wp-mail-smtp #wp-mail-smtp-email-controls-product-education .wp-mail-smtp-product-education__row--inactive{padding-top:20px;padding-bottom:0}#wp-mail-smtp #wp-mail-smtp-email-controls-product-education .wp-mail-smtp-setting-row:last-child{border-bottom:none}#wp-mail-smtp .wp-mail-smtp-page-nw-product-edu h2{vertical-align:middle;display:inline-block}#wp-mail-smtp .wp-mail-smtp-page-nw-product-edu .badge{vertical-align:middle;width:40px;height:17px;margin:0 0 2px 7px}#wp-mail-smtp .wp-mail-smtp-page-nw-product-edu .wp-mail-smtp-setting-row-multisite{color:#9a9a9a}#wp-mail-smtp .wp-mail-smtp-page-nw-product-edu .wp-mail-smtp-setting-row-multisite p.desc{color:#bababa}#wp-mail-smtp .wp-mail-smtp-page-nw-product-edu .wp-mail-smtp-setting-row-multisite input[type=checkbox]:disabled{border-color:#aaa}#wp-mail-smtp .wp-mail-smtp-page-nw-product-edu .wp-mail-smtp-setting-row-no-setting{margin-top:30px}#wp-mail-smtp .wp-mail-smtp-upgrade-license-banner{background:#fff;border:1px solid #c3c4c7;border-radius:4px;max-width:796px;margin-top:20px;padding:20px}#wp-mail-smtp .wp-mail-smtp-upgrade-license-banner p{margin:0 0 10px;font-size:14px}#wp-mail-smtp .wp-mail-smtp-upgrade-license-banner strong{font-weight:500}#wp-mail-smtp .wp-mail-smtp-upgrade-license-banner__discount-line strong{color:#008a20;font-weight:700}#wp-mail-smtp .wp-mail-smtp-upgrade-license-banner__upgrade-btn{border-radius:4px;font-weight:400}#wp-mail-smtp .wp-mail-smtp-upgrade-banner{background:#fff;border:1px solid #dadadf;padding:30px;position:relative;box-shadow:0 2px 4px #0000000d}#wp-mail-smtp .wp-mail-smtp-upgrade-banner p{margin:0}#wp-mail-smtp .wp-mail-smtp-upgrade-banner .wp-mail-smtp-pro-banner-dismiss{position:absolute;top:20px;right:20px}#wp-mail-smtp .wp-mail-smtp-upgrade-banner .wp-mail-smtp-pro-banner-dismiss button{color:#a9a9a9;cursor:pointer;background:0 0;border:none;margin:0;padding:0}#wp-mail-smtp .wp-mail-smtp-upgrade-banner__row+.wp-mail-smtp-upgrade-banner__row{margin-top:25px}#wp-mail-smtp .wp-mail-smtp-upgrade-banner__heading{color:#2c3338;margin:0 0 10px;font-size:17px;font-weight:500;line-height:1.5}#wp-mail-smtp .wp-mail-smtp-upgrade-banner__subheading{color:#646970;margin:0;font-size:14px;font-weight:500;line-height:1.4}#wp-mail-smtp .wp-mail-smtp-upgrade-banner__subheading strong{color:#2c3338;font-weight:700}#wp-mail-smtp .wp-mail-smtp-upgrade-banner__features{grid-column-gap:60px;grid-row-gap:12px;grid-template-columns:fit-content(480px) fit-content(480px);margin:16px 0 0;display:grid}@media (max-width:599px){#wp-mail-smtp .wp-mail-smtp-upgrade-banner__features{grid-template-columns:1fr}}#wp-mail-smtp .wp-mail-smtp-upgrade-banner__features li{margin:0;padding-left:16px;font-size:13px;line-height:1.5;position:relative}#wp-mail-smtp .wp-mail-smtp-upgrade-banner__features li:before{content:"";background-image:url(../../assets/images/icons/check.svg);background-position:50%;background-repeat:no-repeat;background-size:contain;width:10px;height:10px;position:absolute;top:3px;left:0}#wp-mail-smtp .wp-mail-smtp-upgrade-banner__features li h5{color:#2c3338;margin:0 0 4px;font-size:13px;font-weight:700}#wp-mail-smtp .wp-mail-smtp-upgrade-banner__features li p{color:#50575e;margin:0;font-weight:400}#wp-mail-smtp .wp-mail-smtp-upgrade-banner__upgrade-btn{border-radius:4px;padding:13px 19px;font-size:16px;font-weight:500}#wp-mail-smtp .wp-mail-smtp-upgrade-banner__discount-line{color:#646970;align-items:center;margin-top:8px;line-height:1.4;display:flex}#wp-mail-smtp .wp-mail-smtp-upgrade-banner__discount-line img{margin-right:7px}#wp-mail-smtp .wp-mail-smtp-upgrade-banner__discount-line strong{color:#008a20;font-weight:600}#wp-mail-smtp .wp-mail-smtp-upgrade-banner .wp-mail-smtp-product-education__screenshots{gap:25px}#wp-mail-smtp .wp-mail-smtp-upgrade-banner .wp-mail-smtp-product-education__screenshots>div{max-width:365px;margin:0}#wp-mail-smtp .wp-mail-smtp-upgrade-banner .wp-mail-smtp-product-education__screenshots>div a{border-color:#dcdcde}#wp-mail-smtp .wp-mail-smtp-upgrade-banner .wp-mail-smtp-product-education__screenshots>div span{color:#646970}#wp-mail-smtp .wp-mail-smtp-sendlayer-education{background:#fff;border:1px solid #dadadf;border-radius:4px;justify-content:space-between;align-items:center;margin-bottom:30px;display:flex;position:relative;overflow:hidden;box-shadow:0 2px 4px #0000000d}#wp-mail-smtp .wp-mail-smtp-sendlayer-education__content{flex-direction:column;flex:1;gap:24px;padding:24px 30px;display:flex}#wp-mail-smtp .wp-mail-smtp-sendlayer-education__text{flex-direction:column;gap:10px;display:flex}#wp-mail-smtp .wp-mail-smtp-sendlayer-education__heading{color:#1d2327;align-items:center;gap:10px;font-size:17px;font-weight:500;display:flex}#wp-mail-smtp .wp-mail-smtp-sendlayer-education__heading svg{flex-shrink:0}#wp-mail-smtp .wp-mail-smtp-sendlayer-education__desc{color:#50575e;margin:0;font-size:14px;line-height:1.4}#wp-mail-smtp .wp-mail-smtp-sendlayer-education__desc strong{color:#056aab;font-weight:500}#wp-mail-smtp .wp-mail-smtp-sendlayer-education__actions{flex-direction:column;align-items:flex-start;gap:10px;display:flex}#wp-mail-smtp .wp-mail-smtp-sendlayer-education__btn{align-items:center;gap:12px;display:inline-flex}#wp-mail-smtp .wp-mail-smtp-sendlayer-education__btn svg{flex-shrink:0}#wp-mail-smtp .wp-mail-smtp-sendlayer-education__btn.wp-mail-smtp-btn-loading svg{display:none}#wp-mail-smtp .wp-mail-smtp-sendlayer-education__connect-badge{white-space:nowrap;color:#50575e;opacity:.9;background-color:#f0f0f1;border-radius:53px;align-items:center;gap:6px;padding:2px 8px;font-size:12px;line-height:1.4;display:inline-flex}#wp-mail-smtp .wp-mail-smtp-sendlayer-education__connect-badge svg{flex-shrink:0}#wp-mail-smtp .wp-mail-smtp-sendlayer-education__illustration{flex-shrink:0;align-self:stretch;width:400px;overflow:hidden}#wp-mail-smtp .wp-mail-smtp-sendlayer-education__illustration img{object-fit:cover;width:100%;height:100%}#wp-mail-smtp .wp-mail-smtp-sendlayer-education__dismiss{color:#bdbdc3;cursor:pointer;background:0 0;border:none;padding:8px;line-height:1;position:absolute;top:6px;right:3px}#wp-mail-smtp .wp-mail-smtp-sendlayer-education__dismiss svg{display:block}@media (max-width:960px){#wp-mail-smtp .wp-mail-smtp-sendlayer-education__illustration{display:none}}@media (max-width:600px){#wp-mail-smtp .wp-mail-smtp-sendlayer-education__content{padding:20px}}.wp-mail-smtp-lite #wp-mail-smtp .wp-mail-smtp-page-reports .wp-mail-smtp-page-title .page-title{position:relative}.wp-mail-smtp-lite #wp-mail-smtp .wp-mail-smtp-page-reports .wp-mail-smtp-page-title .page-title:after{content:"";background-image:url(../images/pro-badge-small.svg);background-repeat:no-repeat;background-size:contain;width:35px;height:16px;margin-left:10px;display:inline-block}.wp-mail-smtp-lite #wp-mail-smtp #wp-mail-smtp-setting-row-gmail-one_click_setup_enabled-lite .wp-mail-smtp-toggle:after,.wp-mail-smtp-lite #wp-mail-smtp #wp-mail-smtp-setting-row-misc-rate_limit-lite .wp-mail-smtp-toggle:after{content:"";background-image:url(../images/pro-badge-small.svg);background-repeat:no-repeat;background-size:contain;width:35px;height:16px;margin-left:10px}.wp-mail-smtp-loading{animation:.65s linear infinite wp-mail-smtp-loading-spin}.wp-mail-smtp-loading-md{width:32px;height:32px}.wp-mail-smtp-loading-sm{width:16px;height:16px}.wp-mail-smtp-loading-spin{pointer-events:none;position:relative;color:#0000!important}.wp-mail-smtp-loading-spin:after{box-sizing:border-box;content:"";border:2px solid #fff;border-radius:50%;width:16px;height:16px;margin-top:-8px;margin-left:-8px;animation:.65s linear infinite wp-mail-smtp-loading-spin;display:block;position:absolute;top:50%;left:50%;border-top-color:#0000!important;border-right-color:#0000!important}@keyframes wp-mail-smtp-loading-spin{0%{transform:rotate(270deg)}to{transform:rotate(630deg)}}#wp-mail-smtp-flyout{z-index:99999;opacity:1;transition:all .2s ease-in-out;position:fixed;bottom:40px;right:40px}@media (max-width:959px){#wp-mail-smtp-flyout{display:none}}#wp-mail-smtp-flyout .wp-mail-smtp-flyout-head{display:block}#wp-mail-smtp-flyout .wp-mail-smtp-flyout-head:focus{box-shadow:none}#wp-mail-smtp-flyout .wp-mail-smtp-flyout-head figure{background-color:#fff;border:3px solid #e27730;border-radius:50%;margin:0;transition:all .2s ease-in-out;overflow:hidden;box-shadow:0 2px 20px #00000026}#wp-mail-smtp-flyout .wp-mail-smtp-flyout-head figure img{width:54px;height:54px;transition:all .2s ease-in-out;display:block}#wp-mail-smtp-flyout .wp-mail-smtp-flyout-head:hover figure{box-shadow:0 2px 30px #0003}#wp-mail-smtp-flyout .wp-mail-smtp-flyout-head .wp-mail-smtp-flyout-label{opacity:0;margin-right:-50px;right:calc(100% + 15px);transform:translateY(-50%)scale(0)}#wp-mail-smtp-flyout .wp-mail-smtp-flyout-head:hover .wp-mail-smtp-flyout-label{opacity:1;margin-right:0;transform:translateY(-50%)scale(1)}#wp-mail-smtp-flyout .wp-mail-smtp-flyout-label{color:#fff;white-space:nowrap;font-size:12px;line-height:initial;background-color:#a7aaad;border-radius:3px;padding:6px 10px;transition:all .2s ease-out;display:block;position:absolute;top:50%;right:calc(100% + 15px);transform:translateY(-50%);height:auto!important}#wp-mail-smtp-flyout .wp-mail-smtp-flyout-item{opacity:0;visibility:hidden;background-color:#738e9e;border-radius:50%;width:40px;height:40px;transition:all .2s ease-in-out;display:flex;position:absolute;left:10px;transform:scale(0);box-shadow:0 2px 10px #00000026}#wp-mail-smtp-flyout .wp-mail-smtp-flyout-item img{margin:auto}#wp-mail-smtp-flyout .wp-mail-smtp-flyout-item.wp-mail-smtp-flyout-item-0{bottom:75px}#wp-mail-smtp-flyout .wp-mail-smtp-flyout-item.wp-mail-smtp-flyout-item-1{bottom:130px}#wp-mail-smtp-flyout .wp-mail-smtp-flyout-item.wp-mail-smtp-flyout-item-2{bottom:185px}#wp-mail-smtp-flyout .wp-mail-smtp-flyout-item.wp-mail-smtp-flyout-item-3{bottom:240px}#wp-mail-smtp-flyout .wp-mail-smtp-flyout-item.wp-mail-smtp-flyout-item-4{bottom:295px}#wp-mail-smtp-flyout .wp-mail-smtp-flyout-item:hover{background-color:#395360}#wp-mail-smtp-flyout .wp-mail-smtp-flyout-item:hover .wp-mail-smtp-flyout-label{background-color:#787c82}#wp-mail-smtp-flyout.opened .wp-mail-smtp-flyout-item{opacity:1;visibility:visible;transform:scale(1)}#wp-mail-smtp-flyout.opened .wp-mail-smtp-flyout-head .wp-mail-smtp-flyout-label{display:none}#wp-mail-smtp-flyout.opened .wp-mail-smtp-flyout-head figure img{transform:rotate(-15deg)scale(1.17)}#wp-mail-smtp-flyout.opened .wp-mail-smtp-flyout-item-0{transition:transform .2s,background-color .2s}#wp-mail-smtp-flyout.opened .wp-mail-smtp-flyout-item-1{transition:transform .2s 35ms,background-color .2s}#wp-mail-smtp-flyout.opened .wp-mail-smtp-flyout-item-2{transition:transform .2s 70ms,background-color .2s}#wp-mail-smtp-flyout.opened .wp-mail-smtp-flyout-item-3{transition:transform .2s .105s,background-color .2s}#wp-mail-smtp-flyout.opened .wp-mail-smtp-flyout-item-4{transition:transform .2s .14s,background-color .2s}#wp-mail-smtp-flyout.out{opacity:0;visibility:hidden}.wp-mail-smtp-tooltip{cursor:help;color:#50575e;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;display:inline-block;position:relative;margin-top:15px!important}.wp-mail-smtp-tooltip-with-icon{margin-left:10px;margin-top:2px!important}.wp-mail-smtp-tooltip-with-icon .wp-mail-smtp-tooltip-text{bottom:130%}.wp-mail-smtp-tooltip-text{visibility:hidden;z-index:9999;color:#fff;text-align:center;background-color:#2d4f60;border-radius:5px;width:360px;margin-left:-180px;padding:16px 20px;font-size:14px;position:absolute;bottom:100%;left:50%}.wp-mail-smtp-tooltip-text:after{content:" ";border:5px solid #0000;border-top-color:#2d4f60;margin-left:-5px;position:absolute;top:100%;left:50%}.wp-mail-smtp-tooltip-small-text{text-align:left;font-size:12px;line-height:140%}.wp-mail-smtp-tooltip-small-text code,.wp-mail-smtp-tooltip-small-text kbd{font-size:12px}.wp-mail-smtp-tooltip:hover .wp-mail-smtp-tooltip-text{visibility:visible}.wp-mail-smtp-page label.wp-mail-smtp-setting-label-with-tooltip .wp-mail-smtp-tooltip-with-icon{vertical-align:top;margin-left:0;position:relative;top:0;left:0}.wp-mail-smtp-page .wp-mail-smtp-tooltip-text a,.wp-mail-smtp-page .wp-mail-smtp-tooltip-text a:active,.wp-mail-smtp-page .wp-mail-smtp-tooltip-text a:hover{color:#fff}#wp-mail-smtp .wp-mail-smtp-conditional button{cursor:pointer}#wp-mail-smtp .wp-mail-smtp-conditional table{border-collapse:collapse;border-spacing:0;width:100%;max-width:740px;margin:0;padding:0;font-size:13px}#wp-mail-smtp .wp-mail-smtp-conditional table td{padding:0 10px 20px 0}@media (max-width:600px){#wp-mail-smtp .wp-mail-smtp-conditional__row{flex-direction:column;display:flex}}#wp-mail-smtp .wp-mail-smtp-conditional__row input,#wp-mail-smtp .wp-mail-smtp-conditional__row select{text-overflow:ellipsis;max-width:100%;margin:0;width:100%!important}#wp-mail-smtp .wp-mail-smtp-conditional__property-col{width:23%}@media (max-width:600px){#wp-mail-smtp .wp-mail-smtp-conditional__property-col{width:100%}}#wp-mail-smtp .wp-mail-smtp-conditional__operator-col{width:23%}@media (max-width:600px){#wp-mail-smtp .wp-mail-smtp-conditional__operator-col{width:100%}}#wp-mail-smtp .wp-mail-smtp-conditional__value-col{width:42%}@media (max-width:600px){#wp-mail-smtp .wp-mail-smtp-conditional__value-col{width:100%}}#wp-mail-smtp .wp-mail-smtp-conditional__value :disabled{cursor:not-allowed;background-color:#f1f1f1}#wp-mail-smtp .wp-mail-smtp-conditional__actions{align-items:center;padding-right:0;display:flex}#wp-mail-smtp .wp-mail-smtp-conditional__delete-rule{color:#999;vertical-align:middle;background-color:#0000;border:none;margin:0 0 0 10px;padding:0}#wp-mail-smtp .wp-mail-smtp-conditional__delete-rule:hover{color:#d63638}#wp-mail-smtp .wp-mail-smtp-conditional__group-delimiter{color:#646970;margin:0 0 20px;font-size:14px;font-style:italic;font-weight:400;line-height:17px}.wp-mail-smtp-toggle{vertical-align:middle;-webkit-user-select:none;user-select:none;align-items:center;display:inline-flex}.wp-mail-smtp-toggle__switch{cursor:pointer;background-color:#ccc;border-radius:10px;flex-shrink:0;width:27px;height:17px;transition:all .2s ease-in-out;position:relative}.wp-mail-smtp-toggle__switch:before{content:"";background-color:#fff;border-radius:50%;width:13px;height:13px;transition:all .2s ease-in-out;position:absolute;top:2px;left:2px}.wp-mail-smtp-toggle__label{color:#50575e;text-transform:uppercase;margin-left:10px;font-size:13px;font-weight:400;line-height:15px;display:none}.wp-mail-smtp-toggle__label--static{text-transform:none;display:block}.wp-mail-smtp-toggle input[type=checkbox]{clip:rect(1px, 1px, 1px, 1px);white-space:nowrap;width:1px;height:1px;position:absolute;top:auto;overflow:hidden}.wp-mail-smtp-toggle input[type=checkbox]:not(:checked):hover+.wp-mail-smtp-toggle__switch,.wp-mail-smtp-toggle input[type=checkbox]:not(:checked):focus+.wp-mail-smtp-toggle__switch{background-color:#999;box-shadow:0 0 0 1px #fff,0 0 0 3px #999}.wp-mail-smtp-toggle input[type=checkbox]:not(:checked)~.wp-mail-smtp-toggle__label--unchecked{display:block}.wp-mail-smtp-toggle input[type=checkbox]:checked+.wp-mail-smtp-toggle__switch{background-color:#00a32a}.wp-mail-smtp-toggle input[type=checkbox]:checked+.wp-mail-smtp-toggle__switch:before{transform:translate(10px)}.wp-mail-smtp-toggle input[type=checkbox]:checked:hover+.wp-mail-smtp-toggle__switch,.wp-mail-smtp-toggle input[type=checkbox]:checked:focus+.wp-mail-smtp-toggle__switch{box-shadow:0 0 0 1px #fff,0 0 0 3px #00a32a}.wp-mail-smtp-toggle input[type=checkbox]:checked~.wp-mail-smtp-toggle__label--checked{display:block}.wp-mail-smtp-toggle input[type=checkbox]:disabled:hover+.wp-mail-smtp-toggle__switch,.wp-mail-smtp-toggle input[type=checkbox]:disabled:focus+.wp-mail-smtp-toggle__switch{box-shadow:none}.wp-mail-smtp-toggle input[type=checkbox]:disabled~*{opacity:.5;cursor:default}.wp-mail-smtp-admin-page-body #wpbody-content{padding-bottom:200px}@media (max-width:782px){.wp-mail-smtp-admin-page-body #wpbody-content{padding-bottom:0!important}}.wp-mail-smtp-admin-page-body #wpfooter a{color:#2171b1!important}.wp-mail-smtp-admin-page-body #wpfooter a:hover,.wp-mail-smtp-admin-page-body #wpfooter a:active{color:#135e96!important}.wp-mail-smtp-admin-page-body #wpfooter .wp-mail-smtp-footer-promotion{text-align:center;color:#646970;margin-bottom:20px;padding:30px 0;font-size:13px;font-weight:400;line-height:normal}.wp-mail-smtp-admin-page-body #wpfooter .wp-mail-smtp-footer-promotion p{font-weight:600}.wp-mail-smtp-admin-page-body #wpfooter .wp-mail-smtp-footer-promotion-links,.wp-mail-smtp-admin-page-body #wpfooter .wp-mail-smtp-footer-promotion-social{justify-content:center;align-items:center;display:flex}.wp-mail-smtp-admin-page-body #wpfooter .wp-mail-smtp-footer-promotion-links{color:#646970;margin:10px 0}.wp-mail-smtp-admin-page-body #wpfooter .wp-mail-smtp-footer-promotion-links a,.wp-mail-smtp-admin-page-body #wpfooter .wp-mail-smtp-footer-promotion-links a:hover{color:#04558a}.wp-mail-smtp-admin-page-body #wpfooter .wp-mail-smtp-footer-promotion-links span{color:#c3c4c7;padding:0 7px}.wp-mail-smtp-admin-page-body #wpfooter .wp-mail-smtp-footer-promotion-social{gap:10px;margin:0}.wp-mail-smtp-admin-page-body #wpfooter .wp-mail-smtp-footer-promotion-social li{margin-bottom:0}.wp-mail-smtp-admin-page-body #wpfooter .wp-mail-smtp-footer-promotion-social li path{color:#646970}.wp-mail-smtp-admin-page-body #wpfooter .wp-mail-smtp-footer-promotion-social li:hover path{fill:#50575e}.wp-mail-smtp-admin-page-body #wpfooter .wp-mail-smtp-footer-promotion-social a{height:16px;display:block}.wp-mail-smtp-admin-page-body #wpfooter #footer-left{color:#50575e;font-size:13px;font-style:normal;font-weight:400;line-height:normal}.wp-mail-smtp-admin-page-body #wpfooter #footer-left strong{font-weight:600}#wp-mail-smtp .jconfirm.wp-mail-smtp-wpcode-install-dialog .wp-mail-smtp-wpcode-install-modal,#wp-mail-smtp .wp-mail-smtp-wpcode .wp-mail-smtp-wpcode-fallback{text-align:center;flex-direction:column;align-items:center;display:flex}#wp-mail-smtp .jconfirm.wp-mail-smtp-wpcode-install-dialog .wp-mail-smtp-wpcode-install-modal .wp-mail-smtp-wpcode-install-modal-icons,#wp-mail-smtp .wp-mail-smtp-wpcode .wp-mail-smtp-wpcode-fallback .wp-mail-smtp-wpcode-fallback-icons{width:auto;height:50px;margin-bottom:25px}#wp-mail-smtp .jconfirm.wp-mail-smtp-wpcode-install-dialog .wp-mail-smtp-wpcode-install-modal .wp-mail-smtp-wpcode-install-modal-title,#wp-mail-smtp .wp-mail-smtp-wpcode .wp-mail-smtp-wpcode-fallback .wp-mail-smtp-wpcode-fallback-title{color:#2c3338;max-width:380px;margin-bottom:15px;font-size:24px;font-weight:500;line-height:30px}#wp-mail-smtp .jconfirm.wp-mail-smtp-wpcode-install-dialog .wp-mail-smtp-wpcode-install-modal .wp-mail-smtp-wpcode-install-modal-description,#wp-mail-smtp .wp-mail-smtp-wpcode .wp-mail-smtp-wpcode-fallback .wp-mail-smtp-wpcode-fallback-description{color:#50575e;max-width:380px;margin-bottom:25px;font-size:18px;font-weight:400;line-height:26px}#wp-mail-smtp .jconfirm.wp-mail-smtp-wpcode-install-dialog .wp-mail-smtp-wpcode-install-modal .wp-mail-smtp-wpcode-install-modal-link,#wp-mail-smtp .wp-mail-smtp-wpcode .wp-mail-smtp-wpcode-fallback .wp-mail-smtp-wpcode-fallback-link{color:#50575e;font-size:14px;font-weight:400;line-height:20px;text-decoration-line:underline}#wp-mail-smtp .jconfirm.wp-mail-smtp-wpcode-install-dialog .wp-mail-smtp-wpcode-install-modal .wp-mail-smtp-wpcode-install-modal-link:hover,#wp-mail-smtp .wp-mail-smtp-wpcode .wp-mail-smtp-wpcode-fallback .wp-mail-smtp-wpcode-fallback-link:hover{color:#2c3338}#wp-mail-smtp .wp-mail-smtp-wpcode{grid-template-columns:1fr;display:grid}#wp-mail-smtp .wp-mail-smtp-wpcode .wp-mail-smtp-wpcode-container{position:relative}#wp-mail-smtp .wp-mail-smtp-wpcode .wp-mail-smtp-wpcode-blur{filter:blur(6px);pointer-events:none;-webkit-user-select:none;user-select:none;width:100%}@media (max-width:782px) and (min-width:651px){#wp-mail-smtp .wp-mail-smtp-wpcode .wp-mail-smtp-wpcode-blur #wp-mail-smtp-wpcode-snippets-list .list .wp-mail-smtp-wpcode-snippet{display:none}#wp-mail-smtp .wp-mail-smtp-wpcode .wp-mail-smtp-wpcode-blur #wp-mail-smtp-wpcode-snippets-list .list .wp-mail-smtp-wpcode-snippet:nth-child(-n+4){display:flex}}@media (max-width:650px){#wp-mail-smtp .wp-mail-smtp-wpcode .wp-mail-smtp-wpcode-blur #wp-mail-smtp-wpcode-snippets-list .list .wp-mail-smtp-wpcode-snippet{display:none}#wp-mail-smtp .wp-mail-smtp-wpcode .wp-mail-smtp-wpcode-blur #wp-mail-smtp-wpcode-snippets-list .list .wp-mail-smtp-wpcode-snippet:nth-child(-n+2){display:flex}}#wp-mail-smtp .wp-mail-smtp-wpcode .wp-mail-smtp-wpcode-fallback{z-index:100;background:#fff;border-radius:6px;place-self:start center;width:90%;max-width:730px;height:fit-content;margin:80px 0 0;padding:40px;box-shadow:0 5px 60px #0003}#wp-mail-smtp .wp-mail-smtp-wpcode .wp-mail-smtp-wpcode-fallback .wp-mail-smtp-wpcode-fallback-install-plugin{margin-bottom:15px}#wp-mail-smtp .wp-mail-smtp-wpcode .wp-mail-smtp-wpcode-blur,#wp-mail-smtp .wp-mail-smtp-wpcode .wp-mail-smtp-wpcode-fallback{grid-area:1/1}#wp-mail-smtp .wp-mail-smtp-wpcode .wp-mail-smtp-wpcode-header.section-heading{justify-content:space-between;align-items:flex-end;display:flex}#wp-mail-smtp .wp-mail-smtp-wpcode .wp-mail-smtp-wpcode-header.section-heading .wp-mail-smtp-setting-field{margin:0}@media (max-width:599px){#wp-mail-smtp .wp-mail-smtp-wpcode .wp-mail-smtp-wpcode-header.section-heading{flex-direction:column;align-items:flex-start}}#wp-mail-smtp .wp-mail-smtp-wpcode .wp-mail-smtp-wpcode-header-search{margin-left:30px;position:relative}#wp-mail-smtp .wp-mail-smtp-wpcode .wp-mail-smtp-wpcode-header-search .wp-mail-smtp-wpcode-search-icon{color:#a7aaad;pointer-events:none;width:16px;height:16px;position:absolute;top:50%;left:10px;transform:translateY(-50%)}@media (max-width:599px){#wp-mail-smtp .wp-mail-smtp-wpcode .wp-mail-smtp-wpcode-header-search{width:100%;margin-top:20px;margin-left:0}}#wp-mail-smtp .wp-mail-smtp-wpcode #wp-mail-smtp-wpcode-snippet-search{width:250px;height:36px;padding-left:30px}@media (max-width:599px){#wp-mail-smtp .wp-mail-smtp-wpcode #wp-mail-smtp-wpcode-snippet-search{width:100%}}#wp-mail-smtp .wp-mail-smtp-wpcode #wp-mail-smtp-wpcode-snippets-list{margin-top:30px}#wp-mail-smtp .wp-mail-smtp-wpcode #wp-mail-smtp-wpcode-snippets-list .list{grid-gap:20px;grid-template-columns:repeat(auto-fill,minmax(350px,1fr));display:grid}@media (max-width:599px){#wp-mail-smtp .wp-mail-smtp-wpcode #wp-mail-smtp-wpcode-snippets-list .list{grid-template-columns:repeat(auto-fill,minmax(280px,1fr))}}#wp-mail-smtp .wp-mail-smtp-wpcode #wp-mail-smtp-wpcode-snippets-list .list .wp-mail-smtp-wpcode-snippet{background:#fff;border:1px solid #c3c4c7;border-radius:4px;flex-direction:column;justify-content:space-between;align-items:flex-start;height:100%;margin:0;display:flex;overflow:hidden;box-shadow:0 2px 4px #00000012}#wp-mail-smtp .wp-mail-smtp-wpcode #wp-mail-smtp-wpcode-snippets-list .list .wp-mail-smtp-wpcode-snippet .wp-mail-smtp-wpcode-snippet-header{flex-direction:column;gap:10px;width:100%;padding:20px;display:flex}#wp-mail-smtp .wp-mail-smtp-wpcode #wp-mail-smtp-wpcode-snippets-list .list .wp-mail-smtp-wpcode-snippet .wp-mail-smtp-wpcode-snippet-title{color:#2c3338;margin:0;font-size:14px;font-style:normal;font-weight:500;line-height:20px}#wp-mail-smtp .wp-mail-smtp-wpcode #wp-mail-smtp-wpcode-snippets-list .list .wp-mail-smtp-wpcode-snippet .wp-mail-smtp-wpcode-snippet-note{color:#50575e;margin:0;font-size:14px;font-weight:400;line-height:20px}#wp-mail-smtp .wp-mail-smtp-wpcode #wp-mail-smtp-wpcode-snippets-list .list .wp-mail-smtp-wpcode-snippet .wp-mail-smtp-wpcode-snippet-footer{background:#f6f6f6;justify-content:flex-start;align-items:center;gap:15px;width:100%;padding:20px;display:flex}#wp-mail-smtp .wp-mail-smtp-wpcode #wp-mail-smtp-wpcode-snippets-list .list .wp-mail-smtp-wpcode-snippet .wp-mail-smtp-wpcode-snippet-footer .wp-mail-smtp-btn+.wp-mail-smtp-btn{margin-left:0}#wp-mail-smtp .wp-mail-smtp-wpcode #wp-mail-smtp-wpcode-snippets-list .list .wp-mail-smtp-wpcode-snippet .wp-mail-smtp-wpcode-snippet-badge{color:#008a20;letter-spacing:.5px;text-transform:uppercase;margin-left:auto;font-size:11px;font-weight:700;line-height:14px}#wp-mail-smtp .wp-mail-smtp-wpcode #wp-mail-smtp-wpcode-snippets-list .list .wp-mail-smtp-wpcode-snippet .wp-mail-smtp-wpcode-snippet-badge.wp-mail-smtp-wpcode-installing-in-progress{color:#a7aaad}#wp-mail-smtp .wp-mail-smtp-wpcode #wp-mail-smtp-wpcode-no-results{font-size:14px;display:none}#wp-mail-smtp .wp-mail-smtp-wpcode-modal-code .CodeMirror{border:1px solid #dcdcde;border-radius:4px;height:auto;font-size:13px}#wp-mail-smtp .wp-mail-smtp-wpcode-modal-code .CodeMirror-scroll{max-height:60vh}#wp-mail-smtp .wp-mail-smtp-wpcode-modal-code textarea{width:100%;min-height:200px;font-family:Consolas,Monaco,monospace;font-size:13px}#wp-mail-smtp .wp-mail-smtp-wpcode-modal{padding:0 2px 4px}#wp-mail-smtp .jconfirm.wp-mail-smtp-wpcode-install-dialog div.jconfirm-title-c{display:none}#wp-mail-smtp .jconfirm.wp-mail-smtp-wpcode-install-dialog div.jconfirm-content-pane{margin-bottom:0}#wp-mail-smtp .jconfirm.wp-mail-smtp-wpcode-install-dialog .wp-mail-smtp-wpcode-install-modal .wp-mail-smtp-wpcode-modal-error{margin:15px 0 0}#wp-mail-smtp .jconfirm.wp-mail-smtp-wpcode-install-dialog .wp-mail-smtp-wpcode-install-modal .wp-mail-smtp-wpcode-install-modal-btn{text-transform:none;margin-bottom:15px;padding:16px 28px;font-size:16px;font-weight:600}#wp-mail-smtp .jconfirm.wp-mail-smtp-wpcode-dialog .jconfirm-box div.jconfirm-content-pane{margin-bottom:0!important}#wp-mail-smtp .jconfirm.wp-mail-smtp-wpcode-dialog .jconfirm-box div.jconfirm-content .wp-mail-smtp-btn-secondary{text-transform:none;padding:9px 15px;font-weight:500}#wp-mail-smtp .jconfirm-content-pane:has(.wp-mail-smtp-wpcode-modal){margin-bottom:0!important}#wp-mail-smtp .wp-mail-smtp-wpcode-modal-error{color:#d63638;margin:15px 0 0;font-size:14px;line-height:20px}#wp-mail-smtp .wp-mail-smtp-wpcode-modal-error a{color:inherit;text-decoration:underline}#wp-mail-smtp .wp-mail-smtp-wpcode-modal-note{color:#646970;margin-bottom:16px;font-size:14px;line-height:20px}#wp-mail-smtp .wp-mail-smtp-wpcode-modal-footer{justify-content:flex-end;align-items:center;gap:12px;margin-top:30px;display:flex}#wp-mail-smtp .wp-mail-smtp-wpcode-modal-footer .wp-mail-smtp-wpcode-modal-installing{color:#a7aaad;font-size:14px;line-height:20px}#wp-mail-smtp .wp-mail-smtp-ai-mcp{flex-direction:column;gap:20px;display:flex}#wp-mail-smtp .wp-mail-smtp-ai-mcp .wp-mail-smtp-ai-mcp-hero{background:#1d2327;border-radius:9px;align-items:flex-start;gap:12px;display:flex;overflow:hidden}@media (max-width:767px){#wp-mail-smtp .wp-mail-smtp-ai-mcp .wp-mail-smtp-ai-mcp-hero{flex-direction:column;gap:0}}#wp-mail-smtp .wp-mail-smtp-ai-mcp .wp-mail-smtp-ai-mcp-hero-copy{flex:1 0 0;min-width:0;padding:25px}#wp-mail-smtp .wp-mail-smtp-ai-mcp .wp-mail-smtp-ai-mcp-hero-illustration{object-fit:contain;object-position:bottom right;flex-shrink:0;align-self:stretch;width:338px;height:259px}@media (max-width:767px){#wp-mail-smtp .wp-mail-smtp-ai-mcp .wp-mail-smtp-ai-mcp-hero-illustration{width:100%}}#wp-mail-smtp .wp-mail-smtp-ai-mcp .wp-mail-smtp-ai-mcp-eyebrow{letter-spacing:.65px;text-transform:uppercase;color:#ffffff80;margin:0;font-size:13px;font-weight:700;line-height:1.5}#wp-mail-smtp .wp-mail-smtp-ai-mcp .wp-mail-smtp-ai-mcp-title{color:#fff;margin:0;padding:0;font-size:24px;font-weight:700;line-height:1.5}#wp-mail-smtp .wp-mail-smtp-ai-mcp .wp-mail-smtp-ai-mcp-lede{color:#ffffffbf;margin:5px 0 0;font-size:15px;line-height:1.5}#wp-mail-smtp .wp-mail-smtp-ai-mcp .wp-mail-smtp-ai-mcp-lede a{color:inherit;text-decoration:underline}#wp-mail-smtp .wp-mail-smtp-ai-mcp .wp-mail-smtp-ai-mcp-lede a:hover{color:#fff}#wp-mail-smtp .wp-mail-smtp-ai-mcp .wp-mail-smtp-ai-mcp-cta-row{align-items:center;gap:30px;margin-top:30px;display:flex}#wp-mail-smtp .wp-mail-smtp-ai-mcp .wp-mail-smtp-ai-mcp-install-note{color:#ffffff80;margin:10px 0 0;font-size:13px;line-height:1.5}#wp-mail-smtp .wp-mail-smtp-ai-mcp .wp-mail-smtp-ai-mcp-install-error{color:#d63638;margin:10px 0 0;font-size:13px;line-height:1.5}#wp-mail-smtp .wp-mail-smtp-ai-mcp .wp-mail-smtp-ai-mcp-capabilities{background:#fff;border:1px solid #c3c4c7;border-radius:9px;flex-direction:column;gap:20px;padding:25px;display:flex;box-shadow:0 2px 2px #00000012}#wp-mail-smtp .wp-mail-smtp-ai-mcp .wp-mail-smtp-ai-mcp-capabilities-head{justify-content:space-between;align-items:center;display:flex}@media (max-width:767px){#wp-mail-smtp .wp-mail-smtp-ai-mcp .wp-mail-smtp-ai-mcp-capabilities-head{flex-direction:column;align-items:center;gap:10px}}#wp-mail-smtp .wp-mail-smtp-ai-mcp .wp-mail-smtp-ai-mcp-capabilities-title{color:#2c3338;margin:0;padding:0;font-size:16px;font-weight:500;line-height:20px}@media (max-width:767px){#wp-mail-smtp .wp-mail-smtp-ai-mcp .wp-mail-smtp-ai-mcp-capabilities-title{text-align:center}}#wp-mail-smtp .wp-mail-smtp-ai-mcp .wp-mail-smtp-ai-mcp-docs-link{color:#e27730;align-items:center;gap:5px;font-size:14px;font-weight:500;line-height:1.5;text-decoration:none;display:inline-flex}#wp-mail-smtp .wp-mail-smtp-ai-mcp .wp-mail-smtp-ai-mcp-docs-link:hover,#wp-mail-smtp .wp-mail-smtp-ai-mcp .wp-mail-smtp-ai-mcp-docs-link:focus{text-decoration:underline}#wp-mail-smtp .wp-mail-smtp-ai-mcp .wp-mail-smtp-ai-mcp-docs-arrow{font-size:12px}#wp-mail-smtp .wp-mail-smtp-ai-mcp .wp-mail-smtp-ai-mcp-cards{gap:15px;display:flex}@media (max-width:767px){#wp-mail-smtp .wp-mail-smtp-ai-mcp .wp-mail-smtp-ai-mcp-cards{flex-direction:column}}#wp-mail-smtp .wp-mail-smtp-ai-mcp .wp-mail-smtp-ai-mcp-card{background:#fff;border:1px solid #dcdcde;border-radius:6px;flex-direction:column;flex:0 326px;gap:20px;max-width:326px;padding:20px;display:flex;position:relative}@media (max-width:767px){#wp-mail-smtp .wp-mail-smtp-ai-mcp .wp-mail-smtp-ai-mcp-card{flex:auto;max-width:none}}#wp-mail-smtp .wp-mail-smtp-ai-mcp .wp-mail-smtp-ai-mcp-card-head{align-items:center;gap:15px;display:flex}#wp-mail-smtp .wp-mail-smtp-ai-mcp .wp-mail-smtp-ai-mcp-card-icon{color:#e27730;background:#fdf2eb;border-radius:4px;flex-shrink:0;justify-content:center;align-items:center;width:40px;height:40px;font-size:20px;display:inline-flex}#wp-mail-smtp .wp-mail-smtp-ai-mcp .wp-mail-smtp-ai-mcp-card-title{color:#2c3338;margin:0;padding:0;font-size:16px;font-weight:500;line-height:1.5}#wp-mail-smtp .wp-mail-smtp-ai-mcp .wp-mail-smtp-ai-mcp-card-bullets{flex-direction:column;gap:10px;margin:0;padding:0;list-style:none;display:flex}#wp-mail-smtp .wp-mail-smtp-ai-mcp .wp-mail-smtp-ai-mcp-card-bullets li{align-items:flex-start;gap:10px;margin:0;display:flex}#wp-mail-smtp .wp-mail-smtp-ai-mcp .wp-mail-smtp-ai-mcp-card-dot{background:#e27730;border-radius:50%;flex-shrink:0;width:6px;height:6px;margin-top:8px;display:inline-block}#wp-mail-smtp .wp-mail-smtp-ai-mcp .wp-mail-smtp-ai-mcp-card-bullet-text{color:#50575e;font-size:13px;line-height:1.5}#wp-mail-smtp .wp-mail-smtp-ai-mcp .wp-mail-smtp-ai-mcp-pro-badge{top:19px;width:35px;height:16px;position:absolute;inset-inline-end:19px}.wp-mail-smtp-plugin-recommendation-page{width:700px;margin:0 auto;padding:0}.wp-mail-smtp-plugin-recommendation-page *,.wp-mail-smtp-plugin-recommendation-page :before,.wp-mail-smtp-plugin-recommendation-page :after{box-sizing:border-box}.wp-mail-smtp-plugin-recommendation-page section{margin:50px 0}.wp-mail-smtp-plugin-recommendation-page p{font-size:15px}.wp-mail-smtp-plugin-recommendation-page .top{text-align:center}.wp-mail-smtp-plugin-recommendation-page .top img{margin-bottom:50px}.wp-mail-smtp-plugin-recommendation-page .top h1{margin-bottom:25px;padding:0;font-size:26px;font-weight:500}.wp-mail-smtp-plugin-recommendation-page .top p{color:#2c3338;margin:0;font-size:16px;line-height:22px}.wp-mail-smtp-plugin-recommendation-page .top .updated{display:none}.wp-mail-smtp-plugin-recommendation-page .notice{display:none!important}.wp-mail-smtp-plugin-recommendation-page .loader{text-indent:-9999em;background-color:#0000;border:4px solid #969696;border-left-color:#404040;margin:0 auto;animation:1.1s linear infinite wp-mail-smtp-plugin-recommendation-page-load;position:relative;transform:translateZ(0)}.wp-mail-smtp-plugin-recommendation-page .loader,.wp-mail-smtp-plugin-recommendation-page .loader:after{border-radius:50%;width:50px;height:50px;display:block}.wp-mail-smtp-plugin-recommendation-page .screenshot{justify-content:space-between;align-items:center;gap:25px;display:flex}.wp-mail-smtp-plugin-recommendation-page .screenshot .cont{background-color:#fff;border-radius:4px;min-width:315px;margin-top:0;padding:5px;display:inline-block;position:relative;box-shadow:0 2px 4px #00000012}.wp-mail-smtp-plugin-recommendation-page .screenshot .cont img{max-width:100%;display:block}.wp-mail-smtp-plugin-recommendation-page .screenshot .cont .hover{opacity:0;background-color:#00000026;background-image:url(../images/icons/zoom.svg);background-position:50%;background-repeat:no-repeat;background-size:50px;border:5px solid #fff;width:100%;height:100%;transition:all .3s;position:absolute;top:0;left:0}.wp-mail-smtp-plugin-recommendation-page .screenshot .cont:hover .hover{opacity:1;transition:all .3s}.wp-mail-smtp-plugin-recommendation-page .screenshot ul{margin:0}.wp-mail-smtp-plugin-recommendation-page .screenshot ul li{color:#50575e;text-align:start;align-items:flex-start;gap:10px;margin-bottom:20px;padding:0;font-size:15px;line-height:18px;list-style:none;display:flex}.wp-mail-smtp-plugin-recommendation-page .screenshot ul li span{margin-top:2px}.wp-mail-smtp-plugin-recommendation-page .screenshot ul li:first-child{margin-top:0}.wp-mail-smtp-plugin-recommendation-page .step{background-color:#f6f6f6;border:1px solid #dcdcde;border-radius:6px;align-items:center;margin:0 0 50px;display:flex;overflow:hidden;box-shadow:0 2px 4px #00000012}.wp-mail-smtp-plugin-recommendation-page .step:last-child{margin-bottom:36px}.wp-mail-smtp-plugin-recommendation-page .step>*{vertical-align:middle}.wp-mail-smtp-plugin-recommendation-page .step .num{text-align:center;width:100px;height:50px;display:inline-block;position:relative}.wp-mail-smtp-plugin-recommendation-page .step .loader{opacity:1;margin-top:-54px;transition:all .3s}.wp-mail-smtp-plugin-recommendation-page .step div{text-align:start;background-color:#fff;border-left:1px solid #dcdcde;width:calc(100% - 100px);padding:25px;display:inline-block}.wp-mail-smtp-plugin-recommendation-page .step h2{color:#2c3338;margin:0 0 10px;font-size:24px;font-weight:500;line-height:normal}.wp-mail-smtp-plugin-recommendation-page .step p{color:#50575e;margin:0 0 15px;font-size:16px;line-height:22px}.wp-mail-smtp-plugin-recommendation-page .step .error{color:#d63638;margin-bottom:0;font-size:14px}.wp-mail-smtp-plugin-recommendation-page .step .button{text-align:center;border-radius:4px;min-width:180px;height:auto;padding:11px 17px;font-size:16px;font-weight:500;line-height:1.2;transition:all .3s}.wp-mail-smtp-plugin-recommendation-page .step .button.button-primary{background-color:var(--wpms-color-link);border-color:var(--wpms-color-link);color:#fff}.wp-mail-smtp-plugin-recommendation-page .step .button.button-primary:hover,.wp-mail-smtp-plugin-recommendation-page .step .button.button-primary:focus{color:#fff;background-color:#04558a;border-color:#04558a}.wp-mail-smtp-plugin-recommendation-page .step .button.button-primary:focus{border-color:#fff;box-shadow:0 0 0 2px #04558a}.wp-mail-smtp-plugin-recommendation-page .step .button.grey{box-shadow:none;text-shadow:none;color:#50575e;background:#f6f6f6;border:1px solid #8c8f94}.wp-mail-smtp-plugin-recommendation-page .step .button.disabled{cursor:default}.wp-mail-smtp-plugin-recommendation-page .step .link{text-decoration:none}.wp-mail-smtp-plugin-recommendation-page .step .hidden{opacity:0;transition:all .3s}.wp-mail-smtp-plugin-recommendation-page .grey{opacity:.5}@media (max-width:767px){.wp-mail-smtp-plugin-recommendation-page{width:auto}.wp-mail-smtp-plugin-recommendation-page .top img{max-width:245px}.wp-mail-smtp-plugin-recommendation-page .screenshot{flex-direction:column}.wp-mail-smtp-plugin-recommendation-page .screenshot .cont{min-width:auto;margin:auto}.wp-mail-smtp-plugin-recommendation-page .screenshot ul{max-width:none;margin-left:0;display:block}.wp-mail-smtp-plugin-recommendation-page .step{flex-direction:column}.wp-mail-smtp-plugin-recommendation-page .step .num{width:100%;height:auto;margin:15px 0;display:block}.wp-mail-smtp-plugin-recommendation-page .step div{border-top:1px solid #dcdcde;border-left:none;width:100%;display:block}.wp-mail-smtp-plugin-recommendation-page .step div .button{text-wrap:auto}}@keyframes wp-mail-smtp-plugin-recommendation-page-load{0%{transform:rotate(0)}to{transform:rotate(360deg)}}#screen-meta-links,#screen-meta{display:none}#wp-mail-smtp-header-temp{z-index:99;margin:0;position:absolute;top:-1px;left:20px;right:0}#wp-mail-smtp-header{border-top:3px solid #e27730;padding:24px 20px}#wp-mail-smtp-header img{max-width:242px;margin:0;display:block}@media (max-width:782px){#wp-mail-smtp-header img{max-width:200px}}.wp-mail-smtp-page{color:#2c3337}.wp-mail-smtp-page a{color:#2171b1}.wp-mail-smtp-page a:hover,.wp-mail-smtp-page a:active{color:#135e96}#wp-mail-smtp{margin:0}#wp-mail-smtp .wp-mail-smtp-hide{display:none}#wp-mail-smtp .wp-mail-smtp-page-title{background-color:#fff;min-height:58px;margin:0 0 20px;padding:0 20px;font-size:14px}#wp-mail-smtp .wp-mail-smtp-page-title a.tab{box-shadow:none;color:#666;border-bottom:3px solid #fff;margin-right:30px;padding:20px 0 17px;text-decoration:none;display:inline-block}#wp-mail-smtp .wp-mail-smtp-page-title a.tab.active{border-color:#e27730}#wp-mail-smtp .wp-mail-smtp-page-title a.tab:hover{border-color:#999}#wp-mail-smtp .wp-mail-smtp-page-title a.tab.wp-mail-smtp-tab-get-pro{float:right;color:#e27730;margin-right:0}#wp-mail-smtp .wp-mail-smtp-page-title a.action{color:#fff;vertical-align:top;min-height:30px;margin:14px 0 14px 10px;font-weight:600;line-height:28px}@media (max-width:600px){#wp-mail-smtp .wp-mail-smtp-page-title a.action{margin-left:0}}#wp-mail-smtp .wp-mail-smtp-page-title .page-title{background-color:#fff;margin:0;padding:20px 20px 20px 0;font-size:23px;display:inline-block}#wp-mail-smtp .wp-mail-smtp-page-content{padding:0 20px}#wp-mail-smtp .wp-mail-smtp-page-content *,#wp-mail-smtp .wp-mail-smtp-page-content :before,#wp-mail-smtp .wp-mail-smtp-page-content :after{box-sizing:border-box}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-clear:before{content:" ";display:table}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-clear:after{clear:both;content:" ";display:table}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row{border-bottom:1px solid #e4e4e4;padding:30px 0;font-size:14px;line-height:1.3}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row:first-of-type{padding-top:10px!important}@media (max-width:781px){#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row{padding:20px 0}}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row.inactive{display:none}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row .wp-mail-smtp-setting-mid-row-sep{background:#e4e4e4;border:0;height:1px;margin:15px 0}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row.section-heading{border-bottom:none;padding:20px 0 10px}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row.section-heading.no-desc h2,#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row.section-heading.no-desc h4{margin:0}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row.section-heading.wp-mail-smtp-section-heading--has-divider{border-bottom:1px solid #e4e4e4;padding:20px 0 30px}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row.section-heading .wp-mail-smtp-setting-field{max-width:1005px;margin:0}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row.section-heading .desc{font-style:normal}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row.wp-mail-smtp-setting-row-checkbox input[type=checkbox]{float:left;margin:1px 0 0}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row.wp-mail-smtp-setting-row-checkbox input[type=checkbox]+label{vertical-align:baseline;margin:0 0 0 8px}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row.wp-mail-smtp-setting-row-checkbox .desc{margin:0 0 0 30px}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row.wp-mail-smtp-setting-row-checkbox input[type=checkbox]+label+.desc{margin:8px 0 0}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row.wp-mail-smtp-setting-row-text .wp-mail-smtp-setting-label,#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row.wp-mail-smtp-setting-row-password .wp-mail-smtp-setting-label,#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row.wp-mail-smtp-setting-row-number .wp-mail-smtp-setting-label,#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row.wp-mail-smtp-setting-row-email .wp-mail-smtp-setting-label,#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row.wp-mail-smtp-setting-row-select .wp-mail-smtp-setting-label{padding-top:8px}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row.wp-mail-smtp-setting-row-radio .wp-mail-smtp-setting-field input[type=radio]{margin:-3px 10px 0 0}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row.wp-mail-smtp-setting-row-radio .wp-mail-smtp-setting-field label{margin-right:30px;display:inline-block}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row.wp-mail-smtp-setting-row-no-border{border-bottom:none}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row.wp-mail-smtp-setting-row-no-padding{padding:0}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row.wp-mail-smtp-setting-row-license_key .wp-mail-smtp-setting-field p:first-child{margin-top:0}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row.wp-mail-smtp-setting-row-mailer{padding-bottom:20px}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row.wp-mail-smtp-setting-row-mailer .wp-mail-smtp-mailers .wp-mail-smtp-mailer{width:140px;margin-bottom:12px;margin-right:12px;display:inline-block;position:relative}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row.wp-mail-smtp-setting-row-mailer .wp-mail-smtp-mailers .wp-mail-smtp-mailer .wp-mail-smtp-mailer-image{text-align:center;cursor:pointer;background:#fff;border:2px solid #e5e5e5;border-radius:4px;height:76px;margin-bottom:10px;transition:all .2s ease-in-out;position:relative}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row.wp-mail-smtp-setting-row-mailer .wp-mail-smtp-mailers .wp-mail-smtp-mailer .wp-mail-smtp-mailer-image.is-recommended{background-image:url(../images/recommended.svg);background-position:right -2px top;background-repeat:no-repeat;background-size:60%}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row.wp-mail-smtp-setting-row-mailer .wp-mail-smtp-mailers .wp-mail-smtp-mailer .wp-mail-smtp-mailer-image img{opacity:.6;max-width:80%;max-height:40px;transition:all .2s ease-in-out;display:block;position:relative;top:50%;left:50%;transform:translate(-50%,-50%)}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row.wp-mail-smtp-setting-row-mailer .wp-mail-smtp-mailers .wp-mail-smtp-mailer.wp-mail-smtp-mailer-smtp .wp-mail-smtp-mailer-image img{max-height:30px}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row.wp-mail-smtp-setting-row-mailer .wp-mail-smtp-mailers .wp-mail-smtp-mailer.active .wp-mail-smtp-mailer-image{border-color:#e27730}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row.wp-mail-smtp-setting-row-mailer .wp-mail-smtp-mailers .wp-mail-smtp-mailer.active .wp-mail-smtp-mailer-image img{opacity:1}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row.wp-mail-smtp-setting-row-mailer .wp-mail-smtp-mailers .wp-mail-smtp-mailer:hover .wp-mail-smtp-mailer-image{border-color:#ccc}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row.wp-mail-smtp-setting-row-mailer .wp-mail-smtp-mailers .wp-mail-smtp-mailer:hover .wp-mail-smtp-mailer-image img{opacity:1}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row.wp-mail-smtp-setting-row-alert{padding-top:20px;padding-bottom:25px}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row.wp-mail-smtp-setting-row-alert .wp-mail-smtp-setting-row{border:none}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row.wp-mail-smtp-setting-row-alert .section-heading{margin-bottom:25px}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row.wp-mail-smtp-setting-row-alert .section-heading h3{margin-bottom:10px;font-size:18px;font-weight:500;line-height:22px}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row.wp-mail-smtp-setting-row-alert .section-heading .notice{margin-top:20px}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row.wp-mail-smtp-setting-row-alert .section-heading .notice p{margin:6.5px 0;font-size:13px}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row.wp-mail-smtp-setting-row-alert .wp-mail-smtp-setting-row{padding:0!important}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row.wp-mail-smtp-setting-row-alert .wp-mail-smtp-setting-row+.wp-mail-smtp-setting-row{margin-top:20px}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row.wp-mail-smtp-setting-row-alert .wp-mail-smtp-setting-label label{font-weight:500}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row.wp-mail-smtp-setting-row-alert .dashicons-trash{color:#a7aaad;cursor:pointer;transition:all .2s;position:relative;top:6px}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row.wp-mail-smtp-setting-row-alert .dashicons-trash:hover{color:#d63638}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row#wp-mail-smtp-setting-row-alerts-twilio_sms .wp-mail-smtp-setting-row-alert-connection-options{position:relative}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row#wp-mail-smtp-setting-row-alerts-twilio_sms .wp-mail-smtp-setting-row-alert-connection-options+.wp-mail-smtp-setting-row-alert-connection-options:before{content:"";background-color:#dcdcde;max-width:605px;height:1px;margin-bottom:20px;display:block}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row#wp-mail-smtp-setting-row-alerts-whatsapp .wp-mail-smtp-setting-row-alert-connection-options{position:relative}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row#wp-mail-smtp-setting-row-alerts-whatsapp .wp-mail-smtp-setting-row-alert-connection-options+.wp-mail-smtp-setting-row-alert-connection-options:before{content:"";background-color:#dcdcde;max-width:605px;height:1px;margin-bottom:20px;display:block}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row#wp-mail-smtp-setting-row-alerts-whatsapp .wp-mail-smtp-setting-row-alert-connection-options .wp-mail-smtp-setting-row:first-child .wp-mail-smtp-setting-field{position:relative}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row#wp-mail-smtp-setting-row-alerts-whatsapp .wp-mail-smtp-setting-row-alert-connection-options .wp-mail-smtp-setting-row:first-child .wp-mail-smtp-setting-field .js-wp-mail-smtp-setting-alert-remove-connection{position:absolute;top:8px;left:415px}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row.wp-mail-smtp-setting-row-sendlayer-quick-connect-from-email .wp-mail-smtp-sendlayer-from-email-wrap{flex-wrap:wrap;align-items:center;gap:10px;display:flex}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row.wp-mail-smtp-setting-row-sendlayer-quick-connect-from-email .wp-mail-smtp-sendlayer-from-email-wrap input[type=text]{max-width:165px;margin-right:0}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row.wp-mail-smtp-setting-row-sendlayer-quick-connect-from-email .wp-mail-smtp-sendlayer-from-email-domain{color:#3a3a56;font-size:15px}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row.wp-mail-smtp-setting-row-sendlayer-quick-connect-from-email .desc{max-width:462px}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row h2,#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row h4{color:#2c3337;margin:0 0 6px;font-size:20px;font-weight:600}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row h2{margin-bottom:15px}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row h3{color:#2c3337;margin:0 0 20px;font-size:24px;font-weight:600}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row h5{color:#2c3337;margin:0 0 20px;font-size:16px;font-weight:600}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row p{margin:12px 0 0;font-size:14px;line-height:1.5em}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row p:first-of-type{margin:8px 0 0}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row p.desc{color:#50575e;margin:10px 0 0;font-style:italic}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row p.desc+.desc{font-style:normal}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row p.buttonned{margin-top:30px}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row p.inline-notice{box-sizing:border-box;background:#fff;border-left:4px solid #0000;margin:5px 0 15px;box-shadow:0 1px 1px #0000001a}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row p.inline-notice.inline-error{border-color:#d63638;margin-bottom:5px;padding:10px}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row p.inline-notice.inline-info{border-color:#2171b1;margin-bottom:5px;padding:10px}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row p.inline-notice.inline-edu-notice{border-color:#809eb0;padding:10px;line-height:1.5em}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row p.inline-notice a.wp-mail-smtp-mailer-notice-dismiss{float:right;color:#999da1;margin:0 0 10px 10px;text-decoration:none}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row p.inline-notice a.wp-mail-smtp-mailer-notice-dismiss:hover{color:#666a6e}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row ul{margin:8px 0 0}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row ul.list li{margin-left:20px;list-style-type:disc}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row table.actions-list td,#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row table.actions-list th{text-align:left;padding:5px 5px 5px 0}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row table.actions-list td.email{padding-right:2em}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row table.actions-list td.status{width:100px}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row table.actions-list td.actions a{border-bottom:1px solid;margin-right:5px;text-decoration:none;display:inline-block}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row table.actions-list td.actions a[class*=delete],#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row table.actions-list td.actions a[class*=error]{color:#d63638}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row table.actions-list td.actions a[class*=delete]:hover,#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row table.actions-list td.actions a[class*=delete]:active,#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row table.actions-list td.actions a[class*=delete]:focus,#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row table.actions-list td.actions a[class*=error]:hover,#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row table.actions-list td.actions a[class*=error]:active,#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row table.actions-list td.actions a[class*=error]:focus{color:#8a1c1d}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row input[type=text],#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row input[type=email],#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row input[type=number],#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row input[type=password],#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row select{box-shadow:none;color:#2c3337;vertical-align:middle;background-color:#fff;border:1px solid #8d8f95;border-radius:4px;width:400px;min-height:36px;margin:0 10px 0 0;padding:7px 12px;line-height:1.3;display:inline-block}@media (max-width:959px){#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row input[type=text],#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row input[type=email],#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row input[type=number],#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row input[type=password],#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row select{width:300px}}@media (max-width:599px){#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row input[type=text],#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row input[type=email],#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row input[type=number],#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row input[type=password],#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row select{width:100%;max-width:unset;margin-right:0}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row input[type=text]~button,#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row input[type=text]~.wp-mail-smtp-btn,#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row input[type=email]~button,#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row input[type=email]~.wp-mail-smtp-btn,#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row input[type=number]~button,#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row input[type=number]~.wp-mail-smtp-btn,#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row input[type=password]~button,#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row input[type=password]~.wp-mail-smtp-btn,#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row select~button,#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row select~.wp-mail-smtp-btn{margin-top:8px}}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row input[type=text][readonly],#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row input[type=email][readonly],#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row input[type=number][readonly],#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row input[type=password][readonly],#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row select[readonly]{background-color:#f9f9f9}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row input[type=text].small-text,#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row input[type=email].small-text,#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row input[type=number].small-text,#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row input[type=password].small-text,#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row select.small-text{width:75px}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row input[type=text]:focus,#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row input[type=email]:focus,#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row input[type=number]:focus,#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row input[type=password]:focus,#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row select:focus{border:1px solid #016aab;outline:none;box-shadow:0 0 0 1px #016aab}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row input[type=text]:disabled,#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row input[type=email]:disabled,#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row input[type=number]:disabled,#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row input[type=password]:disabled,#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row select:disabled{opacity:.6}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row input[type=checkbox]:focus,#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row input[type=radio]:focus{border:1px solid #016aab;outline:none;box-shadow:0 0 0 1px #016aab}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row .wp-mail-smtp-dashicons-yes-alt-green.wp-mail-smtp-animate{animation:1s linear wp-mail-smtp-fade-in-stop-out}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-row .wp-mail-smtp-dashicons-yes-alt-green.wp-mail-smtp-success{color:#00a32a}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-group{border-bottom:1px solid #e4e4e4;padding:0 0 30px}@media (max-width:781px){#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-group{padding:0 0 20px}}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-group:first-of-type{padding-top:10px}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-group .wp-mail-smtp-setting-row{border:none;padding:30px 0 0!important}@media (max-width:781px){#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-group .wp-mail-smtp-setting-row{padding:20px 0 0}}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-label{float:left;width:205px;padding:0 20px 0 0;display:block}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-label-with-tooltip{display:flex}@media (max-width:781px){#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-label{float:none;width:100%;padding-bottom:15px}}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-label label{font-weight:600;display:block}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-field{max-width:800px;margin:0 0 0 205px;display:block}@media (max-width:781px){#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-field{margin:0}}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-field hr{margin:20px 0}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-field .wp-mail-smtp-setting-field-row{flex-wrap:wrap;display:flex}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-field .wp-mail-smtp-setting-field-row>.wp-mail-smtp-btn+.wp-mail-smtp-btn{margin-left:10px}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-field .wp-mail-smtp-setting-status-label{color:#bd8600;background-color:#fff4cd;border-radius:4px;align-items:center;padding:4px 8px;font-size:12px;display:inline-flex}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-field .wp-mail-smtp-setting-status-label.wp-mail-smtp-setting-status-label-approved{color:#008a20;background-color:#cefad4}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-field .wp-mail-smtp-setting-status-label.wp-mail-smtp-setting-status-label-not-found,#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-field .wp-mail-smtp-setting-status-label.wp-mail-smtp-setting-status-label-rejected{color:#d63638;background-color:#ffe2e4}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-field .wp-mail-smtp-setting-status-label img{width:auto;height:12px;margin-left:4px}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-field .wp-mail-smtp-whatsapp-recheck-status{color:#2271b1;align-items:center;margin-left:5px;margin-right:5px;padding:7px;font-size:12px;text-decoration:none;display:inline-flex}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-field .wp-mail-smtp-whatsapp-recheck-status img{width:auto;height:12px;margin-left:4px;margin-right:4px}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-field .wp-mail-smtp-whatsapp-recheck-status:hover{color:#135e96}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-field .wp-mail-smtp-whatsapp-recheck-status:focus{box-shadow:none;outline:none}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-setting-field .wp-mail-smtp-whatsapp-recheck-status.loading img{animation:2s linear infinite wp-mail-smtp-spin}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-submit{margin:0;padding:25px 0}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-submit .help-text{vertical-align:middle;margin-left:10px}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-mailer-options .wp-mail-smtp-mailer-option .wp-mail-smtp-setting-row.section-heading{padding:20px 0!important}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-mailer-options .wp-mail-smtp-mailer-option.wp-mail-smtp-mailer-option-sendlayer .wp-mail-smtp-setting-row.section-heading{padding-bottom:0!important}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-mailer-options .wp-mail-smtp-mailer-option.wp-mail-smtp-mailer-option-sendlayer .wp-mail-smtp-sendlayer-connect-btn{align-items:center;gap:10px;display:inline-flex}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-mailer-options .wp-mail-smtp-mailer-option.wp-mail-smtp-mailer-option-sendlayer .wp-mail-smtp-sendlayer-connect-badge{white-space:nowrap;color:#50575e;opacity:.9;background-color:#fff;border-radius:53px;align-items:center;gap:6px;padding:2px 8px;font-size:12px;line-height:1.4;display:inline-flex}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-mailer-options .wp-mail-smtp-mailer-option.wp-mail-smtp-mailer-option-sendlayer .wp-mail-smtp-sendlayer-connect-badge svg{flex-shrink:0}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-mailer-options .wp-mail-smtp-mailer-option.wp-mail-smtp-mailer-option-sendlayer .wp-mail-smtp-sendlayer-domain-value{align-items:center;gap:10px;min-height:36px;display:flex;margin-top:0!important}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-mailer-options .wp-mail-smtp-mailer-option.wp-mail-smtp-mailer-option-sendlayer .wp-mail-smtp-sendlayer-change-domain{text-decoration:underline}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-mailer-options .wp-mail-smtp-mailer-option.wp-mail-smtp-mailer-option-sendlayer .wp-mail-smtp-setting-row-sendlayer-domain .desc{max-width:510px}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-mailer-options .wp-mail-smtp-mailer-option blockquote{color:#666;background:#e5e5e5;border-radius:4px;width:100%;max-width:1005px;margin:20px 0;padding:15px;font-size:14px}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-mailer-options .wp-mail-smtp-connected-row{align-items:center;display:flex}@media (max-width:781px){#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-mailer-options .wp-mail-smtp-connected-row{flex-direction:column;align-items:flex-start}}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-mailer-options .wp-mail-smtp-connected-row__info{margin-left:30px}@media (max-width:781px){#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-mailer-options .wp-mail-smtp-connected-row__info{margin-top:10px;margin-left:0}}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-mailer-options p.inline-notice{margin-top:10px}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-mailer-options p.inline-notice:first-child{margin-top:0}#wp-mail-smtp .wp-mail-smtp-page-content .wp-mail-smtp-mailer-options p.inline-notice:last-child{margin-bottom:0}#wp-mail-smtp .wp-mail-smtp-page-content.wp-mail-smtp-page-general p{margin:0}#wp-mail-smtp .wp-mail-smtp-page-content .notice-inline{background:#fff;border-left:4px solid #fff;margin:5px 0 15px;padding:1px 12px;box-shadow:0 1px 1px #0000001a}#wp-mail-smtp .wp-mail-smtp-page-content .notice-inline.notice-success{border-left-color:#00a32a}#wp-mail-smtp .wp-mail-smtp-page-content .notice-inline.notice-warning{border-left-color:#ffb900}#wp-mail-smtp .wp-mail-smtp-page-content .notice-inline.notice-error{border-left-color:#d63638}#wp-mail-smtp .wp-mail-smtp-page-content .notice-inline.notice-info{border-left-color:#00a0d2}#wp-mail-smtp .wp-mail-smtp-page-content .notice p,#wp-mail-smtp .wp-mail-smtp-page-content .notice-inline p,#wp-mail-smtp .wp-mail-smtp-page-content .notice p:first-of-type,#wp-mail-smtp .wp-mail-smtp-page-content .notice-inline p:first-of-type{margin:.5em 0;padding:2px}#wp-mail-smtp .wp-mail-smtp-page-content pre{white-space:pre-line}#wp-mail-smtp .wp-mail-smtp-page-content.active{display:block}#wp-mail-smtp .wp-mail-smtp-page-content .connected-as{margin-left:30px}@media (max-width:599px){#wp-mail-smtp .wp-mail-smtp-page-content .connected-as{margin-top:8px;margin-left:0;display:block}}#wp-mail-smtp .wp-mail-smtp-admin-columns>div[class*=-column-]{float:left}#wp-mail-smtp .wp-mail-smtp-admin-columns .wp-mail-smtp-admin-column-20{width:20%}#wp-mail-smtp .wp-mail-smtp-admin-columns .wp-mail-smtp-admin-column-33{width:33.3333%}#wp-mail-smtp .wp-mail-smtp-admin-columns .wp-mail-smtp-admin-column-40{width:40%}#wp-mail-smtp .wp-mail-smtp-admin-columns .wp-mail-smtp-admin-column-50{width:50%}#wp-mail-smtp .wp-mail-smtp-admin-columns .wp-mail-smtp-admin-column-60{width:60%}#wp-mail-smtp .wp-mail-smtp-admin-columns .wp-mail-smtp-admin-column-80{width:80%}#wp-mail-smtp .wp-mail-smtp-admin-columns .wp-mail-smtp-admin-column-last{float:right!important}#wp-mail-smtp .wp-mail-smtp-admin-columns:after{content:"";clear:both;display:table}#wp-mail-smtp .notice blockquote{margin-left:0;padding:2px}#wp-mail-smtp .notice blockquote pre{white-space:pre-line;margin:0}#wp-mail-smtp #wp-mail-smtp-reset-filter{clear:both;text-align:center;background:#fff;border:1px solid #ccd0d4;margin:20px 0;padding:10px;font-size:15px;box-shadow:0 1px 1px #0000000a}#wp-mail-smtp #wp-mail-smtp-reset-filter a{text-decoration:none}#wp-mail-smtp #wp-mail-smtp-reset-filter .reset{cursor:pointer;color:#a6a6a6;width:18px;height:18px;font-size:15px;line-height:18px}#wp-mail-smtp #wp-mail-smtp-reset-filter .reset:focus,#wp-mail-smtp #wp-mail-smtp-reset-filter .reset:hover{color:#d63638}@media (min-width:783px){#wp-mail-smtp .wp-mail-smtp-tab-tools-debug-events th#event{width:130px}#wp-mail-smtp .wp-mail-smtp-tab-tools-debug-events th#type{width:80px}#wp-mail-smtp .wp-mail-smtp-tab-tools-debug-events th#initiator{width:20%}#wp-mail-smtp .wp-mail-smtp-tab-tools-debug-events th#created_at{width:200px}}#wp-mail-smtp .wp-mail-smtp-tab-misc #wp-mail-smtp-setting-row-rate_limit_periods input[type=number]{width:100px}#wp-mail-smtp .wp-mail-smtp-tab-misc #wp-mail-smtp-setting-row-uninstall .wp-mail-smtp-danger{color:#d63638;font-weight:600}@media (max-width:782px){#wp-mail-smtp .wp-mail-smtp-filter-date{float:none;flex-direction:column;align-items:flex-start;width:100%;max-width:600px;margin-bottom:10px;padding-right:0;display:flex!important}}#wp-mail-smtp .wp-mail-smtp-filter-date__control{float:left;vertical-align:middle;background-color:#fff;max-width:210px;min-height:30px;margin:0 5px 0 0}@media (max-width:782px){#wp-mail-smtp .wp-mail-smtp-filter-date__control{flex-grow:1;width:100%;max-width:none;min-height:40px;margin-bottom:5px;margin-right:0}}#wp-mail-smtp .wp-mail-smtp-filter-date__btn{vertical-align:top;margin:0}#wp-mail-smtp .wp-mail-smtp-sign-in-btn{cursor:pointer;color:#fff;background-color:#4285f4;border-radius:2px;align-items:center;text-decoration:none;transition:all .3s ease-in-out;display:inline-flex;box-shadow:0 2px 4px #00000040}#wp-mail-smtp .wp-mail-smtp-sign-in-btn:hover{box-shadow:0 0 3px 3px #4285f44d}#wp-mail-smtp .wp-mail-smtp-sign-in-btn:active{background:#3367d6}#wp-mail-smtp .wp-mail-smtp-sign-in-btn--disabled{pointer-events:none;box-shadow:none;color:#7f7f7f;background-color:#d4d3d3}#wp-mail-smtp .wp-mail-smtp-sign-in-btn--disabled .wp-mail-smtp-sign-in-icon__border,#wp-mail-smtp .wp-mail-smtp-sign-in-btn--disabled .wp-mail-smtp-sign-in-icon__bg{fill:#d4d3d3}#wp-mail-smtp .wp-mail-smtp-sign-in-btn--disabled .wp-mail-smtp-sign-in-icon__symbol{fill:#7f7f7f}#wp-mail-smtp .wp-mail-smtp-sign-in-btn__icon{border-radius:2px;overflow:hidden}#wp-mail-smtp .wp-mail-smtp-sign-in-btn__icon svg{margin:-3px;display:block}#wp-mail-smtp .wp-mail-smtp-sign-in-btn__text{margin:0 10px;font-size:14px;font-weight:600}#wp-mail-smtp .wpms-email-sending-errors-banner{--wpms-esd-color-dismiss:#a7aaad;--wpms-esd-color-surface-alt:#f6f7f7;--wpms-esd-color-tooltip-bg:#3c434a;--wpms-esd-font-title-size:18px;--wpms-esd-font-body-size:14px;--wpms-esd-font-line-height:20px;--wpms-esd-font-tooltip-size:12px;margin:0 0 var(--wpms-spacing-md);padding:var(--wpms-spacing-md);background:var(--wpms-color-surface-background-white);border:1px solid var(--wpms-color-surface-border);border-radius:var(--wpms-radius-table);color:var(--wpms-color-tertiary);font-size:var(--wpms-esd-font-body-size);line-height:var(--wpms-esd-font-line-height);position:relative}#wp-mail-smtp .wpms-email-sending-errors-banner--failed-severity .wpms-email-sending-errors-banner__icon{color:var(--wpms-color-error)}#wp-mail-smtp .wpms-email-sending-errors-banner--warning-severity .wpms-email-sending-errors-banner__icon{color:var(--wpms-color-utility-yellow-50)}#wp-mail-smtp .wpms-email-sending-errors-banner__header{align-items:center;gap:var(--wpms-spacing-sm);margin:0 0 var(--wpms-spacing-sm);padding-right:32px;display:flex}#wp-mail-smtp .wpms-email-sending-errors-banner__icon{flex-shrink:0;justify-content:center;align-items:center;width:16px;height:16px;line-height:1;display:inline-flex}#wp-mail-smtp .wpms-email-sending-errors-banner__title{min-width:0;font-size:var(--wpms-esd-font-title-size);color:var(--wpms-color-primary);flex:auto;margin:0;font-weight:500;line-height:1.2}#wp-mail-smtp .wpms-email-sending-errors-banner__title strong{font-weight:600}#wp-mail-smtp .wpms-email-sending-errors-banner__body{gap:var(--wpms-spacing-md);flex-direction:column;display:flex}#wp-mail-smtp .wpms-email-sending-errors-banner__body p{font-size:var(--wpms-esd-font-body-size);line-height:var(--wpms-esd-font-line-height);color:var(--wpms-color-tertiary);margin:0}#wp-mail-smtp .wpms-email-sending-errors-banner__body ul{color:var(--wpms-color-tertiary);margin:0;padding-left:21px;list-style:outside}#wp-mail-smtp .wpms-email-sending-errors-banner__body ul li{font-size:var(--wpms-esd-font-body-size);line-height:var(--wpms-esd-font-line-height);margin:0}#wp-mail-smtp .wpms-email-sending-errors-banner__body p a{color:var(--wpms-color-link);font-weight:500;text-decoration:underline}#wp-mail-smtp .wpms-email-sending-errors-banner__body p a:hover,#wp-mail-smtp .wpms-email-sending-errors-banner__body p a:focus{color:#0a4b78;text-decoration:underline}#wp-mail-smtp .wpms-email-sending-errors-banner__diagnostic{background:var(--wpms-esd-color-surface-alt);border:1px solid var(--wpms-color-surface-divider);border-radius:var(--wpms-radius-table);color:var(--wpms-color-secondary);white-space:pre-wrap;word-break:break-word;margin:0;padding:12px 14px;font-family:Consolas,Monaco,Courier New,monospace;font-size:12px;line-height:1.5;overflow-x:auto}#wp-mail-smtp .wpms-email-sending-errors-banner__actions{align-items:center;gap:var(--wpms-spacing-sm);flex-wrap:wrap;display:flex}#wp-mail-smtp .wpms-email-sending-errors-banner__actions .wp-mail-smtp-btn+.wp-mail-smtp-btn{margin-left:0}#wp-mail-smtp .wpms-email-sending-errors-banner__actions .wp-mail-smtp-btn-link:hover,#wp-mail-smtp .wpms-email-sending-errors-banner__actions .wp-mail-smtp-btn-link:active,#wp-mail-smtp .wpms-email-sending-errors-banner__actions .wp-mail-smtp-btn-link:focus{color:var(--wpms-color-primary);text-decoration:underline}#wp-mail-smtp .wpms-email-sending-errors-banner__error-log-toggle{min-width:129px}#wp-mail-smtp .wpms-email-sending-errors-banner__error-log{margin:0;position:relative}#wp-mail-smtp .wpms-email-sending-errors-banner__help{padding-top:var(--wpms-spacing-md);border-top:1px solid var(--wpms-color-surface-divider);background:0 0;flex-direction:column;gap:5px;margin:0;display:flex}#wp-mail-smtp .wpms-email-sending-errors-banner__help ul{margin:0;padding-left:21px;list-style:outside}#wp-mail-smtp .wpms-email-sending-errors-banner__help ul li{font-size:var(--wpms-esd-font-body-size);line-height:var(--wpms-esd-font-line-height);color:var(--wpms-color-tertiary);margin:0}#wp-mail-smtp .wpms-email-sending-errors-banner__help ul li+li{margin-top:5px}#wp-mail-smtp .wpms-email-sending-errors-banner__help a{color:var(--wpms-color-link);font-weight:500;text-decoration:underline}#wp-mail-smtp .wpms-email-sending-errors-banner__help a:hover,#wp-mail-smtp .wpms-email-sending-errors-banner__help a:focus{color:#0a4b78;text-decoration:underline}#wp-mail-smtp .wpms-email-sending-errors-banner__help-title{align-items:center;gap:var(--wpms-spacing-sm);font-size:var(--wpms-esd-font-body-size);font-weight:500;line-height:var(--wpms-esd-font-line-height);display:flex;color:var(--wpms-color-primary)!important;margin:0 0 5px!important}#wp-mail-smtp .wpms-email-sending-errors-error-log{border:1px solid var(--wpms-color-surface-border);background:var(--wpms-color-surface-background-white);box-shadow:var(--wpms-shadow-subtle);margin:0;position:relative}#wp-mail-smtp .wpms-email-sending-errors-error-log__inner{border-left:4px solid var(--wpms-color-error);padding:10px 12px}#wp-mail-smtp .wpms-email-sending-errors-error-log pre{color:#2c3337;margin:0;font-size:13px}#wp-mail-smtp .wpms-email-sending-errors-error-log__copy-icon{width:20px;height:20px;color:var(--wpms-esd-color-dismiss);cursor:pointer;background:0 0;border:0;border-radius:3px;justify-content:center;align-items:center;padding:0;line-height:0;display:inline-flex;position:absolute;top:10px;right:10px}#wp-mail-smtp .wpms-email-sending-errors-error-log__copy-icon:hover,#wp-mail-smtp .wpms-email-sending-errors-error-log__copy-icon:focus{color:var(--wpms-color-secondary);outline:none}#wp-mail-smtp .wpms-email-sending-errors-error-log__copy-icon:focus-visible{box-shadow:0 0 0 2px #056aab59}#wp-mail-smtp .wpms-email-sending-errors-error-log__copy-icon-default,#wp-mail-smtp .wpms-email-sending-errors-error-log__copy-icon-done{justify-content:center;align-items:center;line-height:0;display:inline-flex}#wp-mail-smtp .wpms-email-sending-errors-error-log__copy-icon-default[hidden],#wp-mail-smtp .wpms-email-sending-errors-error-log__copy-icon-done[hidden]{display:none}#wp-mail-smtp .wpms-email-sending-errors-error-log__copy-tooltip{background:var(--wpms-esd-color-tooltip-bg);color:#fff;font-size:var(--wpms-esd-font-tooltip-size);border-radius:var(--wpms-radius-table);white-space:nowrap;pointer-events:none;z-index:1;padding:5px 10px;line-height:1;position:absolute;top:5px;right:-11px;transform:translateY(-100%)}#wp-mail-smtp .wpms-email-sending-errors-icon{vertical-align:middle;width:16px;height:16px;color:var(--wpms-color-error);justify-content:center;align-items:center;margin-left:10px;line-height:1;display:inline-flex}.wp-mail-smtp-btn{cursor:pointer;text-align:center;vertical-align:middle;white-space:nowrap;text-shadow:none;box-shadow:none;border:0;border-radius:3px;outline:none;margin:0;text-decoration:none;display:inline-block}.wp-mail-smtp-btn .dashicons{width:16px;height:16px;font-size:16px}.wp-mail-smtp-btn+.wp-mail-smtp-btn{margin-left:16px}.wp-mail-smtp-btn:disabled,.wp-mail-smtp-btn-disabled{opacity:.5;cursor:not-allowed}.wp-mail-smtp-btn-disabled{pointer-events:none}.wp-mail-smtp-btn-loading{pointer-events:none;-webkit-user-select:none;user-select:none;position:relative;opacity:1!important;color:#0000!important}.wp-mail-smtp-btn-loading svg{visibility:hidden}.wp-mail-smtp-btn-loading:after{content:"";box-sizing:border-box;border:2px solid #fff;border-radius:32px;width:16px;height:16px;margin-top:-8px;margin-left:-8px;animation:.5s linear infinite wp-mail-smtp-spin;display:block;position:absolute;top:50%;left:50%;border-top-color:#0000!important;border-right-color:#0000!important}.wp-mail-smtp-btn.wp-mail-smtp-btn-sm{border-style:solid;border-width:1px;min-height:30px;padding:6px 10px;font-size:13px;font-weight:500;line-height:16px}.wp-mail-smtp-btn.wp-mail-smtp-btn-md{border-style:solid;border-width:1px;min-height:36px;padding:9px 15px;font-size:14px;font-weight:500;line-height:16px}.wp-mail-smtp-btn.wp-mail-smtp-btn-lg{padding:16px 28px;font-size:16px;font-weight:600}.wp-mail-smtp-btn.wp-mail-smtp-btn-cta-large{border-style:solid;border-width:1px;min-height:44px;padding:11px 17px;font-size:16px;font-weight:500;line-height:18px}.wp-mail-smtp-btn.wp-mail-smtp-btn-upgrade,.wp-mail-smtp-btn.wp-mail-smtp-btn-cta{padding:14px 30px;font-size:16px;font-weight:500;line-height:20px}.wp-mail-smtp-btn.wp-mail-smtp-btn-orange{color:#fff;background-color:#e27730;border-color:#e27730}.wp-mail-smtp-btn.wp-mail-smtp-btn-orange:hover,.wp-mail-smtp-btn.wp-mail-smtp-btn-orange:active,.wp-mail-smtp-btn.wp-mail-smtp-btn-orange:focus{color:#fff;background-color:#b85a1b;border-color:#b85a1b}.wp-mail-smtp-btn.wp-mail-smtp-btn-orange:focus{box-shadow:0 0 0 1px #fff,0 0 0 3px #b85a1b}.wp-mail-smtp-btn.wp-mail-smtp-btn-secondary{color:#fff;background-color:#056aab;border-color:#056aab}.wp-mail-smtp-btn.wp-mail-smtp-btn-secondary:hover,.wp-mail-smtp-btn.wp-mail-smtp-btn-secondary:active,.wp-mail-smtp-btn.wp-mail-smtp-btn-secondary:focus{color:#fff;background-color:#045b92;border-color:#045b92}.wp-mail-smtp-btn.wp-mail-smtp-btn-secondary:focus{box-shadow:0 0 0 1px #fff,0 0 0 3px #045b92}.wp-mail-smtp-btn.wp-mail-smtp-btn-red{color:#fff;background-color:#d63638;border-color:#d63638}.wp-mail-smtp-btn.wp-mail-smtp-btn-red:hover,.wp-mail-smtp-btn.wp-mail-smtp-btn-red:active,.wp-mail-smtp-btn.wp-mail-smtp-btn-red:focus{color:#fff;background-color:#b32d2e;border-color:#b32d2e}.wp-mail-smtp-btn.wp-mail-smtp-btn-red:focus{box-shadow:0 0 0 1px #fff,0 0 0 3px #b32d2e}.wp-mail-smtp-btn.wp-mail-smtp-btn-grey{color:#555;background-color:#f8f8f8;border-color:#999}.wp-mail-smtp-btn.wp-mail-smtp-btn-grey:hover,.wp-mail-smtp-btn.wp-mail-smtp-btn-grey:active,.wp-mail-smtp-btn.wp-mail-smtp-btn-grey:focus{color:#444;background-color:#eee;border-color:#555}.wp-mail-smtp-btn.wp-mail-smtp-btn-light-grey{color:#666;background-color:#f5f5f5;border-color:#ccc}.wp-mail-smtp-btn.wp-mail-smtp-btn-light-grey:hover,.wp-mail-smtp-btn.wp-mail-smtp-btn-light-grey:active,.wp-mail-smtp-btn.wp-mail-smtp-btn-light-grey:focus{color:#444;background-color:#eee}.wp-mail-smtp-btn.wp-mail-smtp-btn-light-grey.wp-mail-smtp-btn-loading:after{border-color:#e27730}.wp-mail-smtp-btn.wp-mail-smtp-btn-link{color:#646970;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none;text-underline-position:from-font;background:0 0;border:0;padding:0;font-size:14px;font-weight:400;line-height:20px;text-decoration:underline}.wp-mail-smtp-btn.wp-mail-smtp-btn-link:hover,.wp-mail-smtp-btn.wp-mail-smtp-btn-link:active,.wp-mail-smtp-btn.wp-mail-smtp-btn-link:focus{color:#1d2327;text-decoration:none}.wp-mail-smtp-btn.wp-mail-smtp-btn-link:focus{box-shadow:none}.wp-mail-smtp-btn.wp-mail-smtp-btn-blueish{color:#fff;background-color:#738e9e;border-color:#738e9e}.wp-mail-smtp-btn.wp-mail-smtp-btn-blueish:hover,.wp-mail-smtp-btn.wp-mail-smtp-btn-blueish:active,.wp-mail-smtp-btn.wp-mail-smtp-btn-blueish:focus{color:#fff;background-color:#395360;border-color:#395360}.wp-mail-smtp-btn.wp-mail-smtp-btn-blueish:focus{box-shadow:0 0 0 1px #fff,0 0 0 3px #395360}.wp-mail-smtp-inline-loader{vertical-align:middle;border:2px solid var(--wpms-color-link);border-top-color:#0000;border-right-color:#0000;border-radius:50%;width:1em;height:1em;margin-left:.5em;animation:.6s linear infinite wp-mail-smtp-spin;display:inline-block}a.wp-mail-smtp-btn.wp-mail-smtp-btn-md{line-height:16px}#wp-mail-smtp-domain-check-details h2{color:#2c3337;margin:28px 0 23px;font-size:20px;font-weight:600;line-height:24px}#wp-mail-smtp-domain-check-details .wp-mail-smtp-domain-check-details-check-list-item{border-top:1px solid #e4e4e4;padding:25px 0;display:flex}#wp-mail-smtp-domain-check-details .wp-mail-smtp-domain-check-details-check-list-item:last-child{padding-bottom:0}#wp-mail-smtp-domain-check-details .wp-mail-smtp-domain-check-details-check-list-item-icon{width:18px;height:16px;margin-right:10px}#wp-mail-smtp-domain-check-details .wp-mail-smtp-domain-check-details-check-list-item-content h3{text-transform:uppercase;color:#2c3337;margin:0 0 4px;font-size:14px;font-weight:500;line-height:17px}#wp-mail-smtp-domain-check-details .wp-mail-smtp-domain-check-details-check-list-item-content p{color:#50575e;margin:0 0 10px;font-size:14px;line-height:20px}#wp-mail-smtp-domain-check-details .wp-mail-smtp-domain-check-details-check-list-item-content p:last-of-type{margin-bottom:0}.wp-mail-smtp-tab-tools-test .wp-mail-smtp-setting-row.section-heading{margin-bottom:22px}.wp-mail-smtp-tab-tools-test .wp-mail-smtp-test-email-resend{border-top:1px solid #e4e4e4;margin-top:22px;padding-top:22px}.wp-mail-smtp-tab-tools-test .wp-mail-smtp-test-email-resend a{box-shadow:none;outline:none;font-size:14px}.wp-mail-smtp-tab-tools-test #email-test-success{text-align:center;margin-top:30px}.wp-mail-smtp-tab-tools-test #email-test-success svg{width:100%;max-width:500px;height:auto;margin:0 auto 22px;display:block}.wp-mail-smtp-tab-tools-test #email-test-success h2{color:#2c3337;margin-top:0;margin-bottom:10px;font-size:24px;font-weight:500;line-height:29px}.wp-mail-smtp-tab-tools-test #email-test-success p{color:#50575e;margin-top:0;font-size:16px;line-height:24px}.wp-mail-smtp-tab-tools-test #email-test-form .wp-mail-smtp-setting-row.section-heading{margin-bottom:0}.wp-mail-smtp-tab-tools-test #email-test-form .wp-mail-smtp-btn{min-width:95px}.wp-mail-smtp-tab-tools-test #email-test-form .wp-mail-smtp-loading{margin-bottom:-2px;display:none}.wp-mail-smtp-tab-tools-test #wp-mail-smtp-debug{margin-top:25px}.wp-mail-smtp-tab-tools-test #wp-mail-smtp-debug h2{color:#2c3337;margin:20px 0 10px;font-size:16px;font-weight:600;line-height:20px}.wp-mail-smtp-tab-tools-test #wp-mail-smtp-debug p{color:#50575e;margin:10px 0;font-size:14px;line-height:20px}.wp-mail-smtp-tab-tools-test #wp-mail-smtp-debug ul{list-style-type:disc}.wp-mail-smtp-tab-tools-test #wp-mail-smtp-debug ul,.wp-mail-smtp-tab-tools-test #wp-mail-smtp-debug ol{color:#50575e;margin:10px 0 10px 15px;font-size:14px;line-height:20px}.wp-mail-smtp-tab-tools-test #wp-mail-smtp-debug ul li,.wp-mail-smtp-tab-tools-test #wp-mail-smtp-debug ol li{margin:0 0 8px}.wp-mail-smtp-tab-tools-test #wp-mail-smtp-debug ul li:last-of-type,.wp-mail-smtp-tab-tools-test #wp-mail-smtp-debug ol li:last-of-type{margin:0}.wp-mail-smtp-tab-tools-test #wp-mail-smtp-debug ul li ul,.wp-mail-smtp-tab-tools-test #wp-mail-smtp-debug ol li ul{list-style-type:disc}.wp-mail-smtp-tab-tools-test #wp-mail-smtp-debug .dashicons-star-filled{color:#e27730;vertical-align:text-top;width:16px;height:16px;font-size:16px}.wp-mail-smtp-tab-tools-test #wp-mail-smtp-debug .price-off{color:#00a32a;font-weight:600}.wp-mail-smtp-tab-tools-test #wp-mail-smtp-debug .error-log-button-container{margin-bottom:22px}.wp-mail-smtp-tab-tools-test #wp-mail-smtp-debug .error-log-toggle{float:left;margin-right:10px}.wp-mail-smtp-tab-tools-test #wp-mail-smtp-debug .error-log-copy{min-width:122px}.wp-mail-smtp-tab-tools-test #wp-mail-smtp-debug .error-log-copy .error-log-copy-back,.wp-mail-smtp-tab-tools-test #wp-mail-smtp-debug .error-log-copy-copied .error-log-copy-front{display:none}.wp-mail-smtp-tab-tools-test #wp-mail-smtp-debug .error-log-copy-copied .error-log-copy-back{display:inline}.wp-mail-smtp-tab-tools-test #wp-mail-smtp-debug .error-log{padding-top:10px;padding-bottom:10px;display:none}.wp-mail-smtp-tab-tools-test #wp-mail-smtp-debug .error-log.error-log-selection{opacity:0;height:0;margin:0;padding:0;display:block!important}.wp-mail-smtp-tab-tools-test #wp-mail-smtp-debug .error-log pre,.wp-mail-smtp-tab-tools-action-scheduler .wp-mail-smtp-page-content .wrap,.wp-mail-smtp-tab-tools-action-scheduler .wp-mail-smtp-page-content .subsubsub{margin:0}.wp-mail-smtp-tab-tools-action-scheduler .wp-mail-smtp-page-content>h1{color:#2c3337;margin:0 0 15px;padding:0;font-size:20px;font-weight:600}.wp-mail-smtp-tab-tools-action-scheduler .wp-mail-smtp-page-content>p{font-size:14px}.wp-mail-smtp-tab-tools-action-scheduler .wp-mail-smtp-page-content h1.wp-heading-inline,.wp-mail-smtp-tab-tools-action-scheduler .wp-mail-smtp-page-content span.subtitle{display:none}.wp-mail-smtp-tab-tools-action-scheduler .wp-mail-smtp-page-content hr.wp-header-end{visibility:hidden;margin:-2px 0 0}.wp-mail-smtp-admin-page-body .lity{z-index:999999999;padding:20px}.wp-mail-smtp-admin-page-body .lity-close{margin:10px}.wp-mail-smtp-admin-page-body .lity-content{max-width:80vw;max-height:80vh}.wp-mail-smtp-admin-page-body .lity-content img{max-width:80vw!important;max-height:80vh!important}.wp-mail-smtp-debug-event-preview{text-align:left}.wp-mail-smtp-debug-event-preview h4{margin:15px 0;font-size:18px}.wp-mail-smtp-debug-event-preview .wp-mail-smtp-debug-event-preview-subtitle{text-align:center;margin-bottom:30px}.wp-mail-smtp-debug-event-preview .wp-mail-smtp-debug-event-preview-subtitle span{color:#50575e;font-size:16px;line-height:22px}.wp-mail-smtp-debug-event-preview .wp-mail-smtp-debug-event-row{border-top:1px solid #ddd;justify-content:flex-start;align-items:flex-start;padding:20px 0;font-size:16px;line-height:22px;display:flex}.wp-mail-smtp-debug-event-preview .wp-mail-smtp-debug-event-row .debug-event-label{width:60px;margin-right:30px;font-weight:500}.wp-mail-smtp-debug-event-preview .wp-mail-smtp-debug-event-row .debug-event-value{width:calc(100% - 90px)}.wp-mail-smtp-debug-event-preview .wp-mail-smtp-debug-event-row .debug-event-initiator{margin-bottom:5px;display:inline-block}.wp-mail-smtp-debug-event-preview .wp-mail-smtp-debug-event-row .debug-event-code{color:#50575e}.wp-mail-smtp-debug-event-preview .wp-mail-smtp-debug-event-row:last-child{border-bottom:1px solid #ddd}.wp-mail-smtp-debug-event-preview .wp-mail-smtp-debug-event-preview-content>div{word-break:break-word}[class^=wp-mail-smtp-dashicons]{width:20px;height:20px;display:inline-block}.wp-mail-smtp-dashicons-yes-alt-green{background-image:url(../images/icons/dashicons/dashicons-yes-alt-green.svg)}.wp-mail-smtp-dashicons-pdf-gray{background-image:url(../images/icons/dashicons/dashicons-pdf-grey.svg)}@keyframes wp-mail-smtp-spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.wp-mail-smtp-notice__error-code,.jconfirm .wp-mail-smtp-error-code-box{color:#d63638;background:#d636380f;border:1px solid #d6363833;border-radius:4px;align-items:center;gap:8px;max-width:fit-content;margin-top:10px;padding:8px 12px;font-size:13px;line-height:1.5;display:flex}.wp-mail-smtp-notice__error-code code,.jconfirm .wp-mail-smtp-error-code-box code{color:inherit;box-shadow:none;white-space:nowrap;scrollbar-width:none;background:0 0;border:none;min-width:0;padding:0;font-family:monospace;font-size:13px;overflow-x:auto}.wp-mail-smtp-notice__error-code code::-webkit-scrollbar{display:none}.jconfirm .wp-mail-smtp-error-code-box code::-webkit-scrollbar{display:none}.wp-mail-smtp-notice__copy-btn,.jconfirm .wp-mail-smtp-error-code-box__copy{cursor:pointer;color:#d63638;opacity:.6;background:0 0;border:none;flex-shrink:0;padding:0;line-height:1;transition:opacity .15s}.wp-mail-smtp-notice__copy-btn:hover,.jconfirm .wp-mail-smtp-error-code-box__copy:hover{opacity:1}.wp-mail-smtp-notice__copy-btn svg,.jconfirm .wp-mail-smtp-error-code-box__copy svg{width:14px;height:14px;display:block}.jconfirm .wp-mail-smtp-error-code-box{justify-content:center}.wp-mail-smtp-notice__error-code{margin-bottom:10px}.wp-mail-smtp-notice__debug-detail{color:#2c3338;white-space:pre-wrap;overflow-wrap:anywhere;background:#0000000a;border:1px solid #00000014;border-radius:4px;margin-top:10px;padding:10px 12px;font-family:monospace;font-size:13px;line-height:1.5;display:block}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:" ' + msg + ' '+a+""+t.status+" "+t.statusText+" "+t.responseText,icon:'">
' + successText + '' );
+ $plugin.find( 'span.status-label' )
+ .removeClass( 'status-active status-inactive status-download' )
+ .addClass( cssClass )
+ .removeClass( 'button button-primary button-secondary disabled' )
+ .text( statusText );
+ $btn
+ .removeClass( 'status-active status-inactive status-download' )
+ .removeClass( 'button button-primary button-secondary disabled' )
+ .addClass( cssClass ).html( buttonText );
+ } else {
+ isInstallSuccessful = false;
+
+ if (
+ res.hasOwnProperty( 'data' ) &&
+ res.data.hasOwnProperty( 0 ) &&
+ res.data[ 0 ].hasOwnProperty( 'code' )
+ ) {
+
+ // Specific server-returned error.
+ $plugin.find( '.actions' ).append( '
' + l10n.activate_popup_desc + '
', + buttons: { + activate: { + text: l10n.activate_popup_btn, + btnClass: 'btn btn-confirm', + action: function( button ) { + + button.addClass( 'wp-mail-smtp-btn-loading' ); + window.location.href = l10n.activate_url; + + // Keep the popup open so the loader stays visible during navigation. + return false; + }, + }, + }, + } ); + }, + + /** + * Map a snippet code_type to a CodeMirror mode. + * + * @since 4.9.0 + * + * @param {string} codeType Snippet code type. + * + * @returns {string} CodeMirror mode. + */ + mapMode: function( codeType ) { + + switch ( codeType ) { + case 'js': + case 'javascript': + return 'javascript'; + case 'css': + return 'css'; + case 'html': + return 'htmlmixed'; + default: + return 'text/x-php'; + } + }, + + /** + * Open the read-only code-preview modal for a snippet. + * + * @since 4.9.0 + * + * @param {object} event The click event. + */ + openViewSnippetModal: function( event ) { + + event.preventDefault(); + + var $btn = $( this ); + var libraryId = $btn.data( 'library-id' ); + var installUrl = $btn.data( 'install-url' ); + var plugin = $btn.data( 'plugin' ); + var l10n = window.wp_mail_smtp_code_snippets; + + // $.dialog has no jconfirm button row (whose styles can't be overridden + // cleanly) — the action button is rendered inside our own content. + $.dialog( { + title: l10n.loading_text, + container: '#wp-mail-smtp', + boxWidth: '720px', + useBootstrap: false, + closeIcon: true, + draggable: false, + content: ''+a.activate_popup_desc+"
",buttons:{activate:{text:a.activate_popup_btn,btnClass:"btn btn-confirm",action:function(t){return t.addClass("wp-mail-smtp-btn-loading"),u.location.href=a.activate_url,!1}}}})},mapMode:function(t){switch(t){case"js":case"javascript":return"javascript";case"css":return"css";case"html":return"htmlmixed";default:return"text/x-php"}},openViewSnippetModal:function(t){t.preventDefault();var t=w(this),a=t.data("library-id"),n=t.data("install-url"),i=t.data("plugin"),t=u.wp_mail_smtp_code_snippets;w.dialog({title:t.loading_text,container:"#wp-mail-smtp",boxWidth:"720px",useBootstrap:!1,closeIcon:!0,draggable:!1,content:'' + msg + '
' ); + } + }, + + /** + * Callback for step 'Install' completion. + * + * @since 4.9.0 + * + * @param {string} plugin Plugin name. + */ + stepInstallPluginStatus: ( plugin ) => { + const data = { + action: 'wp_mail_smtp_page_check_' + plugin + '_status', + nonce: wp_mail_smtp.nonce, + provider: plugin, + }; + $.post( wp_mail_smtp.ajax_url, data ).done( app.stepInstallPluginStatusDone ); + }, + + /** + * Done part of the callback for step 'Install' completion. + * + * @since 4.9.0 + * + * @param {object} res Result of $.post() query. + */ + stepInstallPluginStatusDone: ( res ) => { + if ( ! res.success ) { + return; + } + + el.$stepSetup.removeClass( 'grey' ); + el.$stepSetupBtn = el.$stepSetup.find( 'button' ); + + if ( res.data.setup_status > 0 ) { + el.$stepSetupNum.attr( 'src', el.$stepSetupNum.attr( 'src' ).replace( 'step-2.svg', 'complete.svg' ) ); + el.$stepResult.removeClass( 'grey' ); + el.$stepResultBtn = el.$stepResult.find( 'button' ); + + if ( res.data.license_level === 'pro' && res.data.result_status === true ) { + el.$stepResultBtn.html( wp_mail_smtp_recommendations.plugin_page.activated_pro ); + el.$stepResultNum.attr( 'src', el.$stepResultNum.attr( 'src' ).replace( 'step-3.svg', 'complete.svg' ) ); + } else { + el.$stepResultBtn.attr( 'data-url', res.data.step3_button_url ); + el.$stepResultBtn.removeClass( 'grey disabled' ).addClass( 'button-primary' ); + } + } else { + el.$stepSetupBtn.removeClass( 'grey disabled' ).addClass( 'button-primary' ); + } + }, + + /** + * Go to URL. + * + * @since 4.9.0 + * + * @param {Event} e Event object. + */ + gotoURL: ( e ) => { // eslint-disable-line no-unused-vars + const $btn = $( e.currentTarget ), + url = $btn.attr( 'data-url' ); + + if ( $btn.hasClass( 'disabled' ) || ! url ) { + return; + } + + window.open( url, '_blank' ); + }, + + /** + * Show spinner. + * + * @since 4.9.0 + * + * @param {jQuery} $el Element. + */ + showSpinner: ( $el ) => { + $el.siblings( 'i.loader' ).removeClass( 'hidden' ); + }, + + /** + * Hide spinner. + * + * @since 4.9.0 + * + * @param {jQuery} $el Element. + */ + hideSpinner: ( $el ) => { + $el.show(); + $el.siblings( 'i.loader' ).addClass( 'hidden' ); + }, + }; + + // Provide public access to functions and properties. + return app; +}( document, window, jQuery ) ); + +// Initialize. +WPMailSMTPRecommendations.plugin_page.init(); diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-recommendations.min.js b/web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-recommendations.min.js new file mode 100644 index 00000000..38e95f0a --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-recommendations.min.js @@ -0,0 +1 @@ +let WPMailSMTPRecommendations={};WPMailSMTPRecommendations.plugin_page=((l,n)=>{let p={},i={init:()=>{n(i.ready)},ready:()=>{i.initVars(),i.events()},initVars:()=>{p={$stepInstall:n("section.step-install"),$stepInstallNum:n("section.step-install .num img"),$stepSetup:n("section.step-setup"),$stepSetupNum:n("section.step-setup .num img"),$stepResult:n("section.step-result"),$stepResultNum:n("section.step-result .num img")}},events:()=>{p.$stepInstall.on("click","button",i.stepInstallClick),p.$stepSetup.on("click","button",i.gotoURL),p.$stepResult.on("click","button",i.gotoURL)},stepInstallClick:s=>{let a=n(s.currentTarget);if(!a.hasClass("disabled")){let e=a.attr("data-action"),t="";switch(e){case"activate":t="about_plugin_activate",a.html(wp_mail_smtp_recommendations.plugin_page.activating);break;case"install":t="about_plugin_install",a.html(wp_mail_smtp_recommendations.plugin_page.installing);break;case"goto-url":return void(l.location.href=a.attr("data-url"));default:return}a.addClass("disabled"),i.showSpinner(p.$stepInstallNum);s=a.attr("data-plugin"),s={action:"wp_mail_smtp_ajax",task:t,nonce:wp_mail_smtp.nonce,plugin:s};n.post(wp_mail_smtp.ajax_url,s).done(function(t){i.stepInstallDone(t,a,e)}).always(function(){i.hideSpinner(p.$stepInstallNum)})}},stepInstallDone:(t,e,s)=>{var a="install"===s?t.success&&t.data.is_activated:t.success,l=e.data("provider");a?(p.$stepInstallNum.attr("src",p.$stepInstallNum.attr("src").replace("step-1.","complete.")),e.addClass("grey").removeClass("button-primary").html(wp_mail_smtp_recommendations.plugin_page.activated),i.stepInstallPluginStatus(l)):(a="install"===s&&t.success&&!t.data.is_activated||"activate"===s,t=wp_mail_smtp_recommendations.plugin_page[l+"_manual_install_url"]||"",s=wp_mail_smtp_recommendations.plugin_page[l+"_manual_activate_url"]||"",l=a?s:t,s=a?wp_mail_smtp_recommendations.plugin_page.error_could_not_activate:wp_mail_smtp_recommendations.plugin_page.error_could_not_install,t=a?wp_mail_smtp_recommendations.plugin_page.plugins_page:wp_mail_smtp_recommendations.plugin_page.download_now,e.removeClass("grey disabled").html(t).attr("data-action","goto-url").attr("data-url",l),e.after(''+s+"
"))},stepInstallPluginStatus:t=>{t={action:"wp_mail_smtp_page_check_"+t+"_status",nonce:wp_mail_smtp.nonce,provider:t};n.post(wp_mail_smtp.ajax_url,t).done(i.stepInstallPluginStatusDone)},stepInstallPluginStatusDone:t=>{t.success&&(p.$stepSetup.removeClass("grey"),p.$stepSetupBtn=p.$stepSetup.find("button"),0 constant in your wp-config.php file."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:315
+msgid "Copy input value"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:318
+#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:647
+msgid "Copied!"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:321
+msgid "The value entered does not match the required format"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:324
+msgid "Checking Mailer Configuration"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:327
+msgid "We're running some tests in the background to make sure everything is set up properly."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:330
+msgid "Checking mailer configuration image"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:333
+msgid "Whoops, looks like things aren’t configured properly."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:336
+msgid "We just tried to send a test email, but something prevented that from working. To see more details about the issue we detected, as well as our suggestions to fix it, please start troubleshooting."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:339
+msgid "Start Troubleshooting"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:342
+msgid "Send us Feedback"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:345
+msgid "Finish Setup"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:348
+msgid "Import data from your current plugins"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:351
+msgid "We have detected other SMTP plugins installed on your website. Select which plugin's data you would like to import to WP Mail SMTP."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:354
+msgid "Import Data and Continue"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:357
+msgid "Skip this Step"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:360
+msgid "Easy WP SMTP"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:363
+msgid "FluentSMTP"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:366
+msgid "Post SMTP Mailer"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:369
+msgid "SMTP Mailer"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:372
+msgid "WP SMTP"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:375
+msgid "Help Improve WP Mail SMTP + Smart Recommendations"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:378
+msgid "Get helpful suggestions from WP Mail SMTP on how to optimize your email deliverability and grow your business."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:381
+msgid "Your Email Address"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:384
+msgid "Your email is needed, so you can receive recommendations."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:387
+msgid "Help make WP Mail SMTP better for everyone"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:390
+msgid "Yes, count me in"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:393
+#: src/Admin/Pages/MiscTab.php:297
+msgid "By allowing us to track usage data we can better help you because we know with which WordPress configurations, themes and plugins we should test."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:396
+#: src/Providers/Postmark/Options.php:90
+msgid "Server API Token"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:399
+#: src/Providers/Postmark/Options.php:128
+msgid "Message Stream ID"
+msgstr ""
+
+#. Translators: Link to the Postmark API settings.
+#: assets/languages/wp-mail-smtp-vue.php:403
+msgid "%1$sFollow this link%2$s to get a Server API Token for Postmark."
+msgstr ""
+
+#. Translators: Link to the Postmark Message Stream ID settings.
+#: assets/languages/wp-mail-smtp-vue.php:407
+msgid "Message Stream ID is optional. By default outbound (Default Transactional Stream) will be used. More information can be found in our %1$sPostmark documentation%2$s."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:410
+msgid "Read how to set up Postmark"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:413
+msgid "Configure Email Logs"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:416
+msgid "Enable these powerful logging features for more control of your WordPress emails."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:419
+msgid "Store the content for all sent emails"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:422
+msgid "This option must be enabled if you'd like to be able to resend emails. Please be aware that all email content will be stored in your WordPress database. This may include sensitive data, passwords, and personal details."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:425
+msgid "Save file attachments sent from WordPress"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:428
+msgid "All file attachments sent from your site will be saved to the WordPress Uploads folder. Please note that this may reduce available disk space on your server."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:431
+msgid "Track when an email is opened"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:434
+msgid "See which emails were opened by the recipients. Email open tracking works with emails that are sent in HTML format."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:437
+msgid "Track when a link in an email is clicked"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:440
+msgid "See which links were clicked in emails sent from your WordPress site. Click tracking works with emails that are sent in HTML format."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:443
+msgid "The data center location used by your Zoho account."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:446
+#: src/Providers/Gmail/Options.php:113
+msgid "Client ID"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:449
+#: src/Providers/Gmail/Options.php:128
+msgid "Client Secret"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:452
+msgid "Redirect URI"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:455
+#: src/Providers/Gmail/Options.php:180
+msgid "Authorization"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:458
+msgid "Read how to set up Zoho Mail"
+msgstr ""
+
+#. Translators: Link to the SendLayer API settings.
+#: assets/languages/wp-mail-smtp-vue.php:462
+msgid "%1$sFollow this link%2$s to get an API Key for SendLayer."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:465
+msgid "Get Started with SendLayer"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:468
+msgid "Read how to set up SendLayer"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:471
+#: src/Providers/SMTPcom/Options.php:146
+msgid "Sender Name"
+msgstr ""
+
+#. Translators: Link to the SMTP.com API settings.
+#: assets/languages/wp-mail-smtp-vue.php:475
+msgid "%1$sFollow this link%2$s to get an API Key for SMTP.com."
+msgstr ""
+
+#. Translators: Link to the SMTP.com Senders/Channel settings.
+#: assets/languages/wp-mail-smtp-vue.php:479
+msgid "%1$sFollow this link%2$s to get a Sender Name for SMTP.com."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:482
+#: src/Providers/SMTPcom/Options.php:69
+msgid "Get Started with SMTP.com"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:485
+msgid "Read how to set up SMTP.com"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:488
+#: src/Providers/Sendinblue/Options.php:68
+#: src/Providers/SMTPcom/Options.php:74
+msgid "Transparency and Disclosure"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:491
+#: src/Providers/SMTPcom/Options.php:76
+msgid "We believe in full transparency. The SMTP.com links above are tracking links as part of our partnership with SMTP (j2 Global). We can recommend just about any SMTP service, but we only recommend products that we believe will add value to our users."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:494
+msgid "How was your WP Mail SMTP setup experience?"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:497
+msgid "Our goal is to make your SMTP setup as simple and straightforward as possible. We'd love to know how this process went for you!"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:500
+msgid "Congrats, you’ve successfully set up WP Mail SMTP!"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:503
+msgid "Here’s what to do next:"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:506
+msgid "Check out our other free WordPress plugins:"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:509
+msgid "Upgrade to Unlock Powerful SMTP Features"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:512
+msgid "Upgrade to WP Mail SMTP Pro to unlock more awesome features and experience why WP Mail SMTP is used by over 4,000,000 websites."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:515
+msgid "Upgrade to Pro Today"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:518
+#: src/Admin/Pages/TestTab.php:203
+#: src/SiteHealth.php:352
+msgid "Send a Test Email"
+msgstr ""
+
+#. Translators: Different bold styles and discount value (%5$s).
+#: assets/languages/wp-mail-smtp-vue.php:522
+msgid "%1$sBonus:%2$s You can upgrade to the Pro plan and %3$ssave %5$s today%4$s, automatically applied at checkout."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:525
+msgid "Star icon"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:528
+msgid "Thanks for the feedback!"
+msgstr ""
+
+#. Translators: %1$s and %2$s are HTML bold tags; %3$s is a new line HTML tag; %4$s are 5 golden star icons in HTML.
+#: assets/languages/wp-mail-smtp-vue.php:532
+msgid "Help us spread the word %1$sby giving WP Mail SMTP a 5-star rating %3$s(%4$s) on WordPress.org%2$s. Thanks for your support and we look forward to bringing you more awesome features."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:535
+msgid "Rate on WordPress.org"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:538
+msgid "What could we do to improve?"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:541
+msgid "We're sorry things didn't go smoothly for you, and want to keep improving. Please let us know any specific parts of this process that you think could be better. We really appreciate any details you're willing to share!"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:544
+msgid "Yes, I give WP Mail SMTP permission to contact me for any follow up questions."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:547
+msgid "Submit Feedback"
+msgstr ""
+
+#. Translators: Link to the Sendinblue API settings.
+#: assets/languages/wp-mail-smtp-vue.php:551
+msgid "%1$sFollow this link%2$s to get an API Key for Sendinblue."
+msgstr ""
+
+#. Translators: Link to the Sendinblue doc page on wpmailsmtp.com.
+#: assets/languages/wp-mail-smtp-vue.php:555
+msgid "Please input the sending domain/subdomain you configured in your Sendinblue dashboard. More information can be found in our %1$sSendinblue documentation%2$s"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:558
+msgid "Get Started with Sendinblue"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:561
+msgid "Read how to set up Sendinblue"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:564
+msgid "We believe in full transparency. The Sendinblue links above are tracking links as part of our partnership with Sendinblue. We can recommend just about any SMTP service, but we only recommend products that we believe will add value to our users."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:567
+#: src/Providers/OptionsAbstract.php:239
+msgid "SMTP Host"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:570
+#: src/Providers/OptionsAbstract.php:253
+msgid "Encryption"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:573
+#: src/Providers/OptionsAbstract.php:293
+msgid "SMTP Port"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:576
+#: src/Providers/OptionsAbstract.php:307
+msgid "Auto TLS"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:579
+msgid "Enable Auto TLS"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:582
+#: src/Providers/OptionsAbstract.php:321
+msgid "By default, TLS encryption is automatically used if the server supports it (recommended). In some cases, due to server misconfigurations, this can cause issues and may need to be disabled."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:585
+#: src/Providers/OptionsAbstract.php:329
+msgid "Authentication"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:588
+msgid "Enable Authentication"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:591
+#: src/Providers/OptionsAbstract.php:348
+msgid "SMTP Username"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:594
+#: src/Providers/OptionsAbstract.php:362
+msgid "SMTP Password"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:597
+#: src/Providers/OptionsAbstract.php:285
+msgid "For most servers TLS is the recommended option. If your SMTP provider offers both SSL and TLS options, we recommend using TLS."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:600
+#: src/Admin/Pages/SettingsTab.php:368
+#: src/Providers/OptionsAbstract.php:263
+msgid "None"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:603
+#: src/Providers/OptionsAbstract.php:272
+msgid "SSL"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:606
+#: src/Providers/OptionsAbstract.php:281
+msgid "TLS"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:609
+msgid "Access Key ID"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:612
+msgid "Secret Access Key"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:615
+msgid "SES Identities"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:618
+msgid "Please select the Amazon SES API region which is the closest to where your website is hosted. This can help to decrease network latency between your site and Amazon SES, which will speed up email sending."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:621
+msgid "Read how to set up Amazon SES"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:624
+msgid "Amazon SES requires an SSL certificate, and so is not currently compatible with your site. Please contact your host to request a SSL certificate, or check out "
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:627
+msgid "WPBeginner's tutorial on how to set up SSL"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:630
+msgid "If you'd prefer not to set up SSL, or need an SMTP solution in the meantime, please go back and select a different mailer option."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:633
+msgid "Choose Your SMTP Mailer"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:636
+#: src/Admin/ConnectionSettings.php:225
+#: src/Admin/Pages/ExportTab.php:125
+msgid "Mailer"
+msgstr ""
+
+#. Translators: Link to the SMTP Mailer docs page.
+#: assets/languages/wp-mail-smtp-vue.php:640
+msgid "Which mailer would you like to use to send emails? Not sure which mailer to choose? Check out our %1$scomplete mailer guide%2$s for details on each option."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:643
+msgid "I Understand, Continue"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:646
+msgid "Choose a Different Mailer"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:649
+msgid "Recommended Mailers"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:652
+msgid "Your mailer is already configured in a WP Mail SMTP constant, so the options below have been disabled. To change your mailer, please edit or remove the WPMS_MAILER constant in your wp-config.php file."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:655
+msgid "is a PRO Feature"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:658
+msgid "Microsoft 365 / Outlook"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:661
+msgid "Successful Upgrade!"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:664
+msgid "Upgrade Failed!"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:667
+msgid "Enter your WP Mail SMTP License Key"
+msgstr ""
+
+#. Translators: %1$s and %2$s are bold tags.
+#: assets/languages/wp-mail-smtp-vue.php:671
+msgid "You're currently using %1$sWP Mail SMTP Lite%2$s - no license needed. Enjoy!"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:674
+msgid "Continue"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:677
+msgid "Would you like to purchase the following features now?"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:680
+msgid "These features are available as part of WP Mail SMTP Pro plan."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:683
+msgid "Purchase Now"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:686
+msgid "I'll do it later"
+msgstr ""
+
+#. Translators: Link to the WPMailSMTP.com pricing page.
+#: assets/languages/wp-mail-smtp-vue.php:690
+msgid "To unlock selected features, %1$sUpgrade to Pro%2$s and enter your license key below."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:693
+msgid "Enhanced Weekly Email Summary"
+msgstr ""
+
+#. Translators: bold HTML tags.
+#: assets/languages/wp-mail-smtp-vue.php:697
+msgid "Already purchased? Enter your license key below to connect with %1$sWP Mail SMTP Pro%2$s!"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:700
+msgid "Enter your license key below to unlock plugin updates!"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:703
+msgid "Verify License Key"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:706
+#: src/Admin/Pages/SettingsTab.php:170
+msgid "Connect"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:709
+msgid "The License Key format is incorrect. Please enter a valid key and try again."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:712
+msgid "Your license was successfully verified! You are ready for the next step."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:715
+msgid "Pro badge"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:718
+msgid "License key input"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:721
+msgid "Paste your license key here"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:724
+msgid "Successful Verification!"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:727
+msgid "Verification Error!"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:730
+msgid "Authorized Redirect URI"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:733
+msgid "Select which email address you would like to send your emails from."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:736
+msgid "Read how to set up the Gmail mailer"
+msgstr ""
+
+#. Translators: Link to the SparkPost documentation.
+#: assets/languages/wp-mail-smtp-vue.php:740
+msgid "Select your SparkPost account region. %1$sMore information%2$s on SparkPost."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:743
+msgid "Read how to set up SparkPost"
+msgstr ""
+
+#. Translators: Link to the SparkPost Account API section.
+#: assets/languages/wp-mail-smtp-vue.php:747
+msgid "%1$sFollow this link%2$s to get an API Key for SparkPost."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:750
+msgid "Application ID"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:753
+msgid "Application Password"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:756
+msgid "Read how to set up Microsoft Outlook / 365"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:759
+msgid "Outlook / 365 requires an SSL certificate, and so is not currently compatible with your site. Please contact your host to request a SSL certificate, or check out "
+msgstr ""
+
+#. Translators: Minimum and maximum number that can be used.
+#: assets/languages/wp-mail-smtp-vue.php:763
+msgid "Please enter a value between %1$s and %2$s"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:766
+msgid "Value has to be a round number"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:769
+#: src/Admin/Area.php:186
+msgid "There was an error while processing the authentication request. Please try again."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:772
+msgid "There was an error while processing the authentication request. Please recheck your Client ID and Client Secret and try again."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:775
+#: src/Admin/Area.php:172
+msgid "There was an error while processing the authentication request. The nonce is invalid. Please try again."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:778
+msgid "There was an error while processing the authentication request. The authorization code is missing. Please try again."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:781
+msgid "There was an error while processing the authentication request. Please recheck your Region, Client ID and Client Secret and try again."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:784
+#: src/Admin/Area.php:209
+msgid "You have successfully linked the current site with your Google API project. Now you can start sending emails through Gmail."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:787
+msgid "You have successfully linked the current site with your Microsoft API project. Now you can start sending emails through Outlook."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:790
+msgid "You have successfully linked the current site with your Zoho Mail API project. Now you can start sending emails through Zoho Mail."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:793
+msgid "Successful Authorization"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:796
+msgid "Authorization Error!"
+msgstr ""
+
+#. Translators: name of the oAuth provider (Google, Microsoft, ...).
+#: assets/languages/wp-mail-smtp-vue.php:800
+msgid "Connect to %s"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:803
+msgid "Before continuing, you'll need to allow this plugin to send emails using your %s account."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:806
+#: src/Providers/Gmail/Options.php:217
+msgid "Remove OAuth Connection"
+msgstr ""
+
+#. Translators: link to the Google documentation page.
+#: assets/languages/wp-mail-smtp-vue.php:810
+msgid "If you want to use a different From Email address you can setup a Google email alias. %1$sFollow these instructions%2$s, then select the alias in the From Email section below."
+msgstr ""
+
+#. Translators: name of the oAuth provider (Google, Microsoft, ...).
+#: assets/languages/wp-mail-smtp-vue.php:814
+msgid "Removing this OAuth connection will give you the ability to redo the OAuth connection or connect to different %s account."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:817
+msgid "Connected as"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:820
+#: src/Admin/Area.php:165
+msgid "There was an error while processing the authentication request. The state key is invalid. Please try again."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:823
+msgid "Please enter a domain"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:826
+msgid "Please enter a valid email address"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:829
+msgid "Enter the domain name to verify it on Amazon SES and generate the required DNS CNAME records."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:832
+msgid "Enter a valid email address. A verification email will be sent to the email address you entered."
+msgstr ""
+
+#. Translators: Email address.
+#: assets/languages/wp-mail-smtp-vue.php:836
+msgid "Please check the inbox of %s for a confirmation email."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:839
+msgid "Verify Email"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:842
+msgid "No registered domains or emails."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:845
+msgid "You will not be able to send emails until you verify at least one domain or email address for the selected Amazon SES Region."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:848
+msgid "View DNS"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:851
+#: src/Providers/Resend/Options.php:60
+msgid "Resend"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:854
+msgid "Here are the domains and email addresses that have been verified and can be used as the From Email."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:857
+msgid "Verify SES Identity"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:860
+msgid "Add New SES Identity"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:863
+msgid "Name"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:866
+msgid "Value"
+msgstr ""
+
+#. Translators: Link to Amazon SES documentation.
+#: assets/languages/wp-mail-smtp-vue.php:870
+msgid "Please add these CNAME records to your domain's DNS settings. For information on how to add CNAME DNS records, please refer to the %1$sAmazon SES documentation%2$s."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:873
+msgid "Verify Domain"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:876
+msgid "Verify Email Address"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:879
+msgid "Install"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:882
+#: src/Admin/Area.php:618
+#: src/Admin/Pages/CodeSnippetsTab.php:474
+msgid "Installed"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:885
+#: src/Admin/Area.php:715
+#: src/Admin/Pages/AboutTab.php:225
+msgid "Activated"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:888
+#: src/Core.php:1061
+msgid "Loading"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:891
+msgid "WordPress SEO Toolkit"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:894
+msgid "Improve your website's SEO ranking with our toolkit."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:897
+msgid "The following plugin will be installed for free:"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:900
+msgid "Read how to set up SendGrid"
+msgstr ""
+
+#. Translators: Link to the Sendinblue API settings.
+#: assets/languages/wp-mail-smtp-vue.php:904
+msgid "%1$sFollow this link%2$s to get an API Key for Brevo."
+msgstr ""
+
+#. Translators: Link to the Sendinblue doc page on wpmailsmtp.com.
+#: assets/languages/wp-mail-smtp-vue.php:908
+msgid "Please input the sending domain/subdomain you configured in your Brevo dashboard. More information can be found in our %1$sBrevo documentation%2$s"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:911
+msgid "Get Started with Brevo"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:914
+msgid "Read how to set up Brevo"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:917
+msgid "We believe in full transparency. The Brevo links above are tracking links as part of our partnership with Brevo. We can recommend just about any SMTP service, but we only recommend products that we believe will add value to our users."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:920
+#: src/Providers/Mailgun/Options.php:60
+msgid "Mailgun API Key"
+msgstr ""
+
+#. Translators: Link to the Mailgun API settings.
+#: assets/languages/wp-mail-smtp-vue.php:924
+msgid "%1$sFollow this link%2$s to get a Mailgun API Key. Generate a key in the \"Mailgun API Keys\" section."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:927
+msgid "Improve your SEO rankings with the All in One SEO plugin."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:930
+#: src/Providers/Gmail/Options.php:91
+msgid "One-Click Setup"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:933
+#: src/Providers/Gmail/Options.php:103
+msgid "Provides a quick and easy way to connect to Google that doesn't require creating your own app."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:936
+msgid "Enabled"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:939
+msgid "Disabled"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:942
+msgid "Sign in with Google"
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:945
+#: src/Admin/Area.php:200
+msgid "There was an error while processing the authentication request."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:948
+msgid "Now you can continue mailer configuration."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:951
+msgid "Gmail mailer requires a valid Easy WP SMTP Pro license. Please activate your license key."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:954
+msgid "One-Click Setup for Google Mailer requires an active license. Emails are currently not being sent."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:957
+msgid "One-Click Setup for Google Mailer requires an active license. Verify your license to proceed with this one-click setup."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:960
+msgid "One-Click Setup for Google Mailer requires an active license. Verify your license to proceed with this one-click setup, please."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:963
+msgid "One-Click Setup for Google Mailer requires an active license. Verify your license to proceed with this One-Click Setup, please."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:966
+msgid "You have successfully connected your site with your Gmail account. This site will now send emails via your Gmail account."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:969
+msgid "You have successfully connected your site with your Gmail account. Now you can start sending emails through Gmail."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:972
+msgid "The email address that emails are sent from. You can use only connected email address or "
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:975
+msgid "The email address that emails are sent from. You can use only connected email address or his alias."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:978
+msgid "The email address that emails are sent from. The email address that emails are sent from. You can use only the connected email address or its alias."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:981
+msgid "The email address that emails are sent from. You can use only the connected email address or its alias."
+msgstr ""
+
+#: assets/languages/wp-mail-smtp-vue.php:984
+#: src/Admin/Area.php:595
+msgid "One-Click Setup for Google Mailer The Default (PHP) mailer is currently selected, but is not recommended because in most cases it does not resolve email delivery issues.
Please consider selecting and configuring one of the other mailers.
" +msgstr "" + +#: src/Admin/Area.php:564 +#: src/Admin/PageAbstract.php:192 +msgid "Save Settings" +msgstr "" + +#: src/Admin/Area.php:565 +#: src/Admin/Area.php:637 +#: src/Admin/Pages/DebugEventsTab.php:149 +msgid "Cancel" +msgstr "" + +#: src/Admin/Area.php:566 +msgid "Warning icon" +msgstr "" + +#: src/Admin/Area.php:571 +msgid "%name% is a PRO Feature" +msgstr "" + +#: src/Admin/Area.php:572 +msgid "We're sorry, the %name% mailer is not available on your plan. Please upgrade to the PRO plan to unlock all these awesome features." +msgstr "" + +#. Translators: %s - discount value $50. +#: src/Admin/Area.php:578 +msgid "Bonus: WP Mail SMTP users get %s off regular price,Hiding email delivery errors means you will no longer be warned when emails fail to send. This is not recommended and should only be used on staging or development sites.
" +msgstr "" + +#: src/Admin/Area.php:636 +#: src/Admin/Pages/DebugEventsTab.php:151 +#: src/Admin/Review.php:139 +msgid "Yes" +msgstr "" + +#: src/Admin/Area.php:716 +#: src/Admin/Pages/AboutTab.php:222 +msgid "Active" +msgstr "" + +#: src/Admin/Area.php:717 +#: src/Admin/Pages/AboutTab.php:230 +msgid "Inactive" +msgstr "" + +#: src/Admin/Area.php:719 +#: src/Admin/Pages/AboutTab.php:251 +msgid "Visit Site" +msgstr "" + +#: src/Admin/Area.php:721 +msgid "Install and Activate" +msgstr "" + +#: src/Admin/Area.php:723 +msgid "Download" +msgstr "" + +#. translators: %1$s - WP.org link; %2$s - same WP.org link. +#: src/Admin/Area.php:801 +msgid "Please rate WP Mail SMTP ★★★★★ on WordPress.org to help us spread the word. Thank you from the WP Mail SMTP team!" +msgstr "" + +#: src/Admin/Area.php:1226 +#: src/Admin/DebugEvents/DebugEvents.php:104 +#: src/Admin/DebugEvents/DebugEvents.php:147 +#: src/Admin/Pages/DebugEventsTab.php:364 +#: src/Admin/ParentPageAbstract.php:267 +msgid "You don't have the capability to perform this action." +msgstr "" + +#: src/Admin/Area.php:1285 +msgid "WP Mail SMTP Pro related message was successfully dismissed." +msgstr "" + +#: src/Admin/Area.php:1347 +msgid "Educational notice for this mailer was successfully dismissed." +msgstr "" + +#: src/Admin/Area.php:1351 +msgid "Notice was successfully dismissed." +msgstr "" + +#: src/Admin/Area.php:1382 +msgid "Get WP Mail SMTP Pro" +msgstr "" + +#: src/Admin/Area.php:1388 +msgid "Go to WP Mail SMTP Settings page" +msgstr "" + +#: src/Admin/Area.php:1396 +msgid "Go to WPMailSMTP.com documentation page" +msgstr "" + +#: src/Admin/Area.php:1397 +#: src/Admin/Area.php:1581 +msgid "Docs" +msgstr "" + +#: src/Admin/Area.php:1559 +msgid "Made with ♥ by the WP Mail SMTP team" +msgstr "" + +#: src/Admin/Area.php:1570 +msgid "Support" +msgstr "" + +#: src/Admin/Area.php:1589 +msgid "Free Plugins" +msgstr "" + +#: src/Admin/ConnectionSettings.php:95 +msgid "If you're using an email provider (Yahoo, Outlook.com, etc) this should be your email address for that account." +msgstr "" + +#: src/Admin/ConnectionSettings.php:98 +msgid "Please note that other plugins can change this, to prevent this use the setting below." +msgstr "" + +#: src/Admin/ConnectionSettings.php:122 +msgid "Current provider will automatically force From Email to be the email address that you use to set up the OAuth connection below." +msgstr "" + +#: src/Admin/ConnectionSettings.php:126 +msgid "If checked, the From Email setting above will be used for all emails, ignoring values set by other plugins." +msgstr "" + +#: src/Admin/ConnectionSettings.php:186 +msgid "Current provider doesn't support setting and forcing From Name. Emails will be sent on behalf of the account name used to setup the OAuth connection below." +msgstr "" + +#: src/Admin/ConnectionSettings.php:190 +msgid "If checked, the From Name setting above will be used for all emails, ignoring values set by other plugins." +msgstr "" + +#: src/Admin/ConnectionSettings.php:200 +msgid "Return Path" +msgstr "" + +#: src/Admin/ConnectionSettings.php:216 +msgid "Return Path indicates where non-delivery receipts - or bounce messages - are to be sent." +msgstr "" + +#: src/Admin/ConnectionSettings.php:217 +msgid "If unchecked, bounce messages may be lost." +msgstr "" + +#: src/Admin/ConnectionSettings.php:269 +msgid "Don't see what you're looking for?" +msgstr "" + +#: src/Admin/ConnectionSettings.php:274 +msgid "Suggest a Mailer" +msgstr "" + +#: src/Admin/DashboardWidget.php:283 +msgid "View Detailed Email Stats" +msgstr "" + +#: src/Admin/DashboardWidget.php:284 +msgid "Automatically keep track of every email sent from your WordPress site and view valuable statistics right here in your dashboard." +msgstr "" + +#: src/Admin/DashboardWidget.php:368 +#: src/Pro/Emails/Logs/Admin/SinglePage.php:264 +msgid "Error icon" +msgstr "" + +#. translators: %d - number of failed emails. +#: src/Admin/DashboardWidget.php:376 +msgid "We detected %d failed email in the last 30 days." +msgid_plural "We detected %d failed emails in the last 30 days." +msgstr[0] "" +msgstr[1] "" + +#. translators: %s - URL to WPMailSMTP.com. +#: src/Admin/DashboardWidget.php:387 +msgid "Upgrade to Pro and get instant alert notifications when they fail." +msgstr "" + +#: src/Admin/DashboardWidget.php:408 +msgid "Dismiss email alert block" +msgstr "" + +#: src/Admin/DashboardWidget.php:434 +msgid "NEW! Enable Weekly Email Summaries" +msgstr "" + +#: src/Admin/DashboardWidget.php:443 +msgid "View Example" +msgstr "" + +#: src/Admin/DashboardWidget.php:449 +msgid "Weekly Email Summaries have been enabled" +msgstr "" + +#. translators: %s - URL to WPMailSMTP.com. +#: src/Admin/DashboardWidget.php:472 +msgid "Upgrade to Pro for detailed stats, email logs, and more!" +msgstr "" + +#: src/Admin/DashboardWidget.php:498 +msgid "Select timespan" +msgstr "" + +#: src/Admin/DashboardWidget.php:500 +msgid "All Time" +msgstr "" + +#. translators: %d - Number of days. +#: src/Admin/DashboardWidget.php:505 +msgid "Last %d day" +msgid_plural "Last %d days" +msgstr[0] "" +msgstr[1] "" + +#: src/Admin/DashboardWidget.php:521 +msgid "Confirmed Emails" +msgstr "" + +#: src/Admin/DashboardWidget.php:522 +msgid "Unconfirmed Emails" +msgstr "" + +#: src/Admin/DashboardWidget.php:523 +msgid "Failed Emails" +msgstr "" + +#: src/Admin/DashboardWidget.php:528 +msgid "Sent Emails" +msgstr "" + +#: src/Admin/DashboardWidget.php:532 +msgid "Select email type" +msgstr "" + +#: src/Admin/DashboardWidget.php:534 +msgid "All Emails" +msgstr "" + +#: src/Admin/DashboardWidget.php:562 +msgid "Graph Style" +msgstr "" + +#: src/Admin/DashboardWidget.php:566 +msgid "Bar" +msgstr "" + +#: src/Admin/DashboardWidget.php:570 +msgid "Line" +msgstr "" + +#: src/Admin/DashboardWidget.php:575 +msgid "Color Scheme" +msgstr "" + +#: src/Admin/DashboardWidget.php:583 +msgid "WordPress" +msgstr "" + +#: src/Admin/DashboardWidget.php:587 +msgid "Save Changes" +msgstr "" + +#: src/Admin/DashboardWidget.php:632 +msgid "Table cell icon" +msgstr "" + +#. translators: %d number of total emails sent. +#: src/Admin/DashboardWidget.php:663 +msgid "%d total" +msgstr "" + +#. translators: %s fixed string of 'N/A'. +#: src/Admin/DashboardWidget.php:669 +msgid "Confirmed %s" +msgstr "" + +#. translators: %s fixed string of 'N/A'. +#: src/Admin/DashboardWidget.php:675 +msgid "Unconfirmed %s" +msgstr "" + +#. translators: %s fixed string of 'N/A'. +#: src/Admin/DashboardWidget.php:681 +msgid "Failed %s" +msgstr "" + +#. translators: %s fixed string of 'N/A'. +#: src/Admin/DashboardWidget.php:692 +msgid "Sent %s" +msgstr "" + +#: src/Admin/DebugEvents/DebugEvents.php:100 +#: src/Admin/DebugEvents/DebugEvents.php:143 +msgid "Access rejected." +msgstr "" + +#: src/Admin/DebugEvents/DebugEvents.php:108 +#: src/Admin/DebugEvents/DebugEvents.php:151 +msgid "For some reason the database table was not installed correctly. Please contact plugin support team to diagnose and fix the issue." +msgstr "" + +#: src/Admin/DebugEvents/DebugEvents.php:121 +msgid "All debug event entries were deleted successfully." +msgstr "" + +#. translators: %s - WPDB error message. +#: src/Admin/DebugEvents/DebugEvents.php:126 +msgid "There was an issue while trying to delete all debug event entries. Error message: %s" +msgstr "" + +#: src/Admin/DebugEvents/DebugEvents.php:157 +msgid "No Debug Event ID provided!" +msgstr "" + +#: src/Admin/DebugEvents/DebugEvents.php:378 +msgid "Number of events per page:" +msgstr "" + +#. translators: %d the event ID. +#: src/Admin/DebugEvents/Event.php:147 +msgid "Event #%d" +msgstr "" + +#: src/Admin/DebugEvents/Event.php:185 +#: src/SiteHealth.php:141 +msgid "Debug" +msgstr "" + +#: src/Admin/DebugEvents/Event.php:244 +#: src/WP.php:684 +msgid "N/A" +msgstr "" + +#: src/Admin/DebugEvents/Event.php:357 +msgid "Debug Event Details" +msgstr "" + +#: src/Admin/DebugEvents/Event.php:360 +#: src/Admin/DebugEvents/Table.php:170 +msgid "Type" +msgstr "" + +#: src/Admin/DebugEvents/Event.php:364 +#: src/Admin/DebugEvents/Table.php:173 +msgid "Date" +msgstr "" + +#: src/Admin/DebugEvents/Event.php:368 +#: src/Admin/DebugEvents/Table.php:171 +msgid "Content" +msgstr "" + +#: src/Admin/DebugEvents/Event.php:375 +#: src/Admin/DebugEvents/Table.php:172 +#: src/Admin/Pages/ExportTab.php:128 +msgid "Source" +msgstr "" + +#. Translators: %1$s the path of a file, %2$s the line number in the file. +#: src/Admin/DebugEvents/Event.php:381 +msgid "%1$s (line: %2$s)" +msgstr "" + +#: src/Admin/DebugEvents/Event.php:389 +msgid "Backtrace:" +msgstr "" + +#. translators: %1$d - index number; %2$s - function name; %3$s - file path; %4$s - line number. +#: src/Admin/DebugEvents/Event.php:395 +msgid "[%1$d] %2$s called at [%3$s:%4$s]" +msgstr "" + +#. Translators: %s - Email initiator/source name. +#: src/Admin/DebugEvents/Event.php:429 +msgid "Email Source: %s" +msgstr "" + +#: src/Admin/DebugEvents/Table.php:115 +msgid "All" +msgstr "" + +#: src/Admin/DebugEvents/Table.php:169 +msgid "Event" +msgstr "" + +#: src/Admin/DebugEvents/Table.php:489 +msgid "No events found." +msgstr "" + +#: src/Admin/DebugEvents/Table.php:491 +msgid "No events have been logged for now." +msgstr "" + +#: src/Admin/DebugEvents/Table.php:552 +#: src/Admin/Pages/ExportTab.php:133 +msgid "Select a date range" +msgstr "" + +#: src/Admin/DebugEvents/Table.php:556 +msgid "Filter" +msgstr "" + +#: src/Admin/DebugEvents/Table.php:565 +msgid "Delete All Events" +msgstr "" + +#: src/Admin/DomainChecker.php:70 +msgid "Something went wrong. Please try again later." +msgstr "" + +#: src/Admin/DomainChecker.php:178 +msgid "Domain Check Results" +msgstr "" + +#. translators: %s - item state name. +#: src/Admin/DomainChecker.php:190 +msgid "%s icon" +msgstr "" + +#. translators: %s - WPMailSMTP.com Upgrade page URL. +#: src/Admin/Education.php:73 +msgid "You’re using WP Mail SMTP Lite. To unlock more features, consider upgrading to Pro for %2$s OFF." +msgstr "" + +#: src/Admin/Education.php:86 +msgid "Dismiss this message." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:202 +msgid "WP Mail SMTP: One or more emails recently failed to send." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:204 +msgid "View details" +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:216 +msgid "Security check failed. Please reload the page and try again." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:220 +msgid "You don't have permission to perform this action." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:226 +msgid "Missing connection identifier." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:275 +#: src/Admin/Pages/SettingsTab.php:108 +msgid "Primary Connection" +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:284 +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1525 +msgid "an additional connection" +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:307 +msgid "Warning" +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:361 +msgid "Heads up! Your primary connection failed to send the last test email. Your backup connection sent it successfully" +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:362 +msgid "Heads up! Your primary connection failed to send the last email. Your backup connection sent it successfully" +msgstr "" + +#. translators: %s: the connection name, bolded. +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:368 +msgid "Heads up! %s failed to send the last test email. Your backup connection sent it successfully" +msgstr "" + +#. translators: %s: the connection name, bolded. +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:372 +msgid "Heads up! %s failed to send the last email. Your backup connection sent it successfully" +msgstr "" + +#. translators: %s: connection name, bolded. +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:380 +msgid "Heads up! The last test email your site attempted to send via %s was unsuccessful" +msgstr "" + +#. translators: %s: the additional connection name, bolded. +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:384 +msgid "Heads up! The last email your site attempted to send via %s was unsuccessful" +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:390 +msgid "Heads up! The last test email your site attempted to send was unsuccessful" +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:393 +msgid "Heads up! The last email your site attempted to send was unsuccessful" +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:475 +msgid "We've identified your error as a known issue and have a clear, step-by-step solution ready for you! Just refer our troubleshoot guide to get started." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:482 +msgid "Open Troubleshoot Guide" +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:488 +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:505 +msgid "Send Test Email" +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:520 +msgid "No structured error code was recorded for this failure. The diagnostic from the mailer is below." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:596 +msgid "View Error Log" +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:597 +msgid "Hide Error Log" +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:646 +msgid "Copy error log" +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:783 +msgid "Typically this error is returned for one of the following reasons:" +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:817 +msgid "SSL certificate issue." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:819 +msgid "Your server cannot establish secure HTTPS connections." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:820 +msgid "Your server's CA certificate bundle is outdated." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:823 +msgid "Contact your web hosting provider and inform them your site has an issue with SSL certificates." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:824 +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:874 +msgid "Share the Error Log below with them so they can resolve the issue." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:834 +msgid "Could not connect to host." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:836 +msgid "Your web server is blocking outbound connections." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:837 +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:981 +msgid "Your SMTP host is rejecting the connection." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:840 +msgid "Contact your web hosting provider and ask them to verify your server can make outbound connections, and whether a firewall or security policy may be blocking them." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:841 +msgid "If using the \"Other SMTP\" Mailer, triple-check your SMTP settings (host, email, password) and confirm with your SMTP host that they accept outside connections using those settings." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:850 +msgid "Invalid SendGrid API key." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:852 +msgid "Your SendGrid API key is invalid or has been revoked." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:855 +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:936 +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1091 +msgid "Go to the WP Mail SMTP plugin Settings page." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:856 +msgid "Make sure your API Key in the SendGrid mailer settings is correct and valid." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:857 +msgid "Save the plugin settings." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:858 +msgid "If updating the API Key doesn't resolve this issue, please contact our support." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:867 +msgid "Could not connect to your host." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:869 +msgid "Your server is unable to make outbound HTTP connections." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:870 +msgid "Your hosting provider is blocking the request." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:873 +msgid "Contact your web hosting provider and inform them you are having issues making outbound connections." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:883 +msgid "Could not authenticate your SMTP account." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:885 +msgid "Your SMTP username or password is incorrect." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:886 +msgid "Your SMTP host requires a different authentication method." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:889 +msgid "Triple-check your SMTP settings including host address, email, and password. If you have recently reset your password you will need to update the settings." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:890 +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:991 +msgid "Contact your SMTP host to confirm you are using the correct username and password." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:891 +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:992 +msgid "Verify with your SMTP host that your account has permissions to send emails using outside connections." +msgstr "" + +#. translators: %s - URL to the wpmailsmtp.com doc page. +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:894 +msgid "Visit our documentation for additional tips on how to resolve this error." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:913 +msgid "Error due to unsolicited and/or bulk e-mail." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:915 +msgid "You are sending more emails than your SMTP host allows." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:916 +msgid "Your message was flagged as spam by the SMTP host." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:919 +msgid "Reduce the recipient count per email: keep single emails to under 10 recipients." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:920 +msgid "Install an email-logging plugin to inspect your TO, CC, and BCC recipients." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:921 +msgid "Contact your SMTP host to ask about sending and rate limits." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:922 +msgid "Verify with them that your SMTP account is in good standing and has not been flagged." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:931 +msgid "Unauthenticated senders are not allowed." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:933 +msgid "Your SMTP host requires authentication, but it is disabled in the plugin settings." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:937 +msgid "Enable Authentication." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:938 +msgid "Enter the correct SMTP Username (usually an email address) and Password in the appropriate fields." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:949 +msgid "Misconfigured server certificate." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:951 +msgid "The SMTP host's SSL certificate is misconfigured or expired." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:952 +msgid "Your server's OpenSSL CA bundle is outdated." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:955 +msgid "Verify that the host's SSL certificate is valid." +msgstr "" + +#. translators: %s - URL to the PHP openssl manual. +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:958 +msgid "Contact your hosting support and share the Error Log below along with this link." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:977 +msgid "Could not connect to the SMTP host." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:979 +msgid "Your SMTP settings are incorrect (wrong port, security setting, or host)." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:980 +msgid "Your web server is blocking the outbound connection." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:984 +msgid "Triple-check your SMTP settings: host, email, password, port, and security." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:986 +msgid "Contact your web hosting provider and ask them to verify your server can connect to your SMTP host on the configured port and encryption, and whether a firewall or security policy may be blocking the connection. Many shared hosts block certain ports. Note: this is the most common cause of this issue." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1001 +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1016 +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1058 +msgid "Mailgun failed." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1003 +msgid "Your Mailgun account has not been activated." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1006 +msgid "Check the inbox you used to create your Mailgun account and click the activation link in the email from Mailgun." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1007 +msgid "If you do not see the activation email, go to your Mailgun control panel and resend it." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1018 +msgid "Your Mailgun API key is incorrect." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1019 +msgid "Your Mailgun domain name is incorrect." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1020 +msgid "Your Mailgun region is incorrect." +msgstr "" + +#. translators: %1$s - Mailgun API Key area URL. +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1025 +msgid "Go to your Mailgun account and verify that your Mailgun API Key is correct." +msgstr "" + +#. translators: %1$s - Mailgun domains area URL. +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1038 +msgid "Verify your Domain Name is correct." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1049 +msgid "Verify your domain Region is correct." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1060 +msgid "Your Mailgun domain has not been verified." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1061 +msgid "Your Mailgun account is still on the free trial plan." +msgstr "" + +#. translators: %s - Mailgun documentation URL. +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1066 +msgid "Go to our how-to guide for setting up Mailgun with WP Mail SMTP." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1077 +msgid "Complete the steps in section \"2. Verify Your Domain\"." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1086 +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1102 +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1122 +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1138 +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1154 +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1208 +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1236 +msgid "Google API Error." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1088 +msgid "Gmail authorization has not been completed." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1092 +msgid "Click the \"Allow plugin to send emails using your Google account\" button." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1093 +msgid "On the Google authorization screen, click \"Allow\" to grant the plugin permission to send emails on your behalf." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1104 +msgid "The recipient email address is empty or invalid." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1107 +msgid "Check the \"Send To\" email address and confirm it is valid and not empty." +msgstr "" + +#. translators: 1 - correct email address example. 2 - incorrect email address example. +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1109 +msgid "It should look like %1$s. These are invalid: %2$s." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1113 +msgid "If you are generating the email yourself, make sure it includes a TO header." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1124 +msgid "Your Gmail OAuth refresh token has expired." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1125 +msgid "Access was revoked from your Google account." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1128 +msgid "Go to the WP Mail SMTP plugin Settings page and click the \"Remove OAuth Connection\" button." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1129 +msgid "Then click the \"Allow plugin to send emails using your Google account\" button and re-enable access." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1140 +msgid "The Google authorization code has already been used." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1143 +msgid "Do not manually clean up the plugin options to retry the \"Allow...\" step." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1144 +msgid "Reinstall the plugin with clean plugin data enabled on the Misc page. This will remove all plugin options so you can safely retry." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1145 +msgid "Make sure there is no aggressive caching on admin pages, or clear the cache between attempts." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1156 +msgid "Your Google Workspace trial period has expired." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1157 +msgid "Gmail is not enabled in your Google Workspace account." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1158 +msgid "The Gmail API is not enabled in your Google Cloud project." +msgstr "" + +#. translators: %s - Google Workspace Admin area URL. +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1163 +msgid "Verify in Google Workspace Admin that your trial period has not expired." +msgstr "" + +#. translators: %s - Google Workspace Admin area URL. +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1176 +msgid "In the Apps list of Google Workspace Admin, confirm that Gmail is enabled." +msgstr "" + +#. translators: %s - Google Cloud Console URL. +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1189 +msgid "Enable the Gmail API in your project from Google Cloud Console." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1210 +msgid "The Google Cloud project linked to your Client ID/Secret no longer exists." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1211 +msgid "The Gmail API is not enabled on the project." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1214 +msgid "Make sure the Client ID/Secret in your settings corresponds to a project that has the Gmail API enabled." +msgstr "" + +#. translators: %s - Gmail documentation URL. +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1217 +msgid "Follow our Gmail tutorial to ensure your project and credentials are configured correctly." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1238 +msgid "Your Google OAuth client has been disabled." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1241 +msgid "Make sure your Client ID/Secret corresponds to a project that has the Gmail API enabled." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1242 +msgid "In Google Cloud Console, re-enable the disabled OAuth client, or create a new OAuth client and update the plugin settings with the new Client ID/Secret." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1251 +msgid "SMTP.com API Error." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1253 +msgid "Your SMTP.com Sender Name is incorrect." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1256 +msgid "Please make sure you entered an accurate Sender Name in WP Mail SMTP plugin settings." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1265 +msgid "GuzzleHttp requirements." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1267 +msgid "The cURL PHP extension is disabled on your server." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1268 +msgid "The allow_url_fopen PHP setting is disabled on your server." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1271 +msgid "Edit your php.ini file on your hosting server." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1272 +msgid "(Recommended) Enable the cURL PHP extension by adding extension=curl to php.ini." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1273 +msgid "(Or) Enable allow_url_fopen by adding allow_url_fopen = On to php.ini." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1274 +msgid "If you cannot edit php.ini yourself, share the Error Log below with your hosting support and ask them to make this change." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1283 +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1312 +msgid "SparkPost API failed." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1285 +msgid "Your SparkPost API key is incorrect." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1286 +msgid "Your SparkPost API key is missing the \"Transmissions: Read/Write\" permission." +msgstr "" + +#. translators: %1$s - SparkPost API Keys area URL, %2$s - SparkPost EU API Keys area URL. +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1291 +msgid "Go to your SparkPost account or SparkPost EU account and verify that your API key is correct." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1303 +msgid "Verify that your API key has \"Transmissions: Read/Write\" permission." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1314 +msgid "Your SparkPost account region is incorrect." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1317 +msgid "Verify that your SparkPost account region is selected in WP Mail SMTP settings." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1338 +msgid "PCRE library issue." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1340 +msgid "Your server is running PHP 7.4.x with an outdated libpcre2 library (version 10.32 or earlier) that fails email address validation." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1343 +msgid "Contact your web hosting provider and inform them you are having issues with the libpcre2 library on PHP 7.4. They should be able to resolve this." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1344 +msgid "As a temporary workaround, you can downgrade to PHP 7.3 on your server." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1392 +msgid "An issue was detected." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1394 +msgid "Your plugin settings are incorrect (wrong SMTP settings, invalid Mailer configuration, etc.)." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1395 +msgid "Your web server is blocking the connection." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1396 +msgid "Your host is rejecting the connection." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1399 +msgid "Triple-check your plugin settings, reconfigure if needed to make sure everything is correct." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1401 +msgid "Contact your web hosting provider and ask them to verify your server can make outside connections, and whether a firewall or security policy may be blocking them. Many shared hosts block certain ports. Note: this is the most common cause of this issue." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1462 +msgid "Resolve your email sending issues with SendLayer, and send your first 200 emails for free with just a few clicks!" +msgstr "" + +#. translators: %s - White Glove Setup URL. +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1469 +msgid "Need hands-on help? Our White Glove Setup handles everything for you, just share your site details and we’ll do the rest." +msgstr "" + +#. translators: %s - WPMailSMTP.com support URL. +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1480 +msgid "Still stuck? Please log in to your WP Mail SMTP account and submit a support ticket with the error log." +msgstr "" + +#. translators: %1$s - wordpress.org support forum URL, %2$s - upgrade-to-Pro URL. +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1488 +msgid "Still stuck? Get priority support by Upgrading to Pro or create a support thread along with the error log on WordPress.org forums. " +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1500 +msgid "Need Help?" +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1546 +msgid "test" +msgstr "" + +#. translators: %s: connection name, bolded. +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1549 +msgid "your backup connection %s" +msgstr "" + +#. translators: %s: connection display name. +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1556 +msgid "%s failed to send the last email. Your backup connection sent it successfully." +msgstr "" + +#. translators: %1$s: literal word "test", bolded. %2$s: connection display name. +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1561 +msgid "The last %1$s email your site attempted to send via %2$s was unsuccessful." +msgstr "" + +#. translators: %s: connection display name. +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1567 +msgid "The last email your site attempted to send via %s was unsuccessful." +msgstr "" + +#. translators: %s - link to the Additional Connection Settings page. +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1575 +msgid "Manage it on the %s page." +msgstr "" + +#: src/Admin/EmailSendingErrors/EmailSendingErrors.php:1579 +msgid "Additional Connection Settings" +msgstr "" + +#: src/Admin/FlyoutMenu.php:52 +msgid "See Quick Links" +msgstr "" + +#: src/Admin/FlyoutMenu.php:107 +msgid "Support & Docs" +msgstr "" + +#: src/Admin/FlyoutMenu.php:113 +msgid "Follow on Facebook" +msgstr "" + +#: src/Admin/FlyoutMenu.php:118 +msgid "Suggest a Feature" +msgstr "" + +#: src/Admin/Notifications.php:481 +msgid "Notifications" +msgstr "" + +#: src/Admin/Notifications.php:485 +msgid "Dismiss this message" +msgstr "" + +#: src/Admin/Notifications.php:490 +msgid "Previous message" +msgstr "" + +#: src/Admin/Notifications.php:494 +msgid "Next message" +msgstr "" + +#: src/Admin/Pages/About.php:50 +#: src/Admin/Pages/AboutTab.php:44 +msgid "About Us" +msgstr "" + +#: src/Admin/Pages/AboutTab.php:71 +msgid "Hello and welcome to WP Mail SMTP, the easiest and most popular WordPress SMTP plugin. We build software that helps your site reliably deliver emails every time." +msgstr "" + +#: src/Admin/Pages/AboutTab.php:75 +msgid "Email deliverability has been a well-documented problem for all WordPress websites. However as WPForms grew, we became more aware of this painful issue that affects our users and the larger WordPress community. So we decided to solve this problem and make a solution that's beginner friendly." +msgstr "" + +#: src/Admin/Pages/AboutTab.php:78 +msgid "Our goal is to make reliable email deliverability easy for WordPress." +msgstr "" + +#. translators: %1$s - WPForms URL, %2$s - WPBeginner URL, %3$s - OptinMonster URL, %4$s - MonsterInsights URL, %5$s - Awesome Motive URL +#: src/Admin/Pages/AboutTab.php:85 +msgid "WP Mail SMTP is brought to you by the same team that's behind the most user friendly WordPress forms, WPForms, the largest WordPress resource site, WPBeginner, the most popular lead-generation software, OptinMonster, the best WordPress analytics plugin, MonsterInsights, and more." +msgstr "" + +#: src/Admin/Pages/AboutTab.php:104 +msgid "Yup, we know a thing or two about building awesome products that customers love." +msgstr "" + +#: src/Admin/Pages/AboutTab.php:110 +msgid "The WPForms Team photo" +msgstr "" + +#: src/Admin/Pages/AboutTab.php:112 +msgid "The WPForms Team" +msgstr "" + +#: src/Admin/Pages/AboutTab.php:161 +msgid "Plugin icon" +msgstr "" + +#. translators: %s - status HTML text. +#: src/Admin/Pages/AboutTab.php:175 +msgid "Status: %s" +msgstr "" + +#: src/Admin/Pages/AboutTab.php:241 +msgid "Not Installed" +msgstr "" + +#: src/Admin/Pages/AboutTab.php:244 +msgid "Install Plugin" +msgstr "" + +#: src/Admin/Pages/AboutTab.php:272 +msgid "OptinMonster" +msgstr "" + +#: src/Admin/Pages/AboutTab.php:273 +msgid "Instantly get more subscribers, leads, and sales with the #1 conversion optimization toolkit. Create high converting popups, announcement bars, spin a wheel, and more with smart targeting and personalization." +msgstr "" + +#: src/Admin/Pages/AboutTab.php:279 +#: src/Admin/Pages/SmartRoutingTab.php:278 +msgid "WPForms" +msgstr "" + +#: src/Admin/Pages/AboutTab.php:280 +#: src/Admin/Pages/AboutTab.php:286 +msgid "The best drag & drop WordPress form builder. Easily create beautiful contact forms, surveys, payment forms, and more with our 600+ form templates. Trusted by over 5 million websites as the best forms plugin." +msgstr "" + +#: src/Admin/Pages/AboutTab.php:285 +msgid "WPForms Pro" +msgstr "" + +#: src/Admin/Pages/AboutTab.php:293 +msgid "MonsterInsights" +msgstr "" + +#: src/Admin/Pages/AboutTab.php:294 +#: src/Admin/Pages/AboutTab.php:300 +msgid "The leading WordPress analytics plugin that shows you how people find and use your website, so you can make data driven decisions to grow your business. Properly set up Google Analytics without writing code." +msgstr "" + +#: src/Admin/Pages/AboutTab.php:299 +msgid "MonsterInsights Pro" +msgstr "" + +#: src/Admin/Pages/AboutTab.php:307 +#: src/Admin/Pages/AboutTab.php:313 +msgid "AIOSEO" +msgstr "" + +#: src/Admin/Pages/AboutTab.php:308 +#: src/Admin/Pages/AboutTab.php:314 +msgid "The original WordPress SEO plugin and toolkit that improves your website’s search rankings. Comes with all the SEO features like Local SEO, WooCommerce SEO, sitemaps, SEO optimizer, schema, and more." +msgstr "" + +#: src/Admin/Pages/AboutTab.php:321 +#: src/Admin/Pages/AboutTab.php:327 +msgid "SeedProd" +msgstr "" + +#: src/Admin/Pages/AboutTab.php:322 +#: src/Admin/Pages/AboutTab.php:328 +msgid "The fastest drag & drop landing page builder for WordPress. Create custom landing pages without writing code, connect them with your CRM, collect subscribers, and grow your audience. Trusted by 1 million sites." +msgstr "" + +#: src/Admin/Pages/AboutTab.php:335 +msgid "RafflePress" +msgstr "" + +#: src/Admin/Pages/AboutTab.php:336 +#: src/Admin/Pages/AboutTab.php:342 +msgid "Turn your website visitors into brand ambassadors! Easily grow your email list, website traffic, and social media followers with the most powerful giveaways & contests plugin for WordPress." +msgstr "" + +#: src/Admin/Pages/AboutTab.php:341 +msgid "RafflePress Pro" +msgstr "" + +#: src/Admin/Pages/AboutTab.php:349 +msgid "PushEngage" +msgstr "" + +#: src/Admin/Pages/AboutTab.php:350 +msgid "Connect with your visitors after they leave your website with the leading web push notification software. Over 10,000+ businesses worldwide use PushEngage to send 15 billion notifications each month." +msgstr "" + +#: src/Admin/Pages/AboutTab.php:356 +#: src/Admin/Pages/AboutTab.php:362 +msgid "Smash Balloon Instagram Feeds" +msgstr "" + +#: src/Admin/Pages/AboutTab.php:357 +#: src/Admin/Pages/AboutTab.php:363 +msgid "Easily display Instagram content on your WordPress site without writing any code. Comes with multiple templates, ability to show content from multiple accounts, hashtags, and more. Trusted by 1 million websites." +msgstr "" + +#: src/Admin/Pages/AboutTab.php:370 +#: src/Admin/Pages/AboutTab.php:376 +msgid "Smash Balloon Facebook Feeds" +msgstr "" + +#: src/Admin/Pages/AboutTab.php:371 +#: src/Admin/Pages/AboutTab.php:377 +msgid "Easily display Facebook content on your WordPress site without writing any code. Comes with multiple templates, ability to embed albums, group content, reviews, live videos, comments, and reactions." +msgstr "" + +#: src/Admin/Pages/AboutTab.php:384 +#: src/Admin/Pages/AboutTab.php:390 +msgid "Smash Balloon YouTube Feeds" +msgstr "" + +#: src/Admin/Pages/AboutTab.php:385 +#: src/Admin/Pages/AboutTab.php:391 +msgid "Easily display YouTube videos on your WordPress site without writing any code. Comes with multiple layouts, ability to embed live streams, video filtering, ability to combine multiple channel videos, and more." +msgstr "" + +#: src/Admin/Pages/AboutTab.php:398 +#: src/Admin/Pages/AboutTab.php:404 +msgid "Smash Balloon Twitter Feeds" +msgstr "" + +#: src/Admin/Pages/AboutTab.php:399 +#: src/Admin/Pages/AboutTab.php:405 +msgid "Easily display Twitter content in WordPress without writing any code. Comes with multiple layouts, ability to combine multiple Twitter feeds, Twitter card support, tweet moderation, and more." +msgstr "" + +#: src/Admin/Pages/AboutTab.php:412 +msgid "TrustPulse" +msgstr "" + +#: src/Admin/Pages/AboutTab.php:413 +msgid "Boost your sales and conversions by up to 15% with real-time social proof notifications. TrustPulse helps you show live user activity and purchases to help convince other users to purchase." +msgstr "" + +#: src/Admin/Pages/AboutTab.php:419 +#: src/Admin/Pages/AboutTab.php:425 +msgid "SearchWP" +msgstr "" + +#: src/Admin/Pages/AboutTab.php:420 +#: src/Admin/Pages/AboutTab.php:426 +msgid "The most advanced WordPress search plugin. Customize your WordPress search algorithm, reorder search results, track search metrics, and everything you need to leverage search to grow your business." +msgstr "" + +#: src/Admin/Pages/AboutTab.php:433 +#: src/Admin/Pages/AboutTab.php:439 +msgid "AffiliateWP" +msgstr "" + +#: src/Admin/Pages/AboutTab.php:434 +#: src/Admin/Pages/AboutTab.php:440 +msgid "The #1 affiliate management plugin for WordPress. Easily create an affiliate program for your eCommerce store or membership site within minutes and start growing your sales with the power of referral marketing." +msgstr "" + +#: src/Admin/Pages/AboutTab.php:447 +msgid "WP Simple Pay" +msgstr "" + +#: src/Admin/Pages/AboutTab.php:448 +#: src/Admin/Pages/AboutTab.php:454 +msgid "The #1 Stripe payments plugin for WordPress. Start accepting one-time and recurring payments on your WordPress site without setting up a shopping cart. No code required." +msgstr "" + +#: src/Admin/Pages/AboutTab.php:453 +msgid "WP Simple Pay Pro" +msgstr "" + +#: src/Admin/Pages/AboutTab.php:461 +msgid "Easy Digital Downloads" +msgstr "" + +#: src/Admin/Pages/AboutTab.php:462 +msgid "The best WordPress eCommerce plugin for selling digital downloads. Start selling eBooks, software, music, digital art, and more within minutes. Accept payments, manage subscriptions, advanced access control, and more." +msgstr "" + +#: src/Admin/Pages/AboutTab.php:468 +msgid "Sugar Calendar Lite" +msgstr "" + +#: src/Admin/Pages/AboutTab.php:469 +#: src/Admin/Pages/AboutTab.php:475 +msgid "A simple & powerful event calendar plugin for WordPress that comes with all the event management features including payments, scheduling, timezones, ticketing, recurring events, and more." +msgstr "" + +#: src/Admin/Pages/AboutTab.php:474 +msgid "Sugar Calendar" +msgstr "" + +#: src/Admin/Pages/AboutTab.php:482 +msgid "Charitable" +msgstr "" + +#: src/Admin/Pages/AboutTab.php:483 +msgid "Top-rated WordPress donation and fundraising plugin. Over 10,000+ non-profit organizations and website owners use Charitable to create fundraising campaigns and raise more money online." +msgstr "" + +#: src/Admin/Pages/AboutTab.php:489 +msgid "WPCode Lite" +msgstr "" + +#: src/Admin/Pages/AboutTab.php:490 +#: src/Admin/Pages/AboutTab.php:496 +msgid "Future proof your WordPress customizations with the most popular code snippet management plugin for WordPress. Trusted by over 1,500,000+ websites for easily adding code to WordPress right from the admin area." +msgstr "" + +#: src/Admin/Pages/AboutTab.php:495 +msgid "WPCode Pro" +msgstr "" + +#: src/Admin/Pages/AboutTab.php:503 +#: src/Admin/Recommendations/Pages/Duplicator.php:383 +msgid "Duplicator" +msgstr "" + +#: src/Admin/Pages/AboutTab.php:504 +#: src/Admin/Pages/AboutTab.php:510 +msgid "Leading WordPress backup & site migration plugin. Over 1,500,000+ smart website owners use Duplicator to make reliable and secure WordPress backups to protect their websites. It also makes website migration really easy." +msgstr "" + +#: src/Admin/Pages/AboutTab.php:509 +msgid "Duplicator Pro" +msgstr "" + +#: src/Admin/Pages/AboutTab.php:517 +#: src/Admin/Recommendations/Pages/ActiveLayer.php:277 +msgid "ActiveLayer" +msgstr "" + +#: src/Admin/Pages/AboutTab.php:518 +msgid "Smarter spam protection for WordPress. Catch spam in milliseconds with AI, invisible to your real visitors." +msgstr "" + +#: src/Admin/Pages/AboutTab.php:524 +#: src/Admin/Recommendations/Pages/WPConsent.php:364 +msgid "WPConsent" +msgstr "" + +#: src/Admin/Pages/AboutTab.php:525 +msgid "Stay GDPR & privacy compliant. Add a cookie consent banner to your site and meet privacy laws in minutes." +msgstr "" + +#: src/Admin/Pages/AboutTab.php:531 +msgid "Vibe AI" +msgstr "" + +#: src/Admin/Pages/AboutTab.php:532 +msgid "AI-powered tools for WordPress. Work faster and smarter with automation built for your WordPress workflow." +msgstr "" + +#: src/Admin/Pages/AboutTab.php:538 +#: src/Admin/Recommendations/Pages/Universally.php:275 +msgid "Universally" +msgstr "" + +#: src/Admin/Pages/AboutTab.php:539 +msgid "Make your WordPress site accessible to everyone. Automatic accessibility fixes and a visitor widget, no coding required." +msgstr "" + +#: src/Admin/Pages/AboutTab.php:554 +msgid "Could not activate the plugin. Please activate it from the Plugins page." +msgstr "" + +#: src/Admin/Pages/AboutTab.php:580 +msgid "Could not activate the plugin. Plugin is not whitelisted." +msgstr "" + +#: src/Admin/Pages/AboutTab.php:586 +msgid "Plugin activated." +msgstr "" + +#: src/Admin/Pages/AboutTab.php:599 +msgid "Could not install the plugin." +msgstr "" + +#: src/Admin/Pages/AboutTab.php:613 +#: src/Admin/SetupWizard.php:881 +msgid "Could not install the plugin. Plugin is not whitelisted." +msgstr "" + +#: src/Admin/Pages/AboutTab.php:680 +#: src/Connect.php:210 +#: src/Connect.php:218 +#: src/Connect.php:298 +msgid "Plugin installed & activated." +msgstr "" + +#: src/Admin/Pages/AboutTab.php:688 +msgid "Plugin installed." +msgstr "" + +#: src/Admin/Pages/ActionSchedulerTab.php:41 +#: src/Admin/Pages/ActionSchedulerTab.php:101 +msgid "Scheduled Actions" +msgstr "" + +#. translators: %s - Action Scheduler website URL. +#: src/Admin/Pages/ActionSchedulerTab.php:107 +msgid "WP Mail SMTP is using the Action Scheduler library, which allows it to queue and process bigger tasks in the background without making your site slower for your visitors. Below you can see the list of all tasks and their status. This table can be very useful when debugging certain issues." +msgstr "" + +#: src/Admin/Pages/ActionSchedulerTab.php:122 +msgid "Action Scheduler library is also used by other plugins, like WPForms and WooCommerce, so you might see tasks that are not related to our plugin in the table below." +msgstr "" + +#. translators: %s - search term. +#: src/Admin/Pages/ActionSchedulerTab.php:130 +msgid "Search results for %s" +msgstr "" + +#: src/Admin/Pages/AdditionalConnectionsTab.php:49 +#: src/Admin/Pages/AdditionalConnectionsTab.php:106 +msgid "Additional Connections" +msgstr "" + +#: src/Admin/Pages/AdditionalConnectionsTab.php:110 +msgid "Create additional connections to set a backup for your Primary Connection or to configure Smart Routing." +msgstr "" + +#: src/Admin/Pages/AdditionalConnectionsTab.php:143 +#: src/Admin/Pages/SettingsTab.php:337 +#: src/Admin/Pages/SettingsTab.php:361 +msgid "Backup Connection" +msgstr "" + +#: src/Admin/Pages/AdditionalConnectionsTab.php:148 +#: src/Admin/Pages/GetProTab.php:382 +#: src/Admin/Pages/GetProTab.php:525 +#: src/Admin/Pages/SmartRoutingTab.php:51 +#: src/Admin/Pages/SmartRoutingTab.php:102 +msgid "Smart Routing" +msgstr "" + +#: src/Admin/Pages/AdditionalConnectionsTab.php:177 +msgid "With additional connections you can..." +msgstr "" + +#: src/Admin/Pages/AdditionalConnectionsTab.php:180 +msgid "Set a Backup Connection" +msgstr "" + +#: src/Admin/Pages/AdditionalConnectionsTab.php:183 +msgid "Use mailers for different purposes" +msgstr "" + +#: src/Admin/Pages/AdditionalConnectionsTab.php:186 +msgid "Create advanced routing rules" +msgstr "" + +#: src/Admin/Pages/AiMcpTab.php:69 +#: src/Admin/Recommendations/RecommendedPlugins.php:157 +msgid "AI MCP" +msgstr "" + +#: src/Admin/Pages/AiMcpTab.php:113 +msgid "Something went wrong. Please try again." +msgstr "" + +#: src/Admin/Pages/AiMcpTab.php:161 +msgid "Set Up WPVibe" +msgstr "" + +#: src/Admin/Pages/AiMcpTab.php:174 +#: src/Admin/Recommendations/Pages/WPVibe.php:311 +msgid "Activate WPVibe" +msgstr "" + +#: src/Admin/Pages/AiMcpTab.php:187 +#: src/Admin/Recommendations/Pages/WPVibe.php:251 +msgid "Install & Activate WPVibe" +msgstr "" + +#: src/Admin/Pages/AiMcpTab.php:200 +msgid "Install from WordPress.org" +msgstr "" + +#: src/Admin/Pages/AiMcpTab.php:231 +#: src/Admin/Pages/GetProTab.php:362 +#: src/Admin/Pages/SettingsTab.php:221 +#: src/WPCLI/Options/Help.php:202 +msgid "Email Logs" +msgstr "" + +#: src/Admin/Pages/AiMcpTab.php:233 +msgid "Browse and filter logged emails by status, mailer, date, and recipient" +msgstr "" + +#: src/Admin/Pages/AiMcpTab.php:234 +msgid "Get a single email's full details and content" +msgstr "" + +#: src/Admin/Pages/AiMcpTab.php:240 +msgid "Email Stats" +msgstr "" + +#: src/Admin/Pages/AiMcpTab.php:242 +msgid "See aggregate sending stats for any period or date range" +msgstr "" + +#: src/Admin/Pages/AiMcpTab.php:243 +msgid "Scope stats to a single mailer and check the success rate" +msgstr "" + +#: src/Admin/Pages/AiMcpTab.php:249 +#: src/Admin/Pages/DebugEventsTab.php:84 +#: src/Admin/Pages/DebugEventsTab.php:188 +#: src/Admin/Pages/DebugEventsTab.php:201 +msgid "Debug Events" +msgstr "" + +#: src/Admin/Pages/AiMcpTab.php:251 +msgid "List recorded send errors and debug entries" +msgstr "" + +#: src/Admin/Pages/AiMcpTab.php:264 +msgid "WordPress Abilities API + WP Mail SMTP" +msgstr "" + +#: src/Admin/Pages/AiMcpTab.php:265 +msgid "Use WP Mail SMTP With Your Favorite AI" +msgstr "" + +#. translators: %s - WPVibe.ai inline link. +#: src/Admin/Pages/AiMcpTab.php:271 +msgid "Connect your WordPress site and WP Mail SMTP to AI assistants like Claude, ChatGPT, Cursor, and more. Ask them to review your email logs, sending stats, and debug events in plain English. No copy-pasting, no exports. Connect them with the free %s plugin." +msgstr "" + +#: src/Admin/Pages/AiMcpTab.php:295 +msgid "Your site is configured to disallow plugin installation from the dashboard." +msgstr "" + +#: src/Admin/Pages/AiMcpTab.php:313 +msgid "Everything WP Mail SMTP Can Do With AI" +msgstr "" + +#: src/Admin/Pages/AiMcpTab.php:320 +msgid "View Abilities API Documentation" +msgstr "" + +#: src/Admin/Pages/AiMcpTab.php:349 +msgid "Pro feature" +msgstr "" + +#: src/Admin/Pages/AlertsTab.php:43 +#: src/Admin/Pages/AlertsTab.php:81 +#: src/WPCLI/Options/Help.php:205 +msgid "Alerts" +msgstr "" + +#: src/Admin/Pages/AlertsTab.php:85 +msgid "Configure at least one of these integrations to receive notifications when email fails to send from your site. Alert notifications will contain the following important data: email subject, email Send To address, the error message, and helpful links to help you fix the issue." +msgstr "" + +#: src/Admin/Pages/AlertsTab.php:98 +msgid "Notify when" +msgstr "" + +#: src/Admin/Pages/AlertsTab.php:105 +msgid "The initial email sending request fails" +msgstr "" + +#: src/Admin/Pages/AlertsTab.php:112 +msgid "This option is always enabled and will notify you about instant email sending failures." +msgstr "" + +#: src/Admin/Pages/AlertsTab.php:119 +msgid "The deliverability verification process detects a hard bounce" +msgstr "" + +#: src/Admin/Pages/AlertsTab.php:125 +msgid "Get notified about emails that were successfully sent, but have hard bounced on delivery attempt. A hard bounce is an email that has failed to deliver for permanent reasons, such as the recipient's email address being invalid." +msgstr "" + +#: src/Admin/Pages/AlertsTab.php:135 +msgid "Email" +msgstr "" + +#: src/Admin/Pages/AlertsTab.php:136 +msgid "Enter the email addresses (3 max) you’d like to use to receive alerts when email sending fails. Read our documentation on setting up email alerts." +msgstr "" + +#: src/Admin/Pages/AlertsTab.php:141 +msgid "Email Alerts" +msgstr "" + +#: src/Admin/Pages/AlertsTab.php:153 +#: src/Admin/Pages/TestTab.php:237 +msgid "Send To" +msgstr "" + +#: src/Admin/Pages/AlertsTab.php:164 +msgid "Slack" +msgstr "" + +#: src/Admin/Pages/AlertsTab.php:165 +msgid "Paste in the Slack webhook URL you’d like to use to receive alerts when email sending fails. Read our documentation on setting up Slack alerts." +msgstr "" + +#: src/Admin/Pages/AlertsTab.php:170 +msgid "Slack Alerts" +msgstr "" + +#: src/Admin/Pages/AlertsTab.php:182 +#: src/Admin/Pages/AlertsTab.php:211 +#: src/Admin/Pages/AlertsTab.php:240 +#: src/Admin/Pages/AlertsTab.php:316 +msgid "Webhook URL" +msgstr "" + +#: src/Admin/Pages/AlertsTab.php:193 +msgid "Discord" +msgstr "" + +#: src/Admin/Pages/AlertsTab.php:194 +msgid "Paste in the Discord webhook URL you’d like to use to receive alerts when email sending fails. Read our documentation on setting up Discord alerts." +msgstr "" + +#: src/Admin/Pages/AlertsTab.php:199 +msgid "Discord Alerts" +msgstr "" + +#: src/Admin/Pages/AlertsTab.php:222 +msgid "Microsoft Teams" +msgstr "" + +#: src/Admin/Pages/AlertsTab.php:223 +msgid "Paste in the Microsoft Teams webhook URL you’d like to use to receive alerts when email sending fails. Read our documentation on setting up Microsoft Teams alerts." +msgstr "" + +#: src/Admin/Pages/AlertsTab.php:228 +msgid "Microsoft Teams Alerts" +msgstr "" + +#: src/Admin/Pages/AlertsTab.php:251 +msgid "SMS via Twilio" +msgstr "" + +#: src/Admin/Pages/AlertsTab.php:252 +msgid "To receive SMS alerts, you’ll need a Twilio account. Read our documentation to learn how to set up Twilio SMS, then enter your connection details below." +msgstr "" + +#: src/Admin/Pages/AlertsTab.php:257 +msgid "SMS via Twilio Alerts" +msgstr "" + +#: src/Admin/Pages/AlertsTab.php:269 +msgid "Twilio Account ID" +msgstr "" + +#: src/Admin/Pages/AlertsTab.php:275 +msgid "Twilio Auth Token" +msgstr "" + +#: src/Admin/Pages/AlertsTab.php:281 +msgid "From Phone Number" +msgstr "" + +#: src/Admin/Pages/AlertsTab.php:287 +#: src/Admin/Pages/AlertsTab.php:392 +msgid "To Phone Number" +msgstr "" + +#: src/Admin/Pages/AlertsTab.php:298 +msgid "Webhook" +msgstr "" + +#: src/Admin/Pages/AlertsTab.php:299 +msgid "Paste in the webhook URL you’d like to use to receive alerts when email sending fails. Read our documentation on setting up webhook alerts." +msgstr "" + +#: src/Admin/Pages/AlertsTab.php:304 +msgid "Webhook Alerts" +msgstr "" + +#: src/Admin/Pages/AlertsTab.php:327 +msgid "Push Notifications" +msgstr "" + +#: src/Admin/Pages/AlertsTab.php:328 +msgid "To receive push notifications on this device, you'll need to allow our plugin to send notifications via this browser. Read our documentation on setting up Push Notification alerts." +msgstr "" + +#: src/Admin/Pages/AlertsTab.php:333 +msgid "Push Notification Alerts" +msgstr "" + +#: src/Admin/Pages/AlertsTab.php:345 +msgid "Connection Name" +msgstr "" + +#: src/Admin/Pages/AlertsTab.php:356 +msgid "WhatsApp" +msgstr "" + +#: src/Admin/Pages/AlertsTab.php:357 +msgid "Enter your WhatsApp Cloud API credentials to receive alerts when email sending fails. You'll need to create a Meta developer account and set up WhatsApp Business Platform." +msgstr "" + +#: src/Admin/Pages/AlertsTab.php:362 +msgid "WhatsApp Alerts" +msgstr "" + +#: src/Admin/Pages/AlertsTab.php:374 +msgid "Access Token" +msgstr "" + +#: src/Admin/Pages/AlertsTab.php:380 +msgid "WhatsApp Business Account ID" +msgstr "" + +#: src/Admin/Pages/AlertsTab.php:386 +msgid "Phone Number ID" +msgstr "" + +#: src/Admin/Pages/CodeSnippetsTab.php:53 +#: src/Admin/Pages/CodeSnippetsTab.php:277 +#: src/Admin/Recommendations/RecommendedPlugins.php:173 +msgid "Code Snippets" +msgstr "" + +#: src/Admin/Pages/CodeSnippetsTab.php:123 +#: src/Admin/Pages/CodeSnippetsTab.php:449 +#: src/Admin/Pages/CodeSnippetsTab.php:456 +#: src/Admin/Pages/CodeSnippetsTab.php:463 +msgid "Install Snippet" +msgstr "" + +#: src/Admin/Pages/CodeSnippetsTab.php:124 +msgid "Installing…" +msgstr "" + +#: src/Admin/Pages/CodeSnippetsTab.php:125 +msgid "Loading snippet…" +msgstr "" + +#: src/Admin/Pages/CodeSnippetsTab.php:126 +msgid "Could not load this snippet. Please try again." +msgstr "" + +#: src/Admin/Pages/CodeSnippetsTab.php:127 +#: src/Admin/Pages/CodeSnippetsTab.php:319 +msgid "Please Install WPCode to Use the Snippet Library" +msgstr "" + +#: src/Admin/Pages/CodeSnippetsTab.php:128 +#: src/Admin/Pages/CodeSnippetsTab.php:322 +msgid "Using WPCode, you can install WP Mail SMTP code snippets with 1 click right from this page." +msgstr "" + +#: src/Admin/Pages/CodeSnippetsTab.php:129 +#: src/Admin/Pages/CodeSnippetsTab.php:326 +#: src/Admin/Recommendations/Pages/WPCode.php:275 +msgid "Install & Activate WPCode" +msgstr "" + +#: src/Admin/Pages/CodeSnippetsTab.php:130 +#: src/Admin/Pages/CodeSnippetsTab.php:328 +msgid "Learn more about WPCode" +msgstr "" + +#: src/Admin/Pages/CodeSnippetsTab.php:133 +msgid "Please Activate WPCode to Use the Snippet Library" +msgstr "" + +#: src/Admin/Pages/CodeSnippetsTab.php:134 +msgid "WPCode is installed but not active. Activate it to install WP Mail SMTP code snippets with 1 click right from this page." +msgstr "" + +#: src/Admin/Pages/CodeSnippetsTab.php:135 +#: src/Admin/Recommendations/Pages/WPCode.php:335 +msgid "Activate WPCode" +msgstr "" + +#: src/Admin/Pages/CodeSnippetsTab.php:136 +msgid "WPCode could not be installed. Please try again." +msgstr "" + +#: src/Admin/Pages/CodeSnippetsTab.php:137 +msgid "WPCode was installed but could not be activated." +msgstr "" + +#: src/Admin/Pages/CodeSnippetsTab.php:138 +msgid "Activate it manually." +msgstr "" + +#. translators: %s - WPCode library website URL. +#: src/Admin/Pages/CodeSnippetsTab.php:282 +msgid "Using WPCode, you can install WP Mail SMTP code snippets with 1 click directly from this page or the WPCode library." +msgstr "" + +#: src/Admin/Pages/CodeSnippetsTab.php:299 +msgid "Search Snippets" +msgstr "" + +#: src/Admin/Pages/CodeSnippetsTab.php:310 +msgid "Sorry, we didn't find any snippets that match your criteria." +msgstr "" + +#: src/Admin/Pages/CodeSnippetsTab.php:442 +msgid "Edit Snippet" +msgstr "" + +#: src/Admin/Pages/CodeSnippetsTab.php:471 +msgid "View" +msgstr "" + +#: src/Admin/Pages/ControlTab.php:35 +#: src/Admin/Pages/ControlTab.php:237 +#: src/WPCLI/Options/Help.php:204 +msgid "Email Controls" +msgstr "" + +#: src/Admin/Pages/ControlTab.php:63 +msgid "Comments" +msgstr "" + +#: src/Admin/Pages/ControlTab.php:66 +msgid "Awaiting Moderation" +msgstr "" + +#: src/Admin/Pages/ControlTab.php:67 +msgid "Comment is awaiting moderation. Sent to the site admin and post author if they can edit comments." +msgstr "" + +#: src/Admin/Pages/ControlTab.php:70 +msgid "Published" +msgstr "" + +#: src/Admin/Pages/ControlTab.php:71 +msgid "Comment has been published. Sent to the post author." +msgstr "" + +#: src/Admin/Pages/ControlTab.php:76 +msgid "Change of Admin Email" +msgstr "" + +#: src/Admin/Pages/ControlTab.php:79 +msgid "Site Admin Email Change Attempt" +msgstr "" + +#: src/Admin/Pages/ControlTab.php:80 +msgid "Change of site admin email address was attempted. Sent to the proposed new email address." +msgstr "" + +#: src/Admin/Pages/ControlTab.php:83 +msgid "Site Admin Email Changed" +msgstr "" + +#: src/Admin/Pages/ControlTab.php:84 +msgid "Site admin email address was changed. Sent to the old site admin email address." +msgstr "" + +#: src/Admin/Pages/ControlTab.php:87 +msgid "Network Admin Email Change Attempt" +msgstr "" + +#: src/Admin/Pages/ControlTab.php:88 +msgid "Change of network admin email address was attempted. Sent to the proposed new email address." +msgstr "" + +#: src/Admin/Pages/ControlTab.php:91 +msgid "Network Admin Email Changed" +msgstr "" + +#: src/Admin/Pages/ControlTab.php:92 +msgid "Network admin email address was changed. Sent to the old network admin email address." +msgstr "" + +#: src/Admin/Pages/ControlTab.php:97 +msgid "Change of User Email or Password" +msgstr "" + +#: src/Admin/Pages/ControlTab.php:100 +msgid "Reset Password Request" +msgstr "" + +#: src/Admin/Pages/ControlTab.php:101 +msgid "User requested a password reset via \"Lost your password?\". Sent to the user." +msgstr "" + +#: src/Admin/Pages/ControlTab.php:104 +msgid "Password Reset Successfully" +msgstr "" + +#: src/Admin/Pages/ControlTab.php:105 +msgid "User reset their password from the password reset link. Sent to the site admin." +msgstr "" + +#: src/Admin/Pages/ControlTab.php:108 +msgid "Password Changed" +msgstr "" + +#: src/Admin/Pages/ControlTab.php:109 +msgid "User changed their password. Sent to the user." +msgstr "" + +#: src/Admin/Pages/ControlTab.php:112 +msgid "Email Change Attempt" +msgstr "" + +#: src/Admin/Pages/ControlTab.php:113 +msgid "User attempted to change their email address. Sent to the proposed new email address." +msgstr "" + +#: src/Admin/Pages/ControlTab.php:116 +msgid "Email Changed" +msgstr "" + +#: src/Admin/Pages/ControlTab.php:117 +msgid "User changed their email address. Sent to the user." +msgstr "" + +#: src/Admin/Pages/ControlTab.php:122 +msgid "Personal Data Requests" +msgstr "" + +#: src/Admin/Pages/ControlTab.php:125 +msgid "User Confirmed Export / Erasure Request" +msgstr "" + +#: src/Admin/Pages/ControlTab.php:126 +msgid "User clicked a confirmation link in personal data export or erasure request email. Sent to the site or network admin." +msgstr "" + +#: src/Admin/Pages/ControlTab.php:129 +msgid "Admin Erased Data" +msgstr "" + +#: src/Admin/Pages/ControlTab.php:130 +msgid "Site admin clicked \"Erase Personal Data\" button next to a confirmed data erasure request. Sent to the requester email address." +msgstr "" + +#: src/Admin/Pages/ControlTab.php:133 +msgid "Admin Sent Link to Export Data" +msgstr "" + +#: src/Admin/Pages/ControlTab.php:134 +msgid "Site admin clicked \"Email Data\" button next to a confirmed data export request. Sent to the requester email address." +msgstr "" + +#: src/Admin/Pages/ControlTab.php:135 +msgid "Disabling this option will block users from being able to export their personal data, as they will not receive an email with a link." +msgstr "" + +#: src/Admin/Pages/ControlTab.php:140 +msgid "Automatic Updates" +msgstr "" + +#: src/Admin/Pages/ControlTab.php:143 +msgid "Plugin Status" +msgstr "" + +#: src/Admin/Pages/ControlTab.php:144 +msgid "Completion or failure of a background automatic plugin update. Sent to the site or network admin." +msgstr "" + +#: src/Admin/Pages/ControlTab.php:147 +msgid "Theme Status" +msgstr "" + +#: src/Admin/Pages/ControlTab.php:148 +msgid "Completion or failure of a background automatic theme update. Sent to the site or network admin." +msgstr "" + +#: src/Admin/Pages/ControlTab.php:151 +msgid "WP Core Status" +msgstr "" + +#: src/Admin/Pages/ControlTab.php:152 +msgid "Completion or failure of a background automatic core update. Sent to the site or network admin." +msgstr "" + +#: src/Admin/Pages/ControlTab.php:155 +msgid "Full Log" +msgstr "" + +#: src/Admin/Pages/ControlTab.php:156 +msgid "Full log of background update results which includes information about WordPress core, plugins, themes, and translations updates. Only sent when you are using a development version of WordPress. Sent to the site or network admin." +msgstr "" + +#: src/Admin/Pages/ControlTab.php:161 +msgid "New User" +msgstr "" + +#: src/Admin/Pages/ControlTab.php:164 +msgid "Created (Admin)" +msgstr "" + +#: src/Admin/Pages/ControlTab.php:165 +msgid "A new user was created. Sent to the site admin." +msgstr "" + +#: src/Admin/Pages/ControlTab.php:168 +msgid "Created (User)" +msgstr "" + +#: src/Admin/Pages/ControlTab.php:169 +msgid "A new user was created. Sent to the new user." +msgstr "" + +#: src/Admin/Pages/ControlTab.php:172 +msgid "Invited To Site" +msgstr "" + +#: src/Admin/Pages/ControlTab.php:173 +msgid "A new user was invited to a site from Users -> Add New -> Add New User. Sent to the invited user." +msgstr "" + +#: src/Admin/Pages/ControlTab.php:176 +msgid "Created On Site" +msgstr "" + +#: src/Admin/Pages/ControlTab.php:177 +msgid "A new user account was created. Sent to Network Admin." +msgstr "" + +#: src/Admin/Pages/ControlTab.php:180 +msgid "Added / Activated on Site" +msgstr "" + +#: src/Admin/Pages/ControlTab.php:181 +msgid "A user has been added, or their account activation has been successful. Sent to the user, that has been added/activated." +msgstr "" + +#: src/Admin/Pages/ControlTab.php:186 +msgid "New Site" +msgstr "" + +#: src/Admin/Pages/ControlTab.php:189 +msgid "User Created Site" +msgstr "" + +#: src/Admin/Pages/ControlTab.php:190 +msgid "User registered for a new site. Sent to the site admin." +msgstr "" + +#: src/Admin/Pages/ControlTab.php:193 +msgid "Network Admin: User Activated / Added Site" +msgstr "" + +#: src/Admin/Pages/ControlTab.php:194 +msgid "User activated their new site, or site was added from Network Admin -> Sites -> Add New. Sent to Network Admin." +msgstr "" + +#: src/Admin/Pages/ControlTab.php:197 +msgid "Site Admin: Activated / Added Site" +msgstr "" + +#: src/Admin/Pages/ControlTab.php:198 +msgid "User activated their new site, or site was added from Network Admin -> Sites -> Add New. Sent to Site Admin." +msgstr "" + +#: src/Admin/Pages/ControlTab.php:241 +msgid "Email controls allow you to manage the automatic notifications you receive from your WordPress website. With the flick of a switch, you can reduce inbox clutter and focus on the alerts that matter the most. It's easy to disable emails about comments, email or password changes, WordPress updates, user registrations, and personal data requests." +msgstr "" + +#: src/Admin/Pages/DebugEventsTab.php:148 +msgid "Are you sure you want to permanently delete all debug events?" +msgstr "" + +#: src/Admin/Pages/DebugEventsTab.php:150 +#: src/Admin/Pages/SettingsTab.php:239 +msgid "Close" +msgstr "" + +#: src/Admin/Pages/DebugEventsTab.php:154 +msgid "An error occurred!" +msgstr "" + +#: src/Admin/Pages/DebugEventsTab.php:191 +msgid "On this page, you can view different plugin debugging events and email sending errors." +msgstr "" + +#: src/Admin/Pages/DebugEventsTab.php:204 +msgid "On this page, you can view and configure different plugin debugging events. View email sending errors and enable debugging events, allowing you to detect email sending issues." +msgstr "" + +#: src/Admin/Pages/DebugEventsTab.php:212 +msgid "Event Types" +msgstr "" + +#: src/Admin/Pages/DebugEventsTab.php:228 +msgid "Email Sending Errors" +msgstr "" + +#: src/Admin/Pages/DebugEventsTab.php:231 +msgid "This debug event is always enabled and will record any email sending errors in the table below." +msgstr "" + +#: src/Admin/Pages/DebugEventsTab.php:245 +msgid "Debug Email Sending" +msgstr "" + +#: src/Admin/Pages/DebugEventsTab.php:248 +msgid "Check this if you would like to debug the email sending process. Once enabled, all debug events will be logged in the table below. This setting should only be enabled for shorter debugging periods and disabled afterwards." +msgstr "" + +#: src/Admin/Pages/DebugEventsTab.php:256 +msgid "Events Retention Period" +msgstr "" + +#: src/Admin/Pages/DebugEventsTab.php:262 +msgid "Forever" +msgstr "" + +#: src/Admin/Pages/DebugEventsTab.php:271 +msgid "Debug events older than the selected period will be permanently deleted from the database." +msgstr "" + +#. translators: %1$s - number of debug events found; %2$s - filtered type. +#: src/Admin/Pages/DebugEventsTab.php:312 +msgid "Found %1$s %2$s event" +msgid_plural "Found %1$s %2$s events" +msgstr[0] "" +msgstr[1] "" + +#: src/Admin/Pages/DebugEventsTab.php:341 +msgid "Search Events" +msgstr "" + +#: src/Admin/Pages/DebugEventsTab.php:376 +#: src/Admin/Pages/MiscTab.php:546 +#: src/Admin/Pages/SettingsTab.php:435 +msgid "Settings were successfully saved." +msgstr "" + +#. translators: %s The searched term. +#: src/Admin/Pages/DebugEventsTab.php:415 +msgid "where event contains \"%s\"" +msgstr "" + +#. translators: %s - Date. +#: src/Admin/Pages/DebugEventsTab.php:448 +msgid "on %s" +msgstr "" + +#. translators: %1$s - Date. %2$s - Date. +#: src/Admin/Pages/DebugEventsTab.php:454 +msgid "between %1$s and %2$s" +msgstr "" + +#: src/Admin/Pages/DebugEventsTab.php:493 +msgid "Debug Events are Not Installed Correctly" +msgstr "" + +#. translators: %1$s - create missing tables link; %2$s - contact support link. +#: src/Admin/Pages/DebugEventsTab.php:500 +msgid "WP Mail SMTP is using custom database tables for some of its features. In order to work properly, the custom tables should be created, and it seems they are missing. Please try to create the missing DB tables by clicking on this link. If this issue persists, please contact our support and provide the error message below:" +msgstr "" + +#. translators: %1$s - create missing tables link; %2$s - contact support link. +#: src/Admin/Pages/DebugEventsTab.php:517 +msgid "WP Mail SMTP is using custom database tables for some of its features. In order to work properly, the custom tables should be created, and it seems they are missing. Please try to create the missing DB tables by clicking on this link. If this issue persists, please contact our support." +msgstr "" + +#: src/Admin/Pages/DebugEventsTab.php:566 +msgid "1 Week" +msgstr "" + +#: src/Admin/Pages/DebugEventsTab.php:567 +msgid "1 Month" +msgstr "" + +#: src/Admin/Pages/DebugEventsTab.php:568 +msgid "3 Months" +msgstr "" + +#: src/Admin/Pages/DebugEventsTab.php:569 +msgid "6 Months" +msgstr "" + +#: src/Admin/Pages/DebugEventsTab.php:570 +msgid "1 Year" +msgstr "" + +#. translators: %d - days count. +#: src/Admin/Pages/DebugEventsTab.php:584 +msgid "%d Day" +msgid_plural "%d Days" +msgstr[0] "" +msgstr[1] "" + +#: src/Admin/Pages/EmailReports.php:41 +#: src/Admin/Pages/EmailReportsTab.php:42 +#: src/Admin/Pages/GetProTab.php:516 +#: src/Admin/Pages/SettingsTab.php:231 +msgid "Email Reports" +msgstr "" + +#: src/Admin/Pages/EmailReportsTab.php:114 +msgid "Stats at a Glance" +msgstr "" + +#: src/Admin/Pages/EmailReportsTab.php:119 +msgid "Detailed Stats by Subject Line" +msgstr "" + +#: src/Admin/Pages/EmailReportsTab.php:124 +msgid "Weekly Email Report" +msgstr "" + +#: src/Admin/Pages/EmailReportsTab.php:132 +msgid "Email reports make it easy to track deliverability and engagement at-a-glance. Your open and click-through rates are grouped by subject line, making it easy to review the performance of campaigns or notifications. The report also displays Sent and Failed emails each week so you spot any issues quickly. When you upgrade, we'll also add an email report chart right in your WordPress dashboard." +msgstr "" + +#: src/Admin/Pages/EmailReportsTab.php:156 +msgid "Unlock these awesome reporting features:" +msgstr "" + +#: src/Admin/Pages/EmailReportsTab.php:159 +msgid "Get weekly deliverability reports" +msgstr "" + +#: src/Admin/Pages/EmailReportsTab.php:160 +msgid "View stats grouped by subject line" +msgstr "" + +#: src/Admin/Pages/EmailReportsTab.php:163 +msgid "Track total emails sent each week" +msgstr "" + +#: src/Admin/Pages/EmailReportsTab.php:164 +msgid "Measure open rate and click through rates" +msgstr "" + +#: src/Admin/Pages/EmailReportsTab.php:167 +msgid "Spot failed emails quickly" +msgstr "" + +#: src/Admin/Pages/EmailReportsTab.php:168 +msgid "See email report graphs in WordPress" +msgstr "" + +#: src/Admin/Pages/ExportTab.php:42 +msgid "Export" +msgstr "" + +#: src/Admin/Pages/ExportTab.php:80 +msgid "Export Email Logs" +msgstr "" + +#: src/Admin/Pages/ExportTab.php:84 +msgid "Easily export your logs to CSV or Excel. Filter the logs before you export and only download the data you need. This feature lets you easily create your own deliverability reports. You can also use the data in 3rd party dashboards to track deliverability along with your other website statistics." +msgstr "" + +#: src/Admin/Pages/ExportTab.php:95 +msgid "Export Type" +msgstr "" + +#: src/Admin/Pages/ExportTab.php:97 +msgid "Export in CSV (.csv)" +msgstr "" + +#: src/Admin/Pages/ExportTab.php:100 +msgid "Export in Microsoft Excel (.xlsx)" +msgstr "" + +#: src/Admin/Pages/ExportTab.php:103 +msgid "Export in EML (.eml)" +msgstr "" + +#: src/Admin/Pages/ExportTab.php:108 +msgid "Common Information" +msgstr "" + +#: src/Admin/Pages/ExportTab.php:109 +msgid "To Address" +msgstr "" + +#: src/Admin/Pages/ExportTab.php:110 +msgid "From Address" +msgstr "" + +#: src/Admin/Pages/ExportTab.php:112 +#: src/Admin/Pages/SmartRoutingTab.php:157 +msgid "Subject" +msgstr "" + +#: src/Admin/Pages/ExportTab.php:113 +msgid "Body" +msgstr "" + +#: src/Admin/Pages/ExportTab.php:114 +msgid "Created Date" +msgstr "" + +#: src/Admin/Pages/ExportTab.php:115 +msgid "Number of Attachments" +msgstr "" + +#: src/Admin/Pages/ExportTab.php:116 +msgid "Attachments" +msgstr "" + +#: src/Admin/Pages/ExportTab.php:120 +msgid "Additional Information" +msgstr "" + +#: src/Admin/Pages/ExportTab.php:121 +msgid "Status" +msgstr "" + +#: src/Admin/Pages/ExportTab.php:122 +msgid "Carbon Copy (CC)" +msgstr "" + +#: src/Admin/Pages/ExportTab.php:123 +msgid "Blind Carbon Copy (BCC)" +msgstr "" + +#: src/Admin/Pages/ExportTab.php:124 +msgid "Headers" +msgstr "" + +#: src/Admin/Pages/ExportTab.php:126 +msgid "Error Details" +msgstr "" + +#: src/Admin/Pages/ExportTab.php:127 +msgid "Email log ID" +msgstr "" + +#: src/Admin/Pages/ExportTab.php:132 +msgid "Custom Date Range" +msgstr "" + +#: src/Admin/Pages/ExportTab.php:137 +msgid "Search" +msgstr "" + +#: src/Admin/Pages/ExportTab.php:139 +msgid "Email Addresses" +msgstr "" + +#: src/Admin/Pages/GetProTab.php:41 +msgid "Get Pro" +msgstr "" + +#: src/Admin/Pages/GetProTab.php:86 +msgid "Take complete control of your WordPress emails" +msgstr "" + +#: src/Admin/Pages/GetProTab.php:90 +msgid "Get email logs, delivery tracking, backup connections, and instant failure alerts so you catch issues before your customers do." +msgstr "" + +#: src/Admin/Pages/GetProTab.php:100 +msgid "Compare Lite vs Pro features" +msgstr "" + +#: src/Admin/Pages/GetProTab.php:131 +msgid "Features you'll love with Pro" +msgstr "" + +#: src/Admin/Pages/GetProTab.php:177 +msgid "Lite vs Pro: What's the Difference?" +msgstr "" + +#: src/Admin/Pages/GetProTab.php:180 +msgid "Get the most out of WP Mail SMTP by upgrading to Pro and unlocking all of the powerful features." +msgstr "" + +#: src/Admin/Pages/GetProTab.php:215 +msgid "Feature" +msgstr "" + +#: src/Admin/Pages/GetProTab.php:218 +msgid "Lite" +msgstr "" + +#: src/Admin/Pages/GetProTab.php:221 +#: src/Admin/Pages/TestTab.php:396 +msgid "Pro" +msgstr "" + +#: src/Admin/Pages/GetProTab.php:284 +msgid "Available" +msgstr "" + +#: src/Admin/Pages/GetProTab.php:285 +msgid "Partially available" +msgstr "" + +#: src/Admin/Pages/GetProTab.php:286 +#: src/Admin/Pages/GetProTab.php:404 +msgid "Not available" +msgstr "" + +#: src/Admin/Pages/GetProTab.php:343 +#: src/Admin/Pages/TestTab.php:430 +msgid "$50 OFF" +msgstr "" + +#: src/Admin/Pages/GetProTab.php:344 +msgid "for WP Mail SMTP Lite users" +msgstr "" + +#: src/Admin/Pages/GetProTab.php:363 +msgid "Save details about every email sent from your WordPress site." +msgstr "" + +#: src/Admin/Pages/GetProTab.php:367 +msgid "Email Failure Alerts" +msgstr "" + +#: src/Admin/Pages/GetProTab.php:368 +msgid "Receive immediate alerts for email failures to quickly address issues." +msgstr "" + +#: src/Admin/Pages/GetProTab.php:372 +#: src/Admin/Pages/GetProTab.php:489 +msgid "Backup Connections" +msgstr "" + +#: src/Admin/Pages/GetProTab.php:373 +msgid "Set up a secondary email provider in case your primary provider fails." +msgstr "" + +#: src/Admin/Pages/GetProTab.php:377 +msgid "One-Click Mailer Setups" +msgstr "" + +#: src/Admin/Pages/GetProTab.php:378 +msgid "An easy and secure way to configure your Gmail and Outlook mailers." +msgstr "" + +#: src/Admin/Pages/GetProTab.php:383 +msgid "Use conditional logic to send emails through different providers." +msgstr "" + +#: src/Admin/Pages/GetProTab.php:387 +msgid "Advanced mailers" +msgstr "" + +#: src/Admin/Pages/GetProTab.php:388 +msgid "Connect with top providers like Amazon SES, Microsoft 365 / Outlook, and Zoho Mail." +msgstr "" + +#: src/Admin/Pages/GetProTab.php:413 +msgid "Connect your WordPress site to any of the following SMTP providers:" +msgstr "" + +#: src/Admin/Pages/GetProTab.php:414 +msgid "SendLayer, SMTP.com, Brevo, Google Workspace / Gmail, Mailgun, Postmark, SendGrid, SMTP2GO, Sparkpost, and any other SMTP provider using your SMTP credentials." +msgstr "" + +#: src/Admin/Pages/GetProTab.php:418 +msgid "Connect your WordPress site to any of our Lite SMTP providers, as well as:" +msgstr "" + +#: src/Admin/Pages/GetProTab.php:419 +msgid "Amazon SES, Microsoft 365 / Outlook, and Zoho Mail." +msgstr "" + +#: src/Admin/Pages/GetProTab.php:423 +msgid "Weekly Email Summaries" +msgstr "" + +#: src/Admin/Pages/GetProTab.php:426 +msgid "Receive a basic report of your total emails sent including a summary of the previous week's sent emails." +msgstr "" + +#: src/Admin/Pages/GetProTab.php:431 +msgid "Receive a detailed report of your total and previous week's metrics for emails sent, failed, and delivered, plus statistics for your top 5 popular emails." +msgstr "" + +#: src/Admin/Pages/GetProTab.php:439 +#: src/Admin/Pages/GetProTab.php:444 +msgid "View email sending errors in your WordPress dashboard." +msgstr "" + +#: src/Admin/Pages/GetProTab.php:449 +msgid "Google Workspace" +msgstr "" + +#: src/Admin/Pages/GetProTab.php:452 +msgid "Manually connect to your account with Google Workspace/ Gmail mailer" +msgstr "" + +#: src/Admin/Pages/GetProTab.php:457 +msgid "Access your Google Workspace immediately with Gmail One Click Setup" +msgstr "" + +#: src/Admin/Pages/GetProTab.php:462 +msgid "Outlook" +msgstr "" + +#: src/Admin/Pages/GetProTab.php:466 +msgid "Access Outlook immediately with Outlook One Click Setup" +msgstr "" + +#: src/Admin/Pages/GetProTab.php:471 +msgid "Email Logging" +msgstr "" + +#: src/Admin/Pages/GetProTab.php:475 +msgid "View detailed email logs in your WordPress dashboard." +msgstr "" + +#: src/Admin/Pages/GetProTab.php:476 +msgid "Includes delivery status, email content, attachments, source, and technical details, plus the option to resend the email." +msgstr "" + +#: src/Admin/Pages/GetProTab.php:484 +msgid "Receive an alert via your preferred channel whenever an email fails to send." +msgstr "" + +#: src/Admin/Pages/GetProTab.php:485 +msgid "Email, Slack, SMS via Twilio, Microsoft Teams, Discord, or custom webhooks." +msgstr "" + +#: src/Admin/Pages/GetProTab.php:493 +msgid "Connect to multiple SMTP providers." +msgstr "" + +#: src/Admin/Pages/GetProTab.php:494 +msgid "Choose one primary connection, then select another connection as a backup. If an email fails to send via your primary connection, it will automatically be resent using your backup connection." +msgstr "" + +#: src/Admin/Pages/GetProTab.php:498 +msgid "Manage Email Notifications" +msgstr "" + +#: src/Admin/Pages/GetProTab.php:502 +msgid "Choose which default WordPress email notifications your site sends." +msgstr "" + +#: src/Admin/Pages/GetProTab.php:507 +msgid "Open & Click Tracking" +msgstr "" + +#: src/Admin/Pages/GetProTab.php:511 +msgid "Monitor email open and click-through rates." +msgstr "" + +#: src/Admin/Pages/GetProTab.php:520 +msgid "View advanced email reports in your WordPress dashboard." +msgstr "" + +#: src/Admin/Pages/GetProTab.php:521 +msgid "Includes total number of emails sent, emails delivered, failed emails, opened emails, links clicked." +msgstr "" + +#: src/Admin/Pages/GetProTab.php:529 +msgid "Send emails through more than one SMTP provider." +msgstr "" + +#: src/Admin/Pages/GetProTab.php:530 +msgid "Connect WP Mail SMTP to multiple providers, then route emails through your preferred provider based on custom conditional logic rules." +msgstr "" + +#: src/Admin/Pages/LogsTab.php:130 +msgid "Email Log Index" +msgstr "" + +#: src/Admin/Pages/LogsTab.php:135 +#: src/Admin/Pages/SettingsTab.php:226 +msgid "Individual Email Log" +msgstr "" + +#: src/Admin/Pages/LogsTab.php:147 +msgid "Email logging makes it easy to save details about all of the emails sent from your WordPress site. You can search and filter the email log to find specific messages and check the color-coded delivery status. Email logging also allows you to resend emails, save attachments, and export your logs in different formats." +msgstr "" + +#: src/Admin/Pages/LogsTab.php:171 +msgid "Unlock these awesome logging features:" +msgstr "" + +#: src/Admin/Pages/LogsTab.php:174 +msgid "Save detailed email headers" +msgstr "" + +#: src/Admin/Pages/LogsTab.php:175 +msgid "See sent and failed emails" +msgstr "" + +#: src/Admin/Pages/LogsTab.php:178 +msgid "Resend emails and attachments" +msgstr "" + +#: src/Admin/Pages/LogsTab.php:179 +msgid "Track email opens and clicks" +msgstr "" + +#: src/Admin/Pages/LogsTab.php:182 +msgid "Print email logs or save as PDF" +msgstr "" + +#: src/Admin/Pages/LogsTab.php:183 +msgid "Export logs to CSV, XLSX, or EML" +msgstr "" + +#: src/Admin/Pages/MiscTab.php:40 +msgid "Misc" +msgstr "" + +#: src/Admin/Pages/MiscTab.php:52 +msgid "Miscellaneous" +msgstr "" + +#: src/Admin/Pages/MiscTab.php:79 +msgid "Do Not Send" +msgstr "" + +#: src/Admin/Pages/MiscTab.php:87 +msgid "Some plugins, like BuddyPress and Events Manager, are using their own email delivery solutions. By default, this option does not block their emails, as those plugins do not use defaultwp_mail() function to send emails."
+msgstr ""
+
+#: src/Admin/Pages/MiscTab.php:96
+msgid "You will need to consult with their documentation to switch them to use default WordPress email delivery."
+msgstr ""
+
+#: src/Admin/Pages/MiscTab.php:98
+msgid "Test emails are allowed to be sent, regardless of this option."
+msgstr ""
+
+#: src/Admin/Pages/MiscTab.php:115
+msgid "Stop sending all emails"
+msgstr ""
+
+#. translators: %s - The URL to the constants support article.
+#: src/Admin/Pages/MiscTab.php:124
+msgid "Please read this support article if you want to enable this option using constants."
+msgstr ""
+
+#: src/Admin/Pages/MiscTab.php:158
+msgid "Log Blocked Emails"
+msgstr ""
+
+#: src/Admin/Pages/MiscTab.php:165
+msgid "When selected, emails blocked by the \"Do Not Send\" option will be logged in the Email Log."
+msgstr ""
+
+#: src/Admin/Pages/MiscTab.php:183
+msgid "Hide Announcements"
+msgstr ""
+
+#: src/Admin/Pages/MiscTab.php:198
+msgid "Hide plugin announcements and update details."
+msgstr ""
+
+#: src/Admin/Pages/MiscTab.php:208
+msgid "Hide Email Delivery Errors"
+msgstr ""
+
+#: src/Admin/Pages/MiscTab.php:227
+msgid "Hide warnings alerting of email delivery errors."
+msgstr ""
+
+#. translators: %s - filter that was used to disabled.
+#: src/Admin/Pages/MiscTab.php:233
+msgid "Email Delivery Errors were disabled using a %s filter."
+msgstr ""
+
+#: src/Admin/Pages/MiscTab.php:242
+msgid "This is not recommended and should only be done for staging or development sites."
+msgstr ""
+
+#: src/Admin/Pages/MiscTab.php:257
+msgid "Hide Dashboard Widget"
+msgstr ""
+
+#: src/Admin/Pages/MiscTab.php:272
+msgid "Hide the WP Mail SMTP Dashboard Widget."
+msgstr ""
+
+#: src/Admin/Pages/MiscTab.php:282
+msgid "Allow Usage Tracking"
+msgstr ""
+
+#: src/Admin/Pages/MiscTab.php:307
+msgid "Disable Email Summaries"
+msgstr ""
+
+#: src/Admin/Pages/MiscTab.php:326
+msgid "Disable Email Summaries weekly delivery."
+msgstr ""
+
+#. translators: %s - Email Log settings url.
+#: src/Admin/Pages/MiscTab.php:331
+msgid "Please enable Email Logging first, before this setting can be configured."
+msgstr ""
+
+#: src/Admin/Pages/MiscTab.php:344
+msgid "View Email Summary Example"
+msgstr ""
+
+#: src/Admin/Pages/MiscTab.php:360
+msgid "Optimize Email Sending"
+msgstr ""
+
+#. translators: %1$s - Documentation URL.
+#: src/Admin/Pages/MiscTab.php:378
+msgid "Send emails asynchronously, which will make pages with email requests load faster, but may delay email delivery by a minute or two. Learn More"
+msgstr ""
+
+#: src/Admin/Pages/MiscTab.php:409
+msgid "Uninstall WP Mail SMTP"
+msgstr ""
+
+#: src/Admin/Pages/MiscTab.php:424
+msgid "Remove ALL WP Mail SMTP data upon plugin deletion."
+msgstr ""
+
+#: src/Admin/Pages/MiscTab.php:427
+msgid "All settings will be unrecoverable."
+msgstr ""
+
+#: src/Admin/Pages/MiscTab.php:449
+msgid "Email Rate Limiting"
+msgstr ""
+
+#. translators: %1$s - Documentation URL.
+#: src/Admin/Pages/MiscTab.php:464
+msgid "Limit the number of emails this site will send in each time interval (per minute, hour, day, week and month). Emails that will cross those set limits will be queued and sent as soon as your limits allow. Learn More"
+msgstr ""
+
+#: src/Admin/Pages/SettingsTab.php:64
+msgid "License"
+msgstr ""
+
+#: src/Admin/Pages/SettingsTab.php:67
+msgid "Your license key provides access to updates and support."
+msgstr ""
+
+#: src/Admin/Pages/SettingsTab.php:74
+msgid "You're using WP Mail SMTP Lite - no license needed. Enjoy!"
+msgstr ""
+
+#. Translators: %s - discount value $50
+#: src/Admin/Pages/SettingsTab.php:80
+msgid "As a valued WP Mail SMTP Lite user, you can enjoy an exclusive %s discount, automatically applied at checkout to unlock even more features!"
+msgstr ""
+
+#: src/Admin/Pages/SettingsTab.php:98
+msgid "License Key"
+msgstr ""
+
+#: src/Admin/Pages/SettingsTab.php:116
+msgid "Setup Wizard"
+msgstr ""
+
+#: src/Admin/Pages/SettingsTab.php:120
+msgid "Launch Setup Wizard"
+msgstr ""
+
+#: src/Admin/Pages/SettingsTab.php:124
+msgid "We'll guide you through each step needed to get WP Mail SMTP fully set up on your site."
+msgstr ""
+
+#: src/Admin/Pages/SettingsTab.php:164
+msgid "Already purchased? Simply enter your license key below to connect with WP Mail SMTP Pro!"
+msgstr ""
+
+#: src/Admin/Pages/SettingsTab.php:168
+msgid "Paste license key here"
+msgstr ""
+
+#: src/Admin/Pages/SettingsTab.php:245
+msgid "Level Up Your Email Game - Get Pro Features Now"
+msgstr ""
+
+#: src/Admin/Pages/SettingsTab.php:248
+msgid "Upgrade and join over 4,000,000 websites!"
+msgstr ""
+
+#: src/Admin/Pages/SettingsTab.php:254
+msgid "Key Features You’ll Unlock:"
+msgstr ""
+
+#: src/Admin/Pages/SettingsTab.php:259
+msgid "Peace of Mind - Never wonder about your email status"
+msgstr ""
+
+#: src/Admin/Pages/SettingsTab.php:260
+msgid "Email Logging, Alerts, and Backup Connection"
+msgstr ""
+
+#: src/Admin/Pages/SettingsTab.php:263
+msgid "Professional Email Services - Access enterprise-grade email providers"
+msgstr ""
+
+#: src/Admin/Pages/SettingsTab.php:264
+msgid "Gmail one-click setup, Microsoft 365 / Outlook, Amazon SES, and Zoho Mail"
+msgstr ""
+
+#: src/Admin/Pages/SettingsTab.php:267
+msgid "Effortless Management - Control your email experience"
+msgstr ""
+
+#: src/Admin/Pages/SettingsTab.php:268
+msgid "Smart Routing, Multisite Support, and Manage Notifications"
+msgstr ""
+
+#: src/Admin/Pages/SettingsTab.php:271
+msgid "White Glove Setup - Sit back while we handle everything"
+msgstr ""
+
+#: src/Admin/Pages/SettingsTab.php:272
+msgid "Professional Setup and World-Class Support"
+msgstr ""
+
+#: src/Admin/Pages/SettingsTab.php:283
+msgid "Discount"
+msgstr ""
+
+#. Translators: %s - discount value $50.
+#: src/Admin/Pages/SettingsTab.php:288
+msgid "%s OFF for WP Mail SMTP users, applied at checkout."
+msgstr ""
+
+#. translators: %s - WPMailSMTP.com Upgrade page URL.
+#: src/Admin/Pages/SettingsTab.php:343
+msgid "Don’t worry about losing emails. Add an additional connection, then set it as your Backup Connection. Emails that fail to send with the Primary Connection will be sent via the selected Backup Connection. Upgrade to WP Mail SMTP Pro!"
+msgstr ""
+
+#. translators: %s - Smart routing settings page url.
+#: src/Admin/Pages/SettingsTab.php:375
+msgid "Once you add an additional connection, you can select it here."
+msgstr ""
+
+#: src/Admin/Pages/SmartRoutingTab.php:106
+msgid "Send emails from different additional connections based on your configured conditions. Emails that do not match any of the conditions below will be sent via your Primary Connection."
+msgstr ""
+
+#: src/Admin/Pages/SmartRoutingTab.php:119
+msgid "Enable Smart Routing"
+msgstr ""
+
+#: src/Admin/Pages/SmartRoutingTab.php:130
+#: src/Admin/Pages/SmartRoutingTab.php:242
+msgid "Send with"
+msgstr ""
+
+#: src/Admin/Pages/SmartRoutingTab.php:132
+msgid "WooCommerce Emails (SendLayer)"
+msgstr ""
+
+#: src/Admin/Pages/SmartRoutingTab.php:134
+#: src/Admin/Pages/SmartRoutingTab.php:246
+msgid "if the following conditions are met..."
+msgstr ""
+
+#: src/Admin/Pages/SmartRoutingTab.php:138
+#: src/Admin/Pages/SmartRoutingTab.php:250
+msgid "Arrow Up"
+msgstr ""
+
+#: src/Admin/Pages/SmartRoutingTab.php:141
+#: src/Admin/Pages/SmartRoutingTab.php:253
+msgid "Arrow Down"
+msgstr ""
+
+#: src/Admin/Pages/SmartRoutingTab.php:162
+msgid "Contains"
+msgstr ""
+
+#: src/Admin/Pages/SmartRoutingTab.php:166
+msgid "Order"
+msgstr ""
+
+#: src/Admin/Pages/SmartRoutingTab.php:170
+#: src/Admin/Pages/SmartRoutingTab.php:193
+#: src/Admin/Pages/SmartRoutingTab.php:223
+#: src/Admin/Pages/SmartRoutingTab.php:282
+msgid "And"
+msgstr ""
+
+#: src/Admin/Pages/SmartRoutingTab.php:185
+#: src/Admin/Pages/SmartRoutingTab.php:215
+#: src/Admin/Pages/SmartRoutingTab.php:274
+msgid "Is"
+msgstr ""
+
+#: src/Admin/Pages/SmartRoutingTab.php:202
+#: src/Admin/Pages/SmartRoutingTab.php:232
+#: src/Admin/Pages/SmartRoutingTab.php:291
+msgid "or"
+msgstr ""
+
+#: src/Admin/Pages/SmartRoutingTab.php:235
+#: src/Admin/Pages/SmartRoutingTab.php:294
+msgid "Add New Group"
+msgstr ""
+
+#: src/Admin/Pages/SmartRoutingTab.php:244
+msgid "Contact Emails (SMTP.com)"
+msgstr ""
+
+#: src/Admin/Pages/SmartRoutingTab.php:269
+msgid "Initiator"
+msgstr ""
+
+#: src/Admin/Pages/SmartRoutingTab.php:301
+msgid "Light bulb icon"
+msgstr ""
+
+#: src/Admin/Pages/SmartRoutingTab.php:302
+msgid "Friendly reminder, your Primary Connection will be used for all emails that do not match the conditions above."
+msgstr ""
+
+#: src/Admin/Pages/TestTab.php:102
+msgid "Email Test"
+msgstr ""
+
+#: src/Admin/Pages/TestTab.php:163
+msgid "Test failed. Please use a valid email address and try to resend the test email."
+msgstr ""
+
+#: src/Admin/Pages/TestTab.php:243
+msgid "Enter email address where test email will be sent."
+msgstr ""
+
+#: src/Admin/Pages/TestTab.php:260
+msgid "HTML"
+msgstr ""
+
+#: src/Admin/Pages/TestTab.php:273
+msgid "Send this email in HTML or in plain text format."
+msgstr ""
+
+#: src/Admin/Pages/TestTab.php:293
+msgid "You cannot send an email. Mailer is not properly configured. Please check your settings."
+msgstr ""
+
+#: src/Admin/Pages/TestTab.php:297
+msgid "Send Email"
+msgstr ""
+
+#: src/Admin/Pages/TestTab.php:337
+msgid "The test email might have sent, but its deliverability should be improved."
+msgstr ""
+
+#: src/Admin/Pages/TestTab.php:345
+msgid "Send Another Test Email"
+msgstr ""
+
+#: src/Admin/Pages/TestTab.php:373
+msgid "Log, track, and resend any email your site sends"
+msgstr ""
+
+#: src/Admin/Pages/TestTab.php:374
+msgid "Get instant failure alerts via Email, Slack, SMS, Discord, etc"
+msgstr ""
+
+#: src/Admin/Pages/TestTab.php:375
+msgid "Never miss an email with an automatic backup mailer"
+msgstr ""
+
+#: src/Admin/Pages/TestTab.php:387
+msgid "Test email sent successfully! Check your inbox to confirm delivery."
+msgstr ""
+
+#. translators: %s - "Pro" wrapped in a bold tag highlighting the WP Mail SMTP Pro tier.
+#: src/Admin/Pages/TestTab.php:395
+msgid "Level Up Your Email Game! Unlock these %s features and get even more from WP Mail SMTP."
+msgstr ""
+
+#: src/Admin/Pages/TestTab.php:414
+msgid "Priority support from our email deliverability experts"
+msgstr ""
+
+#: src/Admin/Pages/TestTab.php:417
+msgid "...and much more!"
+msgstr ""
+
+#: src/Admin/Pages/TestTab.php:431
+msgid "for WP Mail SMTP users, applied at checkout."
+msgstr ""
+
+#: src/Admin/Pages/TestTab.php:438
+msgid "A person celebrating after sending an email."
+msgstr ""
+
+#: src/Admin/Pages/TestTab.php:457
+#: src/Admin/Pages/TestTab.php:458
+#: src/Providers/Sendlayer/QuickConnect.php:696
+msgid "Dismiss"
+msgstr ""
+
+#: src/Admin/Pages/TestTab.php:486
+msgid "Pro Tip:"
+msgstr ""
+
+#. translators: %s - product name wrapped in , e.g. WPConsent.
+#: src/Admin/Pages/TestTab.php:491
+msgid "%1$s with our sister plugin %2$s"
+msgstr ""
+
+#. translators: %s - product name (e.g. WPConsent).
+#: src/Admin/Pages/TestTab.php:506
+msgid "Install %s (Free)"
+msgstr ""
+
+#. translators: %1$s - plugin name (bold). %2$s - settings-page hyperlink.
+#: src/Admin/Pages/TestTab.php:515
+msgid "%1$s was installed and activated, please visit their %2$s to configure it."
+msgstr ""
+
+#: src/Admin/Pages/TestTab.php:517
+msgid "settings page"
+msgstr ""
+
+#: src/Admin/Pages/TestTab.php:556
+msgid "Smarter Spam Protection for WordPress"
+msgstr ""
+
+#: src/Admin/Pages/TestTab.php:557
+msgid "Catch spam in milliseconds with AI, invisible to your real visitors."
+msgstr ""
+
+#: src/Admin/Pages/TestTab.php:558
+msgid "Stop spam at the door"
+msgstr ""
+
+#: src/Admin/Pages/TestTab.php:577
+msgid "Stay GDPR & Privacy Compliant"
+msgstr ""
+
+#: src/Admin/Pages/TestTab.php:578
+msgid "Add a cookie consent banner to your site and meet privacy laws in minutes."
+msgstr ""
+
+#: src/Admin/Pages/TestTab.php:579
+msgid "Stay GDPR & Privacy compliant"
+msgstr ""
+
+#: src/Admin/Pages/TestTab.php:601
+msgid "Add Secure WordPress Backups"
+msgstr ""
+
+#: src/Admin/Pages/TestTab.php:602
+msgid "Automated, encrypted backups with 1-click restore to keep your site safe."
+msgstr ""
+
+#: src/Admin/Pages/TestTab.php:603
+msgid "Protect your site with automated backups"
+msgstr ""
+
+#: src/Admin/Pages/Tools.php:41
+msgid "Tools"
+msgstr ""
+
+#: src/Admin/Recommendations/PageAbstract.php:209
+msgid "Step 1"
+msgstr ""
+
+#: src/Admin/Recommendations/PageAbstract.php:245
+msgid "Step 2"
+msgstr ""
+
+#: src/Admin/Recommendations/PageAbstract.php:364
+msgid "You do not have permission."
+msgstr ""
+
+#: src/Admin/Recommendations/PageAbstract.php:372
+msgid "Plugin unavailable."
+msgstr ""
+
+#. translators: %1$s - Lite plugin download URL.
+#: src/Admin/Recommendations/PageAbstract.php:423
+msgid "Could not install the plugin automatically. Please download it and install it manually."
+msgstr ""
+
+#. translators: %1$s - Plugins page URL.
+#: src/Admin/Recommendations/PageAbstract.php:435
+msgid "Could not activate the plugin. Please activate it on the Plugins page."
+msgstr ""
+
+#: src/Admin/Recommendations/PageAbstract.php:446
+#: src/Admin/WooCommerceActiveLayerEducation.php:377
+msgid "Installing..."
+msgstr ""
+
+#: src/Admin/Recommendations/PageAbstract.php:447
+#: src/Admin/WooCommerceActiveLayerEducation.php:378
+msgid "Activating..."
+msgstr ""
+
+#: src/Admin/Recommendations/PageAbstract.php:450
+#: src/Admin/Recommendations/Pages/WPCode.php:182
+#: src/Admin/Recommendations/Pages/WPConsent.php:206
+msgid "Install Now"
+msgstr ""
+
+#: src/Admin/Recommendations/PageAbstract.php:451
+msgid "Activate Now"
+msgstr ""
+
+#: src/Admin/Recommendations/PageAbstract.php:452
+msgid "Download Now"
+msgstr ""
+
+#: src/Admin/Recommendations/PageAbstract.php:453
+msgid "Go to Plugins page"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/ActiveLayer.php:61
+msgid "Block Spam Without Blocking Real Visitors"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/ActiveLayer.php:73
+msgid "WP Mail SMTP ♥ ActiveLayer"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/ActiveLayer.php:86
+msgid "ActiveLayer uses server-side AI to catch spam in milliseconds, invisible to your real visitors and deadly to bots. No CAPTCHAs, no friction, no lost conversions."
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/ActiveLayer.php:87
+msgid "Smarter protection for your forms, comments, and reviews."
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/ActiveLayer.php:101
+msgid "AI detection using content, sender reputation, and behavior signals."
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/ActiveLayer.php:102
+msgid "No CAPTCHAs, no friction for real visitors."
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/ActiveLayer.php:103
+msgid "Millisecond verdicts, invisible to legitimate users."
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/ActiveLayer.php:104
+msgid "Works with 9+ popular form plugins out of the box."
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/ActiveLayer.php:117
+msgid "ActiveLayer screenshot"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/ActiveLayer.php:149
+#: src/Admin/Recommendations/Pages/Duplicator.php:149
+#: src/Admin/Recommendations/Pages/Universally.php:149
+#: src/Admin/Recommendations/Pages/WPCode.php:149
+#: src/Admin/Recommendations/Pages/WPConsent.php:166
+#: src/Admin/Recommendations/Pages/WPVibe.php:149
+msgid "Step 3"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/ActiveLayer.php:150
+msgid "Protect Even More of Your Site"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/ActiveLayer.php:151
+msgid "Upgrade your plan for higher limits and advanced controls across all your forms, comments, and reviews."
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/ActiveLayer.php:171
+#: src/Admin/Recommendations/Pages/Universally.php:171
+#: src/Admin/Recommendations/Pages/WPCode.php:171
+#: src/Admin/Recommendations/Pages/WPConsent.php:189
+#: src/Providers/OptionsAbstract.php:417
+msgid "Learn More"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/ActiveLayer.php:253
+#: src/Admin/WooCommerceActiveLayerEducation.php:310
+msgid "Install & Activate ActiveLayer"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/ActiveLayer.php:265
+msgid "Install ActiveLayer from the WordPress.org plugin repository."
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/ActiveLayer.php:289
+msgid "Install ActiveLayer"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/ActiveLayer.php:301
+#: src/Admin/Recommendations/Pages/ActiveLayer.php:373
+msgid "ActiveLayer Installed & Activated"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/ActiveLayer.php:313
+#: src/Admin/WooCommerceActiveLayerEducation.php:314
+msgid "Activate ActiveLayer"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/ActiveLayer.php:325
+msgid "Connect ActiveLayer"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/ActiveLayer.php:337
+msgid "Add your free API key on the settings page to switch on protection."
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/ActiveLayer.php:349
+#: src/Admin/Recommendations/Pages/Universally.php:347
+msgid "Open Settings"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/ActiveLayer.php:361
+#: src/Admin/Recommendations/Pages/Universally.php:359
+#: src/Admin/Recommendations/Pages/WPCode.php:383
+#: src/Admin/Recommendations/Pages/WPConsent.php:448
+#: src/Admin/Recommendations/Pages/WPVibe.php:359
+msgid "Setup Complete"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/Duplicator.php:61
+msgid "WP Mail SMTP Delivers It. Duplicator Protects It."
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/Duplicator.php:73
+msgid "WP Mail SMTP ♥ Duplicator"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/Duplicator.php:86
+msgid "Your email settings and sender reputation live in your database. One bad update, one crash, and everything is gone. Duplicator backs up your entire WordPress site automatically so you can restore with one click."
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/Duplicator.php:87
+msgid "Trusted by over 1.5 million websites."
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/Duplicator.php:101
+msgid "Back up your entire site automatically."
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/Duplicator.php:102
+msgid "Restore your site with one click if anything goes wrong."
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/Duplicator.php:103
+msgid "Store backups safely in Google Drive, Dropbox, or Amazon S3."
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/Duplicator.php:104
+msgid "Schedule daily backups so you never have to think about it."
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/Duplicator.php:117
+msgid "Duplicator screenshot"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/Duplicator.php:150
+msgid "Set Up Scheduled Cloud Backups"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/Duplicator.php:151
+msgid "Keep your data safe forever with automatic daily backups to Google Drive, Dropbox, or Amazon S3."
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/Duplicator.php:173
+msgid "Set Up Cloud Backups"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/Duplicator.php:183
+msgid "Cloud Backups Set Up"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/Duplicator.php:359
+msgid "Install & Activate Duplicator"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/Duplicator.php:371
+msgid "Your first step toward bulletproof backups."
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/Duplicator.php:395
+msgid "Install Duplicator"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/Duplicator.php:407
+msgid "Duplicator Installed & Activated"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/Duplicator.php:419
+msgid "Activate Duplicator"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/Duplicator.php:431
+msgid "Create Your First Backup"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/Duplicator.php:443
+msgid "Back up your site, in just one click."
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/Duplicator.php:455
+msgid "Create First Backup"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/Duplicator.php:467
+msgid "Backup Created"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/Duplicator.php:479
+msgid "Duplicator Pro Installed & Activated"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/Universally.php:61
+msgid "Translate Your Website and Go Global in Minutes"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/Universally.php:73
+msgid "WP Mail SMTP ♥ Universally"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/Universally.php:86
+msgid "Universally uses AI to translate your entire WordPress site into 110+ languages in minutes, understanding context and keeping your brand voice intact. No coding required."
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/Universally.php:87
+msgid "Built for global reach. Designed for everyone."
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/Universally.php:101
+msgid "AI translation into 110+ languages."
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/Universally.php:102
+msgid "Multilingual SEO with translated URLs and meta."
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/Universally.php:103
+msgid "Auto-translates new content when you publish."
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/Universally.php:104
+msgid "Exclude specific pages, classes, or IDs from translation."
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/Universally.php:117
+msgid "Universally screenshot"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/Universally.php:150
+msgid "Reach Even More of the World"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/Universally.php:151
+msgid "Upgrade your plan for more languages and higher translation limits."
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/Universally.php:251
+msgid "Install & Activate Universally"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/Universally.php:263
+msgid "Install Universally from the WordPress.org plugin repository."
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/Universally.php:287
+msgid "Install Universally"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/Universally.php:299
+#: src/Admin/Recommendations/Pages/Universally.php:371
+msgid "Universally Installed & Activated"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/Universally.php:311
+msgid "Activate Universally"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/Universally.php:323
+msgid "Connect Universally"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/Universally.php:335
+msgid "Add your API key on the settings page and choose the languages for your site."
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/WPCode.php:61
+msgid "Add Custom Code to WordPress Without Breaking Your Site"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/WPCode.php:73
+msgid "WP Mail SMTP ♥ WPCode"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/WPCode.php:86
+msgid "WPCode lets you safely add PHP, JavaScript, CSS, and HTML snippets with no theme edits and no risk of losing changes when your theme updates. Built-in error handling keeps your site from breaking."
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/WPCode.php:87
+msgid "Trusted by over 3 million websites."
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/WPCode.php:101
+msgid "Add snippets without editing theme files."
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/WPCode.php:102
+msgid "PHP, JavaScript, CSS, and HTML all supported."
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/WPCode.php:103
+msgid "100+ ready-made snippets for common tasks."
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/WPCode.php:104
+msgid "Auto-insert and one-click toggle for every snippet."
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/WPCode.php:117
+msgid "WPCode screenshot"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/WPCode.php:150
+msgid "Get Advanced Code Snippet Features"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/WPCode.php:151
+msgid "WPCode Pro adds conditional logic, a cloud snippet library, revisions, and conversion pixels to your workflow."
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/WPCode.php:181
+#: src/Admin/Recommendations/Pages/WPCode.php:395
+msgid "WPCode Pro Installed & Activated"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/WPCode.php:287
+msgid "Install WPCode from the WordPress.org plugin repository."
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/WPCode.php:299
+msgid "WPCode"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/WPCode.php:311
+msgid "Install WPCode"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/WPCode.php:323
+msgid "WPCode Installed & Activated"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/WPCode.php:347
+msgid "Set Up WPCode"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/WPCode.php:359
+msgid "Open WPCode to create your first snippet and start customizing your site safely."
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/WPCode.php:371
+msgid "Open WPCode"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/WPConsent.php:78
+msgid "Make Your Website Privacy-Compliant in Minutes"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/WPConsent.php:90
+msgid "WP Mail SMTP ♥ WPConsent"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/WPConsent.php:103
+msgid "Build trust with clear, compliant privacy practices. WPConsent adds clean, professional banners and handles the technical side for you."
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/WPConsent.php:104
+msgid "Built for transparency. Designed for ease."
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/WPConsent.php:118
+msgid "A professional banner that fits your site."
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/WPConsent.php:119
+msgid "Tools like Google Analytics and Facebook Pixel paused until consent."
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/WPConsent.php:120
+msgid "Peace of mind knowing you’re aligned with global laws."
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/WPConsent.php:121
+msgid "Self-hosted. Your data remains on your site."
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/WPConsent.php:134
+msgid "WPConsent screenshot"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/WPConsent.php:167
+msgid "Get Advanced Cookie Consent Features"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/WPConsent.php:168
+msgid "With WPConsent Pro you can access advanced features like geolocation, popup layout, records of consent, multilanguage support, and more."
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/WPConsent.php:205
+#: src/Admin/Recommendations/Pages/WPConsent.php:460
+msgid "WPConsent Pro Installed & Activated"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/WPConsent.php:340
+msgid "Install & Activate WPConsent"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/WPConsent.php:352
+msgid "Install WPConsent from the WordPress.org plugin repository."
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/WPConsent.php:376
+msgid "Install WPConsent"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/WPConsent.php:388
+msgid "WPConsent Installed & Activated"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/WPConsent.php:400
+msgid "Activate WPConsent"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/WPConsent.php:412
+msgid "Set Up WPConsent"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/WPConsent.php:424
+msgid "WPConsent has an intuitive setup wizard to guide you through the cookie consent configuration process."
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/WPConsent.php:436
+msgid "Run Setup Wizard"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/WPVibe.php:61
+msgid "Your AI Just Learned WordPress"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/WPVibe.php:73
+msgid "WP Mail SMTP ♥ WPVibe"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/WPVibe.php:86
+msgid "WPVibe connects your AI assistant straight to your WordPress site, so you can create posts, manage plugins, and run tasks just by asking. Built-in guardrails keep your site safe."
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/WPVibe.php:87
+msgid "Manage WordPress through conversation, not clicking."
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/WPVibe.php:101
+msgid "Works with Claude, ChatGPT, Cursor, and any MCP client."
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/WPVibe.php:102
+msgid "Create and edit posts, pages, media, and users through conversation."
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/WPVibe.php:103
+msgid "Run 40+ WP-CLI commands without leaving your AI chat."
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/WPVibe.php:104
+msgid "One-click setup in about a minute."
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/WPVibe.php:117
+msgid "WPVibe screenshot"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/WPVibe.php:150
+msgid "See What Your AI Can Do"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/WPVibe.php:151
+msgid "WPVibe is free to use. Explore the docs to see every ability your AI assistant now has for your WordPress site."
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/WPVibe.php:171
+msgid "View Docs"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/WPVibe.php:263
+msgid "Install WPVibe from the WordPress.org plugin repository."
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/WPVibe.php:275
+msgid "WPVibe"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/WPVibe.php:287
+msgid "Install WPVibe"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/WPVibe.php:299
+#: src/Admin/Recommendations/Pages/WPVibe.php:371
+msgid "WPVibe Installed & Activated"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/WPVibe.php:323
+msgid "Connect Your AI Assistant"
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/WPVibe.php:335
+msgid "Open WPVibe and authorize your AI client in one click."
+msgstr ""
+
+#: src/Admin/Recommendations/Pages/WPVibe.php:347
+msgid "Open WPVibe"
+msgstr ""
+
+#: src/Admin/Recommendations/RecommendedPlugins.php:131
+msgid "Privacy Compliance"
+msgstr ""
+
+#: src/Admin/Recommendations/RecommendedPlugins.php:140
+msgid "Spam Protection"
+msgstr ""
+
+#: src/Admin/Recommendations/RecommendedPlugins.php:148
+msgid "Backups"
+msgstr ""
+
+#: src/Admin/Recommendations/RecommendedPlugins.php:165
+msgid "Translations"
+msgstr ""
+
+#: src/Admin/Review.php:137
+msgid "Are you enjoying WP Mail SMTP?"
+msgstr ""
+
+#: src/Admin/Review.php:140
+msgid "No"
+msgstr ""
+
+#: src/Admin/Review.php:144
+msgid "We're sorry to hear you aren't enjoying WP Mail SMTP. We would love a chance to improve. Could you take a minute and let us know what we can do better?"
+msgstr ""
+
+#: src/Admin/Review.php:151
+msgid "Provide Feedback"
+msgstr ""
+
+#: src/Admin/Review.php:156
+msgid "No thanks"
+msgstr ""
+
+#: src/Admin/Review.php:161
+msgid "That's fantastic! Would you consider giving it a 5-star rating on WordPress.org? It will help other users with email issues and it will mean the world to us!"
+msgstr ""
+
+#: src/Admin/Review.php:164
+msgid "Yes, I'll rate it with 5-stars"
+msgstr ""
+
+#: src/Admin/Review.php:166
+msgid "No, maybe later"
+msgstr ""
+
+#: src/Admin/Review.php:167
+msgid "I already did"
+msgstr ""
+
+#: src/Admin/SetupWizard.php:247
+msgid "We're sorry, the %mailer% mailer is not available on your plan. Please upgrade to the PRO plan to unlock all these awesome features."
+msgstr ""
+
+#. Translators: %s - discount value $50
+#: src/Admin/SetupWizard.php:252
+msgid "Bonus: WP Mail SMTP users get %s off regular price,
+
+
|
+
' ) !== false ) {
+ $email_content = str_replace( '', "\n" . $pixel_img . "\n", $email_content );
+ } else {
+ $email_content .= $pixel_img;
+ }
+
+ return $email_content;
+ }
+
+ /**
+ * Return pixel image.
+ *
+ * @since 2.9.0
+ *
+ * @param array $event_data Event data from request.
+ */
+ public function get_response( $event_data ) {
+
+ header( 'Cache-Control: must-revalidate, no-cache, no-store, max-age=0, no-transform' );
+ header( 'Pragma: no-cache' );
+ header( 'Content-Type: image/gif' );
+ echo "\x47\x49\x46\x38\x39\x61\x01\x00\x01\x00\x90\x00\x00\xff\x00\x00\x00\x00\x00\x21\xf9\x04\x05\x10\x00\x00\x00\x2c\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02\x04\x01\x00\x3b";
+ exit;
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Tracking/Migration.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Tracking/Migration.php
new file mode 100644
index 00000000..9b64c7d8
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Tracking/Migration.php
@@ -0,0 +1,122 @@
+get_pro()->get_logs()->is_enabled_tracking();
+ }
+
+ /**
+ * Create the email tracking events DB table structure.
+ *
+ * @since 2.9.0
+ */
+ protected function migrate_to_1() {
+
+ global $wpdb;
+
+ $table = Tracking::get_events_table_name();
+ $charset_collate = $wpdb->get_charset_collate();
+
+ $sql = "CREATE TABLE IF NOT EXISTS `$table` (
+ `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
+ `email_log_id` INT UNSIGNED NOT NULL,
+ `event_type` VARCHAR(20) NOT NULL,
+ `date_created` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ `object_id` INT UNSIGNED,
+ PRIMARY KEY (id),
+ INDEX email_log (email_log_id),
+ INDEX event_type (event_type)
+ )
+ ENGINE='InnoDB'
+ {$charset_collate};";
+
+ // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
+ $result = $wpdb->query( $sql );
+
+ if ( ! empty( $wpdb->last_error ) ) {
+ update_option( self::ERROR_OPTION_NAME, $wpdb->last_error, false );
+ }
+
+ // Save the current version to DB.
+ if ( $result !== false ) {
+ $this->update_db_ver( 1 );
+ }
+ }
+
+ /**
+ * Create the email tracking links DB table structure.
+ *
+ * @since 2.9.0
+ */
+ protected function migrate_to_2() {
+
+ $this->maybe_required_older_migrations( 2 );
+
+ global $wpdb;
+
+ $table = Tracking::get_links_table_name();
+ $charset_collate = $wpdb->get_charset_collate();
+
+ $sql = "CREATE TABLE IF NOT EXISTS `$table` (
+ `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
+ `email_log_id` INT UNSIGNED NOT NULL,
+ `url` TEXT,
+ PRIMARY KEY (id),
+ INDEX email_log (email_log_id)
+ )
+ ENGINE='InnoDB'
+ {$charset_collate};";
+
+ // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
+ $result = $wpdb->query( $sql );
+
+ if ( ! empty( $wpdb->last_error ) ) {
+ update_option( self::ERROR_OPTION_NAME, $wpdb->last_error, false );
+ }
+
+ // Save the current version to DB.
+ if ( $result !== false ) {
+ $this->update_db_ver( 2 );
+ }
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Tracking/Tracking.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Tracking/Tracking.php
new file mode 100644
index 00000000..c7474f42
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Tracking/Tracking.php
@@ -0,0 +1,234 @@
+is_enabled = $is_enabled;
+ }
+
+ /**
+ * Register hooks.
+ *
+ * @since 2.9.0
+ */
+ public function hooks() {
+
+ // Register routes to handle tracking events, regardless of tracking being enabled (link redirects have to work).
+ add_action( 'rest_api_init', [ $this, 'register_rest_route' ] );
+
+ if ( $this->is_enabled ) {
+ // Inject tracking code to email content.
+ add_action( 'wp_mail_smtp_mailcatcher_pre_send_before', [ $this, 'inject_tracking_code' ], 20 );
+ add_action( 'wp_mail_smtp_mailcatcher_smtp_pre_send_before', [ $this, 'inject_tracking_code' ], 20 );
+ }
+ }
+
+ /**
+ * Register REST routes to handle tracking events.
+ *
+ * @since 2.9.0
+ */
+ public function register_rest_route() {
+
+ register_rest_route(
+ 'wp-mail-smtp/v1',
+ '/e/(?P.+)',
+ [
+ 'methods' => 'GET',
+ 'callback' => [ $this, 'handle_injectable_event' ],
+ 'permission_callback' => '__return_true',
+ ]
+ );
+ }
+
+ /**
+ * Inject tracking code to email content.
+ *
+ * @since 2.9.0
+ *
+ * @param MailCatcherInterface $mailcatcher The MailCatcher object.
+ */
+ public function inject_tracking_code( $mailcatcher ) {
+
+ if ( ! ( new Events() )->is_valid_db() ) {
+ return;
+ }
+
+ $email_log_id = wp_mail_smtp()->get_pro()->get_logs()->get_current_email_id();
+
+ // Skip if there is no email log ID or email content type is not html.
+ // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
+ if ( empty( $email_log_id ) || $mailcatcher->ContentType !== 'text/html' ) {
+ return;
+ }
+
+ // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
+ $content = $mailcatcher->Body;
+
+ $events = [
+ Injectable\OpenEmailEvent::class,
+ Injectable\ClickLinkEvent::class,
+ ];
+
+ foreach ( $events as $event_class ) {
+ $event = new $event_class( $email_log_id );
+
+ if ( $event->is_active() ) {
+ /**
+ * Filters whether inject tracking code or not.
+ *
+ * @since 2.9.0
+ *
+ * @param bool $is_trackable Whether inject tracking code or not.
+ * @param AbstractInjectableEvent $event Event object.
+ */
+ if ( ! apply_filters( 'wp_mail_smtp_pro_emails_logs_tracking_tracking_inject_tracking_code', true, $event ) ) {
+ continue;
+ }
+
+ $content = $event->inject( $content );
+ }
+ }
+
+ // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
+ $mailcatcher->Body = $content;
+ }
+
+ /**
+ * Handle injectable tracking event.
+ * Tracking event must be handled even if tracking is disabled for getting correct
+ * response (e.g. redirect for click link event or image for open email event).
+ *
+ * @since 2.9.0
+ *
+ * @param WP_REST_Request $request Request object.
+ *
+ * @return mixed REST or custom response.
+ */
+ public function handle_injectable_event( $request ) {
+
+ // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
+ $data = base64_decode( $request->get_param( 'data' ) );
+
+ parse_str( $data, $args );
+
+ // Exit if required event arguments are missed.
+ if ( ! isset( $args['data']['email_log_id'] ) || ! isset( $args['data']['event_type'] ) || ! isset( $args['hash'] ) ) {
+ return false;
+ }
+
+ $tracking_data = $args['data'];
+
+ $email_log_id = intval( $tracking_data['email_log_id'] );
+ $event_type = sanitize_key( $tracking_data['event_type'] );
+
+ $event_factory = new EventFactory();
+
+ $event = $event_factory->create_event( $event_type, $email_log_id );
+
+ // Exit if HMAC authentication is failed.
+ if ( ! $event->verify_signature( $tracking_data, $args['hash'] ) ) {
+ return false;
+ }
+
+ $event->set_request( $request );
+
+ // Set event related object ID if it's present.
+ if ( ! empty( $tracking_data['object_id'] ) && is_numeric( $tracking_data['object_id'] ) ) {
+ $event->set_object_id( intval( $tracking_data['object_id'] ) );
+ }
+
+ // Persist event to DB only if DB tables are available and tracking is enabled.
+ if ( ( new Events() )->is_valid_db() && $event->is_active() ) {
+ $event->persist();
+ }
+
+ /**
+ * Fires after tracking event was processed.
+ *
+ * @since 3.5.0
+ *
+ * @param array $tracking_data The tracking data passed via the URL.
+ */
+ do_action( 'wp_mail_smtp_pro_emails_logs_tracking_handle_injectable_event', $tracking_data );
+
+ return $event->get_response( $tracking_data );
+ }
+
+ /**
+ * Get the email tracking events DB table name.
+ *
+ * @since 2.9.0
+ *
+ * @return string Email tracking events DB table name, prefixed.
+ */
+ public static function get_events_table_name() {
+
+ global $wpdb;
+
+ return $wpdb->prefix . self::BASE_EVENTS_DB_NAME;
+ }
+
+ /**
+ * Get the email tracking links DB table name.
+ *
+ * @since 2.9.0
+ *
+ * @return string Email tracking links DB table name, prefixed.
+ */
+ public static function get_links_table_name() {
+
+ global $wpdb;
+
+ return $wpdb->prefix . self::BASE_LINKS_DB_NAME;
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/AbstractProcessor.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/AbstractProcessor.php
new file mode 100644
index 00000000..b5d70ff6
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/AbstractProcessor.php
@@ -0,0 +1,47 @@
+provider = $provider;
+ }
+
+ /**
+ * Validate webhook incoming request.
+ *
+ * @since 3.3.0
+ *
+ * @param \WP_REST_Request $request Webhook request.
+ *
+ * @return bool
+ */
+ public function validate( \WP_REST_Request $request ) {
+
+ // Skip validation. Validation must be configured on the server level if needed.
+ return true;
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/AbstractProvider.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/AbstractProvider.php
new file mode 100644
index 00000000..1f7903eb
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/AbstractProvider.php
@@ -0,0 +1,285 @@
+mailer_name = $mailer_name;
+ $this->options = Options::init()->get_group( $mailer_name );
+ }
+
+ /**
+ * Initialize provider if necessary.
+ *
+ * @since 3.3.0
+ */
+ public function init() { }
+
+ /**
+ * Get the name/slug of the mailer.
+ *
+ * @since 3.3.0
+ *
+ * @return string
+ */
+ public function get_mailer_name() {
+
+ return $this->mailer_name;
+ }
+
+ /**
+ * Get the plugin mailer object.
+ *
+ * @since 3.3.0
+ *
+ * @return MailerAbstract
+ */
+ public function get_mailer() {
+
+ if ( is_null( $this->mailer ) ) {
+ $phpmailer = wp_mail_smtp()->get_processor()->get_phpmailer();
+ $this->mailer = wp_mail_smtp()->get_providers()->get_mailer( $this->mailer_name, $phpmailer );
+ }
+
+ return $this->mailer;
+ }
+
+ /**
+ * Get the mailed DB option.
+ *
+ * @since 3.3.0
+ *
+ * @param string $key Option key.
+ *
+ * @return mixed|null
+ */
+ public function get_option( $key ) {
+
+ return isset( $this->options[ $key ] ) ? $this->options[ $key ] : null;
+ }
+
+ /**
+ * Get webhook url.
+ *
+ * @since 3.3.0
+ *
+ * @return string
+ */
+ public function get_url() {
+
+ return get_rest_url( null, 'wp-mail-smtp/v1/webhooks/' . $this->mailer_name );
+ }
+
+ /**
+ * Create subscription.
+ *
+ * @since 3.3.0
+ *
+ * @return true|\WP_Error
+ */
+ public function subscribe() {
+
+ $result = $this->get_subscriber()->subscribe();
+
+ if ( ! is_wp_error( $result ) ) {
+ $this->set_setup_status( Webhooks::SUCCESS_SETUP );
+ } else {
+ $this->set_setup_status( Webhooks::FAILED_SETUP );
+ $this->set_subscription_error( $result, 'subscribe' );
+ }
+
+ return $result;
+ }
+
+ /**
+ * Remove subscription.
+ *
+ * @since 3.3.0
+ *
+ * @return true|\WP_Error
+ */
+ public function unsubscribe() {
+
+ // Skip actual unsubscribe request if it was not setup properly.
+ if ( $this->get_setup_status() !== Webhooks::SUCCESS_SETUP ) {
+ $this->reset_setup();
+
+ return true;
+ }
+
+ $result = $this->get_subscriber()->unsubscribe();
+
+ if ( is_wp_error( $result ) ) {
+ $this->set_subscription_error( $result, 'unsubscribe' );
+ }
+
+ $this->reset_setup();
+
+ return $result;
+ }
+
+ /**
+ * Check subscription.
+ *
+ * @since 3.3.0
+ *
+ * @return bool|\WP_Error
+ */
+ public function is_subscribed() {
+
+ return $this->get_subscriber()->is_subscribed();
+ }
+
+ /**
+ * Verify subscription and set appropriate status.
+ *
+ * @since 3.3.0
+ */
+ public function verify_subscription() {
+
+ $is_subscribed = $this->is_subscribed();
+
+ if ( is_wp_error( $is_subscribed ) || $this->get_setup_status() === Webhooks::MANUAL_SETUP ) {
+ return;
+ }
+
+ if ( $this->get_setup_status() === Webhooks::SUCCESS_SETUP && ! $is_subscribed ) {
+ $this->set_setup_status( Webhooks::BROKEN_SETUP );
+ } elseif ( $this->get_setup_status() !== Webhooks::SUCCESS_SETUP && $is_subscribed ) {
+ $this->set_setup_status( Webhooks::SUCCESS_SETUP );
+ }
+ }
+
+ /**
+ * Set setup status.
+ *
+ * @since 3.3.0
+ *
+ * @param string $status Status.
+ */
+ public function set_setup_status( $status ) {
+
+ $updated_settings = [
+ $this->mailer_name => [
+ 'webhooks_setup' => $status,
+ ],
+ ];
+
+ Options::init()->set( $updated_settings, false, false );
+ }
+
+ /**
+ * Get setup status.
+ *
+ * @since 3.3.0
+ *
+ * @return string
+ */
+ public function get_setup_status() {
+
+ return Options::init()->get( $this->mailer_name, 'webhooks_setup' );
+ }
+
+ /**
+ * Reset setup options.
+ *
+ * @since 3.3.0
+ */
+ public function reset_setup() {
+
+ $updated_settings = [
+ $this->mailer_name => [
+ 'webhooks_setup' => '',
+ ],
+ ];
+
+ Options::init()->set( $updated_settings, false, false );
+ }
+
+ /**
+ * Set subscription error.
+ *
+ * @since 3.3.0
+ *
+ * @param \WP_Error $error WP Error object.
+ * @param string $action Error action.
+ */
+ protected function set_subscription_error( $error, $action ) {
+
+ $error_message = implode( "\r\n", array_unique( $error->get_error_messages() ) );
+
+ DebugEvents::add(
+ sprintf( /* translators: %1$s - mailer name; %2$s - action; %3$s - error message. */
+ esc_html__( 'Webhooks subscription. Mailer: %1$s. Action: %2$s. Error: %3$s.', 'wp-mail-smtp-pro' ),
+ $this->get_mailer_name(),
+ $action,
+ $error_message
+ )
+ );
+
+ update_option( Webhooks::SUBSCRIPTION_ERROR_OPTION_NAME, $error_message );
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/AbstractSubscriber.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/AbstractSubscriber.php
new file mode 100644
index 00000000..f8cdd85b
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/AbstractSubscriber.php
@@ -0,0 +1,32 @@
+provider = $provider;
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Events/Delivered.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Events/Delivered.php
new file mode 100644
index 00000000..2eb1e35b
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Events/Delivered.php
@@ -0,0 +1,31 @@
+set_status( Email::STATUS_DELIVERED );
+ $email->save();
+
+ return true;
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Events/EventInterface.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Events/EventInterface.php
new file mode 100644
index 00000000..7e659456
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Events/EventInterface.php
@@ -0,0 +1,25 @@
+get_error_message( $data );
+ $error_code = $this->get_error_code( $email, $data );
+
+ // Build display text with translatable wrapper.
+ if ( ! empty( $raw_reason ) ) {
+ /* translators: %s - The reason the email was rejected. */
+ $error_text = sprintf( esc_html__( 'The email failed to be delivered. Reason: %s', 'wp-mail-smtp-pro' ), $raw_reason );
+ } else {
+ $error_text = esc_html__( 'The email failed to be delivered. No specific reason was provided by the API.', 'wp-mail-smtp-pro' );
+ }
+
+ $email->set_status( Email::STATUS_UNSENT );
+ $email->set_error_text( $error_text );
+ $email->save();
+
+ // Trigger alerts.
+ ( new Alerts() )->handle_hard_bounced_email( $error_text, $email );
+
+ /**
+ * Fires when an email delivery failure is detected via webhook.
+ *
+ * @since 4.8.0
+ *
+ * @param string $mailer_slug Current mailer name.
+ * @param string $error_code Error code.
+ * @param string $error_message Raw error message (not translated).
+ */
+ do_action( 'wp_mail_smtp_email_delivery_failed', $email->get_mailer(), $error_code, $raw_reason ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
+
+ return true;
+ }
+
+ /**
+ * Get error message from event data.
+ *
+ * @since 3.3.0
+ *
+ * @param array $data Event data.
+ *
+ * @return string
+ */
+ protected function get_error_message( $data ) {
+
+ return '';
+ }
+
+ /**
+ * Get error code from event data.
+ *
+ * @since 4.8.0
+ *
+ * @param Email $email Email object.
+ * @param array $data Event data.
+ *
+ * @return string
+ */
+ protected function get_error_code( $email, $data ) {
+
+ return 'unknown';
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/ProcessorInterface.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/ProcessorInterface.php
new file mode 100644
index 00000000..27d43a82
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/ProcessorInterface.php
@@ -0,0 +1,33 @@
+get_params();
+
+ $event_type = $request->get_param( 'type' );
+ $data = $request->get_param( 'data' );
+ $message_id = '';
+
+ if ( ! empty( $data['email']['message']['id'] ) ) {
+ $message_id = $data['email']['message']['id'];
+ }
+
+ if ( empty( $event_type ) || empty( $message_id ) ) {
+ return false;
+ }
+
+ $email = Email::get_by_message_id( $message_id );
+
+ if ( empty( $email ) ) {
+ return false;
+ }
+
+ $event = false;
+
+ if ( $event_type === 'activity.delivered' ) {
+ $event = new Delivered();
+ } elseif ( $event_type === 'activity.hard_bounced' ) {
+ $event = new Failed();
+ }
+
+ if ( empty( $event ) ) {
+ return false;
+ }
+
+ $event->handle( $email, $params['data'] );
+
+ return true;
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/MailerSend/Provider.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/MailerSend/Provider.php
new file mode 100644
index 00000000..a315932b
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/MailerSend/Provider.php
@@ -0,0 +1,47 @@
+processor ) ) {
+ $this->processor = new Processor( $this );
+ }
+
+ return $this->processor;
+ }
+
+ /**
+ * Get the webhook subscription manager.
+ *
+ * @since 4.5.0
+ *
+ * @return AbstractSubscriber
+ */
+ public function get_subscriber() {
+
+ if ( is_null( $this->subscriber ) ) {
+ $this->subscriber = new Subscriber( $this );
+ }
+
+ return $this->subscriber;
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/MailerSend/Subscriber.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/MailerSend/Subscriber.php
new file mode 100644
index 00000000..356c967e
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/MailerSend/Subscriber.php
@@ -0,0 +1,325 @@
+get_subscription();
+
+ if ( is_wp_error( $subscription ) ) {
+ return $subscription;
+ }
+
+ // Already subscribed.
+ if ( $subscription !== false && empty( array_diff( self::EVENTS, $subscription['events'] ) ) ) {
+ return true;
+ }
+
+ $domain_id = $this->get_domain_id();
+
+ if ( is_wp_error( $domain_id ) ) {
+ return $domain_id;
+ }
+
+ $existing_events = ! empty( $subscription['events'] ) ? $subscription['events'] : [];
+ $events = array_unique( array_merge( $existing_events, self::EVENTS ) );
+ $body = [
+ 'url' => $this->provider->get_url(),
+ 'name' => 'WP Mail SMTP',
+ 'events' => array_values( $events ),
+ ];
+
+ if ( ! $subscription ) {
+ $body['domain_id'] = $domain_id;
+
+ $response = $this->request( 'POST', $body );
+ } else {
+ $response = $this->request( 'PUT', $body, $subscription['id'] );
+ }
+
+ if ( is_wp_error( $response ) ) {
+ return $response;
+ }
+
+ return true;
+ }
+
+ /**
+ * Remove webhook subscription.
+ *
+ * @since 4.5.0
+ *
+ * @return true|WP_Error
+ */
+ public function unsubscribe() {
+
+ $subscription = $this->get_subscription();
+
+ if ( is_wp_error( $subscription ) || empty( $subscription['id'] ) ) {
+ return true;
+ }
+
+ $response = $this->request( 'DELETE', [], $subscription['id'] );
+
+ if ( is_wp_error( $response ) ) {
+ return $response;
+ }
+
+ return true;
+ }
+
+ /**
+ * Check webhook subscription.
+ *
+ * @since 4.5.0
+ *
+ * @return bool
+ */
+ public function is_subscribed() {
+
+ $subscription = $this->get_subscription();
+
+ if ( is_wp_error( $subscription ) ) {
+ return false;
+ }
+
+ return ! empty( $subscription['id'] ) && ! empty( $subscription['enabled'] ) && empty( array_diff( self::EVENTS, $subscription['events'] ) );
+ }
+
+ /**
+ * Get domain ID from API.
+ *
+ * @since 4.5.0
+ *
+ * @return string|WP_Error Domain ID or WP_Error on failure.
+ */
+ protected function get_domain_id() {
+
+ $domain_name = $this->get_domain_name_from_email();
+
+ if ( is_wp_error( $domain_name ) ) {
+ return $domain_name;
+ }
+
+ $domains = $this->get_domains_from_api();
+
+ if ( is_wp_error( $domains ) ) {
+ return $domains;
+ }
+
+ // Find matching domain and return its ID.
+ foreach ( $domains as $domain ) {
+ if ( ! empty( $domain['name'] ) && $domain['name'] === $domain_name ) {
+ return $domain['id'];
+ }
+ }
+
+ return new WP_Error(
+ 'mailersend_webhook_api_error',
+ sprintf(
+ /* translators: %s - domain name. */
+ esc_html__( 'Domain "%s" not found.', 'wp-mail-smtp-pro' ),
+ esc_html( $domain_name )
+ )
+ );
+ }
+
+ /**
+ * Get domain name from From Email setting.
+ *
+ * @since 4.5.0
+ *
+ * @return string|WP_Error Domain name or WP_Error on failure.
+ */
+ private function get_domain_name_from_email() {
+
+ $options = Options::init();
+ $from_email = $options->get( 'mail', 'from_email' );
+ $domain_name = WP::get_email_domain( $from_email );
+
+ if ( empty( $domain_name ) ) {
+ return new WP_Error(
+ 'mailersend_webhook_api_error',
+ esc_html__( 'Unable to extract domain from "From Email".', 'wp-mail-smtp-pro' )
+ );
+ }
+
+ return $domain_name;
+ }
+
+ /**
+ * Get domains from MailerSend API.
+ *
+ * @since 4.5.0
+ *
+ * @return array|WP_Error Response data array or WP_Error on failure.
+ */
+ protected function get_domains_from_api() {
+
+ $endpoint = 'https://api.mailersend.com/v1/domains';
+ $args = [
+ 'method' => 'GET',
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->provider->get_option( 'api_key' ),
+ 'Accept' => 'application/json',
+ ],
+ ];
+
+ $response = wp_remote_request( $endpoint, $args );
+
+ if ( is_wp_error( $response ) ) {
+ return $response;
+ }
+
+ $response_code = (int) wp_remote_retrieve_response_code( $response );
+
+ if ( $response_code !== 200 ) {
+ return $this->get_response_error( $response );
+ }
+
+ $body = json_decode( wp_remote_retrieve_body( $response ), true );
+
+ if ( empty( $body['data'] ) || ! is_array( $body['data'] ) ) {
+ return new WP_Error(
+ 'mailersend_webhook_api_error',
+ esc_html__( 'No domains found.', 'wp-mail-smtp-pro' )
+ );
+ }
+
+ return $body['data'];
+ }
+
+ /**
+ * Get subscription if available.
+ *
+ * @since 4.5.0
+ *
+ * @return array|false|WP_Error
+ */
+ protected function get_subscription() {
+
+ $response = $this->request();
+
+ if ( is_wp_error( $response ) ) {
+ return $response;
+ }
+
+ if ( empty( $response['data'] ) ) {
+ return false;
+ }
+
+ $webhooks = array_filter(
+ $response['data'],
+ function ( $data ) {
+ return ! empty( $data['url'] ) && $data['url'] === $this->provider->get_url();
+ }
+ );
+
+ return ! empty( $webhooks ) ? array_values( $webhooks )[0] : false;
+ }
+
+ /**
+ * Make API request.
+ *
+ * @since 4.5.0
+ *
+ * @param string $method Request method.
+ * @param array $params Request body.
+ * @param string $webhook_id Webhook ID.
+ *
+ * @return mixed|WP_Error
+ */
+ protected function request( $method = 'GET', $params = [], $webhook_id = '' ) {
+
+ $endpoint = 'https://api.mailersend.com/v1/webhooks';
+
+ if ( ! empty( $webhook_id ) ) {
+ $endpoint .= '/' . $webhook_id;
+ }
+
+ $args = [
+ 'method' => $method,
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->provider->get_option( 'api_key' ),
+ 'Accept' => 'application/json',
+ 'Content-Type' => 'application/json',
+ ],
+ ];
+
+ if ( $method === 'GET' ) {
+ $endpoint = add_query_arg( $params, $endpoint );
+ $args['body']['domain_id'] = $this->get_domain_id();
+ } else {
+ $args['body'] = wp_json_encode( $params );
+ }
+
+ $response = wp_remote_request( $endpoint, $args );
+
+ if ( is_wp_error( $response ) ) {
+ return $response;
+ }
+
+ if ( ! in_array( wp_remote_retrieve_response_code( $response ), [ 200, 201, 202, 204 ], true ) ) {
+ return $this->get_response_error( $response );
+ }
+
+ return json_decode( wp_remote_retrieve_body( $response ), true );
+ }
+
+ /**
+ * Retrieve errors from MailerSend API response.
+ *
+ * @since 4.5.0
+ *
+ * @param array $response Response array.
+ *
+ * @return WP_Error
+ */
+ protected function get_response_error( $response ) {
+
+ $body = json_decode( wp_remote_retrieve_body( $response ) );
+ $error_code = wp_remote_retrieve_response_code( $response );
+
+ if ( ! empty( $body->message ) ) {
+ $message = $body->message;
+
+ $error_text = Helpers::format_error_message( $message, $error_code );
+ } else {
+ $error_text = WP::wp_remote_get_response_error_message( $response );
+ }
+
+ return new WP_Error( $error_code, $error_text );
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Mailgun/Events/Failed.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Mailgun/Events/Failed.php
new file mode 100644
index 00000000..3acd825c
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Mailgun/Events/Failed.php
@@ -0,0 +1,52 @@
+get_param( 'signature' );
+ $timestamp = isset( $signature_data['timestamp'] ) ? $signature_data['timestamp'] : '';
+ $token = isset( $signature_data['token'] ) ? $signature_data['token'] : '';
+ $signature = isset( $signature_data['signature'] ) ? $signature_data['signature'] : '';
+
+ if ( ! empty( $timestamp ) && ! empty( $token ) && ! empty( $signature ) ) {
+ $hmac = hash_hmac( 'sha256', $timestamp . $token, WPMS_MAILGUN_WEBHOOK_SIGNING_KEY );
+ $is_valid = hash_equals( $hmac, $signature );
+ } else {
+ $is_valid = false;
+ }
+ }
+
+ // Return 406 status to prevent Mailgun from retrying the request.
+ if ( ! $is_valid ) {
+ return new WP_Error(
+ 'invalid_webhook_signature',
+ __( 'Invalid webhook signature.', 'wp-mail-smtp-pro' ),
+ [ 'status' => 406 ]
+ );
+ }
+
+ return true;
+ }
+
+ /**
+ * Handle webhook incoming request.
+ *
+ * @since 3.3.0
+ *
+ * @param \WP_REST_Request $request Webhook request.
+ *
+ * @return bool
+ */
+ public function handle( \WP_REST_Request $request ) {
+
+ $event_data = $request->get_param( 'event-data' );
+
+ if ( empty( $event_data['event'] ) || empty( $event_data['message']['headers']['message-id'] ) ) {
+ return false;
+ }
+
+ $message_id = $event_data['message']['headers']['message-id'];
+ $email = Email::get_by_message_id( $message_id );
+
+ if ( empty( $email ) ) {
+ return false;
+ }
+
+ $event = false;
+
+ if ( $event_data['event'] === 'delivered' ) {
+ $event = new Delivered();
+ } elseif ( $event_data['event'] === 'failed' && $event_data['severity'] === 'permanent' ) {
+ $event = new Failed();
+ }
+
+ if ( $event === false ) {
+ return false;
+ }
+
+ $event->handle( $email, $event_data );
+
+ return true;
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Mailgun/Provider.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Mailgun/Provider.php
new file mode 100644
index 00000000..a58d850d
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Mailgun/Provider.php
@@ -0,0 +1,47 @@
+processor ) ) {
+ $this->processor = new Processor( $this );
+ }
+
+ return $this->processor;
+ }
+
+ /**
+ * Get the webhook subscription manager.
+ *
+ * @since 3.3.0
+ *
+ * @return AbstractSubscriber
+ */
+ public function get_subscriber() {
+
+ if ( is_null( $this->subscriber ) ) {
+ $this->subscriber = new Subscriber( $this );
+ }
+
+ return $this->subscriber;
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Mailgun/Subscriber.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Mailgun/Subscriber.php
new file mode 100644
index 00000000..38d631f2
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Mailgun/Subscriber.php
@@ -0,0 +1,263 @@
+get_subscribed_urls( $event );
+
+ if ( is_wp_error( $subscribed_urls ) ) {
+ return $subscribed_urls;
+ }
+
+ // Already subscribed.
+ if ( in_array( $this->provider->get_url(), $subscribed_urls, true ) ) {
+ continue;
+ }
+
+ $subscribed_urls[] = $this->provider->get_url();
+
+ $body = [
+ 'url' => $subscribed_urls,
+ ];
+
+ $method = count( $subscribed_urls ) === 1 ? 'POST' : 'PUT';
+
+ // Create or update subscription.
+ $response = $this->request( $event, $method, $body );
+
+ if ( is_wp_error( $response ) ) {
+ return $response;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Remove webhook subscription.
+ *
+ * @since 3.3.0
+ *
+ * @return true|WP_Error
+ */
+ public function unsubscribe() {
+
+ foreach ( self::EVENTS as $event ) {
+ $subscribed_urls = $this->get_subscribed_urls( $event );
+
+ if ( is_wp_error( $subscribed_urls ) ) {
+ return $subscribed_urls;
+ }
+
+ // Already unsubscribed.
+ if ( ! in_array( $this->provider->get_url(), $subscribed_urls, true ) ) {
+ continue;
+ }
+
+ if ( count( $subscribed_urls ) === 1 ) {
+
+ // Delete event.
+ $response = $this->request( $event, 'DELETE' );
+ } else {
+
+ // Filter out our subscription url, but keep other.
+ $subscribed_urls = array_filter(
+ $subscribed_urls,
+ function ( $url ) {
+ return $url !== $this->provider->get_url();
+ }
+ );
+
+ $body = [
+ 'url' => $subscribed_urls,
+ ];
+
+ // Update subscription.
+ $response = $this->request( $event, 'PUT', $body );
+ }
+
+ if ( is_wp_error( $response ) ) {
+ return $response;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Check webhook subscription.
+ *
+ * @since 3.3.0
+ *
+ * @return bool|WP_Error
+ */
+ public function is_subscribed() {
+
+ foreach ( self::EVENTS as $event ) {
+ $subscribed_urls = $this->get_subscribed_urls( $event );
+
+ if ( is_wp_error( $subscribed_urls ) ) {
+ return $subscribed_urls;
+ }
+
+ if ( ! in_array( $this->provider->get_url(), $subscribed_urls, true ) ) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Get event subscribed urls.
+ *
+ * @since 3.3.0
+ *
+ * @param string $event Event name.
+ *
+ * @return array|WP_Error
+ */
+ protected function get_subscribed_urls( $event ) {
+
+ $response = $this->request( $event );
+
+ if ( is_wp_error( $response ) && $response->get_error_code() !== 404 ) {
+ return $response;
+ }
+
+ return ! is_wp_error( $response ) ? $response['webhook']['urls'] : [];
+ }
+
+ /**
+ * Performs Mailgun webhooks API HTTP request.
+ *
+ * @since 3.3.0
+ *
+ * @param string $event Event name.
+ * @param string $method Request method.
+ * @param array $params Request params.
+ *
+ * @return mixed|WP_Error
+ */
+ protected function request( $event, $method = 'GET', $params = [] ) {
+
+ // Prepare the API endpoint.
+ $endpoint = $this->provider->get_option( 'region' ) === 'EU' ? Mailer::API_BASE_EU : Mailer::API_BASE_US;
+ $endpoint .= 'domains/' . sanitize_text_field( $this->provider->get_option( 'domain' ) ) . '/webhooks';
+
+ $args = [
+ 'method' => $method,
+ 'headers' => [
+ // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions
+ 'Authorization' => 'Basic ' . base64_encode( 'api:' . $this->provider->get_option( 'api_key' ) ),
+ ],
+ ];
+
+ if ( $method === 'POST' ) {
+ $params['id'] = $event;
+ } else {
+ $endpoint .= '/' . $event;
+ }
+
+ if ( $method === 'GET' ) {
+ $endpoint = add_query_arg( $params, $endpoint );
+ } else {
+ $args['body'] = $this->prepare_body_params( $params );
+ }
+
+ $response = wp_remote_request( $endpoint, $args );
+
+ if ( is_wp_error( $response ) ) {
+ return $response;
+ }
+
+ if ( wp_remote_retrieve_response_code( $response ) !== 200 ) {
+ return $this->get_response_error( $response );
+ }
+
+ return json_decode( wp_remote_retrieve_body( $response ), true );
+ }
+
+ /**
+ * Retrieve errors from Mailgun API response.
+ *
+ * @since 3.3.0
+ *
+ * @param array $response Response array.
+ *
+ * @return WP_Error
+ */
+ protected function get_response_error( $response ) {
+
+ $body = json_decode( wp_remote_retrieve_body( $response ) );
+
+ if ( ! empty( $body->message ) ) {
+ $error_text = Helpers::format_error_message( $body->message );
+ } else {
+ $error_text = WP::wp_remote_get_response_error_message( $response );
+ }
+
+ return new WP_Error( wp_remote_retrieve_response_code( $response ), $error_text );
+ }
+
+ /**
+ * Prepare body params as a query string.
+ * We need to build such structure to have the ability to pass several `url` params.
+ *
+ * @since 3.3.0
+ *
+ * @param array $params Params array.
+ *
+ * @return string
+ */
+ protected function prepare_body_params( $params ) {
+
+ $result = [];
+
+ foreach ( $params as $key => $value ) {
+ if ( $key === 'url' && is_array( $value ) ) {
+ foreach ( $value as $url ) {
+ $result[] = [ $key => $url ];
+ }
+ } else {
+ $result[] = [ $key => $value ];
+ }
+ }
+
+ return implode( '&', array_map( 'http_build_query', $result ) );
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Mailjet/Events/Failed.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Mailjet/Events/Failed.php
new file mode 100644
index 00000000..a905a038
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Mailjet/Events/Failed.php
@@ -0,0 +1,50 @@
+get_param( 'event' ) );
+ $message_id = $request->get_param( 'MessageID' );
+
+ if ( empty( $event_type ) || empty( $message_id ) ) {
+ return false;
+ }
+
+ $email = Email::get_by_message_id( $message_id );
+
+ if ( empty( $email ) ) {
+ return false;
+ }
+
+ $event_data = [
+ 'error' => $request->get_param( 'error' ),
+ 'comment' => $request->get_param( 'comment' ),
+ ];
+ $event = false;
+
+ if ( $event_type === 'sent' ) {
+ $event = new Delivered();
+ } elseif ( $event_type === 'bounce' || $event_type === 'blocked' ) {
+ $event = new Failed();
+ }
+
+ if ( $event === false ) {
+ return false;
+ }
+
+ $event->handle( $email, $event_data );
+
+ return true;
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Mailjet/Provider.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Mailjet/Provider.php
new file mode 100644
index 00000000..f414fdfa
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Mailjet/Provider.php
@@ -0,0 +1,47 @@
+processor ) ) {
+ $this->processor = new Processor( $this );
+ }
+
+ return $this->processor;
+ }
+
+ /**
+ * Get the webhook subscription manager.
+ *
+ * @since 4.2.0
+ *
+ * @return AbstractSubscriber
+ */
+ public function get_subscriber() {
+
+ if ( is_null( $this->subscriber ) ) {
+ $this->subscriber = new Subscriber( $this );
+ }
+
+ return $this->subscriber;
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Mailjet/Subscriber.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Mailjet/Subscriber.php
new file mode 100644
index 00000000..3a16b756
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Mailjet/Subscriber.php
@@ -0,0 +1,258 @@
+get_subscription( $event );
+
+ if ( is_wp_error( $subscription ) ) {
+ return $subscription;
+ }
+
+ // Already subscribed.
+ if ( $subscription !== false ) {
+ continue;
+ }
+
+ $body = [
+ 'EventType' => $event,
+ 'Url' => $this->provider->get_url(),
+ ];
+
+ // Create subscription.
+ $response = $this->request( 'POST', $body );
+
+ if ( is_wp_error( $response ) ) {
+ return $response;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Remove webhook subscription.
+ *
+ * @since 4.2.0
+ *
+ * @return true|WP_Error
+ */
+ public function unsubscribe() {
+
+ foreach ( self::EVENTS as $event ) {
+ $subscription = $this->get_subscription( $event );
+
+ if ( is_wp_error( $subscription ) ) {
+ return $subscription;
+ }
+
+ // Already unsubscribed.
+ if ( $subscription === false ) {
+ continue;
+ }
+
+ $response = $this->request(
+ 'DELETE',
+ [],
+ $subscription['ID']
+ );
+
+ if ( is_wp_error( $response ) ) {
+ return $response;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Check webhook subscription.
+ *
+ * @since 4.2.0
+ *
+ * @return bool|WP_Error
+ */
+ public function is_subscribed() {
+
+ foreach ( self::EVENTS as $event ) {
+ $subscription = $this->get_subscription( $event );
+
+ if ( is_wp_error( $subscription ) ) {
+ return $subscription;
+ }
+
+ if ( $subscription === false ) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Get subscription if available.
+ *
+ * @since 4.2.0
+ *
+ * @param static $event Event name.
+ *
+ * @return array|false|WP_Error
+ */
+ protected function get_subscription( $event ) {
+
+ $response = $this->request();
+
+ if ( is_wp_error( $response ) ) {
+ return $response;
+ }
+
+ $webhooks = array_filter(
+ $response['Data'],
+ function ( $data ) use ( $event ) {
+ return $data['Url'] === $this->provider->get_url() && $data['EventType'] === $event;
+ }
+ );
+
+ return ! empty( $webhooks ) ? array_values( $webhooks )[0] : false;
+ }
+
+ /**
+ * Performs webhooks API HTTP request.
+ *
+ * @since 4.2.0
+ *
+ * @param string $method Request method.
+ * @param array $params Request params.
+ * @param array $webhook_id SendLayer webhooks ID.
+ *
+ * @return mixed|WP_Error
+ */
+ public function request( $method = 'GET', $params = [], $webhook_id = false ) {
+
+ $endpoint = 'https://api.mailjet.com/v3/REST/eventcallbackurl';
+ $user = $this->provider->get_option( 'api_key' );
+ $pass = $this->provider->get_option( 'secret_key' );
+
+ $args = [
+ 'method' => $method,
+ 'headers' => [
+ 'Authorization' => 'Basic ' . base64_encode( "$user:$pass" ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
+ 'Accept' => 'application/json',
+ 'Content-Type' => 'application/json',
+ ],
+ ];
+
+ if ( $method === 'GET' ) {
+ $endpoint = add_query_arg( $params, $endpoint );
+ } else {
+ $args['body'] = wp_json_encode( $params );
+ }
+
+ if ( $webhook_id !== false ) {
+ $endpoint .= '/' . $webhook_id;
+ }
+
+ $response = wp_remote_request( $endpoint, $args );
+
+ if ( is_wp_error( $response ) ) {
+ return $response;
+ }
+
+ $response_code = wp_remote_retrieve_response_code( $response );
+
+ if (
+ ( $method === 'GET' && $response_code !== 200 ) ||
+ ( $method === 'POST' && $response_code !== 201 ) ||
+ ( $method === 'DELETE' && $response_code !== 204 )
+ ) {
+ return $this->get_response_error( $response );
+ }
+
+ return json_decode( wp_remote_retrieve_body( $response ), true );
+ }
+
+ /**
+ * Retrieve errors from API response.
+ *
+ * @since 4.2.0
+ *
+ * @param array $response Response array.
+ *
+ * @return WP_Error
+ */
+ protected function get_response_error( $response ) {
+
+ $body = json_decode( wp_remote_retrieve_body( $response ) );
+ $error_text = [];
+
+ // phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
+ if ( ! empty( $body->ErrorMessage ) ) {
+ $code = ! empty( $body->StatusCode ) ? $body->StatusCode : '';
+ $error_message = $this->get_error_message( $body->ErrorMessage );
+
+ $error_text[] = Helpers::format_error_message( $error_message, $code );
+ } else {
+ $error_text[] = WP::wp_remote_get_response_error_message( $response );
+ }
+ // phpcs:enable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
+
+ return new WP_Error( wp_remote_retrieve_response_code( $response ), implode( WP::EOL, $error_text ) );
+ }
+
+ /**
+ * Append an explanation to specific error messages.
+ *
+ * @since 4.2.0
+ *
+ * @param string $message Error message.
+ *
+ * @return string
+ */
+ private function get_error_message( $message ) {
+
+ // Multiple sites are trying to register webhooks for the same event.
+ if ( strpos( strtolower( $message ), 'mj18' ) !== false ) {
+ $message = sprintf(
+ '%1$s %2$s',
+ esc_html__( 'Mailjet webhook registration limit reached.', 'wp-mail-smtp-pro' ),
+ $message
+ );
+ }
+
+ return $message;
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Mandrill/Events/Failed.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Mandrill/Events/Failed.php
new file mode 100644
index 00000000..8cd6cd3d
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Mandrill/Events/Failed.php
@@ -0,0 +1,59 @@
+get_params();
+
+ if ( empty( $params['mandrill_events'] ) ) {
+ return false;
+ }
+
+ $events = json_decode( $params['mandrill_events'], true );
+
+ if ( empty( $events ) || ! is_array( $events ) ) {
+ return false;
+ }
+
+ $processed = false;
+
+ foreach ( $events as $event_data ) {
+ if ( empty( $event_data['event'] ) || empty( $event_data['msg']['_id'] ) ) {
+ continue;
+ }
+
+ $event_type = $event_data['event'];
+ $message_id = $event_data['msg']['_id'];
+ $email = Email::get_by_message_id( $message_id );
+
+ if ( empty( $email ) ) {
+ continue;
+ }
+
+ $event = false;
+
+ switch ( $event_type ) {
+ case 'delivered':
+ $event = new Delivered();
+ break;
+
+ case 'reject':
+ case 'hard_bounce':
+ $event = new Failed();
+ break;
+ }
+
+ if ( empty( $event ) ) {
+ continue;
+ }
+
+ /*
+ * If an email already has a failed status, do not handle the "reject" event to prevent duplicate alerts.
+ * Usually, the "reject" event is returned instantly in the email-sending API request, but if the email has
+ * attachments, Mandrill queues such emails and triggers the "reject" event asynchronously.
+ */
+ if ( $event_type === 'reject' && $email->has_failed() ) {
+ $processed = true;
+
+ continue;
+ }
+
+ $event->handle( $email, $event_data );
+ $processed = true;
+ }
+
+ return $processed;
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Mandrill/Provider.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Mandrill/Provider.php
new file mode 100644
index 00000000..71bfdfc9
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Mandrill/Provider.php
@@ -0,0 +1,47 @@
+processor ) ) {
+ $this->processor = new Processor( $this );
+ }
+
+ return $this->processor;
+ }
+
+ /**
+ * Get the webhook subscription manager.
+ *
+ * @since 4.6.0
+ *
+ * @return AbstractSubscriber
+ */
+ public function get_subscriber() {
+
+ if ( is_null( $this->subscriber ) ) {
+ $this->subscriber = new Subscriber( $this );
+ }
+
+ return $this->subscriber;
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Mandrill/Subscriber.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Mandrill/Subscriber.php
new file mode 100644
index 00000000..4e891258
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Mandrill/Subscriber.php
@@ -0,0 +1,218 @@
+get_subscription();
+
+ if ( is_wp_error( $subscription ) ) {
+ return $subscription;
+ }
+
+ // Already subscribed.
+ if ( $subscription !== false && empty( array_diff( self::EVENTS, $subscription['events'] ) ) ) {
+ return true;
+ }
+
+ $params = [
+ 'url' => $this->provider->get_url(),
+ 'description' => 'WP Mail SMTP',
+ 'events' => self::EVENTS,
+ ];
+
+ if ( ! $subscription ) {
+ // Create subscription.
+ $response = $this->request( '/webhooks/add', $params );
+ } else {
+ $params['id'] = $subscription['id'];
+ $response = $this->request( '/webhooks/update', $params );
+ }
+
+ if ( is_wp_error( $response ) ) {
+ return $response;
+ }
+
+ return true;
+ }
+
+ /**
+ * Remove webhook subscription.
+ *
+ * @since 4.6.0
+ *
+ * @return true|WP_Error
+ */
+ public function unsubscribe() {
+
+ $subscription = $this->get_subscription();
+
+ if ( is_wp_error( $subscription ) ) {
+ return $subscription;
+ }
+
+ // Already unsubscribed.
+ if ( $subscription === false ) {
+ return true;
+ }
+
+ // Delete subscription.
+ $response = $this->request( '/webhooks/delete', [ 'id' => $subscription['id'] ] );
+
+ if ( is_wp_error( $response ) ) {
+ return $response;
+ }
+
+ return true;
+ }
+
+ /**
+ * Check webhook subscription.
+ *
+ * @since 4.6.0
+ *
+ * @return bool|WP_Error
+ */
+ public function is_subscribed() {
+
+ $subscription = $this->get_subscription();
+
+ if ( is_wp_error( $subscription ) ) {
+ return $subscription;
+ }
+
+ // Subscription does not exist.
+ if ( $subscription === false ) {
+ return false;
+ }
+
+ return empty( array_diff( self::EVENTS, $subscription['events'] ) );
+ }
+
+ /**
+ * Get subscription if available.
+ *
+ * @since 4.6.0
+ *
+ * @return array|false|WP_Error
+ */
+ protected function get_subscription() {
+
+ $response = $this->request( '/webhooks/list' );
+
+ if ( is_wp_error( $response ) ) {
+ return $response;
+ }
+
+ $webhooks = array_filter(
+ $response,
+ function ( $data ) {
+ return $data['url'] === $this->provider->get_url();
+ }
+ );
+
+ return ! empty( $webhooks ) ? array_values( $webhooks )[0] : false;
+ }
+
+ /**
+ * Performs Mandrill webhooks API HTTP request.
+ *
+ * @since 4.6.0
+ *
+ * @param string $endpoint API endpoint path.
+ * @param array $params Request params.
+ *
+ * @return mixed|WP_Error
+ */
+ protected function request( $endpoint, $params = [] ) {
+
+ $params = array_merge(
+ [
+ 'key' => $this->provider->get_option( 'api_key' ),
+ ],
+ $params
+ );
+
+ $args = [
+ 'method' => 'POST',
+ 'headers' => [
+ 'Content-Type' => 'application/json',
+ ],
+ 'body' => wp_json_encode( $params ),
+ ];
+
+ $response = wp_remote_request( self::API_BASE . $endpoint, $args );
+
+ if ( is_wp_error( $response ) ) {
+ return $response;
+ }
+
+ if ( wp_remote_retrieve_response_code( $response ) !== 200 ) {
+ return $this->get_response_error( $response );
+ }
+
+ return json_decode( wp_remote_retrieve_body( $response ), true );
+ }
+
+ /**
+ * Retrieve errors from Mandrill API response.
+ *
+ * @since 4.6.0
+ *
+ * @param array $response Response array.
+ *
+ * @return WP_Error
+ */
+ protected function get_response_error( $response ) {
+
+ $body = json_decode( wp_remote_retrieve_body( $response ), true );
+ $error_text = WP::wp_remote_get_response_error_message( $response );
+
+ if ( ! empty( $body['name'] ) && ! empty( $body['message'] ) ) {
+ $error_text = Helpers::format_error_message( $body['message'], $body['name'] );
+ }
+
+ return new WP_Error( wp_remote_retrieve_response_code( $response ), $error_text );
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Postmark/Events/Failed.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Postmark/Events/Failed.php
new file mode 100644
index 00000000..10c399e6
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Postmark/Events/Failed.php
@@ -0,0 +1,43 @@
+get_params();
+
+ if ( empty( $event_data['RecordType'] ) || empty( $event_data['MessageID'] ) ) {
+ return false;
+ }
+
+ $email = Email::get_by_message_id( $event_data['MessageID'] );
+
+ if ( empty( $email ) ) {
+ return false;
+ }
+
+ $event = false;
+
+ if ( $event_data['RecordType'] === 'Delivery' ) {
+ $event = new Delivered();
+ } elseif ( $event_data['RecordType'] === 'Bounce' && $event_data['Type'] === 'HardBounce' ) {
+ $event = new Failed();
+ }
+
+ if ( $event === false ) {
+ return false;
+ }
+
+ $event->handle( $email, $event_data );
+
+ return true;
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Postmark/Provider.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Postmark/Provider.php
new file mode 100644
index 00000000..5be9992b
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Postmark/Provider.php
@@ -0,0 +1,217 @@
+message_stream = $this->get_option( 'message_stream' );
+ }
+
+ /**
+ * Initialize provider.
+ *
+ * @since 3.3.0
+ */
+ public function init() {
+
+ parent::init();
+
+ add_action( 'wp_mail_smtp_mailcatcher_send_before', [ $this, 'record_additional_message_stream' ] );
+ }
+
+ /**
+ * Get the webhook processor.
+ *
+ * @since 3.3.0
+ *
+ * @return AbstractProcessor
+ */
+ public function get_processor() {
+
+ if ( is_null( $this->processor ) ) {
+ $this->processor = new Processor( $this );
+ }
+
+ return $this->processor;
+ }
+
+ /**
+ * Get the webhook subscription manager.
+ *
+ * @since 3.3.0
+ *
+ * @return AbstractSubscriber
+ */
+ public function get_subscriber() {
+
+ if ( is_null( $this->subscriber ) ) {
+ $this->subscriber = new Subscriber( $this );
+ }
+
+ return $this->subscriber;
+ }
+
+ /**
+ * Remove subscription.
+ *
+ * @since 3.3.0
+ *
+ * @return true|\WP_Error
+ */
+ public function unsubscribe() {
+
+ // Skip actual unsubscribe request if it was not setup properly.
+ if ( $this->get_setup_status() !== Webhooks::SUCCESS_SETUP ) {
+ $this->reset_setup();
+
+ return true;
+ }
+
+ $errors = new \WP_Error();
+ $message_streams = array_merge( [ $this->message_stream ], $this->get_additional_message_streams() );
+
+ foreach ( $message_streams as $message_stream ) {
+ $this->set_message_stream( $message_stream );
+
+ $result = $this->get_subscriber()->unsubscribe();
+
+ if ( is_wp_error( $result ) ) {
+ $errors->add( $result->get_error_code(), $result->get_error_message() );
+ }
+ }
+
+ if ( ! empty( $errors->errors ) ) {
+ $this->set_subscription_error( $errors, 'unsubscribe' );
+ }
+
+ $this->reset_setup();
+
+ return ! empty( $errors->errors ) ? $errors : true;
+ }
+
+ /**
+ * Reset setup options.
+ *
+ * @since 3.3.0
+ */
+ public function reset_setup() {
+
+ $options = Options::init();
+ $all_opt = $options->get_all_raw();
+
+ $all_opt[ $this->mailer_name ]['webhooks_setup'] = '';
+ $all_opt[ $this->mailer_name ]['additional_message_streams'] = [];
+ $options->set( $all_opt );
+ }
+
+ /**
+ * Get message stream.
+ *
+ * @since 3.3.0
+ *
+ * @return string
+ */
+ public function get_message_stream() {
+
+ return $this->message_stream;
+ }
+
+ /**
+ * Set message stream.
+ *
+ * @since 3.3.0
+ *
+ * @param string $message_stream Message stream.
+ */
+ public function set_message_stream( $message_stream ) {
+
+ $this->message_stream = $message_stream;
+ }
+
+ /**
+ * Get additional message streams.
+ *
+ * @since 3.3.0
+ *
+ * @return array
+ */
+ public function get_additional_message_streams() {
+
+ $additional_message_streams = $this->get_option( 'additional_message_streams' );
+
+ return ! is_null( $additional_message_streams ) ? (array) $additional_message_streams : [];
+ }
+
+ /**
+ * Record additional message stream before send email and create subscription.
+ * Additional message stream can be set via filter.
+ * In most cases, there will not be additional message streams.
+ *
+ * @since 3.3.0
+ *
+ * @param MailerAbstract $mailer Mailer object.
+ */
+ public function record_additional_message_stream( $mailer ) {
+
+ if ( $mailer->get_mailer_name() !== $this->mailer_name ) {
+ return;
+ }
+
+ if ( $this->get_setup_status() !== Webhooks::SUCCESS_SETUP ) {
+ return;
+ }
+
+ $body = json_decode( $mailer->get_body(), true );
+
+ if (
+ isset( $body['MessageStream'] ) &&
+ $body['MessageStream'] !== $this->message_stream &&
+ ! in_array( $body['MessageStream'], $this->get_additional_message_streams(), true )
+ ) {
+ $this->set_message_stream( $body['MessageStream'] );
+ $is_subscribed = $this->subscribe();
+
+ if ( $is_subscribed === true ) {
+ $updated_settings = [
+ $this->mailer_name => [
+ 'additional_message_streams' => [ sanitize_key( $body['MessageStream'] ) ],
+ ],
+ ];
+
+ Options::init()->set( $updated_settings, false, false );
+ }
+ }
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Postmark/Subscriber.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Postmark/Subscriber.php
new file mode 100644
index 00000000..5cb02bdc
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Postmark/Subscriber.php
@@ -0,0 +1,246 @@
+get_subscription();
+
+ if ( is_wp_error( $subscription ) ) {
+ return $subscription;
+ }
+
+ if ( $subscription !== false ) {
+ $enabled_events = array_keys(
+ array_filter(
+ $subscription['Triggers'],
+ function ( $event ) {
+ return $event['Enabled'] === true;
+ }
+ )
+ );
+
+ // Already subscribed.
+ if ( empty( array_diff( self::EVENTS, $enabled_events ) ) ) {
+ return true;
+ }
+ }
+
+ $triggers = [];
+
+ foreach ( self::EVENTS as $event ) {
+ $triggers[ $event ] = [
+ 'Enabled' => true,
+ ];
+ }
+
+ $body = [
+ 'Url' => $this->provider->get_url(),
+ 'Triggers' => $triggers,
+ ];
+
+ if ( ! empty( $this->provider->get_message_stream() ) ) {
+ $body['MessageStream'] = $this->provider->get_message_stream();
+ }
+
+ // Create subscription.
+ $response = $this->request( 'POST', $body );
+
+ if ( is_wp_error( $response ) ) {
+ return $response;
+ }
+
+ return true;
+ }
+
+ /**
+ * Remove webhook subscription.
+ *
+ * @since 3.3.0
+ *
+ * @return true|WP_Error
+ */
+ public function unsubscribe() {
+
+ $subscription = $this->get_subscription();
+
+ if ( is_wp_error( $subscription ) ) {
+ return $subscription;
+ }
+
+ // Already unsubscribed.
+ if ( $subscription === false ) {
+ return true;
+ }
+
+ // Delete subscription.
+ $response = $this->request( 'DELETE', [], $subscription['ID'] );
+
+ if ( is_wp_error( $response ) ) {
+ return $response;
+ }
+
+ return true;
+ }
+
+ /**
+ * Check webhook subscription.
+ *
+ * @since 3.3.0
+ *
+ * @return bool|WP_Error
+ */
+ public function is_subscribed() {
+
+ $subscription = $this->get_subscription();
+
+ if ( is_wp_error( $subscription ) ) {
+ return $subscription;
+ }
+
+ // Subscription does not exist.
+ if ( $subscription === false ) {
+ return false;
+ }
+
+ foreach ( self::EVENTS as $event ) {
+ if ( $subscription['Triggers'][ $event ]['Enabled'] !== true ) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Get subscription if available.
+ *
+ * @since 3.3.0
+ *
+ * @return array|false|WP_Error
+ */
+ protected function get_subscription() {
+
+ $response = $this->request();
+
+ if ( is_wp_error( $response ) ) {
+ return $response;
+ }
+
+ $provider = $this->provider;
+ $message_stream = ! empty( $provider->get_message_stream() ) ? $provider->get_message_stream() : 'outbound';
+
+ $webhooks = array_filter(
+ $response['Webhooks'],
+ function ( $data ) use ( $message_stream ) {
+ return $data['Url'] === $this->provider->get_url() && $data['MessageStream'] === $message_stream;
+ }
+ );
+
+ return ! empty( $webhooks ) ? array_values( $webhooks )[0] : false;
+ }
+
+ /**
+ * Performs Postmark webhooks API HTTP request.
+ *
+ * @since 3.3.0
+ *
+ * @param string $method Request method.
+ * @param array $params Request params.
+ * @param array $webhook_id Postmark webhooks ID.
+ *
+ * @return mixed|WP_Error
+ */
+ protected function request( $method = 'GET', $params = [], $webhook_id = false ) {
+
+ $endpoint = 'https://api.postmarkapp.com/webhooks';
+
+ $args = [
+ 'method' => $method,
+ 'headers' => [
+ 'X-Postmark-Server-Token' => $this->provider->get_option( 'server_api_token' ),
+ 'Content-Type' => 'application/json',
+ ],
+ ];
+
+ if ( $method === 'GET' ) {
+ $endpoint = add_query_arg( $params, $endpoint );
+ } else {
+ $args['body'] = wp_json_encode( $params );
+ }
+
+ if ( $webhook_id !== false ) {
+ $endpoint .= '/' . $webhook_id;
+ }
+
+ $response = wp_remote_request( $endpoint, $args );
+
+ if ( is_wp_error( $response ) ) {
+ return $response;
+ }
+
+ if ( wp_remote_retrieve_response_code( $response ) !== 200 ) {
+ return $this->get_response_error( $response );
+ }
+
+ return json_decode( wp_remote_retrieve_body( $response ), true );
+ }
+
+ /**
+ * Retrieve errors from Postmark API response.
+ *
+ * @since 3.3.0
+ *
+ * @param array $response Response array.
+ *
+ * @return WP_Error
+ */
+ protected function get_response_error( $response ) {
+
+ $body = json_decode( wp_remote_retrieve_body( $response ) );
+
+ // phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
+ if ( ! empty( $body->Message ) ) {
+ $message = $body->Message;
+ $code = ! empty( $body->ErrorCode ) ? $body->ErrorCode : '';
+
+ $error_text = Helpers::format_error_message( $message, $code );
+ } else {
+ $error_text = WP::wp_remote_get_response_error_message( $response );
+ }
+ // phpcs:enable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
+
+ return new WP_Error( wp_remote_retrieve_response_code( $response ), $error_text );
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Resend/Events/Failed.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Resend/Events/Failed.php
new file mode 100644
index 00000000..f344b793
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Resend/Events/Failed.php
@@ -0,0 +1,57 @@
+get_param( 'type' );
+ $event_data = $request->get_param( 'data' );
+
+ if ( empty( $event_type ) || empty( $event_data['email_id'] ) ) {
+ return false;
+ }
+
+ $message_id = $event_data['email_id'];
+ $email = Email::get_by_message_id( $message_id );
+
+ if ( empty( $email ) ) {
+ return false;
+ }
+
+ $event = false;
+
+ if ( $event_type === Subscriber::EVENT_DELIVERED ) {
+ $event = new Delivered();
+ } elseif ( in_array( $event_type, [ Subscriber::EVENT_FAILED, Subscriber::EVENT_BOUNCED ], true ) ) {
+ $event = new Failed();
+ }
+
+ if ( $event === false ) {
+ return false;
+ }
+
+ $event->handle( $email, $event_data );
+
+ return true;
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Resend/Provider.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Resend/Provider.php
new file mode 100644
index 00000000..c608f06f
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Resend/Provider.php
@@ -0,0 +1,47 @@
+processor ) ) {
+ $this->processor = new Processor( $this );
+ }
+
+ return $this->processor;
+ }
+
+ /**
+ * Get the webhook subscription manager.
+ *
+ * @since 4.7.0
+ *
+ * @return AbstractSubscriber
+ */
+ public function get_subscriber() {
+
+ if ( is_null( $this->subscriber ) ) {
+ $this->subscriber = new Subscriber( $this );
+ }
+
+ return $this->subscriber;
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Resend/Subscriber.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Resend/Subscriber.php
new file mode 100644
index 00000000..3eebca55
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Resend/Subscriber.php
@@ -0,0 +1,250 @@
+get_subscription( static::EVENTS );
+
+ if ( is_wp_error( $subscription ) ) {
+ return $subscription;
+ }
+
+ // Already subscribed.
+ if ( $subscription !== false ) {
+ return true;
+ }
+
+ $body = [
+ 'events' => static::EVENTS,
+ 'endpoint' => $this->provider->get_url(),
+ ];
+
+ // Create subscription.
+ $response = $this->request( 'POST', $body );
+
+ if ( is_wp_error( $response ) ) {
+ return $response;
+ }
+
+ return true;
+ }
+
+ /**
+ * Remove webhook subscription.
+ *
+ * @since 4.7.0
+ *
+ * @return true|WP_Error
+ */
+ public function unsubscribe() {
+
+ $subscription = $this->get_subscription( static::EVENTS );
+
+ if ( is_wp_error( $subscription ) ) {
+ return $subscription;
+ }
+
+ // Already unsubscribed.
+ if ( $subscription === false ) {
+ return true;
+ }
+
+ $response = $this->request( 'DELETE', [], $subscription['id'] );
+
+ if ( is_wp_error( $response ) ) {
+ return $response;
+ }
+
+ return true;
+ }
+
+ /**
+ * Check webhook subscription.
+ *
+ * @since 4.7.0
+ *
+ * @return bool|WP_Error
+ */
+ public function is_subscribed() {
+
+ $subscription = $this->get_subscription( static::EVENTS );
+
+ if ( is_wp_error( $subscription ) ) {
+ return $subscription;
+ }
+
+ if ( $subscription === false ) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Get subscription if available.
+ *
+ * @since 4.7.0
+ *
+ * @param array $events Event names.
+ *
+ * @return array|false|WP_Error
+ */
+ protected function get_subscription( $events ) {
+
+ $response = $this->request();
+
+ if ( is_wp_error( $response ) ) {
+ return $response;
+ }
+
+ $webhooks = array_filter(
+ $response['data'],
+ fn( $data ) => (
+ $data['endpoint'] === $this->provider->get_url() &&
+ empty( array_diff( $events, $data['events'] ) )
+ )
+ );
+
+ return ! empty( $webhooks ) ? array_values( $webhooks )[0] : false;
+ }
+
+ /**
+ * Performs Resend webhooks API HTTP request.
+ *
+ * @since 4.7.0
+ *
+ * @param string $method Request method.
+ * @param array $params Request params.
+ * @param array $webhook_id Resend webhooks ID.
+ *
+ * @return mixed|WP_Error
+ */
+ protected function request( $method = 'GET', $params = [], $webhook_id = false ) {
+
+ $endpoint = 'https://api.resend.com/webhooks';
+
+ $args = [
+ 'method' => $method,
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->provider->get_option( 'api_key' ),
+ 'Content-Type' => 'application/json',
+ ],
+ ];
+
+ if ( $method === 'GET' ) {
+ $endpoint = add_query_arg( $params, $endpoint );
+ } else {
+ $args['body'] = wp_json_encode( $params );
+ }
+
+ if ( $webhook_id !== false ) {
+ $endpoint .= '/' . $webhook_id;
+ }
+
+ $response = wp_remote_request( $endpoint, $args );
+
+ if ( is_wp_error( $response ) ) {
+ return $response;
+ }
+
+ $response_code = wp_remote_retrieve_response_code( $response );
+ $success_statuses = [
+ 200,
+ 201,
+ ];
+
+ if ( ! in_array( $response_code, $success_statuses, true ) ) {
+ return $this->get_response_error( $response );
+ }
+
+ return json_decode( wp_remote_retrieve_body( $response ), true );
+ }
+
+ /**
+ * Retrieve errors from Resend API response.
+ *
+ * @since 4.7.0
+ *
+ * @param array $response Response array.
+ *
+ * @return WP_Error
+ */
+ protected function get_response_error( $response ) {
+
+ $body = json_decode( wp_remote_retrieve_body( $response ) );
+ $error_text = [];
+
+ // phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
+ if ( ! empty( $body->message ) ) {
+ $error_code = ! empty( $body->statusCode ) ? $body->statusCode : ''; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
+ $error_name = ! empty( $body->name ) ? $body->name : '';
+ $error_text[] = Helpers::format_error_message( $error_name, $error_code, $body->message );
+ } else {
+ $error_text[] = WP::wp_remote_get_response_error_message( $response );
+ }
+
+ // phpcs:enable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
+
+ return new WP_Error( wp_remote_retrieve_response_code( $response ), implode( WP::EOL, $error_text ) );
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/SMTP2GO/Events/Failed.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/SMTP2GO/Events/Failed.php
new file mode 100644
index 00000000..47203933
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/SMTP2GO/Events/Failed.php
@@ -0,0 +1,43 @@
+get_param( 'event' );
+ $message_id = $request->get_param( 'email_id' );
+
+ if ( empty( $event_type ) || empty( $message_id ) ) {
+ return false;
+ }
+
+ $email = Email::get_by_message_id( $message_id );
+
+ if ( empty( $email ) ) {
+ return false;
+ }
+
+ // Only consider the first "TO" address.
+ $to = $email->get_people( 'to' );
+ $to = is_array( $to ) ? $to[0] : $to;
+ $recipient = $request->get_param( 'rcpt' );
+
+ // Only consider events related to the first "TO" address.
+ if ( $recipient !== $to ) {
+ return false;
+ }
+
+ $event_data = [
+ 'message' => $request->has_param( 'message' ) ? $request->get_param( 'message' ) : '',
+ 'bounce' => $request->has_param( 'bounce' ) ? $request->get_param( 'bounce' ) : '',
+ ];
+ $event = false;
+
+ if ( $event_type === 'delivered' ) {
+ $event = new Delivered();
+ } elseif ( $event_type === 'bounce' || $event_type === 'reject' ) {
+ $event = new Failed();
+ }
+
+ if ( $event === false ) {
+ return false;
+ }
+
+ $event->handle( $email, $event_data );
+
+ return true;
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/SMTP2GO/Provider.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/SMTP2GO/Provider.php
new file mode 100644
index 00000000..d939b22a
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/SMTP2GO/Provider.php
@@ -0,0 +1,47 @@
+processor ) ) {
+ $this->processor = new Processor( $this );
+ }
+
+ return $this->processor;
+ }
+
+ /**
+ * Get the webhook subscription manager.
+ *
+ * @since 4.1.0
+ *
+ * @return AbstractSubscriber
+ */
+ public function get_subscriber() {
+
+ if ( is_null( $this->subscriber ) ) {
+ $this->subscriber = new Subscriber( $this );
+ }
+
+ return $this->subscriber;
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/SMTP2GO/Subscriber.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/SMTP2GO/Subscriber.php
new file mode 100644
index 00000000..2dee3c25
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/SMTP2GO/Subscriber.php
@@ -0,0 +1,232 @@
+get_subscription( $event );
+
+ if ( is_wp_error( $subscription ) ) {
+ return $subscription;
+ }
+
+ // Already subscribed.
+ if ( $subscription !== false ) {
+ continue;
+ }
+
+ $events[] = $event;
+ }
+
+ // No events left to subscribe for.
+ if ( empty( $events ) ) {
+ return true;
+ }
+
+ $body = [
+ 'events' => $events,
+ 'url' => $this->provider->get_url(),
+ ];
+
+ // Create subscription.
+ $response = $this->request( 'add', $body );
+
+ if ( is_wp_error( $response ) ) {
+ return $response;
+ }
+
+ return true;
+ }
+
+ /**
+ * Remove webhook subscription.
+ *
+ * @since 4.1.0
+ *
+ * @return true|WP_Error
+ */
+ public function unsubscribe() {
+
+ foreach ( self::EVENTS as $event ) {
+ $subscription = $this->get_subscription( $event );
+
+ if ( is_wp_error( $subscription ) ) {
+ return $subscription;
+ }
+
+ // Already unsubscribed.
+ if ( $subscription === false ) {
+ continue;
+ }
+
+ $response = $this->request(
+ 'remove',
+ [
+ 'id' => $subscription['id'],
+ ]
+ );
+
+ if ( is_wp_error( $response ) ) {
+ return $response;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Check webhook subscription.
+ *
+ * @since 4.1.0
+ *
+ * @return bool|WP_Error
+ */
+ public function is_subscribed() {
+
+ foreach ( self::EVENTS as $event ) {
+ $subscription = $this->get_subscription( $event );
+
+ if ( is_wp_error( $subscription ) ) {
+ return $subscription;
+ }
+
+ if ( $subscription === false ) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Get subscription if available.
+ *
+ * @since 4.1.0
+ *
+ * @param static $event Event name.
+ *
+ * @return array|false|WP_Error
+ */
+ protected function get_subscription( $event ) {
+
+ $response = $this->request();
+
+ if ( is_wp_error( $response ) ) {
+ return $response;
+ }
+
+ if ( empty( $response['data'] ) || empty( $response['data']['webhooks'] ) ) {
+ return false;
+ }
+
+ $webhooks = array_filter(
+ $response['data']['webhooks'],
+ function ( $data ) use ( $event ) {
+ return (
+ $data['url'] === $this->provider->get_url() &&
+ in_array( $event, $data['events'], true )
+ );
+ }
+ );
+
+ return ! empty( $webhooks ) ? array_values( $webhooks )[0] : false;
+ }
+
+ /**
+ * Performs webhooks API HTTP request.
+ *
+ * @since 4.1.0
+ *
+ * @param string $action Request action.
+ * @param array $params Request params.
+ *
+ * @return mixed|WP_Error
+ */
+ public function request( $action = 'view', $params = [] ) {
+
+ $endpoint = "https://api.smtp2go.com/v3/webhook/$action";
+
+ $params['api_key'] = $this->provider->get_option( 'api_key' );
+
+ $args = [
+ 'method' => 'POST',
+ 'headers' => [
+ 'Accept' => 'application/json',
+ 'Content-Type' => 'application/json',
+ ],
+ 'body' => wp_json_encode( $params ),
+ ];
+
+ $response = wp_remote_request( $endpoint, $args );
+
+ if ( is_wp_error( $response ) ) {
+ return $response;
+ }
+
+ if ( wp_remote_retrieve_response_code( $response ) !== 200 ) {
+ return $this->get_response_error( $response );
+ }
+
+ return json_decode( wp_remote_retrieve_body( $response ), true );
+ }
+
+ /**
+ * Retrieve errors from API response.
+ *
+ * @since 4.1.0
+ *
+ * @param array $response Response array.
+ *
+ * @return WP_Error
+ */
+ protected function get_response_error( $response ) {
+
+ $body = json_decode( wp_remote_retrieve_body( $response ) );
+ $error_text = [];
+
+ if ( ! empty( $body->data ) && ! empty( $body->data->error ) ) {
+ $code = ! empty( $body->data->error_code ) ? $body->data->error_code : '';
+
+ $error_text[] = Helpers::format_error_message( $body->data->error, $code );
+ } else {
+ $error_text[] = WP::wp_remote_get_response_error_message( $response );
+ }
+
+ return new WP_Error( wp_remote_retrieve_response_code( $response ), implode( WP::EOL, $error_text ) );
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/SMTPcom/Events/Failed.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/SMTPcom/Events/Failed.php
new file mode 100644
index 00000000..4c43cfe1
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/SMTPcom/Events/Failed.php
@@ -0,0 +1,47 @@
+get_params();
+
+ if ( ! is_array( $events ) ) {
+ return false;
+ }
+
+ foreach ( $events as $event_data ) {
+ if ( empty( $event_data['event_label'] ) || empty( $event_data['tracking_id'] ) ) {
+ continue;
+ }
+
+ $email = Email::get_by_message_id( $event_data['tracking_id'] );
+
+ if ( empty( $email ) ) {
+ continue;
+ }
+
+ $event = false;
+
+ if ( $event_data['event_label'] === 'delivered' ) {
+ $event = new Delivered();
+ } elseif ( $event_data['event_label'] === 'bounce' ) {
+ $event = new Failed();
+ }
+
+ if ( $event === false ) {
+ continue;
+ }
+
+ $event->handle( $email, $event_data );
+ }
+
+ return true;
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/SMTPcom/Provider.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/SMTPcom/Provider.php
new file mode 100644
index 00000000..cc90a5b3
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/SMTPcom/Provider.php
@@ -0,0 +1,47 @@
+processor ) ) {
+ $this->processor = new Processor( $this );
+ }
+
+ return $this->processor;
+ }
+
+ /**
+ * Get the webhook subscription manager.
+ *
+ * @since 3.3.0
+ *
+ * @return AbstractSubscriber
+ */
+ public function get_subscriber() {
+
+ if ( is_null( $this->subscriber ) ) {
+ $this->subscriber = new Subscriber( $this );
+ }
+
+ return $this->subscriber;
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/SMTPcom/Subscriber.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/SMTPcom/Subscriber.php
new file mode 100644
index 00000000..7db3fa2b
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/SMTPcom/Subscriber.php
@@ -0,0 +1,208 @@
+is_subscribed() === true ) {
+ return true;
+ }
+
+ foreach ( self::EVENTS as $event ) {
+ $body = [
+ 'channel' => $this->provider->get_option( 'channel' ),
+ 'address' => $this->provider->get_url(),
+ 'medium' => 'http',
+ ];
+
+ $response = $this->request( $event, 'POST', $body );
+
+ if ( is_wp_error( $response ) ) {
+ return $response;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Remove webhook subscription.
+ *
+ * @since 3.3.0
+ *
+ * @return true|\WP_Error
+ */
+ public function unsubscribe() {
+
+ foreach ( self::EVENTS as $event ) {
+
+ // Already unsubscribed.
+ if ( $this->is_event_subscribed( $event ) === false ) {
+ continue;
+ }
+
+ $body = [
+ 'channel' => $this->provider->get_option( 'channel' ),
+ ];
+
+ $response = $this->request( $event, 'DELETE', $body );
+
+ if ( is_wp_error( $response ) ) {
+ return $response;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Check webhook subscription.
+ *
+ * @since 3.3.0
+ *
+ * @return bool|\WP_Error
+ */
+ public function is_subscribed() {
+
+ foreach ( self::EVENTS as $event ) {
+ $is_subscribed = $this->is_event_subscribed( $event );
+
+ if ( is_wp_error( $is_subscribed ) ) {
+ return $is_subscribed;
+ }
+
+ if ( $is_subscribed === false ) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Check particular event webhook subscription.
+ *
+ * @since 3.3.0
+ *
+ * @param string $event Event name.
+ *
+ * @return bool|\WP_Error
+ */
+ protected function is_event_subscribed( $event ) {
+
+ $body = [
+ 'channel' => $this->provider->get_option( 'channel' ),
+ ];
+
+ $response = $this->request( $event, 'GET', $body );
+
+ // Not found any subscriptions.
+ if ( is_wp_error( $response ) && $response->get_error_code() === 400 ) {
+ return false;
+ }
+
+ if ( is_wp_error( $response ) ) {
+ return $response;
+ }
+
+ return isset( $response['data']['address'] ) && $response['data']['address'] === $this->provider->get_url();
+ }
+
+ /**
+ * Performs SMTP.com webhooks API HTTP request.
+ *
+ * @since 3.3.0
+ *
+ * @param string $event Event name.
+ * @param string $method Request method.
+ * @param array $params Request params.
+ *
+ * @return mixed|\WP_Error
+ */
+ protected function request( $event, $method = 'GET', $params = [] ) {
+
+ $endpoint = 'https://api.smtp.com/v4/callbacks/' . $event;
+
+ $args = [
+ 'method' => $method,
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->provider->get_option( 'api_key' ),
+ 'Accept' => 'application/json',
+ 'content-type' => 'application/json',
+ ],
+ ];
+
+ if ( $method === 'GET' ) {
+ $endpoint = add_query_arg( $params, $endpoint );
+ } else {
+ $args['body'] = wp_json_encode( $params );
+ }
+
+ $response = wp_remote_request( $endpoint, $args );
+
+ if ( is_wp_error( $response ) ) {
+ return $response;
+ } elseif ( wp_remote_retrieve_response_code( $response ) !== 200 ) {
+ return $this->get_response_error( $response );
+ }
+
+ return json_decode( wp_remote_retrieve_body( $response ), true );
+ }
+
+ /**
+ * Retrieve errors from SMTP.com API response.
+ *
+ * @since 3.3.0
+ *
+ * @param array $response Response array.
+ *
+ * @return WP_Error
+ */
+ protected function get_response_error( $response ) {
+
+ $body = json_decode( wp_remote_retrieve_body( $response ) );
+ $error_text = [];
+
+ if ( ! empty( $body->data ) ) {
+ foreach ( (array) $body->data as $error_key => $error_message ) {
+ $error_text[] = Helpers::format_error_message( $error_message, $error_key );
+ }
+ } else {
+ $error_text[] = WP::wp_remote_get_response_error_message( $response );
+ }
+
+ return new WP_Error( wp_remote_retrieve_response_code( $response ), implode( WP::EOL, $error_text ) );
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Sendinblue/Events/Failed.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Sendinblue/Events/Failed.php
new file mode 100644
index 00000000..2781c7c8
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Sendinblue/Events/Failed.php
@@ -0,0 +1,42 @@
+get_params();
+
+ if ( empty( $event_data['event'] ) || empty( $event_data['message-id'] ) ) {
+ return false;
+ }
+
+ $message_id = trim( $event_data['message-id'], '<>' );
+ $email = Email::get_by_message_id( $message_id );
+
+ if ( empty( $email ) ) {
+ return false;
+ }
+
+ $event = false;
+
+ if ( $event_data['event'] === 'delivered' ) {
+ $event = new Delivered();
+ } elseif ( in_array( $event_data['event'], [ 'hard_bounce', 'soft_bounce', 'blocked', 'invalid_email' ], true ) ) {
+ $event = new Failed();
+ }
+
+ if ( $event === false ) {
+ return false;
+ }
+
+ $event->handle( $email, $event_data );
+
+ return true;
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Sendinblue/Provider.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Sendinblue/Provider.php
new file mode 100644
index 00000000..059487f9
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Sendinblue/Provider.php
@@ -0,0 +1,47 @@
+processor ) ) {
+ $this->processor = new Processor( $this );
+ }
+
+ return $this->processor;
+ }
+
+ /**
+ * Get the webhook subscription manager.
+ *
+ * @since 3.3.0
+ *
+ * @return AbstractSubscriber
+ */
+ public function get_subscriber() {
+
+ if ( is_null( $this->subscriber ) ) {
+ $this->subscriber = new Subscriber( $this );
+ }
+
+ return $this->subscriber;
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Sendinblue/Subscriber.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Sendinblue/Subscriber.php
new file mode 100644
index 00000000..9fba748a
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Sendinblue/Subscriber.php
@@ -0,0 +1,235 @@
+get_subscription();
+
+ if ( is_wp_error( $subscription ) ) {
+ return $subscription;
+ }
+
+ // Already subscribed.
+ if ( $subscription !== false && empty( array_diff( self::EVENTS, $subscription['events'] ) ) ) {
+ return true;
+ }
+
+ $events = $subscription !== false ? $subscription['events'] : [];
+ $events = array_values( array_unique( array_merge( $events, self::EVENTS ) ) );
+
+ $body = [
+ 'url' => $this->provider->get_url(),
+ 'events' => $events,
+ 'description' => esc_html__( 'WP Mail SMTP', 'wp-mail-smtp-pro' ),
+ 'type' => 'transactional',
+ ];
+
+ if ( $subscription !== false && ! empty( $subscription['id'] ) ) {
+ // Update subscription.
+ $response = $this->request( 'PUT', $body, $subscription['id'] );
+ } else {
+ // Create subscription.
+ $response = $this->request( 'POST', $body );
+ }
+
+ if ( is_wp_error( $response ) ) {
+ return $response;
+ }
+
+ return true;
+ }
+
+ /**
+ * Remove webhook subscription.
+ *
+ * @since 3.3.0
+ *
+ * @return true|WP_Error
+ */
+ public function unsubscribe() {
+
+ $subscription = $this->get_subscription();
+
+ if ( is_wp_error( $subscription ) ) {
+ return $subscription;
+ }
+
+ // Already unsubscribed.
+ if ( $subscription === false ) {
+ return true;
+ }
+
+ // Delete subscription.
+ $response = $this->request( 'DELETE', [], $subscription['id'] );
+
+ if ( is_wp_error( $response ) ) {
+ return $response;
+ }
+
+ return true;
+ }
+
+ /**
+ * Check webhook subscription.
+ *
+ * @since 3.3.0
+ *
+ * @return bool|WP_Error
+ */
+ public function is_subscribed() {
+
+ $subscription = $this->get_subscription();
+
+ if ( is_wp_error( $subscription ) ) {
+ return $subscription;
+ }
+
+ // Subscription does not exist.
+ if ( $subscription === false ) {
+ return false;
+ }
+
+ return empty( array_diff( self::EVENTS, $subscription['events'] ) );
+ }
+
+ /**
+ * Get subscription if available.
+ *
+ * @since 3.3.0
+ *
+ * @return array|false|WP_Error
+ */
+ protected function get_subscription() {
+
+ $params = [
+ 'type' => 'transactional',
+ ];
+
+ $response = $this->request( 'GET', $params );
+
+ // Not found any subscriptions.
+ if ( is_wp_error( $response ) && $response->get_error_code() === 'document_not_found' ) {
+ return false;
+ }
+
+ if ( is_wp_error( $response ) ) {
+ return $response;
+ }
+
+ $webhooks = array_filter(
+ $response['webhooks'],
+ function ( $data ) {
+ return $data['url'] === $this->provider->get_url();
+ }
+ );
+
+ return ! empty( $webhooks ) ? array_values( $webhooks )[0] : false;
+ }
+
+ /**
+ * Performs Sendinblue webhooks API HTTP request.
+ *
+ * @since 3.3.0
+ *
+ * @param string $method Request method.
+ * @param array $params Request params.
+ * @param string $webhook_id Sendinblue webhook ID.
+ *
+ * @return mixed|WP_Error
+ */
+ protected function request( $method = 'GET', $params = [], $webhook_id = false ) {
+
+ $endpoint = 'https://api.brevo.com/v3/webhooks';
+
+ $args = [
+ 'method' => $method,
+ 'headers' => [
+ 'api-key' => $this->provider->get_option( 'api_key' ),
+ 'content-type' => 'application/json',
+ ],
+ ];
+
+ if ( $method === 'GET' ) {
+ $endpoint = add_query_arg( $params, $endpoint );
+ } else {
+ $args['body'] = wp_json_encode( $params );
+ }
+
+ if ( $webhook_id !== false ) {
+ $endpoint .= '/' . $webhook_id;
+ }
+
+ $response = wp_remote_request( $endpoint, $args );
+
+ if ( is_wp_error( $response ) ) {
+ return $response;
+ }
+
+ if ( ! in_array( wp_remote_retrieve_response_code( $response ), [ 200, 201, 202, 204 ], true ) ) {
+ return $this->get_response_error( $response );
+ }
+
+ return json_decode( wp_remote_retrieve_body( $response ), true );
+ }
+
+ /**
+ * Retrieve errors from Sendinblue API response.
+ *
+ * @since 3.3.0
+ *
+ * @param array $response Response array.
+ *
+ * @return WP_Error
+ */
+ protected function get_response_error( $response ) {
+
+ $body = json_decode( wp_remote_retrieve_body( $response ) );
+ $error_code = wp_remote_retrieve_response_code( $response );
+
+ if ( ! empty( $body->message ) ) {
+ $message = $body->message;
+ $error_code = ! empty( $body->code ) ? $body->code : '';
+
+ $error_text = Helpers::format_error_message( $message, $error_code );
+ } else {
+ $error_text = WP::wp_remote_get_response_error_message( $response );
+ }
+
+ return new WP_Error( $error_code, $error_text );
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Sendlayer/Events/Failed.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Sendlayer/Events/Failed.php
new file mode 100644
index 00000000..aeffd5e0
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Sendlayer/Events/Failed.php
@@ -0,0 +1,43 @@
+get_param( 'EventData' );
+
+ if ( empty( $event_data['Event'] ) || empty( $event_data['MessageID'] ) ) {
+ return false;
+ }
+
+ $message_id = $event_data['MessageID'];
+ $email = Email::get_by_message_id( $message_id );
+
+ if ( empty( $email ) ) {
+ return false;
+ }
+
+ $event = false;
+
+ if ( $event_data['Event'] === 'delivered' ) {
+ $event = new Delivered();
+ } elseif ( $event_data['Event'] === 'bounced' ) {
+ $event = new Failed();
+ }
+
+ if ( $event === false ) {
+ return false;
+ }
+
+ $event->handle( $email, $event_data );
+
+ return true;
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Sendlayer/Provider.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Sendlayer/Provider.php
new file mode 100644
index 00000000..30be906c
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Sendlayer/Provider.php
@@ -0,0 +1,47 @@
+processor ) ) {
+ $this->processor = new Processor( $this );
+ }
+
+ return $this->processor;
+ }
+
+ /**
+ * Get the webhook subscription manager.
+ *
+ * @since 3.4.0
+ *
+ * @return AbstractSubscriber
+ */
+ public function get_subscriber() {
+
+ if ( is_null( $this->subscriber ) ) {
+ $this->subscriber = new Subscriber( $this );
+ }
+
+ return $this->subscriber;
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Sendlayer/Subscriber.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Sendlayer/Subscriber.php
new file mode 100644
index 00000000..cfa9a947
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Sendlayer/Subscriber.php
@@ -0,0 +1,225 @@
+get_subscription( $event );
+
+ if ( is_wp_error( $subscription ) ) {
+ return $subscription;
+ }
+
+ // Already subscribed.
+ if ( $subscription !== false ) {
+ continue;
+ }
+
+ $body = [
+ 'Event' => $event,
+ 'WebhookURL' => $this->provider->get_url(),
+ ];
+
+ // Create subscription.
+ $response = $this->request( 'POST', $body );
+
+ if ( is_wp_error( $response ) ) {
+ return $response;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Remove webhook subscription.
+ *
+ * @since 3.4.0
+ *
+ * @return true|WP_Error
+ */
+ public function unsubscribe() {
+
+ foreach ( self::EVENTS as $event ) {
+ $subscription = $this->get_subscription( $event );
+
+ if ( is_wp_error( $subscription ) ) {
+ return $subscription;
+ }
+
+ // Already unsubscribed.
+ if ( $subscription === false ) {
+ continue;
+ }
+
+ $response = $this->request( 'DELETE', [], $subscription['WebhookID'] );
+
+ if ( is_wp_error( $response ) ) {
+ return $response;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Check webhook subscription.
+ *
+ * @since 3.4.0
+ *
+ * @return bool|WP_Error
+ */
+ public function is_subscribed() {
+
+ foreach ( self::EVENTS as $event ) {
+ $subscription = $this->get_subscription( $event );
+
+ if ( is_wp_error( $subscription ) ) {
+ return $subscription;
+ }
+
+ if ( $subscription === false ) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Get subscription if available.
+ *
+ * @since 3.4.0
+ *
+ * @param static $event Event name.
+ *
+ * @return array|false|WP_Error
+ */
+ protected function get_subscription( $event ) {
+
+ $response = $this->request();
+
+ if ( is_wp_error( $response ) ) {
+ return $response;
+ }
+
+ $webhooks = array_filter(
+ $response['Webhooks'],
+ function ( $data ) use ( $event ) {
+ return $data['WebhookURL'] === $this->provider->get_url() && $data['Event'] === $event;
+ }
+ );
+
+ return ! empty( $webhooks ) ? array_values( $webhooks )[0] : false;
+ }
+
+ /**
+ * Performs SendLayer webhooks API HTTP request.
+ *
+ * @since 3.4.0
+ *
+ * @param string $method Request method.
+ * @param array $params Request params.
+ * @param array $webhook_id SendLayer webhooks ID.
+ *
+ * @return mixed|WP_Error
+ */
+ protected function request( $method = 'GET', $params = [], $webhook_id = false ) {
+
+ $endpoint = 'https://console.sendlayer.com/api/v1/webhooks';
+
+ $args = [
+ 'method' => $method,
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->provider->get_option( 'api_key' ),
+ 'Content-Type' => 'application/json',
+ ],
+ ];
+
+ if ( $method === 'GET' ) {
+ $endpoint = add_query_arg( $params, $endpoint );
+ } else {
+ $args['body'] = wp_json_encode( $params );
+ }
+
+ if ( $webhook_id !== false ) {
+ $endpoint .= '/' . $webhook_id;
+ }
+
+ $response = wp_remote_request( $endpoint, $args );
+
+ if ( is_wp_error( $response ) ) {
+ return $response;
+ }
+
+ if ( wp_remote_retrieve_response_code( $response ) !== 200 ) {
+ return $this->get_response_error( $response );
+ }
+
+ return json_decode( wp_remote_retrieve_body( $response ), true );
+ }
+
+ /**
+ * Retrieve errors from SendLayer API response.
+ *
+ * @since 3.4.0
+ *
+ * @param array $response Response array.
+ *
+ * @return WP_Error
+ */
+ protected function get_response_error( $response ) {
+
+ $body = json_decode( wp_remote_retrieve_body( $response ) );
+ $error_text = [];
+
+ // phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
+ if ( ! empty( $body->Errors ) && is_array( $body->Errors ) ) {
+ foreach ( $body->Errors as $error ) {
+ if ( ! empty( $error->Message ) ) {
+ $message = $error->Message;
+ $code = ! empty( $error->Code ) ? $error->Code : '';
+
+ $error_text[] = Helpers::format_error_message( $message, $code );
+ }
+ }
+ } else {
+ $error_text[] = WP::wp_remote_get_response_error_message( $response );
+ }
+ // phpcs:enable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
+
+ return new WP_Error( wp_remote_retrieve_response_code( $response ), implode( WP::EOL, $error_text ) );
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/SparkPost/Events/Failed.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/SparkPost/Events/Failed.php
new file mode 100644
index 00000000..00cab146
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/SparkPost/Events/Failed.php
@@ -0,0 +1,43 @@
+get_params();
+
+ if ( ! is_array( $events ) ) {
+ return false;
+ }
+
+ foreach ( $events as $event_data ) {
+ if ( ! isset( $event_data['msys']['message_event'] ) ) {
+ continue;
+ }
+
+ $event_data = $event_data['msys']['message_event'];
+
+ if ( empty( $event_data['type'] ) || empty( $event_data['transmission_id'] ) ) {
+ continue;
+ }
+
+ $email = Email::get_by_message_id( $event_data['transmission_id'] );
+
+ if ( empty( $email ) ) {
+ continue;
+ }
+
+ $event = false;
+
+ $failed_event_types = [ 'out_of_band', 'policy_rejection', 'generation_failure', 'generation_rejection' ];
+
+ if ( $event_data['type'] === 'delivery' ) {
+ $event = new Delivered();
+ } elseif (
+ ( // Hard bounce.
+ $event_data['type'] === 'bounce' &&
+ in_array( (int) $event_data['bounce_class'], [ 1, 10, 25, 30, 80, 90 ], true )
+ ) ||
+ in_array( $event_data['type'], $failed_event_types )
+ ) {
+ $event = new Failed();
+ }
+
+ if ( $event === false ) {
+ continue;
+ }
+
+ $event->handle( $email, $event_data );
+ }
+
+ return true;
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/SparkPost/Provider.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/SparkPost/Provider.php
new file mode 100644
index 00000000..80399262
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/SparkPost/Provider.php
@@ -0,0 +1,47 @@
+processor ) ) {
+ $this->processor = new Processor( $this );
+ }
+
+ return $this->processor;
+ }
+
+ /**
+ * Get the webhook subscription manager.
+ *
+ * @since 3.3.0
+ *
+ * @return AbstractSubscriber
+ */
+ public function get_subscriber() {
+
+ if ( is_null( $this->subscriber ) ) {
+ $this->subscriber = new Subscriber( $this );
+ }
+
+ return $this->subscriber;
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/SparkPost/Subscriber.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/SparkPost/Subscriber.php
new file mode 100644
index 00000000..ccae450f
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/SparkPost/Subscriber.php
@@ -0,0 +1,227 @@
+get_subscription();
+
+ if ( is_wp_error( $subscription ) ) {
+ return $subscription;
+ }
+
+ // Already subscribed.
+ if ( $subscription !== false && empty( array_diff( self::EVENTS, $subscription['events'] ) ) ) {
+ return true;
+ }
+
+ $events = $subscription !== false ? $subscription['events'] : [];
+ $events = array_unique( array_merge( $events, self::EVENTS ) );
+
+ $body = [
+ 'name' => esc_html__( 'WP Mail SMTP', 'wp-mail-smtp-pro' ),
+ 'target' => $this->provider->get_url(),
+ 'events' => $events,
+ ];
+
+ // Create subscription.
+ $response = $this->request( 'POST', $body );
+
+ if ( is_wp_error( $response ) ) {
+ return $response;
+ }
+
+ return true;
+ }
+
+ /**
+ * Remove webhook subscription.
+ *
+ * @since 3.3.0
+ *
+ * @return true|WP_Error
+ */
+ public function unsubscribe() {
+
+ $subscription = $this->get_subscription();
+
+ if ( is_wp_error( $subscription ) ) {
+ return $subscription;
+ }
+
+ // Already unsubscribed.
+ if ( $subscription === false ) {
+ return true;
+ }
+
+ // Delete subscription.
+ $response = $this->request( 'DELETE', [], $subscription['id'] );
+
+ if ( is_wp_error( $response ) ) {
+ return $response;
+ }
+
+ return true;
+ }
+
+ /**
+ * Check webhook subscription.
+ *
+ * @since 3.3.0
+ *
+ * @return bool|WP_Error
+ */
+ public function is_subscribed() {
+
+ $subscription = $this->get_subscription();
+
+ if ( is_wp_error( $subscription ) ) {
+ return $subscription;
+ }
+
+ // Subscription does not exist.
+ if ( $subscription === false ) {
+ return false;
+ }
+
+ return empty( array_diff( self::EVENTS, $subscription['events'] ) );
+ }
+
+ /**
+ * Get subscription if available.
+ *
+ * @since 3.3.0
+ *
+ * @return array|false|WP_Error
+ */
+ protected function get_subscription() {
+
+ $response = $this->request();
+
+ if ( is_wp_error( $response ) ) {
+ return $response;
+ }
+
+ $webhooks = array_filter(
+ $response['results'],
+ function ( $data ) {
+ return $data['target'] === $this->provider->get_url();
+ }
+ );
+
+ return ! empty( $webhooks ) ? array_values( $webhooks )[0] : false;
+ }
+
+ /**
+ * Performs SparkPost webhooks API HTTP request.
+ *
+ * @since 3.3.0
+ *
+ * @param string $method Request method.
+ * @param array $params Request params.
+ * @param array $webhook_id SparkPost webhooks ID.
+ *
+ * @return mixed|WP_Error
+ */
+ protected function request( $method = 'GET', $params = [], $webhook_id = false ) {
+
+ $endpoint = ( $this->provider->get_option( 'region' ) === 'EU' ? Mailer::API_BASE_EU : Mailer::API_BASE_US ) . '/webhooks';
+
+ $args = [
+ 'method' => $method,
+ 'headers' => [
+ 'Authorization' => $this->provider->get_option( 'api_key' ),
+ 'Content-Type' => 'application/json',
+ ],
+ ];
+
+ if ( $method === 'GET' ) {
+ $endpoint = add_query_arg( $params, $endpoint );
+ } else {
+ $args['body'] = wp_json_encode( $params );
+ }
+
+ if ( $webhook_id !== false ) {
+ $endpoint .= '/' . $webhook_id;
+ }
+
+ $response = wp_remote_request( $endpoint, $args );
+
+ if ( is_wp_error( $response ) ) {
+ return $response;
+ }
+
+ if ( ! in_array( wp_remote_retrieve_response_code( $response ), [ 200, 204 ], true ) ) {
+ return $this->get_response_error( $response );
+ }
+
+ return json_decode( wp_remote_retrieve_body( $response ), true );
+ }
+
+ /**
+ * Retrieve errors from SparkPost API response.
+ *
+ * @since 3.3.0
+ *
+ * @param array $response Response array.
+ *
+ * @return WP_Error
+ */
+ protected function get_response_error( $response ) {
+
+ $body = json_decode( wp_remote_retrieve_body( $response ) );
+ $error_text = [];
+
+ if ( ! empty( $body->errors ) && is_array( $body->errors ) ) {
+ foreach ( $body->errors as $error ) {
+ if ( ! empty( $error->message ) ) {
+ $message = $error->message;
+ $code = ! empty( $error->code ) ? $error->code : '';
+ $description = ! empty( $error->description ) ? $error->description : '';
+
+ $error_text[] = Helpers::format_error_message( $message, $code, $description );
+ }
+ }
+ } else {
+ $error_text[] = WP::wp_remote_get_response_error_message( $response );
+ }
+
+ return new WP_Error( wp_remote_retrieve_response_code( $response ), implode( WP::EOL, $error_text ) );
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/SubscriberInterface.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/SubscriberInterface.php
new file mode 100644
index 00000000..938143ed
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/SubscriberInterface.php
@@ -0,0 +1,38 @@
+get_active_provider();
+
+ if ( $provider !== false ) {
+ $provider->init();
+ }
+ }
+
+ /**
+ * Register hooks.
+ *
+ * @since 3.3.0
+ */
+ public function hooks() {
+
+ if ( ! self::is_allowed() ) {
+ return;
+ }
+
+ // Register REST route to handle incoming webhook requests.
+ add_action( 'rest_api_init', [ $this, 'register_rest_route' ] );
+
+ // Setup subscription.
+ add_action( 'admin_init', [ $this, 'setup_subscription' ], 50 );
+
+ // Maybe unsubscribe on settings change.
+ add_action(
+ 'wp_mail_smtp_admin_area_process_actions_process_post_before',
+ [ $this, 'maybe_unsubscribe_on_settings_change' ],
+ 20,
+ 2
+ );
+
+ // Maybe unsubscribe on settings change in Setup Wizard.
+ add_action(
+ 'wp_mail_smtp_admin_setup_wizard_update_settings',
+ function ( $data ) {
+ $this->maybe_unsubscribe_on_settings_change( $data, 'setup-wizard' );
+ }
+ );
+
+ // Maybe unsubscribe on settings constants change.
+ add_action( 'admin_init', [ $this, 'maybe_unsubscribe_on_settings_constants_change' ] );
+
+ // Unsubscribe on plugin deactivation.
+ register_deactivation_hook( WPMS_PLUGIN_FILE, [ $this, 'unsubscribe_on_plugin_deactivation' ] );
+
+ // Create subscription ajax handler.
+ add_action( 'wp_ajax_wp_mail_smtp_pro_webhooks_subscribe', [ $this, 'subscribe_ajax' ] );
+
+ // Remove subscription ajax handler.
+ add_action( 'wp_ajax_wp_mail_smtp_pro_webhooks_unsubscribe', [ $this, 'unsubscribe_ajax' ] );
+ }
+
+ /**
+ * Whether webhooks allowed in current setup.
+ *
+ * @since 3.3.0
+ */
+ public static function is_allowed() {
+
+ return ! is_multisite();
+ }
+
+ /**
+ * Register REST route to handle incoming webhook requests.
+ *
+ * @since 3.3.0
+ */
+ public function register_rest_route() {
+
+ $provider = $this->get_active_provider();
+
+ if ( $provider === false ) {
+ return;
+ }
+
+ register_rest_route(
+ 'wp-mail-smtp/v1',
+ '/webhooks/' . $provider->get_mailer_name(),
+ [
+ 'methods' => 'POST',
+ 'callback' => [ $provider->get_processor(), 'handle' ],
+ 'permission_callback' => [ $provider->get_processor(), 'validate' ],
+ ]
+ );
+ }
+
+ /**
+ * Setup subscription.
+ *
+ * @since 3.3.0
+ */
+ public function setup_subscription() {
+
+ // Bail if:
+ // - current request is AJAX;
+ // - current request is cron job;
+ // - email log is disabled.
+ if (
+ WP::is_doing_ajax() ||
+ wp_doing_cron() ||
+ wp_mail_smtp()->get_pro()->get_logs()->is_enabled() === false
+ ) {
+ return;
+ }
+
+ /**
+ * Filters whether webhooks subscription setup allowed.
+ *
+ * @since 3.3.0
+ *
+ * @param bool $is_allowed Whether webhooks subscription setup allowed.
+ */
+ $is_allowed = apply_filters( 'wp_mail_smtp_pro_emails_logs_webhooks_setup_subscription', true );
+
+ if ( ! $is_allowed ) {
+ return;
+ }
+
+ $provider = $this->get_active_provider();
+
+ // Bail if:
+ // - current mail provider doesn't support webhooks;
+ // - subscription was already created;
+ // - mailer configuration was not completed.
+ if (
+ $provider === false ||
+ ! empty( $provider->get_setup_status() ) ||
+ ! $provider->get_mailer()->is_mailer_complete()
+ ) {
+ return;
+ }
+
+ $rest_availability = Helpers::test_rest_availability();
+
+ // Bail if REST API is not accessible.
+ if ( is_wp_error( $rest_availability ) ) {
+ update_option( self::SUBSCRIPTION_ERROR_OPTION_NAME, $this->rest_availability_error_message( $rest_availability ) );
+ $provider->set_setup_status( self::FAILED_SETUP );
+
+ return;
+ }
+
+ $provider->subscribe();
+ }
+
+ /**
+ * Maybe unsubscribe on settings change.
+ *
+ * @since 3.3.0
+ *
+ * @param array $data POST data.
+ * @param string $page_slug Current page slug.
+ */
+ public function maybe_unsubscribe_on_settings_change( $data, $page_slug ) {
+
+ // Bail if unsubscription was already performed.
+ if ( $this->is_unsubscribe === true ) {
+ return;
+ }
+
+ $provider = $this->get_active_provider();
+
+ if ( $provider !== false && ! empty( $provider->get_setup_status() ) ) {
+ $options = Options::init();
+ $mailer = $provider->get_mailer_name();
+
+ if ( isset( $data[ $mailer ] ) ) {
+ $non_settings_keys = [
+ 'webhooks_setup',
+ 'additional_message_streams', // postmark.
+ ];
+
+ foreach ( $non_settings_keys as $key ) {
+ if ( isset( $data[ $mailer ][ $key ] ) ) {
+ unset( $data[ $mailer ][ $key ] );
+ }
+ }
+ }
+
+ if (
+ (
+ $page_slug === 'settings' || $page_slug === 'setup-wizard' &&
+ (
+ ( // If mailer settings was changed.
+ isset( $data[ $mailer ] ) &&
+ ! empty( array_diff( $data[ $mailer ], array_intersect_key( $options->get_group( $mailer ), $data[ $mailer ] ) ) )
+ ) ||
+ ( // If mailer was changed.
+ ! empty( $data['mail']['mailer'] ) &&
+ $data['mail']['mailer'] !== $options->get( 'mail', 'mailer' )
+ ) ||
+ (
+ // If mailsersend from email was changed.
+ ! empty( $data['mail']['from_email'] ) &&
+ $data['mail']['from_email'] !== $options->get( 'mail', 'from_email' ) &&
+ $data['mail']['mailer'] === 'mailersend'
+ )
+ )
+ ) ||
+ (
+ $page_slug === 'logs' &&
+ ( // If email log was disabled.
+ ! isset( $data['logs']['enabled'] ) &&
+ $options->get( 'logs', 'enabled' ) === true
+ )
+ ) ||
+ (
+ $page_slug === 'setup-wizard' &&
+ ( // If email log was disabled in the Setup Wizard.
+ isset( $data['logs']['enabled'] ) &&
+ $data['logs']['enabled'] === false &&
+ $options->get( 'logs', 'enabled' ) === true
+ )
+ )
+ ) {
+ $this->is_unsubscribe = true;
+ $provider->unsubscribe();
+ }
+ }
+ }
+
+ /**
+ * Maybe unsubscribe on settings constants change.
+ *
+ * @since 3.3.0
+ */
+ public function maybe_unsubscribe_on_settings_constants_change() {
+
+ // Bail if we're not in admin panel.
+ if ( ! WP::in_wp_admin() ) {
+ return;
+ }
+
+ // Bail if unsubscription was already performed.
+ if ( $this->is_unsubscribe === true ) {
+ return;
+ }
+
+ $provider = $this->get_active_provider();
+
+ // Bail if current mail provider doesn't support webhooks or subscription was not created yet.
+ if ( $provider === false || empty( $provider->get_setup_status() ) ) {
+ return;
+ }
+
+ $options = Options::init();
+
+ if ( // If email log was disabled.
+ $options->is_const_defined( 'logs', 'enabled' ) &&
+ $options->is_const_changed( 'logs', 'enabled' ) &&
+ $options->get( 'logs', 'enabled' ) === false
+ ) {
+ $this->is_unsubscribe = true;
+ $provider->unsubscribe();
+ }
+ }
+
+ /**
+ * Unsubscribe on plugin deactivation.
+ *
+ * @since 3.3.0
+ */
+ public function unsubscribe_on_plugin_deactivation() {
+
+ $provider = $this->get_active_provider();
+
+ // Bail if current mail provider doesn't support webhooks or subscription was not created yet.
+ if ( $provider === false || empty( $provider->get_setup_status() ) ) {
+ return;
+ }
+
+ $provider->unsubscribe();
+ }
+
+ /**
+ * Create subscription ajax handler.
+ *
+ * @since 3.3.0
+ */
+ public function subscribe_ajax() {
+
+ if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_global_options() ) ) {
+ wp_send_json_error( esc_html__( 'You don\'t have the capability to perform this action.', 'wp-mail-smtp-pro' ) );
+ }
+
+ if (
+ empty( $_POST['nonce'] ) ||
+ ! wp_verify_nonce( sanitize_key( $_POST['nonce'] ), 'wp-mail-smtp-pro-admin' )
+ ) {
+ wp_send_json_error( esc_html__( 'Access rejected.', 'wp-mail-smtp-pro' ) );
+ }
+
+ if ( wp_mail_smtp()->get_pro()->get_logs()->is_enabled() === false ) {
+ wp_send_json_error( esc_html__( 'The email log is disabled. Please first enable the email log.', 'wp-mail-smtp-pro' ) );
+ }
+
+ $provider = $this->get_active_provider();
+
+ if (
+ $provider === false ||
+ ! $provider->get_mailer()->is_mailer_complete()
+ ) {
+ wp_send_json_error( esc_html__( 'Mailer setup is not completed. Please complete your mailer configuration first.', 'wp-mail-smtp-pro' ) );
+ }
+
+ $rest_availability = Helpers::test_rest_availability();
+
+ // Bail if REST API is not accessible.
+ if ( is_wp_error( $rest_availability ) ) {
+ wp_send_json_error( $this->rest_availability_error_message( $rest_availability ) );
+ }
+
+ $result = $provider->subscribe();
+
+ if ( is_wp_error( $result ) ) {
+ wp_send_json_error( implode( "\r\n", array_unique( $result->get_error_messages() ) ) );
+ }
+
+ wp_send_json_success( esc_html__( 'Subscription created successfully.', 'wp-mail-smtp-pro' ) );
+ }
+
+ /**
+ * Remove subscription ajax handler.
+ *
+ * @since 3.3.0
+ */
+ public function unsubscribe_ajax() {
+
+ if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_global_options() ) ) {
+ wp_die( esc_html__( 'You don\'t have the capability to perform this action.', 'wp-mail-smtp-pro' ) );
+ }
+
+ if (
+ empty( $_POST['nonce'] ) ||
+ ! wp_verify_nonce( sanitize_key( $_POST['nonce'] ), 'wp-mail-smtp-pro-admin' )
+ ) {
+ wp_send_json_error( esc_html__( 'Access rejected.', 'wp-mail-smtp-pro' ) );
+ }
+
+ $provider = $this->get_active_provider();
+
+ if (
+ $provider === false ||
+ $provider->get_setup_status() !== self::SUCCESS_SETUP
+ ) {
+ wp_send_json_error( esc_html__( 'The subscription was not created yet.', 'wp-mail-smtp-pro' ) );
+ }
+
+ $result = $provider->unsubscribe();
+
+ $provider->set_setup_status( self::MANUAL_SETUP );
+
+ if ( is_wp_error( $result ) ) {
+ wp_send_json_error( implode( "\r\n", array_unique( $result->get_error_messages() ) ) );
+ }
+
+ wp_send_json_success( esc_html__( 'Subscription removed successfully.', 'wp-mail-smtp-pro' ) );
+ }
+
+ /**
+ * Get current mailer provider if available.
+ *
+ * @since 3.3.0
+ *
+ * @return AbstractProvider|false
+ */
+ public function get_active_provider() {
+
+ $mailer = Options::init()->get( 'mail', 'mailer' );
+
+ return $this->get_provider( $mailer );
+ }
+
+ /**
+ * Get webhook provider.
+ *
+ * @since 3.3.0
+ * @since 3.4.0 Added SendLayer.
+ * @since 4.1.0 Added SMTP2GO.
+ * @since 4.5.0 Added MailerSend.
+ * @since 4.6.0 Added Mandrill.
+ *
+ * @param string $mailer_name Mailer name/slug.
+ *
+ * @return AbstractProvider|false
+ */
+ public function get_provider( $mailer_name ) {
+
+ $provider = false;
+
+ $providers = [
+ 'sendlayer' => Providers\Sendlayer\Provider::class,
+ 'mailgun' => Providers\Mailgun\Provider::class,
+ 'smtpcom' => Providers\SMTPcom\Provider::class,
+ 'sendinblue' => Providers\Sendinblue\Provider::class,
+ 'postmark' => Providers\Postmark\Provider::class,
+ 'sparkpost' => Providers\SparkPost\Provider::class,
+ 'smtp2go' => Providers\SMTP2GO\Provider::class,
+ 'mailjet' => Providers\Mailjet\Provider::class,
+ 'mailersend' => Providers\MailerSend\Provider::class,
+ 'mandrill' => Providers\Mandrill\Provider::class,
+ 'resend' => Providers\Resend\Provider::class,
+ ];
+
+ if ( isset( $providers[ $mailer_name ] ) ) {
+ $provider = new $providers[ $mailer_name ]( $mailer_name );
+ }
+
+ /**
+ * Filters webhook provider.
+ *
+ * @since 3.3.0
+ *
+ * @param AbstractProvider $provider Webhook provider.
+ * @param string $mailer_name Mailer name/slug.
+ */
+ return apply_filters( 'wp_mail_smtp_pro_emails_logs_webhooks_get_provider', $provider, $mailer_name );
+ }
+
+ /**
+ * Generate REST API availability error message.
+ *
+ * @since 3.3.0
+ *
+ * @param \WP_Error $error Error object.
+ *
+ * @return string
+ */
+ private function rest_availability_error_message( $error ) {
+
+ return sprintf( /* translators: %1$s - error message; %2$s - error code. */
+ esc_html__( 'Seems that the REST API on your website is disabled. For correct work of email deliverability status verification via webhooks, REST API must be enabled. Please enable your REST API or whitelist "/wp-mail-smtp/v1" endpoint. The REST API request failed due to an error: %1$s (%2$s).', 'wp-mail-smtp-pro' ),
+ $error->get_error_message(),
+ $error->get_error_code()
+ );
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/RateLimiting/RateLimiting.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/RateLimiting/RateLimiting.php
new file mode 100644
index 00000000..9845486e
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/RateLimiting/RateLimiting.php
@@ -0,0 +1,290 @@
+get( 'rate_limit', 'enabled' );
+ }
+
+ /**
+ * Sanitize options before they're saved.
+ *
+ * @since 4.0.0
+ *
+ * @param array $options Currently processed options passed to a filter hook.
+ *
+ * @return array
+ */
+ public function options_set( $options ) {
+
+ if ( ! isset( $options['rate_limit'] ) ) {
+ // All options are off by default.
+ $options['rate_limit'] = [
+ 'enabled' => false,
+ 'minute' => '',
+ 'hour' => '',
+ 'day' => '',
+ 'week' => '',
+ 'month' => '',
+ ];
+
+ return $options;
+ }
+
+ foreach ( $options['rate_limit'] as $key => $value ) {
+ if ( $key === 'enabled' ) {
+ $value = (bool) $value;
+ } else {
+ if ( is_numeric( $value ) ) {
+ $value = max( 0, intval( $value ) );
+ } else {
+ $value = '';
+ }
+ }
+
+ $options['rate_limit'][ $key ] = $value;
+ }
+
+ return $options;
+ }
+
+ /**
+ * Process the options values through the constants check.
+ *
+ * @since 4.0.0
+ *
+ * @param mixed $return Constant value.
+ * @param string $group The option group.
+ * @param string $key The option key.
+ * @param mixed $value DB option value.
+ *
+ * @return mixed
+ */
+ public function filter_options_get_const_value( $return, $group, $key, $value ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded
+
+ $options = Options::init();
+
+ if ( $group === 'rate_limit' ) {
+
+ switch ( $key ) {
+ case 'enabled':
+ $return = $options->is_const_defined( $group, $key ) ?
+ $options->parse_boolean( WPMS_RATE_LIMIT_ENABLED ) :
+ $value;
+ break;
+
+ case 'minute':
+ $return = $options->is_const_defined( $group, $key ) ?
+ intval( WPMS_RATE_LIMIT_PER_MINUTE ) :
+ $value;
+ break;
+
+ case 'hour':
+ $return = $options->is_const_defined( $group, $key ) ?
+ intval( WPMS_RATE_LIMIT_PER_HOUR ) :
+ $value;
+ break;
+
+ case 'day':
+ $return = $options->is_const_defined( $group, $key ) ?
+ intval( WPMS_RATE_LIMIT_PER_DAY ) :
+ $value;
+ break;
+
+ case 'week':
+ $return = $options->is_const_defined( $group, $key ) ?
+ intval( WPMS_RATE_LIMIT_PER_WEEK ) :
+ $value;
+ break;
+
+ case 'month':
+ $return = $options->is_const_defined( $group, $key ) ?
+ intval( WPMS_RATE_LIMIT_PER_MONTH ) :
+ $value;
+ break;
+ }
+ }
+
+ return $return;
+ }
+
+ /**
+ * Check is constant defined.
+ *
+ * @since 4.0.0
+ *
+ * @param mixed $return Constant value.
+ * @param string $group The option group.
+ * @param string $key The option key.
+ *
+ * @return bool
+ */
+ public function filter_options_is_const_defined( $return, $group, $key ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
+
+ if ( $group === 'rate_limit' ) {
+
+ switch ( $key ) {
+ case 'enabled':
+ $return = defined( 'WPMS_RATE_LIMIT_ENABLED' );
+ break;
+
+ case 'minute':
+ $return = defined( 'WPMS_RATE_LIMIT_PER_MINUTE' );
+ break;
+
+ case 'hour':
+ $return = defined( 'WPMS_RATE_LIMIT_PER_HOUR' );
+ break;
+
+ case 'day':
+ $return = defined( 'WPMS_RATE_LIMIT_PER_DAY' );
+ break;
+
+ case 'week':
+ $return = defined( 'WPMS_RATE_LIMIT_PER_WEEK' );
+ break;
+
+ case 'month':
+ $return = defined( 'WPMS_RATE_LIMIT_PER_MONTH' );
+ break;
+ }
+ }
+
+ return $return;
+ }
+
+ /**
+ * Get a list of interval slugs
+ * and their DateTime offset.
+ *
+ * @since 4.0.0
+ *
+ * @return array
+ */
+ private function get_intervals() {
+
+ return [
+ 'month' => '1 month ago',
+ 'week' => '1 week ago',
+ 'day' => '1 day ago',
+ 'hour' => '1 hour ago',
+ 'minute' => '1 minute ago',
+ ];
+ }
+
+ /**
+ * Count how many emails can be
+ * currently scheduled for sending.
+ *
+ * @since 4.0.0
+ *
+ * @return int|null
+ */
+ public function count_processable_emails() {
+
+ $options = Options::init();
+ $queue = wp_mail_smtp()->get_queue();
+ $intervals = $this->get_intervals();
+ $interval_counts = [];
+
+ foreach ( $intervals as $interval_slug => $interval_value ) {
+ $interval_limit = $options->get( 'rate_limit', $interval_slug );
+
+ // Skip if no rate limit is configured for this interval.
+ if ( ! is_numeric( $interval_limit ) ) {
+ continue;
+ }
+
+ $interval_datetime = new DateTime( $interval_value, new DateTimeZone( 'UTC' ) );
+
+ // Count the amount of sent emails during
+ // the current interval.
+ $interval_count = $queue->count_processed_emails( $interval_datetime );
+
+ // Calculate how many emails could still be
+ // sent in the current interval.
+ $interval_processable_emails = $interval_limit - $interval_count;
+
+ // Bail if the amount of sent emails over the current
+ // interval is already beyond the rate limit.
+ if ( $interval_processable_emails <= 0 ) {
+ return 0;
+ }
+
+ $interval_counts[ $interval_slug ] = $interval_processable_emails;
+ }
+
+ // Return the full queue if no intervals are configured.
+ if ( empty( $interval_counts ) ) {
+ return null;
+ }
+
+ // Return the minimum processable
+ // amount of emails across all intervals.
+ return min( array_values( $interval_counts ) );
+ }
+
+ /**
+ * Get the date and time before which
+ * to cleanup processed emails.
+ *
+ * @param null|DateTime $datetime The date and time before which to cleanup processed emails.
+ *
+ * @since 4.0.0
+ *
+ * @return DateTime
+ */
+ public function get_cleanup_before_datetime( $datetime ) {
+
+ return new DateTime( '1 month ago', new DateTimeZone( 'UTC' ) );
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/TestEmail.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/TestEmail.php
new file mode 100644
index 00000000..abd05383
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/TestEmail.php
@@ -0,0 +1,204 @@
+
+
+ get_features_section();
+ $send_layer_section = $this->get_sendlayer_section();
+
+ if (
+ ( $last_displayed_footer === 'sendlayer' && ! empty( $features_section ) ) ||
+ ( empty( $send_layer_section ) && ! empty( $features_section ) )
+ ) {
+ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ echo $this->get_features_section();
+ update_option( $last_displayed_footer_option_key, 'features', false );
+ } elseif ( ! empty( $send_layer_section ) ) {
+ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ echo $this->get_sendlayer_section();
+ update_option( $last_displayed_footer_option_key, 'sendlayer', false );
+ }
+ }
+
+ /**
+ * Get the features section.
+ *
+ * @since 3.10.0
+ *
+ * @return string
+ */
+ private function get_features_section() {
+
+ $options = Options::init();
+ $features = [
+ [
+ 'title' => 'Backup Connection',
+ 'disabled' => empty( $options->get( 'backup_connection', 'connection_id' ) ),
+ ],
+ [
+ 'title' => 'Email Alerts',
+ 'disabled' => ! ( new Alerts() )->is_enabled(),
+ ],
+ [
+ 'title' => 'Weekly Email Summary',
+ 'disabled' => SummaryReportEmail::is_disabled(),
+ ],
+ [
+ 'title' => 'Dashboard Widget',
+ 'disabled' => ! empty( $options->get( 'general', 'dashboard_widget_hidden' ) ),
+ ],
+ ];
+
+ $features = array_filter(
+ $features,
+ function ( $feature ) {
+ return $feature['disabled'];
+ }
+ );
+
+ if ( empty( $features ) ) {
+ return '';
+ }
+
+ ob_start();
+ ?>
+
+ ', array_column( $features, 'title' ) ), [ 'br' => [] ] ); ?>
+
+ These powerful features are available in your WP Mail SMTP Pro settings.
+
+ Tired of Missing or Delayed Emails?
+
+ Try SendLayer, a reliable email provider that’s powerful and easy to use. Send your first 200 emails for free!
+ %s %s
+ enter and activate your license key for WP Mail SMTP Pro to enable automatic updates.', 'wp-mail-smtp-pro' ),
+ array(
+ 'a' => array(
+ 'href' => array(),
+ ),
+ )
+ ),
+ esc_url( add_query_arg( array( 'page' => 'wp-mail-smtp' ), WP::admin_url( 'admin.php' ) ) )
+ );
+ ?>
+
+ Please click here to renew your license key and continue receiving automatic updates.', 'wp-mail-smtp-pro' ),
+ array(
+ 'a' => array(
+ 'href' => array(),
+ 'target' => array(),
+ 'rel' => array(),
+ ),
+ )
+ ),
+ esc_url( $this->get_renewal_link( 'renew your license key' ) )
+ );
+ ?>
+
+ Your WP Mail SMTP Pro license has no site activations left. You can update the list of your sites or upgrade the license in the Account area. Or you can purchase a new license key.', 'wp-mail-smtp-pro' ),
+ [
+ 'b' => [],
+ 'a' => [
+ 'href' => [],
+ 'target' => [],
+ 'rel' => [],
+ ],
+ ]
+ ),
+ esc_url(
+ wp_mail_smtp()->get_utm_url(
+ 'https://wpmailsmtp.com/account/licenses/',
+ [
+ 'medium' => 'license-admin-notice',
+ 'content' => 'license site limit reached',
+ ]
+ )
+ ),
+ esc_url(
+ wp_mail_smtp()->get_utm_url(
+ 'https://wpmailsmtp.com/pricing/',
+ [
+ 'medium' => 'license-admin-notice',
+ 'content' => 'license site limit reached',
+ ]
+ )
+ )
+ );
+ ?>
+ ', array_map( 'esc_html', $this->errors ) ); ?> ', array_map( 'esc_html', $this->success ) ); ?>
+
+
+ Heads up! The last email your site attempted to send was unsuccessful.', 'wp-mail-smtp-pro' ),
+ [ 'strong' => [] ]
+ );
+ ?>
+
+
+
+
+
+
+ ' . esc_html( $this->get_connection_not_ready_error_text() ) . ' ' . esc_html( $this->get_connection_not_ready_error_text() ) . ' ' . esc_html( $this->get_connection_not_ready_error_text() ) . ' ' . esc_html( $error->get_error_message() ) . '
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ please refer to the Amazon SES documentation.', 'wp-mail-smtp-pro' ),
+ [
+ 'br' => true,
+ 'a' => [
+ 'href' => true,
+ 'target' => true,
+ 'rel' => true,
+ ],
+ ]
+ ),
+ 'https://docs.aws.amazon.com/ses/latest/dg/creating-identities.html#verify-domain-procedure'
+ );
+ ?>
+
+
+ get( 'mail', 'mailer' );
+ $backup_mailer = '';
+ $backup_connection_id = $options->get( 'backup_connection', 'connection_id' );
+
+ if ( ! empty( $backup_connection_id ) ) {
+ $backup_connection = ( new AdditionalConnections() )->get_connection( $backup_connection_id );
+
+ if ( $backup_connection !== false ) {
+ $backup_mailer = $backup_connection->get_mailer_slug();
+ }
+ }
+
+ // Don't display this section if primary or backup connection is already SendLayer.
+ if ( $primary_mailer === 'sendlayer' || $backup_mailer === 'sendlayer' ) {
+ return '';
+ }
+
+ $cta_link_url = wp_mail_smtp()->get_utm_url(
+ 'https://sendlayer.com/wp-mail-smtp/',
+ [
+ 'source' => 'wpmailsmtpplugin',
+ 'medium' => 'test-email',
+ 'content' => 'Try SendLayer',
+ ]
+ );
+
+ ob_start();
+ ?>
+
+
+
+ Take advantage of powerful features
+
+
+
+ has_existing_license_key() ) {
+ return;
+ }
+
+ if ( ! file_exists( $this->get_file_path() ) ) {
+ return;
+ }
+
+ update_option( self::PENDING_OPTION, time(), false );
+ }
+
+ /**
+ * On a real admin page load, claim the pending attempt and try to activate
+ * the bundled key once.
+ *
+ * @since 4.9.0
+ */
+ public function maybe_activate() {
+
+ // Real admin page loads only: no AJAX, cron, REST, or WP-CLI.
+ if (
+ ! is_admin() ||
+ wp_doing_ajax() ||
+ wp_doing_cron() ||
+ ( defined( 'REST_REQUEST' ) && REST_REQUEST ) ||
+ ( defined( 'WP_CLI' ) && WP_CLI )
+ ) {
+ return;
+ }
+
+ // Atomic claim: delete_option() returns true only for the request that
+ // removed the row, so concurrent loads can't double-fire and a stale
+ // object-cache read can't re-claim. Gating on the return (not a prior
+ // get_option) is what makes this the lock.
+ if ( ! delete_option( self::PENDING_OPTION ) ) {
+ return;
+ }
+
+ // The bundled file may have been removed since activation; nothing to do.
+ // Checked after the claim so the one-shot flag is burned exactly once and
+ // a missing file can't leave a stale flag to re-check on every admin load.
+ if ( ! file_exists( $this->get_file_path() ) ) {
+ return;
+ }
+
+ $key = $this->read_bundled_key();
+
+ // Delete the file up front, before any verification. This guarantees a
+ // single attempt: on any failure the file is already gone, so a later
+ // reactivation finds nothing to retry and the user activates manually.
+ $this->delete_bundled_file();
+
+ // Malformed / missing / empty -> no-op. The flag is already claimed.
+ if ( ! $this->is_valid_key_format( $key ) ) {
+ return;
+ }
+
+ $this->get_license()->verify_key( $key );
+ }
+
+ /**
+ * Whether a license key is already configured.
+ *
+ * @since 4.9.0
+ *
+ * @return bool
+ */
+ private function has_existing_license_key() {
+
+ // Options::get() already resolves the WPMS_LICENSE_KEY constant when defined.
+ $key = Options::init()->get( 'license', 'key' );
+
+ return ! empty( $key );
+ }
+
+ /**
+ * Read the license key from the bundled file.
+ *
+ * @since 4.9.0
+ *
+ * @return string Empty string when missing, unreadable, or malformed.
+ */
+ private function read_bundled_key() {
+
+ $path = $this->get_file_path();
+
+ if ( ! file_exists( $path ) ) {
+ return '';
+ }
+
+ // Suppress errors so a malformed bundled file can't surface a PHP notice/warning on an admin screen.
+ $data = @include $path;
+ $key = is_array( $data ) ? ( $data['license_key'] ?? '' ) : '';
+
+ return is_string( $key ) ? $key : '';
+ }
+
+ /**
+ * Whether the key is a 32-char MD5 hex string. Guards against sending junk
+ * to the licensing endpoint.
+ *
+ * @since 4.9.0
+ *
+ * @param string $key License key.
+ *
+ * @return bool
+ */
+ private function is_valid_key_format( $key ) {
+
+ return is_string( $key ) && (bool) preg_match( '/^[a-f0-9]{32}$/i', $key );
+ }
+
+ /**
+ * Delete the bundled file if it exists. A missing file or a read-only
+ * filesystem must not raise an error.
+ *
+ * @since 4.9.0
+ */
+ private function delete_bundled_file() {
+
+ $path = $this->get_file_path();
+
+ if ( ! file_exists( $path ) ) {
+ return;
+ }
+
+ wp_delete_file( $path );
+ }
+
+ /**
+ * Resolve the License instance used to perform the remote verification.
+ *
+ * @since 4.9.0
+ *
+ * @return License
+ */
+ private function get_license() {
+
+ return wp_mail_smtp()->get_pro()->get_license();
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/License/License.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/License/License.php
new file mode 100644
index 00000000..1eac038e
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/License/License.php
@@ -0,0 +1,1317 @@
+register_updater();
+ $this->hooks();
+
+ ( new BundledLicense() )->hooks();
+ }
+
+ /**
+ * Register hooks.
+ *
+ * @since 4.0.0
+ */
+ protected function hooks() {
+
+ // Register licensing ajax action (with custom tasks).
+ add_action( 'wp_ajax_wp_mail_smtp_pro_license_ajax', array( $this, 'process_ajax' ) );
+
+ // Filter admin area options save process.
+ add_filter( 'wp_mail_smtp_options_set', array( $this, 'filter_options_set' ) );
+
+ // Redefine licensing field content.
+ add_filter( 'wp_mail_smtp_admin_get_pages', function ( $pages ) {
+
+ remove_action( 'wp_mail_smtp_admin_pages_settings_license_key', array(
+ \WPMailSMTP\Admin\Pages\SettingsTab::class,
+ 'display_license_key_field_content',
+ ) );
+
+ add_action( 'wp_mail_smtp_admin_pages_settings_license_key', array( $this, 'display_settings_license_key_field_content' ) );
+
+ return $pages;
+ } );
+
+ // Admin notices.
+ add_action( 'admin_notices', array( $this, 'notices' ) );
+
+ if ( WP::use_global_plugin_settings() ) {
+ add_action( 'network_admin_notices', array( $this, 'notices' ) );
+ }
+ }
+
+ /**
+ * Load plugin updater.
+ *
+ * @since 1.5.0
+ */
+ protected function register_updater() {
+
+ // Only in admin area or WP CLI.
+ if ( ! is_admin() && ! Helpers::is_wp_cli() ) {
+ return;
+ }
+
+ $key = wp_mail_smtp()->get_license_key();
+
+ // Only if we have the key.
+ if ( empty( $key ) ) {
+ return;
+ }
+
+ // Initialize the updater.
+ new Updater(
+ array(
+ 'plugin_name' => 'WP Mail SMTP Pro',
+ 'plugin_slug' => Pro::SLUG,
+ 'plugin_path' => Pro::SLUG . '/wp_mail_smtp.php',
+ 'plugin_url' => trailingslashit( wp_mail_smtp()->plugin_url ),
+ 'version' => WPMS_PLUGIN_VER,
+ 'key' => $key,
+ )
+ );
+ }
+
+ /**
+ * Process AJAX requests fired by a pro version of a plugin and related to a license management.
+ *
+ * @since 1.5.0
+ */
+ public function process_ajax() {
+
+ $generic_error = esc_html__( 'Something went wrong. Please try again later.', 'wp-mail-smtp-pro' );
+
+ // Verify nonce.
+ if (
+ ! isset( $_POST['nonce'] ) ||
+ ! wp_verify_nonce( sanitize_key( $_POST['nonce'] ), 'wp_mail_smtp_pro_license_nonce' )
+ ) {
+ wp_send_json_error( $generic_error );
+ }
+
+ if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_global_options() ) ) {
+ wp_send_json_error( $generic_error );
+ }
+
+ $task = isset( $_POST['task'] ) ? sanitize_key( $_POST['task'] ) : '';
+
+ switch ( $task ) {
+ case 'license_verify':
+ $license = isset( $_POST['license'] ) ? sanitize_key( $_POST['license'] ) : '';
+
+ if ( empty( $license ) ) {
+ wp_send_json_error( $generic_error );
+ }
+
+ $this->verify_key( $license, true );
+ break;
+
+ case 'license_deactivate':
+ $this->deactivate_key( true );
+ break;
+
+ case 'license_refresh':
+ $this->validate_key( wp_mail_smtp()->get_license_key(), true, true );
+ break;
+ }
+
+ // Process unknown tasks or other edge cases.
+ wp_send_json_error( $generic_error );
+ }
+
+ /**
+ * Redefine admin area Settings page License Key field content.
+ *
+ * @since 1.5.0
+ * @since 3.11.0 Removed name attribute of license key input element.
+ *
+ * @param Options $options The plugin options.
+ * @param bool $echo Whether to output HTML.
+ */
+ public function display_settings_license_key_field_content( $options, $echo = true ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded
+
+ $key = wp_mail_smtp()->get_license_key();
+ $type = wp_mail_smtp()->get_license_type();
+ $license = $options->get_group( 'license' );
+ $is_expired = isset( $license['is_expired'] ) && $license['is_expired'] === true;
+ $is_disabled = isset( $license['is_disabled'] ) && $license['is_disabled'] === true;
+ $is_invalid = isset( $license['is_invalid'] ) && $license['is_invalid'] === true;
+ $is_limit_reached = isset( $license['is_limit_reached'] ) && $license['is_limit_reached'] === true;
+ $is_valid = ! empty( $key ) && ! $is_expired && ! $is_disabled && ! $is_invalid && ! $is_limit_reached;
+
+ $input_class = '';
+
+ if ( $is_valid ) {
+ $input_class = 'wp-mail-smtp-setting-license-key--valid';
+ } elseif ( ! empty( $key ) ) {
+ $input_class = 'wp-mail-smtp-setting-license-key--invalid';
+ }
+
+ ob_start();
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
%3$s: %4$s',
+ esc_html( $error['name'] ),
+ esc_url( $error['url'] ),
+ esc_html( $error['mailer_title'] ),
+ esc_html( $error['error_message'] )
+ );
+ }
+
+ ?>
+
+
+
+ ' . esc_html( $error ) . '' ),
+ WP::ADMIN_NOTICE_ERROR
+ );
+ break;
+
+ case 'zoho_unsuccessful_oauth':
+ WP::add_admin_notice_with_debug(
+ esc_html__( 'There was an error while processing the authentication request. Please recheck your Region, Client ID and Client Secret and try again.', 'wp-mail-smtp-pro' ),
+ WP::ADMIN_NOTICE_ERROR
+ );
+ break;
+
+ case 'google_one_click_setup_unsuccessful_oauth':
+ case 'outlook_one_click_setup_unsuccessful_oauth':
+ WP::add_admin_notice_with_debug(
+ esc_html__( 'There was an error while processing the authentication request.', 'wp-mail-smtp-pro' ),
+ WP::ADMIN_NOTICE_ERROR
+ );
+ break;
+ }
+
+ switch ( $success ) {
+ case 'microsoft_site_linked':
+ WP::add_admin_notice_with_debug(
+ esc_html__( 'You have successfully linked the current site with your Microsoft API project. Now you can start sending emails through Outlook.', 'wp-mail-smtp-pro' ),
+ WP::ADMIN_NOTICE_SUCCESS
+ );
+ break;
+
+ case 'zoho_site_linked':
+ WP::add_admin_notice_with_debug(
+ esc_html__( 'You have successfully linked the current site with your Zoho Mail API project. Now you can start sending emails through Zoho Mail.', 'wp-mail-smtp-pro' ),
+ WP::ADMIN_NOTICE_SUCCESS
+ );
+ break;
+
+ case 'google_one_click_setup_site_linked':
+ WP::add_admin_notice_with_debug(
+ esc_html__( 'You have successfully connected your site with your Gmail account. This site will now send emails via your Gmail account.', 'wp-mail-smtp-pro' ),
+ WP::ADMIN_NOTICE_SUCCESS
+ );
+ break;
+
+ case 'outlook_one_click_setup_site_linked':
+ WP::add_admin_notice_with_debug(
+ esc_html__( 'You have successfully connected your site with your Outlook account. This site will now send emails via your Outlook account.', 'wp-mail-smtp-pro' ),
+ WP::ADMIN_NOTICE_SUCCESS
+ );
+ break;
+ }
+ }
+
+ /**
+ * Filter the HTML of the auto-updates setting for WP Mail SMTP Pro plugin.
+ *
+ * @deprecated 3.0.0
+ *
+ * @since 2.3.0
+ *
+ * @param string $html The HTML of the plugin's auto-update column content, including
+ * toggle auto-update action links and time to next update.
+ * @param string $plugin_file Path to the plugin file relative to the plugins directory.
+ * @param array $plugin_data An array of plugin data.
+ *
+ * @return string
+ */
+ public function auto_update_setting_html( $html, $plugin_file, $plugin_data ) {
+
+ _deprecated_function( __METHOD__, '3.0.0' );
+
+ if (
+ ! empty( $plugin_data['Author'] ) &&
+ $plugin_data['Author'] === 'WPForms' &&
+ $plugin_file === plugin_basename( WPMS_PLUGIN_FILE )
+ ) {
+ $html = esc_html__( 'Auto-updates are not available.', 'wp-mail-smtp-pro' );
+ }
+
+ return $html;
+ }
+
+ /**
+ * Rollback to default value for automatically update WP Mail SMTP Pro plugin.
+ * Some devs or tools can use `auto_update_plugin` filter and turn on auto-updates for all plugins.
+ *
+ * @deprecated 3.0.0
+ *
+ * @since 2.3.0
+ *
+ * @param mixed $auto_update Whether to update.
+ * @param object $filter_payload The update offer.
+ *
+ * @return null|bool
+ */
+ public function rollback_auto_update_plugin_default_value( $auto_update, $filter_payload ) {
+
+ _deprecated_function( __METHOD__, '3.0.0' );
+
+ // Check whether auto-updates for plugins are supported and enabled. If not, return early.
+ if (
+ ! function_exists( 'wp_is_auto_update_enabled_for_type' ) ||
+ ! wp_is_auto_update_enabled_for_type( 'plugin' )
+ ) {
+ return $auto_update;
+ }
+
+ if ( empty( $auto_update ) ) {
+ return $auto_update;
+ }
+
+ if ( ! is_object( $filter_payload ) || empty( $filter_payload->plugin ) ) {
+ return $auto_update;
+ }
+
+ // Determine if it's a WP Mail SMTP Pro plugin. If so, return null (default value).
+ if ( $filter_payload->plugin === plugin_basename( WPMS_PLUGIN_FILE ) ) {
+ return null;
+ }
+
+ return $auto_update;
+ }
+
+ /**
+ * Filter value, which is prepared for `auto_update_plugins` option before it's saved into DB.
+ * We need to exclude WP Mail SMTP Pro.
+ *
+ * @deprecated 3.0.0
+ *
+ * @since 2.3.0
+ *
+ * @param mixed $plugins New plugins of the network option.
+ * @param mixed $old_plugins Old plugins of the network option.
+ * @param string $option Option name.
+ * @param int $network_id ID of the network.
+ *
+ * @return array
+ */
+ public function update_auto_update_plugins_option( $plugins, $old_plugins, $option, $network_id ) {
+
+ _deprecated_function( __METHOD__, '3.0.0' );
+
+ // No need to filter out our plugins if none were saved.
+ if ( empty( $plugins ) ) {
+ return $plugins;
+ }
+
+ // Check whether auto-updates for plugins are supported and enabled. If so, exclude WP Mail SMTP Pro plugin.
+ if ( function_exists( 'wp_is_auto_update_enabled_for_type' ) && wp_is_auto_update_enabled_for_type( 'plugin' ) ) {
+ return array_diff( (array) $plugins, [ plugin_basename( WPMS_PLUGIN_FILE ) ] );
+ }
+
+ return $plugins;
+ }
+
+ /**
+ * Add the Pro usage tracking data.
+ *
+ * @since 2.3.0
+ *
+ * @param array $data The existing usage tracking data.
+ *
+ * @return array
+ */
+ public function usage_tracking_get_data( $data ) {
+
+ $options = Options::init();
+
+ $disabled_controls = [];
+
+ // Get the state of each control.
+ foreach ( $this->get_control()->get_controls( true ) as $key ) {
+ if ( (bool) $options->get( 'control', $key ) ) {
+ $disabled_controls[] = $key;
+ }
+ }
+
+ $data['wp_mail_smtp_pro_enable_log'] = (bool) $options->get( 'logs', 'enabled' );
+ $data['wp_mail_smtp_pro_log_email_content'] = (bool) $options->get( 'logs', 'log_email_content' );
+ $data['wp_mail_smtp_pro_log_save_attachments'] = (bool) $options->get( 'logs', 'save_attachments' );
+ $data['wp_mail_smtp_pro_log_open_email_tracking'] = (bool) $options->get( 'logs', 'open_email_tracking' );
+ $data['wp_mail_smtp_pro_log_click_link_tracking'] = (bool) $options->get( 'logs', 'click_link_tracking' );
+ $data['wp_mail_smtp_pro_log_retention_period'] = $options->get( 'logs', 'log_retention_period' );
+ $data['wp_mail_smtp_pro_log_entry_count'] = $this->get_logs()->is_valid_db() ? ( new EmailsCollection() )->get_count() : 0;
+ $data['wp_mail_smtp_pro_disabled_controls'] = $disabled_controls;
+
+ // Alerts usage tracking.
+ $alerts_loader = new AlertsLoader();
+
+ $enabled_alerts = array_filter(
+ array_keys( $alerts_loader->get_providers() ),
+ function ( $provider_slug ) use ( $options ) {
+ return $options->get( 'alert_' . $provider_slug, 'enabled' );
+ }
+ );
+
+ $data['wp_mail_smtp_pro_alerts_enabled'] = count( $enabled_alerts ) > 0;
+
+ foreach ( $enabled_alerts as $provider_slug ) {
+ $connections = $options->get( 'alert_' . $provider_slug, 'connections' );
+
+ $data[ 'wp_mail_smtp_pro_alerts_enabled_channel_' . $provider_slug ] = count( $connections );
+ }
+
+ $additional_connections = $this->get_additional_connections()->get_configured_connections();
+ $backup_connection_enabled = ! empty( Options::init()->get( 'backup_connection', 'connection_id' ) );
+ $smart_routing_enabled = (bool) Options::init()->get( 'smart_routing', 'enabled' );
+
+ $data['wp_mail_smtp_pro_additional_connections_count'] = count( $additional_connections );
+ $data['wp_mail_smtp_pro_backup_connection_enabled'] = $backup_connection_enabled;
+ $data['wp_mail_smtp_pro_smart_routing_enabled'] = $smart_routing_enabled;
+
+ // Rate Limiting usage tracking.
+ $data['wp_mail_smtp_pro_rate_limiting_enabled'] = (bool) RateLimiting::is_enabled();
+
+ return $data;
+ }
+
+ /**
+ * Setup any additional PRO mailer options for the Setup Wizard.
+ * This data is passed via `wp_localize_script` before the Vue app is initialized.
+ *
+ * @since 2.6.0
+ * @since 3.11.0 Handle WPMS_AMAZONSES_DISPLAY_IDENTITIES constant.
+ *
+ * @param array $data The default mailer options data.
+ *
+ * @return array
+ */
+ public function setup_wizard_prepare_mailer_options( $data ) {
+
+ if ( key_exists( 'amazonses', $data ) ) {
+ if ( empty( $data['amazonses']['disabled'] ) ) {
+ $amazon_regions = \WPMailSMTP\Pro\Providers\AmazonSES\Auth::get_regions_names();
+ $prepared_regions = [];
+
+ foreach ( $amazon_regions as $value => $label ) {
+ $prepared_regions[] = [
+ 'label' => $label,
+ 'value' => $value,
+ ];
+ }
+
+ $data['amazonses']['region_options'] = $prepared_regions;
+ }
+
+ $data['amazonses']['display_identities'] = (
+ ! defined( 'WPMS_AMAZONSES_DISPLAY_IDENTITIES' ) ||
+ WPMS_AMAZONSES_DISPLAY_IDENTITIES === true
+ );
+ }
+
+ if ( key_exists( 'outlook', $data ) && empty( $data['outlook']['disabled'] ) ) {
+ $data['outlook']['redirect_uri'] = \WPMailSMTP\Pro\Providers\Outlook\Auth::get_plugin_auth_url();
+ }
+
+ if ( key_exists( 'zoho', $data ) && empty( $data['zoho']['disabled'] ) ) {
+ $data['zoho']['redirect_uri'] = \WPMailSMTP\Pro\Providers\Zoho\Auth::get_plugin_auth_url();
+ $data['zoho']['domain_options'] = wp_mail_smtp()->get_providers()->get_options( 'zoho' )->get_zoho_domains();
+ }
+
+ return $data;
+ }
+
+ /**
+ * A filter hook to check if license key exists.
+ *
+ * @since 2.6.0
+ *
+ * @return bool
+ */
+ public function does_license_key_exist() {
+
+ $license = Options::init()->get( 'license', 'key' );
+
+ return ! empty( $license );
+ }
+
+ /**
+ * AJAX callback for getting the current Amazon SES Identities in a JS friendly format.
+ *
+ * @since 2.6.0
+ */
+ public function get_amazon_ses_identities() {
+
+ check_ajax_referer( 'wpms-admin-nonce', 'nonce' );
+
+ if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_global_options() ) ) {
+ wp_send_json_error();
+ }
+
+ $options = Options::init();
+
+ // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
+ $ses_settings = isset( $_POST['value'] ) ? wp_slash( json_decode( wp_unslash( $_POST['value'] ), true ) ) : [];
+
+ if ( empty( $ses_settings ) ) {
+ wp_send_json_error();
+ }
+
+ // Update Amazon SES settings with current settings to retrieve the SES Identities for.
+ $options->set( [ 'amazonses' => $ses_settings ], false, false );
+
+ $table = new \WPMailSMTP\Pro\Providers\AmazonSES\IdentitiesTable();
+ $table->prepare_items();
+
+ $error = $table->get_last_error();
+
+ if ( ! $table->has_items() && $error instanceof WP_Error ) {
+ wp_send_json_error( $error->get_error_message() );
+ }
+
+ wp_send_json_success(
+ [
+ 'columns' => $table->get_columns_for_js(),
+ 'data' => $table->get_items_for_js(),
+ ]
+ );
+ }
+
+ /**
+ * AJAX callback for the Amazon SES identity registration processing.
+ *
+ * @since 2.6.0
+ */
+ public function amazon_ses_identity_registration() {
+
+ check_ajax_referer( 'wpms-admin-nonce', 'nonce' );
+
+ if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_global_options() ) ) {
+ wp_send_json_error();
+ }
+
+ $type = isset( $_POST['type'] ) ? sanitize_key( $_POST['type'] ) : '';
+ $value = isset( $_POST['value'] ) ? sanitize_text_field( wp_unslash( $_POST['value'] ) ) : '';
+
+ if ( $type === 'email' && ! is_email( $value ) ) {
+ wp_send_json_error( esc_html__( 'Please provide a valid email address.', 'wp-mail-smtp-pro' ) );
+ } elseif ( $type === 'domain' && empty( $value ) ) {
+ wp_send_json_error( esc_html__( 'Please provide a domain.', 'wp-mail-smtp-pro' ) );
+ }
+
+ $ses = new \WPMailSMTP\Pro\Providers\AmazonSES\Auth();
+
+ // Verify domain for easier conditional checking below.
+ $domain_dkim_tokens = ( $type === 'domain' ) ? $ses->do_verify_domain_dkim( $value ) : '';
+
+ if ( $type === 'email' && $ses->do_verify_email( $value ) === true ) {
+ wp_send_json_success(
+ [
+ 'type' => $type,
+ 'value' => esc_html( $value ),
+ ]
+ );
+ } elseif ( $type === 'domain' && ! empty( $domain_dkim_tokens ) ) {
+ wp_send_json_success(
+ [
+ 'type' => $type,
+ 'value' => esc_html( $value ),
+ 'domain_dkim_dns_records' => SESOptions::prepare_dkim_dns_records(
+ $value,
+ $domain_dkim_tokens,
+ wp_mail_smtp()->get_connections_manager()->get_primary_connection()
+ ),
+ ]
+ );
+ } else {
+ $error = $ses->get_last_error();
+
+ wp_send_json_error(
+ $error instanceof WP_Error
+ ? esc_html( $error->get_error_message() )
+ : esc_html__( 'Something went wrong. Please try again later.', 'wp-mail-smtp-pro' )
+ );
+ }
+ }
+
+ /**
+ * Prepare the oAuth URL redirect for the PRO oAuth mailers.
+ *
+ * @since 2.6.0
+ *
+ * @param array $data The default oAuth data.
+ * @param string $mailer The mailer to prepare the redirect URL for.
+ *
+ * @return array
+ *
+ * @throws \Exception If auth classes fail to initialize.
+ */
+ public function prepare_oauth_url_redirect( $data, $mailer ) {
+
+ $auth = null;
+
+ switch ( $mailer ) {
+ case 'outlook':
+ $auth = wp_mail_smtp()->get_providers()->get_auth( 'outlook' );
+ break;
+
+ case 'zoho':
+ $auth = new \WPMailSMTP\Pro\Providers\Zoho\Auth();
+ break;
+ }
+
+ if ( ! empty( $auth ) && $auth->is_clients_saved() && $auth->is_auth_required() ) {
+ $data['oauth_url'] = $auth->get_auth_url();
+ }
+
+ return $data;
+ }
+
+ /**
+ * AJAX callback for verifying the license key.
+ *
+ * @since 2.6.0
+ */
+ public function verify_license_key() {
+
+ check_ajax_referer( 'wpms-admin-nonce', 'nonce' );
+
+ if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_global_options() ) ) {
+ wp_send_json_error( esc_html__( 'You don\'t have the permission to perform this action.', 'wp-mail-smtp-pro' ) );
+ }
+
+ $license_key = ! empty( $_POST['license_key'] ) ? sanitize_key( $_POST['license_key'] ) : '';
+
+ if ( empty( $license_key ) ) {
+ wp_send_json_error( esc_html__( 'Please enter a valid license key!', 'wp-mail-smtp-pro' ) );
+ }
+
+ $license_object = $this->get_license();
+
+ // Let the License class handle the rest via AJAX.
+ if ( method_exists( $license_object, 'verify_key' ) ) {
+ $license_object->verify_key( $license_key, true );
+ }
+
+ wp_send_json_error( esc_html__( 'License functionality missing!', 'wp-mail-smtp-pro' ) );
+ }
+
+ /**
+ * Add any Pro AS tasks that need to be temporary canceled (reset) for PHP 8 compatibility in v2.6 release.
+ *
+ * @since 2.6.0
+ *
+ * @param array $tasks The default tasks that will be canceled.
+ *
+ * @return array
+ */
+ public function maybe_cancel_recurring_as_tasks_for_v26( $tasks ) {
+
+ // Get the Logs retention period setting.
+ $retention_period = Options::init()->get( 'logs', 'log_retention_period' );
+
+ if ( ! empty( $retention_period ) ) {
+ $tasks[] = '\WPMailSMTP\Pro\Tasks\EmailLogCleanupTask';
+ }
+
+ return $tasks;
+ }
+
+ /**
+ * Load the rate limiting functionality.
+ *
+ * @since 4.0.0
+ *
+ * @return Emails\RateLimiting
+ */
+ public function get_rate_limiting() {
+
+ static $rate_limiting;
+
+ if ( ! isset( $rate_limiting ) ) {
+ /**
+ * Filter the RateLimiting object.
+ *
+ * @since 4.0.0
+ *
+ * @param RateLimiting $rate_limiting The RateLimiting object.
+ */
+ $rate_limiting = apply_filters( 'wp_mail_smtp_pro_get_rate_limiting', new RateLimiting() );
+
+ $rate_limiting->hooks();
+ }
+
+ return $rate_limiting;
+ }
+
+ /**
+ * Replace the activation link for the Lite version of WP Mail SMTP.
+ * Activating the Lite version of WP Mail SMTP is not allowed when the Pro version is active.
+ *
+ * @since 4.4.0
+ *
+ * @param array $links Plugin row links.
+ *
+ * @return array
+ */
+ public function replace_action_links( array $links ): array {
+
+ $links['activate'] = __( 'Inactive — You are already using WP Mail SMTP Pro', 'wp-mail-smtp-pro' );
+
+ return $links;
+ }
+
+ /**
+ * Load the product API functionality.
+ *
+ * @since 4.4.0
+ *
+ * @return ProductApi
+ */
+ public function get_product_api() {
+
+ static $product_api;
+
+ if ( ! isset( $product_api ) ) {
+
+ /**
+ * Filter the ProductApi object.
+ *
+ * @since 4.4.0
+ *
+ * @param ProductApi $product_api The ProductApi object.
+ */
+ $product_api = apply_filters( 'wp_mail_smtp_pro_product_api', new ProductApi() );
+
+ $product_api->hooks();
+ }
+
+ return $product_api;
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/ProductApi/Client.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/ProductApi/Client.php
new file mode 100644
index 00000000..8ab5d633
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/ProductApi/Client.php
@@ -0,0 +1,203 @@
+product_name = $product_name;
+ $this->base_url = $base_url;
+ $this->site_url = ! empty( $args['site_url'] ) ? $args['site_url'] : get_site_url();
+ $this->license_key = ! empty( $args['license_key'] ) ? $args['license_key'] : '';
+ $this->license_type = ! empty( $args['license_type'] ) ? $args['license_type'] : 'lite';
+ $this->user_agent = ! empty( $args['user_agent'] ) ? $args['user_agent'] : '';
+ $this->environment = ! empty( $args['environment'] ) ? sanitize_key( $args['environment'] ) : 'production';
+
+ $this->credentials_repository = new CredentialsRepository();
+ $this->credentials = $this->credentials_repository->get( $this->environment );
+ }
+
+ /**
+ * Make a request.
+ *
+ * @since 4.4.0
+ *
+ * @param string $method Request method.
+ * @param string $endpoint Request endpoint.
+ * @param array $args Request arguments.
+ * @param bool $authenticated Whether the request should be authenticated.
+ *
+ * @return Response|WP_Error
+ */
+ public function request( $method, $endpoint, $args = [], bool $authenticated = true ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded
+
+ $url = $this->base_url . $endpoint;
+ $request_args = [
+ 'method' => $method,
+ ];
+ $headers = [
+ 'X-Site-Url' => $this->site_url,
+ 'Accept' => 'application/json',
+ ];
+
+ if ( ! empty( $this->user_agent ) ) {
+ $request_args['user-agent'] = $this->user_agent;
+ }
+
+ if ( isset( $args['json'] ) ) {
+ $request_args['body'] = wp_json_encode( $args['json'] );
+ $headers['Content-Type'] = 'application/json';
+ } elseif ( isset( $args['query'] ) ) {
+ $url = add_query_arg( $args['query'], $url );
+ } elseif ( isset( $args['body'] ) ) {
+ $request_args['body'] = $args['body'];
+ }
+
+ if ( isset( $args['timeout'] ) ) {
+ $request_args['timeout'] = $args['timeout'];
+ }
+
+ if ( $this->license_type === 'pro' && ! empty( $this->license_key ) ) {
+ $headers['X-License-Key'] = $this->license_key;
+ }
+
+ if ( $authenticated ) {
+ if ( ! $this->credentials->is_valid() ) {
+ $credentials = $this->generate_credentials();
+
+ if ( is_wp_error( $credentials ) ) {
+ return $credentials;
+ }
+ }
+
+ $headers['X-Site-Id'] = $this->credentials->get_site_id();
+ $headers['X-Public-Key'] = $this->credentials->get_public_key();
+ $headers['X-Token'] = $this->credentials->get_token();
+ }
+
+ $request_args['headers'] = array_merge( $headers, $args['headers'] ?? [] );
+
+ $response = wp_safe_remote_request( $url, $request_args );
+
+ return new Response( $response );
+ }
+
+ /**
+ * Generate credentials.
+ *
+ * @since 4.4.0
+ *
+ * @return Credentials|WP_Error
+ */
+ private function generate_credentials() {
+
+ $credentials_generator = new CredentialsGenerator( $this );
+ $credentials = $credentials_generator->generate( $this->license_type );
+
+ if ( is_wp_error( $credentials ) ) {
+ return $credentials;
+ }
+
+ $this->credentials = $credentials;
+
+ $this->credentials_repository->save( $this->credentials, $this->environment );
+
+ return $this->credentials;
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/ProductApi/Credentials.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/ProductApi/Credentials.php
new file mode 100644
index 00000000..089febaa
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/ProductApi/Credentials.php
@@ -0,0 +1,136 @@
+site_id = $site_id;
+ $this->public_key = $public_key;
+ $this->token = $token;
+ }
+
+ /**
+ * Whether credentials are valid.
+ *
+ * @since 4.4.0
+ *
+ * @return bool
+ */
+ public function is_valid() {
+
+ return ! empty( $this->site_id ) && ! empty( $this->public_key ) && ! empty( $this->token );
+ }
+
+ /**
+ * Get site ID.
+ *
+ * @since 4.4.0
+ *
+ * @return int
+ */
+ public function get_site_id() {
+
+ return $this->site_id;
+ }
+
+ /**
+ * Get public key.
+ *
+ * @since 4.4.0
+ *
+ * @return string
+ */
+ public function get_public_key() {
+
+ return $this->public_key;
+ }
+
+ /**
+ * Get token.
+ *
+ * @since 4.4.0
+ *
+ * @return string
+ */
+ public function get_token() {
+
+ return $this->token;
+ }
+
+ /**
+ * Get credentials as array.
+ *
+ * @since 4.4.0
+ *
+ * @return array
+ */
+ public function to_array() {
+
+ return [
+ 'site_id' => $this->site_id,
+ 'public_key' => $this->public_key,
+ 'token' => $this->token,
+ ];
+ }
+
+ /**
+ * Create credentials from array.
+ *
+ * @since 4.4.0
+ *
+ * @param array $data Credentials data.
+ *
+ * @return self
+ */
+ public static function from_array( array $data ) {
+
+ return new self(
+ ! empty( $data['site_id'] ) ? (int) $data['site_id'] : '',
+ ! empty( $data['public_key'] ) ? $data['public_key'] : '',
+ ! empty( $data['token'] ) ? $data['token'] : ''
+ );
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/ProductApi/CredentialsGenerationNonce.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/ProductApi/CredentialsGenerationNonce.php
new file mode 100644
index 00000000..8b7ea4ce
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/ProductApi/CredentialsGenerationNonce.php
@@ -0,0 +1,81 @@
+get(), $passed );
+ }
+
+ /**
+ * Delete the nonce.
+ *
+ * @since 4.4.0
+ *
+ * @return void
+ */
+ public function delete() {
+
+ delete_option( self::NONCE_OPTION );
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/ProductApi/CredentialsGenerator.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/ProductApi/CredentialsGenerator.php
new file mode 100644
index 00000000..0f61b588
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/ProductApi/CredentialsGenerator.php
@@ -0,0 +1,194 @@
+client = $client;
+ }
+
+ /**
+ * Generate authentication credentials.
+ *
+ * @since 4.4.0
+ *
+ * @param string $license_type License type. Either `pro` or `lite`.
+ *
+ * @return Credentials|WP_Error
+ */
+ public function generate( $license_type ) {
+
+ if ( $this->is_max_credentials_generation_attempts_reached() ) {
+ return new WP_Error( 'credentials_generation_max_attempts_reached', esc_html__( 'Max number of attempts to generate credentials has been reached.', 'wp-mail-smtp-pro' ) );
+ }
+
+ if ( get_transient( self::LOCK_OPTION ) ) {
+ return new WP_Error( 'credentials_generation_in_progress', esc_html__( 'Credentials generation already started by another request.', 'wp-mail-smtp-pro' ) );
+ }
+
+ set_transient( self::LOCK_OPTION, true, MINUTE_IN_SECONDS );
+
+ $nonce = new CredentialsGenerationNonce();
+
+ $response = $this->client->request(
+ 'POST',
+ 'auth/v1/' . $license_type . '/keys',
+ [
+ 'body' => array_merge(
+ $this->get_site_info(),
+ [
+ 'nonce' => $nonce->create(),
+ ]
+ ),
+ ],
+ false
+ );
+
+ $nonce->delete();
+ $this->update_credentials_generation_attempts_count( $response->is_successful() );
+
+ delete_transient( self::LOCK_OPTION );
+
+ if ( ! $response->is_successful() ) {
+ return $response->get_errors();
+ }
+
+ $body = $response->get_body();
+
+ if ( ! isset( $body['site_id'], $body['public_key'], $body['token'] ) ) {
+ return new WP_Error( 'credentials_generation_invalid_response', esc_html__( 'Invalid response for credentials generation.', 'wp-mail-smtp-pro' ) );
+ }
+
+ return Credentials::from_array( $body );
+ }
+
+ /**
+ * Check that we have not reached the max number of attempts to generate credentials.
+ *
+ * @since 4.4.0
+ *
+ * @return bool
+ */
+ private function is_max_credentials_generation_attempts_reached() {
+
+ $attempts_count = get_option( self::ATTEMPT_COUNTER_OPTION, 0 );
+
+ return $attempts_count >= self::MAX_ATTEMPTS;
+ }
+
+ /**
+ * Update count of the attempts to generate credentials.
+ * It allows us to prevent sending requests to the API server infinitely.
+ *
+ * @since 4.4.0
+ *
+ * @param bool $reset Reset the attempts count.
+ */
+ private function update_credentials_generation_attempts_count( $reset = false ) {
+
+ if ( $reset ) {
+ delete_option( self::ATTEMPT_COUNTER_OPTION );
+
+ return;
+ }
+
+ update_option(
+ self::ATTEMPT_COUNTER_OPTION,
+ get_option( self::ATTEMPT_COUNTER_OPTION, 0 ) + 1
+ );
+ }
+
+ /**
+ * Get site info data that required for credentials generation.
+ *
+ * @since 4.4.0
+ *
+ * @return array
+ */
+ private function get_site_info() {
+
+ $site_title = get_bloginfo( 'name' );
+ $site_locale = get_locale();
+ $site_timezone = wp_timezone_string();
+ $admin_email = get_option( 'admin_email' );
+
+ $info = [
+ 'site_title' => $site_title,
+ 'site_locale' => $site_locale,
+ 'site_timezone' => $site_timezone,
+ 'admin_email' => $admin_email,
+ ];
+
+ if ( empty( $admin_email ) ) {
+ return $info;
+ }
+
+ $user = get_user_by( 'email', $admin_email );
+
+ if ( $user === false ) {
+ return $info;
+ }
+
+ if ( ! empty( $user->first_name ) ) {
+ $info['admin_first_name'] = $user->first_name;
+ }
+
+ if ( ! empty( $user->last_name ) ) {
+ $info['admin_last_name'] = $user->last_name;
+ }
+
+ return $info;
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/ProductApi/CredentialsRepository.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/ProductApi/CredentialsRepository.php
new file mode 100644
index 00000000..f1b63fa2
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/ProductApi/CredentialsRepository.php
@@ -0,0 +1,49 @@
+get( 'product_api', 'credentials' );
+
+ return Credentials::from_array( $credentials[ $environment ] ?? [] );
+ }
+
+ /**
+ * Save credentials to storage.
+ *
+ * @since 4.4.0
+ *
+ * @param Credentials $credentials Product API authentication credentials.
+ * @param string $environment Environment for which to save credentials.
+ *
+ * @return void
+ */
+ public function save( Credentials $credentials, $environment = 'production' ) {
+
+ $options = Options::init();
+ $all_opt = $options->get_all_raw();
+
+ $all_opt['product_api']['credentials'][ $environment ] = $credentials->to_array();
+
+ $options->set( $all_opt, false, true );
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/ProductApi/ProductApi.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/ProductApi/ProductApi.php
new file mode 100644
index 00000000..a11387ea
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/ProductApi/ProductApi.php
@@ -0,0 +1,126 @@
+client !== null ) {
+ return $this->client;
+ }
+
+ $base_url = defined( 'WPMS_PRODUCT_API_BASE_URL' ) ? WPMS_PRODUCT_API_BASE_URL : self::BASE_URL;
+ $environment = defined( 'WPMS_PRODUCT_API_ENV' ) ? WPMS_PRODUCT_API_ENV : 'production';
+ $site_url = WP::get_site_url();
+ $license_key = wp_mail_smtp()->get_license_key();
+ $license_type = wp_mail_smtp()->is_pro() && ! empty( $license_key ) ? 'pro' : 'lite';
+ $user_agent = Helpers::get_default_user_agent();
+
+ $this->client = new Client(
+ self::PRODUCT_NAME,
+ $base_url,
+ [
+ 'site_url' => $site_url,
+ 'license_key' => $license_key,
+ 'license_type' => $license_type,
+ 'user_agent' => $user_agent,
+ 'environment' => $environment,
+ ]
+ );
+
+ return $this->client;
+ }
+
+ /**
+ * Verify nonce during the product API credentials generation.
+ *
+ * @since 4.4.0
+ */
+ public function maybe_verify_nonce() {
+
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+ if ( ! isset( $_REQUEST[ self::PRODUCT_NAME . '-product-api-nonce-verification' ] ) ) {
+ return;
+ }
+
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+ $request_none = isset( $_REQUEST['nonce'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['nonce'] ) ) : '';
+ $nonce = new CredentialsGenerationNonce();
+
+ if ( empty( $request_none ) || ! $nonce->verify( $request_none ) ) {
+ wp_send_json( [ 'status' => 'failed' ], 403 );
+ }
+
+ wp_send_json( [ 'status' => 'success' ] );
+ }
+
+ /**
+ * Unlock the product API credentials generation.
+ *
+ * @since 4.4.0
+ */
+ public function maybe_unlock_credentials_generation() {
+
+ if (
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+ ! isset( $_GET[ self::PRODUCT_NAME . '-product-api-unlock-credentials-generation' ] ) ||
+ ! current_user_can( wp_mail_smtp()->get_capability_manage_options() ) ||
+ ! wp_mail_smtp()->get_admin()->is_admin_page()
+ ) {
+ return;
+ }
+
+ delete_option( CredentialsGenerator::ATTEMPT_COUNTER_OPTION );
+ delete_transient( CredentialsGenerator::LOCK_OPTION );
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/ProductApi/Response.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/ProductApi/Response.php
new file mode 100644
index 00000000..fffff012
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/ProductApi/Response.php
@@ -0,0 +1,134 @@
+response = $response;
+ }
+
+ /**
+ * Get response body.
+ *
+ * @since 4.4.0
+ *
+ * @return string|array
+ */
+ public function get_body() {
+
+ $body = wp_remote_retrieve_body( $this->response );
+
+ if ( ! empty( $body ) && is_string( $body ) ) {
+ $decoded_body = json_decode( $body, true );
+
+ if ( json_last_error() === JSON_ERROR_NONE && is_array( $decoded_body ) ) {
+ $body = $decoded_body;
+ }
+ }
+
+ return $body;
+ }
+
+ /**
+ * Get response status code.
+ *
+ * @since 4.4.0
+ *
+ * @return int
+ */
+ public function get_status_code() {
+
+ return wp_remote_retrieve_response_code( $this->response );
+ }
+
+ /**
+ * Get response errors.
+ *
+ * @since 4.4.0
+ *
+ * @return false|WP_Error
+ */
+ public function get_errors() {
+
+ if ( $this->is_successful() ) {
+ return false;
+ }
+
+ if ( is_wp_error( $this->response ) ) {
+ return $this->response;
+ }
+
+ $body = $this->get_body();
+
+ if ( ! empty( $body['errors'] ) && is_array( $body['errors'] ) ) {
+ $errors = new WP_Error();
+
+ foreach ( array_values( $body['errors'] ) as $key => $error ) {
+ if ( empty( $error['message'] ) ) {
+ continue;
+ }
+
+ $message = $error['message'];
+
+ unset( $error['message'] );
+
+ $errors->add( 'error:' . $key, $message, $error );
+ }
+
+ return $errors;
+ }
+
+ return new WP_Error( 'unknown', wp_remote_retrieve_body( $this->response ) );
+ }
+
+ /**
+ * Whether response is successful.
+ *
+ * @since 4.4.0
+ *
+ * @return bool
+ */
+ public function is_successful() {
+
+ return $this->get_status_code() >= 200 && $this->get_status_code() <= 299;
+ }
+
+ /**
+ * Get response header.
+ *
+ * @since 4.4.0
+ *
+ * @param string $header Header name.
+ *
+ * @return string
+ */
+ public function get_header( $header ) {
+
+ return wp_remote_retrieve_header( $this->response, $header );
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/AmazonSES/Auth.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/AmazonSES/Auth.php
new file mode 100644
index 00000000..7aafc1a9
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/AmazonSES/Auth.php
@@ -0,0 +1,796 @@
+mailer_slug !== Options::SLUG ) {
+ return;
+ }
+
+ $this->options = $this->connection_options->get_group( $this->mailer_slug );
+
+ $this->include_vendor_lib();
+ }
+
+ /**
+ * Get AWS SES registered domains.
+ *
+ * @since 2.4.0
+ *
+ * @return array
+ */
+ public function get_registered_domains() {
+
+ if ( ! isset( $this->registered_domains ) ) {
+ $this->populate_identities();
+ }
+
+ return $this->registered_domains;
+ }
+
+ /**
+ * Get AWS SES registered email addresses.
+ *
+ * @since 2.4.0
+ *
+ * @return array
+ */
+ public function get_registered_emails() {
+
+ if ( ! isset( $this->registered_email_addresses ) ) {
+ $this->populate_identities();
+ }
+
+ return $this->registered_email_addresses;
+ }
+
+ /**
+ * Get the last error captured by an API call on this instance.
+ *
+ * Returns null when the most recent public API call on this instance succeeded
+ * (or no call has been made yet).
+ *
+ * @since 4.9.0
+ *
+ * @return WP_Error|null
+ */
+ public function get_last_error() {
+
+ return $this->last_error;
+ }
+
+ /**
+ * Get the list of supported AWS regions.
+ *
+ * @since 1.5.0
+ * @since 2.4.0 Added Ohio, Canada, Mumbai, Tokyo, Seoul, Singapore, Sydney, London, Frankfurt and São Paulo.
+ * @since 3.0.0 Added Cape Town, Milan, Bahrain and GovCloud (US-West).
+ * @since 3.3.0 Added Osaka.
+ *
+ * @return array
+ */
+ public static function get_regions_names() {
+
+ return [
+ self::AWS_US_EAST_1 => esc_html__( 'US East (N. Virginia)', 'wp-mail-smtp-pro' ),
+ self::AWS_US_EAST_2 => esc_html__( 'US East (Ohio)', 'wp-mail-smtp-pro' ),
+ self::AWS_US_WEST_1 => esc_html__( 'US West (N. California)', 'wp-mail-smtp-pro' ),
+ self::AWS_US_WEST_2 => esc_html__( 'US West (Oregon)', 'wp-mail-smtp-pro' ),
+ self::AWS_EU_WEST_1 => esc_html__( 'EU (Ireland)', 'wp-mail-smtp-pro' ),
+ self::AWS_EU_WEST_2 => esc_html__( 'EU (London)', 'wp-mail-smtp-pro' ),
+ self::AWS_EU_WEST_3 => esc_html__( 'EU (Paris)', 'wp-mail-smtp-pro' ),
+ self::AWS_EU_CENTRAL_1 => esc_html__( 'EU (Frankfurt)', 'wp-mail-smtp-pro' ),
+ self::AWS_EU_NORTH_1 => esc_html__( 'EU (Stockholm)', 'wp-mail-smtp-pro' ),
+ self::AWS_EU_SOUTH_1 => esc_html__( 'EU (Milan)', 'wp-mail-smtp-pro' ),
+ self::AWS_AP_SOUTH_1 => esc_html__( 'Asia Pacific (Mumbai)', 'wp-mail-smtp-pro' ),
+ self::AWS_AP_NORTHEAST_1 => esc_html__( 'Asia Pacific (Tokyo)', 'wp-mail-smtp-pro' ),
+ self::AWS_AP_NORTHEAST_2 => esc_html__( 'Asia Pacific (Seoul)', 'wp-mail-smtp-pro' ),
+ self::AWS_AP_NORTHEAST_3 => esc_html__( 'Asia Pacific (Osaka)', 'wp-mail-smtp-pro' ),
+ self::AWS_AP_SOUTHEAST_1 => esc_html__( 'Asia Pacific (Singapore)', 'wp-mail-smtp-pro' ),
+ self::AWS_AP_SOUTHEAST_2 => esc_html__( 'Asia Pacific (Sydney)', 'wp-mail-smtp-pro' ),
+ self::AWS_AF_SOUTH_1 => esc_html__( 'Africa (Cape Town)', 'wp-mail-smtp-pro' ),
+ self::AWS_CA_CENTRAL_1 => esc_html__( 'Canada (Central)', 'wp-mail-smtp-pro' ),
+ self::AWS_ME_SOUTH_1 => esc_html__( 'Middle East (Bahrain)', 'wp-mail-smtp-pro' ),
+ self::AWS_SA_EAST_1 => esc_html__( 'South America (São Paulo)', 'wp-mail-smtp-pro' ),
+ self::AWS_US_GOV_WEST_1 => esc_html__( 'GovCloud (US-West)', 'wp-mail-smtp-pro' ),
+ ];
+ }
+
+ /**
+ * Get the list of supported AWS regions coordinates.
+ *
+ * @since 1.5.0
+ * @since 2.4.0 Added Ohio, Canada, London, São Paulo, Tokyo, Seoul and Singapore.
+ * @since 3.0.0 Added Cape Town, Milan and Bahrain.
+ * @since 3.3.0 Added Osaka.
+ *
+ * @return array
+ */
+ public static function get_regions_coordinates() {
+
+ return [
+ self::AWS_US_EAST_1 => [
+ 'lat' => 38.837392,
+ 'lon' => - 77.447313,
+ ],
+ self::AWS_US_EAST_2 => [
+ 'lat' => 40.417286,
+ 'lon' => - 82.907120,
+ ],
+ self::AWS_US_WEST_1 => [
+ 'lat' => 36.778259,
+ 'lon' => - 119.417931,
+ ],
+ self::AWS_US_WEST_2 => [
+ 'lat' => 45.3573,
+ 'lon' => - 122.6068,
+ ],
+ self::AWS_EU_WEST_1 => [
+ 'lat' => 53.305494,
+ 'lon' => - 7.737649,
+ ],
+ self::AWS_EU_WEST_2 => [
+ 'lat' => 51.5074,
+ 'lon' => 0.1278,
+ ],
+ self::AWS_EU_WEST_3 => [
+ 'lat' => 48.864716,
+ 'lon' => 2.349014,
+ ],
+ self::AWS_EU_CENTRAL_1 => [
+ 'lat' => 50.1109,
+ 'lon' => 8.6821,
+ ],
+ self::AWS_EU_NORTH_1 => [
+ 'lat' => 59.334591,
+ 'lon' => 18.063240,
+ ],
+ self::AWS_EU_SOUTH_1 => [
+ 'lat' => 45.4642,
+ 'lon' => 9.1900,
+ ],
+ self::AWS_AP_SOUTH_1 => [
+ 'lat' => 19.0760,
+ 'lon' => 72.8777,
+ ],
+ self::AWS_AP_NORTHEAST_1 => [
+ 'lat' => 35.689487,
+ 'lon' => 139.691711,
+ ],
+ self::AWS_AP_NORTHEAST_2 => [
+ 'lat' => 37.566536,
+ 'lon' => 126.977966,
+ ],
+ self::AWS_AP_NORTHEAST_3 => [
+ 'lat' => 34.693737,
+ 'lon' => 135.502167,
+ ],
+ self::AWS_AP_SOUTHEAST_1 => [
+ 'lat' => 1.352083,
+ 'lon' => 103.819839,
+ ],
+ self::AWS_AP_SOUTHEAST_2 => [
+ 'lat' => 33.8688,
+ 'lon' => 151.2093,
+ ],
+ self::AWS_AF_SOUTH_1 => [
+ 'lat' => - 33.9249,
+ 'lon' => 18.4241,
+ ],
+ self::AWS_CA_CENTRAL_1 => [
+ 'lat' => 51.2538,
+ 'lon' => 85.3232,
+ ],
+ self::AWS_ME_SOUTH_1 => [
+ 'lat' => 26.0667,
+ 'lon' => 50.5577,
+ ],
+ self::AWS_SA_EAST_1 => [
+ 'lat' => 23.5505,
+ 'lon' => 46.6333,
+ ],
+ ];
+ }
+
+ /**
+ * Init and get AWS SES client to work with.
+ *
+ * @since 1.5.0
+ * @since 2.4.0 Switch to official AWS SDK.
+ * @since 3.5.0 Added `SesV2Client` client support.
+ * @since 3.9.0 Added `disable_request_compression` and `request_min_compression_size_bytes`
+ * in the client arguments.
+ *
+ * @param string $version Client version.
+ *
+ * @return SesClient|SesV2Client
+ */
+ public function get_client( $version = 'v1' ) {
+
+ // Doesn't load client twice + gives ability to overwrite.
+ if ( ! empty( $this->client[ $version ] ) ) {
+ return $this->client[ $version ];
+ }
+
+ /**
+ * Filters AWS SES client arguments.
+ *
+ * @since 2.4.0
+ *
+ * @param array $args AWS SES client arguments.
+ * @param string $version AWS SES client version.
+ */
+ $args = apply_filters(
+ 'wp_mail_smtp_providers_auth_aws_get_client_args',
+ [
+ 'credentials' => [
+ 'key' => $this->options['client_id'],
+ 'secret' => $this->options['client_secret'],
+ ],
+ 'region' => empty( $this->options['region'] ) ? self::AWS_US_EAST_1 : self::prepare_region( $this->options['region'] ),
+ 'version' => $version === 'v1' ? '2010-12-01' : '2019-09-27',
+ ],
+ $version
+ );
+
+ // Prevent AWS SDK to require additional files.
+ $args['disable_request_compression'] = true;
+ $args['request_min_compression_size_bytes'] = 10240;
+
+ // Suppress PHP deprecation warnings.
+ $args['suppress_php_deprecation_warning'] = true;
+
+ // Disable AWS shared config files if open_basedir restriction would prevent access.
+ // This prevents PHP warnings when the SDK tries to read ~/.aws/config or ~/.aws/credentials.
+ // Only override when `wp_mail_smtp_providers_auth_aws_get_client_args` filter hasn't taken
+ // a stance — an explicit `true` (or `false`) from the caller wins over our auto-detection.
+ if ( ! array_key_exists( 'use_aws_shared_config_files', $args ) && $this->is_aws_shared_config_restricted() ) {
+ $args['use_aws_shared_config_files'] = false;
+ }
+
+ $this->client[ $version ] = $version === 'v1' ? new SesClient( $args ) : new SesV2Client( $args );
+
+ return $this->client[ $version ];
+ }
+
+ /**
+ * Prepare the selected region.
+ * The old format for saving regions was: 'email.us-east-1.amazonaws.com'.
+ * The new one is: 'us-east-1'.
+ *
+ * So, if the old one is matched, filter it down to the new format.
+ *
+ * @since 2.4.0
+ *
+ * @param string $region The AWS region in various formats.
+ *
+ * @return string
+ */
+ public static function prepare_region( $region ) {
+
+ if ( preg_match( '/email\.(.*)\.amazonaws\.com/', $region, $match ) ) {
+ $region = ! empty( $match[1] ) ? $match[1] : $region;
+ }
+
+ return $region;
+ }
+
+ /**
+ * Populate the registered domains and email addresses by fetching the data from AWS API
+ * and filter the results.
+ *
+ * The domains have the `VerificationToken` attribute, while the emails don't.
+ *
+ * @since 2.4.0
+ */
+ private function populate_identities() {
+
+ $identities = $this->fetch_identities();
+ $domains = [];
+ $emails = [];
+
+ if ( ! empty( $identities ) ) {
+ foreach ( $identities as $identity => $attributes ) {
+ if ( $attributes['IdentityType'] === 'DOMAIN' ) {
+ $domains[ $identity ] = $attributes;
+ } else {
+ $emails[ $identity ] = $attributes;
+ }
+ }
+ }
+
+ $this->registered_domains = $domains;
+ $this->registered_email_addresses = $emails;
+ }
+
+ /**
+ * Send a request to get the list of all AWS SES registered identities (emails and domains).
+ *
+ * Array example:
+ * key: domain name of email address
+ * value: - IdentityType - DOMAIN or EMAIL_ADDRESS
+ * - VerificationStatus
+ * - VerificationToken (the TXT record value for the DNS setup) - only present for domains!
+ * - DkimEnabled
+ * - DkimVerificationStatus
+ * - DkimTokens (the tokens for generate CNAME record values for the DKIM DNS setup)
+ *
+ * @since 2.4.0
+ * @since 3.3.0 Added DKIM verification attributes.
+ *
+ * @return array
+ */
+ private function fetch_identities() {
+
+ $this->last_error = null;
+
+ try {
+ $identities_response = $this->get_client( 'v2' )->listEmailIdentities();
+ $identities = $identities_response->get( 'EmailIdentities' );
+ $identities_list = array_column( $identities, 'IdentityName' );
+
+ $identities = array_merge_recursive(
+ array_combine( $identities_list, $identities ),
+ $this->get_verification_attributes( $identities_list ),
+ $this->get_dkim_verification_attributes( $identities_list )
+ );
+
+ } catch ( \Exception $e ) {
+ $this->last_error = new WP_Error( 'wp_mail_smtp_ses_fetch_identities_failed', $e->getMessage() );
+
+ return [];
+ }
+
+ return $identities;
+ }
+
+ /**
+ * Send a request to get the identities verification attributes.
+ *
+ * @since 3.3.0
+ *
+ * @param array|string $identities Identities array.
+ *
+ * @return array
+ */
+ private function get_verification_attributes( $identities ) {
+
+ $result = [];
+ $identities_chunks = array_chunk( (array) $identities, 100 );
+
+ foreach ( $identities_chunks as $identities_chunk ) {
+ $response = $this->get_client()->getIdentityVerificationAttributes( [ 'Identities' => $identities_chunk ] );
+
+ if ( ! empty( $response->get( 'VerificationAttributes' ) ) ) {
+ $result = array_merge( $result, $response->get( 'VerificationAttributes' ) );
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Send a request to get the identities DKIM verification attributes.
+ *
+ * @since 3.3.0
+ *
+ * @param array|string $identities Identities array.
+ *
+ * @return array
+ */
+ private function get_dkim_verification_attributes( $identities ) {
+
+ $result = [];
+ $identities_chunks = array_chunk( (array) $identities, 100 );
+
+ foreach ( $identities_chunks as $identities_chunk ) {
+ $response = $this->get_client()->getIdentityDkimAttributes( [ 'Identities' => $identities_chunk ] );
+
+ if ( ! empty( $response->get( 'DkimAttributes' ) ) ) {
+ $result = array_merge( $result, $response->get( 'DkimAttributes' ) );
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Send a request to get the identities DKIM verification tokens.
+ *
+ * @since 3.3.0
+ *
+ * @param string $domain Domain name.
+ *
+ * @return array|WP_Error
+ */
+ public function get_dkim_tokens( $domain ) {
+
+ try {
+ $attributes = $this->get_dkim_verification_attributes( $domain );
+ } catch ( \Exception $e ) {
+ return new WP_Error( $e->getCode(), $e->getMessage() );
+ }
+
+ if ( empty( $attributes ) || empty( current( $attributes )['DkimTokens'] ) ) {
+ return new WP_Error(
+ 'disabled_dkim',
+ sprintf(
+ esc_html__( /* translators: %s - domain name. */
+ 'It looks like DKIM is not enabled for this domain. Please visit AWS SES console and enable DKIM for %s.',
+ 'wp-mail-smtp-pro'
+ ),
+ esc_html( $domain )
+ )
+ );
+ }
+
+ return current( $attributes )['DkimTokens'];
+ }
+
+ /**
+ * Send a request to verify a domain DKIM.
+ *
+ * @since 3.3.0
+ *
+ * @param string $domain Domain to verify.
+ *
+ * @return bool|array False on error or the array of the tokens for generate CNAME record values for the DKIM DNS setup.
+ */
+ public function do_verify_domain_dkim( $domain ) {
+
+ $this->last_error = null;
+
+ try {
+ $response = $this->get_client()->verifyDomainDkim( [ 'Domain' => $domain ] );
+ } catch ( \Exception $e ) {
+ $this->last_error = new WP_Error( 'wp_mail_smtp_ses_verify_domain_dkim_failed', $e->getMessage() );
+
+ return false;
+ }
+
+ if (
+ is_object( $response ) &&
+ ! empty( $response->get( 'DkimTokens' ) )
+ ) {
+ return $response->get( 'DkimTokens' );
+ }
+
+ return false;
+ }
+
+ /**
+ * Send a request to verify an email address.
+ *
+ * @since 1.5.0
+ * @since 2.4.0 Switch to official AWS SDK.
+ *
+ * @param string $email Email address to verify.
+ *
+ * @return bool
+ */
+ public function do_verify_email( $email ) {
+
+ $this->last_error = null;
+
+ try {
+ $response = $this->get_client()->verifyEmailAddress( [ 'EmailAddress' => $email ] );
+ } catch ( \Exception $e ) {
+ $this->last_error = new WP_Error( 'wp_mail_smtp_ses_verify_email_failed', $e->getMessage() );
+
+ return false;
+ }
+
+ if (
+ is_object( $response ) &&
+ ! empty( $response )
+ ) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Send a request to delete a verified email address or domain.
+ *
+ * @since 2.4.0
+ *
+ * @param string $identity Email address or domain to remove.
+ *
+ * @return bool
+ */
+ public function do_delete_identity( $identity ) {
+
+ $this->last_error = null;
+
+ try {
+ $response = $this->get_client()->deleteIdentity( [ 'Identity' => $identity ] );
+ } catch ( \Exception $e ) {
+ $this->last_error = new WP_Error( 'wp_mail_smtp_ses_delete_identity_failed', $e->getMessage() );
+
+ return false;
+ }
+
+ if (
+ is_object( $response ) &&
+ ! empty( $response )
+ ) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * AmazonSES requires a selected region AND both keys.
+ *
+ * @since 1.5.0
+ *
+ * @return bool
+ */
+ public function is_connection_ready() {
+
+ return $this->is_clients_saved() && ! empty( $this->options['region'] );
+ }
+
+ /**
+ * Whether we should perform an extra auth step.
+ *
+ * @since 1.5.0
+ *
+ * @return bool
+ */
+ public function is_auth_required() {
+
+ return false;
+ }
+
+ /**
+ * Check if open_basedir restriction would prevent access to AWS shared config files.
+ *
+ * This prevents PHP warnings when the SDK tries to read ~/.aws/config or ~/.aws/credentials
+ * on servers where open_basedir restriction is in effect.
+ *
+ * Logic mirrors AWS SDK's AbstractConfigurationProvider::getDefaultConfigFilename().
+ *
+ * @since 4.9.0
+ *
+ * @return bool True if shared config files are restricted, false otherwise.
+ */
+ private function is_aws_shared_config_restricted() {
+
+ // Get config file path the same way SDK does.
+ $aws_config_file = $this->get_aws_config_filename();
+
+ // Use @ to suppress open_basedir warnings during the check itself.
+ // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
+ return ! @is_readable( $aws_config_file );
+ }
+
+ /**
+ * Get AWS config filename using the same logic as AWS SDK.
+ *
+ * Mirrors AWS SDK's AbstractConfigurationProvider::getDefaultConfigFilename()
+ * and AbstractConfigurationProvider::getHomeDir().
+ *
+ * @since 4.9.0
+ *
+ * @return string Config file path.
+ */
+ private function get_aws_config_filename() {
+
+ // First check if AWS_CONFIG_FILE env variable is set.
+ // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.runtime_configuration_getenv
+ $filename = getenv( 'AWS_CONFIG_FILE' );
+
+ if ( ! empty( $filename ) ) {
+ return $filename;
+ }
+
+ // Match SDK: with no resolvable home, fall back to a leading-slash path so the
+ // readability check still runs and predictably fails closed.
+ return ( $this->get_aws_home_dir() ?? '' ) . '/.aws/config';
+ }
+
+ /**
+ * Get AWS home directory using the same logic as AWS SDK.
+ *
+ * Mirrors AWS SDK's AbstractConfigurationProvider::getHomeDir().
+ *
+ * @since 4.9.0
+ *
+ * @return string|null Home directory path or null if cannot be determined.
+ */
+ private function get_aws_home_dir() {
+
+ // On Linux/Unix-like systems, use the HOME environment variable.
+ // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.runtime_configuration_getenv
+ $home_dir = getenv( 'HOME' );
+
+ if ( ! empty( $home_dir ) ) {
+ return $home_dir;
+ }
+
+ // Get the HOMEDRIVE and HOMEPATH values for Windows hosts.
+ // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.runtime_configuration_getenv
+ $home_drive = getenv( 'HOMEDRIVE' );
+ // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.runtime_configuration_getenv
+ $home_path = getenv( 'HOMEPATH' );
+
+ if ( ! empty( $home_drive ) && ! empty( $home_path ) ) {
+ return $home_drive . $home_path;
+ }
+
+ return null;
+ }
+
+ /**
+ * Send a request to delete a verified email address.
+ *
+ * Not used anymore.
+ *
+ * @deprecated 2.4.0
+ *
+ * @since 1.5.0
+ * @since 2.4.0 Switch to official AWS SDK.
+ *
+ * @param string $email Email address to remove.
+ *
+ * @return bool
+ */
+ public function do_delete_verified_email( $email ) {
+
+ _deprecated_function(
+ __METHOD__,
+ '2.4.0',
+ 'WPMailSMTP\Pro\Providers\AmazonSES\Auth::do_delete_identity()'
+ );
+
+ return $this->do_delete_identity( $email );
+ }
+
+ /**
+ * Send a request to get the list of verified emails.
+ *
+ * Not used anymore.
+ *
+ * @deprecated 2.4.0
+ *
+ * @since 1.5.0
+ * @since 2.4.0 Switch to official AWS SDK.
+ *
+ * @return array
+ */
+ public function get_verified_emails() {
+
+ _deprecated_function(
+ __METHOD__,
+ '2.4.0',
+ 'WPMailSMTP\Pro\Providers\AmazonSES\Auth::get_registered_emails()'
+ );
+
+ return array_keys( $this->get_registered_emails() );
+ }
+
+ /**
+ * Send a request to verify a domain.
+ *
+ * @deprecated 3.3.0 Switched to DKIM verification.
+ *
+ * @since 2.4.0
+ *
+ * @param string $domain Domain to verify.
+ *
+ * @return bool|string False on error or the string TXT record value for the DNS setting.
+ */
+ public function do_verify_domain( $domain ) {
+
+ _deprecated_function( __METHOD__, '3.3.0', self::class . '::do_verify_domain_dkim' );
+
+ $this->last_error = null;
+
+ try {
+ $response = $this->get_client()->verifyDomainIdentity( [ 'Domain' => $domain ] );
+ } catch ( \Exception $e ) {
+ $this->last_error = new WP_Error( 'wp_mail_smtp_ses_verify_domain_failed', $e->getMessage() );
+
+ return false;
+ }
+
+ if (
+ is_object( $response ) &&
+ ! empty( $response->get( 'VerificationToken' ) )
+ ) {
+ return $response->get( 'VerificationToken' );
+ }
+
+ return false;
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/AmazonSES/IdentitiesTable.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/AmazonSES/IdentitiesTable.php
new file mode 100644
index 00000000..d7860ac8
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/AmazonSES/IdentitiesTable.php
@@ -0,0 +1,326 @@
+connection = $connection;
+ } else {
+ $this->connection = wp_mail_smtp()->get_connections_manager()->get_primary_connection();
+ }
+
+ $this->options = PluginOptions::init();
+ $this->screen = get_current_screen();
+
+ // Set parent defaults.
+ parent::__construct(
+ [
+ 'singular' => 'ses-identity',
+ 'plural' => 'ses-identities',
+ 'ajax' => true,
+ 'screen' => $this->screen,
+ ]
+ );
+ }
+
+ /**
+ * Define the table columns.
+ *
+ * @since 2.4.0
+ *
+ * @return array Associate array of slug=>Name columns data.
+ */
+ public function get_columns() {
+
+ return [
+ 'sender' => esc_html__( 'Sender', 'wp-mail-smtp-pro' ),
+ 'type' => esc_html__( 'Type', 'wp-mail-smtp-pro' ),
+ 'status' => esc_html__( 'Status', 'wp-mail-smtp-pro' ),
+ 'action' => esc_html__( 'Action', 'wp-mail-smtp-pro' ),
+ ];
+ }
+
+ /**
+ * Display identity value (email address or domain name).
+ *
+ * @since 2.4.0
+ *
+ * @param \WPMailSMTP\Pro\Providers\AmazonSES\Identity $item Identity object.
+ *
+ * @return string
+ */
+ public function column_sender( $item ) {
+
+ return esc_html( $item->get_value() );
+ }
+
+ /**
+ * Display identity type.
+ *
+ * @since 2.4.0
+ *
+ * @param \WPMailSMTP\Pro\Providers\AmazonSES\Identity $item Identity object.
+ *
+ * @return string
+ */
+ public function column_type( $item ) {
+
+ if ( $item->get_type() === Identity::EMAIL_TYPE ) {
+ return esc_html__( 'Email', 'wp-mail-smtp-pro' );
+ }
+
+ return esc_html__( 'Domain', 'wp-mail-smtp-pro' );
+ }
+
+ /**
+ * Display identity status.
+ *
+ * @since 2.4.0
+ *
+ * @param \WPMailSMTP\Pro\Providers\AmazonSES\Identity $item Identity object.
+ *
+ * @return string
+ */
+ public function column_status( $item ) {
+
+ return esc_html( $item->get_status() );
+ }
+
+ /**
+ * Display identity action links.
+ *
+ * @since 2.4.0
+ *
+ * @param \WPMailSMTP\Pro\Providers\AmazonSES\Identity $item Identity object.
+ *
+ * @return string
+ */
+ public function column_action( $item ) {
+
+ return $item->get_action_links();
+ }
+
+ /**
+ * Get the data, prepare pagination, process bulk actions.
+ * Prepare columns for display.
+ *
+ * @since 2.4.0
+ */
+ public function prepare_items() {
+
+ // Define our column headers.
+ $this->_column_headers = [ $this->get_columns(), [], $this->get_sortable_columns() ];
+
+ // Get the data from AWS SES API.
+ $this->items = $this->get_items();
+ }
+
+ /**
+ * Get the last error captured by the underlying SES Auth instance.
+ *
+ * Available after {@see self::prepare_items()} has run. Returns null when no
+ * Auth call has been made or the most recent one succeeded.
+ *
+ * @since 4.9.0
+ *
+ * @return WP_Error|null
+ */
+ public function get_last_error() {
+
+ return $this->auth instanceof Auth ? $this->auth->get_last_error() : null;
+ }
+
+ /**
+ * Get all identity objects.
+ *
+ * @since 2.4.0
+ *
+ * @return array Array of Identity objects.
+ */
+ protected function get_items() {
+
+ $this->auth = new Auth( $this->connection );
+
+ if ( ! $this->auth->is_connection_ready() ) {
+ return [];
+ }
+
+ $domains = $this->auth->get_registered_domains();
+ $emails = $this->auth->get_registered_emails();
+ $data = [];
+
+ foreach ( $domains as $identity_value => $identity_data ) {
+ $verification_status = $identity_data['VerificationStatus'];
+
+ if ( is_array( $verification_status ) && count( $verification_status ) === 2 ) {
+ $verification_status = $verification_status[1];
+ }
+
+ $txt_token = empty( $identity_data['VerificationToken'] ) ? null : $identity_data['VerificationToken'];
+ $dkim_tokens = empty( $identity_data['DkimTokens'] ) ? null : $identity_data['DkimTokens'];
+
+ // Preferred DKIM verification, but we need to keep old one via TXT records if it was verified.
+ if (
+ $verification_status !== 'Success' &&
+ ! empty( $identity_data['DkimEnabled'] ) &&
+ ! empty( $identity_data['DkimVerificationStatus'] )
+ ) {
+ $verification_status = $identity_data['DkimVerificationStatus'];
+ }
+
+ $data[] = new Identity( $identity_value, Identity::DOMAIN_TYPE, $verification_status, $txt_token, $dkim_tokens );
+ }
+
+ foreach ( $emails as $identity_value => $identity_data ) {
+ $verification_status = $identity_data['VerificationStatus'];
+
+ if ( is_array( $verification_status ) && count( $verification_status ) === 2 ) {
+ $verification_status = $verification_status[1];
+ }
+
+ $data[] = new Identity( $identity_value, Identity::EMAIL_TYPE, $verification_status );
+ }
+
+ return $data;
+ }
+
+ /**
+ * Whether the table has items to display or not.
+ *
+ * @since 2.4.0
+ *
+ * @return bool
+ */
+ public function has_items() {
+
+ return count( $this->items ) > 0;
+ }
+
+ /**
+ * Message to be displayed when there are no items.
+ *
+ * @since 2.4.0
+ */
+ public function no_items() {
+
+ esc_html_e( 'No registered SES identities found.', 'wp-mail-smtp-pro' );
+ }
+
+ /**
+ * Generates the table navigation above or below the table.
+ * Removed bulk actions code from the parent method.
+ *
+ * @since 2.4.0
+ *
+ * @param string $which Which navigation: 'top' or 'bottom'.
+ */
+ protected function display_tablenav( $which ) {
+
+ ?>
+
+
', $debug_items );
+ }
+
+ /**
+ * Whether the mailer has all its settings correctly set up and saved.
+ *
+ * @since 1.5.0
+ *
+ * @return bool
+ */
+ public function is_mailer_complete() {
+
+ if ( ! $this->is_php_compatible() ) {
+ return false;
+ }
+
+ $auth = new Auth( $this->connection );
+
+ if (
+ $auth->is_clients_saved() &&
+ ! $auth->is_auth_required()
+ ) {
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/AmazonSES/Options.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/AmazonSES/Options.php
new file mode 100644
index 00000000..50b3582a
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/AmazonSES/Options.php
@@ -0,0 +1,549 @@
+ wp_mail_smtp()->assets_url . '/images/providers/aws.svg',
+ 'slug' => self::SLUG,
+ 'title' => esc_html__( 'Amazon SES', 'wp-mail-smtp-pro' ),
+ 'description' => wp_kses( __( 'Amazon SES is a transactional email provider that allows you to send email via its API. We recommend this mailer for existing users of Amazon Web Services because the setup steps are a little more complicated than other mailers.', 'wp-mail-smtp-pro' ), [ 'b' => [] ] ) . '
' .
+ // phpcs:disable
+ sprintf(
+ wp_kses( /* translators: %s - WPMailSMTP.com URL. */
+ __( 'To get started, read our Amazon SES documentation.', 'wp-mail-smtp-pro' ),
+ array(
+ 'a' => array(
+ 'href' => array(),
+ 'rel' => array(),
+ 'target' => array(),
+ ),
+ )
+ ),
+ esc_url( wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/docs/how-to-set-up-the-amazon-ses-mailer-in-wp-mail-smtp/', 'Amazon SES documentation' ) )
+ ),
+ // phpcs:enable
+ 'notices' => array(
+ 'educational' => wp_kses(
+ __( 'The Amazon SES mailer will be a good choice for technically advanced users who already have experience working with Amazon\'s web services.
If you aren\'t sure whether this mailer sounds like the right fit for your site, then we recommend considering one of our other mailer options.', 'wp-mail-smtp-pro' ),
+ [
+ 'br' => [],
+ ]
+ ),
+ ),
+ 'php' => '5.6',
+ 'supports' => [
+ 'from_email' => true,
+ 'from_name' => true,
+ 'return_path' => false,
+ 'from_email_force' => true,
+ 'from_name_force' => true,
+ ],
+ ),
+ $connection
+ );
+ }
+
+ /**
+ * Output the mailer provider options.
+ *
+ * @since 1.5.0
+ * @since 3.10.0 Added WPMS_AMAZONSES_DISPLAY_IDENTITIES constant check to control display of identity list.
+ */
+ public function display_options() {
+
+ // Do not display options if PHP version is not correct.
+ if ( ! $this->is_php_correct() ) {
+ $this->display_php_warning();
+
+ return;
+ }
+
+ // Do not display options if there is no SSL certificate on a site.
+ if ( ! is_ssl() ) {
+ $this->display_ssl_warning();
+
+ return;
+ }
+ ?>
+
+
+
+
+
+
+
+
'; + $notice .= '
+ ' . esc_html__( 'Name:', 'wp-mail-smtp-pro' ) . ' + + +
'; + $notice .= '
+ ' . esc_html__( 'Value:', 'wp-mail-smtp-pro' ) . ' + + +
'; + $notice .= '
' .
+ sprintf(
+ wp_kses( /* translators: %s - URL to Amazon SES documentation. */
+ __( 'For information on how to add TXT DNS records, please
refer to the Amazon SES documentation.', 'wp-mail-smtp-pro' ),
+ [
+ 'br' => true,
+ 'a' => [
+ 'href' => true,
+ 'target' => true,
+ 'rel' => true,
+ ],
+ ]
+ ),
+ 'https://docs.aws.amazon.com/ses/latest/DeveloperGuide/dns-txt-records.html'
+ ) .
+ '
';
+
+ return $notice;
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Gmail/Api/Client.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Gmail/Api/Client.php
new file mode 100644
index 00000000..eba05ee0
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Gmail/Api/Client.php
@@ -0,0 +1,309 @@
+credentials = $credentials;
+ $this->one_time_token = $one_time_token;
+ $this->site_id = $site_id;
+ $this->site_url = $site_url ? $site_url : site_url();
+ }
+
+ /**
+ * Get API base URL.
+ *
+ * @since 3.11.0
+ *
+ * @return string
+ */
+ public static function get_api_base_url() {
+
+ return defined( 'WPMS_GMAIL_ONE_CLICK_SETUP_API_URL' ) ? WPMS_GMAIL_ONE_CLICK_SETUP_API_URL : self::API_BASE_URL;
+ }
+
+ /**
+ * Get authorization URL.
+ *
+ * @since 3.11.0
+ *
+ * @param array $args List of arguments.
+ *
+ * @return string
+ */
+ public function get_auth_url( $args ) {
+
+ $url = self::get_api_base_url() . 'auth/new/pro/';
+
+ return add_query_arg( $this->prepare_auth_args( $args ), $url );
+ }
+
+ /**
+ * Get re-authorization URL.
+ *
+ * @since 3.11.0
+ *
+ * @param array $args List of arguments.
+ *
+ * @return string
+ */
+ public function get_reauth_url( $args ) {
+
+ $args = wp_parse_args(
+ $args,
+ [
+ 'key' => ! empty( $this->credentials['key'] ) ? $this->credentials['key'] : '',
+ 'token' => ! empty( $this->credentials['token'] ) ? $this->credentials['token'] : '',
+ ]
+ );
+
+ $url = self::get_api_base_url() . 'auth/reauth/pro/';
+
+ return add_query_arg( $this->prepare_auth_args( $args ), $url );
+ }
+
+ /**
+ * Prepare arguments for authorization and reauthorization URLs.
+ *
+ * @since 3.11.0
+ *
+ * @param array $args Passed arguments for authorization.
+ *
+ * @return array
+ */
+ private function prepare_auth_args( $args ) {
+
+ $args = wp_parse_args(
+ $args,
+ [
+ 'tt' => $this->one_time_token->get(),
+ 'siteid' => $this->site_id->get(),
+ 'version' => WPMS_PLUGIN_VER,
+ 'ajaxurl' => admin_url( 'admin-ajax.php' ),
+ 'network' => 'site',
+ 'siteurl' => $this->site_url,
+ 'testurl' => self::get_api_base_url() . 'test/',
+ 'license' => wp_mail_smtp()->get_license_key(),
+ 'amc' => defined( 'WPMS_GMAIL_ONE_CLICK_SETUP_AMC' ) ? WPMS_GMAIL_ONE_CLICK_SETUP_AMC : '',
+ ]
+ );
+
+ if ( ! empty( $args['return'] ) ) {
+ $args['return'] = rawurlencode( $args['return'] );
+ }
+
+ return $args;
+ }
+
+ /**
+ * Determine if the connection is valid.
+ *
+ * @since 3.11.0
+ *
+ * @return true|WP_Error
+ */
+ public function verify_auth() {
+
+ $args = [
+ 'tt' => $this->one_time_token->get(),
+ ];
+
+ $response = $this->request( 'auth/verify/pro/', $args, 'POST' );
+
+ if ( ! $response->has_errors() ) {
+ $this->one_time_token->refresh();
+ }
+
+ return $response->has_errors() ? $response->get_errors() : true;
+ }
+
+ /**
+ * Remove connection on the API side.
+ *
+ * @since 3.11.0
+ *
+ * @return true|WP_Error
+ */
+ public function remove_connection() {
+
+ $response = $this->request(
+ 'auth/delete/pro/',
+ [
+ 'tt' => $this->one_time_token->get(),
+ ],
+ 'POST'
+ );
+
+ $this->one_time_token->refresh();
+
+ return $response->has_errors() ? $response->get_errors() : true;
+ }
+
+ /**
+ * Send an email.
+ *
+ * @since 3.11.0
+ *
+ * @param string $message Base64 encoded message.
+ * @param bool $allow_queue Whether to allow to queue message.
+ *
+ * @return Response
+ */
+ public function send_email( $message, $allow_queue = true ) {
+
+ return $this->request(
+ 'endpoints/send-email/',
+ [
+ 'message' => $message,
+ 'allow_queue' => $allow_queue,
+ ],
+ 'POST'
+ );
+ }
+
+ /**
+ * Make a request.
+ *
+ * @since 3.11.0
+ *
+ * @param string $route Endpoint name.
+ * @param array $args List of arguments.
+ * @param string $method Request method.
+ *
+ * @return Response
+ */
+ private function request( $route, $args, $method = 'GET' ) {
+
+ $body = wp_parse_args(
+ $args,
+ [
+ 'key' => ! empty( $this->credentials['key'] ) ? $this->credentials['key'] : '',
+ 'token' => ! empty( $this->credentials['token'] ) ? $this->credentials['token'] : '',
+ 'siteurl' => $this->site_url,
+ 'license' => wp_mail_smtp()->get_license_key(),
+ 'plugin' => 'wp_mail_smtp',
+ 'version' => WPMS_PLUGIN_VER,
+ 'timezone' => gmdate( 'e' ),
+ 'network' => 'site',
+ 'ip' => Geo::get_ip(),
+ ]
+ );
+
+ $content_type = 'application/x-www-form-urlencoded';
+
+ // The `application/json` content type is more preferable,
+ // but use it only if the body was successfully encoded.
+ $body_json = wp_json_encode( $body );
+
+ if ( $body_json !== false ) {
+ $content_type = 'application/json';
+ $body = $body_json;
+ }
+
+ /**
+ * Allow modifying request arguments.
+ *
+ * @since 3.11.0
+ *
+ * @param array $args List of args.
+ */
+ $request_args = apply_filters(
+ 'wp_mail_smtp_pro_providers_gmail_api_client_request_send_args',
+ [
+ 'headers' => [
+ 'Content-Type' => $content_type,
+ 'Accept' => 'application/json',
+ 'Cache-Control' => 'no-store, no-cache, must-revalidate, max-age=0, post-check=0, pre-check=0',
+ 'Pragma' => 'no-cache',
+ 'Expires' => 0,
+ ],
+ 'method' => $method,
+ 'body' => $body,
+ 'timeout' => 3000,
+ 'user-agent' => Helpers::get_default_user_agent(),
+ ]
+ );
+
+ $response = wp_remote_request( self::get_api_base_url() . $route, $request_args );
+
+ return new Response( $response );
+ }
+
+ /**
+ * Verify the one time token value.
+ *
+ * @since 3.11.0
+ *
+ * @param string $passed_one_time_token A passed one time token value.
+ *
+ * @return bool
+ */
+ public function is_valid_one_time_token( $passed_one_time_token ) {
+
+ return $this->one_time_token->validate( $passed_one_time_token );
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Gmail/Api/OneTimeToken.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Gmail/Api/OneTimeToken.php
new file mode 100644
index 00000000..0e442b83
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Gmail/Api/OneTimeToken.php
@@ -0,0 +1,75 @@
+refresh();
+ }
+
+ /**
+ * Refresh the one time token value.
+ *
+ * @since 3.11.0
+ *
+ * @return string
+ */
+ public function refresh() {
+
+ $auth_salt = defined( 'AUTH_SALT' ) ? AUTH_SALT : '';
+
+ $one_time_token = hash(
+ 'sha512',
+ wp_generate_password(
+ 128,
+ true,
+ true
+ ) . $auth_salt . uniqid( '', true )
+ );
+
+ update_option( self::ONE_TIME_TOKEN_OPTION, $one_time_token );
+
+ return $one_time_token;
+ }
+
+ /**
+ * Verify the one time token value.
+ *
+ * @since 3.11.0
+ *
+ * @param string $passed A passed token value.
+ *
+ * @return bool
+ */
+ public function validate( $passed ) {
+
+ return hash_equals( $this->get(), $passed );
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Gmail/Api/Response.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Gmail/Api/Response.php
new file mode 100644
index 00000000..335151b9
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Gmail/Api/Response.php
@@ -0,0 +1,117 @@
+response = $response;
+ }
+
+ /**
+ * Get response body.
+ *
+ * @since 3.11.0
+ *
+ * @return string|array
+ */
+ public function get_body() {
+
+ $body = wp_remote_retrieve_body( $this->response );
+
+ if ( ! empty( $body ) && is_string( $body ) ) {
+ $body = json_decode( $body, true );
+ }
+
+ return $body;
+ }
+
+ /**
+ * Get response status code.
+ *
+ * @since 3.11.0
+ *
+ * @return int
+ */
+ public function get_status_code() {
+
+ return wp_remote_retrieve_response_code( $this->response );
+ }
+
+ /**
+ * Get response errors.
+ *
+ * @since 3.11.0
+ *
+ * @return string|WP_Error
+ */
+ public function get_errors() {
+
+ if ( ! $this->has_errors() ) {
+ return '';
+ }
+
+ if ( is_wp_error( $this->response ) ) {
+ return $this->response;
+ }
+
+ $body = $this->get_body();
+
+ if ( ! empty( $body['message'] ) ) {
+ return new WP_Error( $this->get_status_code(), $body['message'] );
+ }
+
+ return new WP_Error( $this->get_status_code(), WP::wp_remote_get_response_error_message( $this->response ) );
+ }
+
+ /**
+ * Whether response has errors.
+ *
+ * @since 3.11.0
+ *
+ * @return bool
+ */
+ public function has_errors() {
+
+ return $this->get_status_code() !== 200;
+ }
+
+ /**
+ * Get response header.
+ *
+ * @since 3.11.0
+ *
+ * @param string $header Header name.
+ *
+ * @return string
+ */
+ public function get_header( $header ) {
+
+ return wp_remote_retrieve_header( $this->response, $header );
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Gmail/Api/SiteId.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Gmail/Api/SiteId.php
new file mode 100644
index 00000000..d295a826
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Gmail/Api/SiteId.php
@@ -0,0 +1,69 @@
+create();
+
+ update_option( self::SITE_ID_OPTION, $site_id );
+
+ return $site_id;
+ }
+
+ /**
+ * Create a unique site ID.
+ *
+ * @since 3.11.0
+ *
+ * @return string
+ */
+ private function create() {
+
+ $site_id = sprintf(
+ '%s%s%s',
+ defined( 'AUTH_KEY' ) ? AUTH_KEY : '',
+ defined( 'SECURE_AUTH_KEY' ) ? SECURE_AUTH_KEY : '',
+ defined( 'LOGGED_IN_KEY' ) ? LOGGED_IN_KEY : ''
+ );
+
+ /**
+ * Strips any characters except those specifically allowed.
+ *
+ * `^` negates the matching (matches any character that is NOT listed in the set).
+ * `a-z` matches any lowercase characters between a-z.
+ * `A-Z` matches any uppercase characters between A-Z.
+ * `0-9` matches any digits.
+ */
+ $site_id = preg_replace( '/[^a-zA-Z0-9]/', '', $site_id );
+
+ return strlen( $site_id ) > 30 ? substr( $site_id, 0, 30 ) : $site_id;
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Gmail/Auth.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Gmail/Auth.php
new file mode 100644
index 00000000..406d4c98
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Gmail/Auth.php
@@ -0,0 +1,353 @@
+mailer_slug !== Options::SLUG ) {
+ return;
+ }
+
+ $this->options = $this->connection_options->get_group( $this->mailer_slug );
+ }
+
+ /**
+ * Get the url, that users will be redirected back to finish the OAuth process.
+ *
+ * @since 3.11.0
+ *
+ * @param ConnectionInterface $connection The Connection object.
+ *
+ * @return string
+ */
+ public static function get_plugin_auth_url( $connection = null ) {
+
+ if ( is_null( $connection ) ) {
+ $connection = wp_mail_smtp()->get_connections_manager()->get_primary_connection();
+ }
+
+ /**
+ * Filters the plugin auth redirect url.
+ *
+ * @since 3.11.0
+ *
+ * @param string $auth_url The plugin auth redirect url.
+ */
+ $auth_url = apply_filters(
+ 'wp_mail_smtp_pro_providers_gmail_auth_get_plugin_auth_url',
+ add_query_arg(
+ [
+ 'page' => Area::SLUG,
+ 'tab' => 'auth',
+ ],
+ admin_url( 'options-general.php' )
+ )
+ );
+
+ $state = [
+ wp_create_nonce( 'wp_mail_smtp_provider_client_state' ),
+ $connection->get_id(),
+ ];
+
+ return add_query_arg(
+ [
+ 'state' => implode( '-', $state ),
+ 'one_click_setup' => 1,
+ ],
+ $auth_url
+ );
+ }
+
+ /**
+ * Init and get the Client object.
+ *
+ * @since 3.11.0
+ *
+ * @return Client
+ */
+ public function get_client() {
+
+ // Doesn't load client twice + gives ability to overwrite.
+ if ( ! empty( $this->client ) ) {
+ return $this->client;
+ }
+
+ $credentials = $this->options['one_click_setup_credentials'] ?? [];
+ $site_url = WP::get_site_url();
+
+ if ( $this->connection->get_id() !== 'primary' ) {
+ $site_url = add_query_arg( 'connection_id', $this->connection->get_id(), $site_url );
+ }
+
+ $this->client = new Client( $credentials, new OneTimeToken(), new SiteId(), $site_url );
+
+ return $this->client;
+ }
+
+ /**
+ * Process authorization.
+ * Redirect user back to settings with an error message, if failed.
+ *
+ * @since 3.11.0
+ */
+ public function process() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded, Generic.Metrics.CyclomaticComplexity.TooHigh
+
+ $redirect_url = ( new ConnectionSettings( $this->connection ) )->get_admin_page_url();
+ $is_setup_wizard_auth = ! empty( $this->options['is_setup_wizard_auth'] );
+
+ if ( $is_setup_wizard_auth ) {
+ $this->update_is_setup_wizard_auth( false );
+
+ $redirect_url = SetupWizard::get_site_url() . '#/step/configure_mailer/gmail';
+ }
+
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+ if ( ! ( isset( $_GET['tab'] ) && $_GET['tab'] === 'auth' ) ) {
+ wp_safe_redirect( $redirect_url );
+ exit;
+ }
+
+ // phpcs:disable WordPress.Security.NonceVerification.Recommended
+ $key = ! empty( $_GET['key'] ) ? sanitize_text_field( wp_unslash( $_GET['key'] ) ) : '';
+ $token = ! empty( $_GET['token'] ) ? sanitize_text_field( wp_unslash( $_GET['token'] ) ) : '';
+ $user_email = ! empty( $_GET['user_email'] ) ? sanitize_email( wp_unslash( $_GET['user_email'] ) ) : '';
+ // phpcs:enable WordPress.Security.NonceVerification.Recommended
+
+ if ( empty( $key ) || empty( $token ) ) {
+ $event_id = DebugEvents::add( esc_html__( 'Google authorization error. Missing required arguments.', 'wp-mail-smtp-pro' ) );
+
+ wp_safe_redirect(
+ add_query_arg(
+ [
+ 'error' => 'google_one_click_setup_unsuccessful_oauth',
+ 'debug_event_id' => $event_id,
+ ],
+ $redirect_url
+ )
+ );
+ exit;
+ }
+
+ $data = [
+ 'one_click_setup_status' => 'active',
+ 'one_click_setup_credentials' => [
+ 'key' => $key,
+ 'token' => $token,
+ ],
+ 'one_click_setup_user_details' => [
+ 'email' => $user_email,
+ ],
+ ];
+
+ $site_url = WP::get_site_url();
+
+ if ( $this->connection->get_id() !== 'primary' ) {
+ $site_url = add_query_arg( 'connection_id', $this->connection->get_id(), $site_url );
+ }
+
+ // Create a client with a new credentials.
+ $this->client = new Client( $data['one_click_setup_credentials'], new OneTimeToken(), new SiteId(), $site_url );
+
+ $is_valid = $this->get_client()->verify_auth();
+
+ if ( is_wp_error( $is_valid ) ) {
+ $error = $is_valid->get_error_message();
+
+ $event_id = DebugEvents::add(
+ sprintf( /* Translators: %1$s the error code passed from Google. */
+ esc_html__( 'Google authorization error. %1$s', 'wp-mail-smtp-pro' ),
+ esc_html( $error )
+ )
+ );
+
+ wp_safe_redirect(
+ add_query_arg(
+ [
+ 'error' => 'google_one_click_setup_unsuccessful_oauth',
+ 'debug_event_id' => $event_id,
+ ],
+ $redirect_url
+ )
+ );
+
+ return;
+ }
+
+ $all = $this->connection_options->get_all();
+
+ $all[ $this->mailer_slug ] = PluginOptions::array_merge_recursive( $all[ $this->mailer_slug ], $data );
+ $all['mail']['from_email'] = $user_email;
+
+ $this->connection_options->set( $all, false, true );
+
+ wp_safe_redirect(
+ add_query_arg(
+ 'success',
+ 'google_one_click_setup_site_linked',
+ $redirect_url
+ )
+ );
+ exit;
+ }
+
+ /**
+ * Get the auth URL used to process authorization.
+ *
+ * @since 3.11.0
+ *
+ * @return string
+ */
+ public function get_auth_url() {
+
+ $settings_url = rawurlencode( ( new ConnectionSettings( $this->connection ) )->get_admin_page_url() );
+
+ return $this->get_client()->get_auth_url(
+ [
+ 'return' => self::get_plugin_auth_url( $this->connection ),
+ 'plugin_settings_url' => $settings_url,
+ ]
+ );
+ }
+
+ /**
+ * Get the reauth URL used to process reauthorization.
+ *
+ * @since 3.11.0
+ *
+ * @return string
+ */
+ public function get_reauth_url() {
+
+ $settings_url = rawurlencode( ( new ConnectionSettings( $this->connection ) )->get_admin_page_url() );
+
+ return $this->get_client()->get_reauth_url(
+ [
+ 'return' => self::get_plugin_auth_url( $this->connection ),
+ 'plugin_settings_url' => $settings_url,
+ ]
+ );
+ }
+
+ /**
+ * Get user information (like email etc) that is associated with the current OAuth connection.
+ *
+ * @since 3.11.0
+ *
+ * @return array
+ */
+ public function get_user_info() {
+
+ return $this->connection_options->get( $this->mailer_slug, 'one_click_setup_user_details' );
+ }
+
+ /**
+ * Whether client credentials are saved.
+ *
+ * We don't have any settings for One-Click Setup, so it's always `true`.
+ *
+ * @since 3.11.0
+ *
+ * @return true
+ */
+ public function is_clients_saved() {
+
+ return true;
+ }
+
+ /**
+ * Whether auth is required.
+ *
+ * @since 3.11.0
+ *
+ * @return bool
+ */
+ public function is_auth_required() {
+
+ $credentials = $this->options['one_click_setup_credentials'] ?? [];
+
+ return empty( $credentials['key'] ) || empty( $credentials['token'] );
+ }
+
+ /**
+ * Whether reauthorization is required.
+ *
+ * @since 3.11.0
+ *
+ * @return bool
+ */
+ public function is_reauth_required() {
+
+ if ( $this->is_auth_required() ) {
+ return false;
+ }
+
+ $transient_key = 'wp_mail_smtp_gmail_one_click_setup_auth_verified_' . $this->connection->get_id();
+
+ if ( empty( get_transient( $transient_key ) ) ) {
+ $is_active = $this->get_client()->verify_auth();
+
+ if ( is_wp_error( $is_active ) && $is_active->get_error_code() === 401 ) {
+ $this->set_auth_status( 'reauth' );
+ }
+
+ set_transient( $transient_key, true, HOUR_IN_SECONDS );
+ }
+
+ if (
+ isset( $this->options['one_click_setup_status'] ) &&
+ $this->options['one_click_setup_status'] === 'reauth'
+ ) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Set auth status.
+ *
+ * @since 3.11.0
+ *
+ * @param string $status Status name (active or reauth).
+ */
+ public function set_auth_status( $status ) {
+
+ $all = $this->connection_options->get_all();
+
+ // To save in DB.
+ $all[ $this->mailer_slug ]['one_click_setup_status'] = $status;
+
+ // To save in currently retrieved options array.
+ $this->options['one_click_setup_status'] = $status;
+
+ $this->connection_options->set( $all, false, true );
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Gmail/Mailer.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Gmail/Mailer.php
new file mode 100644
index 00000000..9f0bd9f8
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Gmail/Mailer.php
@@ -0,0 +1,259 @@
+process_phpmailer( $phpmailer );
+ }
+
+ /**
+ * Re-use the MailCatcher class methods and properties.
+ *
+ * @since 3.11.0
+ *
+ * @param MailCatcherInterface $phpmailer The MailCatcher object.
+ */
+ public function process_phpmailer( $phpmailer ) {
+
+ // Make sure that we have access to PHPMailer class methods.
+ if ( ! wp_mail_smtp()->is_valid_phpmailer( $phpmailer ) ) {
+ return;
+ }
+
+ $this->phpmailer = $phpmailer;
+ }
+
+ /**
+ * Send email.
+ *
+ * @since 3.11.0
+ */
+ public function send() {
+
+ $auth = new Auth( $this->connection );
+
+ try {
+ // Check whether the mailer is ready to send emails.
+ $this->check_requirements();
+
+ // Prepare a message for sending if any changes happened above.
+ $this->phpmailer->preSend();
+
+ // Get the raw MIME email using MailCatcher data. We need to make base64URL-safe string.
+ $base64 = str_replace(
+ [ '+', '/', '=' ],
+ [ '-', '_', '' ],
+ // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
+ base64_encode( $this->phpmailer->getSentMIMEMessage() )
+ );
+
+ $allow_queue = ! $this->phpmailer->is_test_email();
+
+ $response = $auth->get_client()->send_email( $base64, $allow_queue );
+
+ DebugEvents::add_debug(
+ esc_html__( 'An email request was sent to the Gmail API.', 'wp-mail-smtp-pro' )
+ );
+
+ $this->process_response( $response );
+ } catch ( Exception $e ) {
+ $this->error_message = Helpers::format_error_message( $e->getMessage() );
+
+ if ( $e->getCode() ) {
+ $this->error_code = $e->getCode();
+ }
+ }
+
+ if ( $this->response instanceof Response ) {
+ // Check if we need to reauthenticate.
+ $auth_status = $this->response->get_header( 'X-Auth-Status' );
+
+ if (
+ $auth_status === 'reauth' &&
+ $this->connection_options->get( $this->mailer, 'one_click_setup_status' ) !== 'reauth'
+ ) {
+ $auth->set_auth_status( 'reauth' );
+ }
+
+ // Check if license is not valid and set transient to prevent further requests to API on each email send.
+ $body = $this->response->get_body();
+
+ if ( isset( $body['error_code'] ) && $body['error_code'] === 'invalid_license' ) {
+ set_transient(
+ 'wp_mail_smtp_gmail_one_click_setup_send_email_invalid_license_lock',
+ true,
+ 24 * HOUR_IN_SECONDS
+ );
+ }
+ }
+ }
+
+ /**
+ * Check whether the mailer is ready to send emails.
+ *
+ * @since 3.11.0
+ *
+ * @throws Exception If the mailer is not ready.
+ */
+ private function check_requirements() {
+
+ $auth = new Auth( $this->connection );
+
+ if ( $auth->is_auth_required() ) {
+ throw new Exception( esc_html__( 'One-Click Setup for Google Mailer requires authorization. Perform authorization before sending emails.', 'wp-mail-smtp-pro' ) );
+ } elseif (
+ // If license key was not entered or was removed.
+ empty( wp_mail_smtp()->get_license_key() ) ||
+
+ /*
+ * If license is not valid. We need to throw error only if license is not valid in the plugin
+ * and on the API side (transient represents invalid license status from API side).
+ * The transient is set based on API response. This technique allows us to prevent requests to API on each
+ * email send when license is invalid and in the same time send one request with invalid license to the API.
+ * This one request required to perform various actions (user notification about expired license,
+ * switch account to reauth status etc.) on the API side.
+ */
+ (
+ ! wp_mail_smtp()->get_pro()->get_license()->is_valid() &&
+ ! empty( get_transient( 'wp_mail_smtp_gmail_one_click_setup_send_email_invalid_license_lock' ) )
+ )
+ ) {
+ throw new Exception( esc_html__( 'One-Click Setup for Google Mailer requires an active license. Verify your license before sending emails.', 'wp-mail-smtp-pro' ) );
+ }
+ }
+
+ /**
+ * Process API response.
+ *
+ * @since 3.11.0
+ *
+ * @param Response $response Api response object.
+ */
+ protected function process_response( $response ) {
+
+ $this->response = $response;
+
+ if ( $response->has_errors() ) {
+ $this->error_message = Helpers::format_error_message( $this->response->get_errors()->get_error_message() );
+ $this->error_code = $this->response->get_errors()->get_error_code();
+ } else {
+ $body = $response->get_body();
+
+ if ( empty( $body['message_id'] ) && empty( $body['queued'] ) ) {
+ $this->error_message = esc_html__( 'The email message ID is missing.', 'wp-mail-smtp-pro' );
+ }
+ }
+
+ if ( ! empty( $body['message_id'] ) ) {
+ $this->phpmailer->addCustomHeader( 'X-Msg-ID', $body['message_id'] );
+ }
+
+ if ( ! empty( $body['queued'] ) ) {
+ $this->phpmailer->addCustomHeader( 'X-Msg-Queued', true );
+ }
+ }
+
+ /**
+ * Check whether the email was sent.
+ *
+ * @since 3.11.0
+ *
+ * @return bool
+ */
+ public function is_email_sent() {
+
+ $is_sent = false;
+
+ if (
+ $this->response instanceof Response &&
+ ! $this->response->has_errors()
+ ) {
+ $is_sent = true;
+ }
+
+ /** This filter is documented in src/Providers/MailerAbstract.php. */
+ return apply_filters( 'wp_mail_smtp_providers_mailer_is_email_sent', $is_sent, $this->mailer ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
+ }
+
+ /**
+ * Get the HTTP response code.
+ *
+ * @since 4.8.0
+ *
+ * @return int
+ */
+ public function get_response_code() {
+
+ if ( $this->response instanceof Response ) {
+ return (int) $this->response->get_status_code();
+ }
+
+ return parent::get_response_code();
+ }
+
+ /**
+ * Get mailer debug information.
+ *
+ * @since 3.11.0
+ *
+ * @return string
+ */
+ public function get_debug_info() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded
+
+ $debug = [];
+
+ $credentials = $this->connection_options->get( 'gmail', 'one_click_setup_credentials' );
+
+ $debug[] = 'Key/Token: ' . ( ! empty( $credentials['key'] ) && ! empty( $credentials['token'] ) ? 'Yes' : 'No' );
+
+ return implode( '
', $debug );
+ }
+
+ /**
+ * Whether the mailer has all its settings correctly set up and saved.
+ *
+ * @since 3.11.0
+ *
+ * @return bool
+ */
+ public function is_mailer_complete() {
+
+ if ( ! $this->is_php_compatible() ) {
+ return false;
+ }
+
+ $auth = new Auth( $this->connection );
+
+ if ( ! $auth->is_auth_required() ) {
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Gmail/Options.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Gmail/Options.php
new file mode 100644
index 00000000..25d842b6
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Gmail/Options.php
@@ -0,0 +1,340 @@
+ wp_mail_smtp()->assets_url . '/images/providers/google.svg',
+ 'slug' => self::SLUG,
+ 'title' => esc_html__( 'Google / Gmail', 'wp-mail-smtp-pro' ),
+ 'description' => sprintf(
+ wp_kses( /* translators: %s - URL to our Gmail doc. */
+ __( 'Our Gmail mailer works with any Gmail or Google Workspace account via the Google API. You can send WordPress emails from your main email address or a Gmail alias, and it\'s more secure than connecting to Gmail using SMTP credentials. We now have a One-Click Setup, which simply asks you to authorize your Google account to use our app and takes care of everything for you. Alternatively, you can connect manually, which involves several steps that are more technical than other mailer options, so we created a detailed guide to walk you through the process.
To get started, read our Gmail documentation.', 'wp-mail-smtp-pro' ),
+ [
+ 'br' => [],
+ 'a' => [
+ 'href' => [],
+ 'rel' => [],
+ 'target' => [],
+ ],
+ ]
+ ),
+ esc_url( wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/docs/how-to-set-up-the-gmail-mailer-in-wp-mail-smtp/', 'Gmail documentation' ) )
+ ),
+ 'notices' => [
+ 'educational' => wp_kses(
+ __( 'The Gmail mailer works well for sites that send low numbers of emails. However, Gmail\'s API has rate limitations and a number of additional restrictions that can lead to challenges during setup.
If you expect to send a high volume of emails, or if you find that your web host is not compatible with the Gmail API restrictions, then we recommend considering a different mailer option.', 'wp-mail-smtp-pro' ),
+ [
+ 'br' => [],
+ ]
+ ),
+ ],
+ 'supports' => [
+ 'from_email' => true,
+ 'from_name' => true,
+ 'return_path' => false,
+ 'from_email_force' => true,
+ 'from_name_force' => true,
+ ],
+ ],
+ $connection
+ );
+ }
+
+ /**
+ * Output the mailer provider options.
+ *
+ * @since 3.11.0
+ */
+ public function display_options() {
+
+ // Do not display options if PHP version is not correct.
+ if ( ! $this->is_php_correct() ) {
+ $this->display_php_warning();
+
+ return;
+ }
+
+ $lite_options = new LiteOptions( $this->connection );
+ $one_click_setup_enabled = $this->connection_options->get( 'gmail', 'one_click_setup_enabled' );
+ ?>
+
+
+ +
++ +
+ +
+ get_pro()->get_license(); + $one_click_setup_enabled = $this->connection_options->get( 'gmail', 'one_click_setup_enabled' ); + ?> + is_expired() ) : ?> + +
+ Renew your license and reconnect your authorization.', 'wp-mail-smtp-pro' ), + [ + 'a' => [ + 'href' => [], + 'target' => [], + 'rel' => [], + ], + ] + ), + esc_url( + $license->get_renewal_link( + [ + 'medium' => 'gmail-one-click-setting-notice', + 'content' => 'Renew your license', + ] + ) + ) + ); + ?> +
+ +
+ +
+ + is_valid() ) : ?> + +
+ +
+ +
+ +
+
+
+ process_provider_remove();
+
+ $auth = new Auth( $this->connection );
+ $license = wp_mail_smtp()->get_pro()->get_license();
+ ?>
+ connection->get_mailer_slug() === self::SLUG ) : ?>
+ is_auth_required() ) : ?>
+
+
' . esc_html( $user['email'] ) . ''
+ );
+ }
+ ?>
+ +
+ +
+ +
' . esc_html( $user['email'] ) . ''
+ );
+ }
+ ?>
+ +
+ Follow these instructions and then select the From Email at the top of this page.', 'wp-mail-smtp-pro' ), + [ + 'a' => [ + 'href' => [], + 'rel' => [], + 'target' => [], + ], + ] + ), + esc_url( wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/gmail-send-from-alias-wp-mail-smtp/', 'Gmail aliases description - Follow these instructions' ) ) + ); + ?> +
+
+ +
+
+ +
+ + +
+ +
+ + connection ) )->get_admin_page_url(), 'gmail_one_click_setup_remove', 'gmail_one_click_setup_remove_nonce' ) . '#wp-mail-smtp-setting-row-gmail-one-click-setup-authorize'; + } + + /** + * Remove Provider OAuth connection. + * + * @since 3.11.0 + */ + public function process_provider_remove() { + + if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_global_options() ) ) { + return; + } + + if ( + ! isset( $_GET['gmail_one_click_setup_remove_nonce'] ) || + ! wp_verify_nonce( sanitize_key( $_GET['gmail_one_click_setup_remove_nonce'] ), 'gmail_one_click_setup_remove' ) + ) { + return; + } + + if ( $this->connection->get_mailer_slug() !== $this->get_slug() ) { + return; + } + + $auth = new Auth( $this->connection ); + + $auth->get_client()->remove_connection(); + + $old_opt = $this->connection_options->get_all_raw(); + + unset( $old_opt[ $this->get_slug() ]['one_click_setup_credentials'] ); + unset( $old_opt[ $this->get_slug() ]['one_click_setup_user_details'] ); + unset( $old_opt[ $this->get_slug() ]['one_click_setup_status'] ); + + $this->connection_options->set( $old_opt ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Gmail/Provider.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Gmail/Provider.php new file mode 100644 index 00000000..d092327c --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Gmail/Provider.php @@ -0,0 +1,361 @@ +get_capability_manage_global_options() ) ) { + return $connection; + } + + if ( + $connection instanceof ConnectionInterface && + $connection->get_mailer_slug() === 'gmail' + ) { + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $one_click_setup_enabled = isset( $_GET['one_click_setup'] ); + + $connection->get_options()->set( [ 'gmail' => [ 'one_click_setup_enabled' => $one_click_setup_enabled ] ], false, false ); + } + + return $connection; + }, + PHP_INT_MAX + ); + + /* + * AJAX handler to check if the plugin is installed. + * Currently used only for Gmail mailer One-Click Setup and that's why located here. + */ + add_action( + 'wp_ajax_nopriv_wp_mail_smtp_is_installed', + function () { + wp_send_json_success(); + } + ); + } + + /** + * Process settings data before save on settings page. + * + * Mainly used to convert checkbox value to boolean. + * + * @since 3.11.0 + * + * @param array $data Connection data. + * @param array $old_data Old connection data. + * + * @return array + */ + public function process_settings_data( $data, $old_data ) { + + $data['gmail']['one_click_setup_enabled'] = isset( $data['gmail']['one_click_setup_enabled'] ); + + return $data; + } + + /** + * Set options defaults before they were saved. + * + * @since 3.11.0 + * + * @param mixed $value Option value. + * @param string $group Group key. + * @param string $key Option key. + * + * @return mixed + */ + public function options_defaults( $value, $group, $key ) { + + if ( $group === 'gmail' && $key === 'one_click_setup_enabled' ) { + $value = false; + } + + return $value; + } + + /** + * Sanitize options before save. + * + * @since 3.11.0 + * + * @param array $options Currently processed options passed to a filter hook. + * + * @return array + */ + public function sanitize_options( $options ) { + + if ( ! isset( $options['gmail']['one_click_setup_enabled'] ) ) { + // Disabled by default. + $options['gmail']['one_click_setup_enabled'] = false; + + return $options; + } + + $options['gmail']['one_click_setup_enabled'] = (bool) $options['gmail']['one_click_setup_enabled']; + + return $options; + } + + /** + * Swap to One-Click Setup if it's enabled. + * + * @since 3.11.0 + * + * @param mixed $entity Entity object. + * @param string $provider Provider name. + * @param string $request Entity name. + * @param array $args Entity arguments. + * + * @return mixed + */ + public function swap_to_one_click_setup( $entity, $provider, $request, $args ) { + + if ( $provider === 'gmail' ) { + $connection = null; + $gmail_provider_path = 'WPMailSMTP\Pro\Providers\Gmail\\'; + + if ( $request === 'Mailer' && isset( $args[1] ) ) { + $connection = $args[1]; + } elseif ( ( $request === 'Auth' || $request === 'Options' ) && isset( $args[0] ) ) { + $connection = $args[0]; + } + + if ( is_null( $connection ) ) { + $connection = wp_mail_smtp()->get_connections_manager()->get_primary_connection(); + } + + if ( + $connection instanceof ConnectionInterface && + class_exists( $gmail_provider_path . $request ) && + ( + $connection->get_options()->get( 'gmail', 'one_click_setup_enabled' ) === true || + $request === 'Options' + ) + ) { + $class = $gmail_provider_path . $request; + $entity = new $class( ...$args ); + } + } + + return $entity; + } + + /** + * Display notices. + * + * @since 3.11.0 + */ + public function display_notices() { + + $options = Options::init(); + + if ( + $options->get( 'mail', 'mailer' ) === 'gmail' && + $options->get( 'gmail', 'one_click_setup_enabled' ) === true + ) { + $gmail_auth = new Auth(); + $license = wp_mail_smtp()->get_pro()->get_license(); + + if ( ! $gmail_auth->is_auth_required() && $license->is_expired() ) { // Already authorized and license expired. + ?> +
+
+ Renew your license and reconnect your authorization.', 'wp-mail-smtp-pro' ),
+ [
+ 'a' => [
+ 'href' => [],
+ 'target' => [],
+ 'rel' => [],
+ ],
+ ]
+ ),
+ esc_url(
+ $license->get_renewal_link(
+ [
+ 'medium' => 'gmail-one-click-global-notice',
+ 'content' => 'Renew your license',
+ ]
+ )
+ )
+ );
+ ?>
+
+ is_auth_required() && ! $license->is_valid() ) { // Already authorized and license is not valid. + ?> +
+
+ Verify your license and reconnect your authorization.', 'wp-mail-smtp-pro' ),
+ [
+ 'a' => [
+ 'href' => [],
+ ],
+ ]
+ ),
+ esc_url( wp_mail_smtp()->get_admin()->get_admin_page_url() )
+ );
+ ?>
+
+ is_reauth_required() ) { // Reauthorization required. + ?> +
+
+ reconnect your authorization.', 'wp-mail-smtp-pro' ),
+ [
+ 'a' => [
+ 'href' => [],
+ ],
+ ]
+ ),
+ esc_url( wp_mail_smtp()->get_admin()->get_admin_page_url() . '#wp-mail-smtp-setting-row-gmail-one-click-setup-authorize' )
+ );
+ ?>
+
+ 'authenticate_missing_arg',
+ 'message' => sprintf( /* translators: %1$s is an argument name. */
+ esc_html__( 'The %1$s authenticate parameter is missing', 'wp-mail-smtp-pro' ),
+ esc_html( $arg )
+ ),
+ 'version' => WPMS_PLUGIN_VER,
+ 'pro' => wp_mail_smtp()->is_pro(),
+ ]
+ );
+ }
+ }
+
+ $auth = new Auth();
+
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
+ if ( ! $auth->get_client()->is_valid_one_time_token( $_REQUEST['tt'] ) ) {
+ wp_send_json_error(
+ [
+ 'error' => 'authenticate_invalid_tt',
+ 'message' => esc_html__( 'Invalid one time token sent', 'wp-mail-smtp-pro' ),
+ 'version' => WPMS_PLUGIN_VER,
+ 'pro' => wp_mail_smtp()->is_pro(),
+ ]
+ );
+ }
+
+ wp_send_json_success();
+ }
+
+ /**
+ * AJAX callback for removing the oAuth authorization connection.
+ *
+ * Currently used only for Setup Wizard and works only with primary connection.
+ *
+ * @since 3.11.0
+ */
+ public function remove_oauth_connection() {
+
+ check_ajax_referer( 'wpms-admin-nonce', 'nonce' );
+
+ if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_global_options() ) ) {
+ wp_send_json_error();
+ }
+
+ $auth = new Auth();
+
+ $auth->get_client()->remove_connection();
+
+ $options = Options::init();
+ $old_opt = $options->get_all_raw();
+
+ unset( $old_opt['gmail']['one_click_setup_credentials'] );
+ unset( $old_opt['gmail']['one_click_setup_user_details'] );
+ unset( $old_opt['gmail']['one_click_setup_status'] );
+
+ $options->set( $old_opt );
+
+ wp_send_json_success();
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Outlook/AttachmentsUploader.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Outlook/AttachmentsUploader.php
new file mode 100644
index 00000000..4d17d72e
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Outlook/AttachmentsUploader.php
@@ -0,0 +1,360 @@
+mailer = $mailer;
+ }
+
+ /**
+ * Upload attachment.
+ *
+ * @since 3.4.0
+ *
+ * @param array $attachment PHPMailer attachment array.
+ * @param string $message_id Outlook message ID.
+ *
+ * @return true|WP_Error
+ */
+ public function upload( $attachment, $message_id ) {
+
+ $this->file_size = $this->mailer->get_attachment_file_size( $attachment );
+
+ if ( $this->file_size === false ) {
+ return new WP_Error( 'file-size', __( 'The attachment file size can\'t be calculated.', 'wp-mail-smtp-pro' ) );
+ }
+
+ if ( $this->file_size >= self::LARGE_ATTACHMENT_MIN_FILE_SIZE ) {
+ return $this->upload_large_attachment( $attachment, $message_id );
+ } else {
+ return $this->upload_regular_attachment( $attachment, $message_id );
+ }
+ }
+
+ /**
+ * Upload regular attachment. Less than 2MB.
+ *
+ * @see https://docs.microsoft.com/en-us/graph/api/message-post-attachments
+ *
+ * @since 3.4.0
+ *
+ * @param array $attachment PHPMailer attachment array.
+ * @param string $message_id Outlook message ID.
+ *
+ * @return true|WP_Error
+ */
+ private function upload_regular_attachment( $attachment, $message_id ) {
+
+ $file = $this->mailer->get_attachment_file_content( $attachment );
+
+ if ( $file === false ) {
+ return new WP_Error( 'not-found', __( 'Attachment file not found.', 'wp-mail-smtp-pro' ) );
+ }
+
+ $response = $this->mailer->remote_request(
+ "https://graph.microsoft.com/v1.0/me/messages/{$message_id}/attachments",
+ [
+ 'headers' => $this->mailer->get_headers(),
+ 'body' => wp_json_encode(
+ [
+ '@odata.type' => '#microsoft.graph.fileAttachment',
+ 'name' => $this->mailer->get_attachment_file_name( $attachment ),
+ // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
+ 'contentBytes' => base64_encode( $file ),
+ 'contentType' => $attachment[4],
+ ]
+ ),
+ ]
+ );
+
+ if ( wp_remote_retrieve_response_code( $response ) !== 201 ) {
+ return $this->get_response_error( $response, __( 'Regular attachment upload failed.', 'wp-mail-smtp-pro' ) );
+ }
+
+ return true;
+ }
+
+ /**
+ * Upload large attachment.
+ * Minimum file size 2MB.
+ *
+ * @see https://docs.microsoft.com/en-us/graph/outlook-large-attachments
+ *
+ * @since 3.4.0
+ *
+ * @param array $attachment PHPMailer attachment array.
+ * @param string $message_id Outlook message ID.
+ *
+ * @return true|WP_Error
+ */
+ private function upload_large_attachment( $attachment, $message_id ) {
+
+ // Reset upload progress.
+ $this->large_file_upload_progress = 0;
+
+ // Create upload session.
+ $upload_url = $this->create_upload_session( $attachment, $message_id );
+
+ if ( is_wp_error( $upload_url ) ) {
+ return $upload_url;
+ }
+
+ /*
+ * Upload the various chunks.
+ * $result will be false until the process is complete.
+ */
+ $result = false;
+
+ // If there is a string attachment, create a file in a memory from the string for further processing.
+ if ( $attachment[5] === true ) {
+
+ // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fopen
+ $handle = fopen( 'php://memory', 'r+b' );
+
+ // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fwrite
+ fwrite( $handle, $attachment[0] );
+ rewind( $handle );
+ } else {
+
+ // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fopen
+ $handle = fopen( $attachment[0], 'rb' );
+ }
+
+ while ( $result === false && ! feof( $handle ) ) {
+
+ // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fread
+ $chunk = fread( $handle, self::LARGE_ATTACHMENT_CHUNK_SIZE );
+ $result = $this->next_chunk( $upload_url, $chunk );
+ }
+
+ // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fclose
+ fclose( $handle );
+
+ if ( $result === false ) {
+ return new WP_Error( 'unknown', __( 'Large attachment upload failed.', 'wp-mail-smtp-pro' ) );
+ }
+
+ return $result;
+ }
+
+ /**
+ * Create upload session for upload large attachment.
+ *
+ * @see https://docs.microsoft.com/en-us/graph/api/attachment-createuploadsession
+ *
+ * @since 3.4.0
+ *
+ * @param array $attachment PHPMailer attachment array.
+ * @param string $message_id Outlook message ID.
+ */
+ private function create_upload_session( $attachment, $message_id ) {
+
+ $response = $this->mailer->remote_request(
+ "https://graph.microsoft.com/v1.0/me/messages/{$message_id}/attachments/createUploadSession",
+ [
+ 'headers' => $this->mailer->get_headers(),
+ 'body' => wp_json_encode(
+ [
+ 'AttachmentItem' => [
+ 'attachmentType' => 'file',
+ 'contentType' => $attachment[4],
+ 'name' => $this->mailer->get_attachment_file_name( $attachment ),
+ 'size' => $this->file_size,
+ ],
+ ]
+ ),
+ ]
+ );
+
+ $response_code = wp_remote_retrieve_response_code( $response );
+
+ if ( $response_code !== 201 ) {
+ return $this->get_response_error( $response, __( 'Upload session creation failed.', 'wp-mail-smtp-pro' ) );
+ }
+
+ $body = json_decode( wp_remote_retrieve_body( $response ), true );
+
+ if ( ! isset( $body['uploadUrl'] ) ) {
+ return $this->get_response_error( $response, __( 'Upload session creation failed. The response is invalid, missing the "uploadUrl" field.', 'wp-mail-smtp-pro' ) );
+ }
+
+ return $body['uploadUrl'];
+ }
+
+ /**
+ * Upload large file chunk.
+ *
+ * @since 3.4.0
+ *
+ * @param string $upload_url Upload URL.
+ * @param string $chunk File binary chunk.
+ *
+ * @return bool|WP_Error Returns false when upload not complete yet.
+ */
+ private function next_chunk( $upload_url, $chunk ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
+
+ $chunk_size = Helpers::strsize( $chunk );
+ $last_byte_position = $this->large_file_upload_progress + $chunk_size - 1;
+ $timeout = (int) ini_get( 'max_execution_time' );
+
+ // We shouldn't send the Authorization header here as a temp auth token is included in the upload URL.
+ $response = $this->mailer->remote_request(
+ $upload_url,
+ [
+ 'method' => 'PUT',
+ 'timeout' => $timeout ? $timeout : 30,
+ 'headers' => [
+ 'content-range' => "bytes $this->large_file_upload_progress-$last_byte_position/$this->file_size",
+ 'content-length' => $chunk_size,
+ 'content-type' => 'application/octet-stream',
+ ],
+ 'body' => $chunk,
+ ]
+ );
+
+ $response_code = wp_remote_retrieve_response_code( $response );
+
+ // A 404 code indicates that the upload session no longer exists.
+ if ( $response_code === 404 ) {
+ return $this->get_response_error( $response, __( 'Upload URL has expired.', 'wp-mail-smtp-pro' ) );
+ }
+
+ // If we have uploaded the last chunk, we should receive a 201 Created response code.
+ if ( $last_byte_position === ( $this->file_size - 1 ) ) {
+
+ // If we received a 201 Created response, the upload is complete, return true.
+ if ( $response_code === 201 ) {
+ return true;
+ }
+
+ return $this->get_response_error( $response, __( 'Last part of the file upload failed.', 'wp-mail-smtp-pro' ) );
+ }
+
+ // If we didn't receive a 200 Accepted response from the Graph API, something has gone wrong.
+ if ( $response_code !== 200 ) {
+ return $this->get_response_error( $response, __( 'File chunk upload failed.', 'wp-mail-smtp-pro' ) );
+ }
+
+ /**
+ * If we received a 200 Accepted response, it will include a nextExpectedRanges key, which will tell
+ * us the next range we'll upload.
+ */
+ $body = json_decode( wp_remote_retrieve_body( $response ), true );
+
+ if ( ! isset( $body['nextExpectedRanges'] ) ) {
+ return $this->get_response_error( $response, __( 'File chunk upload failed. The response is invalid, missing the "nextExpectedRanges" field.', 'wp-mail-smtp-pro' ) );
+ }
+
+ $next_expected_ranges = $body['nextExpectedRanges'];
+ $next_range = $next_expected_ranges[0];
+
+ $this->large_file_upload_progress = $next_range;
+
+ // Upload not complete yet, return false.
+ return false;
+ }
+
+ /**
+ * Retrieve Outlook response error.
+ *
+ * @since 3.4.0
+ *
+ * @param array $response Response array.
+ * @param string $message Context based message.
+ *
+ * @return WP_Error
+ */
+ private function get_response_error( $response, $message = false ) {
+
+ $body = wp_remote_retrieve_body( $response );
+
+ if ( $body && WP::is_json( $body ) ) {
+ $body = json_decode( $body );
+ }
+
+ if ( ! empty( $body->error->message ) ) {
+ $message = $body->error->message;
+ $code = ! empty( $body->error->code ) ? $body->error->code : '';
+
+ $error_text = Helpers::format_error_message( $message, $code );
+ } else {
+ $error_text = WP::wp_remote_get_response_error_message( $response );
+ }
+
+ if ( $message ) {
+ $error_text = $message . WP::EOL . $error_text;
+ }
+
+ return new WP_Error( wp_remote_retrieve_response_code( $response ), $error_text );
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Outlook/Auth.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Outlook/Auth.php
new file mode 100644
index 00000000..c45db71f
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Outlook/Auth.php
@@ -0,0 +1,404 @@
+mailer_slug !== Options::SLUG ) {
+ return;
+ }
+
+ $this->options = $this->connection_options->get_group( $this->mailer_slug );
+
+ if ( ! empty( $this->options['one_click_setup_enabled'] ) ) {
+ return;
+ }
+
+ $this->get_client();
+ }
+
+ /**
+ * Init and get the OAuth2 Client object.
+ *
+ * @since 1.0.0
+ * @since 2.5.0 Add force method parameter.
+ *
+ * @param bool $force If the client should be forcefully reinitialized.
+ *
+ * @return GenericProvider
+ * @throws IdentityProviderException Emits exception on requests failure.
+ */
+ public function get_client( $force = false ) {
+
+ // Doesn't load client twice + gives ability to overwrite.
+ if ( ! empty( $this->client ) && ! $force ) {
+ return $this->client;
+ }
+
+ $this->include_vendor_lib();
+
+ /**
+ * Filters auth authorize url.
+ *
+ * @since 2.8.0
+ *
+ * @param string $url Auth authorize url.
+ */
+ $authorize_url = apply_filters(
+ 'wp_mail_smtp_pro_providers_outlook_auth_authorize_url',
+ 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize'
+ );
+
+ /**
+ * Filters auth access token url.
+ *
+ * @since 2.8.0
+ *
+ * @param string $url Auth access token url.
+ */
+ $access_token_url = apply_filters(
+ 'wp_mail_smtp_pro_providers_outlook_auth_access_token_url',
+ 'https://login.microsoftonline.com/common/oauth2/v2.0/token'
+ );
+
+ /**
+ * Filters auth resource owner details url.
+ *
+ * @since 2.8.0
+ *
+ * @param string $url Auth resource owner details url.
+ */
+ $resource_owner_details_url = apply_filters(
+ 'wp_mail_smtp_pro_providers_outlook_auth_resource_owner_details_url',
+ 'https://graph.microsoft.com/v1.0/me'
+ );
+
+ $this->client = new GenericProvider(
+ [
+ 'clientId' => $this->options['client_id'],
+ 'clientSecret' => $this->options['client_secret'],
+ 'redirectUri' => self::get_plugin_auth_url(),
+ 'urlAuthorize' => $authorize_url,
+ 'urlAccessToken' => $access_token_url,
+ 'urlResourceOwnerDetails' => $resource_owner_details_url,
+ 'scopeSeparator' => ' ',
+ ]
+ );
+
+ // Do not process if we don't have both App ID & Password.
+ if ( ! $this->is_clients_saved() ) {
+ return $this->client;
+ }
+
+ $access_token = null;
+
+ if ( ! empty( $this->options['access_token'] ) ) {
+ $access_token = new AccessToken( (array) $this->options['access_token'] );
+ }
+
+ // Refresh expired token if needed.
+ if ( ! empty( $access_token ) && $access_token->hasExpired() ) {
+ $this->refresh_access_token( $access_token );
+ }
+
+ return $this->client;
+ }
+
+ /**
+ * Try to get an access token using the authorization code grant.
+ *
+ * @since 3.4.0
+ * @since 4.9.0 Returns WP_Error on failure (carrying debug_event_id) or true on success.
+ * Accepts the auth code as a parameter instead of reading from options.
+ *
+ * @param string $code The authorization code returned by Microsoft.
+ *
+ * @return WP_Error|true
+ */
+ private function obtain_access_token( $code ) {
+
+ if ( empty( $code ) ) {
+ return new WP_Error(
+ 'wp_mail_smtp_outlook_auth_missing_code',
+ esc_html__( 'Authorization code is missing.', 'wp-mail-smtp-pro' )
+ );
+ }
+
+ try {
+ $access_token = $this->client->getAccessToken(
+ 'authorization_code',
+ [ 'code' => $code ]
+ );
+
+ $this->update_access_token( $access_token->jsonSerialize() );
+ $this->update_refresh_token( $access_token->getRefreshToken() );
+ $this->update_user_details( $access_token );
+ $this->update_scopes( $this->get_scopes() );
+
+ // Set from email address.
+ $all = $this->connection_options->get_all();
+ $user_details = $all[ $this->mailer_slug ]['user_details'];
+
+ if ( ! empty( $user_details['email'] ) ) {
+ $all['mail']['from_email'] = $user_details['email'];
+ }
+
+ $this->connection_options->set( $all, false, true );
+
+ return true;
+ } catch ( IdentityProviderException $e ) {
+ $response = $e->getResponseBody();
+
+ return new WP_Error(
+ 'outlook_auth_failed',
+ Helpers::format_error_message( $response['error_description'], $response['error'] )
+ );
+ } catch ( Exception $e ) { // Catch any other general exceptions just in case.
+ return new WP_Error( 'outlook_auth_failed', $e->getMessage() );
+ }
+ }
+
+ /**
+ * Refresh expired access token.
+ *
+ * @since 3.4.0
+ *
+ * @param AccessToken $access_token Expired access token.
+ */
+ private function refresh_access_token( $access_token ) {
+
+ try {
+ $new_access_token = $this->client->getAccessToken(
+ 'refresh_token',
+ [ 'refresh_token' => $access_token->getRefreshToken() ]
+ );
+
+ $this->update_access_token( $new_access_token->jsonSerialize() );
+ $this->update_refresh_token( $new_access_token->getRefreshToken() );
+ $this->update_user_details( $new_access_token );
+ } catch ( IdentityProviderException $e ) {
+ $response = $e->getResponseBody();
+
+ DebugEvents::add_throttled(
+ 'Mailer: Outlook' . WP::EOL .
+ Helpers::format_error_message( $response['error_description'], $response['error'] ),
+ 'outlook_refresh_error_' . $this->connection->get_id()
+ );
+ } catch ( Exception $e ) { // Catch any other general exception just in case.
+ DebugEvents::add_throttled(
+ 'Mailer: Outlook' . WP::EOL .
+ $e->getMessage(),
+ 'outlook_refresh_error_' . $this->connection->get_id()
+ );
+ }
+ }
+
+ /**
+ * Microsoft Apps doesn't support URLs with query params, and should be less than 256 characters.
+ * That's why we use /wp-admin/ page and will redirect user once again after processing MS request.
+ *
+ * @since 1.5.0
+ *
+ * @return string
+ */
+ public static function get_plugin_auth_url() {
+
+ return apply_filters( 'wp_mail_smtp_outlook_get_plugin_auth_url', WP::admin_url() );
+ }
+
+ /**
+ * Check and process the auth code for this provider.
+ *
+ * @since 1.5.0
+ * @since 4.9.0 Returns WP_Error on failure (carrying debug_event_id) or true on success.
+ * The auth code is no longer persisted to the database — exchange is
+ * fully request-bound.
+ *
+ * @param string $code The authorization code returned by Microsoft.
+ *
+ * @return WP_Error|true
+ */
+ public function process_auth( $code ) {
+
+ // Ensure the SDK client is initialized.
+ $this->get_client();
+
+ return $this->obtain_access_token( $code );
+ }
+
+ /**
+ * Get the auth URL used to proceed to Provider to request access to send emails.
+ *
+ * @since 1.5.0
+ *
+ * @return string
+ * @throws Exception Emitted when something went wrong.
+ */
+ public function get_auth_url() {
+
+ $client = $this->get_client();
+
+ if (
+ ! empty( $client ) &&
+ class_exists( '\WPMailSMTP\Vendor\League\OAuth2\Client\Provider\GenericProvider', false ) &&
+ $client instanceof GenericProvider
+ ) {
+ $url_options = [
+ 'state' => $this->get_state(),
+ 'scope' => $this->get_scopes(),
+ ];
+
+ $auth_url = $client->getAuthorizationUrl( $url_options );
+
+ return $auth_url;
+ }
+
+ return '#';
+ }
+
+ /**
+ * Get auth scopes.
+ *
+ * @since 2.8.0
+ *
+ * @return array
+ */
+ protected function get_scopes() {
+
+ /**
+ * Filters auth scopes.
+ *
+ * @since 2.8.0
+ *
+ * @param array $scopes Auth scopes.
+ */
+ return apply_filters( 'wp_mail_smtp_pro_providers_outlook_auth_get_scopes', self::SCOPES );
+ }
+
+ /**
+ * Get and update user-related details (display name and email).
+ *
+ * @since 1.5.0
+ *
+ * @param AccessTokenInterface $access_token The outlook action token object.
+ *
+ * @throws IdentityProviderException Emits exception when error occurs.
+ */
+ protected function update_user_details( $access_token ) {
+
+ $connection_options = $this->connection->get_options();
+
+ if ( empty( $access_token ) ) {
+ $access_token = new AccessToken( (array) $connection_options->get( $this->mailer_slug, 'access_token' ) );
+ }
+
+ // Default values.
+ $user = [
+ 'display_name' => '',
+ 'email' => '',
+ ];
+
+ try {
+ $resource_owner = $this->get_client()->getResourceOwner( $access_token );
+ $resource_data = $resource_owner->toArray();
+
+ $user = [
+ 'display_name' => $resource_data['displayName'],
+ 'email' => $resource_data['userPrincipalName'],
+ ];
+ } catch ( IdentityProviderException $e ) {
+ $response = $e->getResponseBody();
+
+ DebugEvents::add(
+ 'Mailer: Outlook (requesting user details)' . WP::EOL .
+ Helpers::format_error_message( $response['error_description'], $response['error'] )
+ );
+ } catch ( Exception $e ) {
+ // Catch general any other exception just in case.
+ DebugEvents::add(
+ 'Mailer: Outlook (requesting user details)' . WP::EOL .
+ $e->getMessage()
+ );
+ }
+
+ $all = $connection_options->get_all();
+
+ // To save in DB.
+ $all[ $this->mailer_slug ]['user_details'] = $user;
+
+ // To save in currently retrieved options array.
+ $this->options['user_details'] = $user;
+
+ // NOTE: These options need to be saved by overwriting all options, because WP automatic updates can cause an issue: GH #575!
+ $connection_options->set( $all, false, true );
+ }
+
+ /**
+ * Get the state value that should be used for the "state" parameter in the OAuth authorization request.
+ *
+ * @since 3.7.0
+ *
+ * @return string
+ */
+ protected function get_state() {
+
+ /*
+ * Include "wp-mail-smtp" to the state to identify Outlook OAuth requests, since Outlook mailer doesn't use
+ * a regular auth route because of Microsoft API limitations.
+ */
+ return 'wp-mail-smtp-' . parent::get_state();
+ }
+
+ /**
+ * Get user information associated with the current OAuth connection.
+ *
+ * @since 4.3.0
+ *
+ * @return array
+ */
+ public function get_user_info() {
+
+ return $this->connection_options->get( $this->mailer_slug, 'user_details' );
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Outlook/Mailer.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Outlook/Mailer.php
new file mode 100644
index 00000000..9dd347f8
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Outlook/Mailer.php
@@ -0,0 +1,893 @@
+ [],
+ 'saveToSentItems' => true,
+ ];
+
+ /**
+ * Which response code from HTTP provider is considered to be successful?
+ *
+ * @since 1.5.0
+ *
+ * @var int
+ */
+ protected $email_sent_code = 202;
+
+ /**
+ * URL to make an API request to.
+ *
+ * @since 1.5.0
+ *
+ * @var string
+ */
+ protected $url = 'https://graph.microsoft.com/v1.0/me/sendMail';
+
+ /**
+ * Email attachments.
+ *
+ * @since 3.4.0
+ *
+ * @var array PHPMailer attachments array.
+ */
+ private $attachments = [];
+
+ /**
+ * Authentication instance.
+ *
+ * @since 4.3.0
+ *
+ * @var AuthAbstract Authentication instance.
+ */
+ private $auth;
+
+ /**
+ * Mailer constructor.
+ *
+ * @since 1.5.0
+ *
+ * @param MailCatcherInterface $phpmailer The MailCatcher object.
+ * @param ConnectionInterface $connection The Connection object.
+ */
+ public function __construct( $phpmailer, $connection = null ) {
+
+ // We want to prefill everything from MailCatcher class, which extends PHPMailer.
+ parent::__construct( $phpmailer, $connection );
+
+ if ( $this->connection_options->get( $this->mailer, 'one_click_setup_enabled' ) ) {
+ // Init the client that checks tokens and re-saves them if needed.
+ $this->auth = new OneClickAuth( $connection );
+
+ $token = $this->connection_options->get( $this->mailer, 'one_click_setup_credentials' );
+ } else {
+ // Init the client that checks tokens and re-saves them if needed.
+ $this->auth = new Auth( $connection );
+
+ $token = $this->connection_options->get( $this->mailer, 'access_token' );
+ }
+
+ if ( ! empty( $token['access_token'] ) ) {
+ $this->set_header( 'Authorization', 'Bearer ' . $token['access_token'] );
+ }
+ $this->set_header( 'content-type', 'application/json' );
+
+ /**
+ * Filters API send email url.
+ *
+ * @since 2.8.0
+ *
+ * @param string $url API sent email url.
+ */
+ $this->url = apply_filters( 'wp_mail_smtp_pro_providers_outlook_mailer_api_url', $this->url );
+ }
+
+ /**
+ * PhpMailer generates certain headers, including our custom.
+ * We want to preserve them when sending an email.
+ * Thus we need to custom process them and add to message body headers.
+ *
+ * @since 1.5.0
+ *
+ * @param array $headers
+ */
+ public function set_headers( $headers ) {
+
+ foreach ( $headers as $header ) {
+ $name = isset( $header[0] ) ? $header[0] : false;
+ $value = isset( $header[1] ) ? $header[1] : false;
+
+ $this->set_body_header( $name, $value );
+ }
+
+ // Add custom PHPMailer-specific header.
+ $this->set_body_header( 'X-Mailer', 'WPMailSMTP/Mailer/' . $this->mailer . ' ' . WPMS_PLUGIN_VER );
+ }
+
+ /**
+ * MS Graph object is nested inside 'message'.
+ *
+ * @since 1.5.0
+ *
+ * @param array $param
+ */
+ public function set_body_param( $param ) {
+
+ $this->body['message'] = PluginOptions::array_merge_recursive( $this->body['message'], $param );
+ }
+
+ /**
+ * We are allowed to provide custom header for emails.
+ *
+ * @see https://docs.microsoft.com/en-us/graph/api/resources/internetmessageheader?view=graph-rest-1.0
+ *
+ * @since 1.5.0
+ *
+ * @param string $name
+ * @param string $value
+ */
+ public function set_body_header( $name, $value ) {
+
+ $is_duplicated = false;
+
+ $name = sanitize_text_field( $name );
+ if ( empty( $name ) ) {
+ return;
+ }
+
+ // List-Unsubscribe header needs special handling.
+ // See https://learn.microsoft.com/en-us/answers/questions/1460824/sending-emails-using-ms-graph-with-list-unsubscrib.
+ if ( strtolower( $name ) === 'list-unsubscribe' ) {
+ $this->set_body_param(
+ [
+ 'singleValueExtendedProperties' => [
+ [
+ 'id' => self::LIST_UNSUBSCRIBE_HEADER_ID,
+ 'value' => $this->sanitize_header_value( $name, $value ),
+ ],
+ ],
+ ]
+ );
+
+ return;
+ }
+
+ // Email will not be sent if the header's name is not prepended with 'X-'.
+ if ( ! in_array( substr( $name, 0, 2 ), [ 'x-', 'X-' ], true ) ) {
+ $name = 'X-' . $name;
+ }
+
+ $headers = isset( $this->body['message']['internetMessageHeaders'] ) ? (array) $this->body['message']['internetMessageHeaders'] : array();
+
+ // Do not allow duplicate names.
+ foreach ( $headers as $header ) {
+
+ if ( $header['name'] === $name ) {
+ $is_duplicated = true;
+ }
+ }
+
+ if ( $is_duplicated ) {
+ return;
+ }
+
+ $this->set_body_param(
+ array(
+ 'internetMessageHeaders' => [
+ [
+ 'name' => $name,
+ 'value' => $this->sanitize_header_value( $name, $value ),
+ ],
+ ],
+ )
+ );
+ }
+
+ /**
+ * Define the FROM (name and email) and SENDER.
+ * The authorized user should have permission to send an email from the defined email address.
+ *
+ * @see https://docs.microsoft.com/en-us/graph/api/resources/recipient?view=graph-rest-1.0
+ * @see https://docs.microsoft.com/en-us/graph/outlook-send-mail-from-other-user
+ *
+ * @since 1.5.0
+ * @since 3.4.0 Allow custom email address.
+ *
+ * @param string $email From mail.
+ * @param string $name From name.
+ */
+ public function set_from( $email, $name = '' ) {
+
+ if ( $this->is_legacy_from() ) {
+ $this->set_legacy_from();
+
+ return;
+ }
+
+ if ( ! filter_var( $email, FILTER_VALIDATE_EMAIL ) ) {
+ return;
+ }
+
+ $from['address'] = $email;
+
+ if ( ! empty( $name ) ) {
+ $from['name'] = $name;
+ }
+
+ $this->set_body_param(
+ [
+ 'from' => [ 'emailAddress' => $from ],
+ 'sender' => [ 'emailAddress' => $from ],
+ ]
+ );
+ }
+
+ /**
+ * Define the CC/BCC/TO (with names and emails).
+ *
+ * @see https://docs.microsoft.com/en-us/graph/api/resources/recipient?view=graph-rest-1.0
+ *
+ * @since 1.5.0
+ *
+ * @param array $recipients
+ */
+ public function set_recipients( $recipients ) {
+
+ if ( empty( $recipients ) ) {
+ return;
+ }
+
+ // Allow for now only these recipient types.
+ $default = array( 'to', 'cc', 'bcc' );
+ $data = array();
+
+ foreach ( $recipients as $type => $emails ) {
+ if (
+ ! in_array( $type, $default, true ) ||
+ empty( $emails ) ||
+ ! is_array( $emails )
+ ) {
+ continue;
+ }
+
+ $type_id = $type . 'Recipients';
+
+ $data[ $type_id ] = array();
+
+ // Iterate over all emails for each type.
+ // There might be multiple to/cc/bcc emails.
+ foreach ( $emails as $email ) {
+ $holder = array();
+ $addr = isset( $email[0] ) ? $email[0] : false;
+ $name = isset( $email[1] ) ? $email[1] : false;
+
+ if ( ! filter_var( $addr, FILTER_VALIDATE_EMAIL ) ) {
+ continue;
+ }
+
+ $holder['address'] = $addr;
+ if ( ! empty( $name ) ) {
+ $holder['name'] = $name;
+ }
+
+ array_push( $data[ $type_id ], array( 'emailAddress' => $holder ) );
+ }
+ }
+
+ if ( ! empty( $data ) ) {
+ foreach ( $data as $type_id => $type_data ) {
+ $this->set_body_param(
+ array(
+ $type_id => $type_data,
+ )
+ );
+ }
+ }
+ }
+
+ /**
+ * Set the email content.
+ * MS Graph supports only 1 type at a time (no multipart emails),
+ * so in case of multipart we ignore the text/plain part.
+ *
+ * @see https://docs.microsoft.com/en-us/graph/api/resources/itembody?view=graph-rest-1.0
+ *
+ * @since 1.5.0
+ *
+ * @param array|string $content String when text/plain, array otherwise.
+ */
+ public function set_content( $content ) {
+
+ if ( empty( $content ) ) {
+ return;
+ }
+
+ if ( is_array( $content ) ) {
+
+ if (
+ ! isset( $content['text'] ) ||
+ ! isset( $content['html'] )
+ ) {
+ return;
+ }
+
+ if ( ! empty( $content['html'] ) ) {
+ $body = array(
+ 'contentType' => 'html',
+ 'content' => $content['html'],
+ );
+ } else {
+ $body = array(
+ 'contentType' => 'text',
+ 'content' => $content['text'],
+ );
+ }
+
+ $this->set_body_param(
+ array(
+ 'body' => $body,
+ )
+ );
+ } else {
+ $body = array(
+ 'contentType' => 'html',
+ 'content' => $content,
+ );
+
+ if ( $this->phpmailer->ContentType === 'text/plain' ) {
+ $body['contentType'] = 'text';
+ }
+
+ $this->set_body_param(
+ array(
+ 'body' => $body,
+ )
+ );
+ }
+ }
+
+ /**
+ * Set Reply-To part of the message.
+ * This is not in email header, but in body>message.
+ *
+ * @see https://docs.microsoft.com/en-us/graph/api/resources/recipient?view=graph-rest-1.0
+ *
+ * @since 1.5.0
+ *
+ * @param array $reply_to
+ */
+ public function set_reply_to( $reply_to ) {
+
+ if ( empty( $reply_to ) ) {
+ return;
+ }
+
+ $data = array();
+
+ foreach ( $reply_to as $key => $emails ) {
+ if (
+ empty( $emails ) ||
+ ! is_array( $emails )
+ ) {
+ continue;
+ }
+
+ $addr = isset( $emails[0] ) ? $emails[0] : false;
+ $name = isset( $emails[1] ) ? $emails[1] : false;
+
+ if ( ! filter_var( $addr, FILTER_VALIDATE_EMAIL ) ) {
+ continue;
+ }
+
+ $holder = array();
+
+ $holder['address'] = $addr;
+ if ( ! empty( $name ) ) {
+ $holder['name'] = $name;
+ }
+
+ $data[] = array( 'emailAddress' => $holder );
+ }
+
+ if ( ! empty( $data ) ) {
+ $this->set_body_param(
+ array(
+ 'replyTo' => $data,
+ )
+ );
+ }
+ }
+
+ /**
+ * MS Graph doesn't support sender or return_path params.
+ * So we do nothing.
+ *
+ * @since 1.5.0
+ *
+ * @param string $from_email
+ */
+ public function set_return_path( $from_email ) {
+ }
+
+ /**
+ * Add attachments to the body.
+ * MS Graph accepts an array of files content in body, so we will include all files and send.
+ * Doesn't handle exceeding the limits etc, as this is done and reported by MS Graph API via errors.
+ *
+ * @see https://docs.microsoft.com/en-us/graph/api/resources/attachment?view=graph-rest-1.0
+ *
+ * @since 1.5.0
+ *
+ * @param array $attachments The array of attachments data.
+ */
+ public function set_attachments( $attachments ) {
+
+ if ( empty( $attachments ) ) {
+ return;
+ }
+
+ $this->attachments = $attachments;
+
+ $data = $this->prepare_attachments( $attachments );
+
+ if ( ! empty( $data ) ) {
+ $this->set_body_param(
+ [
+ 'hasAttachments' => true,
+ 'attachments' => $data,
+ ]
+ );
+ }
+ }
+
+ /**
+ * Prepare attachments data for Outlook API.
+ *
+ * @since 3.4.0
+ *
+ * @param array $attachments Array of attachments.
+ *
+ * @return array
+ */
+ private function prepare_attachments( $attachments ) {
+
+ $data = [];
+
+ foreach ( $attachments as $attachment ) {
+ $file = $this->get_attachment_file_content( $attachment );
+
+ if ( $file === false ) {
+ continue;
+ }
+
+ $data[] = [
+ '@odata.type' => '#microsoft.graph.fileAttachment',
+ 'name' => $this->get_attachment_file_name( $attachment ),
+ // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
+ 'contentBytes' => base64_encode( $file ),
+ 'contentType' => $attachment[4],
+ ];
+ }
+
+ return $data;
+ }
+
+ /**
+ * Redefine the way email body is returned.
+ * Microsoft Graph needs JSON object.
+ *
+ * @see https://docs.microsoft.com/en-us/graph/api/resources/message?view=graph-rest-1.0
+ *
+ * @since 1.5.0
+ *
+ * @return string
+ */
+ public function get_body() {
+
+ return wp_json_encode( $this->get_body_raw() );
+ }
+
+ /**
+ * Get raw body.
+ *
+ * @since 3.4.0
+ *
+ * @return array
+ */
+ private function get_body_raw() {
+
+ return $this->process_body_headers_unique( parent::get_body() );
+ }
+
+ /**
+ * Outlook doesn't allow duplicated headers in emails.
+ * This will make sure that no duplicates are available.
+ * The last duplicate header will be preserved, the 1st one will be removed.
+ *
+ * @since 1.5.0
+ *
+ * @param array $body Email body will all email-related headers and attachments.
+ *
+ * @return array
+ */
+ protected function process_body_headers_unique( $body ) {
+
+ $headers = isset( $body['message']['internetMessageHeaders'] ) ? $body['message']['internetMessageHeaders'] : array();
+ $to_process = array();
+
+ // Get keys and header name for all headers, with duplicates.
+ foreach ( $headers as $key => $header_outer ) {
+ $to_process[ $key ] = $header_outer['name'];
+ }
+
+ if ( ! empty( $to_process ) ) {
+ // With this double flipping we remove duplicates,
+ // preserving the last define value with the biggest key.
+ $to_process = array_flip( array_flip( $to_process ) );
+
+ // Now get headers without duplicates.
+ $headers = array_filter( $headers, function( $key ) use ( $to_process ) {
+ return isset( $to_process[ $key ] );
+ }, ARRAY_FILTER_USE_KEY );
+ }
+
+ // Reset keys in headers array and assign back to body.
+ $body['message']['internetMessageHeaders'] = array_values( $headers );
+
+ return $body;
+ }
+
+ /**
+ * Send the email using Microsoft Graph API.
+ *
+ * @see https://docs.microsoft.com/en-us/graph/api/user-sendmail?view=graph-rest-1.0
+ *
+ * @since 1.5.0
+ */
+ public function send() {
+
+ if ( $this->is_legacy_from() ) {
+ /*
+ * Right now Outlook doesn't allow to redefine From and Sender email headers.
+ * It always uses the email address that was used to connect to its API.
+ * With code below we are making sure that Email Log archive and single Email Log
+ * have the save value for From email header.
+ */
+ $sender = $this->connection_options->get( $this->mailer, 'user_details' );
+
+ if ( ! empty( $sender['email'] ) ) {
+ $this->phpmailer->From = $sender['email'];
+ $this->phpmailer->Sender = $sender['email'];
+ }
+ }
+
+ $large_message = false;
+
+ if (
+ ! empty( $this->attachments ) &&
+ Helpers::strsize( $this->get_body() ) > self::REGULAR_MESSAGE_MAX_BODY_SIZE
+ ) {
+ $large_message = true;
+ }
+
+ /**
+ * Filters whether message large.
+ *
+ * @since 3.4.0
+ *
+ * @param bool $large_message Whether message large.
+ * @param MailerAbstract $mailer Mailer object.
+ */
+ $large_message = apply_filters( 'wp_mail_smtp_pro_providers_outlook_mailer_send_large', $large_message, $this );
+
+ if ( $large_message === true ) {
+ $response = $this->send_large();
+ } else {
+ $response = $this->send_regular();
+ }
+
+ DebugEvents::add_debug(
+ esc_html__( 'An email request was sent to the Microsoft Graph API.', 'wp-mail-smtp-pro' )
+ );
+
+ $this->process_response( $response );
+ }
+
+ /**
+ * Send regular email using Microsoft Graph API.
+ *
+ * @see https://docs.microsoft.com/en-us/graph/api/user-sendmail
+ *
+ * @since 3.4.0
+ *
+ * @return array|\WP_Error
+ */
+ private function send_regular() {
+
+ return $this->remote_request(
+ $this->url,
+ [
+ 'headers' => $this->get_headers(),
+ 'body' => $this->get_body(),
+ ]
+ );
+ }
+
+ /**
+ * Send large email using Microsoft Graph API.
+ * Upload attachments in a separate requests.
+ *
+ * @see https://docs.microsoft.com/en-us/graph/api/user-post-messages
+ * @see https://docs.microsoft.com/en-us/graph/api/message-send
+ *
+ * @since 3.4.0
+ *
+ * @return array|\WP_Error
+ */
+ private function send_large() {
+
+ $body = $this->get_body_raw();
+ $message = $body['message'];
+
+ // Attachments will be uploaded in a separate process.
+ unset( $message['hasAttachments'] );
+ unset( $message['attachments'] );
+
+ // Create draft.
+ $response = $this->remote_request(
+ 'https://graph.microsoft.com/v1.0/me/messages',
+ [
+ 'headers' => $this->get_headers(),
+ 'body' => wp_json_encode( $message ),
+ ]
+ );
+
+ if ( wp_remote_retrieve_response_code( $response ) !== 201 ) {
+ return $response;
+ }
+
+ $body = json_decode( wp_remote_retrieve_body( $response ), true );
+
+ if ( ! isset( $body['id'] ) ) {
+ return $response;
+ }
+
+ $message_id = $body['id'];
+
+ // Upload attachments.
+ $uploader = new AttachmentsUploader( $this );
+
+ foreach ( $this->attachments as $attachment ) {
+ $is_uploaded = $uploader->upload( $attachment, $message_id );
+
+ if ( is_wp_error( $is_uploaded ) ) {
+ DebugEvents::add_debug(
+ __( 'Outlook attachment upload failed.', 'wp-mail-smtp-pro' ) . WP::EOL . $is_uploaded->get_error_message()
+ );
+ }
+ }
+
+ // Send email.
+ return $this->remote_request(
+ "https://graph.microsoft.com/v1.0/me/messages/{$message_id}/send",
+ [
+ 'headers' => $this->get_headers(),
+ ]
+ );
+ }
+
+ /**
+ * Process Outlook-specific response with a helpful error.
+ *
+ * @see https://docs.microsoft.com/en-us/graph/errors
+ * @see https://docs.microsoft.com/en-us/azure/active-directory/develop/reference-aadsts-error-codes
+ * @see https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/responsecode
+ *
+ * @since 1.5.0
+ *
+ * @return string
+ */
+ public function get_response_error() {
+
+ $error_text[] = $this->error_message;
+
+ if ( ! empty( $this->response ) ) {
+ $body = wp_remote_retrieve_body( $this->response );
+
+ if ( ! empty( $body->error->message ) ) {
+ $message = $body->error->message;
+ $code = ! empty( $body->error->code ) ? $body->error->code : '';
+ $description = '';
+
+ if ( $code === 'ErrorAccessDenied' ) {
+ $description = esc_html__( 'Note: this issue could also be caused by hitting the total message size limit. If you are using big attachments, please remove the existing Outlook mailer OAuth connection in WP Mail SMTP settings and connect it again. We recently added support for bigger attachments, but the oAuth re-connection is required.', 'wp-mail-smtp-pro' );
+ }
+
+ $error_text[] = Helpers::format_error_message( $message, $code, $description );
+ } else {
+ $error_text[] = WP::wp_remote_get_response_error_message( $this->response );
+ }
+ }
+
+ return implode( WP::EOL, array_map( 'esc_textarea', array_filter( $error_text ) ) );
+ }
+
+ /**
+ * Get the error code from the Microsoft Graph API response.
+ *
+ * @since 4.8.0
+ *
+ * @return string
+ */
+ public function get_response_error_code() {
+
+ if ( ! empty( $this->response ) ) {
+ $body = wp_remote_retrieve_body( $this->response );
+
+ if ( ! empty( $body->error->code ) ) {
+ return $body->error->code;
+ }
+ }
+
+ return parent::get_response_error_code();
+ }
+
+ /**
+ * Get mailer debug information, that is helpful during support.
+ *
+ * @since 1.5.0
+ *
+ * @return string
+ */
+ public function get_debug_info() {
+
+ $mg_text = [];
+
+ if ( ! $this->connection_options->get( $this->mailer, 'one_click_setup_enabled' ) ) {
+ $mg_text[] = 'App ID/Pass: ' . ( $this->auth->is_clients_saved() ? 'Yes' : 'No' );
+ }
+
+ $mg_text[] = 'Tokens: ' . ( ! $this->auth->is_auth_required() ? 'Yes' : 'No' );
+
+ return implode( '
', $mg_text );
+ }
+
+ /**
+ * Whether the mailer has all its settings correctly set up and saved.
+ *
+ * @since 1.5.0
+ *
+ * @return bool
+ */
+ public function is_mailer_complete() {
+
+ if ( ! $this->is_php_compatible() ) {
+ return false;
+ }
+
+ if (
+ $this->auth->is_clients_saved() &&
+ ! $this->auth->is_auth_required()
+ ) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Whether legacy from email address should be used.
+ *
+ * @since 3.4.0
+ *
+ * @return bool
+ */
+ private function is_legacy_from() {
+
+ /**
+ * Filters whether to use legacy from email address.
+ *
+ * @since 3.4.0
+ *
+ * @param bool $is_legacy_from Whether to use legacy from email address.
+ */
+ return apply_filters(
+ 'wp_mail_smtp_pro_providers_outlook_mailer_is_legacy_from',
+ Migration::get_current_version() < 1
+ );
+ }
+
+ /**
+ * Define the FROM (name and email) and SENDER.
+ * It doesn't support random email, should be the same as used for connection.
+ *
+ * @since 3.4.0
+ */
+ private function set_legacy_from() {
+
+ $sender = $this->connection_options->get( $this->mailer, 'user_details' );
+
+ $email_address = [
+ 'emailAddress' => [
+ 'name' => isset( $sender['display_name'] ) ? sanitize_text_field( $sender['display_name'] ) : '',
+ 'address' => isset( $sender['email'] ) ? $sender['email'] : '',
+ ],
+ ];
+
+ // The FROM and SENDER parameters are defined the same way, so use above data for both.
+ $this->set_body_param(
+ [
+ 'from' => $email_address,
+ 'sender' => $email_address,
+ ]
+ );
+ }
+
+ /**
+ * Get the default email addresses for the reply to email parameter.
+ *
+ * @deprecated 2.1.1
+ *
+ * @since 2.1.0
+ * @since 2.1.1 Not used anymore.
+ *
+ * @return array
+ */
+ public function default_reply_to_addresses() {
+
+ _deprecated_function( __CLASS__ . '::' . __METHOD__, '2.1.1 of WP Mail SMTP plugin' );
+
+ $sender = $this->connection_options->get( $this->mailer, 'user_details' );
+
+ return [
+ $sender['email'] => [
+ $sender['email'],
+ sanitize_text_field( $sender['display_name'] ),
+ ],
+ ];
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Outlook/OneClick/Auth.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Outlook/OneClick/Auth.php
new file mode 100644
index 00000000..b247412a
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Outlook/OneClick/Auth.php
@@ -0,0 +1,405 @@
+mailer_slug !== Options::SLUG ) {
+ return;
+ }
+
+ $this->options = $this->connection_options->get_group( $this->mailer_slug );
+
+ if ( empty( $this->options['one_click_setup_enabled'] ) ) {
+ return;
+ }
+
+ $this->get_client();
+ }
+
+ /**
+ * Init and get the OAuth2 Client object.
+ *
+ * @since 4.3.0
+ *
+ * @param bool $force If the client should be forcefully reinitialized.
+ *
+ * @return Client
+ */
+ public function get_client( $force = false ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
+
+ // Doesn't load client twice + gives ability to overwrite.
+ if ( ! empty( $this->client ) && ! $force ) {
+ return $this->client;
+ }
+
+ $this->client = new Client( WP::get_site_url() );
+
+ if ( ! $this->is_auth_required() && ! $this->is_reauth_required() ) {
+ $credentials = $this->options['one_click_setup_credentials'] ?? [];
+ $expires = $credentials['expires'] ?? '';
+ $refresh_token = $credentials['refresh_token'] ?? '';
+
+ // Update the old token if needed.
+ if ( ! empty( $expires ) && $expires < time() && ! empty( $refresh_token ) ) {
+ $this->refresh_access_token( $refresh_token );
+ }
+ }
+
+ return $this->client;
+ }
+
+ /**
+ * Get access and refresh tokens.
+ *
+ * @since 4.3.0
+ *
+ * @param string $authorization_code Authorization code.
+ *
+ * @throws Exception When a request error occurs.
+ */
+ private function get_tokens( $authorization_code ) {
+
+ $tokens = $this->client->get_tokens( $authorization_code );
+ $data = [
+ 'one_click_setup_status' => 'active',
+ 'one_click_setup_credentials' => [
+ 'access_token' => $tokens['access_token'],
+ 'refresh_token' => $tokens['refresh_token'],
+ 'expires' => $tokens['expires'],
+ ],
+ 'one_click_setup_user_details' => [
+ 'email' => $tokens['user_email'],
+ 'name' => $tokens['user_name'],
+ ],
+ ];
+
+ $all = $this->connection_options->get_all();
+
+ $all[ $this->mailer_slug ] = PluginOptions::array_merge_recursive( $all[ $this->mailer_slug ], $data );
+
+ if ( ! empty( $tokens['user_email'] ) ) {
+ $all['mail']['from_email'] = $tokens['user_email'];
+ }
+
+ $this->connection_options->set( $all, false, true );
+ }
+
+ /**
+ * Refresh expired access token.
+ *
+ * @since 4.3.0
+ *
+ * @param string $refresh_token Refresh token.
+ */
+ private function refresh_access_token( $refresh_token ) {
+
+ try {
+ $new_access_token = $this->client->refresh_access_token( $refresh_token );
+
+ $all = $this->connection_options->get_all();
+
+ // To save in DB.
+ $all[ $this->mailer_slug ]['one_click_setup_credentials']['access_token'] = $new_access_token['access_token'];
+ $all[ $this->mailer_slug ]['one_click_setup_credentials']['refresh_token'] = $new_access_token['refresh_token'];
+ $all[ $this->mailer_slug ]['one_click_setup_credentials']['expires'] = $new_access_token['expires'];
+
+ // To save in currently retrieved options array.
+ $this->options['one_click_setup_credentials']['access_token'] = $new_access_token['access_token'];
+ $this->options['one_click_setup_credentials']['refresh_token'] = $new_access_token['refresh_token'];
+ $this->options['one_click_setup_credentials']['expires'] = $new_access_token['expires'];
+
+ $this->connection_options->set( $all, false, true );
+ } catch ( Exception $e ) { // Catch any other general exception just in case.
+ DebugEvents::add_throttled(
+ 'Mailer: Outlook' . WP::EOL .
+ sprintf(
+ /* translators: %1$s - exception message. */
+ esc_html__( 'Failed to refresh access token. %1$s', 'wp-mail-smtp-pro' ),
+ $e->getMessage()
+ ),
+ 'outlook_refresh_error_' . $this->connection->get_id()
+ );
+
+ if ( $e->getCode() === 401 ) {
+ $this->set_auth_status( 'reauth' );
+ }
+ }
+ }
+
+ /**
+ * Get the url, that users will be redirected back to finish the OAuth process.
+ *
+ * @since 4.3.0
+ *
+ * @param ConnectionInterface $connection The Connection object.
+ *
+ * @return string
+ */
+ public static function get_plugin_auth_url( $connection = null ) {
+
+ if ( is_null( $connection ) ) {
+ $connection = wp_mail_smtp()->get_connections_manager()->get_primary_connection();
+ }
+
+ /**
+ * Filters the plugin auth redirect url.
+ *
+ * @since 4.3.0
+ *
+ * @param string $auth_url The plugin auth redirect url.
+ */
+ $auth_url = apply_filters(
+ 'wp_mail_smtp_pro_providers_outlook_one_click_auth_get_plugin_auth_url',
+ add_query_arg(
+ [
+ 'page' => Area::SLUG,
+ 'tab' => 'auth',
+ ],
+ admin_url( 'options-general.php' )
+ )
+ );
+
+ $state = [
+ wp_create_nonce( 'wp_mail_smtp_provider_client_state' ),
+ $connection->get_id(),
+ ];
+
+ return add_query_arg(
+ [
+ 'state' => implode( '-', $state ),
+ 'one_click_setup' => 1,
+ ],
+ $auth_url
+ );
+ }
+
+ /**
+ * Process authorization.
+ * Redirect user back to settings with an error message, if failed.
+ *
+ * @since 4.3.0
+ */
+ public function process() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded, Generic.Metrics.CyclomaticComplexity.TooHigh
+
+ $redirect_url = ( new ConnectionSettings( $this->connection ) )->get_admin_page_url();
+ $is_setup_wizard_auth = ! empty( $this->options['is_setup_wizard_auth'] );
+
+ if ( $is_setup_wizard_auth ) {
+ $this->update_is_setup_wizard_auth( false );
+
+ $redirect_url = SetupWizard::get_site_url() . '#/step/configure_mailer/outlook';
+ }
+
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+ if ( ! ( isset( $_GET['tab'] ) && $_GET['tab'] === 'auth' ) ) {
+ wp_safe_redirect( $redirect_url );
+ exit;
+ }
+
+ $state = isset( $_GET['state'] ) ? sanitize_key( $_GET['state'] ) : false;
+
+ if ( empty( $state ) ) {
+ wp_safe_redirect(
+ add_query_arg( 'error', 'oauth_invalid_state', $redirect_url )
+ );
+ exit;
+ }
+
+ [ $nonce ] = array_pad( explode( '-', $state ), 1, false );
+
+ // Verify the nonce that should be returned in the state parameter.
+ if ( ! wp_verify_nonce( $nonce, $this->state_key ) ) {
+ wp_safe_redirect(
+ add_query_arg(
+ 'error',
+ 'microsoft_invalid_nonce',
+ $redirect_url
+ )
+ );
+ exit;
+ }
+
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+ $authorization_code = ! empty( $_GET['authorization_code'] ) ? sanitize_text_field( wp_unslash( $_GET['authorization_code'] ) ) : '';
+
+ if ( empty( $authorization_code ) ) {
+ $event_id = DebugEvents::add( esc_html__( 'Microsoft authorization error. Authorization code is empty.', 'wp-mail-smtp-pro' ) );
+
+ wp_safe_redirect(
+ add_query_arg(
+ [
+ 'error' => 'outlook_one_click_setup_unsuccessful_oauth',
+ 'debug_event_id' => $event_id,
+ ],
+ $redirect_url
+ )
+ );
+ exit;
+ }
+
+ // Get access and refresh tokens.
+ try {
+ $this->get_tokens( $authorization_code );
+
+ wp_safe_redirect(
+ add_query_arg(
+ 'success',
+ 'outlook_one_click_setup_site_linked',
+ $redirect_url
+ )
+ );
+ exit;
+ } catch ( Exception $e ) { // Catch any other general exception just in case.
+ $event_id = DebugEvents::add(
+ 'Mailer: Outlook' . WP::EOL .
+ sprintf(
+ /* translators: %1$s - exception message. */
+ esc_html__( 'Failed to obtain access token. %1$s', 'wp-mail-smtp-pro' ),
+ $e->getMessage()
+ )
+ );
+
+ wp_safe_redirect(
+ add_query_arg(
+ [
+ 'error' => 'outlook_one_click_setup_unsuccessful_oauth',
+ 'debug_event_id' => $event_id,
+ ],
+ $redirect_url
+ )
+ );
+ exit;
+ }
+ }
+
+ /**
+ * Get the auth URL used to process authorization.
+ *
+ * @since 4.3.0
+ *
+ * @return string
+ */
+ public function get_auth_url() {
+
+ $settings_url = rawurlencode( ( new ConnectionSettings( $this->connection ) )->get_admin_page_url() );
+
+ return $this->get_client()->get_auth_url(
+ [
+ 'return' => self::get_plugin_auth_url( $this->connection ),
+ 'plugin_settings_url' => $settings_url,
+ ]
+ );
+ }
+
+ /**
+ * Get user information (like email etc) that is associated with the current OAuth connection.
+ *
+ * @since 4.3.0
+ *
+ * @return array
+ */
+ public function get_user_info() {
+
+ return $this->connection_options->get( $this->mailer_slug, 'one_click_setup_user_details' );
+ }
+
+ /**
+ * Whether client credentials are saved.
+ *
+ * We don't have any settings for One-Click Setup, so it's always `true`.
+ *
+ * @since 4.3.0
+ *
+ * @return true
+ */
+ public function is_clients_saved() {
+
+ return true;
+ }
+
+ /**
+ * Whether auth is required.
+ *
+ * @since 4.3.0
+ *
+ * @return bool
+ */
+ public function is_auth_required() {
+
+ $credentials = $this->options['one_click_setup_credentials'] ?? [];
+
+ return empty( $credentials['access_token'] ) || empty( $credentials['refresh_token'] );
+ }
+
+ /**
+ * Whether reauthorization is required.
+ *
+ * @since 4.3.0
+ *
+ * @return bool
+ */
+ public function is_reauth_required() {
+
+ if ( $this->is_auth_required() ) {
+ return false;
+ }
+
+ if (
+ isset( $this->options['one_click_setup_status'] ) &&
+ $this->options['one_click_setup_status'] === 'reauth'
+ ) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Set auth status.
+ *
+ * @since 4.3.0
+ *
+ * @param string $status Status name (active or reauth).
+ */
+ private function set_auth_status( $status ) {
+
+ $all = $this->connection_options->get_all();
+
+ // To save in DB.
+ $all[ $this->mailer_slug ]['one_click_setup_status'] = $status;
+
+ // To save in currently retrieved options array.
+ $this->options['one_click_setup_status'] = $status;
+
+ $this->connection_options->set( $all, false, true );
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Outlook/OneClick/Auth/Client.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Outlook/OneClick/Auth/Client.php
new file mode 100644
index 00000000..a1da75f8
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Outlook/OneClick/Auth/Client.php
@@ -0,0 +1,206 @@
+site_url = $site_url ? $site_url : site_url();
+ }
+
+ /**
+ * Get API base URL.
+ *
+ * @since 4.3.0
+ *
+ * @return string
+ */
+ public static function get_api_base_url() {
+
+ return defined( 'WPMS_OUTLOOK_ONE_CLICK_SETUP_AUTH_URL' ) ? WPMS_OUTLOOK_ONE_CLICK_SETUP_AUTH_URL : self::API_BASE_URL;
+ }
+
+ /**
+ * Get authorization URL.
+ *
+ * @since 4.3.0
+ *
+ * @param array $args List of arguments.
+ *
+ * @return string
+ */
+ public function get_auth_url( $args ) {
+
+ $url = self::get_api_base_url() . 'authorize/';
+
+ $args = wp_parse_args(
+ $args,
+ [
+ 'version' => WPMS_PLUGIN_VER,
+ 'siteurl' => $this->site_url,
+ 'license' => wp_mail_smtp()->get_license_key(),
+ ]
+ );
+
+ if ( ! empty( $args['return'] ) ) {
+ $args['return'] = rawurlencode( $args['return'] );
+ }
+
+ return add_query_arg( $args, $url );
+ }
+
+ /**
+ * Get access and refresh tokens.
+ *
+ * @since 4.3.0
+ *
+ * @param string $authorization_code Authorization code.
+ *
+ * @throws Exception On request error.
+ *
+ * @return array
+ */
+ public function get_tokens( $authorization_code ) {
+
+ $response = $this->request(
+ 'token/',
+ [
+ 'authorization_code' => $authorization_code,
+ ],
+ 'POST'
+ );
+
+ if ( $response->has_errors() ) {
+ throw new Exception( $response->get_errors()->get_error_message(), $response->get_status_code() );
+ }
+
+ return $response->get_body();
+ }
+
+ /**
+ * Refresh access token.
+ *
+ * @since 4.3.0
+ *
+ * @param string $refresh_token Refresh token.
+ *
+ * @throws Exception On request error.
+ *
+ * @return array
+ */
+ public function refresh_access_token( $refresh_token ) {
+
+ $response = $this->request(
+ 'refresh-token/',
+ [
+ 'refresh_token' => $refresh_token,
+ ],
+ 'POST'
+ );
+
+ if ( $response->has_errors() ) {
+ throw new Exception( $response->get_errors()->get_error_message(), $response->get_status_code() );
+ }
+
+ return $response->get_body();
+ }
+
+ /**
+ * Disconnect.
+ *
+ * @since 4.3.0
+ *
+ * @return void
+ */
+ public function disconnect() {
+
+ $this->request(
+ 'disconnect/',
+ [
+ 'siteurl' => $this->site_url,
+ ],
+ 'POST'
+ );
+ }
+
+ /**
+ * Make a request.
+ *
+ * @since 4.3.0
+ *
+ * @param string $route Endpoint name.
+ * @param array $args List of arguments.
+ * @param string $method Request method.
+ *
+ * @return Response
+ */
+ private function request( $route, $args, $method = 'GET' ) {
+
+ $body = wp_parse_args(
+ $args,
+ [
+ 'version' => WPMS_PLUGIN_VER,
+ 'siteurl' => $this->site_url,
+ 'license' => wp_mail_smtp()->get_license_key(),
+ ]
+ );
+
+ /**
+ * Allow modifying request arguments.
+ *
+ * @since 4.3.0
+ *
+ * @param array $args List of args.
+ */
+ $request_args = apply_filters(
+ 'wp_mail_smtp_pro_providers_outlook_one_click_auth_client_request_send_args',
+ [
+ 'headers' => [
+ 'Content-Type' => 'application/json',
+ 'Accept' => 'application/json',
+ 'Cache-Control' => 'no-store, max-age=0',
+ ],
+ 'method' => $method,
+ 'body' => wp_json_encode( $body ),
+ 'timeout' => 30,
+ 'user-agent' => Helpers::get_default_user_agent(),
+ ]
+ );
+
+ $response = wp_remote_request( self::get_api_base_url() . $route, $request_args );
+
+ return new Response( $response );
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Outlook/OneClick/Auth/Response.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Outlook/OneClick/Auth/Response.php
new file mode 100644
index 00000000..4c5c7af4
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Outlook/OneClick/Auth/Response.php
@@ -0,0 +1,117 @@
+response = $response;
+ }
+
+ /**
+ * Get response body.
+ *
+ * @since 4.3.0
+ *
+ * @return string|array
+ */
+ public function get_body() {
+
+ $body = wp_remote_retrieve_body( $this->response );
+
+ if ( ! empty( $body ) && is_string( $body ) ) {
+ $body = json_decode( $body, true );
+ }
+
+ return $body;
+ }
+
+ /**
+ * Get response status code.
+ *
+ * @since 4.3.0
+ *
+ * @return int
+ */
+ public function get_status_code() {
+
+ return (int) wp_remote_retrieve_response_code( $this->response );
+ }
+
+ /**
+ * Get response errors.
+ *
+ * @since 4.3.0
+ *
+ * @return string|WP_Error
+ */
+ public function get_errors() {
+
+ if ( ! $this->has_errors() ) {
+ return '';
+ }
+
+ if ( is_wp_error( $this->response ) ) {
+ return $this->response;
+ }
+
+ $body = $this->get_body();
+
+ if ( ! empty( $body['error'] ) ) {
+ return new WP_Error( $this->get_status_code(), $body['error'] );
+ }
+
+ return new WP_Error( $this->get_status_code(), WP::wp_remote_get_response_error_message( $this->response ) );
+ }
+
+ /**
+ * Whether response has errors.
+ *
+ * @since 4.3.0
+ *
+ * @return bool
+ */
+ public function has_errors() {
+
+ return $this->get_status_code() !== 200;
+ }
+
+ /**
+ * Get response header.
+ *
+ * @since 4.3.0
+ *
+ * @param string $header Header name.
+ *
+ * @return string
+ */
+ public function get_header( $header ) {
+
+ return wp_remote_retrieve_header( $this->response, $header );
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Outlook/OneClick/Options.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Outlook/OneClick/Options.php
new file mode 100644
index 00000000..c8c3878e
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Outlook/OneClick/Options.php
@@ -0,0 +1,305 @@
+manual_setup_options = new ManualSetupOptions( $connection );
+
+ parent::__construct(
+ [
+ 'logo_url' => $this->manual_setup_options->get_logo_url(),
+ 'slug' => self::SLUG,
+ 'title' => $this->manual_setup_options->get_title(),
+ 'description' => $this->manual_setup_options->get_description(),
+ 'notices' => $this->manual_setup_options->get_notices(),
+ 'php' => $this->manual_setup_options->get_php_version(),
+ 'supports' => $this->manual_setup_options->get_supports(),
+ ],
+ $connection
+ );
+ }
+
+ /**
+ * Output the mailer provider options.
+ *
+ * @since 4.3.0
+ */
+ public function display_options() {
+
+ // Do not display options if PHP version is not correct.
+ if ( ! $this->is_php_correct() ) {
+ $this->display_php_warning();
+
+ return;
+ }
+
+ $one_click_setup_enabled = $this->connection_options->get( 'outlook', 'one_click_setup_enabled' );
+ ?>
+
+
+ +
++ +
+ +
+ get_pro()->get_license(); + $one_click_setup_enabled = $this->connection_options->get( 'outlook', 'one_click_setup_enabled' ); + ?> + is_expired() ) : ?> + +
+ Renew your license and reconnect your authorization.', 'wp-mail-smtp-pro' ), + [ + 'a' => [ + 'href' => [], + 'target' => [], + 'rel' => [], + ], + ] + ), + esc_url( + $license->get_renewal_link( + [ + 'medium' => 'outlook-one-click-setting-notice', + 'content' => 'Renew your license', + ] + ) + ) + ); + ?> +
+ +
+ +
+ + is_valid() ) : ?> + +
+ +
+ +
+ +
+
+
+ process_provider_remove();
+
+ $auth = new Auth( $this->connection );
+ $license = wp_mail_smtp()->get_pro()->get_license();
+ ?>
+ connection->get_mailer_slug() === self::SLUG ) : ?>
+ is_auth_required() ) : ?>
+
+
' . esc_html( $user['name'] . ' <' . $user['email'] . '>' ) . ''
+ );
+ }
+ ?>
+ +
+ +
+ +
' . esc_html( $user['name'] . ' <' . $user['email'] . '>' ) . ''
+ );
+ }
+ ?>
+ +
+ +
+
+ +
+ + +
+ +
+
+ connection ) )->get_admin_page_url(), 'outlook_one_click_setup_remove', 'outlook_one_click_setup_remove_nonce' ) . '#wp-mail-smtp-setting-row-outlook-one-click-setup-authorize';
+ }
+
+ /**
+ * Remove Provider OAuth connection.
+ *
+ * @since 4.3.0
+ */
+ public function process_provider_remove() {
+
+ if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_global_options() ) ) {
+ return;
+ }
+
+ if (
+ ! isset( $_GET['outlook_one_click_setup_remove_nonce'] ) ||
+ ! wp_verify_nonce( sanitize_key( $_GET['outlook_one_click_setup_remove_nonce'] ), 'outlook_one_click_setup_remove' )
+ ) {
+ return;
+ }
+
+ if ( $this->connection->get_mailer_slug() !== $this->get_slug() ) {
+ return;
+ }
+
+ $old_opt = $this->connection_options->get_all_raw();
+
+ unset( $old_opt[ $this->get_slug() ]['one_click_setup_credentials'] );
+ unset( $old_opt[ $this->get_slug() ]['one_click_setup_user_details'] );
+
+ $this->connection_options->set( $old_opt );
+
+ ( new Auth( $this->connection ) )->get_client()->disconnect();
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Outlook/Options.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Outlook/Options.php
new file mode 100644
index 00000000..79f79d38
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Outlook/Options.php
@@ -0,0 +1,262 @@
+ wp_mail_smtp()->assets_url . '/images/providers/microsoft.svg',
+ 'slug' => self::SLUG,
+ 'title' => esc_html__( '365 / Outlook', 'wp-mail-smtp-pro' ),
+ 'description' => sprintf(
+ wp_kses( /* translators: %s - URL to Outlook doc. */
+ __( 'The Microsoft 365 / Outlook mailer is a great choice if you already use Microsoft\'s email services (Outlook, Office 365, Microsoft 365, or Hotmail). Due to the fairly complex manual Microsoft App configuration, we recommend the One-Click Setup, which will get you up and running in just a few seconds.
To get started, read our Microsoft 365 / Outlook documentation.', 'wp-mail-smtp-pro' ),
+ [
+ 'br' => [],
+ 'a' => [
+ 'href' => [],
+ 'rel' => [],
+ 'target' => [],
+ ],
+ ]
+ ),
+ esc_url( wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/docs/how-to-set-up-the-outlook-mailer-in-wp-mail-smtp/', 'Microsoft 365 / Outlook documentation' ) )
+ ),
+ 'php' => '5.6',
+ 'supports' => [
+ 'from_email' => true,
+ 'from_name' => true,
+ 'return_path' => false,
+ 'from_email_force' => true,
+ 'from_name_force' => true,
+ ],
+ ],
+ $connection
+ );
+ }
+
+ /**
+ * Output the mailer provider options.
+ *
+ * @since 1.5.0
+ */
+ public function display_options() {
+
+ // Do not display options if PHP version is not correct.
+ if ( ! $this->is_php_correct() ) {
+ $this->display_php_warning();
+
+ return;
+ }
+
+ // Do not display options if there is no SSL certificate on a site.
+ if ( ! is_ssl() ) {
+ $this->display_ssl_warning();
+
+ return;
+ }
+ ?>
+
+
+
+ + +
+ + +
+
+
+ Redirect URIs" web field for your application on Microsoft Azure site for your project there.', 'wp-mail-smtp-pro' ); ?>
+
+ + +
+ + process_provider_remove(); + + $auth = new Auth( $this->connection ); + ?> + + is_clients_saved() ) : ?> + + is_auth_required() ) : ?> + + + + +
+ +
+
+
+
+
+
+
+
+ connection_options->get( $this->get_slug(), 'user_details' );
+
+ if ( ! empty( $user['email'] ) && ! empty( $user['display_name'] ) ) {
+ printf(
+ /* translators: %s - Display name and email, as received from oAuth provider. */
+ esc_html__( 'Connected as %s', 'wp-mail-smtp-pro' ),
+ '' . esc_html( $user['display_name'] . ' <' . $user['email'] . '>' ) . ''
+ );
+ }
+ ?>
+
+
+ +
+
+ +
+ + + + + +
+ +
+ + get_capability_manage_global_options() ) ) { + return; + } + + if ( + ! isset( $_GET['outlook_remove_nonce'] ) || + ! wp_verify_nonce( sanitize_key( $_GET['outlook_remove_nonce'] ), 'outlook_remove' ) + ) { + return; + } + + if ( $this->connection->get_mailer_slug() !== $this->get_slug() ) { + return; + } + + $old_opt = $this->connection_options->get_all_raw(); + + unset( $old_opt[ $this->get_slug() ]['auth_code'] ); + unset( $old_opt[ $this->get_slug() ]['access_token'] ); + unset( $old_opt[ $this->get_slug() ]['refresh_token'] ); + unset( $old_opt[ $this->get_slug() ]['user_details'] ); + unset( $old_opt[ $this->get_slug() ]['scopes'] ); + + $this->connection_options->set( $old_opt ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Outlook/Provider.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Outlook/Provider.php new file mode 100644 index 00000000..b8e21126 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Outlook/Provider.php @@ -0,0 +1,298 @@ +get_capability_manage_global_options() ) ) { + return $connection; + } + + if ( + $connection instanceof ConnectionInterface && + $connection->get_mailer_slug() === 'outlook' + ) { + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $one_click_setup_enabled = isset( $_GET['one_click_setup'] ); + + $connection->get_options()->set( [ 'outlook' => [ 'one_click_setup_enabled' => $one_click_setup_enabled ] ], false, false ); + } + + return $connection; + }, + PHP_INT_MAX + ); + } + + /** + * Process settings data before save on settings page. + * + * Mainly used to convert checkbox value to boolean. + * + * @since 4.3.0 + * + * @param array $data Connection data. + * @param array $old_data Old connection data. + * + * @return array + */ + public function process_settings_data( $data, $old_data ) { + + $data['outlook']['one_click_setup_enabled'] = isset( $data['outlook']['one_click_setup_enabled'] ); + + return $data; + } + + /** + * Set options defaults before they were saved. + * + * @since 4.3.0 + * + * @param mixed $value Option value. + * @param string $group Group key. + * @param string $key Option key. + * + * @return mixed + */ + public function options_defaults( $value, $group, $key ) { + + if ( $group === 'outlook' && $key === 'one_click_setup_enabled' ) { + $value = false; + } + + return $value; + } + + /** + * Sanitize options before save. + * + * @since 4.3.0 + * + * @param array $options Currently processed options passed to a filter hook. + * + * @return array + */ + public function sanitize_options( $options ) { + + if ( ! isset( $options['outlook']['one_click_setup_enabled'] ) ) { + // Disabled by default. + $options['outlook']['one_click_setup_enabled'] = false; + + return $options; + } + + $options['outlook']['one_click_setup_enabled'] = (bool) $options['outlook']['one_click_setup_enabled']; + + return $options; + } + + /** + * Swap to One-Click Setup if it's enabled. + * + * @since 4.3.0 + * + * @param mixed $entity Entity object. + * @param string $provider Provider name. + * @param string $request Entity name. + * @param array $args Entity arguments. + * + * @return mixed + */ + public function swap_to_one_click_setup( $entity, $provider, $request, $args ) { + + if ( $provider === 'outlook' ) { + $connection = null; + $one_click_provider_path = 'WPMailSMTP\Pro\Providers\Outlook\OneClick\\'; + + if ( $request === 'Mailer' && isset( $args[1] ) ) { + $connection = $args[1]; + } elseif ( ( $request === 'Auth' || $request === 'Options' ) && isset( $args[0] ) ) { + $connection = $args[0]; + } + + if ( is_null( $connection ) ) { + $connection = wp_mail_smtp()->get_connections_manager()->get_primary_connection(); + } + + if ( + $connection instanceof ConnectionInterface && + class_exists( $one_click_provider_path . $request ) && + ( + $connection->get_options()->get( 'outlook', 'one_click_setup_enabled' ) === true || + $request === 'Options' + ) + ) { + $class = $one_click_provider_path . $request; + $entity = new $class( ...$args ); + } + } + + return $entity; + } + + /** + * AJAX callback for removing the oAuth authorization connection. + * + * Currently used only for Setup Wizard and works only with primary connection. + * + * @since 4.3.0 + */ + public function remove_oauth_connection() { + + check_ajax_referer( 'wpms-admin-nonce', 'nonce' ); + + if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_global_options() ) ) { + wp_send_json_error(); + } + + $options = Options::init(); + $old_opt = $options->get_all_raw(); + + unset( $old_opt['outlook']['one_click_setup_credentials'] ); + unset( $old_opt['outlook']['one_click_setup_user_details'] ); + + $options->set( $old_opt ); + + wp_send_json_success(); + } + + /** + * Display notices. + * + * @since 3.11.0 + */ + public function display_notices() { + + $options = Options::init(); + + if ( + $options->get( 'mail', 'mailer' ) === 'outlook' && + $options->get( 'outlook', 'one_click_setup_enabled' ) === true + ) { + $auth = new AuthOneClick(); + $license = wp_mail_smtp()->get_pro()->get_license(); + + if ( ! $auth->is_auth_required() && $license->is_expired() ) { // Already authorized and license expired. + ?> +
+
+ Renew your license and reconnect your authorization.', 'wp-mail-smtp-pro' ),
+ [
+ 'a' => [
+ 'href' => [],
+ 'target' => [],
+ 'rel' => [],
+ ],
+ ]
+ ),
+ esc_url(
+ $license->get_renewal_link(
+ [
+ 'medium' => 'outlook-one-click-global-notice',
+ 'content' => 'Renew your license',
+ ]
+ )
+ )
+ );
+ ?>
+
+ is_auth_required() && ! $license->is_valid() ) { // Already authorized and license is not valid. + ?> +
+
+ Verify your license and reconnect your authorization.', 'wp-mail-smtp-pro' ),
+ [
+ 'a' => [
+ 'href' => [],
+ ],
+ ]
+ ),
+ esc_url( wp_mail_smtp()->get_admin()->get_admin_page_url() )
+ );
+ ?>
+
+ is_reauth_required() ) { // Reauthorization required. + ?> +
+
+ reconnect your authorization.', 'wp-mail-smtp-pro' ),
+ [
+ 'a' => [
+ 'href' => [],
+ ],
+ ]
+ ),
+ esc_url( wp_mail_smtp()->get_admin()->get_admin_page_url() . '#wp-mail-smtp-setting-row-outlook-one-click-setup-authorize' )
+ );
+ ?>
+
+ hooks();
+ ( new OutlookProvider() )->hooks();
+ }
+
+ /**
+ * Inject own Pro providers.
+ *
+ * @since 1.5.0
+ * @since 2.3.0 Added Zoho mailer.
+ *
+ * @param array $providers The default providers.
+ *
+ * @return array
+ */
+ public function inject_providers( $providers ) {
+
+ $providers['amazonses'] = 'WPMailSMTP\Pro\Providers\AmazonSES\\';
+ $providers['outlook'] = 'WPMailSMTP\Pro\Providers\Outlook\\';
+ $providers['zoho'] = 'WPMailSMTP\Pro\Providers\Zoho\\';
+
+ return $providers;
+ }
+
+ /**
+ * Complete the auth process for the Provider.
+ * Currently used for Microsoft Outlook only.
+ *
+ * @since 1.5.0
+ */
+ public function process_auth_code() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
+
+ // Bail if the auth request is not allowed.
+ if ( ! $this->allow_auth_request() ) {
+ return;
+ }
+
+ // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated
+ $state = sanitize_key( $_GET['state'] );
+ $state = str_replace( 'wp-mail-smtp-', '', $state );
+
+ list( $nonce, $connection_id ) = array_pad( explode( '-', $state ), 2, false );
+
+ if ( empty( $state ) || empty( $nonce ) || empty( $connection_id ) ) {
+ wp_safe_redirect(
+ add_query_arg( 'error', 'oauth_invalid_state', wp_mail_smtp()->get_admin()->get_admin_page_url() )
+ );
+ exit;
+ }
+
+ $connection = wp_mail_smtp()->get_connections_manager()->get_connection( $connection_id, false );
+
+ if ( $connection === false ) {
+ wp_safe_redirect(
+ add_query_arg( 'error', 'oauth_invalid_connection', wp_mail_smtp()->get_admin()->get_admin_page_url() )
+ );
+ exit;
+ }
+
+ $auth = new OutlookAuth( $connection );
+
+ $redirect_url = ( new ConnectionSettings( $connection ) )->get_admin_page_url();
+ $outlook_options = $connection->get_options()->get_group( 'outlook' );
+ $is_setup_wizard_auth = ! empty( $outlook_options['is_setup_wizard_auth'] );
+
+ if ( $is_setup_wizard_auth ) {
+ $auth->update_is_setup_wizard_auth( false );
+
+ $redirect_url = SetupWizard::get_site_url() . '#/step/configure_mailer/outlook';
+ }
+
+ if ( ! wp_verify_nonce( $nonce, $auth->state_key ) ) {
+ $url = add_query_arg( 'error', 'microsoft_invalid_nonce', $redirect_url );
+ } elseif ( isset( $_GET['error'] ) && isset( $_GET['error_description'] ) ) {
+ $error_code = sanitize_text_field( wp_unslash( $_GET['error'] ) );
+ $error_message = sanitize_text_field( wp_unslash( $_GET['error_description'] ) );
+
+ $event_id = DebugEvents::add( 'Mailer: Outlook' . WP::EOL . Helpers::format_error_message( $error_message, $error_code ) );
+
+ $url = add_query_arg(
+ [
+ 'error' => 'microsoft_unsuccessful_oauth',
+ 'debug_event_id' => $event_id,
+ ],
+ $redirect_url
+ );
+ } elseif ( ! isset( $_GET['code'] ) ) {
+ $url = add_query_arg( 'error', 'microsoft_no_code', $redirect_url );
+ } else {
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput
+ $code = preg_replace( '/[^a-zA-Z0-9_\-.]/', '', $_GET['code'] );
+
+ $result = $auth->process_auth( $code );
+
+ if ( $result instanceof WP_Error ) {
+ $event_id = DebugEvents::add( 'Mailer: Outlook' . WP::EOL . $result->get_error_message() );
+
+ $url = add_query_arg(
+ [
+ 'error' => 'microsoft_unsuccessful_oauth',
+ 'debug_event_id' => $event_id,
+ ],
+ $redirect_url
+ );
+ } else {
+ $url = add_query_arg( 'success', 'microsoft_site_linked', $redirect_url );
+
+ // Switch off one-click setup.
+ $connection->get_options()->set( [ 'outlook' => [ 'one_click_setup_enabled' => false ] ], false, false );
+ }
+ }
+
+ wp_safe_redirect( $url );
+ exit;
+ }
+
+ /**
+ * Whether we allow the auth request to be processed.
+ *
+ * @since 3.0.0
+ *
+ * @return bool
+ */
+ private function allow_auth_request() {
+
+ if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_global_options() ) ) {
+ return false;
+ }
+
+ // Ajax is not supported.
+ if ( WP::is_doing_ajax() ) {
+ return false;
+ }
+
+ // We should be coming from somewhere.
+ if ( empty( $_SERVER['HTTP_REFERER'] ) ) {
+ return false;
+ }
+
+ // We should have a required GET data.
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+ if ( ! isset( $_GET['state'] ) ) {
+ return false;
+ }
+
+ $state = sanitize_key( $_GET['state'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+
+ // Check whether state contains "wp-mail-smtp-".
+ if ( substr( $state, 0, 13 ) !== 'wp-mail-smtp-' ) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Inject Pro features specific assets: CSS & JS.
+ *
+ * @since 1.5.0
+ */
+ public function enqueue_assets() {
+
+ // CSS.
+ wp_enqueue_style(
+ 'wp-mail-smtp-admin-pro-settings',
+ wp_mail_smtp()->pro->assets_url . '/css/smtp-pro-settings.min.css',
+ [ 'wp-mail-smtp-admin' ],
+ WPMS_PLUGIN_VER,
+ false
+ );
+
+ /*
+ * JavaScript.
+ */
+ wp_enqueue_script(
+ 'wp-mail-smtp-admin-pro-settings',
+ wp_mail_smtp()->pro->assets_url . '/js/smtp-pro-settings' . WP::asset_min() . '.js',
+ [ 'jquery', 'wp-mail-smtp-admin-jconfirm' ],
+ WPMS_PLUGIN_VER,
+ false
+ );
+
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+ $connection_id = isset( $_GET['connection_id'] ) ? sanitize_key( $_GET['connection_id'] ) : 'primary';
+
+ wp_localize_script(
+ 'wp-mail-smtp-admin-pro-settings',
+ 'wp_mail_smtp_pro',
+ [
+ 'ses_text_sending' => esc_html__( 'Sending...', 'wp-mail-smtp-pro' ),
+ 'ses_text_loading' => esc_html__( 'Loading...', 'wp-mail-smtp-pro' ),
+ 'ses_text_sent' => esc_html__( 'Sent', 'wp-mail-smtp-pro' ),
+ 'ses_text_resend' => esc_html__( 'Resend', 'wp-mail-smtp-pro' ),
+ 'ses_text_email_delete' => esc_html__( 'Are you sure you want to delete this email address? You will need to add and verify it again if you want to use it in the future.', 'wp-mail-smtp-pro' ),
+ 'ses_text_smth_wrong' => esc_html__( 'Something went wrong, please reload the page and try again.', 'wp-mail-smtp-pro' ),
+ 'ses_text_email_invalid' => esc_html__( 'Please make sure that the email address is valid.', 'wp-mail-smtp-pro' ),
+ 'ok' => esc_html__( 'OK', 'wp-mail-smtp-pro' ),
+ 'plugin_url' => esc_url( wp_mail_smtp()->plugin_url ),
+ 'icon' => esc_html__( 'Icon', 'wp-mail-smtp-pro' ),
+ 'error_occurred' => esc_html__( 'An error occurred!', 'wp-mail-smtp-pro' ),
+ 'ses_text_resend_failed' => esc_html__( 'Resend failed!', 'wp-mail-smtp-pro' ),
+ 'ses_text_cancel' => esc_html__( 'Cancel', 'wp-mail-smtp-pro' ),
+ 'ses_text_close' => esc_html__( 'Close', 'wp-mail-smtp-pro' ),
+ 'ses_text_yes' => esc_html__( 'Yes', 'wp-mail-smtp-pro' ),
+ 'ses_text_done' => esc_html__( 'Done', 'wp-mail-smtp-pro' ),
+ 'ses_text_domain_delete' => esc_html__( 'Are you sure you want to delete this domain? You will need to add and verify it again if you want to use it in the future.', 'wp-mail-smtp-pro' ),
+ 'ses_text_dns_dkim_title' => esc_html__( 'Add verified domain', 'wp-mail-smtp-pro' ),
+ 'ses_text_no_identities' => esc_html__( 'The AWS SES identities could not load because of an error.', 'wp-mail-smtp-pro' ),
+ 'ses_add_identity_modal_content' => SESOptions::prepare_add_new_identity_content(),
+ 'ses_add_identity_modal_title' => SESOptions::prepare_add_new_identity_title(),
+ 'loader_white_small' => wp_mail_smtp()->prepare_loader( 'white', 'sm' ),
+ 'nonce' => wp_create_nonce( 'wp-mail-smtp-pro-admin' ),
+ 'text_heads_up_title' => esc_html__( 'Heads up!', 'wp-mail-smtp-pro' ),
+ 'text_yes' => esc_html__( 'Yes', 'wp-mail-smtp-pro' ),
+ 'text_yes_delete' => esc_html__( 'Yes, Delete', 'wp-mail-smtp-pro' ),
+ 'text_cancel' => esc_html__( 'Cancel', 'wp-mail-smtp-pro' ),
+ 'text_delete_connection' => esc_html__( 'You\'re about to delete a connection. Are you sure you want to proceed?', 'wp-mail-smtp-pro' ),
+ 'text_delete_backup_connection' => esc_html__( 'You\'re about to delete your Backup Connection. Are you sure you want to proceed?', 'wp-mail-smtp-pro' ),
+ 'text_delete_smart_routing_connection' => esc_html__( 'You\'re about to delete a connection that is used in Smart Routing. Are you sure you want to proceed? You will need to reconfigure your Smart Routing rules.', 'wp-mail-smtp-pro' ),
+ 'text_switch_witch_primary_connection' => esc_html__( 'Are you sure you want to use this additional connection as your new primary connection? Your existing primary connection will be moved to the additional connections list.', 'wp-mail-smtp-pro' ),
+ 'connection_id' => $connection_id,
+ // WhatsApp alerts data.
+ 'whatsapp_text_checking' => esc_html__( 'Rechecking...', 'wp-mail-smtp-pro' ),
+ 'whatsapp_text_check_error' => esc_html__( 'Error rechecking status', 'wp-mail-smtp-pro' ),
+ 'whatsapp_text_recheck' => esc_html__( 'Recheck Status', 'wp-mail-smtp-pro' ),
+ ]
+ );
+ }
+
+ /**
+ * Process AJAX requests fired by a pro version of a plugin and related to providers.
+ * Currently, only AmazonSES has some AJAX.
+ * So we will hard-code this behavior for now.
+ *
+ * @since 1.5.0
+ * @since 2.4.0 Major changes (AWS SDK and domain verification changes).
+ */
+ public function process_ajax() {
+
+ $generic_error = esc_html__( 'Something went wrong. Please try again later.', 'wp-mail-smtp-pro' );
+
+ if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_global_options() ) ) {
+ wp_send_json_error( esc_html__( 'You don\'t have the permission to perform this action.', 'wp-mail-smtp-pro' ) );
+ }
+
+ // Verify nonce existence. Actual nonce verification happens below.
+ if ( ! isset( $_POST['nonce'] ) ) {
+ wp_send_json_error( $generic_error );
+ }
+
+ $mailer = isset( $_POST['mailer'] ) ? sanitize_key( $_POST['mailer'] ) : '';
+
+ if ( $mailer !== 'amazonses' ) {
+ wp_send_json_error( $generic_error );
+ }
+
+ $connection_id = isset( $_POST['connection_id'] ) ? sanitize_key( $_POST['connection_id'] ) : false;
+ $task = isset( $_POST['task'] ) ? sanitize_key( $_POST['task'] ) : '';
+
+ $connection = wp_mail_smtp()->get_connections_manager()->get_connection( $connection_id, false );
+
+ if ( $connection === false ) {
+ wp_send_json_error( $generic_error );
+ }
+
+ switch ( $task ) {
+ case 'load_ses_identities':
+ if ( ! wp_verify_nonce( sanitize_key( $_POST['nonce'] ), 'wp_mail_smtp_pro_amazonses_load_ses_identities' ) ) {
+ wp_send_json_error( $generic_error );
+ }
+
+ $ses_options = wp_mail_smtp()->get_providers()->get_options( SESOptions::SLUG, $connection );
+
+ wp_send_json_success( $ses_options->prepare_ses_identities_content() );
+
+ break;
+
+ case 'identity_registration':
+ if ( ! wp_verify_nonce( sanitize_key( $_POST['nonce'] ), 'wp_mail_smtp_pro_amazonses_register_identity' ) ) {
+ wp_send_json_error( $generic_error );
+ }
+
+ $type = isset( $_POST['type'] ) ? sanitize_text_field( wp_unslash( $_POST['type'] ) ) : '';
+ $value = isset( $_POST['value'] ) ? sanitize_text_field( wp_unslash( $_POST['value'] ) ) : '';
+
+ if ( $type === 'email' && ! is_email( $value ) ) {
+ wp_send_json_error( esc_html__( 'Please provide a valid email address.', 'wp-mail-smtp-pro' ) );
+ } elseif ( $type === 'domain' && empty( $value ) ) {
+ wp_send_json_error( esc_html__( 'Please provide a domain name.', 'wp-mail-smtp-pro' ) );
+ }
+
+ $ses = new SESAuth( $connection );
+
+ // Verify domain for easier conditional checking below.
+ $domain_dkim_tokens = ( $type === 'domain' ) ? $ses->do_verify_domain_dkim( $value ) : '';
+
+ if ( $type === 'email' && $ses->do_verify_email( $value ) === true ) {
+ wp_send_json_success(
+ sprintf(
+ wp_kses( /* translators: %s - email address. */
+ __( 'Please check inbox of
+
+
+
+
+
+
+
+
+ %s %s
+
+
+
+
+ ' .
+ esc_html__( 'Get API Key', 'wp-mail-smtp' ) .
+ ''
+ );
+ ?>
+
+
+
+
+
+
+
+ Follow these instructions and then select the From Email at the top of this page.', 'wp-mail-smtp' ),
+ [
+ 'a' => [
+ 'href' => [],
+ 'rel' => [],
+ 'target' => [],
+ ],
+ ]
+ ),
+ esc_url( wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/gmail-send-from-alias-wp-mail-smtp/', 'Gmail aliases description - Follow these instructions' ) )
+ );
+ ?>
+
+
+
+
+
+
+
+ ' .
+ esc_html__( 'Get API Key', 'wp-mail-smtp' ) .
+ ''
+ );
+ ?>
+
+ ' .
+ esc_html__( 'pricing page', 'wp-mail-smtp' ) .
+ ''
+ );
+ ?>
+
+ get a Mailgun API Key. Generate a key in the "Mailgun API Keys" section.', 'wp-mail-smtp' ),
+ 'https://app.mailgun.com/settings/api_security'
+ ),
+ [
+ 'a' => [
+ 'href' => [],
+ 'rel' => [],
+ 'target' => [],
+ ],
+ ]
+ );
+ ?>
+
+ ' .
+ esc_html__( 'Get a Domain Name', 'wp-mail-smtp' ) .
+ ''
+ );
+ ?>
+
+
+ ' .
+ esc_html__( 'API Key Management', 'wp-mail-smtp' ) .
+ ''
+ );
+ ?>
+
+ ' .
+ esc_html__( 'API Key Management', 'wp-mail-smtp' ) .
+ ''
+ );
+ ?>
+
+ ' .
+ esc_html__( 'Get API Key', 'wp-mail-smtp' ) .
+ ''
+ );
+ ?>
+
+
+ %s address for a verification email.', 'wp-mail-smtp-pro' ),
+ [ 'code' => [] ]
+ ),
+ esc_html( $value )
+ )
+ );
+ } elseif ( $type === 'domain' && ! empty( $domain_dkim_tokens ) ) {
+ wp_send_json_success(
+ SESOptions::prepare_domain_dkim_records_notice( $value, $domain_dkim_tokens, $connection )
+ );
+ } else {
+ $error = $ses->get_last_error();
+
+ wp_send_json_error(
+ $error instanceof WP_Error ? esc_html( $error->get_error_message() ) : $generic_error
+ );
+ }
+
+ break;
+
+ case 'identity_delete':
+ if ( ! wp_verify_nonce( sanitize_key( $_POST['nonce'] ), 'wp_mail_smtp_pro_amazonses_identity_delete' ) ) {
+ wp_send_json_error( $generic_error );
+ }
+
+ $type = isset( $_POST['type'] ) ? sanitize_text_field( wp_unslash( $_POST['type'] ) ) : '';
+ $value = isset( $_POST['value'] ) ? sanitize_text_field( wp_unslash( $_POST['value'] ) ) : '';
+
+ if ( $type === 'email' && ! is_email( $value ) ) {
+ wp_send_json_error( esc_html__( 'Please provide a valid email address.', 'wp-mail-smtp-pro' ) );
+ } elseif ( $type === 'domain' && empty( $value ) ) {
+ wp_send_json_error( esc_html__( 'Please provide a domain name.', 'wp-mail-smtp-pro' ) );
+ }
+
+ $ses = new SESAuth( $connection );
+
+ if ( $ses->do_delete_identity( $value ) === true ) {
+ wp_send_json_success(
+ sprintf(
+ wp_kses(
+ ( $type === 'email' ) ?
+ /* translators: %s - actual email address. */
+ __( 'Email address %s was successfully deleted.', 'wp-mail-smtp-pro' ) :
+ /* translators: %s - actual domain name. */
+ __( 'Domain %s was successfully deleted.', 'wp-mail-smtp-pro' ),
+ [ 'code' => [] ]
+ ),
+ esc_html( $value )
+ )
+ );
+ } else {
+ $error = $ses->get_last_error();
+
+ wp_send_json_error(
+ $error instanceof WP_Error ? esc_html( $error->get_error_message() ) : $generic_error
+ );
+ }
+
+ break;
+
+ case 'load_dns_records':
+ if ( ! wp_verify_nonce( sanitize_key( $_POST['nonce'] ), 'wp_mail_smtp_pro_amazonses_load_dns_records' ) ) {
+ wp_send_json_error( $generic_error );
+ }
+
+ $domain = isset( $_POST['domain'] ) ? sanitize_text_field( wp_unslash( $_POST['domain'] ) ) : '';
+
+ if ( empty( $domain ) ) {
+ wp_send_json_error( esc_html__( 'Please provide a domain name.', 'wp-mail-smtp-pro' ) );
+ }
+
+ $ses = new SESAuth( $connection );
+
+ $dkim_tokens = $ses->get_dkim_tokens( $domain );
+
+ if ( is_wp_error( $dkim_tokens ) ) {
+ wp_send_json_error( esc_html( $dkim_tokens->get_error_message() ) );
+ }
+
+ wp_send_json_success( SESOptions::prepare_domain_dkim_records_notice( $domain, $dkim_tokens, $connection ) );
+
+ break;
+ }
+ }
+
+ /**
+ * Update PHPMailer properties before email send.
+ *
+ * @since 3.5.0
+ *
+ * @param MailCatcherInterface $phpmailer The MailCatcher object.
+ */
+ public function update_php_mailer_properties( $phpmailer ) {
+
+ $mailer = wp_mail_smtp()->get_connections_manager()->get_mail_connection()->get_mailer_slug();
+
+ /*
+ * Switch mailer to "sendmail" for Amazon SES. Since we send MIME message via Amazon API,
+ * we should avoid "mail" mailer limitations. Namely, long headers encoding.
+ */
+ if ( $mailer === 'amazonses' ) {
+ // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
+ $phpmailer->Mailer = 'sendmail';
+ }
+ }
+
+ /**
+ * Display providers related notices.
+ *
+ * @since 3.10.0
+ */
+ public function display_notices() {
+
+ if ( ! wp_mail_smtp()->get_admin()->is_admin_page() ) {
+ return;
+ }
+
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+ $message = isset( $_GET['message'] ) ? sanitize_key( $_GET['message'] ) : '';
+
+ switch ( $message ) {
+ case 'additional_connection_switched_with_primary':
+ WP::add_admin_notice(
+ esc_html__( 'The additional connection has been successfully switched to the primary connection.', 'wp-mail-smtp-pro' ),
+ WP::ADMIN_NOTICE_SUCCESS
+ );
+ }
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Zoho/Auth.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Zoho/Auth.php
new file mode 100644
index 00000000..4e289ccc
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Zoho/Auth.php
@@ -0,0 +1,355 @@
+mailer_slug !== Options::SLUG ) {
+ return;
+ }
+
+ $this->options = $this->connection_options->get_group( $this->mailer_slug );
+
+ $this->get_client();
+ }
+
+ /**
+ * Init and get the OAuth2 Client object.
+ *
+ * @since 2.3.0
+ *
+ * @param bool $force If the client should be forcefully reinitialized.
+ *
+ * @return Zoho
+ */
+ public function get_client( $force = false ) {
+
+ // Doesn't load client twice.
+ if ( ! empty( $this->client ) && ! $force ) {
+ return $this->client;
+ }
+
+ $this->include_vendor_lib();
+
+ $this->client = new Zoho(
+ [
+ 'domain' => $this->options['domain'],
+ 'clientId' => $this->options['client_id'],
+ 'clientSecret' => $this->options['client_secret'],
+ 'redirectUri' => self::get_plugin_auth_url(),
+ 'state' => $this->get_state(),
+ ]
+ );
+
+ // Do not process if we don't have both Client ID & Client password.
+ if ( ! $this->is_clients_saved() ) {
+ return $this->client;
+ }
+
+ if ( ! empty( $this->options['access_token'] ) ) {
+ $access_token = new AccessToken( (array) $this->options['access_token'] );
+
+ // Refresh expired token if needed.
+ if ( $access_token->hasExpired() ) {
+ $this->refresh_access_token( $access_token );
+ }
+ }
+
+ return $this->client;
+ }
+
+ /**
+ * Try to get an access token using the authorization code grant.
+ *
+ * @since 4.9.0
+ *
+ * @param string $code The authorization code returned by Zoho.
+ *
+ * @return WP_Error|true
+ */
+ private function obtain_access_token( $code ) {
+
+ if ( empty( $code ) ) {
+ return true;
+ }
+
+ try {
+ $access_token = $this->client->getAccessToken(
+ 'authorization_code',
+ [
+ 'code' => $code,
+ 'scope' => implode( ',', $this->client->default_scopes ),
+ 'state' => $this->client->getState(),
+ ]
+ );
+
+ $this->update_access_token( $access_token->jsonSerialize() );
+ $this->update_refresh_token( $access_token->getRefreshToken() );
+ $this->update_user_details( $access_token );
+
+ return true;
+ } catch ( \Exception $e ) {
+ return new WP_Error( 'zoho_auth_failed', $e->getMessage() );
+ }
+ }
+
+ /**
+ * Refresh expired access token.
+ *
+ * @since 4.9.0
+ *
+ * @param AccessToken $access_token Expired access token.
+ */
+ private function refresh_access_token( $access_token ) {
+
+ try {
+ $refresh_token = ! empty( $this->options['refresh_token'] ) ? $this->options['refresh_token'] : '';
+ $new_access_token = $this->client->getAccessToken(
+ 'refresh_token',
+ [ 'refresh_token' => $refresh_token ]
+ );
+
+ $this->update_access_token( $new_access_token->jsonSerialize() );
+ } catch ( \Exception $e ) {
+ DebugEvents::add_throttled(
+ 'Mailer: Zoho Mail' . WP::EOL . $e->getMessage(),
+ 'zoho_refresh_error_' . $this->connection->get_id()
+ );
+ }
+ }
+
+ /**
+ * Get the auth URL used to proceed to Provider to request access to send emails.
+ *
+ * @since 2.3.0
+ *
+ * @return string
+ */
+ public function get_auth_url() {
+
+ $client = $this->get_client();
+
+ if ( ! empty( $client ) && $client instanceof Zoho ) {
+ return $client->getAuthorizationUrl();
+ }
+
+ return '#';
+ }
+
+ /**
+ * Get and update user-related details (account ID, display name and email).
+ *
+ * @since 2.3.0
+ *
+ * @param \WPMailSMTP\Vendor\League\OAuth2\Client\Token\AccessTokenInterface $access_token The access token.
+ *
+ * @throws \Exception When plugin from email does not match any Zoho API from emails.
+ */
+ protected function update_user_details( $access_token ) {
+
+ if ( empty( $access_token ) ) {
+ $access_token = new AccessToken( (array) $this->options['access_token'] );
+ }
+
+ // Default values.
+ $user = [
+ 'email' => '',
+ 'display_name' => '',
+ 'account_id' => '',
+ ];
+
+ $connection_options = $this->connection->get_options();
+ $from_email = $connection_options->get( 'mail', 'from_email' );
+
+ try {
+ $resource_owner = $this->get_client()->getResourceOwner( $access_token );
+ $user = $resource_owner->getAvailableSendEmailDetailsByEmail( $from_email );
+
+ if ( empty( $user ) ) {
+ $available_emails = $resource_owner->getAvailableSendEmailDetails();
+ $user = array_values( $available_emails )[0];
+ }
+ } catch ( \Exception $e ) {
+ DebugEvents::add( 'Mailer: Zoho Mail' . WP::EOL . $e->getMessage() );
+ }
+
+ // To save in DB.
+ $updated_settings = [
+ 'mail' => [
+ 'from_email' => $user['email'],
+ ],
+ $this->mailer_slug => [
+ 'user_details' => $user,
+ ],
+ ];
+
+ // To save in currently retrieved options array.
+ $this->options['user_details'] = $user;
+
+ $connection_options->set( $updated_settings, false, false );
+ }
+
+ /**
+ * Get the auth code from the $_GET and save it.
+ * Redirect user back to settings with an error message, if failed.
+ *
+ * @since 2.3.0
+ */
+ public function process() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded
+
+ $redirect_url = ( new ConnectionSettings( $this->connection ) )->get_admin_page_url();
+ $is_setup_wizard_auth = ! empty( $this->options['is_setup_wizard_auth'] );
+
+ if ( $is_setup_wizard_auth ) {
+ $this->update_is_setup_wizard_auth( false );
+
+ $redirect_url = SetupWizard::get_site_url() . '#/step/configure_mailer/zoho';
+ }
+
+ if ( ! ( isset( $_GET['tab'] ) && $_GET['tab'] === 'auth' ) ) {
+ wp_safe_redirect( $redirect_url );
+ exit;
+ }
+
+ $state = isset( $_GET['state'] ) ? sanitize_key( $_GET['state'] ) : false;
+
+ if ( empty( $state ) ) {
+ wp_safe_redirect(
+ add_query_arg( 'error', 'oauth_invalid_state', $redirect_url )
+ );
+ exit;
+ }
+
+ list( $nonce ) = array_pad( explode( '-', $state ), 1, false );
+
+ // Verify the nonce.
+ if ( ! wp_verify_nonce( $nonce, $this->state_key ) ) {
+ wp_safe_redirect(
+ add_query_arg(
+ 'error',
+ 'zoho_invalid_nonce',
+ $redirect_url
+ )
+ );
+ exit;
+ }
+
+ // We can't process without saved client_id/secret.
+ if ( ! $this->is_clients_saved() ) {
+ wp_safe_redirect(
+ add_query_arg(
+ 'error',
+ 'zoho_no_clients',
+ $redirect_url
+ )
+ );
+ exit;
+ }
+
+ $this->include_vendor_lib();
+
+ $error = isset( $_GET['error'] ) ? sanitize_key( $_GET['error'] ) : '';
+
+ // In case of any error: display a message to a user.
+ if ( ! empty( $error ) ) {
+ wp_safe_redirect(
+ add_query_arg(
+ 'error',
+ 'zoho_' . $error,
+ $redirect_url
+ )
+ );
+ exit;
+ }
+
+ // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
+ $code = isset( $_GET['code'] ) ? $_GET['code'] : '';
+
+ if ( empty( $code ) ) {
+ wp_safe_redirect(
+ add_query_arg(
+ 'error',
+ 'zoho_no_code',
+ $redirect_url
+ )
+ );
+ exit;
+ }
+
+ // Ensure the SDK client is initialized.
+ $this->get_client();
+
+ // Exchange the auth code for an access token.
+ $result = $this->obtain_access_token( $code );
+
+ if ( $result instanceof WP_Error ) {
+ $event_id = DebugEvents::add( 'Mailer: Zoho Mail' . WP::EOL . $result->get_error_message() );
+
+ wp_safe_redirect(
+ add_query_arg(
+ [
+ 'error' => 'zoho_unsuccessful_oauth',
+ 'debug_event_id' => $event_id,
+ ],
+ $redirect_url
+ )
+ );
+ exit;
+ }
+
+ wp_safe_redirect(
+ add_query_arg(
+ 'success',
+ 'zoho_site_linked',
+ $redirect_url
+ )
+ );
+ exit;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Modify the token expiration timestamp. By default the token expires in 1 hour.
+ * We are reducing the expiration by 11 minutes because of cache issues and server time mismatches.
+ *
+ * @since 2.3.0
+ *
+ * @param mixed $token The token data.
+ */
+ protected function update_access_token( $token ) {
+
+ if ( isset( $token['expires'] ) && is_int( $token['expires'] ) ) {
+ $token['expires'] -= apply_filters( 'wp_mail_smtp_pro_update_access_token_expires_reduction', 11 * 60 );
+ }
+
+ parent::update_access_token( $token );
+ }
+
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Zoho/Auth/Zoho.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Zoho/Auth/Zoho.php
new file mode 100644
index 00000000..cef6f286
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Zoho/Auth/Zoho.php
@@ -0,0 +1,314 @@
+ 'https://accounts.zoho.',
+ 'eu' => 'https://accounts.zoho.',
+ 'in' => 'https://accounts.zoho.',
+ 'com.cn' => 'https://accounts.zoho.',
+ 'com.au' => 'https://accounts.zoho.',
+ 'jp' => 'https://accounts.zoho.',
+ 'ca' => 'https://accounts.zohocloud.',
+ ];
+
+ /**
+ * Available Zoho Mail API endpoints.
+ *
+ * @since 4.2.0
+ *
+ * @var array
+ */
+ public static $mail_endpoints = [
+ 'com' => 'https://mail.zoho.',
+ 'eu' => 'https://mail.zoho.',
+ 'in' => 'https://mail.zoho.',
+ 'com.cn' => 'https://mail.zoho.',
+ 'com.au' => 'https://mail.zoho.',
+ 'jp' => 'https://mail.zoho.',
+ 'ca' => 'https://mail.zohocloud.',
+ ];
+
+ /**
+ * The root endpoint of the zoho authentication URL.
+ *
+ * The domain extension will be added later since the Zoho Mail API is divided by regions/domains.
+ *
+ * @since 4.2.0 Will be defined in the constructor based on the domain.
+ *
+ * @var string
+ */
+ protected $endpoint = '';
+
+ /**
+ * The access type attribute for the Zoho OAuth2 request.
+ *
+ * @since 2.3.0
+ *
+ * @var string
+ */
+ protected $access_type = 'offline';
+
+ /**
+ * The Zoho API domain for particular Zoho user account.
+ *
+ * Currently available option: 'com', 'eu', 'in', 'com.cn', 'com.au', 'ca'.
+ *
+ * @since 4.2.0 Added the CA (Canada) domain.
+ *
+ * @var string
+ */
+ protected $domain;
+
+ /**
+ * The default Zoho Mail scopes.
+ *
+ * @since 2.3.0
+ *
+ * @var array
+ */
+ public $default_scopes = [
+ 'ZohoMail.messages.CREATE',
+ 'ZohoMail.accounts.READ',
+ ];
+
+ /**
+ * Zoho constructor.
+ *
+ * @since 2.3.0
+ *
+ * @param array $options The provider options.
+ * @param array $collaborators The array of collaborators that may be used to
+ * override this provider's default behavior. Collaborators include
+ * `grantFactory`, `requestFactory`, and `httpClient`.
+ *
+ * @throws \InvalidArgumentException If the required options are not present.
+ */
+ public function __construct( array $options = [], array $collaborators = [] ) {
+
+ parent::__construct( $options, $collaborators );
+
+ foreach ( [ 'domain', 'clientId', 'clientSecret', 'redirectUri' ] as $key ) {
+ if ( ! isset( $options[ $key ] ) ) {
+ throw new \InvalidArgumentException( $key . ' is missing' );
+ }
+ }
+
+ // Check if the domain is set. Default to 'com' if not set.
+ if ( ! array_key_exists( $options['domain'], $this->account_endpoints ) ) {
+ $options['domain'] = 'com';
+ }
+
+ // Set the endpoint based on the domain.
+ $this->endpoint = $this->account_endpoints[ $options['domain'] ];
+ }
+
+ /**
+ * Returns the base URL for authorizing a client.
+ *
+ * Eg. https://accounts.zoho.eu/oauth/v2/auth
+ *
+ * @since 2.3.0
+ *
+ * @return string
+ */
+ public function getBaseAuthorizationUrl() {
+
+ return $this->endpoint . $this->domain . '/oauth/v2/auth';
+ }
+
+ /**
+ * Returns the base URL for requesting an access token.
+ *
+ * Eg. https://accounts.zoho.eu/oauth/v2/token
+ *
+ * @since 2.3.0
+ *
+ * @param array $params The token query parameters.
+ *
+ * @return string
+ */
+ public function getBaseAccessTokenUrl( array $params ) {
+
+ return $this->endpoint . $this->domain . '/oauth/v2/token';
+ }
+
+ /**
+ * Returns the URL for requesting the resource owner's details.
+ *
+ * @since 2.3.0
+ * @since 4.2.0 Added the region specific mail endpoint.
+ *
+ * @param AccessToken $token The access token.
+ *
+ * @return string
+ */
+ public function getResourceOwnerDetailsUrl( AccessToken $token ) {
+
+ // Use region specific mail API endpoint.
+ $api_root_url = self::$mail_endpoints[ $this->domain ];
+
+ // Set up the URL.
+ return sprintf(
+ '%s%s/api/accounts',
+ untrailingslashit( $api_root_url ), // Example: 'https://mail.zoho.'.
+ untrailingslashit( $this->domain ) // Example: 'eu'.
+ );
+ }
+
+ /**
+ * Get the Authorization header.
+ *
+ * @since 2.3.0
+ *
+ * @param mixed|null $token The access token.
+ *
+ * @return array
+ */
+ protected function getAuthorizationHeaders( $token = null ) {
+
+ return [
+ 'Authorization' => 'Zoho-oauthtoken ' . $token,
+ ];
+ }
+
+ /**
+ * Get the authorization parameters.
+ *
+ * @since 2.3.0
+ *
+ * @param array $options The client provider options.
+ *
+ * @return array
+ */
+ protected function getAuthorizationParameters( array $options ) {
+
+ if ( empty( $options['state'] ) ) {
+ $options['state'] = $this->state;
+ }
+
+ if ( empty( $options['scope'] ) ) {
+ $options['scope'] = $this->getDefaultScopes();
+ }
+
+ $options += [
+ 'response_type' => 'code',
+ 'prompt' => 'consent',
+ ];
+
+ if ( is_array( $options['scope'] ) ) {
+ $separator = $this->getScopeSeparator();
+ $options['scope'] = implode( $separator, $options['scope'] );
+ }
+
+ // Store the state as it may need to be accessed later on.
+ $this->state = $options['state'];
+
+ // Business code layer might set a different redirect_uri parameter depending on the context, leave it as-is.
+ if ( ! isset( $options['redirect_uri'] ) ) {
+ $options['redirect_uri'] = $this->redirectUri; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
+ }
+
+ $options['client_id'] = $this->clientId; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
+
+ if (
+ isset( $this->access_type ) &&
+ in_array( $this->access_type, [ 'offline', 'online' ], true )
+ ) {
+ $options['access_type'] = $this->access_type;
+ }
+
+ return $options;
+ }
+
+ /**
+ * Returns the default scopes used by this provider.
+ *
+ * This should only be the scopes that are required to request the details
+ * of the resource owner, rather than all the available scopes.
+ *
+ * @since 2.3.0
+ *
+ * @return array
+ */
+ protected function getDefaultScopes() {
+
+ return $this->default_scopes;
+ }
+
+ /**
+ * Checking response to see if provider returned any error.
+ *
+ * @since 2.3.0
+ *
+ * @param ResponseInterface $response The response object.
+ * @param array|string $data Parsed response data.
+ *
+ * @return void
+ *
+ * @throws IdentityProviderException If the response has an error.
+ */
+ protected function checkResponse( ResponseInterface $response, $data ) {
+
+ // A non-JSON body is parsed to a string; there is nothing to inspect.
+ if ( ! is_array( $data ) ) {
+ return;
+ }
+
+ // OAuth token endpoint errors use a top-level "error" key.
+ if ( array_key_exists( 'error', $data ) ) {
+ throw new IdentityProviderException( $data['error'], $response->getStatusCode(), $response ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
+ }
+
+ // The Zoho Mail API reports the real status in the body's `status.code`,
+ // often alongside an HTTP 200.
+ if ( isset( $data['status']['code'] ) && (int) $data['status']['code'] >= 400 ) {
+ $message = '';
+
+ if ( isset( $data['data']['moreInfo'] ) && is_string( $data['data']['moreInfo'] ) ) {
+ $message = $data['data']['moreInfo'];
+ } elseif ( isset( $data['status']['description'] ) && is_string( $data['status']['description'] ) ) {
+ $message = $data['status']['description'];
+ }
+
+ throw new IdentityProviderException( $message, (int) $data['status']['code'], $response ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
+ }
+ }
+
+ /**
+ * Generates a resource owner object from a successful resource owner
+ * details request.
+ *
+ * @since 2.3.0
+ *
+ * @param array $response The response data.
+ * @param AccessToken $token The access token.
+ *
+ * @return ZohoUser
+ */
+ protected function createResourceOwner( array $response, AccessToken $token ) {
+
+ return new ZohoUser( $response );
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Zoho/Auth/ZohoUser.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Zoho/Auth/ZohoUser.php
new file mode 100644
index 00000000..7dc72ac4
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Zoho/Auth/ZohoUser.php
@@ -0,0 +1,171 @@
+response = $response;
+
+ $this->processAvailableSendEmailDetails();
+ }
+
+ /**
+ * Returns the identifier of the authorized resource owner.
+ *
+ * The API returns array of account objects, so we are taking the first one.
+ *
+ * @since 2.3.0
+ *
+ * @return mixed
+ */
+ public function getId() {
+
+ return $this->getResponseData( 'data.0.ZUID' );
+ }
+
+ /**
+ * Get the data from the Zoho API response.
+ *
+ * @since 2.3.0
+ *
+ * @param string $path The attribute/path to get.
+ * @param null $default The default value, if the attribute/path can't be found.
+ *
+ * @return mixed
+ */
+ public function getResponseData( $path, $default = null ) {
+
+ return $this->getValueByKey( $this->response, $path, $default );
+ }
+
+ /**
+ * Return all of the owner details available as an array.
+ *
+ * @since 2.3.0
+ *
+ * @return array
+ */
+ public function toArray() {
+
+ return [
+ 'id' => $this->getId(),
+ 'available_send_email_details' => $this->getAvailableSendEmailDetails(),
+ ];
+ }
+
+ /**
+ * Get the available email addresses data for the current Zoho user.
+ *
+ * @since 2.3.0
+ *
+ * @return array
+ */
+ public function getAvailableSendEmailDetails() {
+
+ return $this->availableSendEmailDetails; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
+ }
+
+ /**
+ * Get available send email details of the provided email.
+ *
+ * @since 2.3.0
+ *
+ * @param string $email The email to search for.
+ *
+ * @return array
+ */
+ public function getAvailableSendEmailDetailsByEmail( $email ) {
+
+ if ( ! empty( $this->availableSendEmailDetails[ $email ] ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
+ return $this->availableSendEmailDetails[ $email ]; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
+ }
+
+ return [];
+ }
+
+ /**
+ * Prepare the available email addresses, their display names and account IDs for the current Zoho user.
+ *
+ * @since 2.3.0
+ */
+ protected function processAvailableSendEmailDetails() {
+
+ if ( empty( $this->response['data'] ) || ! is_array( $this->response['data'] ) ) {
+ return;
+ }
+
+ foreach ( $this->response['data'] as $account ) {
+ $this->processAccountSendMailDetails( $account );
+ }
+ }
+
+ /**
+ * Prepare the available email addresses for a single Zoho account from the API response.
+ *
+ * @since 4.9.0
+ *
+ * @param mixed $account A single account entry from the Zoho API response.
+ */
+ protected function processAccountSendMailDetails( $account ) {
+
+ if ( ! is_array( $account ) || empty( $account['sendMailDetails'] ) || ! is_array( $account['sendMailDetails'] ) ) {
+ return;
+ }
+
+ $account_id = isset( $account['accountId'] ) ? $account['accountId'] : '';
+
+ foreach ( $account['sendMailDetails'] as $details ) {
+ if ( ! is_array( $details ) || ! isset( $details['fromAddress'], $details['displayName'] ) ) {
+ continue;
+ }
+
+ $this->availableSendEmailDetails[ $details['fromAddress'] ] = [ // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
+ 'email' => $details['fromAddress'],
+ 'display_name' => $details['displayName'],
+ 'account_id' => $account_id,
+ ];
+ }
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Zoho/Mailer.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Zoho/Mailer.php
new file mode 100644
index 00000000..7fb13ed8
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Zoho/Mailer.php
@@ -0,0 +1,467 @@
+/api/accounts/
', $mg_text );
+ }
+
+ /**
+ * Whether the mailer has all its settings correctly set up and saved.
+ *
+ * @since 2.3.0
+ *
+ * @return bool
+ */
+ public function is_mailer_complete() {
+
+ if ( ! $this->is_php_compatible() ) {
+ return false;
+ }
+
+ $auth = new Auth( $this->connection );
+
+ if (
+ $auth->is_clients_saved() &&
+ ! $auth->is_auth_required() &&
+ ! empty( $this->connection_options->get( $this->mailer, 'domain' ) )
+ ) {
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Zoho/Options.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Zoho/Options.php
new file mode 100644
index 00000000..254b372f
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Zoho/Options.php
@@ -0,0 +1,319 @@
+ 'United States (US)',
+ 'eu' => 'Europe (EU)',
+ 'in' => 'India (IN)',
+ 'com.cn' => 'China (CN)',
+ 'com.au' => 'Australia (AU)',
+ 'jp' => 'Japan (JP)',
+ 'ca' => 'Canada (CA)',
+ ];
+
+ /**
+ * Outlook Options constructor.
+ *
+ * @since 2.3.0
+ *
+ * @param ConnectionInterface $connection The Connection object.
+ */
+ public function __construct( $connection = null ) {
+
+ parent::__construct(
+ [
+ 'logo_url' => wp_mail_smtp()->assets_url . '/images/providers/zoho.svg',
+ 'slug' => self::SLUG,
+ 'title' => esc_html__( 'Zoho Mail', 'wp-mail-smtp-pro' ),
+ 'description' => sprintf(
+ wp_kses( /* translators: %1$s - URL to Zoho Mail page, %2$s - URL to Zoho Mail documentation page on WP Mail SMTP. */
+ __( 'Zoho Mail allows you to create secure email accounts for your business without providing your credit card details. It\'s easy to connect to Zoho Mail using its API. This allows you to send WordPress emails from your Zoho Mail address.
To get started, read our Zoho Mail documentation.', 'wp-mail-smtp-pro' ),
+ [
+ 'br' => [],
+ 'a' => [
+ 'href' => [],
+ 'rel' => [],
+ 'target' => [],
+ ],
+ ]
+ ),
+ 'https://www.zoho.com/mail/',
+ esc_url( wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/docs/how-to-set-up-the-zoho-mailer-in-wp-mail-smtp/', 'Zoho Mail documentation' ) )
+ ),
+ 'php' => '5.6',
+ 'supports' => [
+ 'from_email' => true,
+ 'from_name' => true,
+ 'return_path' => false,
+ 'from_email_force' => true,
+ 'from_name_force' => true,
+ ],
+ ],
+ $connection
+ );
+ }
+
+ /**
+ * Output the mailer provider options.
+ *
+ * @since 2.3.0
+ */
+ public function display_options() {
+
+ // Do not display options if the PHP version requirement is not met.
+ if ( ! $this->is_php_correct() ) {
+ $this->display_php_warning();
+
+ return;
+ }
+
+ $current_domain = $this->connection_options->get( $this->get_slug(), 'domain' );
+ $current_domain = ! empty( $current_domain ) ? $current_domain : 'com';
+ ?>
+
+
+
+
+ Authorized Redirect URIs" web field for your application on Zoho Developer Console.', 'wp-mail-smtp-pro' ); ?>
+ ' . esc_html( $user['display_name'] . ' <' . $user['email'] . '>' ) . ''
+ );
+ }
+ ?>
+
+
If you\'re just starting out, you can use Elastic Email\'s free plan to send emails to your account address from one of your verified email addresses. You don\'t need to use a credit card to try it out. When you\'re ready, you can upgrade to a higher plan.', 'wp-mail-smtp' ) .
+ '
' .
+ /* translators: %2$s - URL to wpmailsmtp.com doc. */
+ __( 'To get started, read our Elastic Email documentation.', 'wp-mail-smtp' ),
+ [
+ 'strong' => true,
+ 'br' => true,
+ 'a' => [
+ 'href' => true,
+ 'rel' => true,
+ 'target' => true,
+ ],
+ ]
+ ),
+ 'https://www.elasticemail.com/',
+ esc_url( wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/docs/how-to-set-up-the-elastic-email-mailer-in-wp-mail-smtp/', 'Elastic Email documentation' ) )
+ );
+
+ parent::__construct(
+ [
+ 'logo_url' => wp_mail_smtp()->assets_url . '/images/providers/elasticemail.svg',
+ 'slug' => self::SLUG,
+ 'title' => esc_html__( 'Elastic Email', 'wp-mail-smtp' ),
+ 'php' => '5.6',
+ 'description' => $description,
+ 'supports' => [
+ 'from_email' => true,
+ 'from_name' => true,
+ 'return_path' => false,
+ 'from_email_force' => true,
+ 'from_name_force' => true,
+ ],
+ ],
+ $connection
+ );
+ }
+
+ /**
+ * Output the mailer provider options.
+ *
+ * @since 4.3.0
+ */
+ public function display_options() {
+
+ // Do not display options if PHP version is not correct.
+ if ( ! $this->is_php_correct() ) {
+ $this->display_php_warning();
+
+ return;
+ }
+ ?>
+
+
+
Server:';
+
+ $gmail_text[] = 'OpenSSL: ' . ( extension_loaded( 'openssl' ) && defined( 'OPENSSL_VERSION_TEXT' ) ? OPENSSL_VERSION_TEXT : 'No' );
+ $gmail_text[] = 'PHP.allow_url_fopen: ' . ( ini_get( 'allow_url_fopen' ) ? 'Yes' : 'No' );
+ $gmail_text[] = 'PHP.stream_socket_client(): ' . ( function_exists( 'stream_socket_client' ) ? 'Yes' : 'No' );
+ $gmail_text[] = 'PHP.fsockopen(): ' . ( function_exists( 'fsockopen' ) ? 'Yes' : 'No' );
+ $gmail_text[] = 'PHP.curl_version(): ' . $curl_ver;
+ if ( function_exists( 'apache_get_modules' ) ) {
+ $modules = apache_get_modules();
+ $gmail_text[] = 'Apache.mod_security: ' . ( in_array( 'mod_security', $modules, true ) || in_array( 'mod_security2', $modules, true ) ? 'Yes' : 'No' );
+ }
+ if ( function_exists( 'selinux_is_enabled' ) ) {
+ $gmail_text[] = 'OS.SELinux: ' . ( selinux_is_enabled() ? 'Yes' : 'No' );
+ }
+ if ( function_exists( 'grsecurity_is_enabled' ) ) {
+ $gmail_text[] = 'OS.grsecurity: ' . ( grsecurity_is_enabled() ? 'Yes' : 'No' );
+ }
+
+ return implode( '
', $gmail_text );
+ }
+
+ /**
+ * Whether the mailer has all its settings correctly set up and saved.
+ *
+ * @since 1.4.0
+ *
+ * @return bool
+ */
+ public function is_mailer_complete() {
+
+ if ( ! $this->is_php_compatible() ) {
+ return false;
+ }
+
+ $auth = new Auth( $this->connection );
+
+ if (
+ $auth->is_clients_saved() &&
+ ! $auth->is_auth_required()
+ ) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Process the exception message and append additional explanation to it.
+ *
+ * @since 2.1.0
+ *
+ * @param mixed $message A string or an object with strings.
+ *
+ * @return string
+ */
+ protected function process_exception_message( $message ) {
+
+ // Transform the passed message to a string.
+ if ( ! is_string( $message ) ) {
+ $message = wp_json_encode( $message );
+ } else {
+ $message = wp_strip_all_tags( $message, false );
+ }
+
+ // Define known errors, that we will scan the message with.
+ $known_errors = [
+ [
+ 'errors' => [
+ 'invalid_grant',
+ ],
+ 'explanation' => esc_html__( 'Please re-grant Google app permissions!', 'wp-mail-smtp' ) . ' ' . WP::EOL .
+ esc_html__( 'Go to WP Mail SMTP plugin settings page. Click the “Remove OAuth Connection” button.', 'wp-mail-smtp' ) . ' ' . WP::EOL .
+ esc_html__( 'Then click the “Allow plugin to send emails using your Google account” button and re-enable access.', 'wp-mail-smtp' ),
+ ],
+ ];
+
+ // Check if we get a match and append the explanation to the original message.
+ foreach ( $known_errors as $error ) {
+ foreach ( $error['errors'] as $error_fragment ) {
+ if ( false !== strpos( $message, $error_fragment ) ) {
+ return Helpers::format_error_message( $message, '', $error['explanation'] );
+ }
+ }
+ }
+
+ // If we get no match we return the original message (as a string).
+ return $message;
+ }
+
+ /**
+ * Get the default email addresses for the reply to email parameter.
+ *
+ * @deprecated 2.1.1
+ *
+ * @since 2.1.0
+ * @since 2.1.1 Not used anymore.
+ *
+ * @return array
+ */
+ public function default_reply_to_addresses() {
+
+ _deprecated_function( __CLASS__ . '::' . __METHOD__, '2.1.1 of WP Mail SMTP plugin' );
+
+ $gmail_creds = ( new Auth( $this->connection ) )->get_user_info();
+
+ if ( empty( $gmail_creds['email'] ) ) {
+ return [];
+ }
+
+ return [
+ $gmail_creds['email'] => [
+ $gmail_creds['email'],
+ '',
+ ],
+ ];
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Providers/Gmail/Options.php b/web/app/plugins/wp-mail-smtp-pro/src/Providers/Gmail/Options.php
new file mode 100644
index 00000000..d5209739
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Providers/Gmail/Options.php
@@ -0,0 +1,299 @@
+ wp_mail_smtp()->assets_url . '/images/providers/google.svg',
+ 'slug' => self::SLUG,
+ 'title' => esc_html__( 'Google / Gmail', 'wp-mail-smtp' ),
+ 'description' => sprintf(
+ wp_kses( /* translators: %s - URL to our Gmail doc. */
+ __( 'Our Gmail mailer works with any Gmail or Google Workspace account via the Google API. You can send WordPress emails from your main email address or a Gmail alias, and it\'s more secure than connecting to Gmail using SMTP credentials. We now have a One-Click Setup, which simply asks you to authorize your Google account to use our app and takes care of everything for you. Alternatively, you can connect manually, which involves several steps that are more technical than other mailer options, so we created a detailed guide to walk you through the process.
To get started, read our Gmail documentation.', 'wp-mail-smtp' ),
+ [
+ 'br' => [],
+ 'a' => [
+ 'href' => [],
+ 'rel' => [],
+ 'target' => [],
+ ],
+ ]
+ ),
+ esc_url( wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/docs/how-to-set-up-the-gmail-mailer-in-wp-mail-smtp/', 'Gmail documentation' ) )
+ ),
+ 'notices' => [
+ 'educational' => wp_kses(
+ __( 'The Gmail mailer works well for sites that send low numbers of emails. However, Gmail\'s API has rate limitations and a number of additional restrictions that can lead to challenges during setup.
If you expect to send a high volume of emails, or if you find that your web host is not compatible with the Gmail API restrictions, then we recommend considering a different mailer option.', 'wp-mail-smtp' ),
+ [
+ 'br' => [],
+ ]
+ ),
+ ],
+ 'php' => '5.6',
+ 'supports' => [
+ 'from_email' => true,
+ 'from_name' => true,
+ 'return_path' => false,
+ 'from_email_force' => true,
+ 'from_name_force' => true,
+ ],
+ ],
+ $connection
+ );
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function display_options() {
+
+ // Do not display options if PHP version is not correct.
+ if ( ! $this->is_php_correct() ) {
+ $this->display_php_warning();
+
+ return;
+ }
+ ?>
+
+ is_pro() ) : ?>
+ ' . esc_html( $user['email'] ) . ''
+ );
+ }
+ ?>
+
+
', $mail_text );
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function is_mailer_complete() {
+
+ return true;
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Providers/Mail/Options.php b/web/app/plugins/wp-mail-smtp-pro/src/Providers/Mail/Options.php
new file mode 100644
index 00000000..db32c34f
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Providers/Mail/Options.php
@@ -0,0 +1,59 @@
+ wp_mail_smtp()->assets_url . '/images/providers/php.svg',
+ 'slug' => 'mail',
+ 'title' => esc_html__( 'Default (none)', 'wp-mail-smtp' ),
+ )
+ );
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function display_options() {
+ ?>
+
+
+ Default (none) mailer selected, which won\'t improve email deliverability. Please select any other email provider and use the easy Setup Wizard to configure it.', 'wp-mail-smtp' ),
+ [
+ 'strong' => [],
+ 'a' => [
+ 'href' => [],
+ 'rel' => [],
+ 'target' => [],
+ ],
+ ]
+ ),
+ esc_url( wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/docs/a-complete-guide-to-wp-mail-smtp-mailers/', 'Default mailer - any other email provider' ) ),
+ esc_url( SetupWizard::get_site_url() )
+ );
+ ?>
+
+
+ connection = $connection;
+ } else {
+ $this->connection = wp_mail_smtp()->get_connections_manager()->get_primary_connection();
+ }
+
+ $this->connection_options = $this->connection->get_options();
+ $this->mailer = $this->connection->get_mailer_slug();
+ $this->options = Options::init();
+
+ // Only non-SMTP mailers need URL and extra processing for PHPMailer class.
+ if ( ! $this->connection_options->is_mailer_smtp() && empty( $this->url ) ) {
+ return;
+ }
+
+ $this->process_phpmailer( $phpmailer );
+ }
+
+ /**
+ * Re-use the MailCatcher class methods and properties.
+ *
+ * @since 1.0.0
+ *
+ * @param MailCatcherInterface $phpmailer The MailCatcher object.
+ */
+ public function process_phpmailer( $phpmailer ) {
+
+ // Make sure that we have access to PHPMailer class methods.
+ if ( ! wp_mail_smtp()->is_valid_phpmailer( $phpmailer ) ) {
+ return;
+ }
+
+ $this->phpmailer = $phpmailer;
+
+ // Prevent working with those methods, as they are not needed for SMTP-like mailers.
+ if ( $this->connection_options->is_mailer_smtp() ) {
+ return;
+ }
+
+ $this->set_headers( $this->phpmailer->getCustomHeaders() );
+ $this->set_from( $this->phpmailer->From, $this->phpmailer->FromName );
+ $this->set_recipients(
+ array(
+ 'to' => $this->phpmailer->getToAddresses(),
+ 'cc' => $this->phpmailer->getCcAddresses(),
+ 'bcc' => $this->phpmailer->getBccAddresses(),
+ )
+ );
+ $this->set_subject( $this->phpmailer->Subject );
+ if ( $this->phpmailer->ContentType === 'text/plain' ) {
+ $this->set_content( $this->phpmailer->Body );
+ } else {
+ $this->set_content(
+ array(
+ 'text' => $this->phpmailer->AltBody,
+ 'html' => $this->phpmailer->Body,
+ )
+ );
+ }
+ $this->set_return_path( $this->phpmailer->From );
+ $this->set_reply_to( $this->phpmailer->getReplyToAddresses() );
+
+ /*
+ * In some cases we will need to modify the internal structure
+ * of the body content, if attachments are present.
+ * So lets make this call the last one.
+ */
+ $this->set_attachments( $this->phpmailer->getAttachments() );
+ }
+
+ /**
+ * Set the email headers.
+ *
+ * @since 1.0.0
+ *
+ * @param array $headers List of key=>value pairs.
+ */
+ public function set_headers( $headers ) {
+
+ foreach ( $headers as $header ) {
+ $name = isset( $header[0] ) ? $header[0] : false;
+ $value = isset( $header[1] ) ? $header[1] : false;
+
+ if ( empty( $name ) || empty( $value ) ) {
+ continue;
+ }
+
+ $this->set_header( $name, $value );
+ }
+ }
+
+ /**
+ * Set individual header key=>value pair for the email.
+ *
+ * @since 1.0.0
+ *
+ * @param string $name
+ * @param string $value
+ */
+ public function set_header( $name, $value ) {
+
+ $name = sanitize_text_field( $name );
+
+ $this->headers[ $name ] = WP::sanitize_value( $value );
+ }
+
+ /**
+ * Set email subject.
+ *
+ * @since 1.0.0
+ *
+ * @param string $subject
+ */
+ public function set_subject( $subject ) {
+
+ $this->set_body_param(
+ array(
+ 'subject' => $subject,
+ )
+ );
+ }
+
+ /**
+ * Set the request params, that goes to the body of the HTTP request.
+ *
+ * @since 1.0.0
+ *
+ * @param array $param Key=>value of what should be sent to a 3rd party API.
+ *
+ * @internal param array $params
+ */
+ protected function set_body_param( $param ) {
+
+ $this->body = Options::array_merge_recursive( $this->body, $param );
+ }
+
+ /**
+ * Get the email body.
+ *
+ * @since 1.0.0
+ *
+ * @return string|array
+ */
+ public function get_body() {
+
+ return apply_filters( 'wp_mail_smtp_providers_mailer_get_body', $this->body, $this->mailer );
+ }
+
+ /**
+ * Get the email headers.
+ *
+ * @since 1.0.0
+ *
+ * @return array
+ */
+ public function get_headers() {
+
+ return apply_filters( 'wp_mail_smtp_providers_mailer_get_headers', $this->headers, $this->mailer );
+ }
+
+ /**
+ * Send the email.
+ *
+ * @since 1.0.0
+ * @since 1.8.0 Added timeout for requests, same as max_execution_time.
+ */
+ public function send() {
+
+ $timeout = (int) ini_get( 'max_execution_time' );
+
+ $params = Options::array_merge_recursive(
+ $this->get_default_params(),
+ array(
+ 'headers' => $this->get_headers(),
+ 'body' => $this->get_body(),
+ 'timeout' => $timeout ? $timeout : 30,
+ )
+ );
+
+ $response = wp_safe_remote_post( $this->url, $params );
+
+ DebugEvents::add_debug(
+ esc_html__( 'An email request was sent.', 'wp-mail-smtp' )
+ );
+
+ $this->process_response( $response );
+ }
+
+ /**
+ * We might need to do something after the email was sent to the API.
+ * In this method we preprocess the response from the API.
+ *
+ * @since 1.0.0
+ *
+ * @param mixed $response Response array.
+ */
+ protected function process_response( $response ) {
+
+ if ( is_wp_error( $response ) ) {
+ // Save the error text.
+ foreach ( $response->errors as $error_code => $error_message ) {
+ $this->error_message .= Helpers::format_error_message( $error_message, $error_code ) . WP::EOL;
+ }
+
+ return;
+ }
+
+ if ( wp_remote_retrieve_response_code( $response ) !== $this->email_sent_code ) {
+ $this->error_code = wp_remote_retrieve_response_code( $response );
+ }
+
+ if ( isset( $response['body'] ) && WP::is_json( $response['body'] ) ) {
+ $response['body'] = json_decode( $response['body'] );
+ }
+
+ $this->response = $response;
+ }
+
+ /**
+ * Get the default params, required for wp_safe_remote_post().
+ *
+ * @since 1.0.0
+ *
+ * @return array
+ */
+ protected function get_default_params() {
+
+ return apply_filters(
+ 'wp_mail_smtp_providers_mailer_get_default_params',
+ array(
+ 'timeout' => 15,
+ 'httpversion' => '1.1',
+ 'blocking' => true,
+ ),
+ $this->mailer
+ );
+ }
+
+ /**
+ * Whether the email is sent or not.
+ * We basically check the response code from a request to provider.
+ * Might not be 100% correct, not guarantees that email is delivered.
+ *
+ * @since 1.0.0
+ *
+ * @return bool
+ */
+ public function is_email_sent() {
+
+ $is_sent = false;
+
+ if ( wp_remote_retrieve_response_code( $this->response ) === $this->email_sent_code ) {
+ $is_sent = true;
+ }
+
+ /**
+ * Filters whether the email is sent or not.
+ *
+ * @since 3.1.0
+ *
+ * @param bool $is_sent Whether the email is sent or not.
+ * @param MailerAbstract $mailer Mailer object.
+ */
+ return apply_filters( 'wp_mail_smtp_providers_mailer_is_email_sent', $is_sent, $this->mailer );
+ }
+
+ /**
+ * The error message when email sending failed.
+ * Should be overwritten when appropriate.
+ *
+ * @since 1.2.0
+ * @since 2.5.0 Return a non-empty error_message attribute.
+ *
+ * @return string
+ */
+ public function get_response_error() {
+
+ return ! empty( $this->error_message ) ? $this->error_message : '';
+ }
+
+ /**
+ * The error code when email sending failed.
+ * Should be overwritten when appropriate.
+ *
+ * @since 4.8.0
+ *
+ * @return string
+ */
+ public function get_response_error_code() {
+
+ return ! empty( $this->error_code ) ? $this->error_code : '';
+ }
+
+ /**
+ * Get a header from the retained send response.
+ *
+ * @since 4.9.0
+ *
+ * @param string $name Header name (case-insensitive).
+ *
+ * @return string
+ */
+ public function get_response_header( $name ) {
+
+ return wp_remote_retrieve_header( $this->response, $name );
+ }
+
+ /**
+ * Get the HTTP response code.
+ *
+ * @since 4.8.0
+ *
+ * @return int
+ */
+ public function get_response_code() {
+
+ if ( empty( $this->response ) || ! is_array( $this->response ) ) {
+ return 0;
+ }
+
+ return (int) wp_remote_retrieve_response_code( $this->response );
+ }
+
+ /**
+ * Whether the mailer supports the current PHP version or not.
+ *
+ * @since 1.0.0
+ *
+ * @return bool
+ */
+ public function is_php_compatible() {
+
+ $options = wp_mail_smtp()->get_providers()->get_options( $this->mailer, $this->connection );
+
+ return version_compare( phpversion(), $options->get_php_version(), '>=' );
+ }
+
+ /**
+ * This method is relevant to SMTP and Pepipost.
+ * All other custom mailers should override it with own information.
+ *
+ * @since 1.2.0
+ *
+ * @return string
+ */
+ public function get_debug_info() {
+
+ global $phpmailer;
+
+ $smtp_text = array();
+
+ // Mail mailer has nothing to return.
+ if ( $this->connection_options->is_mailer_smtp() ) {
+ // phpcs:disable
+ $smtp_text[] = 'Host: ' . $phpmailer->Host;
+ $smtp_text[] = 'Port: ' . $phpmailer->Port;
+ $smtp_text[] = 'SMTPSecure: ' . Helpers::pvar( $phpmailer->SMTPSecure );
+ $smtp_text[] = 'SMTPAutoTLS: ' . Helpers::pvar( $phpmailer->SMTPAutoTLS );
+ $smtp_text[] = 'SMTPAuth: ' . Helpers::pvar( $phpmailer->SMTPAuth );
+ if ( ! empty( $phpmailer->SMTPOptions ) ) {
+ $smtp_text[] = 'SMTPOptions: ' . wp_json_encode( $phpmailer->SMTPOptions ) . '';
+ }
+ // phpcs:enable
+ }
+
+ $smtp_text[] = '
Server:';
+ $smtp_text[] = 'OpenSSL: ' . ( extension_loaded( 'openssl' ) && defined( 'OPENSSL_VERSION_TEXT' ) ? OPENSSL_VERSION_TEXT : 'No' );
+ if ( function_exists( 'apache_get_modules' ) ) {
+ $modules = apache_get_modules();
+ $smtp_text[] = 'Apache.mod_security: ' . ( in_array( 'mod_security', $modules, true ) || in_array( 'mod_security2', $modules, true ) ? 'Yes' : 'No' );
+ }
+ if ( function_exists( 'selinux_is_enabled' ) ) {
+ $smtp_text[] = 'OS.SELinux: ' . ( selinux_is_enabled() ? 'Yes' : 'No' );
+ }
+ if ( function_exists( 'grsecurity_is_enabled' ) ) {
+ $smtp_text[] = 'OS.grsecurity: ' . ( grsecurity_is_enabled() ? 'Yes' : 'No' );
+ }
+
+ return implode( '
', $smtp_text );
+ }
+
+ /**
+ * Get the email addresses for the reply to email parameter.
+ *
+ * @deprecated 2.1.1
+ *
+ * @since 2.1.0
+ * @since 2.1.1 Not used anymore.
+ *
+ * @return array
+ */
+ public function get_reply_to_addresses() {
+
+ _deprecated_function( __CLASS__ . '::' . __METHOD__, '2.1.1 of WP Mail SMTP plugin' );
+
+ $reply_to = $this->phpmailer->getReplyToAddresses();
+
+ // Return the passed reply to addresses, if defined.
+ if ( ! empty( $reply_to ) ) {
+ return $reply_to;
+ }
+
+ // Return the default reply to addresses.
+ return apply_filters(
+ 'wp_mail_smtp_providers_mailer_default_reply_to_addresses',
+ $this->default_reply_to_addresses()
+ );
+ }
+
+ /**
+ * Get the default email addresses for the reply to email parameter.
+ *
+ * @deprecated 2.1.1
+ *
+ * @since 2.1.0
+ * @since 2.1.1 Not used anymore.
+ *
+ * @return array
+ */
+ public function default_reply_to_addresses() {
+
+ _deprecated_function( __CLASS__ . '::' . __METHOD__, '2.1.1 of WP Mail SMTP plugin' );
+
+ return [
+ $this->phpmailer->From => [
+ $this->phpmailer->From,
+ $this->phpmailer->FromName,
+ ],
+ ];
+ }
+
+ /**
+ * Should the email sent by this mailer have its "sent status" verified via its API?
+ *
+ * @since 2.5.0
+ *
+ * @return bool
+ */
+ public function should_verify_sent_status() {
+
+ return $this->verify_sent_status;
+ }
+
+ /**
+ * Verify the "sent status" of the provided email log ID.
+ * The actual verification background task is triggered in the below action hook.
+ *
+ * @since 2.5.0
+ *
+ * @param int $email_log_id The ID of the email log.
+ */
+ public function verify_sent_status( $email_log_id ) {
+
+ if ( ! $this->should_verify_sent_status() ) {
+ return;
+ }
+
+ do_action( 'wp_mail_smtp_providers_mailer_verify_sent_status', $email_log_id, $this );
+ }
+
+ /**
+ * Get the name/slug of the current mailer.
+ *
+ * @since 2.5.0
+ *
+ * @return string
+ */
+ public function get_mailer_name() {
+
+ return $this->mailer;
+ }
+
+ /**
+ * Get PHPMailer attachment file content.
+ *
+ * @since 3.1.0
+ *
+ * @param array $attachment PHPMailer attachment.
+ *
+ * @return string|false
+ */
+ public function get_attachment_file_content( $attachment ) {
+
+ $file = false;
+
+ /*
+ * We are not using WP_Filesystem API as we can't reliably work with it.
+ * It is not always available, same as credentials for FTP.
+ */
+ try {
+ if ( $attachment[5] === true ) { // Whether there is string attachment.
+ $file = $attachment[0];
+ } elseif ( is_file( $attachment[0] ) && is_readable( $attachment[0] ) ) {
+ $file = file_get_contents( $attachment[0] );
+ }
+ } catch ( \Exception $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
+ // We don't handle this exception as we define a default value above.
+ }
+
+ return $file;
+ }
+
+ /**
+ * Get PHPMailer attachment file size.
+ *
+ * @since 3.4.0
+ *
+ * @param array $attachment PHPMailer attachment.
+ *
+ * @return int|false
+ */
+ public function get_attachment_file_size( $attachment ) {
+
+ $size = false;
+
+ if ( $attachment[5] === true ) { // Whether there is string attachment.
+ $size = Helpers::strsize( $attachment[0] );
+ } elseif ( is_file( $attachment[0] ) && is_readable( $attachment[0] ) ) {
+ $size = filesize( $attachment[0] );
+ }
+
+ return $size;
+ }
+
+ /**
+ * Get PHPMailer attachment file name.
+ *
+ * @since 3.4.0
+ *
+ * @param array $attachment PHPMailer attachment.
+ *
+ * @return string
+ */
+ public function get_attachment_file_name( $attachment ) {
+
+ $filetype = str_replace( ';', '', trim( $attachment[4] ) );
+
+ return ! empty( $attachment[2] ) ? trim( $attachment[2] ) : 'file-' . wp_hash( microtime() ) . '.' . $filetype;
+ }
+
+ /**
+ * Perform remote request with merged default params.
+ *
+ * @since 3.4.0
+ *
+ * @param string $url Request url.
+ * @param array $params Request params.
+ *
+ * @return array
+ */
+ public function remote_request( $url, $params ) {
+
+ if ( ! isset( $params['method'] ) ) {
+ $params['method'] = 'POST';
+ }
+
+ $params = Options::array_merge_recursive( $this->get_default_params(), $params );
+
+ /**
+ * Filters request params.
+ *
+ * @since 3.4.0
+ *
+ * @param array $params Request params.
+ * @param MailerAbstract $mailer Mailer object.
+ */
+ $params = apply_filters( 'wp_mail_smtp_providers_mailer_remote_request_params', $params, $this );
+
+ /**
+ * Filters request url.
+ *
+ * @since 3.4.0
+ *
+ * @param string $url Request url.
+ * @param MailerAbstract $mailer Mailer object.
+ */
+ $url = apply_filters( 'wp_mail_smtp_providers_mailer_remote_request_url', $url, $this );
+
+ return wp_safe_remote_request( $url, $params );
+ }
+
+ /**
+ * Get the Connection object.
+ *
+ * @since 3.7.0
+ *
+ * @return ConnectionInterface
+ */
+ public function get_connection() {
+
+ return $this->connection;
+ }
+
+ /**
+ * Sanitize email header values.
+ *
+ * @param string $name Name of the header.
+ * @param string $value Value of the header.
+ *
+ * @since 3.11.1
+ */
+ public function sanitize_header_value( $name, $value ) {
+
+ if (
+ in_array(
+ strtolower( $name ),
+ [
+ 'cc',
+ 'bcc',
+ 'reply-to',
+ 'message-id',
+ 'list-unsubscribe',
+ 'references',
+ 'in-reply-to'
+ ],
+ true
+ )
+ ) {
+ return $value;
+ }
+
+ return WP::sanitize_value( $value );
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Providers/MailerInterface.php b/web/app/plugins/wp-mail-smtp-pro/src/Providers/MailerInterface.php
new file mode 100644
index 00000000..6205b4df
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Providers/MailerInterface.php
@@ -0,0 +1,86 @@
+set_header( 'Authorization', 'Bearer ' . $this->connection_options->get( $this->mailer, 'api_key' ) );
+ $this->set_header( 'Accept', 'application/json' );
+ $this->set_header( 'Content-Type', 'application/json' );
+ }
+
+ /**
+ * Check if custom headers are supported based on the plan setting.
+ *
+ * @since 4.5.0
+ *
+ * @return bool
+ */
+ private function has_pro_features(): bool {
+
+ return (bool) $this->connection_options->get( $this->mailer, 'has_pro_plan' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement
+ }
+
+ /**
+ * Process the custom headers for this mailer.
+ * Headers are only supported on Professional plan and higher.
+ *
+ * @since 4.5.0
+ *
+ * @param array $headers Headers array.
+ */
+ public function set_headers( $headers ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
+
+ if ( empty( $headers ) ) {
+ return;
+ }
+
+ if ( ! $this->has_pro_features() ) {
+ return;
+ }
+
+ foreach ( $headers as $header ) {
+ $name = isset( $header[0] ) ? $header[0] : false;
+ $value = isset( $header[1] ) ? $header[1] : false;
+
+ if ( empty( $name ) || empty( $value ) ) {
+ continue;
+ }
+
+ $this->set_body_header( $name, $value );
+ }
+
+ $this->set_body_header( 'X-Mailer', 'WPMailSMTP/Mailer/' . $this->mailer . ' ' . WPMS_PLUGIN_VER );
+ }
+
+ /**
+ * This mailer supports email-related custom headers inside a body of the message.
+ *
+ * @since 4.5.0
+ *
+ * @param string $name Header name.
+ * @param string $value Header value.
+ */
+ public function set_body_header( $name, $value ) {
+
+ $name = sanitize_text_field( $name );
+ $value = sanitize_text_field( $value );
+
+ if ( empty( $name ) ) {
+ return;
+ }
+
+ $headers = isset( $this->body['headers'] ) ? (array) $this->body['headers'] : [];
+ $headers[] = [
+ 'name' => $name,
+ 'value' => $value,
+ ];
+
+ $this->set_body_param(
+ [
+ 'headers' => $headers,
+ ]
+ );
+ }
+
+ /**
+ * Set the FROM addresses.
+ *
+ * @since 4.5.0
+ *
+ * @param string $email From email address.
+ * @param string $name From name.
+ */
+ public function set_from( $email, $name = '' ) {
+
+ if ( ! filter_var( $email, FILTER_VALIDATE_EMAIL ) ) {
+ return;
+ }
+
+ $from['email'] = $email;
+
+ if ( ! empty( $name ) ) {
+ $from['name'] = sanitize_text_field( $name );
+ }
+
+ $this->set_body_param(
+ [
+ 'from' => $from,
+ ]
+ );
+ }
+
+ /**
+ * Set email recipients: to, cc, bcc.
+ *
+ * @since 4.5.0
+ *
+ * @param array $recipients Email recipients.
+ */
+ public function set_recipients( $recipients ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded
+
+ if ( empty( $recipients ) ) {
+ return;
+ }
+
+ // Allow for now only these recipient types.
+ $default = [ 'to', 'cc', 'bcc' ];
+ $data = [];
+
+ foreach ( $recipients as $type => $emails ) {
+ if (
+ ! in_array( $type, $default, true ) ||
+ empty( $emails ) ||
+ ! is_array( $emails )
+ ) {
+ continue;
+ }
+
+ $data[ $type ] = [];
+
+ foreach ( $emails as $email ) {
+ $holder = [];
+ $addr = isset( $email[0] ) ? $email[0] : false;
+ $name = isset( $email[1] ) ? $email[1] : false;
+
+ if ( ! filter_var( $addr, FILTER_VALIDATE_EMAIL ) ) {
+ continue;
+ }
+
+ $holder['email'] = $addr;
+
+ if ( ! empty( $name ) ) {
+ $holder['name'] = $name;
+ }
+
+ array_push( $data[ $type ], $holder );
+ }
+ }
+
+ if ( ! empty( $data ) ) {
+ foreach ( $data as $type => $type_data ) {
+ $this->set_body_param(
+ [
+ $type => $type_data,
+ ]
+ );
+ }
+ }
+ }
+
+ /**
+ * Set reply to.
+ *
+ * @since 4.5.0
+ *
+ * @param array $emails Reply to email addresses.
+ */
+ public function set_reply_to( $emails ) {
+
+ if ( empty( $emails ) ) {
+ return;
+ }
+
+ $first_email = reset( $emails );
+
+ if ( ! isset( $first_email[0] ) || ! filter_var( $first_email[0], FILTER_VALIDATE_EMAIL ) ) {
+ return;
+ }
+
+ $reply_to = [
+ 'email' => $first_email[0],
+ ];
+
+ if ( ! empty( $first_email[1] ) ) {
+ $reply_to['name'] = $first_email[1];
+ }
+
+ if ( ! empty( $reply_to ) ) {
+ $this->set_body_param(
+ [
+ 'reply_to' => $reply_to,
+ ]
+ );
+ }
+ }
+
+ /**
+ * Set email subject.
+ *
+ * @since 4.5.0
+ *
+ * @param string $subject Email subject.
+ */
+ public function set_subject( $subject ) {
+
+ $this->set_body_param(
+ [
+ 'subject' => $subject,
+ ]
+ );
+ }
+
+ /**
+ * Set email content.
+ *
+ * @since 4.5.0
+ *
+ * @param string|array $content Email content.
+ */
+ public function set_content( $content ) {
+
+ if ( empty( $content ) ) {
+ return;
+ }
+
+ if ( is_array( $content ) ) {
+ if ( ! empty( $content['text'] ) ) {
+ $this->set_body_param(
+ [
+ 'text' => $content['text'],
+ ]
+ );
+ }
+
+ if ( ! empty( $content['html'] ) ) {
+ $this->set_body_param(
+ [
+ 'html' => $content['html'],
+ ]
+ );
+ }
+ } else {
+ if ( $this->phpmailer->ContentType === 'text/plain' ) {
+ $this->set_body_param(
+ [
+ 'text' => $content,
+ ]
+ );
+ } else {
+ $this->set_body_param(
+ [
+ 'html' => $content,
+ ]
+ );
+ }
+ }
+ }
+
+ /**
+ * Set attachments for an email.
+ *
+ * @since 4.5.0
+ *
+ * @param array $attachments Attachments array.
+ */
+ public function set_attachments( $attachments ) {
+
+ if ( empty( $attachments ) ) {
+ return;
+ }
+
+ $data = $this->prepare_attachments( $attachments );
+
+ if ( ! empty( $data ) ) {
+ $this->set_body_param(
+ [
+ 'attachments' => $data,
+ ]
+ );
+ }
+ }
+
+ /**
+ * Prepare attachments data for MailerSend API.
+ *
+ * @since 4.5.0
+ *
+ * @param array $attachments Array of attachments.
+ *
+ * @return array
+ */
+ protected function prepare_attachments( $attachments ) {
+
+ $data = [];
+
+ foreach ( $attachments as $attachment ) {
+ $file = $this->get_attachment_file_content( $attachment );
+
+ if ( $file === false ) {
+ continue;
+ }
+
+ $filetype = str_replace( ';', '', trim( $attachment[4] ) );
+
+ $data[] = [
+ 'filename' => empty( $attachment[2] ) ? 'file-' . wp_hash( microtime() ) . '.' . $filetype : trim( $attachment[2] ),
+ // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
+ 'content' => base64_encode( $file ),
+ 'type' => $attachment[4],
+ 'disposition' => in_array( $attachment[6], [ 'inline', 'attachment' ], true ) ? $attachment[6] : 'attachment',
+ 'id' => empty( $attachment[7] ) ? '' : trim( (string) $attachment[7] ),
+ ];
+ }
+
+ return $data;
+ }
+
+ /**
+ * Redefine the way email body is returned.
+ * By default, we are sending an array of data.
+ * MailerSend requires a JSON, so we encode the body.
+ *
+ * @since 4.5.0
+ */
+ public function get_body() {
+
+ $body = parent::get_body();
+
+ return wp_json_encode( $body );
+ }
+
+ /**
+ * We might need to do something after the email was sent to the API.
+ * In this method we preprocess the response from the API.
+ *
+ * @since 4.5.0
+ *
+ * @param mixed $response Response data.
+ */
+ protected function process_response( $response ) {
+
+ parent::process_response( $response );
+
+ if ( ! is_wp_error( $response ) ) {
+ $message_id = wp_remote_retrieve_header( $response, 'x-message-id' );
+
+ if ( ! empty( $message_id ) ) {
+ $this->phpmailer->addCustomHeader( 'X-Msg-ID', $message_id );
+ $this->verify_sent_status = true;
+ }
+ }
+ }
+
+ /**
+ * Get a MailerSend-specific response with a helpful error.
+ *
+ * @since 4.5.0
+ *
+ * @return string
+ */
+ public function get_response_error() {
+
+ $body = (object) wp_remote_retrieve_body( $this->response );
+ $error_message = $this->get_response_error_message( $body );
+
+ return $error_message ? $error_message : WP::wp_remote_get_response_error_message( $this->response );
+ }
+
+ /**
+ * Get the error code from the MailerSend API response.
+ *
+ * @since 4.8.0
+ *
+ * @return string
+ */
+ public function get_response_error_code() {
+
+ if ( ! empty( $this->response ) ) {
+ $body = wp_remote_retrieve_body( $this->response );
+
+ // Extract #MS error reference code from message (e.g. #MS42207).
+ if ( ! empty( $body->message ) && preg_match( '/#(MS\d+)/', $body->message, $matches ) ) {
+ return $matches[1];
+ }
+
+ // Fall back to first error field key (e.g. "from.email").
+ if ( ! empty( $body->errors ) && is_object( $body->errors ) ) {
+ $keys = array_keys( (array) $body->errors );
+
+ if ( ! empty( $keys[0] ) ) {
+ return $keys[0];
+ }
+ }
+ }
+
+ return parent::get_response_error_code();
+ }
+
+ /**
+ * Get formatted error message from response body.
+ *
+ * @since 4.5.0
+ *
+ * @param object $body Response body object.
+ *
+ * @return string
+ */
+ private function get_response_error_message( $body ) {
+
+ $error_messages = [];
+
+ // Handle main error message.
+ if ( ! empty( $body->message ) ) {
+ $error_messages[] = Helpers::format_error_message( $body->message );
+ }
+
+ // Handle array of errors with context.
+ if ( ! empty( $body->errors ) && is_array( $body->errors ) && count( $body->errors ) > 1 ) {
+ foreach ( $body->errors as $field => $field_errors ) {
+ $field_errors = (array) $field_errors;
+
+ foreach ( $field_errors as $error ) {
+ $error_messages[] = sprintf( '%s (%s)', $error, $field );
+ }
+ }
+ }
+
+ return implode( PHP_EOL, $error_messages );
+ }
+
+ /**
+ * Whether the mailer is ready to be used.
+ *
+ * @since 4.5.0
+ *
+ * @return bool
+ */
+ public function is_mailer_complete() {
+
+ $options = $this->connection_options->get_group( $this->mailer );
+
+ // Only API key is required.
+ if ( ! empty( $options['api_key'] ) ) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Doesn't support this.
+ * So we do nothing.
+ *
+ * @since 4.5.0
+ *
+ * @param string $email Return Path email address.
+ */
+ public function set_return_path( $email ) { }
+
+ /**
+ * Get mailer debug information, that is helpful during support.
+ *
+ * @since 4.5.0
+ *
+ * @return string
+ */
+ public function get_debug_info() {
+
+ $mailersend = $this->connection_options->get_group( $this->mailer );
+
+ $text[] = 'Api Key: ' . ( ! empty( $mailersend['api_key'] ) ? 'Yes' : 'No' );
+
+ return implode( '
', $text );
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Providers/MailerSend/Options.php b/web/app/plugins/wp-mail-smtp-pro/src/Providers/MailerSend/Options.php
new file mode 100644
index 00000000..be97ebd4
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Providers/MailerSend/Options.php
@@ -0,0 +1,160 @@
+get_connections_manager()->get_primary_connection();
+ }
+
+ $description = sprintf(
+ wp_kses(
+ /* translators: %1$s - URL to mailersend.com; %2$s - URL to MailerSend documentation on wpmailsmtp.com. */
+ __( 'MailerSend is a reliable transactional email provider with powerful features. They offer 12,000 emails per month for free and have affordable plans for higher volumes. Their modern API and excellent deliverability make them a great choice for WordPress sites.
To get started, read our MailerSend documentation.', 'wp-mail-smtp' ),
+ [
+ 'strong' => [],
+ 'br' => [],
+ 'a' => [
+ 'href' => [],
+ 'rel' => [],
+ 'target' => [],
+ ],
+ ]
+ ),
+ esc_url( 'https://mailersend.com/' ),
+ // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound
+ esc_url( wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/docs/how-to-set-up-the-mailersend-mailer-in-wp-mail-smtp/', 'MailerSend Documentation' ) )
+ );
+
+ parent::__construct(
+ [
+ 'logo_url' => wp_mail_smtp()->assets_url . '/images/providers/mailersend.svg',
+ 'slug' => self::SLUG,
+ 'title' => esc_html__( 'MailerSend', 'wp-mail-smtp' ),
+ 'description' => $description,
+ 'supports' => [
+ 'from_email' => true,
+ 'from_name' => true,
+ 'return_path' => false,
+ 'from_email_force' => true,
+ 'from_name_force' => true,
+ ],
+ ],
+ $connection
+ );
+ }
+
+ /**
+ * Output the mailer provider options.
+ *
+ * @since 4.5.0
+ */
+ public function display_options() {
+
+ $get_api_key_url = 'https://app.mailersend.com/api-tokens';
+ ?>
+
+
+
', $mg_text );
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function is_mailer_complete() {
+
+ $options = $this->connection_options->get_group( $this->mailer );
+
+ // API key is the only required option.
+ if (
+ ! empty( $options['api_key'] ) &&
+ ! empty( $options['domain'] )
+ ) {
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Providers/Mailgun/Options.php b/web/app/plugins/wp-mail-smtp-pro/src/Providers/Mailgun/Options.php
new file mode 100644
index 00000000..f6ee25e4
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Providers/Mailgun/Options.php
@@ -0,0 +1,178 @@
+ wp_mail_smtp()->assets_url . '/images/providers/mailgun.svg',
+ 'slug' => 'mailgun',
+ 'title' => esc_html__( 'Mailgun', 'wp-mail-smtp' ),
+ 'description' => sprintf(
+ wp_kses(
+ /* translators: %1$s - URL to mailgun.com; %2$s - URL to Mailgun documentation on wpmailsmtp.com */
+ __( 'Mailgun is a transactional email provider that offers a generous 3-month free trial. After that, it offers a \'Pay As You Grow\' plan that allows you to pay for what you use without committing to a fixed monthly rate.
To get started, read our Mailgun documentation.', 'wp-mail-smtp' ),
+ array(
+ 'br' => array(),
+ 'a' => array(
+ 'href' => array(),
+ 'rel' => array(),
+ 'target' => array(),
+ ),
+ )
+ ),
+ 'https://www.mailgun.com',
+ esc_url( wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/docs/how-to-set-up-the-mailgun-mailer-in-wp-mail-smtp/', 'Mailgun documentation' ) )
+ ),
+ ),
+ $connection
+ );
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function display_options() {
+ ?>
+
+
+
+
+ More information on Mailgun.com.', 'wp-mail-smtp' ),
+ array(
+ 'a' => array(
+ 'href' => array(),
+ 'rel' => array(),
+ 'target' => array(),
+ ),
+ )
+ ),
+ 'https://www.mailgun.com/regions'
+ );
+ ?>
+
' .
+ /* translators: %2$s - URL to wpmailsmtp.com doc. */
+ __( 'To get started, read our Mailjet documentation.', 'wp-mail-smtp' ),
+ [
+ 'strong' => true,
+ 'br' => true,
+ 'a' => [
+ 'href' => true,
+ 'rel' => true,
+ 'target' => true,
+ ],
+ ]
+ ),
+ 'https://www.mailjet.com/',
+ esc_url( wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/docs/how-to-set-up-the-mailjet-mailer-in-wp-mail-smtp/', 'Mailjet documentation' ) )
+ );
+
+ parent::__construct(
+ [
+ 'logo_url' => wp_mail_smtp()->assets_url . '/images/providers/mailjet.svg',
+ 'slug' => self::SLUG,
+ 'title' => esc_html__( 'Mailjet', 'wp-mail-smtp' ),
+ 'php' => '5.6',
+ 'description' => $description,
+ 'supports' => [
+ 'from_email' => true,
+ 'from_name' => true,
+ 'return_path' => false,
+ 'from_email_force' => true,
+ 'from_name_force' => true,
+ ],
+ ],
+ $connection
+ );
+ }
+
+ /**
+ * Output the mailer provider options.
+ *
+ * @since 4.2.0
+ */
+ public function display_options() {
+
+ // Do not display options if PHP version is not correct.
+ if ( ! $this->is_php_correct() ) {
+ $this->display_php_warning();
+
+ return;
+ }
+ ?>
+
+
+
', $text );
+ }
+
+ /**
+ * Whether the mailer has all its settings correctly set up and saved.
+ *
+ * This mailer is configured when `api_key` setting is defined.
+ *
+ * @since 4.6.0
+ *
+ * @return bool
+ */
+ public function is_mailer_complete() {
+
+ $options = $this->connection_options->get_group( $this->mailer );
+
+ if ( ! empty( $options['api_key'] ) ) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Set the return path email.
+ *
+ * @since 4.6.0
+ *
+ * @param string $email The return path email address.
+ */
+ public function set_return_path( $email ) {}
+
+ /**
+ * Whether the email was sent.
+ *
+ * @since 4.6.0
+ *
+ * @return bool
+ */
+ public function is_email_sent() {
+
+ $is_sent = false;
+
+ if ( wp_remote_retrieve_response_code( $this->response ) === $this->email_sent_code ) {
+ $body = isset( $this->response['body'] ) ? $this->response['body'] : '';
+
+ if ( ! empty( $body ) && is_array( $body ) && isset( $body[0]->status ) && in_array( $body[0]->status, [ 'sent', 'queued' ], true ) ) {
+ $is_sent = true;
+ }
+ }
+
+ /**
+ * Filters whether the email is sent or not.
+ *
+ * @since 3.1.0
+ *
+ * @param bool $is_sent Whether the email is sent or not.
+ * @param MailerAbstract $mailer Mailer object.
+ */
+ return apply_filters( 'wp_mail_smtp_providers_mandrill_mailer_is_email_sent', $is_sent, $this->mailer );
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Providers/Mandrill/Options.php b/web/app/plugins/wp-mail-smtp-pro/src/Providers/Mandrill/Options.php
new file mode 100644
index 00000000..4590e8ee
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Providers/Mandrill/Options.php
@@ -0,0 +1,128 @@
+get_connections_manager()->get_primary_connection();
+ }
+
+ $description = sprintf(
+ wp_kses( /* translators: %1$s - URL to Mandrill website. */
+ __( 'Mandrill is a transactional email API for MailChimp users. It is a reliable, scalable, and secure delivery API for transactional emails. Mandrill requires a paid monthly subscription to send emails.', 'wp-mail-smtp' ) .
+ '
' .
+ /* translators: %2$s - URL to wpmailsmtp.com doc. */
+ __( 'To get started, read our Mandrill documentation.', 'wp-mail-smtp' ),
+ [
+ 'strong' => true,
+ 'br' => true,
+ 'a' => [
+ 'href' => true,
+ 'rel' => true,
+ 'target' => true,
+ ],
+ ]
+ ),
+ 'https://mandrillapp.com/',
+ esc_url( wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/docs/how-to-set-up-the-mandrill-mailer-in-wp-mail-smtp/', 'Mandrill documentation' ) )
+ );
+
+ parent::__construct(
+ [
+ 'logo_url' => wp_mail_smtp()->assets_url . '/images/providers/mandrill.svg',
+ 'slug' => self::SLUG,
+ 'title' => esc_html__( 'Mandrill', 'wp-mail-smtp' ),
+ 'description' => $description,
+ 'supports' => [
+ 'from_email' => true,
+ 'from_name' => true,
+ 'return_path' => false,
+ 'from_email_force' => true,
+ 'from_name_force' => true,
+ ],
+ 'recommended' => false,
+ ],
+ $connection
+ );
+ }
+
+ /**
+ * Output the mailer provider options.
+ *
+ * @since 4.6.0
+ */
+ public function display_options() {
+
+ // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound, WordPress.Security.NonceVerification.Recommended
+ $get_api_key_url = 'https://mandrillapp.com/settings/index/';
+ ?>
+
+
+
+ get_title() ),
+ esc_html( $this->php ),
+ esc_html( phpversion() )
+ );
+ ?>
+
+
+
+
+
+
+
+ WPBeginner\'s tutorial on how to set up SSL.', 'wp-mail-smtp' ),
+ [
+ 'a' => [
+ 'href' => [],
+ 'target' => [],
+ ],
+ ]
+ ),
+ esc_html( $this->get_title() )
+ );
+ ?>
+
+
+ %s
+
+
+
', $this->options->get_const_set_message( $constant ) ); //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + + /** + * Return the defaults for the mailer supported settings. + * + * @since 2.3.0 + * + * @return array + */ + public function get_supports_defaults() { + + return [ + 'from_email' => true, + 'from_name' => true, + 'return_path' => true, + 'from_email_force' => true, + 'from_name_force' => true, + ]; + } + + /** + * Get the mailer supported settings. + * + * @since 2.3.0 + * + * @return array + */ + public function get_supports() { + return apply_filters( 'wp_mail_smtp_providers_provider_get_supports', $this->supports, $this ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Providers/OptionsInterface.php b/web/app/plugins/wp-mail-smtp-pro/src/Providers/OptionsInterface.php new file mode 100644 index 00000000..235d0945 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Providers/OptionsInterface.php @@ -0,0 +1,71 @@ + wp_mail_smtp()->assets_url . '/images/providers/microsoft.svg', + 'slug' => 'outlook', + 'title' => esc_html__( '365 / Outlook', 'wp-mail-smtp' ), + 'disabled' => true, + ) + ); + } + + /** + * @inheritdoc + */ + public function display_options() { + + ?> +
+ +
+
+ get_connections_manager()->get_primary_connection();
+
+ // Bail if Other SMTP is not the current mailer.
+ if ( $connection->get_mailer_slug() !== 'smtp' ) {
+ return;
+ }
+
+ $host = $connection->get_options()->get( 'smtp', 'host' );
+ $host_suffix = strtolower( implode( '.', array_slice( explode( '.', $host ), - 2 ) ) );
+ $domains = [
+ 'live.com',
+ 'hotmail.com',
+ 'outlook.com',
+ 'office.com',
+ 'office365.com',
+ ];
+
+ // Bail if current SMTP host is not Microsoft-related.
+ if ( ! in_array( $host_suffix, $domains, true ) ) {
+ return;
+ }
+
+ $message = wp_kses(
+ sprintf( /* translators: %1$s - plugin name; %2$s - documentation link. */
+ __( '%1$s
Heads up! Microsoft is discontinuing support for basic SMTP connections. To continue using Outlook or Hotmail, switch to our Outlook mailer for uninterrupted email sending.', 'wp-mail-smtp' ),
+ esc_html__( 'WP Mail SMTP', 'wp-mail-smtp' ),
+ wp_mail_smtp()->get_utm_url(
+ 'https://wpmailsmtp.com/microsoft-outlook-smtp-how-to-fix-basic-authentication-error/',
+ [
+ 'medium' => 'outlook-smtp-notice',
+ 'content' => 'other-smtp-lite-to-outlook',
+ ]
+ )
+ ),
+ [
+ 'strong' => [],
+ 'br' => [],
+ 'a' => [
+ 'href' => [],
+ 'rel' => [],
+ 'target' => [],
+ ],
+ ]
+ );
+
+ if ( ! wp_mail_smtp()->is_pro() ) {
+ $message = wp_kses(
+ sprintf( /* translators: %1$s - Notice message; %2$s - upgrade link. */
+ __( '%1$s Upgrade to Pro now for easy, one-click Outlook setup.', 'wp-mail-smtp' ),
+ $message,
+ wp_mail_smtp()->get_upgrade_link( [ 'medium' => 'outlook-smtp-notice', 'content' => 'other-smtp-lite-to-outlook' ] ) // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound
+ ),
+ [
+ 'strong' => [],
+ 'br' => [],
+ 'a' => [
+ 'href' => [],
+ 'rel' => [],
+ 'target' => [],
+ ],
+ ]
+ );
+ }
+
+ WP::add_admin_notice(
+ $message,
+ implode( ' ', [ WP::ADMIN_NOTICE_ERROR, 'microsoft_basic_auth_deprecation_notice' ] ),
+ true,
+ 'microsoft_basic_auth_deprecation'
+ );
+ }
+
+ /**
+ * Dismiss basic auth deprecation notice.
+ *
+ * @since 4.3.0
+ * @deprecated 4.5.0
+ */
+ public function dismiss_basic_auth_notice() {
+
+ _deprecated_function( __METHOD__, '4.5.0' );
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Providers/Pepipost/Mailer.php b/web/app/plugins/wp-mail-smtp-pro/src/Providers/Pepipost/Mailer.php
new file mode 100644
index 00000000..e6f5763d
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Providers/Pepipost/Mailer.php
@@ -0,0 +1,32 @@
+options->get_group( $this->mailer );
+
+ // Host and Port are the only really required options.
+ if (
+ ! empty( $options['host'] ) &&
+ ! empty( $options['port'] )
+ ) {
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Providers/Pepipost/Options.php b/web/app/plugins/wp-mail-smtp-pro/src/Providers/Pepipost/Options.php
new file mode 100644
index 00000000..e886ce3a
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Providers/Pepipost/Options.php
@@ -0,0 +1,29 @@
+ wp_mail_smtp()->assets_url . '/images/providers/pepipost-smtp.png',
+ 'slug' => 'pepipost',
+ 'title' => esc_html__( 'Pepipost SMTP', 'wp-mail-smtp' ),
+ )
+ );
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Providers/PepipostAPI/Mailer.php b/web/app/plugins/wp-mail-smtp-pro/src/Providers/PepipostAPI/Mailer.php
new file mode 100644
index 00000000..9bf110c3
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Providers/PepipostAPI/Mailer.php
@@ -0,0 +1,477 @@
+set_header( 'api_key', $this->options->get( $this->mailer, 'api_key' ) );
+ $this->set_header( 'content-type', 'application/json' );
+ }
+
+ /**
+ * Redefine the way email body is returned.
+ * By default we are sending an array of data.
+ * Pepipost requires a JSON, so we encode the body.
+ *
+ * @since 1.8.0
+ *
+ * @return string
+ */
+ public function get_body() {
+
+ $body = parent::get_body();
+
+ return wp_json_encode( $body );
+ }
+
+ /**
+ * Set the FROM header of the email.
+ *
+ * @since 1.8.0
+ * @since 2.2.0 Changed the attribute names (API v5 changes).
+ *
+ * @param string $email From mail.
+ * @param string $name From name.
+ */
+ public function set_from( $email, $name = '' ) {
+
+ if ( ! filter_var( $email, FILTER_VALIDATE_EMAIL ) ) {
+ return;
+ }
+
+ $from['email'] = $email;
+
+ if ( ! empty( $name ) ) {
+ $from['name'] = $name;
+ }
+
+ $this->set_body_param(
+ [
+ 'from' => $from,
+ ]
+ );
+ }
+
+ /**
+ * Set the names/emails of people who will receive the email.
+ *
+ * @since 1.8.0
+ * @since 2.2.0 change the attribute names (API v5 changes).
+ *
+ * @param array $recipients List of recipients: cc/bcc/to.
+ */
+ public function set_recipients( $recipients ) {
+
+ if ( empty( $recipients ) ) {
+ return;
+ }
+
+ $data = [];
+
+ if ( ! empty( $recipients['to'] ) ) {
+ $data['to'] = $this->prepare_list_of_to_emails( $recipients['to'] );
+ }
+
+ if ( ! empty( $recipients['cc'] ) ) {
+ $data['cc'] = $this->prepare_list_of_emails( $recipients['cc'] );
+ }
+
+ if ( ! empty( $recipients['bcc'] ) ) {
+ $data['bcc'] = $this->prepare_list_of_emails( $recipients['bcc'] );
+ }
+
+ $this->set_body_personalizations( $data );
+ }
+
+ /**
+ * Set the email content.
+ * Pepipost API only supports HTML emails, so we have to replace new lines in plain text emails with
.
+ *
+ * @since 1.8.0
+ * @since 2.2.0 Change the way the content is prepared (API v5 changes).
+ *
+ * @param array|string $content Email content.
+ */
+ public function set_content( $content ) {
+
+ if ( empty( $content ) ) {
+ return;
+ }
+
+ $html = '';
+
+ if ( ! is_array( $content ) ) {
+ $html = $content;
+
+ if ( $this->phpmailer->ContentType === 'text/plain' ) {
+ $html = nl2br( $html );
+ }
+ } else {
+
+ if ( ! empty( $content['html'] ) ) {
+ $html = $content['html'];
+ } elseif ( ! empty( $content['text'] ) ) {
+ $html = nl2br( $content['text'] );
+ }
+ }
+
+ $this->set_body_param(
+ [
+ 'content' => [
+ [
+ 'type' => 'html',
+ 'value' => $html,
+ ],
+ ],
+ ]
+ );
+ }
+
+ /**
+ * Redefine the way custom headers are processed for this mailer - they should be in body (personalizations).
+ *
+ * @since 1.8.0
+ * @since 2.2.0 Change the way the headers are processed (API v5 changes).
+ *
+ * @param array $headers The email headers to be applied.
+ */
+ public function set_headers( $headers ) {
+
+ $valid_headers = [];
+
+ foreach ( $headers as $header ) {
+ $name = isset( $header[0] ) ? $header[0] : false;
+ $value = isset( $header[1] ) ? $header[1] : false;
+
+ $valid_headers[ $name ] = WP::sanitize_value( $value );
+ }
+
+ // Add custom PHPMailer-specific header.
+ $valid_headers['X-Mailer'] = WP::sanitize_value( 'WPMailSMTP/Mailer/' . $this->mailer . ' ' . WPMS_PLUGIN_VER );
+
+ if ( ! empty( $valid_headers ) ) {
+ $this->set_body_personalizations( [ 'headers' => $valid_headers ] );
+ }
+ }
+
+ /**
+ * Pepipost API accepts an array of files content in body, so we will include all files and send.
+ * Doesn't handle exceeding the limits etc, as this will be reported by the API response.
+ *
+ * @since 1.8.0
+ * @since 2.2.0 Change the way the attachments are processed (API v5 changes).
+ *
+ * @param array $attachments The list of attachments data.
+ */
+ public function set_attachments( $attachments ) {
+
+ if ( empty( $attachments ) ) {
+ return;
+ }
+
+ $data = $this->prepare_attachments( $attachments );
+
+ if ( ! empty( $data ) ) {
+ $this->set_body_param(
+ [
+ 'attachments' => $data,
+ ]
+ );
+ }
+ }
+
+ /**
+ * Prepare the attachments data for Pepipost API.
+ *
+ * @since 2.2.0
+ *
+ * @param array $attachments Array of attachments.
+ *
+ * @return array
+ */
+ protected function prepare_attachments( $attachments ) {
+
+ $data = [];
+
+ foreach ( $attachments as $attachment ) {
+ $file = false;
+
+ /*
+ * We are not using WP_Filesystem API as we can't reliably work with it.
+ * It is not always available, same as credentials for FTP.
+ */
+ try {
+ if ( is_file( $attachment[0] ) && is_readable( $attachment[0] ) ) {
+ $file = file_get_contents( $attachment[0] );
+ }
+ } catch ( \Exception $e ) {
+ $file = false;
+ }
+
+ if ( $file === false ) {
+ continue;
+ }
+
+ $data[] = [
+ 'content' => base64_encode( $file ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
+ 'name' => $attachment[2],
+ ];
+ }
+
+ return $data;
+ }
+
+ /**
+ * Set the reply-to property of the email.
+ * Pepipost API only supports one reply_to email, so we take the first one and discard the rest.
+ *
+ * @since 1.8.0
+ * @since 2.2.0 Change the way the reply_to is processed (API v5 changes).
+ *
+ * @param array $reply_to Name/email for reply-to feature.
+ */
+ public function set_reply_to( $reply_to ) {
+
+ if ( empty( $reply_to ) ) {
+ return;
+ }
+
+ $email_array = array_shift( $reply_to );
+
+ if ( empty( $email_array[0] ) ) {
+ return;
+ }
+
+ $email = $email_array[0];
+
+ if ( ! filter_var( $email, FILTER_VALIDATE_EMAIL ) ) {
+ return;
+ }
+
+ if ( ! empty( $email ) ) {
+ $this->set_body_param(
+ [
+ 'reply_to' => $email,
+ ]
+ );
+ }
+ }
+
+ /**
+ * Pepipost API doesn't support sender or return_path params.
+ * So we do nothing.
+ *
+ * @since 1.8.0
+ *
+ * @param string $from_email The from email address.
+ */
+ public function set_return_path( $from_email ) {}
+
+ /**
+ * Get a Pepipost-specific response with a helpful error.
+ *
+ * @see https://developers.pepipost.com/email-api/email-api/sendemail#responses
+ *
+ * @since 1.8.0
+ * @since 2.2.0 Change the way the response error message is processed (API v5 changes).
+ *
+ * @return string
+ */
+ public function get_response_error() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded
+
+ $body = (array) wp_remote_retrieve_body( $this->response );
+
+ $error = ! empty( $body['error'] ) ? $body['error'] : '';
+ $info = ! empty( $body['info'] ) ? $body['info'] : '';
+ $message = '';
+
+ if ( ! empty( $this->error_message ) ) {
+ $message = $this->error_message;
+ } elseif ( is_string( $error ) ) {
+ $message = $error . ( ( ! empty( $info ) ) ? ' - ' . $info : '' );
+ } elseif ( is_array( $error ) ) {
+ $message = '';
+
+ foreach ( $error as $item ) {
+ $message .= sprintf(
+ '%1$s (%2$s - %3$s)',
+ ! empty( $item->description ) ? $item->description : esc_html__( 'General error', 'wp-mail-smtp' ),
+ ! empty( $item->message ) ? $item->message : esc_html__( 'Error', 'wp-mail-smtp' ),
+ ! empty( $item->field ) ? $item->field : ''
+ ) . PHP_EOL;
+ }
+ }
+
+ return $message;
+ }
+
+ /**
+ * Get mailer debug information, that is helpful during support.
+ *
+ * @since 1.8.0
+ *
+ * @return string
+ */
+ public function get_debug_info() {
+
+ $sendgrid_text[] = 'Api Key: ' . ( $this->is_mailer_complete() ? 'Yes' : 'No' );
+
+ return implode( '
', $sendgrid_text );
+ }
+
+ /**
+ * Whether the mailer has all its settings correctly set up and saved.
+ *
+ * @since 1.8.0
+ *
+ * @return bool
+ */
+ public function is_mailer_complete() {
+
+ $options = $this->options->get_group( $this->mailer );
+
+ // API key is the only required option.
+ if ( ! empty( $options['api_key'] ) ) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * A special set method for Pepipost API "personalizations" attribute.
+ * We are sending one email at a time, so we should set just the first
+ * personalization item.
+ *
+ * Mainly used in set_headers and set_recipients.
+ *
+ * @see https://developers.pepipost.com/email-api/email-api/sendemail
+ *
+ * @since 2.2.0
+ *
+ * @param array $data The personalizations array of data (array of arrays).
+ */
+ private function set_body_personalizations( $data ) {
+
+ if ( empty( $data ) ) {
+ return;
+ }
+
+ if ( ! empty( $this->body['personalizations'][0] ) ) {
+ $this->body['personalizations'][0] = PluginOptions::array_merge_recursive(
+ $this->body['personalizations'][0],
+ $data
+ );
+ } else {
+ $this->set_body_param(
+ [
+ 'personalizations' => [
+ $data,
+ ],
+ ]
+ );
+ }
+ }
+
+ /**
+ * Prepare list of emails by filtering valid emails first.
+ *
+ * @since 2.2.0
+ *
+ * @param array $items A 2D array of email and name pair items (0 = email, 1 = name).
+ *
+ * @return array 2D array with 'email' keys.
+ */
+ private function prepare_list_of_emails( $items ) {
+
+ $valid_emails = array_filter(
+ array_column( $items, 0 ),
+ function ( $email ) {
+ return filter_var( $email, FILTER_VALIDATE_EMAIL );
+ }
+ );
+
+ return array_map(
+ function( $email ) {
+ return [ 'email' => $email ];
+ },
+ $valid_emails
+ );
+ }
+
+ /**
+ * Prepare list of TO emails by filtering valid emails first
+ * and returning array of arrays (email, name).
+ *
+ * @since 2.2.0
+ *
+ * @param array $items A 2D array of email and name pair items (0 = email, 1 = name).
+ *
+ * @return array 2D array with 'email' and optional 'name' attributes.
+ */
+ private function prepare_list_of_to_emails( $items ) {
+
+ $data = [];
+
+ foreach ( $items as $item ) {
+ $email = filter_var( $item[0], FILTER_VALIDATE_EMAIL );
+
+ if ( empty( $email ) ) {
+ continue;
+ }
+
+ $pair['email'] = $email;
+
+ if ( ! empty( $item[1] ) ) {
+ $pair['name'] = $item[1];
+ }
+
+ $data[] = $pair;
+ }
+
+ return $data;
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Providers/PepipostAPI/Options.php b/web/app/plugins/wp-mail-smtp-pro/src/Providers/PepipostAPI/Options.php
new file mode 100644
index 00000000..a40cd8d9
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Providers/PepipostAPI/Options.php
@@ -0,0 +1,127 @@
+Pepipost is a transactional email service. Every month Pepipost delivers over 8 billion emails from 20,000+ customers. Their mission is to reliably send emails in the most efficient way and at the most disruptive pricing ever. Pepipost provides users 30,000 free emails the first 30 days.', 'wp-mail-smtp' ) .
+ '
' .
+ /* translators: %1$s - URL to wpmailsmtp.com doc. */
+ __( 'Read our Pepipost documentation to learn how to configure Pepipost and improve your email deliverability.', 'wp-mail-smtp' ),
+ array(
+ 'br' => true,
+ 'a' => array(
+ 'href' => true,
+ 'rel' => true,
+ 'target' => true,
+ ),
+ )
+ ),
+ 'https://wpmailsmtp.com/go/pepipost/',
+ esc_url( wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/docs/how-to-set-up-the-pepipost-mailer-in-wp-mail-smtp/', 'Pepipost documentation' ) )
+ );
+
+ $api_key = PluginOptions::init()->get( self::SLUG, 'api_key' );
+
+ if ( empty( $api_key ) ) {
+ $description .= sprintf(
+ '
', + 'https://wpmailsmtp.com/go/pepipost/', + esc_html__( 'Get Started with Pepipost', 'wp-mail-smtp' ) + ); + } + + parent::__construct( + [ + 'logo_url' => wp_mail_smtp()->assets_url . '/images/providers/pepipost.png', + 'slug' => self::SLUG, + 'title' => esc_html__( 'Pepipost', 'wp-mail-smtp' ), + 'description' => $description, + 'php' => '5.3', + 'supports' => [ + 'from_email' => true, + 'from_name' => true, + 'return_path' => false, + 'from_email_force' => true, + 'from_name_force' => true, + ], + ] + ); + } + + /** + * Output the mailer provider options. + * + * @since 1.8.0 + */ + public function display_options() { + + // Do not display options if PHP version is not correct. + if ( ! $this->is_php_correct() ) { + $this->display_php_warning(); + + return; + } + ?> + + +
+ ' . + esc_html__( 'Get the API Key', 'wp-mail-smtp' ) . + '' + ); + ?> +
+
+
+ set_header( 'X-Postmark-Server-Token', $this->connection_options->get( $this->mailer, 'server_api_token' ) );
+ $this->set_header( 'Accept', 'application/json' );
+ $this->set_header( 'Content-Type', 'application/json' );
+
+ // Set mailer specific body parameters.
+ $message_stream = $this->get_message_stream();
+
+ if ( ! empty( $message_stream ) ) {
+ $this->set_body_param(
+ [
+ 'MessageStream' => $message_stream,
+ ]
+ );
+ }
+ }
+
+ /**
+ * Redefine the way custom headers are processed for this mailer - they should be in body.
+ *
+ * @since 3.1.0
+ *
+ * @param array $headers Headers array.
+ */
+ public function set_headers( $headers ) {
+
+ foreach ( $headers as $header ) {
+ $name = isset( $header[0] ) ? $header[0] : false;
+ $value = isset( $header[1] ) ? $header[1] : false;
+
+ $this->set_body_header( $name, $value );
+ }
+
+ // Add custom PHPMailer-specific header.
+ $this->set_body_header( 'X-Mailer', 'WPMailSMTP/Mailer/' . $this->mailer . ' ' . WPMS_PLUGIN_VER );
+ $this->set_body_header( 'Message-ID', $this->phpmailer->getLastMessageID() );
+ }
+
+ /**
+ * This mailer supports email-related custom headers inside a body of the message.
+ *
+ * @since 3.1.0
+ *
+ * @param string $name Header name.
+ * @param string $value Header value.
+ */
+ public function set_body_header( $name, $value ) {
+
+ $name = sanitize_text_field( $name );
+
+ if ( empty( $name ) ) {
+ return;
+ }
+
+ $headers = isset( $this->body['Headers'] ) ? (array) $this->body['Headers'] : [];
+ $value = $this->sanitize_header_value( $name, $value );
+
+ // Prevent duplicates.
+ $key = array_search( $name, array_column( $headers, 'Name' ), true );
+
+ if ( $key !== false ) {
+ unset( $headers[ $key ] );
+ }
+
+ $headers[] = [
+ 'Name' => $name,
+ 'Value' => $value,
+ ];
+
+ $this->body['Headers'] = array_values( $headers );
+ }
+
+ /**
+ * Set the From information for an email.
+ *
+ * @since 3.1.0
+ *
+ * @param string $email The sender email address.
+ * @param string $name The sender name.
+ */
+ public function set_from( $email, $name ) {
+
+ if ( ! filter_var( $email, FILTER_VALIDATE_EMAIL ) ) {
+ return;
+ }
+
+ $this->set_body_param(
+ [
+ 'From' => $this->phpmailer->addrFormat( [ $email, $name ] ),
+ ]
+ );
+ }
+
+ /**
+ * Set email recipients: to, cc, bcc.
+ *
+ * @since 3.1.0
+ *
+ * @param array $recipients Email recipients.
+ */
+ public function set_recipients( $recipients ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
+
+ if ( empty( $recipients ) ) {
+ return;
+ }
+
+ $default = [ 'to', 'cc', 'bcc' ];
+
+ foreach ( $recipients as $type => $emails ) {
+ if (
+ ! in_array( $type, $default, true ) ||
+ empty( $emails ) ||
+ ! is_array( $emails )
+ ) {
+ continue;
+ }
+
+ $data = [];
+
+ foreach ( $emails as $email ) {
+ $addr = isset( $email[0] ) ? $email[0] : false;
+
+ if ( ! filter_var( $addr, FILTER_VALIDATE_EMAIL ) ) {
+ continue;
+ }
+
+ $data[] = $this->phpmailer->addrFormat( $email );
+ }
+
+ if ( ! empty( $data ) ) {
+ $this->set_body_param(
+ [
+ ucfirst( $type ) => implode( ',', $data ),
+ ]
+ );
+ }
+ }
+ }
+
+ /**
+ * Set the Reply To information for an email.
+ *
+ * @since 3.1.0
+ *
+ * @param array $emails Reply To email addresses.
+ */
+ public function set_reply_to( $emails ) {
+
+ if ( empty( $emails ) ) {
+ return;
+ }
+
+ $data = [];
+
+ foreach ( $emails as $email ) {
+ $addr = isset( $email[0] ) ? $email[0] : false;
+
+ if ( ! filter_var( $addr, FILTER_VALIDATE_EMAIL ) ) {
+ continue;
+ }
+
+ $data[] = $this->phpmailer->addrFormat( $email );
+ }
+
+ if ( ! empty( $data ) ) {
+ $this->set_body_param(
+ [
+ 'ReplyTo' => implode( ',', $data ),
+ ]
+ );
+ }
+ }
+
+ /**
+ * Set email subject.
+ *
+ * @since 3.1.0
+ *
+ * @param string $subject Email subject.
+ */
+ public function set_subject( $subject ) {
+
+ $this->set_body_param(
+ [
+ 'Subject' => $subject,
+ ]
+ );
+ }
+
+ /**
+ * Set email content.
+ *
+ * @since 3.1.0
+ *
+ * @param string|array $content Email content.
+ */
+ public function set_content( $content ) {
+
+ if ( empty( $content ) ) {
+ return;
+ }
+
+ if ( is_array( $content ) ) {
+ if ( ! empty( $content['text'] ) ) {
+ $this->set_body_param(
+ [
+ 'TextBody' => $content['text'],
+ ]
+ );
+ }
+
+ if ( ! empty( $content['html'] ) ) {
+ $this->set_body_param(
+ [
+ 'HtmlBody' => $content['html'],
+ ]
+ );
+ }
+ } else {
+ if ( $this->phpmailer->ContentType === 'text/plain' ) {
+ $this->set_body_param(
+ [
+ 'TextBody' => $content,
+ ]
+ );
+ } else {
+ $this->set_body_param(
+ [
+ 'HtmlBody' => $content,
+ ]
+ );
+ }
+ }
+ }
+
+ /**
+ * Set attachments for an email.
+ *
+ * @since 3.1.0
+ *
+ * @param array $attachments Attachments array.
+ */
+ public function set_attachments( $attachments ) {
+
+ if ( empty( $attachments ) ) {
+ return;
+ }
+
+ $data = $this->prepare_attachments( $attachments );
+
+ if ( ! empty( $data ) ) {
+ $this->set_body_param(
+ [
+ 'Attachments' => $data,
+ ]
+ );
+ }
+ }
+
+ /**
+ * Prepare attachments data for Postmark API.
+ *
+ * @since 3.1.0
+ *
+ * @param array $attachments Array of attachments.
+ *
+ * @return array
+ */
+ protected function prepare_attachments( $attachments ) {
+
+ $data = [];
+
+ foreach ( $attachments as $attachment ) {
+ $file = $this->get_attachment_file_content( $attachment );
+
+ if ( $file === false ) {
+ continue;
+ }
+
+ $data[] = [
+ 'Name' => $this->get_attachment_file_name( $attachment ),
+ 'Content' => base64_encode( $file ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
+ 'ContentType' => $attachment[4],
+ ];
+ }
+
+ return $data;
+ }
+
+ /**
+ * Doesn't support this.
+ * Return path can be configured in Postmark account.
+ *
+ * @since 3.1.0
+ *
+ * @param string $email Return Path email address.
+ */
+ public function set_return_path( $email ) { }
+
+ /**
+ * Redefine the way email body is returned.
+ * By default, we are sending an array of data.
+ * Postmark requires a JSON, so we encode the body.
+ *
+ * @since 3.1.0
+ */
+ public function get_body() {
+
+ $body = parent::get_body();
+
+ return wp_json_encode( $body );
+ }
+
+ /**
+ * We might need to do something after the email was sent to the API.
+ * In this method we preprocess the response from the API.
+ *
+ * @since 3.1.0
+ *
+ * @param mixed $response Response data.
+ */
+ protected function process_response( $response ) {
+
+ parent::process_response( $response );
+
+ if (
+ ! is_wp_error( $response ) &&
+ ! empty( $this->response['body']->MessageID )
+ ) {
+ $this->phpmailer->addCustomHeader( 'X-Msg-ID', $this->response['body']->MessageID );
+ $this->verify_sent_status = true;
+ }
+ }
+
+ /**
+ * Get the error code from the Postmark API response.
+ *
+ * @since 4.8.0
+ *
+ * @return string
+ */
+ public function get_response_error_code() {
+
+ if ( ! empty( $this->response ) ) {
+ $body = wp_remote_retrieve_body( $this->response );
+
+ // phpcs:ignore WordPress.NamingConventions.ValidVariableName
+ if ( ! empty( $body->ErrorCode ) ) {
+ // phpcs:ignore WordPress.NamingConventions.ValidVariableName
+ return $body->ErrorCode;
+ }
+ }
+
+ return parent::get_response_error_code();
+ }
+
+ /**
+ * Get a Postmark-specific response with a helpful error.
+ *
+ * @since 3.1.0
+ *
+ * @return string
+ */
+ public function get_response_error() {
+
+ $error_text[] = $this->error_message;
+
+ if ( ! empty( $this->response ) ) {
+ $body = wp_remote_retrieve_body( $this->response );
+
+ // phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
+ if ( ! empty( $body->Message ) ) {
+ $message = $body->Message;
+ $code = ! empty( $body->ErrorCode ) ? $body->ErrorCode : '';
+
+ $error_text[] = Helpers::format_error_message( $message, $code );
+ } else {
+ $error_text[] = WP::wp_remote_get_response_error_message( $this->response );
+ }
+ // phpcs:enable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
+ }
+
+ return implode( WP::EOL, array_map( 'esc_textarea', array_filter( $error_text ) ) );
+ }
+
+ /**
+ * Get mailer debug information, that is helpful during support.
+ *
+ * @since 3.1.0
+ *
+ * @return string
+ */
+ public function get_debug_info() {
+
+ $options = $this->connection_options->get_group( $this->mailer );
+
+ $text[] = '' . esc_html__( 'Server API Token:', 'wp-mail-smtp' ) . ' ' .
+ ( ! empty( $options['server_api_token'] ) ? 'Yes' : 'No' );
+ $text[] = '' . esc_html__( 'Message Stream ID:', 'wp-mail-smtp' ) . ' ' .
+ ( ! empty( $this->get_message_stream() ) ? esc_html( $this->get_message_stream() ) : 'No' );
+
+ return implode( '
', $text );
+ }
+
+ /**
+ * Get the Message Stream ID.
+ *
+ * @since 3.1.0
+ *
+ * @link https://postmarkapp.com/message-streams
+ *
+ * @return string
+ */
+ private function get_message_stream() {
+
+ $message_stream = $this->connection_options->get( $this->mailer, 'message_stream' );
+
+ /**
+ * Filters Message Stream ID.
+ *
+ * @since 3.1.0
+ *
+ * @link https://postmarkapp.com/message-streams
+ *
+ * @param string $message_stream Message Stream ID.
+ */
+ return apply_filters( 'wp_mail_smtp_providers_postmark_mailer_get_message_stream', $message_stream );
+ }
+
+ /**
+ * Whether the mailer has all its settings correctly set up and saved.
+ *
+ * This mailer is configured when `server_api_token` setting is defined.
+ *
+ * @since 3.1.0
+ *
+ * @return bool
+ */
+ public function is_mailer_complete() {
+
+ $options = $this->connection_options->get_group( $this->mailer );
+
+ if ( ! empty( $options['server_api_token'] ) ) {
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Providers/Postmark/Options.php b/web/app/plugins/wp-mail-smtp-pro/src/Providers/Postmark/Options.php
new file mode 100644
index 00000000..be13a72b
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Providers/Postmark/Options.php
@@ -0,0 +1,164 @@
+Postmark is a transactional email provider that offers great deliverability and accessible pricing for any business. You can start out with the free trial that allows you to send 100 test emails each month via its secure API.', 'wp-mail-smtp' ) .
+ '
' .
+ /* translators: %2$s - URL to wpmailsmtp.com doc. */
+ __( 'To get started, read our Postmark documentation.', 'wp-mail-smtp' ),
+ [
+ 'strong' => true,
+ 'br' => true,
+ 'a' => [
+ 'href' => true,
+ 'rel' => true,
+ 'target' => true,
+ ],
+ ]
+ ),
+ 'https://postmarkapp.com',
+ esc_url( wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/docs/how-to-set-up-the-postmark-mailer-in-wp-mail-smtp/', 'Postmark documentation' ) )
+ );
+
+ parent::__construct(
+ [
+ 'logo_url' => wp_mail_smtp()->assets_url . '/images/providers/postmark.svg',
+ 'slug' => self::SLUG,
+ 'title' => esc_html__( 'Postmark', 'wp-mail-smtp' ),
+ 'php' => '5.6',
+ 'description' => $description,
+ 'supports' => [
+ 'from_email' => true,
+ 'from_name' => true,
+ 'return_path' => false,
+ 'from_email_force' => true,
+ 'from_name_force' => true,
+ ],
+ 'recommended' => false,
+ ],
+ $connection
+ );
+ }
+
+ /**
+ * Output the mailer provider options.
+ *
+ * @since 3.1.0
+ */
+ public function display_options() {
+
+ // Do not display options if PHP version is not correct.
+ if ( ! $this->is_php_correct() ) {
+ $this->display_php_warning();
+
+ return;
+ }
+ ?>
+
+
+
+ ' . + esc_html__( 'Get Server API Token', 'wp-mail-smtp' ) . + '' + ); + ?> +
++ + +
+ optional. By default outbound (Default Transactional Stream) will be used. More information can be found in our Postmark documentation.', 'wp-mail-smtp' ), + [ + 'strong' => [], + 'a' => [ + 'href' => [], + 'rel' => [], + 'target' => [], + ], + ] + ), + esc_url( wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/docs/how-to-set-up-the-postmark-mailer-in-wp-mail-smtp/#message-stream', 'Postmark documentation - message stream' ) ) + ); + ?> +
+
+
+ set_header( 'Authorization', 'Bearer ' . $this->connection_options->get( $this->mailer, 'api_key' ) );
+ $this->set_header( 'Accept', 'application/json' );
+ $this->set_header( 'Content-Type', 'application/json' );
+ }
+
+ /**
+ * Set the From information for an email.
+ *
+ * @since 4.7.0
+ *
+ * @param string $email The sender email address.
+ * @param string $name The sender name.
+ */
+ public function set_from( $email, $name ) {
+
+ if ( ! filter_var( $email, FILTER_VALIDATE_EMAIL ) ) {
+ return;
+ }
+
+ $this->set_body_param(
+ [
+ 'from' => $this->address_format( [ $email, $name ] ),
+ ]
+ );
+ }
+
+ /**
+ * Set email recipients: to, cc, bcc.
+ *
+ * @since 4.7.0
+ *
+ * @param array $recipients Email recipients.
+ */
+ public function set_recipients( $recipients ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
+
+ if ( empty( $recipients ) ) {
+ return;
+ }
+
+ // Allow only these recipient types.
+ $allowed_types = [ 'to', 'cc', 'bcc' ];
+ $data = [];
+
+ foreach ( $recipients as $type => $emails ) {
+ if (
+ ! in_array( $type, $allowed_types, true ) ||
+ empty( $emails ) ||
+ ! is_array( $emails )
+ ) {
+ continue;
+ }
+
+ // Iterate over all emails for each type.
+ // There might be multiple cc/to/bcc emails.
+ foreach ( $emails as $email ) {
+ if ( ! isset( $email[0] ) || ! filter_var( $email[0], FILTER_VALIDATE_EMAIL ) ) {
+ continue;
+ }
+
+ $data[ $type ][] = $this->address_format( $email );
+ }
+ }
+
+ if ( ! empty( $data ) ) {
+ $this->set_body_param( $data );
+ }
+ }
+
+ /**
+ * Set the Reply To information for an email.
+ *
+ * @since 4.7.0
+ *
+ * @param array $emails Reply To email addresses.
+ */
+ public function set_reply_to( $emails ) {
+
+ if ( empty( $emails ) ) {
+ return;
+ }
+
+ $data = [];
+
+ foreach ( $emails as $email ) {
+ if ( ! isset( $email[0] ) || ! filter_var( $email[0], FILTER_VALIDATE_EMAIL ) ) {
+ continue;
+ }
+
+ $data[] = $this->address_format( $email );
+ }
+
+ if ( ! empty( $data ) ) {
+ $this->set_body_param(
+ [
+ 'reply_to' => $data,
+ ]
+ );
+ }
+ }
+
+ /**
+ * Set email subject.
+ *
+ * @since 4.7.0
+ *
+ * @param string $subject Email subject.
+ */
+ public function set_subject( $subject ) {
+
+ $this->set_body_param(
+ [
+ 'subject' => $subject,
+ ]
+ );
+ }
+
+ /**
+ * Set email content.
+ *
+ * @since 4.7.0
+ *
+ * @param string|array $content Email content.
+ */
+ public function set_content( $content ) {
+
+ if ( empty( $content ) ) {
+ return;
+ }
+
+ if ( is_array( $content ) ) {
+ if ( ! empty( $content['text'] ) ) {
+ $this->set_body_param(
+ [
+ 'text' => $content['text'],
+ ]
+ );
+ }
+
+ if ( ! empty( $content['html'] ) ) {
+ $this->set_body_param(
+ [
+ 'html' => $content['html'],
+ ]
+ );
+ }
+ } else {
+ if ( $this->phpmailer->ContentType === 'text/plain' ) {
+ $this->set_body_param(
+ [
+ 'text' => $content,
+ ]
+ );
+ } else {
+ $this->set_body_param(
+ [
+ 'html' => $content,
+ ]
+ );
+ }
+ }
+ }
+
+ /**
+ * Set attachments for an email.
+ *
+ * @since 4.7.0
+ *
+ * @param array $attachments Attachments array.
+ */
+ public function set_attachments( $attachments ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
+
+ if ( empty( $attachments ) ) {
+ return;
+ }
+
+ $data = [];
+
+ // Prepare attachments.
+ foreach ( $attachments as $attachment ) {
+ $file = $this->get_attachment_file_content( $attachment );
+
+ if ( $file === false ) {
+ continue;
+ }
+
+ $attachment_data = [
+ 'filename' => $this->get_attachment_file_name( $attachment ),
+ 'content_type' => $attachment[4],
+ 'content' => base64_encode( $file ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
+ ];
+
+ if ( $attachment[6] === 'inline' ) {
+ $attachment_data['content_id'] = empty( $attachment[7] ) ? '' : trim( (string) $attachment[7] );
+ }
+
+ $data[] = $attachment_data;
+ }
+
+ if ( ! empty( $data ) ) {
+ $this->set_body_param(
+ [
+ 'attachments' => $data,
+ ]
+ );
+ }
+ }
+
+ /**
+ * Set return path.
+ *
+ * @since 4.7.0
+ *
+ * @param string $email Return Path email address.
+ */
+ public function set_return_path( $email ) {}
+
+ /**
+ * Set headers.
+ *
+ * @since 4.7.0
+ *
+ * @param array $headers Headers array.
+ */
+ public function set_headers( $headers ) {
+
+ foreach ( $headers as $header ) {
+ $name = isset( $header[0] ) ? $header[0] : false;
+ $value = isset( $header[1] ) ? $header[1] : false;
+
+ $this->set_body_header( $name, $value );
+ }
+
+ // Add custom header.
+ $this->set_body_header( 'X-Mailer', 'WPMailSMTP/Mailer/' . $this->mailer . ' ' . WPMS_PLUGIN_VER );
+ }
+
+ /**
+ * Set body header.
+ *
+ * @since 4.7.0
+ *
+ * @param string $name Header name.
+ * @param string $value Header value.
+ */
+ public function set_body_header( $name, $value ) {
+
+ $name = sanitize_text_field( $name );
+
+ if ( empty( $name ) ) {
+ return;
+ }
+
+ $headers = isset( $this->body['headers'] ) ? (array) $this->body['headers'] : [];
+
+ // Sanitize headers.
+ $headers[ $name ] = $this->sanitize_header_value( $name, $value );
+
+ $this->set_body_param(
+ [
+ 'headers' => $headers,
+ ]
+ );
+ }
+
+ /**
+ * Get request body.
+ *
+ * @since 4.7.0
+ */
+ public function get_body() {
+
+ $body = parent::get_body();
+
+ return wp_json_encode( $body );
+ }
+
+ /**
+ * We might need to do something after the email was sent to the API.
+ * In this method we preprocess the response from the API.
+ *
+ * @since 4.7.0
+ *
+ * @param mixed $response Response data.
+ */
+ protected function process_response( $response ) {
+
+ parent::process_response( $response );
+
+ if (
+ ! is_wp_error( $response ) &&
+ ! empty( $this->response['body']->id )
+ ) {
+ $this->phpmailer->addCustomHeader( 'X-Msg-ID', $this->response['body']->id );
+ $this->verify_sent_status = true;
+ }
+ }
+
+ /**
+ * Whether the email is sent or not.
+ *
+ * @since 4.7.0
+ *
+ * @return bool
+ */
+ public function is_email_sent() {
+
+ $is_sent = false;
+
+ if (
+ wp_remote_retrieve_response_code( $this->response ) === $this->email_sent_code &&
+ ! empty( $this->response['body']->id )
+ ) {
+ $is_sent = true;
+ }
+
+ // phpcs:disable WPForms.Comments.Since.MissingPhpDoc, WPForms.PHP.ValidateHooks.InvalidHookName
+
+ /** This filter is documented in src/Providers/MailerAbstract.php. */
+ return apply_filters( 'wp_mail_smtp_providers_mailer_is_email_sent', $is_sent, $this->mailer );
+ // phpcs:enable WPForms.Comments.Since.MissingPhpDoc, WPForms.PHP.ValidateHooks.InvalidHookName
+ }
+
+ /**
+ * Get a Resend-specific response with a helpful error.
+ *
+ * @since 4.7.0
+ *
+ * @return string
+ */
+ public function get_response_error() { // phpcs:ignore Generic.Metrics.NestingLevel.MaxExceeded, Generic.Metrics.CyclomaticComplexity.TooHigh
+
+ $error_text = [
+ $this->error_message,
+ ];
+
+ if ( ! empty( $this->response ) ) {
+ $body = wp_remote_retrieve_body( $this->response );
+
+ if ( ! empty( $body->message ) ) {
+ $error_code = ! empty( $body->statusCode ) ? $body->statusCode : ''; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
+ $error_name = ! empty( $body->name ) ? $body->name : '';
+ $error_text[] = Helpers::format_error_message( $error_name, $error_code, $body->message );
+ }
+ }
+
+ return implode( WP::EOL, array_map( 'esc_textarea', array_filter( $error_text ) ) );
+ }
+
+ /**
+ * Get the error code from the Resend API response.
+ *
+ * @since 4.8.0
+ *
+ * @return string
+ */
+ public function get_response_error_code() {
+
+ if ( ! empty( $this->response ) ) {
+ $body = wp_remote_retrieve_body( $this->response );
+
+ if ( ! empty( $body->name ) ) {
+ return $body->name;
+ }
+ }
+
+ return parent::get_response_error_code();
+ }
+
+ /**
+ * Whether the mailer has all its settings correctly set up and saved.
+ *
+ * @since 4.7.0
+ *
+ * @return bool
+ */
+ public function is_mailer_complete() {
+
+ $options = $this->connection_options->get_group( $this->mailer );
+
+ // API key is the only required option.
+ if ( ! empty( $options['api_key'] ) ) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Get mailer debug information, that is helpful during support.
+ *
+ * @since 4.7.0
+ *
+ * @return string
+ */
+ public function get_debug_info() {
+
+ $options = $this->connection_options->get_group( $this->mailer );
+
+ $text[] = '' . esc_html__( 'API Key:', 'wp-mail-smtp' ) . ' ' .
+ ( ! empty( $options['api_key'] ) ? 'Yes' : 'No' );
+
+ return implode( '
', $text );
+ }
+
+ /**
+ * Prepare address param.
+ *
+ * @since 4.7.0
+ *
+ * @param array $address Address array.
+ *
+ * @return array
+ */
+ private function address_format( $address ) {
+
+ $email = isset( $address[0] ) ? $address[0] : false;
+ $name = isset( $address[1] ) ? $address[1] : false;
+
+ $result = $email;
+
+ if ( ! empty( $name ) ) {
+ $result = "$name <$email>";
+ }
+
+ return $result;
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Providers/Resend/Options.php b/web/app/plugins/wp-mail-smtp-pro/src/Providers/Resend/Options.php
new file mode 100644
index 00000000..2c9fe2f4
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Providers/Resend/Options.php
@@ -0,0 +1,133 @@
+get_connections_manager()->get_primary_connection();
+ }
+
+ $description = sprintf(
+ wp_kses( /* translators: %1$s - URL to resend.com site. */
+ __( 'Resend is a modern email delivery platform that enables developers to send transactional and marketing emails quickly and reliably. If you\'re just getting started, you can send up to 3000 emails per month without a credit card.', 'wp-mail-smtp' ) .
+ '
' .
+ /* translators: %2$s - URL to wpmailsmtp.com doc. */
+ __( 'To get started, read our Resend documentation.', 'wp-mail-smtp' ),
+ [
+ 'strong' => true,
+ 'br' => true,
+ 'a' => [
+ 'href' => true,
+ 'rel' => true,
+ 'target' => true,
+ ],
+ ]
+ ),
+ 'https://resend.com/',
+ esc_url( wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/docs/how-to-set-up-the-resend-mailer-in-wp-mail-smtp/', 'Resend documentation' ) )
+ );
+
+ parent::__construct(
+ [
+ 'logo_url' => wp_mail_smtp()->assets_url . '/images/providers/resend.svg',
+ 'slug' => self::SLUG,
+ 'title' => esc_html__( 'Resend', 'wp-mail-smtp' ),
+ 'php' => '5.6',
+ 'description' => $description,
+ 'supports' => [
+ 'from_email' => true,
+ 'from_name' => true,
+ 'return_path' => false,
+ 'from_email_force' => true,
+ 'from_name_force' => true,
+ ],
+ ],
+ $connection
+ );
+ }
+
+ /**
+ * Output the mailer provider options.
+ *
+ * @since 4.7.0
+ */
+ public function display_options() {
+
+ // Do not display options if PHP version is not correct.
+ if ( ! $this->is_php_correct() ) {
+ $this->display_php_warning();
+
+ return;
+ }
+ ?>
+
+
+
+ ' . + esc_html__( 'API Keys', 'wp-mail-smtp' ) . + '' + ); + ?> +
+
+
+ connection_options->get_group( $this->mailer );
+
+ // Host and Port are the only really required options.
+ if (
+ ! empty( $options['host'] ) &&
+ ! empty( $options['port'] )
+ ) {
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Providers/SMTP/Options.php b/web/app/plugins/wp-mail-smtp-pro/src/Providers/SMTP/Options.php
new file mode 100644
index 00000000..d53bfe0e
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Providers/SMTP/Options.php
@@ -0,0 +1,48 @@
+ wp_mail_smtp()->assets_url . '/images/providers/smtp.svg',
+ 'slug' => 'smtp',
+ 'title' => esc_html__( 'Other SMTP', 'wp-mail-smtp' ),
+ 'description' => sprintf(
+ wp_kses(
+ /* translators: %s - URL to SMTP documentation. */
+ __( 'The Other SMTP option lets you send emails through an SMTP server instead of using a provider\'s API. This is easy and convenient, but it\'s less secure than the other mailers. Please note that your provider may not allow you to send a large number of emails. In that case, please use a different mailer.
To get started, read our Other SMTP documentation.', 'wp-mail-smtp' ),
+ [
+ 'br' => [],
+ 'a' => [
+ 'href' => [],
+ 'rel' => [],
+ 'target' => [],
+ ],
+ ]
+ ),
+ esc_url( wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/docs/how-to-set-up-the-other-smtp-mailer-in-wp-mail-smtp/', 'Other SMTP documentation' ) )
+ ),
+ ],
+ $connection
+ );
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Providers/SMTP2GO/Mailer.php b/web/app/plugins/wp-mail-smtp-pro/src/Providers/SMTP2GO/Mailer.php
new file mode 100644
index 00000000..af14614c
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Providers/SMTP2GO/Mailer.php
@@ -0,0 +1,529 @@
+set_header( 'X-Smtp2go-Api-Key', $this->connection_options->get( $this->mailer, 'api_key' ) );
+ $this->set_header( 'Accept', 'application/json' );
+ $this->set_header( 'Content-Type', 'application/json' );
+ }
+
+ /**
+ * Redefine the way custom headers are processed for this mailer - they should be in body.
+ *
+ * @since 4.1.0
+ *
+ * @param array $headers Headers array.
+ */
+ public function set_headers( $headers ) {
+
+ foreach ( $headers as $header ) {
+ $name = isset( $header[0] ) ? $header[0] : false;
+ $value = isset( $header[1] ) ? $header[1] : false;
+
+ $this->set_body_header( $name, $value );
+ }
+
+ // Add custom header.
+ $this->set_body_header( 'X-Mailer', 'WPMailSMTP/Mailer/' . $this->mailer . ' ' . WPMS_PLUGIN_VER );
+ }
+
+ /**
+ * This mailer supports email-related custom headers inside a body of the message.
+ *
+ * @since 4.1.0
+ *
+ * @param string $name Header name.
+ * @param string $value Header value.
+ */
+ public function set_body_header( $name, $value ) {
+
+ $name = sanitize_text_field( $name );
+
+ if ( empty( $name ) ) {
+ return;
+ }
+
+ $headers = isset( $this->body['custom_headers'] ) ? (array) $this->body['custom_headers'] : [];
+
+ // Index by key to remove duplicates.
+ $headers = wp_list_pluck( $headers, 'value', 'header' );
+
+ // Sanitize headers.
+ $headers[ $name ] = $this->sanitize_header_value( $name, $value );
+
+ // Turn headers into a list of header => value entries.
+ $data = array_reduce(
+ array_keys( $headers ),
+ function ( $data, $header ) use ( $headers ) {
+
+ $data[] = [
+ 'header' => $header,
+ 'value' => $headers[ $header ],
+ ];
+
+ return $data;
+ },
+ []
+ );
+
+ $this->body['custom_headers'] = $data;
+ }
+
+ /**
+ * Set the From information for an email.
+ *
+ * @since 4.1.0
+ *
+ * @param string $email The sender email address.
+ * @param string $name The sender name.
+ */
+ public function set_from( $email, $name ) {
+
+ if ( ! filter_var( $email, FILTER_VALIDATE_EMAIL ) ) {
+ return;
+ }
+
+ $this->set_body_param(
+ [
+ 'sender' => $this->address_format( [ $email, $name ] ),
+ ]
+ );
+ }
+
+ /**
+ * Set email recipients: to, cc, bcc.
+ *
+ * @since 4.1.0
+ *
+ * @param array $recipients Email recipients.
+ */
+ public function set_recipients( $recipients ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
+
+ if ( empty( $recipients ) ) {
+ return;
+ }
+
+ // Allow only these recipient types.
+ $allowed_types = [ 'to', 'cc', 'bcc' ];
+ $data = [];
+
+ foreach ( $recipients as $type => $emails ) {
+ if (
+ ! in_array( $type, $allowed_types, true ) ||
+ empty( $emails ) ||
+ ! is_array( $emails )
+ ) {
+ continue;
+ }
+
+ // Iterate over all emails for each type.
+ // There might be multiple cc/to/bcc emails.
+ foreach ( $emails as $email ) {
+ if ( ! isset( $email[0] ) || ! filter_var( $email[0], FILTER_VALIDATE_EMAIL ) ) {
+ continue;
+ }
+
+ $data[ $type ][] = $this->address_format( $email );
+ }
+ }
+
+ if ( ! empty( $data ) ) {
+ $this->set_body_param( $data );
+ }
+ }
+
+ /**
+ * Set the Reply To information for an email.
+ *
+ * @since 4.1.1
+ *
+ * @param array $emails Reply To email addresses.
+ */
+ public function set_reply_to( $emails ) {
+
+ if ( empty( $emails ) ) {
+ return;
+ }
+
+ $data = [];
+
+ foreach ( $emails as $email ) {
+ if ( ! isset( $email[0] ) || ! filter_var( $email[0], FILTER_VALIDATE_EMAIL ) ) {
+ continue;
+ }
+
+ $data[] = $this->address_format( $email );
+ }
+
+ if ( ! empty( $data ) ) {
+ $this->set_body_header(
+ 'Reply-To',
+ implode( ',', $data )
+ );
+ }
+ }
+
+ /**
+ * Set email subject.
+ *
+ * @since 4.1.0
+ *
+ * @param string $subject Email subject.
+ */
+ public function set_subject( $subject ) {
+
+ $this->set_body_param(
+ [
+ 'subject' => $subject,
+ ]
+ );
+ }
+
+ /**
+ * Set email content.
+ *
+ * @since 4.1.0
+ *
+ * @param string|array $content Email content.
+ */
+ public function set_content( $content ) {
+
+ if ( empty( $content ) ) {
+ return;
+ }
+
+ if ( is_array( $content ) ) {
+ if ( ! empty( $content['text'] ) ) {
+ $this->set_body_param(
+ [
+ 'text_body' => $content['text'],
+ ]
+ );
+ }
+
+ if ( ! empty( $content['html'] ) ) {
+ $this->set_body_param(
+ [
+ 'html_body' => $content['html'],
+ ]
+ );
+ }
+ } else {
+ if ( $this->phpmailer->ContentType === 'text/plain' ) {
+ $this->set_body_param(
+ [
+ 'text_body' => $content,
+ ]
+ );
+ } else {
+ $this->set_body_param(
+ [
+ 'html_body' => $content,
+ ]
+ );
+ }
+ }
+ }
+
+ /**
+ * Set attachments for an email.
+ *
+ * @since 4.1.0
+ *
+ * @param array $attachments Attachments array.
+ */
+ public function set_attachments( $attachments ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
+
+ if ( empty( $attachments ) ) {
+ return;
+ }
+
+ $data = [];
+
+ // Split attachments into "attachments" and "inlines" groups.
+ foreach ( $attachments as $attachment ) {
+ $mode = in_array( $attachment[6], [ 'inline', 'attachment' ], true ) ? $attachment[6] : 'attachment';
+
+ if ( $mode === 'inline' ) {
+ $data['inlines'][] = $attachment;
+ } else {
+ $data['attachments'][] = $attachment;
+ }
+ }
+
+ // Prepare attachments.
+ foreach ( $data as $disposition => $attachments ) {
+ $data[ $disposition ] = $this->prepare_attachments( $attachments );
+ }
+
+ if ( ! empty( $data ) ) {
+ $this->set_body_param( $data );
+ }
+ }
+
+ /**
+ * Doesn't support this.
+ * So we do nothing.
+ *
+ * @since 4.1.0
+ *
+ * @param string $email Return Path email address.
+ */
+ public function set_return_path( $email ) {}
+
+ /**
+ * Redefine the way email body is returned.
+ * By default, we are sending an array of data.
+ * SMTP2GO requires a JSON, so we encode the body.
+ *
+ * @since 4.1.0
+ */
+ public function get_body() {
+
+ $body = parent::get_body();
+
+ return wp_json_encode( $body );
+ }
+
+ /**
+ * Prepare attachments data.
+ *
+ * @since 4.1.0
+ *
+ * @param array $attachments Array of attachments.
+ *
+ * @return array
+ */
+ protected function prepare_attachments( $attachments ) {
+
+ $data = [];
+
+ foreach ( $attachments as $attachment ) {
+ $file = $this->get_attachment_file_content( $attachment );
+
+ if ( $file === false ) {
+ continue;
+ }
+
+ $filetype = str_replace( ';', '', trim( $attachment[4] ) );
+
+ $data[] = [
+ 'filename' => empty( $attachment[2] ) ? 'file-' . wp_hash( microtime() ) . '.' . $filetype : trim( $attachment[2] ),
+ // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
+ 'fileblob' => base64_encode( $file ),
+ 'mimetype' => $attachment[4],
+ ];
+ }
+
+ return $data;
+ }
+
+ /**
+ * We might need to do something after the email was sent to the API.
+ * In this method we preprocess the response from the API.
+ *
+ * @since 4.1.0
+ *
+ * @param mixed $response Response data.
+ */
+ protected function process_response( $response ) {
+
+ parent::process_response( $response );
+
+ if (
+ ! is_wp_error( $response ) &&
+ ! empty( $this->response['body']->data ) &&
+ ! empty( $this->response['body']->data->email_id )
+ ) {
+ $this->phpmailer->addCustomHeader( 'X-Msg-ID', $this->response['body']->data->email_id );
+ $this->verify_sent_status = true;
+ }
+ }
+
+ /**
+ * Whether the email is sent or not.
+ * We check response code and a non-empty `email_id` field in the response body.
+ *
+ * @since 4.1.0
+ *
+ * @return bool
+ */
+ public function is_email_sent() {
+
+ $is_sent = false;
+
+ if (
+ wp_remote_retrieve_response_code( $this->response ) === $this->email_sent_code &&
+ ! empty( $this->response['body']->data ) &&
+ ! empty( $this->response['body']->data->email_id )
+ ) {
+ $is_sent = true;
+ }
+
+ // phpcs:disable WPForms.Comments.Since.MissingPhpDoc, WPForms.PHP.ValidateHooks.InvalidHookName
+
+ /** This filter is documented in src/Providers/MailerAbstract.php. */
+ return apply_filters( 'wp_mail_smtp_providers_mailer_is_email_sent', $is_sent, $this->mailer );
+ // phpcs:enable WPForms.Comments.Since.MissingPhpDoc, WPForms.PHP.ValidateHooks.InvalidHookName
+ }
+
+ /**
+ * Get a SMTP2GO-specific response with a helpful error.
+ *
+ * @since 4.1.0
+ *
+ * @return string
+ */
+ public function get_response_error() { // phpcs:ignore Generic.Metrics.NestingLevel.MaxExceeded, Generic.Metrics.CyclomaticComplexity.TooHigh
+
+ $error_text = [
+ $this->error_message,
+ ];
+
+ if ( ! empty( $this->response ) ) {
+ $body = wp_remote_retrieve_body( $this->response );
+
+ if ( ! empty( $body->data ) ) {
+ if ( ! empty( $body->data->failures ) ) {
+ foreach ( $body->data->failures as $error ) {
+ $error_text[] = Helpers::format_error_message( $error );
+ }
+ }
+
+ if ( ! empty( $body->data->error ) ) {
+ $error_code = ! empty( $body->data->error_code ) ? $body->data->error_code : '';
+
+ $error_text[] = Helpers::format_error_message( $body->data->error, $error_code );
+ }
+
+ if ( ! empty( $body->data->field_validation_errors ) ) {
+ $message = '';
+ $code = '';
+
+ if ( ! empty( $body->data->field_validation_errors->message ) ) {
+ $message = $body->data->field_validation_errors->message;
+ }
+
+ if ( ! empty( $body->data->field_validation_errors->fieldname ) ) {
+ $code = $body->data->field_validation_errors->fieldname;
+ }
+
+ if ( ! empty( $message ) ) {
+ $error_text[] = Helpers::format_error_message( $message, $code );
+ }
+ }
+ } else {
+ $error_text[] = WP::wp_remote_get_response_error_message( $this->response );
+ }
+ }
+
+ return implode( WP::EOL, array_map( 'esc_textarea', array_filter( $error_text ) ) );
+ }
+
+ /**
+ * Get the error code from the response.
+ *
+ * @since 4.8.0
+ *
+ * @return string
+ */
+ public function get_response_error_code() {
+
+ if ( ! empty( $this->response ) ) {
+ $body = wp_remote_retrieve_body( $this->response );
+
+ // API-level error code (non-200 responses).
+ if ( ! empty( $body->data->error_code ) ) {
+ return $body->data->error_code;
+ }
+ }
+
+ return parent::get_response_error_code();
+ }
+
+ /**
+ * Whether the mailer has all its settings correctly set up and saved.
+ *
+ * @since 4.1.0
+ *
+ * @return bool
+ */
+ public function is_mailer_complete() {
+
+ $options = $this->connection_options->get_group( $this->mailer );
+
+ // API key is the only required option.
+ if ( ! empty( $options['api_key'] ) ) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Prepare address param.
+ *
+ * @since 4.1.0
+ *
+ * @param array $address Address array.
+ *
+ * @return array
+ */
+ private function address_format( $address ) {
+
+ $email = isset( $address[0] ) ? $address[0] : false;
+ $name = isset( $address[1] ) ? $address[1] : false;
+
+ $result = $email;
+
+ if ( ! empty( $name ) ) {
+ $result = "\"{$name}\" <{$email}>";
+ }
+
+ return $result;
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Providers/SMTP2GO/Options.php b/web/app/plugins/wp-mail-smtp-pro/src/Providers/SMTP2GO/Options.php
new file mode 100644
index 00000000..d15cc208
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Providers/SMTP2GO/Options.php
@@ -0,0 +1,133 @@
+get_connections_manager()->get_primary_connection();
+ }
+
+ $description = sprintf(
+ wp_kses( /* translators: %1$s - URL to SMTP2GO.com site. */
+ __( 'SMTP2GO provides a robust and reliable email delivery service with global infrastructure, real-time analytics, and advanced security features. If you\'re just starting out, you can use SMTP2GO\'s free plan to send up to 1000 emails per month.', 'wp-mail-smtp' ) .
+ '
' .
+ /* translators: %2$s - URL to wpmailsmtp.com doc. */
+ __( 'To get started, read our SMTP2GO documentation.', 'wp-mail-smtp' ),
+ [
+ 'strong' => true,
+ 'br' => true,
+ 'a' => [
+ 'href' => true,
+ 'rel' => true,
+ 'target' => true,
+ ],
+ ]
+ ),
+ 'https://www.smtp2go.com/',
+ esc_url( wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/docs/how-to-set-up-the-smtp2go-mailer-in-wp-mail-smtp/', 'SMTP2GO documentation' ) )
+ );
+
+ parent::__construct(
+ [
+ 'logo_url' => wp_mail_smtp()->assets_url . '/images/providers/smtp2go.svg',
+ 'slug' => self::SLUG,
+ 'title' => esc_html__( 'SMTP2GO', 'wp-mail-smtp' ),
+ 'php' => '5.6',
+ 'description' => $description,
+ 'supports' => [
+ 'from_email' => true,
+ 'from_name' => true,
+ 'return_path' => false,
+ 'from_email_force' => true,
+ 'from_name_force' => true,
+ ],
+ ],
+ $connection
+ );
+ }
+
+ /**
+ * Output the mailer provider options.
+ *
+ * @since 4.1.0
+ */
+ public function display_options() {
+
+ // Do not display options if PHP version is not correct.
+ if ( ! $this->is_php_correct() ) {
+ $this->display_php_warning();
+
+ return;
+ }
+ ?>
+
+
+
+ ' . + esc_html__( 'control panel', 'wp-mail-smtp' ) . + '' + ); + ?> +
+
+
+ set_header( 'Authorization', 'Bearer ' . $this->connection_options->get( $this->mailer, 'api_key' ) );
+ $this->set_header( 'Accept', 'application/json' );
+ $this->set_header( 'content-type', 'application/json' );
+
+ // Set mailer specific body parameters.
+ $this->set_body_param(
+ array(
+ 'channel' => $this->connection_options->get( $this->mailer, 'channel' ),
+ )
+ );
+ }
+
+ /**
+ * Redefine the way email body is returned.
+ * By default we are sending an array of data.
+ * SMTP.com requires a JSON, so we encode the body.
+ *
+ * @since 2.0.0
+ */
+ public function get_body() {
+
+ $body = parent::get_body();
+
+ return wp_json_encode( $body );
+ }
+
+ /**
+ * Define the FROM (name and email).
+ *
+ * @since 2.0.0
+ *
+ * @param string $email From Email address.
+ * @param string $name From Name.
+ */
+ public function set_from( $email, $name = '' ) {
+
+ if ( ! filter_var( $email, FILTER_VALIDATE_EMAIL ) ) {
+ return;
+ }
+
+ $from['address'] = $email;
+
+ if ( ! empty( $name ) ) {
+ $from['name'] = $name;
+ }
+
+ $this->set_body_param(
+ array(
+ 'originator' => array(
+ 'from' => $from,
+ ),
+ )
+ );
+ }
+
+ /**
+ * Define the CC/BCC/TO (with names and emails).
+ *
+ * @since 2.0.0
+ *
+ * @param array $recipients
+ */
+ public function set_recipients( $recipients ) {
+
+ if ( empty( $recipients ) ) {
+ return;
+ }
+
+ // Allow only these recipient types.
+ $allowed_types = array( 'to', 'cc', 'bcc' );
+ $data = array();
+
+ foreach ( $recipients as $type => $emails ) {
+ if (
+ ! in_array( $type, $allowed_types, true ) ||
+ empty( $emails ) ||
+ ! is_array( $emails )
+ ) {
+ continue;
+ }
+
+ $data[ $type ] = array();
+
+ // Iterate over all emails for each type.
+ // There might be multiple cc/to/bcc emails.
+ foreach ( $emails as $email ) {
+ $holder = array();
+ $address = isset( $email[0] ) ? $email[0] : false;
+ $name = isset( $email[1] ) ? $email[1] : false;
+
+ if ( ! filter_var( $address, FILTER_VALIDATE_EMAIL ) ) {
+ continue;
+ }
+
+ $holder['address'] = $address;
+ if ( ! empty( $name ) ) {
+ $holder['name'] = $name;
+ }
+
+ array_push( $data[ $type ], $holder );
+ }
+ }
+
+ if ( ! empty( $data ) ) {
+ $this->set_body_param(
+ array(
+ 'recipients' => $data,
+ )
+ );
+ }
+ }
+
+ /**
+ * Set the email content.
+ *
+ * @since 2.0.0
+ *
+ * @param array|string $content String when text/plain, array otherwise.
+ */
+ public function set_content( $content ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
+
+ if ( empty( $content ) ) {
+ return;
+ }
+
+ $parts = [];
+
+ if ( is_array( $content ) ) {
+ $allowed = [ 'text', 'html' ];
+
+ foreach ( $content as $type => $body ) {
+ if (
+ ! in_array( $type, $allowed, true ) ||
+ empty( $body )
+ ) {
+ continue;
+ }
+
+ $content_type = 'text/plain';
+ $content_value = $body;
+
+ if ( $type === 'html' ) {
+ $content_type = 'text/html';
+ }
+
+ $parts[] = [
+ 'type' => $content_type,
+ 'content' => $content_value,
+ 'charset' => $this->phpmailer->CharSet,
+ ];
+ }
+ } else {
+ $content_type = 'text/html';
+ $content_value = $content;
+
+ if ( $this->phpmailer->ContentType === 'text/plain' ) {
+ $content_type = 'text/plain';
+ }
+
+ $parts[] = [
+ 'type' => $content_type,
+ 'content' => $content_value,
+ 'charset' => $this->phpmailer->CharSet,
+ ];
+ }
+
+ $this->set_body_param(
+ [
+ 'body' => [
+ 'parts' => $parts,
+ ],
+ ]
+ );
+ }
+
+ /**
+ * Redefine the way custom headers are processed for this mailer - they should be in body.
+ *
+ * @since 2.0.0
+ *
+ * @param array $headers
+ */
+ public function set_headers( $headers ) {
+
+ foreach ( $headers as $header ) {
+ $name = isset( $header[0] ) ? $header[0] : false;
+ $value = isset( $header[1] ) ? $header[1] : false;
+
+ $this->set_body_header( $name, $value );
+ }
+
+ // Add custom PHPMailer-specific header.
+ $this->set_body_header( 'X-Mailer', 'WPMailSMTP/Mailer/' . $this->mailer . ' ' . WPMS_PLUGIN_VER );
+ }
+
+ /**
+ * This mailer supports email-related custom headers inside a body of the message.
+ *
+ * @since 2.0.0
+ *
+ * @param string $name
+ * @param string $value
+ */
+ public function set_body_header( $name, $value ) {
+
+ $name = sanitize_text_field( $name );
+ if ( empty( $name ) ) {
+ return;
+ }
+
+ $headers = isset( $this->body['custom_headers'] ) ? (array) $this->body['custom_headers'] : array();
+
+ $headers[ $name ] = $this->sanitize_header_value( $name, $value );
+
+ $this->set_body_param(
+ array(
+ 'custom_headers' => $headers,
+ )
+ );
+ }
+
+ /**
+ * SMTP.com accepts an array of attachments in body.attachments section of the JSON payload.
+ *
+ * @since 2.0.0
+ *
+ * @param array $attachments The array of attachments data.
+ */
+ public function set_attachments( $attachments ) {
+
+ if ( empty( $attachments ) ) {
+ return;
+ }
+
+ $data = [];
+
+ foreach ( $attachments as $attachment ) {
+ $file = $this->get_attachment_file_content( $attachment );
+
+ if ( $file === false ) {
+ continue;
+ }
+
+ $filetype = str_replace( ';', '', trim( $attachment[4] ) );
+
+ $data[] = [
+ 'content' => chunk_split( base64_encode( $file ) ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
+ 'type' => $filetype,
+ 'encoding' => 'base64',
+ 'filename' => $this->get_attachment_file_name( $attachment ),
+ 'disposition' => in_array( $attachment[6], [ 'inline', 'attachment' ], true ) ? $attachment[6] : 'attachment', // either inline or attachment.
+ 'cid' => empty( $attachment[7] ) ? '' : trim( (string) $attachment[7] ),
+ ];
+ }
+
+ if ( ! empty( $data ) ) {
+ $this->set_body_param(
+ [
+ 'body' => [
+ 'attachments' => $data,
+ ],
+ ]
+ );
+ }
+ }
+
+ /**
+ * Set Reply-To part of the message.
+ *
+ * @since 2.0.0
+ *
+ * @param array $reply_to
+ */
+ public function set_reply_to( $reply_to ) {
+
+ if ( empty( $reply_to ) ) {
+ return;
+ }
+
+ $data = array();
+
+ foreach ( $reply_to as $key => $emails ) {
+ if (
+ empty( $emails ) ||
+ ! is_array( $emails )
+ ) {
+ continue;
+ }
+
+ $address = isset( $emails[0] ) ? $emails[0] : false;
+ $name = isset( $emails[1] ) ? $emails[1] : false;
+
+ if ( ! filter_var( $address, FILTER_VALIDATE_EMAIL ) ) {
+ continue;
+ }
+
+ $data['address'] = $address;
+ if ( ! empty( $name ) ) {
+ $data['name'] = $name;
+ }
+
+ // Let the first valid email from the passed $reply_to serve as the reply_to parameter in STMP.com API.
+ // Only one email address and name is allowed in the `reply_to` parameter in the SMTP.com API payload.
+ break;
+ }
+
+ if ( ! empty( $data ) ) {
+ $this->set_body_param(
+ array(
+ 'originator' => array(
+ 'reply_to' => $data,
+ ),
+ )
+ );
+ }
+ }
+
+ /**
+ * SMTP.com doesn't support return_path params.
+ * So we do nothing.
+ *
+ * @since 2.0.0
+ *
+ * @param string $from_email
+ */
+ public function set_return_path( $from_email ) {}
+
+ /**
+ * We might need to do something after the email was sent to the API.
+ * In this method we preprocess the response from the API.
+ *
+ * @since 2.5.0
+ *
+ * @param mixed $response Response data.
+ */
+ protected function process_response( $response ) {
+
+ parent::process_response( $response );
+
+ if (
+ ! is_wp_error( $response ) &&
+ ! empty( $this->response['body']->data->message )
+ ) {
+ preg_match( '/msg_id: (.*)/', $this->response['body']->data->message, $output );
+
+ if ( ! empty( $output[1] ) ) {
+ $this->phpmailer->addCustomHeader( 'X-Msg-ID', $output[1] );
+ $this->verify_sent_status = true;
+ }
+ }
+ }
+
+ /**
+ * Get a SMTP.com-specific response with a helpful error.
+ *
+ * SMTP.com API error response (non 200 error code responses) is:
+ * {
+ * "status": "fail",
+ * "data": {
+ * "error_key": "short error message",
+ * }
+ * }
+ *
+ * It's good to combine the error_key and the message together for the best error explanation.
+ *
+ * @since 2.0.0
+ *
+ * @return string
+ */
+ public function get_response_error() {
+
+ $error_text[] = $this->error_message;
+
+ if ( ! empty( $this->response ) ) {
+ $body = wp_remote_retrieve_body( $this->response );
+
+ if ( ! empty( $body->data ) ) {
+ foreach ( (array) $body->data as $error_key => $error_message ) {
+ $error_text[] = Helpers::format_error_message( $error_message, $error_key );
+ }
+ } else {
+ $error_text[] = WP::wp_remote_get_response_error_message( $this->response );
+ }
+ }
+
+ return implode( WP::EOL, array_map( 'esc_textarea', array_filter( $error_text ) ) );
+ }
+
+ /**
+ * Get the error code from the SMTP.com API response.
+ *
+ * @since 4.8.0
+ *
+ * @return string
+ */
+ public function get_response_error_code() {
+
+ if ( ! empty( $this->response ) ) {
+ $body = wp_remote_retrieve_body( $this->response );
+
+ // Only extract error keys when the API reports failure.
+ if ( ! empty( $body->status ) && $body->status === 'fail' && ! empty( $body->data ) ) {
+ $keys = array_keys( (array) $body->data );
+
+ if ( ! empty( $keys[0] ) ) {
+ return $keys[0];
+ }
+ }
+ }
+
+ return parent::get_response_error_code();
+ }
+
+ /**
+ * Get mailer debug information, that is helpful during support.
+ *
+ * @since 2.0.0
+ *
+ * @return string
+ */
+ public function get_debug_info() {
+
+ $options = $this->connection_options->get_group( $this->mailer );
+
+ $text[] = '' . esc_html__( 'Api Key:', 'wp-mail-smtp' ) . ' ' .
+ ( ! empty( $options['api_key'] ) ? 'Yes' : 'No' );
+ $text[] = '' . esc_html__( 'Channel:', 'wp-mail-smtp' ) . ' ' .
+ ( ! empty( $options['channel'] ) ? 'Yes' : 'No' );
+
+ return implode( '
', $text );
+ }
+
+ /**
+ * Whether the mailer has all its settings correctly set up and saved.
+ *
+ * This mailer is configured when `api_key` and `channel` settings are defined.
+ *
+ * @since 2.0.0
+ *
+ * @return bool
+ */
+ public function is_mailer_complete() {
+
+ $options = $this->connection_options->get_group( $this->mailer );
+
+ if ( ! empty( $options['api_key'] ) && ! empty( $options['channel'] ) ) {
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Providers/SMTPcom/Options.php b/web/app/plugins/wp-mail-smtp-pro/src/Providers/SMTPcom/Options.php
new file mode 100644
index 00000000..94477d75
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Providers/SMTPcom/Options.php
@@ -0,0 +1,174 @@
+get_connections_manager()->get_primary_connection();
+ }
+
+ $allowed_kses_html = array(
+ 'strong' => array(),
+ 'br' => array(),
+ 'a' => array(
+ 'href' => array(),
+ 'rel' => array(),
+ 'target' => array(),
+ ),
+ );
+
+ $description = sprintf(
+ wp_kses( /* translators: %s - URL to smtp.com site. */
+ __( 'SMTP.com is one of our recommended mailers. It\'s a transactional email provider that\'s currently used by 100,000+ businesses. SMTP.com is an established brand that\'s been offering email services for more than 20 years.
SMTP.com offers a free 30-day trial that allows you to send up to 50,000 emails.', 'wp-mail-smtp' ),
+ $allowed_kses_html
+ ),
+ 'https://wpmailsmtp.com/go/smtp/'
+ );
+ $description .= '
';
+ $description .= sprintf(
+ wp_kses( /* translators: %s - URL to wpmailsmtp.com doc page for stmp.com. */
+ __( 'To get started, read our SMTP.com documentation.', 'wp-mail-smtp' ),
+ $allowed_kses_html
+ ),
+ esc_url( wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/docs/how-to-set-up-the-smtp-com-mailer-in-wp-mail-smtp/', 'SMTP.com documentation' ) )
+ );
+
+ $mailer_options = $connection->get_options()->get_group( self::SLUG );
+
+ if ( empty( $mailer_options['api_key'] ) && empty( $mailer_options['channel'] ) ) {
+ $description .= sprintf(
+ '
', + 'https://wpmailsmtp.com/go/smtp/', + esc_html__( 'Get Started with SMTP.com', 'wp-mail-smtp' ) + ); + } + + $description .= '
' . + esc_html__( 'Transparency and Disclosure', 'wp-mail-smtp' ) . + '' . + esc_html__( 'We believe in full transparency. The SMTP.com links above are tracking links as part of our partnership with SMTP (j2 Global). We can recommend just about any SMTP service, but we only recommend products that we believe will add value to our users.', 'wp-mail-smtp' ) . + '
'; + + parent::__construct( + [ + 'logo_url' => wp_mail_smtp()->assets_url . '/images/providers/smtp-com.svg', + 'slug' => self::SLUG, + 'title' => esc_html__( 'SMTP.com', 'wp-mail-smtp' ), + 'description' => $description, + 'recommended' => true, + 'supports' => [ + 'from_email' => true, + 'from_name' => true, + 'return_path' => false, + 'from_email_force' => true, + 'from_name_force' => true, + ], + ], + $connection + ); + } + + /** + * @inheritdoc + */ + public function display_options() { + ?> + + +
+ ' . + esc_html__( 'Get API Key', 'wp-mail-smtp' ) . + '' + ); + ?> +
++ + +
+ ' . + esc_html__( 'Get Sender Name', 'wp-mail-smtp' ) . + '' + ); + ?> +
+
+
+ set_header( 'Authorization', 'Bearer ' . $this->connection_options->get( $this->mailer, 'api_key' ) );
+ $this->set_header( 'content-type', 'application/json' );
+ }
+
+ /**
+ * Redefine the way email body is returned.
+ * By default we are sending an array of data.
+ * SendGrid requires a JSON, so we encode the body.
+ *
+ * @since 1.0.0
+ */
+ public function get_body() {
+
+ $body = parent::get_body();
+
+ return wp_json_encode( $body );
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function set_from( $email, $name = '' ) {
+
+ if ( ! filter_var( $email, FILTER_VALIDATE_EMAIL ) ) {
+ return;
+ }
+
+ $from['email'] = $email;
+
+ if ( ! empty( $name ) ) {
+ $from['name'] = $name;
+ }
+
+ $this->set_body_param(
+ array(
+ 'from' => $from,
+ )
+ );
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function set_recipients( $recipients ) {
+
+ if ( empty( $recipients ) ) {
+ return;
+ }
+
+ // Allow for now only these recipient types.
+ $default = array( 'to', 'cc', 'bcc' );
+ $data = array();
+
+ foreach ( $recipients as $type => $emails ) {
+ if (
+ ! in_array( $type, $default, true ) ||
+ empty( $emails ) ||
+ ! is_array( $emails )
+ ) {
+ continue;
+ }
+
+ $data[ $type ] = array();
+
+ // Iterate over all emails for each type.
+ // There might be multiple cc/to/bcc emails.
+ foreach ( $emails as $email ) {
+ $holder = array();
+ $addr = isset( $email[0] ) ? $email[0] : false;
+ $name = isset( $email[1] ) ? $email[1] : false;
+
+ if ( ! filter_var( $addr, FILTER_VALIDATE_EMAIL ) ) {
+ continue;
+ }
+
+ $holder['email'] = $addr;
+ if ( ! empty( $name ) ) {
+ $holder['name'] = $name;
+ }
+
+ array_push( $data[ $type ], $holder );
+ }
+ }
+
+ if ( ! empty( $data ) ) {
+ $this->set_body_param(
+ array(
+ 'personalizations' => array( $data ),
+ )
+ );
+ }
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function set_content( $content ) {
+
+ if ( empty( $content ) ) {
+ return;
+ }
+
+ if ( is_array( $content ) ) {
+
+ $default = array( 'text', 'html' );
+ $data = array();
+
+ foreach ( $content as $type => $body ) {
+ if (
+ ! in_array( $type, $default, true ) ||
+ empty( $body )
+ ) {
+ continue;
+ }
+
+ $content_type = 'text/plain';
+ $content_value = $body;
+
+ if ( $type === 'html' ) {
+ $content_type = 'text/html';
+ }
+
+ $data[] = array(
+ 'type' => $content_type,
+ 'value' => $content_value,
+ );
+ }
+
+ $this->set_body_param(
+ array(
+ 'content' => $data,
+ )
+ );
+ } else {
+ $data['type'] = 'text/html';
+ $data['value'] = $content;
+
+ if ( $this->phpmailer->ContentType === 'text/plain' ) {
+ $data['type'] = 'text/plain';
+ }
+
+ $this->set_body_param(
+ array(
+ 'content' => array( $data ),
+ )
+ );
+ }
+ }
+
+ /**
+ * Redefine the way custom headers are processed for this mailer - they should be in body.
+ *
+ * @since 1.5.0
+ *
+ * @param array $headers
+ */
+ public function set_headers( $headers ) {
+
+ foreach ( $headers as $header ) {
+ $name = isset( $header[0] ) ? $header[0] : false;
+ $value = isset( $header[1] ) ? $header[1] : false;
+
+ $this->set_body_header( $name, $value );
+ }
+
+ // Add custom PHPMailer-specific header.
+ $this->set_body_header( 'X-Mailer', 'WPMailSMTP/Mailer/' . $this->mailer . ' ' . WPMS_PLUGIN_VER );
+ }
+
+ /**
+ * This mailer supports email-related custom headers inside a body of the message.
+ *
+ * @since 1.5.0
+ *
+ * @param string $name
+ * @param string $value
+ */
+ public function set_body_header( $name, $value ) {
+
+ $name = sanitize_text_field( $name );
+ if ( empty( $name ) ) {
+ return;
+ }
+
+ $headers = isset( $this->body['headers'] ) ? (array) $this->body['headers'] : array();
+
+ $headers[ $name ] = $this->sanitize_header_value( $name, $value );
+
+ $this->set_body_param(
+ array(
+ 'headers' => $headers,
+ )
+ );
+ }
+
+ /**
+ * SendGrid accepts an array of files content in body, so we will include all files and send.
+ * Doesn't handle exceeding the limits etc, as this is done and reported by SendGrid API.
+ *
+ * @since 1.0.0
+ *
+ * @param array $attachments The array of attachments data.
+ */
+ public function set_attachments( $attachments ) {
+
+ if ( empty( $attachments ) ) {
+ return;
+ }
+
+ $data = [];
+
+ foreach ( $attachments as $attachment ) {
+ $file = $this->get_attachment_file_content( $attachment );
+
+ if ( $file === false ) {
+ continue;
+ }
+
+ $filetype = str_replace( ';', '', trim( $attachment[4] ) );
+
+ $data[] = [
+ 'content' => base64_encode( $file ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
+ 'type' => $filetype, // string, no ;, no CRLF.
+ 'filename' => $this->get_attachment_file_name( $attachment ), // required string, no CRLF.
+ 'disposition' => in_array( $attachment[6], [ 'inline', 'attachment' ], true ) ? $attachment[6] : 'attachment', // either inline or attachment.
+ 'content_id' => empty( $attachment[7] ) ? '' : trim( (string) $attachment[7] ), // string, no CRLF.
+ ];
+ }
+
+ if ( ! empty( $data ) ) {
+ $this->set_body_param(
+ [
+ 'attachments' => $data,
+ ]
+ );
+ }
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function set_reply_to( $reply_to ) {
+
+ if ( empty( $reply_to ) ) {
+ return;
+ }
+
+ $data = array();
+
+ foreach ( $reply_to as $key => $emails ) {
+ if (
+ empty( $emails ) ||
+ ! is_array( $emails )
+ ) {
+ continue;
+ }
+
+ $addr = isset( $emails[0] ) ? $emails[0] : false;
+ $name = isset( $emails[1] ) ? $emails[1] : false;
+
+ if ( ! filter_var( $addr, FILTER_VALIDATE_EMAIL ) ) {
+ continue;
+ }
+
+ $data['email'] = $addr;
+ if ( ! empty( $name ) ) {
+ $data['name'] = $name;
+ }
+ }
+
+ if ( ! empty( $data ) ) {
+ $this->set_body_param(
+ array(
+ 'reply_to' => $data,
+ )
+ );
+ }
+ }
+
+ /**
+ * SendGrid doesn't support sender or return_path params.
+ * So we do nothing.
+ *
+ * @since 1.0.0
+ *
+ * @param string $from_email
+ */
+ public function set_return_path( $from_email ) {}
+
+ /**
+ * Get a SendGrid-specific response with a helpful error.
+ *
+ * @since 1.2.0
+ *
+ * @return string
+ */
+ public function get_response_error() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh, Generic.Metrics.NestingLevel.MaxExceeded
+
+ $error_text[] = $this->error_message;
+
+ if ( ! empty( $this->response ) ) {
+ $body = wp_remote_retrieve_body( $this->response );
+
+ if ( ! empty( $body->errors ) && is_array( $body->errors ) ) {
+ foreach ( $body->errors as $error ) {
+ if ( ! empty( $error->message ) ) {
+ $message = $error->message;
+ $code = ! empty( $error->field ) ? $error->field : '';
+ $description = ! empty( $error->help ) ? $error->help : '';
+
+ $error_text[] = Helpers::format_error_message( $message, $code, $description );
+ }
+ }
+ } else {
+ $error_text[] = WP::wp_remote_get_response_error_message( $this->response );
+ }
+ }
+
+ return implode( WP::EOL, array_map( 'esc_textarea', array_filter( $error_text ) ) );
+ }
+
+ /**
+ * Get the error code from the SendGrid API response.
+ *
+ * @since 4.8.0
+ *
+ * @return string
+ */
+ public function get_response_error_code() {
+
+ if ( ! empty( $this->response ) ) {
+ $body = wp_remote_retrieve_body( $this->response );
+
+ if ( ! empty( $body->errors ) && is_array( $body->errors ) && ! empty( $body->errors[0]->field ) ) {
+ return $body->errors[0]->field;
+ }
+ }
+
+ return parent::get_response_error_code();
+ }
+
+ /**
+ * Get mailer debug information, that is helpful during support.
+ *
+ * @since 1.2.0
+ *
+ * @return string
+ */
+ public function get_debug_info() {
+
+ $sendgrid_text[] = 'Api Key: ' . ( $this->is_mailer_complete() ? 'Yes' : 'No' );
+
+ return implode( '
', $sendgrid_text );
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function is_mailer_complete() {
+
+ $options = $this->connection_options->get_group( $this->mailer );
+
+ // API key is the only required option.
+ if ( ! empty( $options['api_key'] ) ) {
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Providers/Sendgrid/Options.php b/web/app/plugins/wp-mail-smtp-pro/src/Providers/Sendgrid/Options.php
new file mode 100644
index 00000000..5b3b511a
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Providers/Sendgrid/Options.php
@@ -0,0 +1,148 @@
+ wp_mail_smtp()->assets_url . '/images/providers/sendgrid.svg',
+ 'slug' => 'sendgrid',
+ 'title' => esc_html__( 'SendGrid', 'wp-mail-smtp' ),
+ 'description' => sprintf(
+ wp_kses(
+ /* translators: %1$s - URL to sendgrid.com; %2$s - URL to Sendgrid documentation on wpmailsmtp.com */
+ __( 'SendGrid is a popular transactional email provider that sends more than 35 billion emails every month. If you\'re just starting out, the free plan allows you to send up to 100 emails each day without entering your credit card details.
To get started, read our SendGrid documentation.', 'wp-mail-smtp' ),
+ [
+ 'br' => [],
+ 'a' => [
+ 'href' => [],
+ 'rel' => [],
+ 'target' => [],
+ ],
+ ]
+ ),
+ 'https://sendgrid.com',
+ esc_url( wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/docs/how-to-set-up-the-sendgrid-mailer-in-wp-mail-smtp/', 'SendGrid documentation' ) )
+ ),
+ 'supports' => [
+ 'from_email' => true,
+ 'from_name' => true,
+ 'return_path' => false,
+ 'from_email_force' => true,
+ 'from_name_force' => true,
+ ],
+ ],
+ $connection
+ );
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function display_options() {
+ ?>
+
+
+
+ ' .
+ esc_html__( 'Create API Key', 'wp-mail-smtp' ) .
+ ''
+ );
+ ?>
+
+ Mail Send'
+ );
+ ?>
+
+ + +
+ SendGrid documentation.', 'wp-mail-smtp' ), + [ + 'br' => [], + 'a' => [ + 'href' => [], + 'rel' => [], + 'target' => [], + ], + ] + ), + esc_url( wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/docs/how-to-set-up-the-sendgrid-mailer-in-wp-mail-smtp/#setup', 'SendGrid documentation - setup' ) ) + ); + ?> +
+
+
+ connection = $connection;
+ } else {
+ $this->connection = wp_mail_smtp()->get_connections_manager()->get_primary_connection();
+ }
+
+ $this->options = $this->connection->get_options()->get_group( Options::SLUG );
+ }
+
+ /**
+ * Configure API key authorization: api-key.
+ *
+ * @since 1.6.0
+ * @deprecated 3.9.0 We are no longer using the Sendinblue SDK.
+ *
+ * @return null
+ */
+ protected function get_api_config() {
+
+ _deprecated_function( __METHOD__, '3.9.0' );
+
+ return null;
+ }
+
+ /**
+ * Get the mailer client instance for Account API.
+ *
+ * @since 1.6.0
+ * @deprecated 3.9.0 We are no longer using the Sendinblue SDK.
+ */
+ public function get_account_client() {
+
+ _deprecated_function( __METHOD__, '3.9.0' );
+
+ return null;
+ }
+
+ /**
+ * Get the mailer client instance for Sender API.
+ *
+ * @since 1.6.0
+ * @deprecated 3.9.0 We are no longer using the Sendinblue SDK.
+ */
+ public function get_sender_client() {
+
+ _deprecated_function( __METHOD__, '3.9.0' );
+
+ return null;
+ }
+
+ /**
+ * Get the mailer client instance for SMTP API.
+ *
+ * @since 1.6.0
+ * @deprecated 3.9.0 We are no longer using the Sendinblue SDK.
+ */
+ public function get_smtp_client() {
+
+ _deprecated_function( __METHOD__, '3.9.0' );
+
+ return null;
+ }
+
+ /**
+ * Whether the mailer is ready to be used in API calls.
+ *
+ * @since 1.6.0
+ *
+ * @return bool
+ */
+ public function is_ready() {
+
+ return ! empty( $this->options['api_key'] );
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Providers/Sendinblue/Mailer.php b/web/app/plugins/wp-mail-smtp-pro/src/Providers/Sendinblue/Mailer.php
new file mode 100644
index 00000000..93dc4060
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Providers/Sendinblue/Mailer.php
@@ -0,0 +1,474 @@
+set_header( 'api-key', $this->connection_options->get( $this->mailer, 'api_key' ) );
+ $this->set_header( 'Accept', 'application/json' );
+ $this->set_header( 'content-type', 'application/json' );
+ }
+
+ /**
+ * The list of allowed attachment files extensions.
+ *
+ * @see https://developers.sendinblue.com/reference#sendTransacEmail_attachment__title
+ *
+ * @since 1.6.0
+ *
+ * @var array
+ */
+ // @formatter:off
+ protected $allowed_attach_ext = array( 'xlsx', 'xls', 'ods', 'docx', 'docm', 'doc', 'csv', 'pdf', 'txt', 'gif', 'jpg', 'jpeg', 'png', 'tif', 'tiff', 'rtf', 'bmp', 'cgm', 'css', 'shtml', 'html', 'htm', 'zip', 'xml', 'ppt', 'pptx', 'tar', 'ez', 'ics', 'mobi', 'msg', 'pub', 'eps', 'odt', 'mp3', 'm4a', 'm4v', 'wma', 'ogg', 'flac', 'wav', 'aif', 'aifc', 'aiff', 'mp4', 'mov', 'avi', 'mkv', 'mpeg', 'mpg', 'wmv' );
+ // @formatter:on
+
+ /**
+ * Redefine the way custom headers are processed for this mailer - they should be in body.
+ *
+ * @since 3.9.0
+ *
+ * @param array $headers List of key=>value pairs.
+ */
+ public function set_headers( $headers ) {
+
+ foreach ( $headers as $header ) {
+ $name = isset( $header[0] ) ? $header[0] : false;
+ $value = isset( $header[1] ) ? $header[1] : false;
+
+ $this->set_body_header( $name, $value );
+ }
+
+ // Add custom PHPMailer-specific header.
+ $this->set_body_header( 'X-Mailer', 'WPMailSMTP/Mailer/' . $this->mailer . ' ' . WPMS_PLUGIN_VER );
+ }
+
+ /**
+ * This mailer supports email-related custom headers inside a body of the message.
+ *
+ * @since 3.9.0
+ *
+ * @param string $name Key.
+ * @param string $value Value.
+ */
+ public function set_body_header( $name, $value ) {
+
+ $name = sanitize_text_field( $name );
+
+ if ( empty( $name ) ) {
+ return;
+ }
+
+ $headers = isset( $this->body['headers'] ) ? (array) $this->body['headers'] : [];
+ $headers[ $name ] = $this->sanitize_header_value( $name, $value );
+
+ $this->set_body_param(
+ [
+ 'headers' => $headers,
+ ]
+ );
+ }
+
+ /**
+ * Set the From information for an email.
+ *
+ * @since 1.6.0
+ *
+ * @param string $email
+ * @param string $name
+ */
+ public function set_from( $email, $name ) {
+
+ if ( ! filter_var( $email, FILTER_VALIDATE_EMAIL ) ) {
+ return;
+ }
+
+ $this->body['sender'] = array(
+ 'email' => $email,
+ 'name' => ! empty( $name ) ? WP::sanitize_value( $name ) : '',
+ );
+ }
+
+ /**
+ * Set email recipients: to, cc, bcc.
+ *
+ * @since 1.6.0
+ *
+ * @param array $recipients
+ */
+ public function set_recipients( $recipients ) {
+
+ if ( empty( $recipients ) ) {
+ return;
+ }
+
+ // Allow for now only these recipient types.
+ $default = array( 'to', 'cc', 'bcc' );
+ $data = array();
+
+ foreach ( $recipients as $type => $emails ) {
+
+ if (
+ ! in_array( $type, $default, true ) ||
+ empty( $emails ) ||
+ ! is_array( $emails )
+ ) {
+ continue;
+ }
+
+ $data[ $type ] = array();
+
+ // Iterate over all emails for each type.
+ // There might be multiple cc/to/bcc emails.
+ foreach ( $emails as $email ) {
+ $holder = array();
+ $addr = isset( $email[0] ) ? $email[0] : false;
+ $name = isset( $email[1] ) ? $email[1] : false;
+
+ if ( ! filter_var( $addr, FILTER_VALIDATE_EMAIL ) ) {
+ continue;
+ }
+
+ $holder['email'] = $addr;
+ if ( ! empty( $name ) ) {
+ $holder['name'] = $name;
+ }
+
+ array_push( $data[ $type ], $holder );
+ }
+ }
+
+ foreach ( $data as $type => $type_recipients ) {
+ $this->body[ $type ] = $type_recipients;
+ }
+ }
+
+ /**
+ * @inheritDoc
+ *
+ * @since 1.6.0
+ */
+ public function set_subject( $subject ) {
+
+ $this->body['subject'] = $subject;
+ }
+
+ /**
+ * Set email content.
+ *
+ * @since 1.6.0
+ *
+ * @param string|array $content
+ */
+ public function set_content( $content ) {
+
+ if ( empty( $content ) ) {
+ return;
+ }
+
+ if ( is_array( $content ) ) {
+
+ if ( ! empty( $content['text'] ) ) {
+ $this->body['textContent'] = $content['text'];
+ }
+
+ if ( ! empty( $content['html'] ) ) {
+ $this->body['htmlContent'] = $content['html'];
+ }
+ } else {
+ if ( $this->phpmailer->ContentType === 'text/plain' ) {
+ $this->body['textContent'] = $content;
+ } else {
+ $this->body['htmlContent'] = $content;
+ }
+ }
+ }
+
+ /**
+ * Doesn't support this.
+ *
+ * @since 1.6.0
+ *
+ * @param string $email
+ */
+ public function set_return_path( $email ) {
+
+ }
+
+ /**
+ * Set the Reply To headers if not set already.
+ *
+ * @since 1.6.0
+ *
+ * @param array $emails
+ */
+ public function set_reply_to( $emails ) {
+
+ if ( empty( $emails ) ) {
+ return;
+ }
+
+ $data = array();
+
+ foreach ( $emails as $user ) {
+ $holder = array();
+ $addr = isset( $user[0] ) ? $user[0] : false;
+ $name = isset( $user[1] ) ? $user[1] : false;
+
+ if ( ! filter_var( $addr, FILTER_VALIDATE_EMAIL ) ) {
+ continue;
+ }
+
+ $holder['email'] = $addr;
+ if ( ! empty( $name ) ) {
+ $holder['name'] = $name;
+ }
+
+ $data[] = $holder;
+ }
+
+ if ( ! empty( $data ) ) {
+ $this->body['replyTo'] = $data[0];
+ }
+ }
+
+ /**
+ * Set attachments for an email.
+ *
+ * @since 1.6.0
+ *
+ * @param array $attachments The array of attachments data.
+ */
+ public function set_attachments( $attachments ) {
+
+ if ( empty( $attachments ) ) {
+ return;
+ }
+
+ foreach ( $attachments as $attachment ) {
+
+ $ext = pathinfo( $attachment[1], PATHINFO_EXTENSION );
+
+ if ( ! in_array( $ext, $this->allowed_attach_ext, true ) ) {
+ continue;
+ }
+
+ $file = $this->get_attachment_file_content( $attachment );
+
+ if ( $file === false ) {
+ continue;
+ }
+
+ $this->body['attachment'][] = [
+ 'name' => $this->get_attachment_file_name( $attachment ),
+ 'content' => base64_encode( $file ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
+ ];
+ }
+ }
+
+ /**
+ * Get the email body.
+ *
+ * @since 1.6.0
+ * @since 3.9.0 Returns email body array instead of `SendSmtpEmail` object.
+ * @since 3.10.0 Returns JSON encoded email body instead of array.
+ *
+ * @return string
+ */
+ public function get_body() {
+
+ /**
+ * Filters Sendinblue email body.
+ *
+ * @since 3.5.0
+ *
+ * @param array $body Email body.
+ */
+ $body = apply_filters( 'wp_mail_smtp_providers_sendinblue_mailer_get_body', $this->body );
+
+ return wp_json_encode( $body );
+ }
+
+ /**
+ * We might need to do something after the email was sent to the API.
+ * In this method we preprocess the response from the API.
+ *
+ * @since 1.6.0
+ * @since 3.9.0 Expect a generic class object instead of `CreateSmtpEmail`.
+ *
+ * @param mixed $response Response from the API.
+ */
+ protected function process_response( $response ) {
+
+ parent::process_response( $response );
+
+ if ( $this->has_message_id() ) {
+ $this->phpmailer->MessageID = $this->response['body']->messageId;
+ $this->verify_sent_status = true;
+ }
+ }
+
+ /**
+ * Get a Sendinblue-specific response with a helpful error.
+ *
+ * @since 3.9.0
+ *
+ * @return string
+ */
+ public function get_response_error() {
+
+ $error_text = [];
+
+ if ( ! empty( $this->error_message ) ) {
+ $error_text[] = $this->error_message;
+ }
+
+ if ( ! empty( $this->response ) ) {
+ $body = wp_remote_retrieve_body( $this->response );
+
+ if ( ! empty( $body->message ) ) {
+ $error_text[] = Helpers::format_error_message( $body->message, ! empty( $body->code ) ? $body->code : '' );
+ } else {
+ $error_text[] = WP::wp_remote_get_response_error_message( $this->response );
+ }
+ }
+
+ return implode( WP::EOL, array_map( 'esc_textarea', array_filter( $error_text ) ) );
+ }
+
+ /**
+ * Get the error code from the Brevo API response.
+ *
+ * @since 4.8.0
+ *
+ * @return string
+ */
+ public function get_response_error_code() {
+
+ if ( ! empty( $this->response ) ) {
+ $body = wp_remote_retrieve_body( $this->response );
+
+ if ( ! empty( $body->code ) ) {
+ return $body->code;
+ }
+ }
+
+ return parent::get_response_error_code();
+ }
+
+ /**
+ * Check whether the response has `messageId` property.
+ *
+ * @since 3.9.0
+ *
+ * @return bool
+ */
+ private function has_message_id() {
+
+ if (
+ ! in_array(
+ wp_remote_retrieve_response_code( $this->response ),
+ [ $this->email_sent_code, $this->email_scheduled_code ],
+ true
+ ) ||
+ empty( $this->response['body']->messageId )
+ ) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Check whether the email was sent.
+ *
+ * @since 1.6.0
+ * @since 3.9.0 Check if `$this->response` has `messageId` property to check if the email was sent.
+ *
+ * @return bool
+ */
+ public function is_email_sent() {
+
+ /** This filter is documented in src/Providers/MailerAbstract.php. */
+ return apply_filters( 'wp_mail_smtp_providers_mailer_is_email_sent', $this->has_message_id(), $this->mailer ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
+ }
+
+ /**
+ * @inheritdoc
+ *
+ * @since 1.6.0
+ */
+ public function get_debug_info() {
+
+ $mailjet_text[] = 'API Key: ' . ( $this->is_mailer_complete() ? 'Yes' : 'No' );
+
+ return implode( '
', $mailjet_text );
+ }
+
+ /**
+ * @inheritdoc
+ *
+ * @since 1.6.0
+ */
+ public function is_mailer_complete() {
+
+ $options = $this->connection_options->get_group( $this->mailer );
+
+ // API key is the only required option.
+ if ( ! empty( $options['api_key'] ) ) {
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Providers/Sendinblue/Options.php b/web/app/plugins/wp-mail-smtp-pro/src/Providers/Sendinblue/Options.php
new file mode 100644
index 00000000..60c44109
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Providers/Sendinblue/Options.php
@@ -0,0 +1,184 @@
+get_connections_manager()->get_primary_connection();
+ }
+
+ $description = sprintf(
+ wp_kses( /* translators: %1$s - URL to brevo.com site. */
+ __( 'Brevo (formerly Sendinblue) is one of our recommended mailers. It\'s a transactional email provider with scalable price plans, so it\'s suitable for any size of business.
If you\'re just starting out, you can use Brevo\'s free plan to send up to 300 emails a day. You don\'t need to use a credit card to try it out. When you\'re ready, you can upgrade to a higher plan to increase your sending limits.', 'wp-mail-smtp' ) .
+ '
' .
+ /* translators: %2$s - URL to wpmailsmtp.com doc. */
+ __( 'To get started, read our Brevo documentation.', 'wp-mail-smtp' ),
+ [
+ 'strong' => true,
+ 'br' => true,
+ 'a' => [
+ 'href' => true,
+ 'rel' => true,
+ 'target' => true,
+ ],
+ ]
+ ),
+ 'https://wpmailsmtp.com/go/sendinblue/',
+ esc_url( wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/docs/how-to-set-up-the-sendinblue-mailer-in-wp-mail-smtp/', 'Brevo documentation' ) )
+ );
+
+ $api_key = $connection->get_options()->get( self::SLUG, 'api_key' );
+
+ if ( empty( $api_key ) ) {
+ $description .= sprintf(
+ '
', + 'https://wpmailsmtp.com/go/sendinblue/', + esc_html__( 'Get Brevo Now (Free)', 'wp-mail-smtp' ) + ); + } + + $description .= '
' . + esc_html__( 'Transparency and Disclosure', 'wp-mail-smtp' ) . + '' . + esc_html__( 'We believe in full transparency. The Brevo (formerly Sendinblue) links above are tracking links as part of our partnership with Brevo. We can recommend just about any SMTP service, but we only recommend products that we believe will add value to our users.', 'wp-mail-smtp' ) . + '
'; + + parent::__construct( + [ + 'logo_url' => wp_mail_smtp()->assets_url . '/images/providers/brevo.svg', + 'slug' => self::SLUG, + 'title' => esc_html__( 'Brevo', 'wp-mail-smtp' ), + 'php' => '5.6', + 'description' => $description, + 'supports' => [ + 'from_email' => true, + 'from_name' => true, + 'return_path' => false, + 'from_email_force' => true, + 'from_name_force' => true, + ], + 'recommended' => true, + ], + $connection + ); + } + + /** + * Output the mailer provider options. + * + * @since 1.6.0 + */ + public function display_options() { + + // Do not display options if PHP version is not correct. + if ( ! $this->is_php_correct() ) { + $this->display_php_warning(); + + return; + } + ?> + + +
+ ' . + esc_html__( 'Get v3 API Key', 'wp-mail-smtp' ) . + '' + ); + ?> +
++ + +
+ Brevo documentation.', 'wp-mail-smtp' ), + [ + 'br' => [], + 'a' => [ + 'href' => [], + 'rel' => [], + 'target' => [], + ], + ] + ), + esc_url( wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/docs/how-to-set-up-the-sendinblue-mailer-in-wp-mail-smtp/#setup-smtp', 'Brevo documentation' ) ) + ); + ?> +
+
+
+ set_header( 'Authorization', 'Bearer ' . $this->connection_options->get( $this->mailer, 'api_key' ) );
+ $this->set_header( 'Accept', 'application/json' );
+ $this->set_header( 'Content-Type', 'application/json' );
+ }
+
+ /**
+ * Redefine the way custom headers are processed for this mailer - they should be in body.
+ *
+ * @since 3.4.0
+ *
+ * @param array $headers Headers array.
+ */
+ public function set_headers( $headers ) {
+
+ foreach ( $headers as $header ) {
+ $name = isset( $header[0] ) ? $header[0] : false;
+ $value = isset( $header[1] ) ? $header[1] : false;
+
+ $this->set_body_header( $name, $value );
+ }
+
+ // Add custom header.
+ $this->set_body_header( 'X-Mailer', 'WPMailSMTP/Mailer/' . $this->mailer . ' ' . WPMS_PLUGIN_VER );
+ }
+
+ /**
+ * This mailer supports email-related custom headers inside a body of the message.
+ *
+ * @since 3.4.0
+ *
+ * @param string $name Header name.
+ * @param string $value Header value.
+ */
+ public function set_body_header( $name, $value ) {
+
+ $name = sanitize_text_field( $name );
+
+ if ( empty( $name ) ) {
+ return;
+ }
+
+ $headers = isset( $this->body['Headers'] ) ? (array) $this->body['Headers'] : [];
+
+ $headers[ $name ] = $this->sanitize_header_value( $name, $value );
+
+ $this->set_body_param(
+ [
+ 'Headers' => $headers,
+ ]
+ );
+ }
+
+ /**
+ * Set the From information for an email.
+ *
+ * @since 3.4.0
+ *
+ * @param string $email The sender email address.
+ * @param string $name The sender name.
+ */
+ public function set_from( $email, $name ) {
+
+ if ( ! filter_var( $email, FILTER_VALIDATE_EMAIL ) ) {
+ return;
+ }
+
+ $this->set_body_param(
+ [
+ 'From' => $this->address_format( [ $email, $name ] ),
+ ]
+ );
+ }
+
+ /**
+ * Set email recipients: to, cc, bcc.
+ *
+ * @since 3.4.0
+ *
+ * @param array $recipients Email recipients.
+ */
+ public function set_recipients( $recipients ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
+
+ if ( empty( $recipients ) ) {
+ return;
+ }
+
+ // Allow only these recipient types.
+ $allowed_types = [ 'to', 'cc', 'bcc' ];
+ $data = [];
+
+ foreach ( $recipients as $type => $emails ) {
+ if (
+ ! in_array( $type, $allowed_types, true ) ||
+ empty( $emails ) ||
+ ! is_array( $emails )
+ ) {
+ continue;
+ }
+
+ $type = ucfirst( $type );
+
+ // Iterate over all emails for each type.
+ // There might be multiple cc/to/bcc emails.
+ foreach ( $emails as $email ) {
+ if ( ! isset( $email[0] ) || ! filter_var( $email[0], FILTER_VALIDATE_EMAIL ) ) {
+ continue;
+ }
+
+ $data[ $type ][] = $this->address_format( $email );
+ }
+ }
+
+ if ( ! empty( $data ) ) {
+ $this->set_body_param( $data );
+ }
+ }
+
+ /**
+ * Set the Reply To information for an email.
+ *
+ * @since 3.4.0
+ *
+ * @param array $emails Reply To email addresses.
+ */
+ public function set_reply_to( $emails ) {
+
+ if ( empty( $emails ) ) {
+ return;
+ }
+
+ $data = [];
+
+ foreach ( $emails as $email ) {
+ if ( ! isset( $email[0] ) || ! filter_var( $email[0], FILTER_VALIDATE_EMAIL ) ) {
+ continue;
+ }
+
+ $data[] = $this->address_format( $email );
+ }
+
+ if ( ! empty( $data ) ) {
+ $this->set_body_param(
+ [
+ 'ReplyTo' => $data,
+ ]
+ );
+ }
+ }
+
+ /**
+ * Set email subject.
+ *
+ * @since 3.4.0
+ *
+ * @param string $subject Email subject.
+ */
+ public function set_subject( $subject ) {
+
+ $this->set_body_param(
+ [
+ 'Subject' => $subject,
+ ]
+ );
+ }
+
+ /**
+ * Set email content.
+ *
+ * @since 3.4.0
+ *
+ * @param string|array $content Email content.
+ */
+ public function set_content( $content ) {
+
+ if ( empty( $content ) ) {
+ return;
+ }
+
+ if ( is_array( $content ) ) {
+ if ( ! empty( $content['text'] ) ) {
+ $this->set_body_param(
+ [
+ 'ContentType' => 'plain',
+ 'PlainContent' => $content['text'],
+ ]
+ );
+ }
+
+ if ( ! empty( $content['html'] ) ) {
+ $this->set_body_param(
+ [
+ 'ContentType' => 'html',
+ 'HTMLContent' => $content['html'],
+ ]
+ );
+ }
+ } else {
+ if ( $this->phpmailer->ContentType === 'text/plain' ) {
+ $this->set_body_param(
+ [
+ 'ContentType' => 'plain',
+ 'PlainContent' => $content,
+ ]
+ );
+ } else {
+ $this->set_body_param(
+ [
+ 'ContentType' => 'html',
+ 'HTMLContent' => $content,
+ ]
+ );
+ }
+ }
+ }
+
+ /**
+ * Set attachments for an email.
+ *
+ * @since 3.4.0
+ *
+ * @param array $attachments Attachments array.
+ */
+ public function set_attachments( $attachments ) {
+
+ if ( empty( $attachments ) ) {
+ return;
+ }
+
+ $data = $this->prepare_attachments( $attachments );
+
+ if ( ! empty( $data ) ) {
+ $this->set_body_param(
+ [
+ 'Attachments' => $data,
+ ]
+ );
+ }
+ }
+
+ /**
+ * Prepare attachments data for SendLayer API.
+ *
+ * @since 3.4.0
+ *
+ * @param array $attachments Array of attachments.
+ *
+ * @return array
+ */
+ protected function prepare_attachments( $attachments ) {
+
+ $data = [];
+
+ foreach ( $attachments as $attachment ) {
+ $file = $this->get_attachment_file_content( $attachment );
+
+ if ( $file === false ) {
+ continue;
+ }
+
+ $filetype = str_replace( ';', '', trim( $attachment[4] ) );
+
+ $data[] = [
+ 'Filename' => empty( $attachment[2] ) ? 'file-' . wp_hash( microtime() ) . '.' . $filetype : trim( $attachment[2] ),
+ // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
+ 'Content' => base64_encode( $file ),
+ 'Type' => $attachment[4],
+ 'Disposition' => in_array( $attachment[6], [ 'inline', 'attachment' ], true ) ? $attachment[6] : 'attachment',
+ 'ContentId' => empty( $attachment[7] ) ? '' : trim( (string) $attachment[7] ),
+ ];
+ }
+
+ return $data;
+ }
+
+ /**
+ * Doesn't support this.
+ * So we do nothing.
+ *
+ * @since 3.4.0
+ *
+ * @param string $email Return Path email address.
+ */
+ public function set_return_path( $email ) { }
+
+ /**
+ * Redefine the way email body is returned.
+ * By default, we are sending an array of data.
+ * SendLayer requires a JSON, so we encode the body.
+ *
+ * @since 3.4.0
+ */
+ public function get_body() {
+
+ $body = parent::get_body();
+
+ return wp_json_encode( $body );
+ }
+
+ /**
+ * We might need to do something after the email was sent to the API.
+ * In this method we preprocess the response from the API.
+ *
+ * @since 3.4.0
+ *
+ * @param mixed $response Response data.
+ */
+ protected function process_response( $response ) {
+
+ parent::process_response( $response );
+
+ if (
+ ! is_wp_error( $response ) &&
+ ! empty( $this->response['body']->MessageID )
+ ) {
+ $this->phpmailer->addCustomHeader( 'X-Msg-ID', $this->response['body']->MessageID );
+ $this->verify_sent_status = true;
+ }
+ }
+
+ /**
+ * Get a SendLayer-specific response with a helpful error.
+ *
+ * @since 3.4.0
+ *
+ * @return string
+ */
+ public function get_response_error() { // phpcs:ignore Generic.Metrics.NestingLevel.MaxExceeded
+
+ $error_text[] = $this->error_message;
+
+ if ( ! empty( $this->response ) ) {
+ $body = wp_remote_retrieve_body( $this->response );
+
+ // phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
+ if ( ! empty( $body->Errors ) && is_array( $body->Errors ) ) {
+ foreach ( $body->Errors as $error ) {
+ if ( ! empty( $error->Message ) ) {
+ $message = $error->Message;
+ $code = ! empty( $error->Code ) ? $error->Code : '';
+
+ $error_text[] = Helpers::format_error_message( $message, $code );
+ }
+ }
+ } else {
+ $error_text[] = WP::wp_remote_get_response_error_message( $this->response );
+ }
+ // phpcs:enable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
+ }
+
+ return implode( WP::EOL, array_map( 'esc_textarea', array_filter( $error_text ) ) );
+ }
+
+ /**
+ * Get the error code from the SendLayer API response.
+ *
+ * @since 4.8.0
+ *
+ * @return string
+ */
+ public function get_response_error_code() {
+
+ if ( ! empty( $this->response ) ) {
+ $body = wp_remote_retrieve_body( $this->response );
+
+ // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
+ if ( ! empty( $body->Errors ) && is_array( $body->Errors ) && ! empty( $body->Errors[0]->Code ) ) {
+ return $body->Errors[0]->Code; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
+ }
+ }
+
+ return parent::get_response_error_code();
+ }
+
+ /**
+ * Get mailer debug information, that is helpful during support.
+ *
+ * @since 3.4.0
+ *
+ * @return string
+ */
+ public function get_debug_info() {
+
+ $options = $this->connection_options->get_group( $this->mailer );
+
+ $text[] = '' . esc_html__( 'API Key:', 'wp-mail-smtp' ) . ' ' .
+ ( ! empty( $options['api_key'] ) ? 'Yes' : 'No' );
+
+ return implode( '
', $text );
+ }
+
+ /**
+ * Whether the mailer has all its settings correctly set up and saved.
+ *
+ * This mailer is configured when `server_api_token` setting is defined.
+ *
+ * @since 3.4.0
+ *
+ * @return bool
+ */
+ public function is_mailer_complete() {
+
+ $options = $this->connection_options->get_group( $this->mailer );
+
+ if ( ! empty( $options['api_key'] ) ) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Prepare address param.
+ *
+ * @since 3.4.0
+ *
+ * @param array $address Address array.
+ *
+ * @return array
+ */
+ private function address_format( $address ) {
+
+ $result = [];
+ $email = isset( $address[0] ) ? $address[0] : false;
+ $name = isset( $address[1] ) ? $address[1] : false;
+
+ $result['Email'] = $email;
+
+ if ( ! empty( $name ) ) {
+ $result['Name'] = $name;
+ }
+
+ return $result;
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Providers/Sendlayer/Options.php b/web/app/plugins/wp-mail-smtp-pro/src/Providers/Sendlayer/Options.php
new file mode 100644
index 00000000..83f003ae
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Providers/Sendlayer/Options.php
@@ -0,0 +1,226 @@
+get_connections_manager()->get_primary_connection();
+ }
+
+ $description = sprintf(
+ wp_kses(
+ /* translators: %1$s - URL to sendlayer.com; %2$s - URL to SendLayer documentation on wpmailsmtp.com. */
+ __( 'SendLayer is our #1 recommended mailer. It offers affordable pricing and is easy to set up, which makes it an excellent option for WordPress sites. With SendLayer, your domain will be authenticated so all your outgoing emails reach your customers\' inboxes. Our detailed documentation will walk you through the entire process, start to finish. When you sign up for a free trial, you can send your first emails at no charge.', 'wp-mail-smtp' ), // phpcs:ignore WordPress.WP.I18n.NoHtmlWrappedStrings
+ [
+ 'strong' => [],
+ 'a' => [
+ 'href' => [],
+ 'rel' => [],
+ 'target' => [],
+ ],
+ 'span' => [
+ 'class' => [],
+ ],
+ ]
+ ),
+ // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound, WordPress.Security.NonceVerification.Recommended
+ esc_url( wp_mail_smtp()->get_utm_url( 'https://sendlayer.com/wp-mail-smtp/', [ 'source' => 'wpmailsmtpplugin', 'medium' => 'WordPress', 'content' => isset( $_GET['page'] ) && $_GET['page'] === 'wp-mail-smtp-setup-wizard' ? 'Setup Wizard - Mailer Description' : 'Plugin Settings - Mailer Description' ] ) ),
+ // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound
+ esc_url( wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/docs/how-to-set-up-the-sendlayer-mailer-in-wp-mail-smtp/', 'SendLayer Documentation' ) )
+ );
+
+ parent::__construct(
+ [
+ 'logo_url' => wp_mail_smtp()->assets_url . '/images/providers/sendlayer.svg',
+ 'slug' => self::SLUG,
+ 'title' => esc_html__( 'SendLayer', 'wp-mail-smtp' ),
+ 'description' => $description,
+ 'recommended' => true,
+ 'supports' => [
+ 'from_email' => true,
+ 'from_name' => true,
+ 'return_path' => false,
+ 'from_email_force' => true,
+ 'from_name_force' => true,
+ ],
+ ],
+ $connection
+ );
+ }
+
+ /**
+ * Output the mailer provider options.
+ *
+ * @since 3.4.0
+ */
+ public function display_options() {
+
+ // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound
+ $get_api_key_url = wp_mail_smtp()->get_utm_url( 'https://app.sendlayer.com/settings/api/', ['source' => 'wpmailsmtpplugin', 'medium' => 'WordPress', 'content' => 'Plugin Settings - Get API Key'] );
+ $is_const = $this->connection_options->is_const_defined( $this->get_slug(), 'api_key' );
+ $has_api_key = $is_const || ! empty( $this->connection_options->get( 'sendlayer', 'api_key' ) );
+ $is_quick_connect = (bool) $this->connection_options->get( 'sendlayer', 'quick_connect' );
+ $is_shared_domain = $is_quick_connect && (bool) $this->connection_options->get( 'sendlayer', 'is_shared_domain' );
+ $sender_domain = $is_shared_domain ? $this->connection_options->get( 'sendlayer', 'sender_domain' ) : '';
+
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+ $is_new_additional = ! $this->connection->is_primary() && isset( $_GET['mode'] ) && $_GET['mode'] === 'new';
+ ?>
+
+
+ + + + +
++ + + +
+ + + + +
+ + + + +
++ +
++ SendLayer dashboard to change it here. Check our documentation on how to add a custom domain.', 'wp-mail-smtp' ), + [ + 'a' => [ + 'href' => [], + 'rel' => [], + 'target' => [], + ], + ] + ), + // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound + esc_url( wp_mail_smtp()->get_utm_url( 'https://app.sendlayer.com/', [ 'source' => 'wpmailsmtpplugin', 'medium' => 'WordPress', 'content' => 'Plugin Settings - Domain Link' ] ) ), + // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound + esc_url( wp_mail_smtp()->get_utm_url( 'https://sendlayer.com/docs/authorizing-your-domain/', [ 'source' => 'wpmailsmtpplugin', 'medium' => 'WordPress', 'content' => 'Plugin Settings - Custom Domain Documentation' ] ) ) + ); + ?> +
++ + + +
+ ' . + esc_html__( 'Get API Key', 'wp-mail-smtp' ) . + '' + ); + ?> +
++ + + + usage = new QuickConnectUsage(); + } + + /** + * Register hooks. + * + * @since 4.8.0 + */ + public function hooks() { + + add_action( 'wp_ajax_wp_mail_smtp_sendlayer_connect', [ $this, 'ajax_init_connect' ] ); + add_action( 'init', [ $this, 'handle_ownership_verification_callback' ] ); + add_action( 'admin_init', [ $this, 'handle_auth_complete' ] ); + add_action( 'admin_init', [ $this, 'display_notice' ] ); + add_action( 'admin_init', [ $this, 'handle_disconnect' ] ); + add_action( 'wp_ajax_wp_mail_smtp_sendlayer_disconnect', [ $this, 'ajax_disconnect' ] ); + add_action( 'wp_mail_smtp_admin_connection_settings_display_after_from_email_setting_row', [ $this, 'display_from_email_field' ], 10, 3 ); + add_filter( 'wp_mail_smtp_options_get', [ $this, 'filter_option' ], 10, 3 ); + add_action( 'wp_mail_smtp_admin_pages_before_content', [ $this, 'display_sendlayer_education_banner' ] ); + add_filter( 'wp_mail_smtp_admin_connection_settings_process_data', [ $this, 'process_settings_data' ], 10, 2 ); + + $this->usage->hooks(); + } + + /** + * AJAX handler: Initiate the connect session with the marketing site. + * + * Called when the "Connect to SendLayer" button is clicked. + * + * @since 4.8.0 + */ + public function ajax_init_connect() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded + + // Verify nonce. + check_ajax_referer( 'wp-mail-smtp-sendlayer-connect', 'nonce' ); + + // Check for permissions. + if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_global_options() ) ) { + wp_send_json_error( + [ + 'message' => esc_html__( 'You do not have permission to perform this action.', 'wp-mail-smtp' ), + 'error_code' => 'plugin.init_connect.permission_denied', + ] + ); + } + + // Accept the return URL from the initiator page (settings, wizard, additional connection). + // This is where the user ends up after the flow completes. + if ( empty( $_POST['return_url'] ) ) { + wp_send_json_error( + [ + 'message' => $this->get_generic_error_message(), + 'error_code' => 'plugin.init_connect.missing_return_url', + ] + ); + } + + // Validate it's a local URL to prevent open redirect. + $return_url = wp_validate_redirect( + esc_url_raw( wp_unslash( $_POST['return_url'] ) ), + false + ); + + if ( ! $return_url ) { + wp_send_json_error( + [ + 'message' => $this->get_generic_error_message(), + 'error_code' => 'plugin.init_connect.invalid_return_url', + ] + ); + } + + $connection_id = ! empty( $_POST['connection_id'] ) ? sanitize_key( $_POST['connection_id'] ) : ''; + + // Optional mode flag. Currently only `backup_mailer` is supported — the + // return handler will create a new additional connection and assign it + // as the backup connection in one OAuth round-trip. + $mode = ! empty( $_POST['connect_args']['mode'] ) ? sanitize_key( $_POST['connect_args']['mode'] ) : ''; + $allowed_modes = [ 'backup_mailer' ]; + $mode = in_array( $mode, $allowed_modes, true ) ? $mode : ''; + + // Build the redirect URL on the general settings page. + // The auth handler always fires here; return_url is the final clean destination. + $redirect_args = [ + 'wp_mail_smtp_sendlayer_quick_connect_auth_complete' => 1, + 'nonce' => wp_create_nonce( 'wp_mail_smtp_sendlayer_quick_connect' ), + 'return_url' => rawurlencode( $return_url ), + ]; + + if ( ! empty( $connection_id ) ) { + $redirect_args['connection_id'] = $connection_id; + } + + if ( ! empty( $mode ) ) { + $redirect_args['mode'] = $mode; + } + + $redirect_url = add_query_arg( $redirect_args, wp_mail_smtp()->get_admin()->get_admin_page_url() ); + + // Generate a challenge secret for HMAC-based ownership verification. + $challenge_secret = bin2hex( random_bytes( 16 ) ); + + set_transient( 'wp_mail_smtp_sendlayer_quick_connect_challenge_secret', $challenge_secret, 300 ); + + // POST to the marketing site start-session endpoint. + $response = wp_remote_post( + $this->get_marketing_site_url() . '/wp-json/smtp-plugin-connect/v1/start-session', + [ + 'timeout' => 30, + 'body' => [ + 'site_url' => $this->get_site_url(), + 'return_url' => $redirect_url, + 'challenge_secret' => $challenge_secret, + 'source' => 'wpms', + ], + ] + ); + + if ( is_wp_error( $response ) ) { + $error_code = 'plugin.init_connect.start_session_request_to_site.connection_failed'; + + DebugEvents::add( + 'SendLayer Quick Connect: ' . $error_code . ' — ' . $response->get_error_message() + ); + + wp_send_json_error( + [ + 'message' => $this->get_generic_error_message(), + 'error_code' => $error_code, + ] + ); + } + + $response_code = wp_remote_retrieve_response_code( $response ); + $response_body = json_decode( wp_remote_retrieve_body( $response ), true ); + + // Non-200 response. + if ( $response_code !== 200 ) { + // Expected error — site returned a parseable error code. + $error_code = ! empty( $response_body['code'] ) + ? $this->sanitize_error_code( $response_body['code'] ) + : 'plugin.init_connect.start_session_request_to_site.unexpected_error_response'; + + DebugEvents::add( + 'SendLayer Quick Connect: ' . $error_code . ' — HTTP ' . $response_code + ); + + wp_send_json_error( + [ + 'message' => $this->get_generic_error_message(), + 'error_code' => $error_code, + ] + ); + } + + // Unexpected success — 200 but missing expected data. + if ( empty( $response_body['session_id'] ) ) { + $error_code = 'plugin.init_connect.start_session_request_to_site.unexpected_success_response'; + + DebugEvents::add( + 'SendLayer Quick Connect: ' . $error_code . ' — missing session_id' + ); + + wp_send_json_error( + [ + 'message' => $this->get_generic_error_message(), + 'error_code' => $error_code, + ] + ); + } + + $session_id = sanitize_text_field( $response_body['session_id'] ); + + $redirect_url = add_query_arg( 'session', $session_id, $this->get_marketing_site_url() . '/smtp-plugin-connect' ); + + // Add UTM parameters to the redirect URL for tracking which button initiated the flow. + $utm_content = ! empty( $_POST['connect_args']['utm_content'] ) ? sanitize_text_field( wp_unslash( $_POST['connect_args']['utm_content'] ) ) : 'Quick Connect'; + + // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound + $redirect_url = wp_mail_smtp()->get_utm_url( $redirect_url, [ 'source' => 'wpmailsmtpplugin', 'medium' => 'WordPress', 'content' => $utm_content ] ); + + wp_send_json_success( + [ + 'redirect_url' => $redirect_url, + ] + ); + } + + /** + * Handle the ownership verification callback. + * + * The marketing site sends a server-to-server GET request with a `nonce` + * query parameter during the start-session process. We compute an + * HMAC-SHA256 signature using the stored challenge secret and return it + * for verification. + * + * Listens for the `wp_mail_smtp_sendlayer_quick_connect_verify` GET parameter. + * + * @since 4.8.0 + */ + public function handle_ownership_verification_callback() { + + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + if ( ! isset( $_GET['wp_mail_smtp_sendlayer_quick_connect_verify'] ) ) { + return; + } + + // Read the nonce from the GET query parameter. + // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + if ( empty( $_GET['nonce'] ) ) { + wp_send_json_error( + [ + 'message' => 'Missing nonce.', + 'code' => 'missing_nonce', + ], + 400 + ); + } + + // Retrieve the stored challenge secret. + $challenge_secret = get_transient( 'wp_mail_smtp_sendlayer_quick_connect_challenge_secret' ); + + if ( empty( $challenge_secret ) ) { + wp_send_json_error( + [ + 'message' => 'No active verification session.', + 'code' => 'no_active_session', + ], + 400 + ); + } + + // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + $nonce = sanitize_text_field( wp_unslash( $_GET['nonce'] ) ); + $signature = hash_hmac( 'sha256', $nonce, $challenge_secret ); + + // Delete the transient after use (single-use). + delete_transient( 'wp_mail_smtp_sendlayer_quick_connect_challenge_secret' ); + + wp_send_json( [ 'signature' => $signature ] ); + } + + /** + * Handle the return from the marketing site signup flow. + * + * Handles both success (`_auth_complete`) and error (`_auth_error`) redirects. + * On success, exchanges the code for an API key, stores it, and redirects. + * On error, forwards the error code to the notice display via redirect. + * + * @since 4.8.0 + */ + public function handle_auth_complete() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded, Generic.Metrics.CyclomaticComplexity.TooHigh + + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + if ( + empty( $_GET['wp_mail_smtp_sendlayer_quick_connect_auth_complete'] ) || + ! wp_mail_smtp()->get_admin()->is_admin_page() + ) { + return; + } + + // Resolve the return URL early so every exit path redirects to it. + // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + $return_url = ! empty( $_GET['return_url'] ) + ? wp_validate_redirect( esc_url_raw( wp_unslash( $_GET['return_url'] ) ), wp_mail_smtp()->get_admin()->get_admin_page_url() ) + : wp_mail_smtp()->get_admin()->get_admin_page_url(); + + if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_global_options() ) ) { + $this->redirect_with_result( $return_url, 'plugin.return.permission_denied' ); + } + + // Verify the nonce that was appended to the return URL during session init. + // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + $nonce = isset( $_GET['nonce'] ) + ? sanitize_text_field( wp_unslash( $_GET['nonce'] ) ) + : ''; + + if ( ! wp_verify_nonce( $nonce, 'wp_mail_smtp_sendlayer_quick_connect' ) ) { + $this->redirect_with_result( $return_url, 'plugin.return.invalid_nonce' ); + } + + if ( ! empty( $_GET['exit'] ) ) { + wp_safe_redirect( $return_url ); + exit; + } + + // If the marketing site redirected with an error, forward to the notice display. + if ( ! empty( $_GET['error'] ) ) { + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + $error_code = $this->sanitize_error_code( wp_unslash( $_GET['error'] ) ); + + $this->redirect_with_result( $return_url, $error_code ); + } + + $exchange_code = isset( $_GET['exchange_code'] ) + ? sanitize_text_field( wp_unslash( $_GET['exchange_code'] ) ) + : ''; + + if ( empty( $exchange_code ) ) { + $this->redirect_with_result( $return_url, 'plugin.return.missing_exchange_code' ); + } + + // POST to the marketing site REST API to retrieve connection details. + $response = wp_remote_post( + $this->get_marketing_site_url() . '/wp-json/smtp-plugin-connect/v1/connection', + [ + 'timeout' => 30, + 'body' => [ + 'exchange_code' => $exchange_code, + 'source' => 'wpms', + ], + ] + ); + + if ( is_wp_error( $response ) ) { + $this->redirect_with_result( $return_url, 'plugin.return.exchange_request_to_site.connection_failed' ); + } + + $response_code = wp_remote_retrieve_response_code( $response ); + $response_body = json_decode( wp_remote_retrieve_body( $response ), true ); + + // Non-200 response. + if ( $response_code !== 200 ) { + // Expected error — site returned a parseable error code. + $error_code = ! empty( $response_body['code'] ) + ? $this->sanitize_error_code( $response_body['code'] ) + : 'plugin.return.exchange_request_to_site.unexpected_error_response'; + + $this->redirect_with_result( $return_url, $error_code ); + } + + // Unexpected success — 200 but missing expected data. + if ( empty( $response_body['api_key'] ) ) { + $this->redirect_with_result( $return_url, 'plugin.return.exchange_request_to_site.unexpected_success_response' ); + } + + $api_key = sanitize_text_field( $response_body['api_key'] ); + $sender_domain = ! empty( $response_body['sender_domain'] ) ? sanitize_text_field( $response_body['sender_domain'] ) : ''; + $is_shared_domain = ! empty( $response_body['is_shared_domain'] ); + + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $mode = ! empty( $_GET['mode'] ) ? sanitize_key( $_GET['mode'] ) : ''; + + $connection = wp_mail_smtp()->get_connections_manager()->get_primary_connection(); + + if ( $mode === 'backup_mailer' && wp_mail_smtp()->is_pro() ) { + $new_connection_id = uniqid(); + $connection_options = new ConnectionOptions( $new_connection_id ); + + $connection_options->set( + [ + 'connection' => [ + 'name' => __( 'Backup', 'wp-mail-smtp' ), + ], + ] + ); + + $connection = new AdditionalConnection( $new_connection_id ); + } else { + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $connection_id = ! empty( $_GET['connection_id'] ) ? sanitize_key( $_GET['connection_id'] ) : ''; + + if ( ! empty( $connection_id ) && wp_mail_smtp()->is_pro() ) { + $connection = wp_mail_smtp()->get_connections_manager()->get_connection( $connection_id, false ); + } + } + + if ( $connection === false ) { + $this->redirect_with_result( $return_url, 'plugin.return.invalid_connection' ); + } + + // Store the API key using the connection's Options. + $options = $connection->get_options(); + $all_opt = $options->get_all_raw(); + + $all_opt['mail']['mailer'] = 'sendlayer'; + $all_opt['sendlayer']['api_key'] = $api_key; + $all_opt['sendlayer']['quick_connect'] = true; + $all_opt['sendlayer']['is_shared_domain'] = $is_shared_domain; + $all_opt['sendlayer']['free_upgrade_url'] = ! empty( $response_body['free_upgrade_url'] ) ? esc_url_raw( $response_body['free_upgrade_url'] ) : ''; + + if ( $mode === 'backup_mailer' && wp_mail_smtp()->is_pro() ) { + $all_opt['mail']['from_name'] = (string) Options::init()->get( 'mail', 'from_name' ); + $all_opt['mail']['from_email'] = (string) Options::init()->get( 'mail', 'from_email' ); + } + + // Store the sender domain and configure From Email for shared domains. + if ( ! empty( $sender_domain ) ) { + $all_opt['sendlayer']['sender_domain'] = $sender_domain; + + $current_from = ! empty( $all_opt['mail']['from_email'] ) ? $all_opt['mail']['from_email'] : ''; + + $all_opt['mail']['from_email'] = $this->build_from_email( $current_from, $sender_domain ); + $all_opt['mail']['from_email_force'] = true; + } + + $options->set( $all_opt ); + + if ( $mode === 'backup_mailer' && wp_mail_smtp()->is_pro() ) { + Options::init()->set( + [ + 'backup_connection' => [ + 'connection_id' => $connection->get_id(), + ], + ], + false, + false + ); + + $this->redirect_with_result( $return_url, 'backup_success' ); + } + + $this->usage->reset(); + + $this->redirect_with_result( $return_url, 'success' ); + } + + /** + * Display admin notices for SendLayer Quick Connect and disconnect results. + * + * Reads result query params set by handle_auth_complete() and handle_disconnect() + * and renders the appropriate success or error notice. + * + * @since 4.8.0 + */ + public function display_notice() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + + if ( ! wp_mail_smtp()->get_admin()->is_admin_page() ) { + return; + } + + // phpcs:disable WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + $disconnect_result = isset( $_GET['sendlayer_quick_connect_disconnect_result'] ) + ? $this->sanitize_error_code( wp_unslash( $_GET['sendlayer_quick_connect_disconnect_result'] ) ) + : ''; + // phpcs:enable WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + + if ( $disconnect_result === 'success' ) { + WP::add_admin_notice( + esc_html__( 'SendLayer disconnected successfully.', 'wp-mail-smtp' ), + WP::ADMIN_NOTICE_SUCCESS + ); + + return; + } + + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + if ( empty( $_GET['sendlayer_quick_connect_result'] ) ) { + return; + } + + // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + $result = $this->sanitize_error_code( wp_unslash( $_GET['sendlayer_quick_connect_result'] ) ); + + // Success — show admin notice banner. + if ( $result === 'success' ) { + WP::add_admin_notice( + esc_html__( 'SendLayer connected successfully! You can now send emails through SendLayer.', 'wp-mail-smtp' ), + WP::ADMIN_NOTICE_SUCCESS + ); + + return; + } + + // Backup-mailer mode success. + if ( $result === 'backup_success' ) { + WP::add_admin_notice( + esc_html__( 'SendLayer is now set up as your Backup Connection. Emails that fail to send with your primary connection will be sent via SendLayer.', 'wp-mail-smtp' ), + WP::ADMIN_NOTICE_SUCCESS + ); + + return; + } + + // Errors — show admin notice with error code box. + $actionable_messages = [ + 'plugin.return.permission_denied' => esc_html__( 'SendLayer connection failed. You do not have permission to perform this action.', 'wp-mail-smtp' ), + 'plugin.return.invalid_nonce' => esc_html__( 'SendLayer connection failed. Your session has expired. Please try again.', 'wp-mail-smtp' ), + 'site.exchange.rate_limit' => esc_html__( 'SendLayer connection failed. Too many attempts. Please wait a few minutes and try again.', 'wp-mail-smtp' ), + 'site.exchange.code_expired' => esc_html__( 'SendLayer connection failed. Your connection code has expired. Please try again.', 'wp-mail-smtp' ), + ]; + + $message = isset( $actionable_messages[ $result ] ) + ? $actionable_messages[ $result ] + : $this->get_generic_error_message(); + + WP::add_admin_notice( $message, WP::ADMIN_NOTICE_ERROR, true, '', $result ); + } + + /** + * Handle SendLayer disconnect action. + * + * Clears the API key, quick connect flag, and sender domain when the user + * clicks "Disconnect" via a nonce-protected URL, then redirects back to + * the settings page without the nonce query parameter. + * + * @since 4.8.0 + */ + public function handle_disconnect() { + + if ( + ! isset( $_GET['sendlayer_quick_connect_disconnect_nonce'] ) || // phpcs:ignore WordPress.Security.NonceVerification.Recommended + ! wp_mail_smtp()->get_admin()->is_admin_page() + ) { + return; + } + + if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_global_options() ) ) { + WP::add_admin_notice( + esc_html__( 'SendLayer disconnect failed. You do not have permission to perform this action.', 'wp-mail-smtp' ), + WP::ADMIN_NOTICE_ERROR + ); + + return; + } + + if ( ! wp_verify_nonce( sanitize_key( $_GET['sendlayer_quick_connect_disconnect_nonce'] ), 'sendlayer_quick_connect_disconnect' ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + WP::add_admin_notice( + esc_html__( 'SendLayer disconnect failed. The nonce is invalid. Please try again.', 'wp-mail-smtp' ), + WP::ADMIN_NOTICE_ERROR + ); + + return; + } + + $connection = wp_mail_smtp()->get_connections_manager()->get_primary_connection(); + $options = $connection->get_options(); + $all_opt = $options->get_all_raw(); + + $all_opt['sendlayer']['api_key'] = ''; + + unset( $all_opt['sendlayer']['quick_connect'] ); + unset( $all_opt['sendlayer']['is_shared_domain'] ); + unset( $all_opt['sendlayer']['sender_domain'] ); + + $options->set( $all_opt ); + + $this->usage->reset(); + + wp_safe_redirect( add_query_arg( 'sendlayer_quick_connect_disconnect_result', 'success', remove_query_arg( 'sendlayer_quick_connect_disconnect_nonce' ) ) ); + exit; + } + + /** + * AJAX handler: Disconnect SendLayer quick connect. + * + * Clears the API key, quick connect flag, shared domain flag, and sender domain. + * + * @since 4.8.0 + */ + public function ajax_disconnect() { + + check_ajax_referer( 'wp-mail-smtp-sendlayer-connect', 'nonce' ); + + if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_global_options() ) ) { + wp_send_json_error( + [ + 'message' => esc_html__( 'You do not have permission to perform this action.', 'wp-mail-smtp' ), + ] + ); + } + + $old_opt = Options::init()->get_all_raw(); + + $old_opt['sendlayer']['api_key'] = ''; + + unset( $old_opt['sendlayer']['quick_connect'] ); + unset( $old_opt['sendlayer']['is_shared_domain'] ); + unset( $old_opt['sendlayer']['sender_domain'] ); + + Options::init()->set( $old_opt ); + + $this->usage->reset(); + + wp_send_json_success(); + } + + /** + * Display the SendLayer education banner when the default PHP mailer is active. + * + * Shown above the page content (below tabs) on the Settings page only. + * + * @since 4.8.0 + */ + public function display_sendlayer_education_banner() { + + $mailer = wp_mail_smtp()->get_connections_manager()->get_primary_connection()->get_mailer_slug(); + + if ( $mailer !== 'mail' ) { + return; + } + + if ( ! wp_mail_smtp()->get_admin()->is_admin_page() ) { + return; + } + + $is_dismissed = (bool) get_user_meta( get_current_user_id(), 'wp_mail_smtp_notice_sendlayer_education_dismissed', true ); + + if ( $is_dismissed ) { + return; + } + + // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound + $sendlayer_url = wp_mail_smtp()->get_utm_url( 'https://sendlayer.com/', [ 'source' => 'wpmailsmtpplugin', 'medium' => 'WordPress', 'content' => 'Default Mailer - SendLayer Education' ] ); + ?> +
+ get( 'sendlayer', 'quick_connect' ) || ! $connection_options->get( 'sendlayer', 'is_shared_domain' ) ) { + return; + } + + $sender_domain = $connection_options->get( 'sendlayer', 'sender_domain' ); + + if ( empty( $sender_domain ) ) { + return; + } + + $from_email = $connection_options->get( 'mail', 'from_email' ); + // phpcs:ignore WordPress.WP.CapitalPDangit.Misspelled -- email local part, not a brand reference. + $local_part = 'wordpress'; + + if ( ! empty( $from_email ) && strpos( $from_email, '@' ) !== false ) { + $parts = explode( '@', $from_email ); + $local_part = $parts[0]; + } + + $is_active = $mailer === 'sendlayer'; + ?> +
+ +
++ SendLayer dashboard. Check our documentation on how to add a custom domain.', 'wp-mail-smtp' ), + [ + 'a' => [ + 'href' => [], + 'rel' => [], + 'target' => [], + ], + ] + ), + // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound + esc_url( wp_mail_smtp()->get_utm_url( 'https://app.sendlayer.com/', [ 'source' => 'wpmailsmtpplugin', 'medium' => 'WordPress', 'content' => 'Plugin Settings - From Email Domain Link' ] ) ), + // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound + esc_url( wp_mail_smtp()->get_utm_url( 'https://sendlayer.com/docs/authorizing-your-domain/', [ 'source' => 'wpmailsmtpplugin', 'medium' => 'WordPress', 'content' => 'Plugin Settings - Custom Domain Documentation' ] ) ) + ); + ?> +
+
+ usage->reset();
+ }
+
+ return $data;
+ }
+
+ /**
+ * Force quick_connect to false when the API key is defined via constant.
+ *
+ * Constant-based configuration takes precedence over quick connect state.
+ *
+ * @since 4.8.0
+ *
+ * @param mixed $value The option value.
+ * @param string $group The options group.
+ * @param string $key The options key.
+ *
+ * @return mixed
+ */
+ public function filter_option( $value, $group, $key ) {
+
+ if ( $group === 'sendlayer' && in_array( $key, [ 'quick_connect', 'is_shared_domain' ], true ) && Options::init()->is_const_defined( 'sendlayer', 'api_key' ) ) {
+ return false;
+ }
+
+ return $value;
+ }
+
+ /**
+ * Get the marketing site URL.
+ *
+ * Allows overriding via the WP_MAIL_SMTP_SENDLAYER_SITE_URL constant.
+ *
+ * @since 4.8.0
+ *
+ * @return string
+ */
+ private function get_marketing_site_url() {
+
+ if ( defined( 'WP_MAIL_SMTP_SENDLAYER_SITE_URL' ) && WP_MAIL_SMTP_SENDLAYER_SITE_URL ) {
+ return rtrim( WP_MAIL_SMTP_SENDLAYER_SITE_URL, '/' );
+ }
+
+ return self::SITE_URL;
+ }
+
+ /**
+ * Build a from email address for the shared sender domain.
+ *
+ * Takes the user part (local-part) from the current from email and
+ * combines it with the shared sender domain. Falls back to "WordPress"
+ * when no current from email is configured.
+ *
+ * @since 4.8.0
+ *
+ * @param string $current_from The current from email address.
+ * @param string $sender_domain The shared sender domain.
+ *
+ * @return string
+ */
+ private function build_from_email( $current_from, $sender_domain ) {
+
+ // phpcs:ignore WordPress.WP.CapitalPDangit.Misspelled -- email local part, not a brand reference.
+ $user_part = 'wordpress';
+
+ if ( ! empty( $current_from ) && strpos( $current_from, '@' ) !== false ) {
+ $parts = explode( '@', $current_from );
+ $user_part = $parts[0];
+ }
+
+ return $user_part . '@' . $sender_domain;
+ }
+
+ /**
+ * Redirect to a URL with a quick connect result code.
+ *
+ * @since 4.8.0
+ *
+ * @param string $url The URL to redirect to.
+ * @param string $result The result code (e.g. 'success', 'plugin_invalid_session').
+ */
+ private function redirect_with_result( $url, $result ) {
+
+ if ( $result !== 'success' ) {
+ DebugEvents::add( 'SendLayer Quick Connect: ' . $result );
+ }
+
+ wp_safe_redirect( add_query_arg( 'sendlayer_quick_connect_result', $result, $url ) );
+ exit;
+ }
+
+ /**
+ * Build a generic error message for support reference.
+ *
+ * @since 4.8.0
+ *
+ * @return string
+ */
+ private function get_generic_error_message() {
+
+ return esc_html__( 'Something went wrong while connecting to SendLayer. Please try again, or contact support and provide the error code.', 'wp-mail-smtp' );
+ }
+
+ /**
+ * Get the home URL bypassing filters.
+ *
+ * Reads from WP_HOME constant or directly from the database
+ * to avoid any filter modifications to the URL.
+ *
+ * @since 4.8.0
+ *
+ * @return string
+ */
+ private function get_site_url() {
+
+ global $wpdb;
+
+ if ( defined( 'WP_HOME' ) && WP_HOME ) {
+ return WP_HOME;
+ }
+
+ // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- intentional: bypass filters to get unmodified site URL.
+ return $wpdb->get_var(
+ $wpdb->prepare(
+ "SELECT option_value FROM {$wpdb->options} WHERE option_name = %s",
+ 'home'
+ )
+ );
+ }
+
+ /**
+ * Sanitize an error code slug, preserving dots for dot-notation format.
+ *
+ * WordPress's `sanitize_key()` strips dots. This method allows dots,
+ * lowercase letters, digits, underscores, and hyphens.
+ *
+ * @since 4.8.0
+ *
+ * @param string $code The error code to sanitize.
+ *
+ * @return string
+ */
+ private function sanitize_error_code( $code ) {
+
+ $code = strtolower( trim( $code ) );
+
+ return preg_replace( '/[^a-z0-9._-]/', '', $code );
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Providers/Sendlayer/QuickConnectUsage.php b/web/app/plugins/wp-mail-smtp-pro/src/Providers/Sendlayer/QuickConnectUsage.php
new file mode 100644
index 00000000..d1c10a81
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Providers/Sendlayer/QuickConnectUsage.php
@@ -0,0 +1,706 @@
+connections_manager = $connections_manager === null
+ ? wp_mail_smtp()->get_connections_manager()
+ : $connections_manager;
+ }
+
+ /**
+ * Register hooks.
+ *
+ * @since 4.9.0
+ */
+ public function hooks() {
+
+ // Nothing here is relevant unless the primary connection is a QC connection.
+ if ( ! $this->is_quick_connect() ) {
+ return;
+ }
+
+ add_action( 'wp_mail_smtp_mailcatcher_send_after', [ $this, 'capture_usage_from_response' ], 10, 1 );
+ add_action( 'wp_mail_smtp_mailcatcher_send_after', [ $this, 'clear_limit_reached_on_success' ], 10, 1 );
+ add_action( 'wp_mail_smtp_mailcatcher_send_failed', [ $this, 'maybe_flag_limit_reached' ], 10, 6 );
+ add_action( 'admin_init', [ $this, 'maybe_add_notice' ] );
+ }
+
+ /**
+ * Whether the primary connection is a SendLayer Quick Connect connection.
+ *
+ * @since 4.9.0
+ *
+ * @return bool
+ */
+ public function is_quick_connect() {
+
+ $connection = $this->connections_manager->get_primary_connection();
+
+ return $connection->get_mailer_slug() === 'sendlayer'
+ && (bool) $connection->get_options()->get( 'sendlayer', 'quick_connect' );
+ }
+
+ /**
+ * Whether the primary connection is a Quick Connect connection on a shared
+ * (SendLayer-provided) domain.
+ *
+ * @since 4.9.0
+ *
+ * @return bool
+ */
+ public function is_shared_domain() {
+
+ if ( ! $this->is_quick_connect() ) {
+ return false;
+ }
+
+ $options = $this->connections_manager->get_primary_connection()->get_options();
+
+ return (bool) $options->get( 'sendlayer', 'is_shared_domain' );
+ }
+
+ /**
+ * Store the quota snapshot from the QC send response header.
+ *
+ * Hooked to the after-send action; the header is advisory, so a send that lacks it
+ * leaves stored usage unchanged. Runs only for a primary Quick Connect send. Capture
+ * is never gated on is_bailed() - it is the classifier that arms the suppress gate.
+ *
+ * @since 4.9.0
+ *
+ * @param MailerAbstract $mailer The mailer that performed the send.
+ */
+ public function capture_usage_from_response( MailerAbstract $mailer ) {
+
+ if ( ! $this->is_quick_connect() || ! $mailer->get_connection()->is_primary() ) {
+ return;
+ }
+
+ $parsed = $this->parse_quota_header( $mailer->get_response_header( self::QUOTA_HEADER ) );
+
+ if ( $parsed === null ) {
+ return;
+ }
+
+ $this->save_state(
+ [
+ 'usage' => [
+ 'limit' => $parsed['limit'],
+ 'used' => $parsed['used'],
+ ],
+ 'plan' => $parsed['plan'],
+ 'cc' => $parsed['cc'],
+ ]
+ );
+ }
+
+ /**
+ * Parse the X-SendLayer-Quota header value into the usage sub-array.
+ *
+ * @since 4.9.0
+ *
+ * @param string $header Raw header value, e.g. `limit=100000;used=437;plan=free;cc=0`.
+ *
+ * @return array|null { limit, used, plan, cc } when limit+used are present, else null.
+ */
+ private function parse_quota_header( $header ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
+
+ if ( ! is_string( $header ) || $header === '' ) {
+ return null;
+ }
+
+ $pairs = [];
+
+ foreach ( explode( ';', $header ) as $segment ) {
+ if ( strpos( $segment, '=' ) === false ) {
+ continue;
+ }
+
+ list( $key, $value ) = explode( '=', $segment, 2 );
+
+ $pairs[ trim( $key ) ] = trim( $value );
+ }
+
+ if ( ! isset( $pairs['limit'], $pairs['used'] ) ) {
+ return null;
+ }
+
+ return [
+ 'limit' => (int) $pairs['limit'],
+ 'used' => (int) $pairs['used'],
+ 'plan' => isset( $pairs['plan'] ) ? sanitize_text_field( $pairs['plan'] ) : '',
+ 'cc' => isset( $pairs['cc'] ) ? (int) $pairs['cc'] : 0,
+ ];
+ }
+
+ /**
+ * Reset all QC usage-notice state.
+ *
+ * Called on QC connect/disconnect and when the SendLayer API key changes, since each
+ * starts a clean slate against a different (or absent) account. Clears usage + the
+ * reached flag and re-arms every notice so the next account starts un-dismissed.
+ *
+ * @since 4.9.0
+ */
+ public function reset() {
+
+ $this->save_state(
+ [
+ 'usage' => $this->default_usage(),
+ 'plan' => '',
+ 'cc' => 0,
+ 'limit_reached' => false,
+ ]
+ );
+
+ $this->rearm_notice( $this->custom_domain_key() );
+ $this->rearm_notice( $this->limit_warning_key() );
+ $this->rearm_notice( $this->limit_reached_key() );
+ }
+
+ /**
+ * Clear the per-user dismissal of a notice for all users, so it shows again.
+ *
+ * @since 4.9.0
+ *
+ * @param string $key Notice key.
+ */
+ private function rearm_notice( $key ) {
+
+ delete_metadata( 'user', 0, "wp_mail_smtp_notice_{$key}_dismissed", '', true );
+ }
+
+ /**
+ * Queue the single top-precedence due usage notice (limit_reached > limit_warning >
+ * custom_domain) for WP::display_admin_notices() to render dashboard-wide.
+ *
+ * All three require a QC primary connection, the global-options capability (the QC
+ * connection state is main-site-scoped in network-wide mode), and a not-bailed feature.
+ *
+ * @since 4.9.0
+ */
+ public function maybe_add_notice() {
+
+ if (
+ $this->is_bailed() ||
+ ! current_user_can( wp_mail_smtp()->get_capability_manage_global_options() )
+ ) {
+ return;
+ }
+
+ if ( $this->should_show_limit_reached() ) {
+ WP::add_admin_notice( $this->limit_reached_message(), WP::ADMIN_NOTICE_ERROR, true, $this->limit_reached_key() );
+
+ return;
+ }
+
+ if ( $this->should_show_limit_warning() ) {
+ WP::add_admin_notice( $this->limit_warning_message(), WP::ADMIN_NOTICE_WARNING, true, $this->limit_warning_key() );
+
+ return;
+ }
+
+ if ( $this->should_show_custom_domain() ) {
+ WP::add_admin_notice( $this->custom_domain_message(), WP::ADMIN_NOTICE_INFO, true, $this->custom_domain_key() );
+ }
+ }
+
+ /**
+ * Notice key for the custom-domain notice. Stable; dismissal is re-armed on reset.
+ *
+ * @since 4.9.0
+ *
+ * @return string
+ */
+ protected function custom_domain_key() {
+
+ return 'sendlayer_qc_custom_domain';
+ }
+
+ /**
+ * Notice key for the limit-warning notice. Stable; dismissal is re-armed on reset.
+ *
+ * @since 4.9.0
+ *
+ * @return string
+ */
+ protected function limit_warning_key() {
+
+ return 'sendlayer_qc_limit_warning';
+ }
+
+ /**
+ * Notice key for the limit-reached notice. Stable; dismissal is re-armed on reset.
+ *
+ * @since 4.9.0
+ *
+ * @return string
+ */
+ protected function limit_reached_key() {
+
+ return 'sendlayer_qc_limit_reached';
+ }
+
+ /**
+ * Whether the custom-domain notice is due.
+ *
+ * @since 4.9.0
+ *
+ * @return bool
+ */
+ public function should_show_custom_domain() {
+
+ $usage = $this->get_usage();
+ $used = isset( $usage['used'] ) ? (int) $usage['used'] : 0;
+
+ return $this->is_shared_domain() &&
+ $used >= self::CUSTOM_DOMAIN_NOTICE_LIMIT;
+ }
+
+ /**
+ * Whether the limit-warning notice is due.
+ *
+ * @since 4.9.0
+ *
+ * @return bool
+ */
+ public function should_show_limit_warning() {
+
+ $usage = $this->get_usage();
+
+ if ( empty( $usage ) ) {
+ return false;
+ }
+
+ $limit = isset( $usage['limit'] ) ? (int) $usage['limit'] : 0;
+ $used = isset( $usage['used'] ) ? (int) $usage['used'] : 0;
+
+ // No data or a nonsensical limit: never render (a wrong notice is worse
+ // than none). At-or-over the limit is the limit-reached notice's territory.
+ if ( $limit <= 0 || $used >= $limit ) {
+ return false;
+ }
+
+ return ( $used / $limit ) >= 0.5;
+ }
+
+ /**
+ * Whether the limit-reached notice is due.
+ *
+ * @since 4.9.0
+ *
+ * @return bool
+ */
+ public function should_show_limit_reached() {
+
+ return (bool) $this->get_state( 'limit_reached' );
+ }
+
+ /**
+ * Flag the limit-reached state when a primary-QC send fails with the
+ * SendLayer email-quota-reached error.
+ *
+ * @since 4.9.0
+ *
+ * @param string $error_message Error message.
+ * @param mixed $mailcatcher The MailCatcher object.
+ * @param string $mailer_slug Current mailer slug.
+ * @param string $error_code Error code/slug from the mailer response.
+ * @param int $response_code HTTP/SMTP response code.
+ * @param ConnectionInterface|null $connection The connection the send used.
+ */
+ public function maybe_flag_limit_reached( $error_message, $mailcatcher, $mailer_slug, $error_code, $response_code = 0, ConnectionInterface $connection = null ) {
+
+ if (
+ $this->is_bailed() ||
+ ! $connection instanceof ConnectionInterface ||
+ ! $connection->is_primary()
+ ) {
+ return;
+ }
+
+ // The SendLayer mailer surfaces Errors[0].Code, which is a numeric quota
+ // code on a 429; compare as a string so an int or string code both match.
+ if ( (string) $error_code !== self::QUOTA_REACHED_ERROR_CODE ) {
+ return;
+ }
+
+ $this->rearm_notice( $this->limit_reached_key() );
+
+ if ( ! $this->get_state( 'limit_reached' ) ) {
+ $this->save_state( [ 'limit_reached' => true ] );
+ }
+ }
+
+ /**
+ * Clear the limit-reached state on the next successful primary-QC send.
+ *
+ * @since 4.9.0
+ *
+ * @param MailerAbstract $mailer The mailer that performed the send.
+ */
+ public function clear_limit_reached_on_success( MailerAbstract $mailer ) {
+
+ if ( $this->is_bailed() || ! $mailer->get_connection()->is_primary() ) {
+ return;
+ }
+
+ if ( $this->get_state( 'limit_reached' ) ) {
+ $this->save_state( [ 'limit_reached' => false ] );
+ }
+ }
+
+ /**
+ * Resolve the upgrade CTA URL: the connect-time free_upgrade_url stored on the
+ * primary connection when present, otherwise the UTM-tagged pricing fallback.
+ *
+ * @since 4.9.0
+ *
+ * @return string
+ */
+ public function get_free_upgrade_url() {
+
+ $stored = $this->connections_manager->get_primary_connection()->get_options()->get( 'sendlayer', 'free_upgrade_url' );
+
+ $target = ! empty( $stored ) ? $stored : self::PRICING_URL;
+
+ // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound
+ return wp_mail_smtp()->get_utm_url( $target, [ 'source' => 'wpmailsmtpplugin', 'medium' => 'WordPress', 'content' => 'SendLayer QC Usage Notice - Upgrade' ] );
+ }
+
+ /**
+ * Whether the feature is suppressed: notices run only for a primary QC connection the
+ * quota header has classified as free with no card on file. An unclassified account
+ * (empty plan) bails until the header confirms free+no-card; capture still runs, so
+ * classification still happens.
+ *
+ * @since 4.9.0
+ *
+ * @return bool
+ */
+ public function is_bailed() {
+
+ return ! $this->is_quick_connect()
+ || ! ( $this->get_state( 'plan' ) === 'free' && (int) $this->get_state( 'cc' ) === 0 );
+ }
+
+ /**
+ * Whether the account has a card on file (per the latest quota header).
+ *
+ * @since 4.9.0
+ *
+ * @return bool
+ */
+ public function has_card_on_file() {
+
+ return (int) $this->get_state( 'cc' ) === 1;
+ }
+
+ /**
+ * Get the stored usage data ({ limit, used }), or an empty array when there is no
+ * usable limit yet.
+ *
+ * @since 4.9.0
+ *
+ * @return array
+ */
+ public function get_usage() {
+
+ $usage = $this->get_state( 'usage' );
+
+ if ( $usage['limit'] === null ) {
+ return [];
+ }
+
+ return [
+ 'limit' => (int) $usage['limit'],
+ 'used' => (int) $usage['used'],
+ ];
+ }
+
+ /**
+ * Custom-domain suggestion notice copy.
+ *
+ * @since 4.9.0
+ *
+ * @return string
+ */
+ protected function custom_domain_message() {
+
+ // phpcs:disable WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound
+ $app_url = wp_mail_smtp()->get_utm_url( 'https://app.sendlayer.com/', [ 'source' => 'wpmailsmtpplugin', 'medium' => 'WordPress', 'content' => 'QC Usage Notice - Custom Domain App' ] );
+ $doc_url = wp_mail_smtp()->get_utm_url( 'https://sendlayer.com/docs/authorizing-your-domain/', [ 'source' => 'wpmailsmtpplugin', 'medium' => 'WordPress', 'content' => 'QC Usage Notice - Custom Domain Docs' ] );
+ // phpcs:enable WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound
+
+ $message = sprintf(
+ wp_kses(
+ /* translators: %1$s - SendLayer dashboard URL; %2$s - custom domain documentation URL. */
+ __( 'You\'re sending through a shared SendLayer domain. For better deliverability, add your own domain on the SendLayer dashboard and follow our custom domain guide.', 'wp-mail-smtp' ),
+ [
+ 'a' => [
+ 'href' => [],
+ 'rel' => [],
+ 'target' => [],
+ ],
+ ]
+ ),
+ esc_url( $app_url ),
+ esc_url( $doc_url )
+ );
+
+ return $message;
+ }
+
+ /**
+ * Limit-warning notice copy.
+ *
+ * @since 4.9.0
+ *
+ * @return string
+ */
+ protected function limit_warning_message() {
+
+ $usage = $this->get_usage();
+
+ $message = sprintf(
+ wp_kses(
+ /* translators: %1$d - emails used; %2$d - free sending limit; %3$s - upgrade URL. */
+ __( 'You\'ve used %1$d of your %2$d free SendLayer emails. Upgrade your plan to keep your emails flowing without interruption.', 'wp-mail-smtp' ),
+ [
+ 'a' => [
+ 'href' => [],
+ 'rel' => [],
+ 'target' => [],
+ ],
+ ]
+ ),
+ isset( $usage['used'] ) ? (int) $usage['used'] : 0,
+ isset( $usage['limit'] ) ? (int) $usage['limit'] : 0,
+ esc_url( $this->get_free_upgrade_url() )
+ );
+
+ return $message;
+ }
+
+ /**
+ * Limit-reached notice copy.
+ *
+ * @since 4.9.0
+ *
+ * @return string
+ */
+ protected function limit_reached_message() {
+
+ $message = sprintf(
+ wp_kses(
+ /* translators: %s - upgrade URL. */
+ __( 'You\'ve reached your free SendLayer limit. Emails are failing and won\'t be delivered! Upgrade your plan to start sending again.', 'wp-mail-smtp' ),
+ [
+ 'a' => [
+ 'href' => [],
+ 'rel' => [],
+ 'target' => [],
+ ],
+ ]
+ ),
+ esc_url( $this->get_free_upgrade_url() )
+ );
+
+ return $message;
+ }
+
+ /*
+ * --- Consolidated state ---
+ *
+ * All site-level usage-notice state lives in one non-autoloaded option. The
+ * accessors below merge the stored value over defaults on read and persist the
+ * whole array on write.
+ */
+
+ /**
+ * Default state shape, used both as the read-time merge base and the reset target.
+ *
+ * @since 4.9.0
+ *
+ * @return array
+ */
+ private function default_state() {
+
+ return [
+ 'usage' => $this->default_usage(),
+ 'plan' => '',
+ 'cc' => 0,
+ 'limit_reached' => false,
+ ];
+ }
+
+ /**
+ * Default usage sub-array. A null limit marks "no usable data yet" (no quota header
+ * received).
+ *
+ * @since 4.9.0
+ *
+ * @return array
+ */
+ private function default_usage() {
+
+ return [
+ 'limit' => null,
+ 'used' => null,
+ ];
+ }
+
+ /**
+ * Read the consolidated state, merged over defaults so callers always get the full
+ * shape (including the usage sub-array). Network-aware.
+ *
+ * @since 4.9.0
+ *
+ * @param string|null $key Dot-notation key into the state, or null for the full array.
+ * @param mixed $default_value Value to return when a keyed read misses.
+ *
+ * @return mixed
+ */
+ private function get_state( $key = null, $default_value = null ) {
+
+ $stored = $this->is_network_scoped()
+ ? get_blog_option( get_main_site_id(), self::STATE_OPTION, [] )
+ : get_option( self::STATE_OPTION, [] );
+
+ if ( ! is_array( $stored ) ) {
+ $stored = [];
+ }
+
+ $state = array_merge( $this->default_state(), $stored );
+ $state['usage'] = array_merge( $this->default_usage(), is_array( $state['usage'] ) ? $state['usage'] : [] );
+
+ if ( $key === null ) {
+ return $state;
+ }
+
+ return Data::get( $state, $key, $default_value );
+ }
+
+ /**
+ * Merge the changed keys over the current state and persist. Callers pass only the
+ * keys they are changing. Non-autoloaded, network-aware.
+ *
+ * @since 4.9.0
+ *
+ * @param array $changes Top-level state keys to overwrite.
+ */
+ private function save_state( array $changes ) {
+
+ $state = array_merge( $this->get_state(), $changes );
+
+ if ( $this->is_network_scoped() ) {
+ update_blog_option( get_main_site_id(), self::STATE_OPTION, $state );
+
+ return;
+ }
+
+ update_option( self::STATE_OPTION, $state, false );
+ }
+
+ /**
+ * Whether plugin state should be scoped to the network's main site.
+ *
+ * In network-wide mode the SendLayer connection lives on the main site, so every
+ * sub-site must agree on one per-account view of the usage-notice state.
+ *
+ * @since 4.9.0
+ *
+ * @return bool
+ */
+ private function is_network_scoped() {
+
+ // Short-circuit on single-site before consulting the network setting, so the
+ // common path never touches main-site option reads.
+ if ( ! is_multisite() ) {
+ return false;
+ }
+
+ return WP::use_global_plugin_settings();
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Providers/SparkPost/Mailer.php b/web/app/plugins/wp-mail-smtp-pro/src/Providers/SparkPost/Mailer.php
new file mode 100644
index 00000000..45a24c01
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Providers/SparkPost/Mailer.php
@@ -0,0 +1,559 @@
+url = self::API_BASE_US;
+
+ // We want to prefill everything from MailCatcher class, which extends PHPMailer.
+ parent::__construct( $phpmailer, $connection );
+
+ // We have a special API URL to query in case of EU region.
+ if ( $this->connection_options->get( $this->mailer, 'region' ) === 'EU' ) {
+ $this->url = self::API_BASE_EU;
+ }
+
+ $this->url .= '/transmissions';
+
+ // Set mailer specific headers.
+ $this->set_header( 'Authorization', $this->connection_options->get( $this->mailer, 'api_key' ) );
+ $this->set_header( 'Content-Type', 'application/json' );
+
+ // Set default body params.
+ $this->set_body_param(
+ [
+ 'options' => [
+ 'open_tracking' => false,
+ 'click_tracking' => false,
+ 'transactional' => true,
+ ],
+ ]
+ );
+
+ /**
+ * Filters return path.
+ *
+ * Email address to use for envelope FROM.
+ * The domain of the return_path address must be a CNAME-verified sending domain.
+ * The local part of the return_path address will be overwritten by SparkPost.
+ *
+ * @since 3.2.0
+ *
+ * @param string $return_path Email address, by default will be used value configured in SparkPost dashboard.
+ */
+ $return_path = apply_filters( 'wp_mail_smtp_providers_sparkpost_mailer_return_path', '' );
+
+ if ( $return_path && filter_var( $return_path, FILTER_VALIDATE_EMAIL ) ) {
+ $this->set_body_param(
+ [
+ 'return_path' => $return_path,
+ ]
+ );
+ }
+ }
+
+ /**
+ * Redefine the way custom headers are processed for this mailer - they should be in body.
+ *
+ * @since 3.2.0
+ *
+ * @param array $headers Headers array.
+ */
+ public function set_headers( $headers ) {
+
+ foreach ( $headers as $header ) {
+ $name = isset( $header[0] ) ? $header[0] : false;
+ $value = isset( $header[1] ) ? $header[1] : false;
+
+ $this->set_body_header( $name, $value );
+ }
+
+ // Add custom PHPMailer-specific header.
+ $this->set_body_header( 'X-Mailer', 'WPMailSMTP/Mailer/' . $this->mailer . ' ' . WPMS_PLUGIN_VER );
+ $this->set_body_header( 'Message-ID', $this->phpmailer->getLastMessageID() );
+ }
+
+ /**
+ * This mailer supports email-related custom headers inside a body of the message.
+ *
+ * @since 3.2.0
+ *
+ * @param string $name Header name.
+ * @param string $value Header value.
+ */
+ public function set_body_header( $name, $value ) {
+
+ $name = sanitize_text_field( $name );
+
+ if ( empty( $name ) ) {
+ return;
+ }
+
+ $headers = isset( $this->body['content']['headers'] ) ? (array) $this->body['content']['headers'] : [];
+ $value = $this->sanitize_header_value( $name, $value );
+
+ $headers[ $name ] = $value;
+
+ $this->set_body_param(
+ [
+ 'content' => [
+ 'headers' => $headers,
+ ],
+ ]
+ );
+ }
+
+ /**
+ * Set the From information for an email.
+ *
+ * @since 3.2.0
+ *
+ * @param string $email The sender email address.
+ * @param string $name The sender name.
+ */
+ public function set_from( $email, $name ) {
+
+ if ( ! filter_var( $email, FILTER_VALIDATE_EMAIL ) ) {
+ return;
+ }
+
+ $from['email'] = $email;
+
+ if ( ! empty( $name ) ) {
+ $from['name'] = $name;
+ }
+
+ $this->set_body_param(
+ [
+ 'content' => [
+ 'from' => $from,
+ ],
+ ]
+ );
+ }
+
+ /**
+ * Set email recipients: to, cc, bcc.
+ *
+ * @since 3.2.0
+ *
+ * @param array $recipients Email recipients.
+ */
+ public function set_recipients( $recipients ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
+
+ if ( empty( $recipients ) ) {
+ return;
+ }
+
+ $recipients_to = isset( $recipients['to'] ) && is_array( $recipients['to'] ) ? $recipients['to'] : [];
+ $header_to = implode( ',', array_map( [ $this->phpmailer, 'addrFormat' ], $recipients_to ) );
+
+ $default = [ 'to', 'cc', 'bcc' ];
+
+ foreach ( $recipients as $type => $emails ) {
+ if (
+ ! in_array( $type, $default, true ) ||
+ empty( $emails ) ||
+ ! is_array( $emails )
+ ) {
+ continue;
+ }
+
+ $data = [];
+
+ foreach ( $emails as $email ) {
+ $addr = isset( $email[0] ) ? $email[0] : false;
+
+ if ( ! filter_var( $addr, FILTER_VALIDATE_EMAIL ) ) {
+ continue;
+ }
+
+ $data[] = [
+ 'address' => $this->build_recipient( $email, $header_to ),
+ ];
+ }
+
+ // CC recipients must be also included as header.
+ if ( $type === 'cc' ) {
+ $this->set_body_header( 'CC', implode( ',', array_map( [ $this->phpmailer, 'addrFormat' ], $emails ) ) );
+ }
+
+ if ( ! empty( $data ) ) {
+ $this->set_body_param(
+ [
+ 'recipients' => $data,
+ ]
+ );
+ }
+ }
+ }
+
+ /**
+ * Set the Reply To information for an email.
+ *
+ * @since 3.2.0
+ *
+ * @param array $emails Reply To email addresses.
+ */
+ public function set_reply_to( $emails ) {
+
+ if ( empty( $emails ) ) {
+ return;
+ }
+
+ $data = [];
+
+ foreach ( $emails as $email ) {
+ $addr = isset( $email[0] ) ? $email[0] : false;
+
+ if ( ! filter_var( $addr, FILTER_VALIDATE_EMAIL ) ) {
+ continue;
+ }
+
+ $data[] = $this->phpmailer->addrFormat( $email );
+ }
+
+ if ( ! empty( $data ) ) {
+ $this->set_body_param(
+ [
+ 'content' => [
+ 'reply_to' => implode( ',', $data ),
+ ],
+ ]
+ );
+ }
+ }
+
+ /**
+ * Set email subject.
+ *
+ * @since 3.2.0
+ *
+ * @param string $subject Email subject.
+ */
+ public function set_subject( $subject ) {
+
+ $this->set_body_param(
+ [
+ 'content' => [
+ 'subject' => $subject,
+ ],
+ ]
+ );
+ }
+
+ /**
+ * Set email content.
+ *
+ * @since 3.2.0
+ *
+ * @param string|array $content Email content.
+ */
+ public function set_content( $content ) {
+
+ if ( empty( $content ) ) {
+ return;
+ }
+
+ if ( is_array( $content ) ) {
+ if ( ! empty( $content['text'] ) ) {
+ $this->set_body_param(
+ [
+ 'content' => [
+ 'text' => $content['text'],
+ ],
+ ]
+ );
+ }
+
+ if ( ! empty( $content['html'] ) ) {
+ $this->set_body_param(
+ [
+ 'content' => [
+ 'html' => $content['html'],
+ ],
+ ]
+ );
+ }
+ } else {
+ if ( $this->phpmailer->ContentType === 'text/plain' ) {
+ $this->set_body_param(
+ [
+ 'content' => [
+ 'text' => $content,
+ ],
+ ]
+ );
+ } else {
+ $this->set_body_param(
+ [
+ 'content' => [
+ 'html' => $content,
+ ],
+ ]
+ );
+ }
+ }
+ }
+
+ /**
+ * Set attachments for an email.
+ *
+ * @since 3.2.0
+ *
+ * @param array $attachments Attachments array.
+ */
+ public function set_attachments( $attachments ) {
+
+ if ( empty( $attachments ) ) {
+ return;
+ }
+
+ $data = $this->prepare_attachments( $attachments );
+
+ if ( ! empty( $data ) ) {
+ $this->set_body_param(
+ [
+ 'content' => [
+ 'attachments' => $data,
+ ],
+ ]
+ );
+ }
+ }
+
+ /**
+ * Prepare attachments data for SparkPost API.
+ *
+ * @since 3.2.0
+ *
+ * @param array $attachments Array of attachments.
+ *
+ * @return array
+ */
+ protected function prepare_attachments( $attachments ) {
+
+ $data = [];
+
+ foreach ( $attachments as $attachment ) {
+ $file = $this->get_attachment_file_content( $attachment );
+
+ if ( $file === false ) {
+ continue;
+ }
+
+ $data[] = [
+ 'name' => $this->get_attachment_file_name( $attachment ),
+ 'data' => base64_encode( $file ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
+ 'type' => $attachment[4],
+ ];
+ }
+
+ return $data;
+ }
+
+ /**
+ * Doesn't support this.
+ * Return path can be configured in SparkPost account.
+ *
+ * @since 3.2.0
+ *
+ * @param string $email Return Path email address.
+ */
+ public function set_return_path( $email ) { }
+
+ /**
+ * Redefine the way email body is returned.
+ * By default, we are sending an array of data.
+ * SparkPost requires a JSON, so we encode the body.
+ *
+ * @since 3.2.0
+ */
+ public function get_body() {
+
+ $body = parent::get_body();
+
+ return wp_json_encode( $body );
+ }
+
+ /**
+ * We might need to do something after the email was sent to the API.
+ * In this method we preprocess the response from the API.
+ *
+ * @since 3.2.0
+ *
+ * @param mixed $response Response data.
+ */
+ protected function process_response( $response ) {
+
+ parent::process_response( $response );
+
+ if (
+ ! is_wp_error( $response ) &&
+ ! empty( $this->response['body']->results->id )
+ ) {
+ $this->phpmailer->addCustomHeader( 'X-Msg-ID', $this->response['body']->results->id );
+ $this->verify_sent_status = true;
+ }
+ }
+
+ /**
+ * Get a SparkPost-specific response with a helpful error.
+ *
+ * @since 3.2.0
+ *
+ * @return string
+ */
+ public function get_response_error() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh, Generic.Metrics.NestingLevel.MaxExceeded
+
+ $error_text[] = $this->error_message;
+
+ if ( ! empty( $this->response ) ) {
+ $body = wp_remote_retrieve_body( $this->response );
+
+ if ( ! empty( $body->errors ) && is_array( $body->errors ) ) {
+ foreach ( $body->errors as $error ) {
+ if ( ! empty( $error->message ) ) {
+ $message = $error->message;
+ $code = ! empty( $error->code ) ? $error->code : '';
+ $description = ! empty( $error->description ) ? $error->description : '';
+
+ $error_text[] = Helpers::format_error_message( $message, $code, $description );
+ }
+ }
+ } else {
+ $error_text[] = WP::wp_remote_get_response_error_message( $this->response );
+ }
+ }
+
+ return implode( WP::EOL, array_map( 'esc_textarea', array_filter( $error_text ) ) );
+ }
+
+ /**
+ * Get the error code from the SparkPost API response.
+ *
+ * @since 4.8.0
+ *
+ * @return string
+ */
+ public function get_response_error_code() {
+
+ if ( ! empty( $this->response ) ) {
+ $body = wp_remote_retrieve_body( $this->response );
+
+ if ( ! empty( $body->errors ) && is_array( $body->errors ) && ! empty( $body->errors[0]->code ) ) {
+ return $body->errors[0]->code;
+ }
+ }
+
+ return parent::get_response_error_code();
+ }
+
+ /**
+ * Get mailer debug information, that is helpful during support.
+ *
+ * @since 3.2.0
+ *
+ * @return string
+ */
+ public function get_debug_info() {
+
+ $options = $this->connection_options->get_group( $this->mailer );
+
+ $text[] = '' . esc_html__( 'API Key:', 'wp-mail-smtp' ) . ' ' .
+ ( ! empty( $options['api_key'] ) ? 'Yes' : 'No' );
+ $text[] = '' . esc_html__( 'Region:', 'wp-mail-smtp' ) . ' ' .
+ ( ! empty( $options['region'] ) ? 'Yes' : 'No' );
+
+ return implode( '
', $text );
+ }
+
+ /**
+ * Whether the mailer has all its settings correctly set up and saved.
+ *
+ * This mailer is configured when `api_key` setting is defined.
+ *
+ * @since 3.2.0
+ *
+ * @return bool
+ */
+ public function is_mailer_complete() {
+
+ $options = $this->connection_options->get_group( $this->mailer );
+
+ if ( ! empty( $options['api_key'] ) ) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Build recipient array.
+ *
+ * @since 3.2.0
+ *
+ * @param array $address Email address array.
+ * @param string $header_to Email recipients To header.
+ *
+ * @return array
+ */
+ private function build_recipient( $address, $header_to ) {
+
+ $holder = [];
+
+ $holder['email'] = $address[0];
+
+ if ( ! empty( $address[1] ) ) {
+ $holder['name'] = $address[1];
+ }
+
+ if ( ! empty( $header_to ) ) {
+ $holder['header_to'] = $header_to;
+ unset( $holder['name'] );
+ }
+
+ return $holder;
+ }
+}
diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Providers/SparkPost/Options.php b/web/app/plugins/wp-mail-smtp-pro/src/Providers/SparkPost/Options.php
new file mode 100644
index 00000000..9aaebda0
--- /dev/null
+++ b/web/app/plugins/wp-mail-smtp-pro/src/Providers/SparkPost/Options.php
@@ -0,0 +1,182 @@
+SparkPost is a transactional email provider that\'s trusted by big brands and small businesses. It sends more than 4 trillion emails each year and reports 99.9%% uptime. You can get started with the free test account that lets you send up to 500 emails per month.', 'wp-mail-smtp' ) .
+ '
' .
+ /* translators: %2$s - URL to wpmailsmtp.com doc. */
+ __( 'To get started, read our SparkPost documentation.', 'wp-mail-smtp' ),
+ [
+ 'br' => true,
+ 'a' => [
+ 'href' => true,
+ 'rel' => true,
+ 'target' => true,
+ ],
+ ]
+ ),
+ 'https://www.sparkpost.com/',
+ esc_url( wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/docs/how-to-set-up-the-sparkpost-mailer-in-wp-mail-smtp/', 'SparkPost documentation' ) )
+ );
+
+ parent::__construct(
+ [
+ 'logo_url' => wp_mail_smtp()->assets_url . '/images/providers/sparkpost.svg',
+ 'slug' => self::SLUG,
+ 'title' => esc_html__( 'SparkPost', 'wp-mail-smtp' ),
+ 'php' => '5.6',
+ 'description' => $description,
+ 'supports' => [
+ 'from_email' => true,
+ 'from_name' => true,
+ 'return_path' => false,
+ 'from_email_force' => true,
+ 'from_name_force' => true,
+ ],
+ 'recommended' => false,
+ ],
+ $connection
+ );
+ }
+
+ /**
+ * Output the mailer provider options.
+ *
+ * @since 3.2.0
+ */
+ public function display_options() {
+
+ // Do not display options if PHP version is not correct.
+ if ( ! $this->is_php_correct() ) {
+ $this->display_php_warning();
+ return;
+ }
+ ?>
+
+
+
+ connection_options->get( $this->get_slug(), 'region' ) === 'EU' ? 'eu.' . $url : $url; + $url = 'https://app.' . $url . '/account/api-keys'; + + printf( /* translators: %s - API Key link. */ + esc_html__( 'Follow this link to get an API Key from SparkPost: %s.', 'wp-mail-smtp' ), + '' . + esc_html__( 'Get API Key', 'wp-mail-smtp' ) . + '' + ); + ?> +
++ + +
+ + More information on SparkPost.', 'wp-mail-smtp' ), + [ + 'a' => [ + 'href' => [], + 'rel' => [], + 'target' => [], + ], + ] + ), + 'https://www.sparkpost.com/docs/getting-started/getting-started-sparkpost' + ); + ?> +
++ + wp_mail_smtp()->assets_url . '/images/providers/zoho.svg', + 'slug' => 'zoho', + 'title' => esc_html__( 'Zoho Mail', 'wp-mail-smtp' ), + 'disabled' => true, + ) + ); + } + + /** + * {@inheritdoc} + * + * @since 2.3.0 + */ + public function display_options() { + + ?> +
+ +
++ process_attachment( $path, $name, $is_string_attachment ); + + if ( ! empty( $path ) ) { + $attachment[0] = $path; + } + + return $attachment; + }, + $attachments + ); + + return $attachments; + } + + /** + * Process an attachment,obfuscating its path + * and storing its file on disk. + * + * @since 4.0.0 + * + * @param string $path The path to obfuscate. + * @param string $name The name of the file at $path. + * @param bool $is_string_attachment Whether this attachment is a string attachment. + * + * @return string|false New path of the attachment, or false for no path. + */ + private function process_attachment( $path, $name = '', $is_string_attachment = false ) { + + $file_content = $this->get_attachment_file_content( $path, $is_string_attachment ); + + if ( $file_content === false ) { + return false; + } + + if ( ! $is_string_attachment && $name === '' ) { + $name = wp_basename( $path ); + } + + $name = sanitize_file_name( $name ); + $obfuscated_path = $this->store_file( $file_content, $name ); + + if ( empty( $obfuscated_path ) ) { + return $path; + } + + return $obfuscated_path; + } + + /** + * Return the contents of a given file. + * + * @since 4.0.0 + * + * @param string $path The file's path. + * @param bool $is_string_attachment Whether this file is a string attachment. + * + * @return string File contents. + */ + private function get_attachment_file_content( $path, $is_string_attachment ) { + + if ( ! $is_string_attachment ) { + if ( ! file_exists( $path ) ) { + return false; + } + + return file_get_contents( $path ); + } + + return $path; + } + + /** + * Store a file. + * + * @since 4.0.0 + * + * @param string $file_content The file's contents. + * @param string $original_filename The original file's name. + * + * @return string The file's path. + */ + private function store_file( $file_content, $original_filename ) { + + $uploads_directory = $this->get_uploads_directory(); + + if ( is_wp_error( $uploads_directory ) ) { + return false; + } + + if ( ! is_dir( $uploads_directory ) ) { + wp_mkdir_p( $uploads_directory ); + + // Check if the .htaccess exists in the root upload directory, if not - create it. + Uploads::create_upload_dir_htaccess_file(); + + // Check if the index.html exists in the directories, if not - create them. + Uploads::create_index_html_file( Uploads::upload_dir()['path'] ); + Uploads::create_index_html_file( $uploads_directory ); + } + + $file_extension = pathinfo( $original_filename, PATHINFO_EXTENSION ); + $filename = wp_unique_filename( $uploads_directory, wp_generate_password( 32, false, false ) . '.' . $file_extension ); + $uploads_directory = trailingslashit( $uploads_directory ); + + if ( ! is_writeable( $uploads_directory ) ) { + return false; + } + + $upload_path = $uploads_directory . $filename; + + if ( file_put_contents( $upload_path, $file_content ) !== false ) { // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_file_put_contents + return $upload_path; + } + + return false; + } + + /** + * Delete attachments, removing their files from disk. + * + * @since 4.0.0 + * + * @param null|array $attachments List of attachments to cleanup, or null for all attachments. + * @param null|DateTime $before_datetime The datetime attachments should be older than + * to be removed, or null for all attachments. + * + * @return void. + */ + public function delete_attachments( $attachments = null, $before_datetime = null ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + + $uploads_directory = $this->get_uploads_directory(); + + if ( + is_wp_error( $uploads_directory ) || + ! is_dir( $uploads_directory ) + ) { + return; + } + + $files = []; + + // If no attachment list is provided, just iterate over all files in our uploads directory. + if ( is_null( $attachments ) ) { + $nodes = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator( $uploads_directory, RecursiveDirectoryIterator::SKIP_DOTS ), + RecursiveIteratorIterator::CHILD_FIRST + ); + $files = []; + + foreach ( $nodes as $fileinfo ) { + if ( ! $fileinfo->isDir() ) { + $files[] = $fileinfo->getRealPath(); + } + } + } else { + // Map attachments to their paths. + $files = wp_list_pluck( $attachments, 0 ); + + // Exclude any files that aren't in our uploads directory. + $files = array_filter( + $files, + function( $file ) use ( $uploads_directory ) { + return trailingslashit( dirname( $file ) ) === $uploads_directory; + } + ); + } + + // Skip any file that doesn't exist. + $files = array_filter( + $files, + function( $file ) { + return file_exists( $file ); + } + ); + + if ( ! is_null( $before_datetime ) ) { + // Skip any file that isn't older than the provided datetime. + $before_timestamp = $before_datetime->getTimestamp(); + $files = array_filter( + $files, + function( $file ) use ( $before_timestamp ) { + return ( + filemtime( $file ) !== false && + filemtime( $file ) < $before_timestamp + ); + } + ); + } + + foreach ( $files as $file ) { + @unlink( $file ); + } + } + + /** + * Get the upload directory path. + * + * @since 4.0.0 + * + * @return string|WP_Error The upload directory path. + */ + private function get_uploads_directory() { + + $uploads_directory = Uploads::upload_dir(); + + if ( is_wp_error( $uploads_directory ) ) { + return $uploads_directory; + } + + return trailingslashit( trailingslashit( $uploads_directory['path'] ) . 'queue_attachments' ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Queue/Email.php b/web/app/plugins/wp-mail-smtp-pro/src/Queue/Email.php new file mode 100644 index 00000000..d96d3466 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Queue/Email.php @@ -0,0 +1,608 @@ +date_enqueued = new DateTime( 'now', new DateTimeZone( 'UTC' ) ); + } + + /** + * Get a list of allowed statuses. + * + * @since 4.0.0 + * + * @return array + */ + public static function get_statuses() { + + return [ + self::STATUS_QUEUED, + self::STATUS_PROCESSING, + self::STATUS_PROCESSED, + ]; + } + + /** + * Construct an email from an array of data. + * + * @since 4.0.0 + * + * @param object $data Database row object. + * + * @throws Exception If supplied data is missing or malformed. + * + * @return Email + */ + public static function from_data( $data ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + + if ( is_null( $data ) ) { + throw new Exception( esc_html__( 'Record not found in DB', 'wp-mail-smtp' ) ); + } + + if ( + ! is_object( $data ) || + ! property_exists( $data, 'data' ) || + ! isset( + $data->id, + $data->status, + $data->date_enqueued + ) + ) { + throw new Exception( esc_html__( 'Invalid record format', 'wp-mail-smtp' ) ); + } + + // Data can be null if email has been anonymized. + // Only check for valid JSON if data isn't null. + if ( ! is_null( $data->data ) && ! WP::is_json( $data->data ) ) { + throw new Exception( + sprintf( + /* translators: %1$s - JSON error message. */ + esc_html__( 'Data JSON decoding error: %1$s', 'wp-mail-smtp' ), + esc_html( json_last_error_msg() ) + ) + ); + } + + $email = new Email(); + $email_data = is_null( $data->data ) ? [] : json_decode( $data->data, true ); + $email_data = wp_parse_args( + $email_data, + [ + 'initiator_state' => [], + 'wp_mail_args' => [], + 'connection_data' => [], + 'mailer_state' => [], + ] + ); + + $email->id = (int) $data->id; + $email->initiator_state = $email_data['initiator_state']; + $email->wp_mail_args = $email_data['wp_mail_args']; + $email->connection_data = $email_data['connection_data']; + $email->mailer_state = $email_data['mailer_state']; + $email->status = (int) $data->status; + $email->date_enqueued = $email->get_datetime( $data->date_enqueued ); + + if ( isset( $data->date_processed ) ) { + $email->date_processed = $email->get_datetime( $data->date_processed ); + } + + return $email; + } + + /** + * Get this email's ID. + * + * @since 4.0.0 + * + * @return int + */ + public function get_id() { + + return (int) $this->id; + } + + /** + * Get this email's status. + * + * @since 4.0.0 + * + * @return int + */ + public function get_status() { + + return $this->status; + } + + /** + * Set this email's status. + * + * @since 4.0.0 + * + * @param int $status Email status. + * + * @return Email + */ + public function set_status( $status ) { + + $status = (int) $status; + + if ( ! in_array( $status, self::get_statuses(), true ) ) { + $status = self::STATUS_QUEUED; + } + + $this->status = $status; + + return $this; + } + + /** + * Get this email's `wp_mail` call arguments. + * + * @since 4.0.0 + * + * @return array + */ + public function get_wp_mail_args() { + + return $this->wp_mail_args; + } + + /** + * Set this email's `wp_mail` call arguments. + * + * @since 4.0.0 + * + * @param array $args Array of arguments. + * + * @return Email + */ + public function set_wp_mail_args( $args ) { + + $args = wp_parse_args( + $args, + [ + 'headers' => '', + 'attachments' => [], + ] + ); + + $this->wp_mail_args = $args; + + return $this; + } + + /** + * Get this email's MailCatcher state. + * + * @since 4.0.0 + * + * @return array + */ + public function get_connection_data() { + + return $this->connection_data; + } + + /** + * Set this email's connection data. + * + * @since 4.0.0 + * + * @param array $data Connection data. + * + * @return Email + */ + public function set_connection_data( $data ) { + + $this->connection_data = wp_parse_args( + $data, + [ + 'from_email' => '', + 'from_name' => '', + ] + ); + + return $this; + } + + /** + * Get this email's MailCatcher state. + * + * @since 4.0.0 + * + * @return array + */ + public function get_mailer_state() { + + return $this->mailer_state; + } + + /** + * Set this email's MailCatcher state. + * + * @since 4.0.0 + * + * @param array $state MailCatcher state. + * + * @return Email + */ + public function set_mailer_state( $state ) { + + $this->mailer_state = wp_parse_args( + $state, + [ + 'CharSet' => '', + 'ContentType' => '', + 'Encoding' => '', + 'CustomHeader' => '', + 'Subject' => '', + 'Body' => '', + 'AltBody' => '', + 'ReplyTo' => '', + 'to' => '', + 'cc' => '', + 'bcc' => '', + 'attachment' => '', + ] + ); + + return $this; + } + + /** + * Get this email's WPMailInitiator state. + * + * @since 4.0.0 + * + * @return array + */ + public function get_initiator_state() { + + return $this->initiator_state; + } + + /** + * Set this email's WPMailInitiator state. + * + * @since 4.0.0 + * + * @param array $state MailCatcher state. + * + * @return Email + */ + public function set_initiator_state( $state ) { + + $this->initiator_state = wp_parse_args( + $state, + [ + 'file' => '', + 'line' => '', + 'backtrace' => '', + ] + ); + + return $this; + } + + /** + * Get the date and time this email + * was enqueued at. + * + * @since 4.0.0 + * + * @return DateTime + */ + public function get_date_enqueued() { + + return $this->date_enqueued; + } + + /** + * Set the date and time this email + * was enqueued at. + * + * @since 4.0.0 + * + * @param DateTime $datetime Date and time of enqueueing. + * + * @return Email + */ + public function set_date_enqueued( $datetime ) { + + $this->date_enqueued = $this->get_datetime( $datetime ); + + return $this; + } + + /** + * Get the date and time this email + * was processed at. + * + * @since 4.0.0 + * + * @return DateTime + */ + public function get_date_processed() { + + return $this->date_processed; + } + + /** + * Set the date and time this email + * was processed at. + * + * @since 4.0.0 + * + * @param DateTime $datetime Date and time of processing. + * + * @return Email + */ + public function set_date_processed( $datetime ) { + + $this->date_processed = $this->get_datetime( $datetime ); + + return $this; + } + + /** + * Convert a database string to a DateTime + * object, if necessary. + * + * @since 4.0.0 + * + * @param string $datetime Date and time. + * + * @return DateTime + */ + private function get_datetime( $datetime ) { + + if ( ! is_a( $datetime, DateTime::class ) ) { + // Validate the date. Time is ignored. + $mm = substr( $datetime, 5, 2 ); + $jj = substr( $datetime, 8, 2 ); + $aa = substr( $datetime, 0, 4 ); + + $valid_date = wp_checkdate( $mm, $jj, $aa, $datetime ); + $timezone = new DateTimeZone( 'UTC' ); + + if ( $valid_date ) { + $datetime = DateTime::createFromFormat( WP::datetime_mysql_format(), $datetime, $timezone ); + } else { + $datetime = new DateTime( 'now', $timezone ); + } + } + + return $datetime; + } + + /** + * Erase any potentially sensitive data. + * + * @since 4.0.0 + * + * @return @return Email + */ + public function anonymize() { + + $this->initiator_state = null; + $this->wp_mail_args = null; + $this->connection_data = null; + $this->mailer_state = null; + + return $this; + } + + /** + * Save a new or modified email in DB. + * + * @since 4.0.0 + * + * @throws Exception If data can't be encoded, + * or a database error occurred. + * + * @return int New or updated email ID. + */ + public function save() { + + global $wpdb; + + $table = Queue::get_table_name(); + $data = [ + 'initiator_state' => $this->initiator_state, + 'wp_mail_args' => $this->wp_mail_args, + 'connection_data' => $this->connection_data, + 'mailer_state' => $this->mailer_state, + ]; + + $data = array_filter( $data ); + + if ( ! empty( $data ) ) { + $data = wp_json_encode( + [ + 'initiator_state' => $this->initiator_state, + 'wp_mail_args' => $this->wp_mail_args, + 'connection_data' => $this->connection_data, + 'mailer_state' => $this->mailer_state, + ] + ); + + if ( $data === false ) { + throw new Exception( + sprintf( + /* translators: %1$s - JSON error message. */ + esc_html__( 'Data JSON encoding error: %1$s', 'wp-mail-smtp' ), + esc_html( json_last_error_msg() ) + ) + ); + } + } else { + $data = null; + } + + if ( (bool) $this->get_id() ) { + // Update the existing DB table record. + $result = $wpdb->update( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching + $table, + [ + 'data' => $data, + 'status' => $this->status, + 'date_processed' => $this->get_date_processed()->format( WP::datetime_mysql_format() ), + ], + [ + 'id' => $this->get_id(), + ], + [ + '%s', // data. + '%s', // status. + '%s', // date_processed. + ], + [ + '%d', + ] + ); + + $email_id = $this->get_id(); + } else { + // Create a new DB table record. + $result = $wpdb->insert( + $table, + [ + 'data' => $data, + 'status' => $this->status, + 'date_enqueued' => $this->get_date_enqueued()->format( WP::datetime_mysql_format() ), + ], + [ + '%s', // data. + '%s', // status. + '%s', // date_enqueued. + ] + ); + + $email_id = $wpdb->insert_id; + } + + if ( $result === false ) { + throw new Exception( + sprintf( + /* translators: %1$s - Database error message. */ + esc_html__( 'Insert/update SQL query error: %1$s', 'wp-mail-smtp' ), + esc_html( $wpdb->last_error ) + ) + ); + } + + return (int) $email_id; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Queue/Migration.php b/web/app/plugins/wp-mail-smtp-pro/src/Queue/Migration.php new file mode 100644 index 00000000..ac9000e6 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Queue/Migration.php @@ -0,0 +1,87 @@ +get_queue()->is_enabled(); + } + + /** + * Initial migration - create the table structure. + * + * @since 4.0.0 + */ + protected function migrate_to_1() { + + global $wpdb; + + $table = Queue::get_table_name(); + $collate = ! empty( $wpdb->collate ) ? "COLLATE='{$wpdb->collate}'" : ''; + + /* + * Create the table. + */ + $sql = " + CREATE TABLE `$table` ( + `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `data` LONGTEXT NULL, + `status` TINYINT UNSIGNED NOT NULL DEFAULT '0', + `date_enqueued` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + `date_processed` TIMESTAMP NULL, + PRIMARY KEY (id), + INDEX status (status), + INDEX date_processed (date_processed) + ) + ENGINE='InnoDB' + {$collate};"; + + $result = $wpdb->query( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching + + if ( ! empty( $wpdb->last_error ) ) { + update_option( self::ERROR_OPTION_NAME, $wpdb->last_error, false ); + } + + // Save the current version to DB. + if ( $result !== false ) { + $this->update_db_ver( 1 ); + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Queue/Queue.php b/web/app/plugins/wp-mail-smtp-pro/src/Queue/Queue.php new file mode 100644 index 00000000..388cdf64 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Queue/Queue.php @@ -0,0 +1,763 @@ +is_valid_db() ) { + return false; + } + + global $phpmailer; + + $wp_mail_args = wp_mail_smtp()->get_processor()->get_filtered_wp_mail_args(); + $initiator = wp_mail_smtp()->get_wp_mail_initiator(); + $processor = wp_mail_smtp()->get_processor(); + $initiator_state = [ + 'file' => $initiator->get_file(), + 'line' => $initiator->get_line(), + 'backtrace' => $initiator->get_backtrace(), + ]; + $connection_data = [ + 'from_email' => $processor->get_filtered_from_email(), + 'from_name' => $processor->get_filtered_from_name(), + ]; + + // Keep a reference to the original attachments, + // if something goes wrong while enqueueing the email. + $original_attachments = $phpmailer->getAttachments(); + + // Obfuscate attachment paths for the enqueued email. + $processed_attachments = ( new Attachments() )->process_attachments( $original_attachments ); + + // Set obfuscated path attachments. + $this->set_attachments( $processed_attachments ); + + // Add queued date header in the same format as "Date" header. + $phpmailer->addCustomHeader( 'X-WP-Mail-SMTP-Queued', $phpmailer::rfcDate() ); + + $email = ( new Email() ) + ->set_wp_mail_args( $wp_mail_args ) + ->set_initiator_state( $initiator_state ) + ->set_connection_data( $connection_data ) + ->set_mailer_state( $phpmailer->get_state() ); + + // Add the email to the queue. + try { + $this->add_email( $email ); + } catch ( Exception $e ) { + // Cleanup any obfuscated path attachments. + $this->cleanup_attachments(); + + // Reset original attachments. + $this->set_attachments( $original_attachments ); + + $message = sprintf( + /* translators: %1$s - exception message. */ + esc_html__( '[Emails Queue] Skipped enqueueing email. %1$s.', 'wp-mail-smtp' ), + esc_html( $e->getMessage() ) + ); + + DebugEvents::add_debug( $message ); + + return false; + } + + return true; + } + + /** + * Send an email. Can only be called + * by a running SendEnqueuedEmailTask. + * + * @since 4.0.0 + * + * @param int|string $email_id Email's ID. + */ + public function send_email( $email_id ) { // phpcs:ignore WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks + + // This method can't be called directly. + if ( ! doing_action( SendEnqueuedEmailTask::ACTION ) ) { + $message = sprintf( + /* translators: %1$d - email ID. */ + esc_html__( '[Emails Queue] Skipped email sending from the queue. Queue::send_email method was called directly. Email ID: %1$d.', 'wp-mail-smtp' ), + $email_id + ); + + DebugEvents::add_debug( $message ); + + return; + } + + try { + $email = $this->get_email( $email_id ); + } catch ( Exception $e ) { + $this->delete_email( $email_id ); + + $message = sprintf( + /* translators: %1$s - exception message; %2$s - email ID. */ + esc_html__( '[Emails Queue] Skipped email sending from the queue. %1$s. Email ID: %2$s', 'wp-mail-smtp' ), + esc_html( $e->getMessage() ), + $email_id + ); + + DebugEvents::add_debug( $message ); + + return; + } + + // Bail early if the email still enqueued, or already processed. + if ( $email->get_status() !== Email::STATUS_PROCESSING ) { + $message = sprintf( + /* translators: %1$d - email ID; %2$s - email status. */ + esc_html__( '[Emails Queue] Skipped email sending from the queue. Wrong email status. Email ID: %1$d, email status: %2$s.', 'wp-mail-smtp' ), + $email_id, + $email->get_status() + ); + + DebugEvents::add_debug( $message ); + + return; + } + + // Keep a reference to the email + // being sent so that it's accessible + // across hooks. + $this->email = $email; + + // Un-hook all user-defined hooks. + $this->clear_wp_mail_hooks(); + + // Stop enqueueing emails. + add_filter( 'wp_mail_smtp_mail_catcher_send_enqueue_email', '__return_false', PHP_INT_MAX ); + + // Re-hook Processor functionality, before applying PHPMailer state, + // so that From and From Name are correctly filtered. + wp_mail_smtp()->get_processor()->hooks(); + + // Apply the email's PHPMailer state. + add_action( 'phpmailer_init', [ $this, 'apply_mailer_state' ], PHP_INT_MAX ); + + // Retrieve original wp_mail arguments. + $wp_mail_args = new WPMailArgs( $email->get_wp_mail_args() ); + + // Inject user-filtered From and From Name. + $wp_mail_headers = $wp_mail_args->get_headers(); + $wp_mail_headers[] = $this->get_connection_from_header( $email->get_connection_data() ); + + // Inject the original initiator state. + add_filter( 'wp_mail_smtp_wp_mail_initiator_set_initiator', [ $this, 'apply_initiator_state' ] ); + + // Send the email. + wp_mail( + $wp_mail_args->get_to_email(), + $wp_mail_args->get_subject(), + $wp_mail_args->get_message(), + $wp_mail_headers, + $wp_mail_args->get_attachments() + ); + + // Update the email. + try { + $this->email->set_status( Email::STATUS_PROCESSED ) + ->set_date_processed( new DateTime( 'now', new DateTimeZone( 'UTC' ) ) ) + ->anonymize() + ->save(); + } catch ( Exception $e ) { + $this->delete_email( $email_id ); + + $message = sprintf( + /* translators: %1$s - exception message; %2$d - email ID. */ + esc_html__( '[Emails Queue] Failed to update queue record after sending email from the queue. %1$s. Email ID: %2$d', 'wp-mail-smtp' ), + esc_html( $e->getMessage() ), + $email_id + ); + + DebugEvents::add_debug( $message ); + } + + // Cleanup any attachments. + $this->cleanup_attachments(); + + // Stop injecting the original initiator state. + remove_filter( 'wp_mail_smtp_wp_mail_initiator_set_initiator', [ $this, 'apply_initiator_state' ] ); + + // Stop applying PHPMailer state. + remove_action( 'phpmailer_init', [ $this, 'apply_mailer_state' ], PHP_INT_MAX ); + + // Clear the email reference. + $this->email = null; + + // Re-hook all user-defined hooks. + $this->restore_wp_mail_hooks(); + + // Start enqueueing emails again. + remove_filter( 'wp_mail_smtp_mail_catcher_send_enqueue_email', '__return_false', PHP_INT_MAX ); + } + + /** + * Return the current email's WPMailInitiator state. + * + * @since 4.0.0 + * + * @return array WPMailInitiator state. + */ + public function apply_initiator_state() { + + return $this->email->get_initiator_state(); + } + + /** + * Apply state to the current mailer. + * + * @since 4.0.0 + * + * @param PHPMailer $phpmailer PHPMailer instance. + */ + public function apply_mailer_state( &$phpmailer ) { + + $phpmailer->set_state( $this->email->get_mailer_state() ); + } + + /** + * Get the table name. + * + * @since 4.0.0 + * + * @return string Table name, prefixed. + */ + public static function get_table_name() { + + global $wpdb; + + return $wpdb->prefix . 'wpmailsmtp_emails_queue'; + } + + /** + * Count processing or processed emails since a given date. + * + * @since 4.0.0 + * + * @param null|DateTime $since_datetime Date to count from, or null for all emails. + * + * @return int Email count. + */ + public function count_processed_emails( ?DateTime $since_datetime = null ) { + + if ( ! $this->is_valid_db() ) { + return 0; + } + + global $wpdb; + + $table = self::get_table_name(); + $where = $wpdb->prepare( + 'status IN (%d, %d)', + Email::STATUS_PROCESSING, + Email::STATUS_PROCESSED + ); + + if ( ! is_null( $since_datetime ) ) { + $where .= $wpdb->prepare( + ' AND date_processed >= %s', + $since_datetime->format( WP::datetime_mysql_format() ) + ); + } + + // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared + $count = $wpdb->get_var( + "SELECT COUNT(*) + FROM $table + WHERE $where;" + ); + // phpcs:enable WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared + + return (int) $count; + } + + /** + * Count queued emails. + * + * @since 4.0.0 + * + * @return int Email count. + */ + public function count_queued_emails() { + + if ( ! $this->is_valid_db() ) { + return 0; + } + + global $wpdb; + + $table = self::get_table_name(); + $where = $wpdb->prepare( 'status = %d', Email::STATUS_QUEUED ); + // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared + $count = $wpdb->get_var( + "SELECT COUNT(*) + FROM $table + WHERE $where;" + ); + // phpcs:enable WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared + + return (int) $count; + } + + /** + * Schedule emails for sending. + * + * @since 4.0.0 + */ + public function process() { // phpcs:ignore WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks + + if ( ! $this->is_valid_db() ) { + return; + } + + /** + * Filters the amount of emails the queue should process. + * + * @since 4.0.0 + * + * @param int|null $count Amount of emails to process. + */ + $count = apply_filters( 'wp_mail_smtp_queue_process_count', null ); + + // If the queue has been disabled, just process all emails. + if ( ! $this->is_enabled() ) { + $count = null; + } + + $emails = $this->get_emails( $count ); + $task = new SendEnqueuedEmailTask(); + + foreach ( $emails as $email ) { + try { + $email->set_status( Email::STATUS_PROCESSING ) + ->set_date_processed( new DateTime( 'now', new DateTimeZone( 'UTC' ) ) ) + ->save(); + } catch ( Exception $e ) { + $this->delete_email( $email->get_id() ); + + $message = sprintf( + /* translators: %1$s - exception message. */ + esc_html__( '[Emails Queue] Skipped processing enqueued email. %1$s. Email ID: %2$d', 'wp-mail-smtp' ), + esc_html( $e->getMessage() ), + $email->get_id() + ); + + DebugEvents::add_debug( $message ); + + continue; + } + + $task->schedule( $email->get_id() ); + } + } + + /** + * Cleanup emails processed before a given date. + * + * @since 4.0.0 + */ + public function cleanup() { // phpcs:ignore WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks + + /** + * Filters the date before which emails should + * be removed from the queue. + * + * @since 4.0.0 + * + * @param DateTime|null $datetime Date before which to remove emails. + */ + $datetime = apply_filters( 'wp_mail_smtp_queue_cleanup_before_datetime', null ); + + // If the queue has been disabled, just cleanup all emails. + if ( ! $this->is_enabled() ) { + $datetime = null; + } + + $this->delete_emails_before( $datetime ); + } + + /** + * Whether the DB table exists. + * + * @since 4.0.0 + * + * @return bool + */ + public function is_valid_db() { + + global $wpdb; + + static $is_valid = null; + + // Return cached value only if table already exists. + if ( $is_valid === true ) { + return true; + } + + $table = self::get_table_name(); + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching + $is_valid = (bool) $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s;', $table ) ); + + return $is_valid; + } + + /** + * Set current email's attachments. + * + * @since 4.0.0 + * + * @param array $attachments List of attachments. + */ + private function set_attachments( $attachments ) { + + global $phpmailer; + + $phpmailer->clearAttachments(); + + foreach ( $attachments as $attachment ) { + [ $path, , $name, $encoding, $type, , $disposition ] = $attachment; + + try { + $phpmailer->addAttachment( $path, $name, $encoding, $type, $disposition ); + } catch ( Exception $e ) { + continue; + } + } + } + + /** + * Remove email attachments after sending. + * + * @since 4.0.0 + */ + private function cleanup_attachments() { + + global $phpmailer; + + $attachments = $phpmailer->getAttachments(); + + ( new Attachments() )->delete_attachments( $attachments ); + } + + /** + * Get the From/From Name header + * from an email's connection data. + * + * @since 4.0.0 + * + * @param array $connection_data Email's connection data. + */ + private function get_connection_from_header( $connection_data ) { + + [ + 'from_email' => $from_email, + 'from_name' => $from_name + ] = $connection_data; + + $from = ( + $from_name === '' ? + $from_email : + sprintf( '%1s <%2s>', $from_name, $from_email ) + ); + + $from_header = sprintf( + 'From:%s', + $from + ); + + return $from_header; + } + + /** + * Return a list of the `wp_mail` related hooks + * that should be de-registered before sending + * an enqueued email. + * + * @since 4.0.0 + * + * @return array List of hooks. + */ + private function get_wp_mail_hooks() { + + return [ + 'wp_mail', + 'pre_wp_mail', + 'wp_mail_from', + 'wp_mail_from_name', + 'wp_mail_succeeded', + 'wp_mail_failed', + ]; + } + + /** + * Clear any user-defined `wp_mail` related hooks + * before sending an enqueued email. + * + * @since 4.0.0 + */ + private function clear_wp_mail_hooks() { + + global $wp_filter; + + $wp_mail_hooks = array_intersect_key( + $wp_filter, + array_flip( $this->get_wp_mail_hooks() ) + ); + + foreach ( $wp_mail_hooks as $hook_name => $hook ) { + foreach ( $hook->callbacks as $priority => $callbacks ) { + foreach ( $callbacks as $callback ) { + $this->registered_wp_mail_hooks[] = [ + $hook_name, + $callback['function'], + $priority, + $callback['accepted_args'], + ]; + } + } + + remove_all_filters( $hook_name ); + } + } + + /** + * Re-register any previous de-registered `wp_mail` related hooks + * after sending an enqueued email. + * + * @since 4.0.0 + */ + private function restore_wp_mail_hooks() { // phpcs:ignore WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks + + foreach ( $this->registered_wp_mail_hooks as $hook ) { + list( $hook_name, $callback, $priority, $accepted_args ) = $hook; + + add_filter( $hook_name, $callback, $priority, $accepted_args ); + } + } + + /** + * Add an email to the queue. + * + * @since 4.0.0 + * + * @throws Exception When email couldn't be saved. + * + * @param Email $email The email to enqueue. + */ + private function add_email( Email $email ) { + + if ( ! $this->is_valid_db() ) { + return; + } + + $email->set_date_enqueued( new DateTime( 'now', new DateTimeZone( 'UTC' ) ) ) + ->set_status( Email::STATUS_QUEUED ) + ->save(); + } + + /** + * Get an email. + * + * @since 4.0.0 + * + * @param int|string $email_id The email's ID. + * + * @return null|Email The email, or null if not found. + */ + private function get_email( $email_id ) { + + if ( ! $this->is_valid_db() ) { + return null; + } + + global $wpdb; + + $table = self::get_table_name(); + $where = $wpdb->prepare( 'ID = %d', (int) $email_id ); + // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared + $data = $wpdb->get_row( "SELECT * FROM $table WHERE $where" ); + // phpcs:enable WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared + + $email = Email::from_data( $data ); + + return $email; + } + + /** + * Get queued emails from the queue. + * + * @since 4.0.0 + * + * @param null|int $count Amount of emails to return, or null for all emails. + * + * @return Email[] Array of emails. + */ + private function get_emails( $count = null ) { + + if ( ! $this->is_valid_db() ) { + return []; + } + + global $wpdb; + + $table = self::get_table_name(); + $where = $wpdb->prepare( 'status = %d', Email::STATUS_QUEUED ); + $limit = ''; + + if ( ! is_null( $count ) ) { + $limit = $wpdb->prepare( + 'LIMIT 0, %d', + max( 0, intval( $count ) ) + ); + } + + // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared + $data = $wpdb->get_results( + "SELECT * + FROM $table + WHERE $where + ORDER BY date_enqueued ASC + $limit;" + ); + // phpcs:enable WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared + + $emails = []; + + foreach ( $data as $row ) { + try { + $emails[] = Email::from_data( $row ); + } catch ( Exception $e ) { + $this->delete_email( $row->id ); + + $message = sprintf( + /* translators: %1$s - exception message. */ + esc_html__( '[Emails Queue] Skipped processing enqueued email. %1$s. Email ID: %2$d', 'wp-mail-smtp' ), + esc_html( $e->getMessage() ), + $row->id + ); + + DebugEvents::add_debug( $message ); + } + } + + return $emails; + } + + /** + * Delete emails processed before a given date. + * + * @since 4.0.0 + * + * @param DateTime|null $before_datetime Date before which to remove emails, or null for all emails. + */ + private function delete_emails_before( $before_datetime ) { + + if ( ! $this->is_valid_db() ) { + return; + } + + global $wpdb; + + $table = self::get_table_name(); + $where = $wpdb->prepare( 'status = %d', Email::STATUS_PROCESSED ); + + if ( is_a( $before_datetime, DateTime::class ) ) { + $where .= $wpdb->prepare( + ' AND date_processed < %s', + $before_datetime->format( WP::datetime_mysql_format() ) + ); + } + + // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared + $wpdb->query( "DELETE FROM $table WHERE $where" ); + // phpcs:enable WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared + } + + /** + * Delete an email. + * + * @since 4.0.0 + * + * @param int $email_id ID of the email. + */ + private function delete_email( $email_id ) { + + if ( ! $this->is_valid_db() ) { + return; + } + + global $wpdb; + + $table = self::get_table_name(); + + // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared + $wpdb->query( + $wpdb->prepare( "DELETE FROM $table WHERE ID = %d", $email_id ) + ); + // phpcs:enable WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Reports/Emails/Summary.php b/web/app/plugins/wp-mail-smtp-pro/src/Reports/Emails/Summary.php new file mode 100644 index 00000000..79932690 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Reports/Emails/Summary.php @@ -0,0 +1,433 @@ +get( 'general', self::SETTINGS_SLUG ); + + // If option was not already set, then plugin was updated from lower version. + if ( + ( $value === '' || $value === null ) && + ( is_multisite() || ! wp_mail_smtp()->is_pro() ) + ) { + $value = true; + } + + /** + * Filters whether summary report email is disabled. + * + * @since 3.0.0 + * + * @param bool $is_disabled + */ + $value = apply_filters( 'wp_mail_smtp_reports_emails_summary_is_disabled', $value ); + + return (bool) $value; + } + + /** + * Get summary report email preview link. + * + * @since 3.0.0 + * + * @return string Preview link. + */ + public static function get_preview_link() { + + return add_query_arg( + [ 'mode' => 'summary_report_email_preview' ], + wp_mail_smtp()->get_admin()->get_admin_page_url() + ); + } + + /** + * Send summary report email. + * + * @since 3.0.0 + */ + public function send() { + + if ( $this->is_disabled() ) { + return; + } + + $parsed_home_url = wp_parse_url( home_url() ); + $site_domain = $parsed_home_url['host']; + + if ( is_multisite() && isset( $parsed_home_url['path'] ) ) { + $site_domain .= $parsed_home_url['path']; + } + + $subject = sprintf( /* translators: %s - site domain. */ + esc_html__( 'Your Weekly WP Mail SMTP Summary for %s', 'wp-mail-smtp' ), + $site_domain + ); + + /** + * Filters the summaries email subject. + * + * @since 3.0.0 + * + * @param string $subject Email subject. + */ + $subject = apply_filters( 'wp_mail_smtp_reports_emails_summary_send_subject', $subject ); + + /** + * Filters the summaries recipient email address. + * + * @since 3.0.0 + * + * @param string $email Recipient email address. + */ + $to_email = apply_filters( 'wp_mail_smtp_reports_emails_summary_send_to', get_option( 'admin_email' ) ); + + add_filter( 'wp_mail_content_type', [ $this, 'set_html_content_type' ] ); + + wp_mail( $to_email, $subject, $this->get_content() ); + + remove_filter( 'wp_mail_content_type', [ $this, 'set_html_content_type' ] ); + } + + /** + * Get summary report email content. + * + * @since 3.0.0 + * + * @return string + */ + public function get_content() { + + $content = $this->get_header_html(); + $content .= $this->get_main_html(); + $content .= $this->get_footer_html(); + + return $content; + } + + /** + * Get summary report email header HTML. + * + * @since 3.0.0 + * + * @return string + */ + private function get_header_html() { + + ob_start(); + ?> + + +
+ + + + + + + +
+ + +
+